feat(goose): add local v1.49 canary routing, smoke gates, and migration docs

Wire Portal and TKMind proxy to loopback Goose v1.49 via canary env blocks,
with verification scripts, Phase 2/3 evidence baselines, and rollback runbooks
so local upgrade stays isolated from stable 1.41 and production.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-09-08 21:10:20 +08:00
parent d2d463b1db
commit 716ef407fe
68 changed files with 48162 additions and 2 deletions
+32 -2
View File
@@ -23,6 +23,10 @@ import { reconcileAgentSession } from './session-reconcile.mjs';
import { consumeSessionEventsUntilFinish } from './session-reply-wait.mjs';
import { createSessionAccess, isSessionBrokerEnabled } from './session-broker.mjs';
import { createSessionBrokerMetrics, isSessionBrokerMetricsEnabled } from './session-broker-metrics.mjs';
import {
resolveGooseApiTargetsFromEnv,
resolveGooseTargetForIdentity,
} from './scripts/goose-v149-canary.mjs';
import { createImgproxySigner } from './imgproxy-signer.mjs';
import { isDirectChatSessionId } from './direct-chat-service.mjs';
import { ensureGooseUserMessageMetadata } from './goose-message.mjs';
@@ -1189,6 +1193,15 @@ export function createTkmindProxy({
: null;
const targets = apiTargets?.length ? apiTargets : apiTarget ? [apiTarget] : [];
const primaryTarget = targets[0] ?? apiTarget ?? '';
let gooseCanaryConfig = null;
try {
gooseCanaryConfig = resolveGooseApiTargetsFromEnv(process.env);
} catch (err) {
console.warn(
'[TKMindProxy] Goose v1.49 canary config ignored:',
err instanceof Error ? err.message : err,
);
}
let rrIdx = 0;
let imgproxySigner = null;
@@ -1369,6 +1382,23 @@ export function createTkmindProxy({
return fallbackPick();
}
async function pickTargetForUser(userId, user = null) {
if (gooseCanaryConfig?.mode === 'users' && userId) {
const target = resolveGooseTargetForIdentity(
{
id: String(userId),
username: user?.username ?? user?.name ?? '',
wechatUserId: user?.wechatUserId ?? user?.wechat_user_id ?? '',
},
gooseCanaryConfig,
);
if (target && (targets.includes(target) || target === gooseCanaryConfig.v149 || target === gooseCanaryConfig.stable)) {
if (await targetHealthy(target)) return target;
}
}
return pickTarget();
}
async function rememberSessionTarget(sessionId, target) {
if (!runtimeRouter || !sessionId || !target) return;
await runtimeRouter.registerSession(sessionId, target).catch(() => null);
@@ -1491,7 +1521,7 @@ export function createTkmindProxy({
const resolvedSessionPolicy = disableImageReading
? withoutSessionImageRead(baseSessionPolicy)
: baseSessionPolicy;
const startTarget = await pickTarget();
const startTarget = await pickTargetForUser(userId);
const upstream = await apiFetch(startTarget, apiSecret, '/agent/start', {
method: 'POST',
body: JSON.stringify({
@@ -2355,7 +2385,7 @@ export function createTkmindProxy({
try {
const workingDir = await userAuth.resolveWorkingDir(req.currentUser.id);
const sessionPolicy = await userAuth.getAgentSessionPolicy(req.currentUser.id);
const startTarget = await pickTarget();
const startTarget = await pickTargetForUser(req.currentUser.id, req.currentUser);
const upstream = await apiFetch(startTarget, apiSecret, '/agent/start', {
method: 'POST',
body: JSON.stringify({