From d782b7fa11a4e064a2497ac99477b76789069fb1 Mon Sep 17 00:00:00 2001 From: Huarch Date: Fri, 17 Jul 2026 16:33:11 +0800 Subject: [PATCH] fix(session): propagate network context --- cli/src/core/http.ts | 10 ++++++++-- package.json | 1 + src/auth/agentAuth.ts | 4 ++++ src/chat/sessionBridge.ts | 5 +++++ src/routes/chat.ts | 1 + src/runtime/sessionContext.ts | 1 + src/server.ts | 10 +++++++++- tests/runtime/sessionContext.test.ts | 2 ++ 8 files changed, 31 insertions(+), 3 deletions(-) diff --git a/cli/src/core/http.ts b/cli/src/core/http.ts index 05aaa48..8872764 100644 --- a/cli/src/core/http.ts +++ b/cli/src/core/http.ts @@ -35,12 +35,18 @@ function appendParams(url: URL, params: Record = {}): void { } } +function withNetworkParam(params: Record | undefined, network: string): Record { + if (params?.network !== undefined || params?.network_name !== undefined || params?.name !== undefined) return params ?? {}; + return { ...(params ?? {}), network }; +} + export async function requestJson(ctx: RuntimeContext, request: RequestOptions): Promise<[unknown, number]> { const { method, path, params, body, requireAuth = true, requireProject = false, requireNetworkCtx = false, requireUsernameCtx = false } = request; - if (requireNetworkCtx) requireNetwork(ctx); + const network = requireNetworkCtx ? requireNetwork(ctx) : null; if (requireUsernameCtx) requireUsername(ctx); const url = new URL(`/api/v1${path}`, ctx.server.replace(/\/+$/, "")); - appendParams(url, params); + const requestParams = network && (params !== undefined || body === undefined) ? withNetworkParam(params, network) : params; + appendParams(url, requestParams); const started = performance.now(); const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), ctx.timeout * 1000); diff --git a/package.json b/package.json index 1cde165..f75136d 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "dev": "bun --watch src/server.ts", "build": "bun run check", "check": "bun run typecheck && bun run typecheck:opencode", + "migrate:session-identity": "bun scripts/migrate-session-identity.ts", "pipeline:trigger": "bash scripts/trigger-gitea-pipeline.sh", "start": "bun src/server.ts", "start:prod": "bun run check && bun src/server.ts" diff --git a/src/auth/agentAuth.ts b/src/auth/agentAuth.ts index d53193d..791ebcd 100644 --- a/src/auth/agentAuth.ts +++ b/src/auth/agentAuth.ts @@ -11,6 +11,7 @@ export type AgentAuthContext = { role: string; isSuperuser: boolean; projectId: string; + network: string; projectRole: string; tokenExpiresAt?: string; }; @@ -22,6 +23,7 @@ type AgentAuthContextResponse = { role?: unknown; is_superuser?: unknown; project_id?: unknown; + network?: unknown; project_role?: unknown; token_expires_at?: unknown; }; @@ -53,6 +55,7 @@ const normalizeAgentAuthContext = ( typeof payload.role !== "string" || typeof payload.is_superuser !== "boolean" || typeof payload.project_id !== "string" || + typeof payload.network !== "string" || typeof payload.project_role !== "string" ) { return null; @@ -65,6 +68,7 @@ const normalizeAgentAuthContext = ( role: payload.role, isSuperuser: payload.is_superuser, projectId: payload.project_id, + network: payload.network, projectRole: payload.project_role, tokenExpiresAt: typeof payload.token_expires_at === "string" diff --git a/src/chat/sessionBridge.ts b/src/chat/sessionBridge.ts index 92bed04..dfdbe2a 100644 --- a/src/chat/sessionBridge.ts +++ b/src/chat/sessionBridge.ts @@ -17,6 +17,7 @@ export type SessionBinding = { export type SessionContext = { clientSessionId: string; accessToken?: string; + network?: string; projectId?: string; tokenExpiresAt?: string; userId?: string; @@ -36,6 +37,7 @@ export class ChatSessionBridge { async resolve(context: { sessionId?: string; accessToken?: string; + network?: string; projectId?: string; traceId?: string; tokenExpiresAt?: string; @@ -71,6 +73,7 @@ export class ChatSessionBridge { allowLearningWrite: true, clientSessionId: requestContext.clientSessionId, learningMode: "interactive", + network: requestContext.network, projectId: requestContext.projectId, projectKey: requestContext.projectKey, sessionId, @@ -144,6 +147,7 @@ export class ChatSessionBridge { private buildRequestContext(context: { sessionId?: string; accessToken?: string; + network?: string; projectId?: string; traceId?: string; tokenExpiresAt?: string; @@ -154,6 +158,7 @@ export class ChatSessionBridge { clientSessionId: sessionId || this.createClientSessionId(), accessToken: context.accessToken, actorKey: toActorKey(context.userId), + network: context.network, projectId: context.projectId, projectKey: toProjectKey(context.projectId), tokenExpiresAt: context.tokenExpiresAt, diff --git a/src/routes/chat.ts b/src/routes/chat.ts index a6ddeb9..946deca 100644 --- a/src/routes/chat.ts +++ b/src/routes/chat.ts @@ -525,6 +525,7 @@ export const buildChatRouter = ( const { binding, requestContext, created } = await sessionBridge.resolve({ sessionId: requestedSessionId, accessToken, + network: authContext.network, projectId, traceId, tokenExpiresAt: authContext.tokenExpiresAt, diff --git a/src/runtime/sessionContext.ts b/src/runtime/sessionContext.ts index 97cbd88..b3b1d30 100644 --- a/src/runtime/sessionContext.ts +++ b/src/runtime/sessionContext.ts @@ -9,6 +9,7 @@ export type RuntimeSessionContext = { clientSessionId: string; learningMode?: "interactive" | "review"; memoryListReadScopes?: Partial>; + network?: string; projectId?: string; projectKey: string; sessionId: string; diff --git a/src/server.ts b/src/server.ts index 8827c8d..35f4d1a 100644 --- a/src/server.ts +++ b/src/server.ts @@ -101,11 +101,19 @@ app.post("/internal/tools/tjwater-cli-call", async (req, res) => { const timeoutSec = typeof req.body?.timeout === "number" && req.body.timeout > 0 ? req.body.timeout : 120; + if (!context.network) { + res.status(400).json({ + message: "runtime network missing; refresh chat context", + detail: sessionId, + }); + return; + } + const authJson = JSON.stringify({ server: config.TJWATER_API_BASE_URL, access_token: context.accessToken, project_id: context.projectId, - network:"tjwater", + network: context.network, }); const cliArgs = ["--auth-stdin", ...command.split(/\s+/).filter(Boolean)]; diff --git a/tests/runtime/sessionContext.test.ts b/tests/runtime/sessionContext.test.ts index ab37d5c..69b7ccb 100644 --- a/tests/runtime/sessionContext.test.ts +++ b/tests/runtime/sessionContext.test.ts @@ -14,6 +14,7 @@ describe("runtime session context", () => { allowLearningWrite: true, clientSessionId: "chat-session-1", learningMode: "interactive", + network: "fengyang", projectId: "project-id-1", projectKey: "project-1", sessionId: "runtime-session-1", @@ -24,6 +25,7 @@ describe("runtime session context", () => { expect(runtimeContext?.accessToken).toBe("token-1"); expect(runtimeContext?.clientSessionId).toBe("chat-session-1"); + expect(runtimeContext?.network).toBe("fengyang"); expect(runtimeContext?.sessionId).toBe("runtime-session-1"); removeRuntimeSessionContext("runtime-session-1");