fix(cli): 继承 Aipod 会话持久身份

This commit is contained in:
AgentRun Codex
2026-07-13 10:36:04 +02:00
parent eafac6023c
commit 9762f2ad41
2 changed files with 133 additions and 4 deletions
+71 -1
View File
@@ -1570,7 +1570,13 @@ async function submitQueueTaskWithAipod(args: ParsedArgs, aipod: string): Promis
async function sessionSendWithAipod(args: ParsedArgs, positionalSessionId: string | null, aipod: string): Promise<JsonRecord> {
const sessionId = positionalSessionId ?? optionalFlag(args, "session-id") ?? newSessionId();
const rendered = await renderAipodForCommand(args, aipod, positionalSessionId ? 3 : 2, { sessionId });
const existing = await fetchDurableSessionForAipodSend(args, sessionId);
const renderInput = inheritDurableSessionAipodIdentity(
await aipodRenderInput(args, positionalSessionId ? 3 : 2, { sessionId }),
sessionId,
existing,
);
const rendered = await client(args).post(`/api/v1/aipod-specs/${encodeURIComponent(aipod)}/render`, renderInput) as RenderedAipodQueueTask;
const task = rendered.queueTask;
const profile = String(task.backendProfile);
const sessionRef = objectField(task as unknown as JsonRecord, "sessionRef", {});
@@ -1605,6 +1611,70 @@ async function sessionSendWithAipod(args: ParsedArgs, positionalSessionId: strin
return { ...summarizeSessionSendResult(result, sessionId, profile, aipod), modelResolution: rendered.modelResolution };
}
async function fetchDurableSessionForAipodSend(args: ParsedArgs, sessionId: string): Promise<JsonRecord | null> {
try {
return await client(args).get(`/api/v1/sessions/${encodeURIComponent(sessionId)}`) as JsonRecord;
} catch (error) {
if (error instanceof AgentRunError && error.httpStatus === 404) return null;
throw error;
}
}
export function inheritDurableSessionAipodIdentity(
renderInput: RenderAipodInput,
sessionId: string,
existing: JsonRecord | null,
): RenderAipodInput {
if (existing === null) return renderInput;
const durableSessionId = stringValue(existing.sessionId);
const tenantId = stringValue(existing.tenantId);
const projectId = stringValue(existing.projectId);
if (durableSessionId !== sessionId || tenantId === null || projectId === null) {
throw new AgentRunError("schema-invalid", `session ${sessionId} durable identity is incomplete or mismatched`, {
httpStatus: 400,
details: { sessionId, valuesPrinted: false },
});
}
assertDurableSessionBoundary(renderInput.tenantId, tenantId, sessionId, "tenantId");
assertDurableSessionBoundary(renderInput.projectId, projectId, sessionId, "projectId");
const requestedSessionRef = jsonRecordValue(renderInput.sessionRef) ?? {};
const requestedSessionId = stringValue(requestedSessionRef.sessionId);
if (requestedSessionId !== null && requestedSessionId !== sessionId) {
throw durableSessionBoundaryError(sessionId, "sessionRef.sessionId");
}
const conversationId = stringValue(existing.conversationId);
const threadId = stringValue(existing.threadId);
const expiresAt = stringValue(existing.expiresAt);
return {
...renderInput,
tenantId,
projectId,
sessionId,
sessionRef: {
sessionId,
...(conversationId === null ? {} : { conversationId }),
...(threadId === null ? {} : { threadId }),
...(expiresAt === null ? {} : { expiresAt }),
metadata: {
...(jsonRecordValue(requestedSessionRef.metadata) ?? {}),
...(jsonRecordValue(existing.metadata) ?? {}),
},
},
};
}
function assertDurableSessionBoundary(value: string | undefined, durable: string, sessionId: string, field: string): void {
if (value === undefined || value === durable) return;
throw durableSessionBoundaryError(sessionId, field);
}
function durableSessionBoundaryError(sessionId: string, field: string): AgentRunError {
return new AgentRunError("tenant-policy-denied", "sessionRef cannot be reused across tenant or project boundary", {
httpStatus: 403,
details: { sessionId, field, valuesPrinted: false },
});
}
async function submitQueueTask(args: ParsedArgs): Promise<JsonValue> {
const aipod = optionalFlag(args, "aipod") ?? optionalFlag(args, "aipod-spec");
if (aipod) return submitQueueTaskWithAipod(args, aipod);