fix(session): propagate network context
Agent CI/CD / docker-image (push) Failing after 15m0s
Agent CI/CD / deploy-fallback-log (push) Successful in 0s

This commit is contained in:
2026-07-17 16:33:11 +08:00
parent 40c0395fb1
commit d782b7fa11
8 changed files with 31 additions and 3 deletions
+8 -2
View File
@@ -35,12 +35,18 @@ function appendParams(url: URL, params: Record<string, unknown> = {}): void {
}
}
function withNetworkParam(params: Record<string, unknown> | undefined, network: string): Record<string, unknown> {
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);
+1
View File
@@ -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"
+4
View File
@@ -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"
+5
View File
@@ -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,
+1
View File
@@ -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,
+1
View File
@@ -9,6 +9,7 @@ export type RuntimeSessionContext = {
clientSessionId: string;
learningMode?: "interactive" | "review";
memoryListReadScopes?: Partial<Record<"user" | "workspace", boolean>>;
network?: string;
projectId?: string;
projectKey: string;
sessionId: string;
+9 -1
View File
@@ -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)];
+2
View File
@@ -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");