fix: 降低 session 和 queue CLI 默认输出噪声

This commit is contained in:
Codex
2026-06-10 01:25:23 +08:00
parent c7b3bd3837
commit dbc469ee1d
2 changed files with 294 additions and 16 deletions
+248 -14
View File
@@ -15,7 +15,7 @@ import { AgentRunError, errorToJson } from "../../src/common/errors.js";
import type { RunnerOnceOptions } from "../../src/runner/run-once.js";
import { backendProfileSpec, isBackendProfile } from "../../src/common/backend-profiles.js";
import { outputBytesFromPayload, outputTruncatedFromPayload } from "../../src/common/output.js";
import { redactText } from "../../src/common/redaction.js";
import { redactJson, redactText } from "../../src/common/redaction.js";
interface ParsedArgs {
positional: string[];
@@ -70,7 +70,7 @@ async function dispatch(args: ParsedArgs): Promise<CliResult> {
if (group === "sessions" && command === "storage" && id) return sessionStorageGet(args, id);
if (group === "sessions" && command === "storage" && !id) throw new AgentRunError("schema-invalid", "sessions storage requires a sessionId", { httpStatus: 2 });
if (group === "sessions" && command === "show" && id) return client(args).get(`/api/v1/sessions/${encodeURIComponent(id)}${readerQuery(args)}`);
if (group === "sessions" && command === "read" && id) return client(args).post(`/api/v1/sessions/${encodeURIComponent(id)}/read`, { readerId: optionalFlag(args, "reader-id") ?? "cli" });
if (group === "sessions" && command === "read" && id) return sessionRead(args, id);
if (group === "sessions" && command === "trace" && id) return sessionEvents(args, id, "trace");
if (group === "sessions" && command === "output" && id) return sessionEvents(args, id, "output");
if (group === "sessions" && command === "turn") return sessionTurn(args, id ?? null);
@@ -181,6 +181,16 @@ interface RunEventSummaryOptions {
summaryChars: number;
}
interface SessionEventSummaryOptions {
kind: "trace" | "output";
sessionId: string;
afterSeq: number;
limit: number;
runId: string | null;
tail: number | null;
summaryChars: number;
}
export function summarizeRunEventPage(page: JsonValue, options: RunEventSummaryOptions): JsonRecord {
const events = eventPageItems(page);
const selected = options.tail === null ? events : events.slice(-options.tail);
@@ -201,6 +211,69 @@ export function summarizeRunEventPage(page: JsonValue, options: RunEventSummaryO
};
}
export function summarizeSessionEventPage(page: JsonValue, options: SessionEventSummaryOptions): 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 selected = options.tail === null ? events : events.slice(-options.tail);
const items = selected.map((event) => summarizeRunEvent(event, options.summaryChars));
const lastSeq = items.length > 0 ? items[items.length - 1]?.seq ?? null : null;
const runId = stringValue(record.runId) ?? options.runId;
return {
action: `session-${options.kind}-summary`,
sessionId: stringValue(record.sessionId) ?? options.sessionId,
runId,
afterSeq: options.afterSeq,
limit: options.limit,
tail: options.tail,
sourceCount: events.length,
count: items.length,
cursor: stringValue(record.cursor),
lastSeq,
nextAfterSeq: lastSeq,
valuesPrinted: false,
items,
drillDownCommands: sessionEventDrillDownCommands(options.kind, options.sessionId, { afterSeq: options.afterSeq, limit: options.limit, runId }),
};
}
export function summarizeQueueDispatchResult(result: JsonValue, taskId: string): JsonRecord {
const record = jsonRecordValue(result);
if (!record) throw new AgentRunError("schema-invalid", "queue dispatch response must be an object", { httpStatus: 2 });
const task = jsonRecordValue(record.task);
const run = jsonRecordValue(record.run);
const command = jsonRecordValue(record.command);
const runnerJob = jsonRecordValue(record.runnerJob);
const latestAttempt = jsonRecordValue(record.latestAttempt) ?? jsonRecordValue(task?.latestAttempt);
const runId = stringValue(run?.id) ?? stringValue(latestAttempt?.runId);
const commandId = stringValue(command?.id) ?? stringValue(latestAttempt?.commandId);
const sessionId = stringValue(latestAttempt?.sessionId) ?? stringValue(jsonRecordValue(run?.sessionRef)?.sessionId) ?? stringValue(jsonRecordValue(task?.sessionRef)?.sessionId);
return {
action: "queue-dispatch-summary",
taskId: stringValue(task?.id) ?? taskId,
mutation: record.mutation === true,
task: summarizeQueueTaskRecord(task, taskId),
latestAttempt: summarizeAttemptRecord(latestAttempt),
run: summarizeRunRecord(run),
command: summarizeCommandRecord(command),
runnerJob: summarizeRunnerJobRecord(runnerJob),
fullResponseBytes: jsonByteLength(result),
valuesPrinted: false,
pollCommands: {
queue: `./scripts/agentrun queue show ${stringValue(task?.id) ?? taskId}`,
...(runId ? { run: `./scripts/agentrun runs show ${runId}` } : {}),
...(runId && commandId ? { command: `./scripts/agentrun commands show ${commandId} --run-id ${runId}` } : {}),
...(runId ? { events: `./scripts/agentrun runs events ${runId} --after-seq 0 --limit 100 --tail-summary` } : {}),
...(sessionId ? { trace: `./scripts/agentrun sessions trace ${sessionId} --after-seq 0 --limit 100`, output: `./scripts/agentrun sessions output ${sessionId} --after-seq 0 --limit 100` } : {}),
},
expandedOutput: {
fullFlag: "--full",
rawFlag: "--raw",
note: "For mutating commands, request expanded output on the original invocation.",
},
};
}
function summarizeRunEvent(event: JsonRecord, summaryChars: number): JsonRecord {
const payload = jsonRecordValue(event.payload) ?? {};
const command = boundedSummaryString(stringValue(payload.command), summaryChars);
@@ -209,11 +282,14 @@ function summarizeRunEvent(event: JsonRecord, summaryChars: number): JsonRecord
const message = boundedSummaryString(stringValue(payload.failureMessage) ?? stringValue(payload.message), summaryChars);
const summary = text ?? command ?? outputSummary ?? message ?? "";
const summaryTruncated = [command, text, outputSummary, message].some((value) => value?.endsWith("...") === true);
const runnerTrace = findNestedValue(payload, "runnerTrace");
return {
seq: numberValue(event.seq) ?? 0,
type: stringValue(event.type) ?? "unknown",
method: stringValue(payload.method),
status: stringValue(payload.status) ?? stringValue(payload.terminalStatus) ?? stringValue(payload.phase),
phase: stringValue(payload.phase),
commandId: stringValue(payload.commandId),
command,
text,
exitCode: numberValue(payload.exitCode),
@@ -222,6 +298,11 @@ function summarizeRunEvent(event: JsonRecord, summaryChars: number): JsonRecord
outputBytes: outputBytesFromPayload(payload),
outputSummary,
summary,
payloadBytes: jsonByteLength(payload),
payloadKeys: Object.keys(payload).sort().slice(0, 24),
hasRunnerTrace: runnerTrace !== null,
runnerTraceBytes: runnerTrace === null ? 0 : jsonByteLength(runnerTrace),
hasRawEvent: hasNestedKey(payload, ["raw", "rawEvent", "raw_event", "event"]),
};
}
@@ -310,6 +391,140 @@ function isPlainTextOutput(value: CliResult): value is PlainTextOutput {
return typeof value === "object" && value !== null && plainTextOutputMarker in value;
}
function wantsExpandedOutput(args: ParsedArgs): boolean {
return args.flags.get("full") === true || args.flags.get("raw") === true;
}
function summarizeSessionMutationResult(action: "session-cancel" | "session-read", sessionId: string, result: JsonValue, flags: JsonRecord): JsonRecord {
const record = jsonRecordValue(result);
return {
action,
sessionId,
mutation: true,
...flags,
result: summarizeGenericRecord(record),
fullResponseBytes: jsonByteLength(result),
valuesPrinted: false,
drillDownCommands: {
show: `./scripts/agentrun sessions show ${sessionId} --reader-id cli`,
trace: `./scripts/agentrun sessions trace ${sessionId} --after-seq 0 --limit 100`,
output: `./scripts/agentrun sessions output ${sessionId} --after-seq 0 --limit 100`,
traceFull: `./scripts/agentrun sessions trace ${sessionId} --after-seq 0 --limit 100 --full`,
outputFull: `./scripts/agentrun sessions output ${sessionId} --after-seq 0 --limit 100 --full`,
},
expandedOutput: {
fullFlag: "--full",
rawFlag: "--raw",
note: "For mutating commands, request expanded output on the original invocation.",
},
};
}
function sessionEventDrillDownCommands(kind: "trace" | "output", sessionId: string, options: { afterSeq: number; limit: number; runId: string | null }): JsonRecord {
const base = `./scripts/agentrun sessions ${kind} ${sessionId} --after-seq ${options.afterSeq} --limit ${options.limit}${options.runId ? ` --run-id ${options.runId}` : ""}`;
return {
full: `${base} --full`,
raw: `${base} --raw`,
next: `./scripts/agentrun sessions ${kind} ${sessionId} --after-seq <nextAfterSeq> --limit ${options.limit}${options.runId ? ` --run-id ${options.runId}` : ""}`,
};
}
function summarizeQueueTaskRecord(record: JsonRecord | null, fallbackTaskId: string): JsonRecord {
return compactRecord(record, {
fallback: { id: fallbackTaskId },
keys: ["id", "state", "queue", "lane", "title", "priority", "backendProfile", "providerId", "sessionPath", "version", "updatedAt", "cancelledAt", "cancelReason"],
});
}
function summarizeAttemptRecord(record: JsonRecord | null): JsonRecord | null {
if (!record) return null;
return compactRecord(record, { keys: ["attemptId", "state", "runId", "commandId", "runnerJobId", "sessionId", "sessionPath"] });
}
function summarizeRunRecord(record: JsonRecord | null): JsonRecord | null {
if (!record) return null;
return compactRecord(record, { keys: ["id", "status", "terminalStatus", "failureKind", "failureMessage", "backendProfile", "providerId", "claimedBy", "leaseExpiresAt", "createdAt", "updatedAt"] });
}
function summarizeCommandRecord(record: JsonRecord | null): JsonRecord | null {
if (!record) return null;
return compactRecord(record, { keys: ["id", "runId", "seq", "type", "state", "createdAt", "updatedAt", "acknowledgedAt"] });
}
function summarizeRunnerJobRecord(record: JsonRecord | null): JsonRecord | null {
if (!record) return null;
const runner = jsonRecordValue(record.runner);
const jobIdentity = jsonRecordValue(record.jobIdentity);
const kubernetes = jsonRecordValue(record.kubernetes);
return {
...compactRecord(record, { keys: ["action", "mutation", "runId", "commandId", "attemptId", "runnerId", "namespace", "jobName"] }),
logPath: stringValue(runner?.logPath),
backendProfile: stringValue(runner?.backendProfile),
jobUid: stringValue(jobIdentity?.uid),
created: kubernetes?.created === true,
warnings: Array.isArray(record.warnings) ? record.warnings.map((item) => boundedSummaryString(typeof item === "string" ? item : JSON.stringify(item), 240)).filter((item): item is string => Boolean(item)) : [],
fullResponseBytes: jsonByteLength(record),
valuesPrinted: false,
};
}
function summarizeGenericRecord(record: JsonRecord | null): JsonRecord | null {
if (!record) return null;
return compactRecord(record, {
keys: ["id", "runId", "commandId", "sessionId", "readerId", "sessionVersion", "taskId", "taskVersion", "status", "state", "terminalStatus", "failureKind", "failureMessage", "updatedAt", "readAt", "cancelledAt", "cancelReason"],
});
}
function compactRecord(record: JsonRecord | null, options: { keys: string[]; fallback?: JsonRecord }): JsonRecord {
const result: JsonRecord = { ...(options.fallback ?? {}) };
if (!record) return result;
for (const key of options.keys) {
const value = record[key];
if (value === undefined) continue;
if (typeof value === "string") result[key] = boundedSummaryString(value, key.toLowerCase().includes("message") ? 300 : 900) ?? "";
else if (typeof value === "number" || typeof value === "boolean" || value === null) result[key] = value;
}
result.fullRecordBytes = jsonByteLength(record);
result.valuesPrinted = false;
return result;
}
function jsonByteLength(value: unknown): number {
try {
return Buffer.byteLength(JSON.stringify(redactJson(value)) ?? "null", "utf8");
} catch {
return 0;
}
}
function findNestedValue(value: unknown, key: string, depth = 0): unknown | null {
if (depth > 8 || value === null || value === undefined) return null;
if (Array.isArray(value)) {
for (const item of value) {
const found = findNestedValue(item, key, depth + 1);
if (found !== null) return found;
}
return null;
}
if (typeof value !== "object") return null;
const record = value as Record<string, unknown>;
if (Object.prototype.hasOwnProperty.call(record, key)) return record[key] ?? null;
for (const entry of Object.values(record)) {
const found = findNestedValue(entry, key, depth + 1);
if (found !== null) return found;
}
return null;
}
function hasNestedKey(value: unknown, keys: readonly string[], depth = 0): boolean {
if (depth > 8 || value === null || value === undefined) return false;
if (Array.isArray(value)) return value.some((item) => hasNestedKey(item, keys, depth + 1));
if (typeof value !== "object") return false;
const record = value as Record<string, unknown>;
if (keys.some((key) => Object.prototype.hasOwnProperty.call(record, key))) return true;
return Object.values(record).some((entry) => hasNestedKey(entry, keys, depth + 1));
}
async function listSessions(args: ParsedArgs): Promise<JsonValue> {
const params = new URLSearchParams();
const state = optionalFlag(args, "state") ?? (args.flags.get("running") === true ? "running" : args.flags.get("unread") === true ? "unread" : args.flags.get("all") === true ? "all" : null);
@@ -328,14 +543,24 @@ async function listSessions(args: ParsedArgs): Promise<JsonValue> {
async function sessionEvents(args: ParsedArgs, sessionId: string, kind: "trace" | "output"): Promise<JsonValue> {
const params = new URLSearchParams();
const afterSeq = optionalFlag(args, "after-seq");
const limit = optionalFlag(args, "limit");
const afterSeq = integerFlag(args, "after-seq", 0, { min: 0 });
const limit = integerFlag(args, "limit", 100, { min: 1, max: 500 });
const runId = optionalFlag(args, "run-id");
if (afterSeq) params.set("afterSeq", afterSeq);
if (limit) params.set("limit", limit);
params.set("afterSeq", String(afterSeq));
params.set("limit", String(limit));
if (runId) params.set("runId", runId);
const query = params.toString();
return client(args).get(`/api/v1/sessions/${encodeURIComponent(sessionId)}/${kind}${query ? `?${query}` : ""}`);
const page = await client(args).get(`/api/v1/sessions/${encodeURIComponent(sessionId)}/${kind}${query ? `?${query}` : ""}`);
if (wantsExpandedOutput(args)) return page;
return summarizeSessionEventPage(page, {
kind,
sessionId,
afterSeq,
limit,
runId,
tail: tailFlag(args, limit),
summaryChars: integerFlag(args, "summary-chars", 900, { min: 1, max: 4_000 }),
});
}
async function sessionCreate(args: ParsedArgs, positionalSessionId: string | null): Promise<JsonRecord> {
@@ -445,7 +670,14 @@ async function sessionSteer(args: ParsedArgs, sessionId: string): Promise<JsonRe
async function sessionCancel(args: ParsedArgs, sessionId: string): Promise<JsonRecord> {
const result = await client(args).post(`/api/v1/sessions/${encodeURIComponent(sessionId)}/control`, { action: "cancel", ...cancelBody(args) });
return { action: "session-cancel", sessionId, result };
if (wantsExpandedOutput(args)) return { action: "session-cancel", sessionId, result: result as JsonValue };
return summarizeSessionMutationResult("session-cancel", sessionId, result, { cancelled: true });
}
async function sessionRead(args: ParsedArgs, sessionId: string): Promise<JsonValue> {
const result = await client(args).post(`/api/v1/sessions/${encodeURIComponent(sessionId)}/read`, { readerId: optionalFlag(args, "reader-id") ?? "cli" });
if (wantsExpandedOutput(args)) return result;
return summarizeSessionMutationResult("session-read", sessionId, result, { read: true });
}
async function submitQueueTask(args: ParsedArgs): Promise<JsonValue> {
@@ -497,7 +729,9 @@ async function dispatchQueueTask(args: ParsedArgs, taskId: string): Promise<Json
copy("source-commit", "sourceCommit");
copyRunnerManagerUrlFlag(args, body);
copy("service-account-name", "serviceAccountName");
return client(args).post(`/api/v1/queue/tasks/${encodeURIComponent(taskId)}/dispatch`, body);
const result = await client(args).post(`/api/v1/queue/tasks/${encodeURIComponent(taskId)}/dispatch`, body);
if (wantsExpandedOutput(args)) return result;
return summarizeQueueDispatchResult(result, taskId);
}
async function showRunnerJobStatus(args: ParsedArgs): Promise<JsonValue> {
@@ -998,10 +1232,10 @@ function help(args: ParsedArgs, group?: string): JsonRecord {
"sessions show <sessionId> [--reader-id <reader>]",
"sessions turn [sessionId] --json-file <run-base.json> --prompt-file <file> [--profile codex|deepseek|minimax-m3|dsflash-go|<dynamic-profile>|M3] [--runner-json-file <job.json>]",
"sessions steer <sessionId> --prompt-file <file>",
"sessions cancel <sessionId> [--reason <text>]",
"sessions trace <sessionId> [--after-seq <n>] [--limit <n>] [--run-id <runId>]",
"sessions output <sessionId> [--after-seq <n>] [--limit <n>] [--run-id <runId>]",
"sessions read <sessionId> [--reader-id <reader>]",
"sessions cancel <sessionId> [--reason <text>] [--full|--raw]",
"sessions trace <sessionId> [--after-seq <n>] [--limit <n>] [--run-id <runId>] [--summary-chars <n>] [--full|--raw]",
"sessions output <sessionId> [--after-seq <n>] [--limit <n>] [--run-id <runId>] [--summary-chars <n>] [--full|--raw]",
"sessions read <sessionId> [--reader-id <reader>] [--full|--raw]",
"commands create <runId> --type turn|steer|interrupt --json-file <payload.json>",
"commands show <commandId> --run-id <runId>",
"commands result <commandId> --run-id <runId>",
@@ -1018,7 +1252,7 @@ function help(args: ParsedArgs, group?: string): JsonRecord {
"queue commander [--queue <queue>] [--reader-id <reader>]",
"queue read <taskId> [--reader-id <reader>]",
"queue cancel <taskId> [--reason <text>]",
"queue dispatch <taskId> [--json-file <dispatch.json>] [--idempotency-key <key>] [--image <image>] [--namespace <namespace>]",
"queue dispatch <taskId> [--json-file <dispatch.json>] [--idempotency-key <key>] [--image <image>] [--namespace <namespace>] [--full|--raw]",
"queue refresh <taskId>",
"secrets codex render --dry-run [--profile codex|deepseek|minimax-m3|dsflash-go|<dynamic-profile>] [--codex-home <dir>] [--model-catalog-file <file>] [--namespace agentrun-v01] [--secret-name <name>]",
"provider-profiles list",