From bb0f0ec4972afbfa636a26b46923ea4d1b2ffa29 Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 11 Jun 2026 09:06:26 +0000 Subject: [PATCH] fix: make AgentRun log tail render-only --- scripts/agentrun-cli-contract-test.ts | 6 ++-- scripts/src/agentrun.ts | 52 +++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/scripts/agentrun-cli-contract-test.ts b/scripts/agentrun-cli-contract-test.ts index b1523f86..b087bc22 100644 --- a/scripts/agentrun-cli-contract-test.ts +++ b/scripts/agentrun-cli-contract-test.ts @@ -117,8 +117,10 @@ assertCondition( assertCondition( agentRunSource.includes("const effectiveLimit = options.tail ?? options.limit;") + && agentRunSource.includes("resourceLogsTailResult(config, ref.name, effectiveLimit, options.fullText)") + && agentRunSource.includes("clientTail: {") && agentRunSource.includes("return renderEventLike(command, result, { ...options, limit: effectiveLimit }, \"Log\""), - "AgentRun logs must map --tail N into the low-noise page limit and preserve pagination commands", + "AgentRun logs must map --tail N into render-only client tailing for human and raw outputs", ); assertCondition( @@ -143,7 +145,7 @@ console.log(JSON.stringify({ "AgentRun resource parser supports apply -f -", "AgentRun resource task dispatch and active task list visibility", "AgentRun resource failure output is visible in human mode", - "AgentRun logs tail controls page limit", + "AgentRun logs tail is enforced by the render-only client", "AgentRun dry-run mutations keep resource-command follow-up", ], })); diff --git a/scripts/src/agentrun.ts b/scripts/src/agentrun.ts index 5a29d105..8f469da5 100644 --- a/scripts/src/agentrun.ts +++ b/scripts/src/agentrun.ts @@ -498,12 +498,54 @@ async function resourceLogs(config: UniDeskConfig | null, command: string, actio const effectiveLimit = options.tail ?? options.limit; const logArgs = ["output", ref.name, "--limit", String(effectiveLimit)]; if (options.afterSeq !== null) logArgs.push("--after-seq", String(options.afterSeq)); - else logArgs.push("--tail"); if (options.fullText) logArgs.push("--full-text"); - const result = await runAgentRunRestCommand(config, "sessions", logArgs); + let result: Record; + if (options.afterSeq === null && options.tail !== null) { + result = await resourceLogsTailResult(config, ref.name, effectiveLimit, options.fullText); + } else { + result = await runAgentRunRestCommand(config, "sessions", logArgs); + } return renderEventLike(command, result, { ...options, limit: effectiveLimit }, "Log", normalizeLogItems(innerData(result)), ref.name); } +async function resourceLogsTailResult(config: UniDeskConfig | null, sessionId: string, limit: number, fullText: boolean): Promise> { + const session = record(innerData(await runAgentRunRestCommand(config, "sessions", ["show", sessionId]))); + const lastSeq = nonNegativeIntegerOrNull(session.lastEventSeq) ?? 0; + let window = Math.max(limit * 4, 100); + let result: Record | null = null; + let data: Record = {}; + for (let attempt = 0; attempt < 6; attempt += 1) { + const afterSeq = Math.max(0, lastSeq - window); + const queryLimit = Math.max(limit, Math.min(1000, window)); + const args = ["output", sessionId, "--limit", String(queryLimit), "--after-seq", String(afterSeq)]; + if (fullText) args.push("--full-text"); + result = await runAgentRunRestCommand(config, "sessions", args); + data = record(innerData(result)); + const items = arrayRecords(data.items); + if (items.length >= limit || afterSeq === 0) break; + window *= 2; + } + if (result === null) return await runAgentRunRestCommand(config, "sessions", ["output", sessionId, "--limit", String(limit), "--after-seq", "0"]); + const items = arrayRecords(data.items); + const tailedItems = items.slice(-limit); + const lastReturnedSeq = tailedItems.length > 0 ? nonNegativeIntegerOrNull(tailedItems[tailedItems.length - 1]?.seq) : null; + return { + ...result, + data: { + ...data, + items: tailedItems, + count: tailedItems.length, + cursor: lastReturnedSeq === null ? data.cursor : String(lastReturnedSeq), + clientTail: { + requested: limit, + sourceLastEventSeq: lastSeq, + scanned: items.length, + valuesPrinted: false, + }, + }, + }; +} + async function resourceResult(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise { const ref = parseResourceRef(action, args); if (ref.kind === "run") { @@ -4029,6 +4071,12 @@ function stringOrNull(value: unknown): string | null { return typeof value === "string" && value.length > 0 ? value : null; } +function nonNegativeIntegerOrNull(value: unknown): number | null { + if (typeof value === "number" && Number.isInteger(value) && value >= 0) return value; + if (typeof value === "string" && /^\d+$/u.test(value)) return Number(value); + return null; +} + function parseArgoStatus(text: string): { revision: string | null; syncStatus: string | null; healthStatus: string | null } { const lines = text.split(/\r?\n/u); const index = lines.findIndex((line) => line.trim() === "argo");