Improve WeChat MP replies and ship MindSpace/H5 production updates.

Add WeChat service account routing with sync acks, connectivity tests, and context isolation; document deploy runbooks; and bundle related MindSpace, voice, Plaza, and server gateway changes for production rollout.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-06-19 23:06:43 +08:00
parent b0f5d6a51c
commit 229805a070
241 changed files with 13190 additions and 902 deletions
+107 -21
View File
@@ -64,11 +64,51 @@ function extractSessionId(req, body) {
return null;
}
export function createTkmindProxy({ apiTarget, apiSecret, userAuth, llmProviderService }) {
export function createTkmindProxy({ apiTarget, apiTargets, apiSecret, userAuth, llmProviderService }) {
const targets = apiTargets?.length ? apiTargets : apiTarget ? [apiTarget] : [];
const primaryTarget = targets[0] ?? apiTarget ?? '';
let rrIdx = 0;
async function targetHealthy(target) {
try {
const upstream = await apiFetch(target, apiSecret, '/status', {
method: 'GET',
signal: AbortSignal.timeout(1500),
});
return upstream.ok;
} catch {
return false;
}
}
async function pickTarget() {
if (targets.length <= 1) return primaryTarget;
for (let i = 0; i < targets.length; i += 1) {
const target = targets[rrIdx];
rrIdx = (rrIdx + 1) % targets.length;
if (await targetHealthy(target)) return target;
}
return primaryTarget;
}
async function resolveTarget(sessionId) {
if (targets.length <= 1 || !sessionId) return primaryTarget;
try {
const node = await userAuth.getSessionNode(sessionId);
return targets[node] ?? primaryTarget;
} catch {
return primaryTarget;
}
}
async function applySessionLlmProvider(sessionId) {
if (!llmProviderService || !sessionId) return null;
try {
return await llmProviderService.applyBestProviderForSession(sessionId);
const target = await resolveTarget(sessionId);
return await llmProviderService.applyBestProviderForSession(
sessionId,
(url, init) => apiFetch(target, apiSecret, `${url.pathname}${url.search}`, init),
);
} catch (err) {
console.warn(
'LLM provider apply skipped:',
@@ -78,6 +118,15 @@ export function createTkmindProxy({ apiTarget, apiSecret, userAuth, llmProviderS
}
}
async function applyLocalFallbackForSession(sessionId) {
if (!llmProviderService || !sessionId) return null;
const target = await resolveTarget(sessionId);
return llmProviderService.applyLocalFallbackForSession(
sessionId,
(url, init) => apiFetch(target, apiSecret, `${url.pathname}${url.search}`, init),
);
}
const requireUser = async (req, res, next) => {
try {
const session = req.userSession;
@@ -120,7 +169,8 @@ export function createTkmindProxy({ apiTarget, apiSecret, userAuth, llmProviderS
try {
const workingDir = await userAuth.resolveWorkingDir(req.currentUser.id);
const sessionPolicy = await userAuth.getAgentSessionPolicy(req.currentUser.id);
const upstream = await apiFetch(apiTarget, apiSecret, '/agent/start', {
const startTarget = await pickTarget();
const upstream = await apiFetch(startTarget, apiSecret, '/agent/start', {
method: 'POST',
body: JSON.stringify({
working_dir: workingDir,
@@ -138,9 +188,13 @@ export function createTkmindProxy({ apiTarget, apiSecret, userAuth, llmProviderS
}
const session = JSON.parse(text);
if (session?.id) {
await userAuth.registerAgentSession(req.currentUser.id, session.id);
await userAuth.registerAgentSession(
req.currentUser.id,
session.id,
Math.max(0, targets.indexOf(startTarget)),
);
if (sessionPolicy.gooseMode) {
const modeRes = await apiFetch(apiTarget, apiSecret, '/agent/update_session', {
const modeRes = await apiFetch(startTarget, apiSecret, '/agent/update_session', {
method: 'POST',
body: JSON.stringify({
session_id: session.id,
@@ -154,7 +208,7 @@ export function createTkmindProxy({ apiTarget, apiSecret, userAuth, llmProviderS
}
}
const publishLayout = await userAuth.getUserPublishLayout(req.currentUser.id);
const api = (pathname, init) => apiFetch(apiTarget, apiSecret, pathname, init);
const api = (pathname, init) => apiFetch(startTarget, apiSecret, pathname, init);
try {
await reconcileAgentSession(api, session.id, {
workingDir,
@@ -203,7 +257,8 @@ export function createTkmindProxy({ apiTarget, apiSecret, userAuth, llmProviderS
return;
}
const upstream = await apiFetch(apiTarget, apiSecret, '/agent/resume', {
const resumeTarget = await resolveTarget(sessionId);
const upstream = await apiFetch(resumeTarget, apiSecret, '/agent/resume', {
method: 'POST',
body: JSON.stringify(req.body ?? {}),
});
@@ -221,12 +276,13 @@ export function createTkmindProxy({ apiTarget, apiSecret, userAuth, llmProviderS
const publishLayout = await userAuth.getUserPublishLayout(req.currentUser.id);
try {
await reconcileAgentSession(
(pathname, init) => apiFetch(apiTarget, apiSecret, pathname, init),
(pathname, init) => apiFetch(resumeTarget, apiSecret, pathname, init),
sessionId,
{
workingDir,
sessionPolicy,
sandboxConstraints: publishLayout?.constraints ?? null,
tolerateInvalidWorkingDir: true,
userContext: publishLayout
? {
userId: req.currentUser.id,
@@ -261,18 +317,40 @@ export function createTkmindProxy({ apiTarget, apiSecret, userAuth, llmProviderS
requireUser,
async (req, res) => {
try {
const upstream = await apiFetch(apiTarget, apiSecret, '/sessions', {
method: 'GET',
});
const text = await upstream.text();
if (!upstream.ok) {
res.status(upstream.status).send(text);
const owned = await userAuth.listOwnedSessionIds(req.currentUser.id);
const sessionsById = new Map();
let healthyTargets = 0;
let lastFailure = null;
for (const target of targets) {
try {
const upstream = await apiFetch(target, apiSecret, '/sessions', {
method: 'GET',
signal: AbortSignal.timeout(3000),
});
const text = await upstream.text();
if (!upstream.ok) {
lastFailure = text || `upstream ${upstream.status}`;
continue;
}
healthyTargets += 1;
const payload = JSON.parse(text);
for (const item of payload.sessions ?? []) {
if (owned.has(item.id)) sessionsById.set(item.id, item);
}
} catch (err) {
lastFailure = err instanceof Error ? err.message : '读取会话失败';
}
}
if (healthyTargets === 0) {
res.status(502).json({ message: lastFailure ?? '所有 goose 服务不可用' });
return;
}
const payload = JSON.parse(text);
const owned = await userAuth.listOwnedSessionIds(req.currentUser.id);
const sessions = (payload.sessions ?? []).filter((item) => owned.has(item.id));
res.json({ sessions });
if (healthyTargets < targets.length) {
res.setHeader('X-TKMind-Degraded', '1');
}
res.json({ sessions: [...sessionsById.values()] });
} catch (err) {
res.status(500).json({ message: err instanceof Error ? err.message : '读取会话失败' });
}
@@ -307,7 +385,8 @@ export function createTkmindProxy({ apiTarget, apiSecret, userAuth, llmProviderS
const proxySessionEvents = async (req, res, sessionId) => {
try {
const pathname = `/sessions/${sessionId}/events`;
const upstream = await apiFetch(apiTarget, apiSecret, pathname, {
const sessionTarget = await resolveTarget(sessionId);
const upstream = await apiFetch(sessionTarget, apiSecret, pathname, {
method: 'GET',
headers: {
Accept: 'text/event-stream',
@@ -400,7 +479,11 @@ export function createTkmindProxy({ apiTarget, apiSecret, userAuth, llmProviderS
? JSON.stringify(req.body)
: undefined;
const upstream = await apiFetch(apiTarget, apiSecret, pathname, {
const sessionMatch = pathname.match(/^\/sessions\/([^/]+)/);
const fallbackTarget =
req.goosedTarget ??
(sessionMatch ? await resolveTarget(sessionMatch[1]) : primaryTarget);
const upstream = await apiFetch(fallbackTarget, apiSecret, pathname, {
method: req.method,
body,
headers: {
@@ -419,11 +502,14 @@ export function createTkmindProxy({ apiTarget, apiSecret, userAuth, llmProviderS
requireUser,
ensureChatAllowed,
applySessionLlmProvider,
applyLocalFallbackForSession,
handlers,
sessionScoped,
proxyFallback,
proxySessionEvents,
apiFetch: (pathname, init) => apiFetch(apiTarget, apiSecret, pathname, init),
resolveTarget,
apiFetch: async (pathname, init) => apiFetch(await pickTarget(), apiSecret, pathname, init),
apiFetchTo: (target, pathname, init) => apiFetch(target, apiSecret, pathname, init),
};
}