fix: 降低 session 和 queue CLI 默认输出噪声
This commit is contained in:
+248
-14
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user