Repository navigation
Transcript: one live line that holds the latest step and keeps its clock #29938
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b0a3e41
427e775
d027a23
58cefe8
b1294f6
64f8205
c8cb7ea
bd1ce43
ff392ce
fbfd657
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -20,7 +20,7 @@ import { QUESTION_RECORD } from "../question/record"; | |||||
| import type { Translate } from "../i18n"; | ||||||
| import { editedPaths, isBareShellName, toolLabel } from "../toolPaths"; | ||||||
| import { shortCommand } from "./shortCommand"; | ||||||
| import { awaitsPermission, groupId, isDraftCall, isRunning, toolGroups } from "./toolGroups"; | ||||||
| import { awaitsPermission, groupId, isDraftCall, isFailed as callFailed, isRunning, toolGroups } from "./toolGroups"; | ||||||
| import { acpmuxMcpSends } from "./acpmuxMcpSends"; | ||||||
| import { acpmuxSendCalls } from "./acpmuxSends"; | ||||||
| import { subsessionCall } from "./subsessionCalls"; | ||||||
|
|
@@ -975,13 +975,14 @@ function liveTurn( | |||||
| function codexLiveTurn( | ||||||
| user: AcpmuxRow, | ||||||
| drawn: AcpmuxRow[], | ||||||
| rows: AcpmuxRow[], | ||||||
| runs: AcpmuxRow[], | ||||||
| last: AcpmuxRow | undefined, | ||||||
| named: LineCall | undefined, | ||||||
| at: number, | ||||||
| clockOffset: number, | ||||||
| sessionStatus: boolean, | ||||||
| ): AcpmuxRow[] { | ||||||
| let rows = runs; | ||||||
| // The one live block sits at the turn's bottom, as the ChatGPT and Codex apps draw it: no | ||||||
| // "Working for" over the turn (with steers it stood above all of them, far from the line; Leo, | ||||||
| // 2026-10-21). Between steps it reads "Thinking 18s", timed from the prompt. | ||||||
|
|
@@ -1005,6 +1006,22 @@ function codexLiveTurn( | |||||
| } else if (last.kind === "assistant") line.quietKey = `${last.id}:${last.version}`; | ||||||
| const answering = last.kind === "assistant"; | ||||||
| const plan = sessionStatus ? [...rows].reverse().find((row) => row.kind === "plan" && row.plan?.length) : undefined; | ||||||
| // With a todo list and no answer streaming, the activity line under the list is the live line | ||||||
| // (below), which names the plan's step, so no call leaves its run for it. | ||||||
| if (!named && !answering && !plan) { | ||||||
| // Between calls the line keeps the latest finished step ("Ran grep …") instead of a separate | ||||||
| // "Thinking" under it, shimmering and timed from the prompt like any step: the line is the | ||||||
| // turn's current activity and just keeps going (Leo, 2026-11-01: "this should be replacing | ||||||
| // the thinking area and its shimmer across things", "like the thinking or whatever it should | ||||||
| // just keep going"). The step leaves its run while the line names it, so it reads once. | ||||||
| const lifted = liftedStep(rows, last); | ||||||
| if (lifted) { | ||||||
| rows = lifted.rows; | ||||||
| line.doing = lifted.doing; | ||||||
| line.doingShell = lifted.shell; | ||||||
| line.doingDone = true; | ||||||
| } | ||||||
| } | ||||||
| if (!plan) return [...rows, line]; | ||||||
| const work = rows.filter((row) => row !== plan); | ||||||
| // The answer streams under the list, the line under both. | ||||||
|
|
@@ -1025,6 +1042,51 @@ function codexLiveTurn( | |||||
| ]; | ||||||
| } | ||||||
|
|
||||||
| /// The latest run's last finished call, lifted out of the run for the live line to name: a command | ||||||
| /// as its short line ("Ran grep …"), any other call as its row names it. Only a plain step: a failed | ||||||
| /// call keeps its mark in the run, a command sent to the background keeps its state there, and | ||||||
| /// messages, sub-sessions, render cards and edits keep their own rows. Nil when the latest step is anything else. | ||||||
| function liftedStep( | ||||||
| rows: AcpmuxRow[], | ||||||
| last: AcpmuxRow, | ||||||
| ): { rows: AcpmuxRow[]; doing: string; shell: boolean } | undefined { | ||||||
| if (last.kind !== "activity") return undefined; | ||||||
| // A run that a short note joined to an earlier one keeps the earlier one's id (mergeShortNotes). | ||||||
| const at = rows.findIndex((row) => row.id === last.id || row.joined?.includes(last.id)); | ||||||
| const run = rows[at]; | ||||||
| if (!run?.foldRun) return undefined; | ||||||
| const items = run.items ?? []; | ||||||
| // Thoughts after it are the agent thinking about it; the line still names the call. | ||||||
| let index = items.length - 1; | ||||||
| while (index >= 0 && !items[index]!.tool) index -= 1; | ||||||
| const item = items[index]; | ||||||
| const call = item?.tool; | ||||||
| if ( | ||||||
| !call || | ||||||
| isRunning(call) || | ||||||
| call.background !== undefined || | ||||||
| callFailed(call) || | ||||||
| isEditTool(call) || | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Completed Prompt for AI agents
Suggested change
|
||||||
| renderCall(call) || | ||||||
| subsessionCall(call) || | ||||||
| agentMessage(call) || | ||||||
| acpmuxSendCalls(call) || | ||||||
| acpmuxMcpSends(call) | ||||||
| ) | ||||||
| return undefined; | ||||||
| const shell = | ||||||
| call.kind === "execute" && !call.description && (Boolean(call.command) || isBareShellName(call, call.title)); | ||||||
| const doing = shell ? (call.command ? shortCommand(call.command) : "") : runningLabel(call, item!.text); | ||||||
| const kept = items.filter((_, position) => position !== index); | ||||||
| const next = rows.slice(); | ||||||
| if (kept.length) next[at] = { ...run, items: kept, version: run.version * LIFTED_STATES + 1 }; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: This encoding can reuse a prior version on the live-to-ended transition, so React may skip the completed run when its activity version changes from 1 to 2 in the same snapshot. Use a distinct state offset for lifted runs. Prompt for AI agents
Suggested change
|
||||||
| else next.splice(at, 1); | ||||||
| return { rows: next, doing, shell }; | ||||||
| } | ||||||
| /// A run with its last call on the live line differs from the same run whole. (Not a sixth | ||||||
| /// `RUN_STATES`: the minimap's edit-card versions are pinned to five.) | ||||||
| const LIFTED_STATES = 2; | ||||||
|
|
||||||
| /// The newest call still running in a live turn's latest row. | ||||||
| function runningCall( | ||||||
| row: AcpmuxRow, | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -388,10 +388,9 @@ await requireBrowserLane("agent-pane-peer-chat.test.ts", async () => { | |
| const row = page.locator(".cv-thinking", { hasText: "Run make" }); | ||
| await row.first().waitFor(); | ||
| const named = await row.count(); | ||
| // The call ends: between steps the live line times the turn from the prompt ("Thinking 5s"), | ||
| // moved onto this computer's clock. | ||
| // The call ends: the live line holds it until the next step and times the turn from the | ||
| // prompt ("Run make 5s"), moved onto this computer's clock, as it did while the call ran. | ||
| send(3, "tool_call_update", { toolCallId: "t1", status: "completed" }); | ||
| await row.first().waitFor({ state: "detached" }); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: This no longer waits for any post-completion state: Prompt for AI agents |
||
| const time = page.locator(".cv-worked.is-below .cv-worked__time"); | ||
| await time.waitFor(); | ||
| const label = (await time.textContent()) ?? ""; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -478,7 +478,7 @@ | |
| await ended(page, marker); | ||
| await rest(page); | ||
| const frames = await stop(page); | ||
| expect({ drops: drops(frames), atEnd: (await fromEnd(page)) <= 4 }).toEqual({ drops: [], atEnd: true }); | ||
|
Check failure on line 481 in webviews/test/agent-pane-stream-jitter.test.ts
|
||
| await page.close(); | ||
| }, | ||
| ); | ||
|
|
@@ -558,10 +558,11 @@ | |
| if (!scenario.toolEnding) expect(Math.abs(moved)).toBeLessThanOrEqual(1); | ||
| else { | ||
| // A finished turn folds its tool rows into "Worked for" above the reply (80e6a34d0b9c, | ||
| // on purpose): the tool row that ran after the reply leaves from under it, so the reply | ||
| // drops by what went from under it, once, in the settle's frame, and rests there. | ||
| // on purpose): whatever ran after the reply leaves from under it, so the reply drops by | ||
| // what went from under it, once, in the settle's frame, and rests there. The live line | ||
| // holds the finished call until the turn ends (Leo, 2026-11-01: "it should just keep | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: This allowance removes the only assertion that the tool-ending fixture actually contributes a tool row. A regression that omits or fails to fold that row can still satisfy the remaining geometry equality; assert the live/finished row content separately while allowing zero height change. Prompt for AI agents |
||
| // going"), and the footer takes the line's place at its height, so that may be nothing. | ||
| const left = before!.under! - after!.under!; | ||
| expect(left).toBeGreaterThan(20); | ||
| expect(Math.abs(moved - left)).toBeLessThanOrEqual(1); | ||
| const rested = measured.filter((frame) => frame.settled).map((frame) => frame.messageBottom!); | ||
| expect(Math.max(...rested) - Math.min(...rested)).toBeLessThanOrEqual(1); | ||
|
|
@@ -640,7 +641,7 @@ | |
| for (let line = 1; line <= 5; line++) { | ||
| await page.keyboard.type(`Line ${line} of a draft`); | ||
| await page.keyboard.press("Shift+Enter"); | ||
| await painted(page, 3); | ||
|
Check failure on line 644 in webviews/test/agent-pane-stream-jitter.test.ts
|
||
| } | ||
| await painted(page, 12); | ||
| await sample(page, marker); | ||
|
|
@@ -650,7 +651,7 @@ | |
| // fixed-wait: stream() paces the reply's chunks as acpmux sends them; the cadence is the behavior. | ||
| await stream(`q-${turn}`, "then write the changelog line for it", `Next grown marker ${turn}`, 30); | ||
| endTurn(); | ||
| await page.getByText(`Next grown marker ${turn}`).waitFor(); | ||
|
Check failure on line 654 in webviews/test/agent-pane-stream-jitter.test.ts
|
||
| await painted(page, 40); | ||
| const frames = await stop(page); | ||
| expect(frames.filter((frame) => frame.marker !== undefined).length).toBeGreaterThan(5); | ||
|
|
@@ -748,7 +749,7 @@ | |
| const resting = await gapAbove(page); | ||
| await watchGap(page, resting); | ||
| unqueue("queue_removed", `q-${turn}`); | ||
| const after = await closed(page); | ||
|
Check failure on line 752 in webviews/test/agent-pane-stream-jitter.test.ts
|
||
| toolDone(`t-${turn}`); | ||
| endTurn(); | ||
| // The last row rests on the stack's top again, a card's height lower, well before a second: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: A completed latest step disappears when a todo list is present:
liftedStepremoves it, but the plan branch discards the populatedlineand renders a newactivityLine. Preserve the lifted line's label and completion state in this branch, so the live line still says the completed step.Prompt for AI agents