fix: 收敛 session detail 按 id 精确拉取 (#137)

Co-authored-by: Codex <codex@pikas.tech>
This commit is contained in:
Lyon
2026-06-10 10:05:43 +08:00
committed by GitHub
parent ecbe1368ba
commit 2e95276db8
3 changed files with 62 additions and 11 deletions
+57 -7
View File
@@ -355,14 +355,16 @@ function withSessionDetailCommands(items: JsonRecord[], kind: "trace" | "output"
const seq = numberValue(item.seq);
const itemId = stringValue(item.itemId);
const eventId = stringValue(item.eventId);
const detail = seq === null ? null : `./scripts/agentrun sessions ${kind} ${sessionId} --seq ${seq}${runId ? ` --run-id ${runId}` : ""} --full`;
const pageHint = seq === null ? "" : ` --after-seq ${Math.max(0, seq - 1)} --limit 1`;
const runFlag = runId ? ` --run-id ${runId}` : "";
const detail = seq === null ? null : `./scripts/agentrun sessions ${kind} ${sessionId}${pageHint} --seq ${seq}${runFlag} --full`;
return {
...item,
...(detail || itemId || eventId ? {
detailCommands: {
...(detail ? { seq: detail } : {}),
...(itemId ? { item: `./scripts/agentrun sessions ${kind} ${sessionId} --item-id ${itemId}${runId ? ` --run-id ${runId}` : ""} --full` } : {}),
...(eventId ? { event: `./scripts/agentrun sessions ${kind} ${sessionId} --event-id ${eventId}${runId ? ` --run-id ${runId}` : ""} --full` } : {}),
...(itemId ? { item: `./scripts/agentrun sessions ${kind} ${sessionId}${pageHint} --item-id ${itemId}${runFlag} --full` } : {}),
...(eventId ? { event: `./scripts/agentrun sessions ${kind} ${sessionId}${pageHint} --event-id ${eventId}${runFlag} --full` } : {}),
},
} : {}),
};
@@ -390,6 +392,13 @@ function sessionEventDetailResult(page: JsonValue, options: { kind: "trace" | "o
},
});
}
return sessionEventDetailResultFromMatches(page, matches, options);
}
function sessionEventDetailResultFromMatches(page: JsonValue, matches: JsonRecord[], options: { kind: "trace" | "output"; sessionId: string; runId: string | null; summaryChars: number; filter: SessionEventDetailFilter; pagesScanned?: number; eventsScanned?: number }): JsonRecord {
const record = jsonRecordValue(page);
if (!record) throw new AgentRunError("schema-invalid", "sessions event response must be an object", { httpStatus: 2 });
const events = eventPageItems(page);
const runId = stringValue(record.runId) ?? options.runId;
return {
action: `session-${options.kind}-event-detail`,
@@ -398,6 +407,8 @@ function sessionEventDetailResult(page: JsonValue, options: { kind: "trace" | "o
filter: options.filter as unknown as JsonRecord,
sourceCount: events.length,
count: matches.length,
...(options.pagesScanned === undefined ? {} : { pagesScanned: options.pagesScanned }),
...(options.eventsScanned === undefined ? {} : { eventsScanned: options.eventsScanned }),
valuesPrinted: false,
items: matches.map((event) => ({
summary: summarizeRunEvent(event, options.summaryChars),
@@ -829,15 +840,19 @@ async function listSessions(args: ParsedArgs): Promise<JsonValue> {
async function sessionEvents(args: ParsedArgs, sessionId: string, kind: "trace" | "output"): Promise<JsonValue> {
const params = new URLSearchParams();
const requestedSeq = optionalIntegerFlag(args, "seq", { min: 0 });
const afterSeq = requestedSeq !== null && optionalFlag(args, "after-seq") === null ? Math.max(0, requestedSeq - 1) : integerFlag(args, "after-seq", 0, { min: 0 });
const hasExplicitAfterSeq = optionalFlag(args, "after-seq") !== null;
const afterSeq = requestedSeq !== null && !hasExplicitAfterSeq ? Math.max(0, requestedSeq - 1) : integerFlag(args, "after-seq", 0, { min: 0 });
const limit = requestedSeq !== null && optionalFlag(args, "limit") === null ? 1 : integerFlag(args, "limit", 100, { min: 1, max: 500 });
const runId = optionalFlag(args, "run-id");
const detailFilter = sessionEventDetailFilter(args, requestedSeq);
if (detailFilter && requestedSeq === null && !hasExplicitAfterSeq && (detailFilter.eventId || detailFilter.itemId)) {
return scanSessionEventDetail(args, { kind, sessionId, runId, afterSeq, limit, summaryChars: integerFlag(args, "summary-chars", 1_200, { min: 1, max: 8_000 }), filter: detailFilter });
}
params.set("afterSeq", String(afterSeq));
params.set("limit", String(limit));
if (runId) params.set("runId", runId);
const query = params.toString();
const page = await client(args).get(`/api/v1/sessions/${encodeURIComponent(sessionId)}/${kind}${query ? `?${query}` : ""}`);
const detailFilter = sessionEventDetailFilter(args, requestedSeq);
if (detailFilter) return sessionEventDetailResult(page, { kind, sessionId, runId, summaryChars: integerFlag(args, "summary-chars", 1_200, { min: 1, max: 8_000 }), filter: detailFilter });
if (wantsExpandedOutput(args)) return page;
return summarizeSessionEventPage(page, {
@@ -852,6 +867,41 @@ async function sessionEvents(args: ParsedArgs, sessionId: string, kind: "trace"
});
}
async function scanSessionEventDetail(args: ParsedArgs, options: { kind: "trace" | "output"; sessionId: string; runId: string | null; afterSeq: number; limit: number; summaryChars: number; filter: SessionEventDetailFilter }): Promise<JsonRecord> {
const maxPages = integerFlag(args, "detail-scan-pages", 20, { min: 1, max: 100 });
let afterSeq = options.afterSeq;
let pagesScanned = 0;
let eventsScanned = 0;
while (pagesScanned < maxPages) {
const params = new URLSearchParams();
params.set("afterSeq", String(afterSeq));
params.set("limit", String(options.limit));
if (options.runId) params.set("runId", options.runId);
const query = params.toString();
const page = await client(args).get(`/api/v1/sessions/${encodeURIComponent(options.sessionId)}/${options.kind}${query ? `?${query}` : ""}`);
const events = eventPageItems(page);
pagesScanned += 1;
eventsScanned += events.length;
const matches = events.filter((event) => matchesSessionEventFilter(event, options.filter));
if (matches.length > 0) return sessionEventDetailResultFromMatches(page, matches, { kind: options.kind, sessionId: options.sessionId, runId: options.runId, summaryChars: options.summaryChars, filter: options.filter, pagesScanned, eventsScanned });
const lastSeq = events.length > 0 ? numberValue(events[events.length - 1]?.seq) : null;
const cursorSeq = numberValue(jsonRecordValue(page)?.cursor);
const nextSeq = cursorSeq ?? lastSeq;
if (nextSeq === null || nextSeq <= afterSeq) break;
afterSeq = nextSeq;
}
throw new AgentRunError("schema-invalid", "no session event matched --event-id/--item-id in scanned pages", {
httpStatus: 2,
details: {
filter: options.filter as unknown as JsonRecord,
pagesScanned,
eventsScanned,
nextAfterSeq: afterSeq,
hint: "use the detailCommands from the summary, pass --seq, or add --after-seq/--limit near the event if the trace is longer than the scan window",
},
});
}
async function sessionCreate(args: ParsedArgs, positionalSessionId: string | null): Promise<JsonRecord> {
const sessionId = positionalSessionId ?? optionalFlag(args, "session-id") ?? newSessionId();
const profile = normalizeProfile(optionalFlag(args, "profile") ?? optionalFlag(args, "backend-profile") ?? "codex");
@@ -1581,8 +1631,8 @@ function help(args: ParsedArgs, group?: string): JsonRecord {
"sessions turn [sessionId] [--json-file <run-base.json>|--json-stdin] [--prompt-file <file>|--prompt-stdin|--prompt <text>] [--profile codex|deepseek|minimax-m3|dsflash-go|<dynamic-profile>|M3] [--runner-json-file <job.json>|--runner-json-stdin]",
"sessions steer <sessionId> [--prompt-file <file>|--prompt-stdin|--prompt <text>]",
"sessions cancel <sessionId> [--reason <text>] [--full|--raw]",
"sessions trace <sessionId> [--after-seq <n>] [--limit <n>] [--run-id <runId>] [--summary-chars <n>] [--include-output] [--seq <n>|--event-id <id>|--item-id <id>] [--full|--raw]",
"sessions output <sessionId> [--after-seq <n>] [--limit <n>] [--run-id <runId>] [--summary-chars <n>] [--include-output] [--seq <n>|--event-id <id>|--item-id <id>] [--full|--raw]",
"sessions trace <sessionId> [--after-seq <n>] [--limit <n>] [--run-id <runId>] [--summary-chars <n>] [--include-output] [--seq <n>|--event-id <id>|--item-id <id>] [--detail-scan-pages <n>] [--full|--raw]",
"sessions output <sessionId> [--after-seq <n>] [--limit <n>] [--run-id <runId>] [--summary-chars <n>] [--include-output] [--seq <n>|--event-id <id>|--item-id <id>] [--detail-scan-pages <n>] [--full|--raw]",
"sessions read <sessionId> [--reader-id <reader>] [--full|--raw]",
"commands create <runId> --type turn|steer|interrupt --json-file <payload.json>|--json-stdin",
"commands show <commandId> --run-id <runId>",