@duetso/agent 0.1.80 → 0.1.82

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/package.json +1 -1
  2. package/dist/src/memory/observational.d.ts +8 -1
  3. package/dist/src/memory/observational.d.ts.map +1 -1
  4. package/dist/src/memory/observational.js +9 -3
  5. package/dist/src/memory/observational.js.map +1 -1
  6. package/dist/src/session/session.d.ts +31 -13
  7. package/dist/src/session/session.d.ts.map +1 -1
  8. package/dist/src/session/session.js +63 -23
  9. package/dist/src/session/session.js.map +1 -1
  10. package/dist/src/tui/app.d.ts +25 -35
  11. package/dist/src/tui/app.d.ts.map +1 -1
  12. package/dist/src/tui/app.js +195 -2308
  13. package/dist/src/tui/app.js.map +1 -1
  14. package/dist/src/tui/autocomplete-controller.d.ts +83 -0
  15. package/dist/src/tui/autocomplete-controller.d.ts.map +1 -0
  16. package/dist/src/tui/autocomplete-controller.js +308 -0
  17. package/dist/src/tui/autocomplete-controller.js.map +1 -0
  18. package/dist/src/tui/boot-screen.d.ts +64 -0
  19. package/dist/src/tui/boot-screen.d.ts.map +1 -0
  20. package/dist/src/tui/boot-screen.js +137 -0
  21. package/dist/src/tui/boot-screen.js.map +1 -0
  22. package/dist/src/tui/controllers.d.ts +39 -0
  23. package/dist/src/tui/controllers.d.ts.map +1 -0
  24. package/dist/src/tui/controllers.js +53 -0
  25. package/dist/src/tui/controllers.js.map +1 -0
  26. package/dist/src/tui/copy-controller.d.ts +75 -0
  27. package/dist/src/tui/copy-controller.d.ts.map +1 -0
  28. package/dist/src/tui/copy-controller.js +140 -0
  29. package/dist/src/tui/copy-controller.js.map +1 -0
  30. package/dist/src/tui/history-replay.d.ts +33 -0
  31. package/dist/src/tui/history-replay.d.ts.map +1 -0
  32. package/dist/src/tui/history-replay.js +47 -0
  33. package/dist/src/tui/history-replay.js.map +1 -0
  34. package/dist/src/tui/initial-prompt.d.ts +24 -0
  35. package/dist/src/tui/initial-prompt.d.ts.map +1 -0
  36. package/dist/src/tui/initial-prompt.js +29 -0
  37. package/dist/src/tui/initial-prompt.js.map +1 -0
  38. package/dist/src/tui/key-handlers.d.ts +49 -0
  39. package/dist/src/tui/key-handlers.d.ts.map +1 -0
  40. package/dist/src/tui/key-handlers.js +192 -0
  41. package/dist/src/tui/key-handlers.js.map +1 -0
  42. package/dist/src/tui/layout.d.ts +71 -0
  43. package/dist/src/tui/layout.d.ts.map +1 -0
  44. package/dist/src/tui/layout.js +245 -0
  45. package/dist/src/tui/layout.js.map +1 -0
  46. package/dist/src/tui/paste-controller.d.ts +90 -0
  47. package/dist/src/tui/paste-controller.d.ts.map +1 -0
  48. package/dist/src/tui/paste-controller.js +234 -0
  49. package/dist/src/tui/paste-controller.js.map +1 -0
  50. package/dist/src/tui/question-picker.d.ts +111 -0
  51. package/dist/src/tui/question-picker.d.ts.map +1 -0
  52. package/dist/src/tui/question-picker.js +308 -0
  53. package/dist/src/tui/question-picker.js.map +1 -0
  54. package/dist/src/tui/renderer-lifecycle.d.ts +20 -0
  55. package/dist/src/tui/renderer-lifecycle.d.ts.map +1 -0
  56. package/dist/src/tui/renderer-lifecycle.js +50 -0
  57. package/dist/src/tui/renderer-lifecycle.js.map +1 -0
  58. package/dist/src/tui/session-subscription.d.ts +30 -0
  59. package/dist/src/tui/session-subscription.d.ts.map +1 -0
  60. package/dist/src/tui/session-subscription.js +78 -0
  61. package/dist/src/tui/session-subscription.js.map +1 -0
  62. package/dist/src/tui/sidebar.d.ts +2 -2
  63. package/dist/src/tui/sidebar.d.ts.map +1 -1
  64. package/dist/src/tui/sidebar.js +1 -1
  65. package/dist/src/tui/sidebar.js.map +1 -1
  66. package/dist/src/tui/slash-commands.d.ts +23 -0
  67. package/dist/src/tui/slash-commands.d.ts.map +1 -0
  68. package/dist/src/tui/slash-commands.js +79 -0
  69. package/dist/src/tui/slash-commands.js.map +1 -0
  70. package/dist/src/tui/starter-section.d.ts +99 -0
  71. package/dist/src/tui/starter-section.d.ts.map +1 -0
  72. package/dist/src/tui/starter-section.js +254 -0
  73. package/dist/src/tui/starter-section.js.map +1 -0
  74. package/dist/src/tui/status-controller.d.ts +94 -0
  75. package/dist/src/tui/status-controller.d.ts.map +1 -0
  76. package/dist/src/tui/status-controller.js +186 -0
  77. package/dist/src/tui/status-controller.js.map +1 -0
  78. package/dist/src/tui/step-renderer.d.ts +55 -0
  79. package/dist/src/tui/step-renderer.d.ts.map +1 -0
  80. package/dist/src/tui/step-renderer.js +246 -0
  81. package/dist/src/tui/step-renderer.js.map +1 -0
  82. package/dist/src/tui/transcript-writer.d.ts +63 -0
  83. package/dist/src/tui/transcript-writer.d.ts.map +1 -0
  84. package/dist/src/tui/transcript-writer.js +130 -0
  85. package/dist/src/tui/transcript-writer.js.map +1 -0
  86. package/dist/src/turn-runner/turn-runner.d.ts +77 -1
  87. package/dist/src/turn-runner/turn-runner.d.ts.map +1 -1
  88. package/dist/src/turn-runner/turn-runner.js +150 -12
  89. package/dist/src/turn-runner/turn-runner.js.map +1 -1
  90. package/dist/src/turn-runner/usage-accounting.d.ts +18 -1
  91. package/dist/src/turn-runner/usage-accounting.d.ts.map +1 -1
  92. package/dist/src/turn-runner/usage-accounting.js +48 -27
  93. package/dist/src/turn-runner/usage-accounting.js.map +1 -1
  94. package/dist/src/types/protocol.d.ts +48 -17
  95. package/dist/src/types/protocol.d.ts.map +1 -1
  96. package/package.json +1 -1
@@ -0,0 +1,186 @@
1
+ import { HINT_IDLE, HINT_RUNNING, HINT_SELECTION_COPY } from "./theme.js";
2
+ import { isTextBufferDestroyedError } from "./transcript-writer.js";
3
+ /**
4
+ * Owns the "is a turn running?" surface of the TUI: the working-status line,
5
+ * the bottom hint row, and the 1 s ticker that advances both while a turn is
6
+ * in flight. Also remembers the most recent terminal event so `runTui` can
7
+ * return it to the caller after the renderer is destroyed.
8
+ *
9
+ * Preserves the v0.1.68 dual-mechanism guard against the OpenTUI race where
10
+ * child TextBuffers are destroyed synchronously *before* the `destroy` event
11
+ * fires: writes short-circuit on `destroyed`, and a `try/catch` around the
12
+ * actual buffer mutation swallows the late-arriving exception and tears down
13
+ * the ticker so the next callback does not throw again.
14
+ */
15
+ export class StatusController {
16
+ opts;
17
+ destroyed = false;
18
+ running = false;
19
+ workingStartedAt;
20
+ workingTicker;
21
+ workingMessage = "working…";
22
+ queuedFollowUps = 0;
23
+ pendingImageCount = 0;
24
+ lastSelectionText = "";
25
+ terminal;
26
+ constructor(opts) {
27
+ this.opts = opts;
28
+ }
29
+ isRunning() {
30
+ return this.running;
31
+ }
32
+ lastTerminal() {
33
+ return this.terminal;
34
+ }
35
+ /** Wall-clock start of the current turn, or undefined while idle. Read by
36
+ * the "● turn finished in Ns" and sleep banners which live outside this
37
+ * controller but want to show the same elapsed counter. */
38
+ getWorkingStartedAt() {
39
+ return this.workingStartedAt;
40
+ }
41
+ setWorkingMessage(message) {
42
+ this.workingMessage = message;
43
+ this.refreshWorkingStatus();
44
+ }
45
+ setQueuedFollowUps(count) {
46
+ this.queuedFollowUps = count;
47
+ this.refreshWorkingStatus();
48
+ }
49
+ /** Mirror of the most recent drag-selection text. Stored here so the hint
50
+ * row can advertise the copy keystroke only while something is selectable;
51
+ * the canonical selection state lives in `runTui`. */
52
+ setSelectionText(text) {
53
+ this.lastSelectionText = text;
54
+ this.refreshHint();
55
+ }
56
+ /** Mirror of `pendingImages.length`. Stored here so the hint row can show
57
+ * the attachment count without dragging the whole `PendingImage[]` into
58
+ * the controller. */
59
+ setPendingImageCount(count) {
60
+ this.pendingImageCount = count;
61
+ this.refreshHint();
62
+ }
63
+ markRunning() {
64
+ this.running = true;
65
+ this.workingMessage = "working…";
66
+ this.workingStartedAt = Date.now();
67
+ this.refreshHint();
68
+ this.refreshWorkingStatus();
69
+ this.startWorkingTicker();
70
+ }
71
+ /** Settle the working-status surface and optionally record the terminal
72
+ * event that ended the turn. The terminal is exposed via `lastTerminal()`
73
+ * so `runTui` can return it to its caller after renderer teardown. */
74
+ markIdle(terminal) {
75
+ if (terminal !== undefined)
76
+ this.terminal = terminal;
77
+ this.running = false;
78
+ this.stopWorkingTicker();
79
+ this.workingStartedAt = undefined;
80
+ this.workingMessage = "working…";
81
+ this.refreshHint();
82
+ this.refreshWorkingStatus();
83
+ }
84
+ setStatus(text) {
85
+ // Renderer teardown destroys the underlying TextBuffer synchronously, but
86
+ // in-flight async work (session events, ticker callbacks, upgrade-status
87
+ // pushes) may still drive chrome updates on the next microtask. The
88
+ // `destroyed` flag catches writes after our destroy handler runs; the
89
+ // try/catch backstops the window between OpenTUI tearing down child
90
+ // TextBuffers and emitting the `destroy` event, which is when the ticker
91
+ // callback typically lands.
92
+ if (this.destroyed)
93
+ return;
94
+ try {
95
+ this.opts.status.content = text;
96
+ }
97
+ catch (error) {
98
+ if (isTextBufferDestroyedError(error)) {
99
+ this.shutdown();
100
+ return;
101
+ }
102
+ throw error;
103
+ }
104
+ }
105
+ /** Recompose the bottom hint row from the controller's current running
106
+ * state plus any active attachment / selection segments. Called from any
107
+ * input that changes a hint segment (attachments, selection) and from
108
+ * the running-state transitions in `markRunning` / `markIdle`. */
109
+ refreshHint() {
110
+ if (this.destroyed)
111
+ return;
112
+ const base = this.running ? HINT_RUNNING : HINT_IDLE;
113
+ const segments = [];
114
+ if (this.pendingImageCount > 0)
115
+ segments.push(this.attachmentHint());
116
+ segments.push(base);
117
+ if (this.lastSelectionText.trim().length > 0)
118
+ segments.push(HINT_SELECTION_COPY);
119
+ try {
120
+ this.opts.hint.content = segments.join(" · ");
121
+ }
122
+ catch (error) {
123
+ if (isTextBufferDestroyedError(error)) {
124
+ this.shutdown();
125
+ return;
126
+ }
127
+ throw error;
128
+ }
129
+ }
130
+ refreshWorkingStatus() {
131
+ if (this.destroyed)
132
+ return;
133
+ this.opts.refreshActiveToolBlocks();
134
+ if (this.workingStartedAt === undefined) {
135
+ this.setStatus(this.queuedFollowUps > 0 ? `queued follow-ups: ${this.queuedFollowUps}` : "");
136
+ return;
137
+ }
138
+ const elapsed = formatElapsed(Date.now() - this.workingStartedAt);
139
+ const queued = this.queuedFollowUps > 0 ? ` · queued follow-ups: ${this.queuedFollowUps}` : "";
140
+ this.setStatus(`● ${this.workingMessage} (${elapsed})${queued}`);
141
+ }
142
+ /** Flip the destroyed flag and stop the ticker. Invoked from the renderer
143
+ * destroy handler and from our own try/catch when a buffer write fires the
144
+ * destroyed-buffer guard mid-flight. Subsequent chrome writes no-op. */
145
+ shutdown() {
146
+ this.destroyed = true;
147
+ this.stopWorkingTicker();
148
+ }
149
+ startWorkingTicker() {
150
+ if (this.workingTicker !== undefined)
151
+ return;
152
+ this.workingTicker = setInterval(() => this.refreshWorkingStatus(), 1000);
153
+ }
154
+ stopWorkingTicker() {
155
+ if (this.workingTicker !== undefined) {
156
+ clearInterval(this.workingTicker);
157
+ this.workingTicker = undefined;
158
+ }
159
+ }
160
+ attachmentHint() {
161
+ const n = this.pendingImageCount;
162
+ return n === 1 ? "📎 1 image attached" : `📎 ${n} images attached`;
163
+ }
164
+ }
165
+ /**
166
+ * Renders an elapsed millisecond span as `${seconds}s` below a minute, and
167
+ * `${minutes}m ${seconds}s` past it. Exported because the tool-call finalizer
168
+ * and the post-turn banners share the same format.
169
+ */
170
+ export function formatElapsed(ms) {
171
+ const totalSeconds = Math.max(0, Math.floor(ms / 1000));
172
+ if (totalSeconds < 60)
173
+ return `${totalSeconds}s`;
174
+ const minutes = Math.floor(totalSeconds / 60);
175
+ const seconds = totalSeconds % 60;
176
+ return `${minutes}m ${seconds}s`;
177
+ }
178
+ /**
179
+ * Spinner marker for an in-flight tool call. Sub-second runs drop the
180
+ * elapsed counter so a transcript of fast tools is not littered with "0s"
181
+ * markers.
182
+ */
183
+ export function runningMarker(elapsedMs) {
184
+ return elapsedMs >= 1000 ? `⏳ ${formatElapsed(elapsedMs)}` : "⏳";
185
+ }
186
+ //# sourceMappingURL=status-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status-controller.js","sourceRoot":"","sources":["../../../src/tui/status-controller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { type CliRenderer, type ScrollBoxRenderable } from "@opentui/core";
2
+ import { StatusController } from "./status-controller.js";
3
+ import { TranscriptWriter } from "./transcript-writer.js";
4
+ import type { TurnEvent, TurnStep, TurnTokenUsage } from "../types/protocol.js";
5
+ export interface StepRendererOptions {
6
+ renderer: CliRenderer;
7
+ transcript: ScrollBoxRenderable;
8
+ transcriptWriter: TranscriptWriter;
9
+ statusController: StatusController;
10
+ /** Invoked at the start of `renderStep` so a stale question picker
11
+ * panel is dismissed before the next agent activity lands; the next
12
+ * step supersedes the open question. */
13
+ onStepStart?: () => void;
14
+ }
15
+ /**
16
+ * Renders the streaming surface of a turn: text deltas, reasoning deltas,
17
+ * tool-call blocks (live spinner → ✓/✗), memory phase banners, and the
18
+ * post-turn usage / elapsed / sleep lines.
19
+ *
20
+ * Owns the in-flight stream/tool-block bookkeeping so the StatusController
21
+ * ticker can call `refreshActiveToolBlocks()` to advance spinners without
22
+ * cracking open the renderer state.
23
+ */
24
+ export declare class StepRenderer {
25
+ private readonly opts;
26
+ private activeTextStream;
27
+ private activeReasoningStream;
28
+ private readonly activeToolBlocks;
29
+ constructor(opts: StepRendererOptions);
30
+ renderStep(step: TurnStep): void;
31
+ /** Repaint every in-flight tool block header with a fresh spinner +
32
+ * elapsed counter. Invoked from StatusController's 1 s ticker so the
33
+ * spinner column advances in step with the working-status line. */
34
+ refreshActiveToolBlocks(): void;
35
+ renderUsage(usage?: TurnTokenUsage): void;
36
+ renderTurnElapsed(): void;
37
+ renderSleeping(wakeAt: number): void;
38
+ renderMemoryStatus(event: Extract<TurnEvent, {
39
+ type: "memory";
40
+ }>): void;
41
+ private renderDelta;
42
+ private renderToolCall;
43
+ private finalizeToolCall;
44
+ private finalizeDelta;
45
+ private toolBlockColumns;
46
+ }
47
+ /**
48
+ * Format the body of a memory phase event for display. Returns the empty
49
+ * string when there are no observations or usage bumps to show, so the
50
+ * caller can elide the block entirely.
51
+ */
52
+ export declare function formatMemoryEventBody(event: Extract<TurnEvent, {
53
+ type: "memory";
54
+ }>): string;
55
+ //# sourceMappingURL=step-renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"step-renderer.d.ts","sourceRoot":"","sources":["../../../src/tui/step-renderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,mBAAmB,EAAkB,MAAM,eAAe,CAAC;AAC3F,OAAO,EAAgC,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAIxF,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAiChF,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,WAAW,CAAC;IACtB,UAAU,EAAE,mBAAmB,CAAC;IAChC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,gBAAgB,EAAE,gBAAgB,CAAC;IACnC;;6CAEyC;IACzC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED;;;;;;;;GAQG;AACH,qBAAa,YAAY;IAQX,OAAO,CAAC,QAAQ,CAAC,IAAI;IAPjC,OAAO,CAAC,gBAAgB,CAA6B;IACrD,OAAO,CAAC,qBAAqB,CAA6B;IAI1D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAgC;gBAEpC,IAAI,EAAE,mBAAmB;IAEtD,UAAU,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI;IA+ChC;;wEAEoE;IACpE,uBAAuB,IAAI,IAAI;IAa/B,WAAW,CAAC,KAAK,CAAC,EAAE,cAAc,GAAG,IAAI;IAwBzC,iBAAiB,IAAI,IAAI;IAazB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAWpC,kBAAkB,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC,GAAG,IAAI;IAcvE,OAAO,CAAC,WAAW;IAgCnB,OAAO,CAAC,cAAc;IA2CtB,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,gBAAgB;CAIzB;AAOD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,GAAG,MAAM,CAa3F"}
@@ -0,0 +1,246 @@
1
+ import { TextRenderable } from "@opentui/core";
2
+ import { formatElapsed, runningMarker } from "./status-controller.js";
3
+ import { SIDEBAR_WIDTH } from "./sidebar.js";
4
+ import { COLORS } from "./theme.js";
5
+ import { assembleToolBlock, formatToolBlock, truncateToolText } from "./tool-formatters.js";
6
+ /**
7
+ * Renders the streaming surface of a turn: text deltas, reasoning deltas,
8
+ * tool-call blocks (live spinner → ✓/✗), memory phase banners, and the
9
+ * post-turn usage / elapsed / sleep lines.
10
+ *
11
+ * Owns the in-flight stream/tool-block bookkeeping so the StatusController
12
+ * ticker can call `refreshActiveToolBlocks()` to advance spinners without
13
+ * cracking open the renderer state.
14
+ */
15
+ export class StepRenderer {
16
+ opts;
17
+ activeTextStream;
18
+ activeReasoningStream;
19
+ // Tool calls fire twice (running → completed/error). Track the rendered
20
+ // block by toolCallId so the second event updates the same line in place
21
+ // instead of pushing a separate block.
22
+ activeToolBlocks = new Map();
23
+ constructor(opts) {
24
+ this.opts = opts;
25
+ }
26
+ renderStep(step) {
27
+ this.opts.onStepStart?.();
28
+ if (step.type === "text_delta") {
29
+ this.activeTextStream = this.renderDelta(this.activeTextStream, null, step.delta, COLORS.agent);
30
+ }
31
+ else if (step.type === "reasoning_delta") {
32
+ this.activeReasoningStream = this.renderDelta(this.activeReasoningStream, "[reasoning]", step.delta, COLORS.reasoning, true);
33
+ }
34
+ else if (step.type === "text") {
35
+ this.opts.transcriptWriter.recordEntry("agent", step.text);
36
+ if (this.activeTextStream) {
37
+ this.finalizeDelta(this.activeTextStream, step.text);
38
+ this.activeTextStream = undefined;
39
+ return;
40
+ }
41
+ this.opts.transcriptWriter.appendBlock(null, step.text, COLORS.agent);
42
+ }
43
+ else if (step.type === "reasoning") {
44
+ const trimmed = step.text.trim();
45
+ if (this.activeReasoningStream) {
46
+ this.finalizeDelta(this.activeReasoningStream, trimmed);
47
+ this.activeReasoningStream = undefined;
48
+ return;
49
+ }
50
+ if (trimmed) {
51
+ this.opts.transcriptWriter.appendBlock("[reasoning]", truncateToolText(trimmed), COLORS.reasoning);
52
+ }
53
+ }
54
+ else if (step.type === "tool_call") {
55
+ this.renderToolCall(step);
56
+ }
57
+ else if (step.type === "system") {
58
+ this.opts.transcriptWriter.appendBlock("[system]", step.message, COLORS.system);
59
+ }
60
+ }
61
+ /** Repaint every in-flight tool block header with a fresh spinner +
62
+ * elapsed counter. Invoked from StatusController's 1 s ticker so the
63
+ * spinner column advances in step with the working-status line. */
64
+ refreshActiveToolBlocks() {
65
+ if (this.activeToolBlocks.size === 0)
66
+ return;
67
+ const columns = this.toolBlockColumns();
68
+ for (const block of this.activeToolBlocks.values()) {
69
+ if (block.startedAt === undefined)
70
+ continue;
71
+ block.line.content = assembleToolBlock({ header: block.header, body: block.body || undefined }, runningMarker(Date.now() - block.startedAt), { columns });
72
+ }
73
+ }
74
+ renderUsage(usage) {
75
+ if (!usage)
76
+ return;
77
+ // Cumulative cost is updated on the session when the terminal event is
78
+ // handled; sidebar refreshes from `getSessionCostUsd()` via `refreshSidebar`.
79
+ // Tokens stay terse (just in/out) since the cost breakdown below is
80
+ // where the cache wins actually matter. Cost is split across all four
81
+ // buckets (in / out / cache read / cache write) so prompt-cache hits and
82
+ // writes are visible at a glance; zero buckets collapse out.
83
+ const tokens = `Tokens: in=${usage.input} out=${usage.output}`;
84
+ const costParts = [
85
+ ["in", usage.cost.input],
86
+ ["out", usage.cost.output],
87
+ ["cr", usage.cost.cacheRead],
88
+ ["cw", usage.cost.cacheWrite],
89
+ ]
90
+ .filter(([, value]) => value > 0)
91
+ .map(([label, value]) => `${label}=$${value.toFixed(4)}`);
92
+ const cost = usage.cost.total === 0
93
+ ? ""
94
+ : ` · Cost: $${usage.cost.total.toFixed(4)}${costParts.length > 1 ? ` (${costParts.join(" ")})` : ""}`;
95
+ this.opts.transcriptWriter.appendLine(`[usage] ${tokens}${cost}`, COLORS.hint);
96
+ }
97
+ renderTurnElapsed() {
98
+ const startedAt = this.opts.statusController.getWorkingStartedAt();
99
+ if (startedAt === undefined)
100
+ return;
101
+ this.opts.transcriptWriter.appendLine(`● turn finished in ${formatElapsed(Date.now() - startedAt)}`, COLORS.status);
102
+ }
103
+ // Sleep terminals replace the usual "turn finished" line because the session
104
+ // is going back to sleep, not wrapping up. When a turn ran before the sleep
105
+ // (e.g. an injected prompt while waiting on a state machine), include its
106
+ // duration so the user can still see how long the work took.
107
+ renderSleeping(wakeAt) {
108
+ const wakeLabel = new Date(wakeAt).toLocaleTimeString();
109
+ const startedAt = this.opts.statusController.getWorkingStartedAt();
110
+ const turnDuration = startedAt === undefined ? "" : ` · turn took ${formatElapsed(Date.now() - startedAt)}`;
111
+ this.opts.transcriptWriter.appendLine(`● sleeping until ${wakeLabel}${turnDuration}`, COLORS.status);
112
+ }
113
+ renderMemoryStatus(event) {
114
+ if (event.status === "running") {
115
+ this.opts.statusController.setWorkingMessage(event.message);
116
+ return;
117
+ }
118
+ const body = formatMemoryEventBody(event);
119
+ if (body) {
120
+ this.opts.transcriptWriter.appendBlock(`[memory:${event.phase}]`, body, COLORS.memory);
121
+ }
122
+ if (this.opts.statusController.isRunning()) {
123
+ this.opts.statusController.setWorkingMessage("working…");
124
+ }
125
+ }
126
+ renderDelta(block, label, delta, fg, truncate = false) {
127
+ const next = block ??
128
+ {
129
+ line: new TextRenderable(this.opts.renderer, { content: "", fg }),
130
+ label,
131
+ body: "",
132
+ truncate,
133
+ };
134
+ if (!block) {
135
+ this.opts.transcriptWriter.beginBlock();
136
+ this.opts.transcript.add(next.line);
137
+ }
138
+ next.body += delta;
139
+ updateStreamingBlock(next);
140
+ return next;
141
+ }
142
+ // Render a tool call as a single, self-updating block. The first event
143
+ // (`status: "running"`) creates the block with a spinner; the second event
144
+ // (`completed` or `error`) replaces the spinner with ✓/✗ and appends the
145
+ // truncated result inline so the call and its outcome stay visually paired.
146
+ // Per-tool formatters in `tool-formatters.ts` decide the header text and
147
+ // whether the call should appear in the transcript at all (e.g.
148
+ // ask_user_question hides itself live and lets the `ask` terminal event
149
+ // own the question display).
150
+ renderToolCall(step) {
151
+ const existing = this.activeToolBlocks.get(step.toolCallId);
152
+ if (existing) {
153
+ this.finalizeToolCall(step, existing);
154
+ return;
155
+ }
156
+ const isLive = step.status === "running" || step.status === "pending";
157
+ const formatStatus = isLive ? "running" : step.status === "error" ? "error" : "completed";
158
+ const formatted = formatToolBlock({
159
+ toolName: step.toolName,
160
+ status: formatStatus,
161
+ input: step.input,
162
+ output: step.output,
163
+ mode: "live",
164
+ });
165
+ if (formatted.hidden)
166
+ return;
167
+ const startedAt = isLive ? Date.now() : undefined;
168
+ const marker = "⏳";
169
+ const fg = step.status === "error" ? COLORS.error : COLORS.tool;
170
+ const columns = this.toolBlockColumns();
171
+ const line = new TextRenderable(this.opts.renderer, {
172
+ content: assembleToolBlock(formatted, marker, { columns }),
173
+ fg,
174
+ });
175
+ this.opts.transcriptWriter.beginBlock();
176
+ this.opts.transcript.add(line);
177
+ const block = {
178
+ line,
179
+ header: formatted.header,
180
+ body: formatted.body ?? "",
181
+ input: step.input,
182
+ startedAt,
183
+ };
184
+ this.activeToolBlocks.set(step.toolCallId, block);
185
+ // The same event may already carry a terminal status (cached/replayed
186
+ // history). Fall through to finalize against the just-created block.
187
+ if (!isLive) {
188
+ this.finalizeToolCall(step, block);
189
+ }
190
+ }
191
+ finalizeToolCall(step, block) {
192
+ const isError = step.status === "error";
193
+ const glyph = isError ? "✗" : "✓";
194
+ const elapsedMs = block.startedAt === undefined ? 0 : Date.now() - block.startedAt;
195
+ // Sub-second runs drop the elapsed suffix so the transcript does not get
196
+ // littered with "0s" markers from fast tools (read, ls, todo_write, …).
197
+ const durationSuffix = elapsedMs >= 1000 ? ` ${formatElapsed(elapsedMs)}` : "";
198
+ const formatted = formatToolBlock({
199
+ toolName: step.toolName,
200
+ status: isError ? "error" : "completed",
201
+ input: block.input,
202
+ output: step.output,
203
+ mode: "live",
204
+ });
205
+ block.line.content = assembleToolBlock(formatted, `${glyph}${durationSuffix}`, {
206
+ columns: this.toolBlockColumns(),
207
+ });
208
+ block.line.fg = isError ? COLORS.error : COLORS.tool;
209
+ this.activeToolBlocks.delete(step.toolCallId);
210
+ }
211
+ finalizeDelta(block, body) {
212
+ block.body = body;
213
+ updateStreamingBlock(block);
214
+ }
215
+ // Width budget for a tool block: terminal width minus the fixed sidebar
216
+ // column and a small fudge for borders/padding. Recomputed per render so a
217
+ // resize after a tool block lands updates new blocks; existing blocks keep
218
+ // the width they were rendered at, which is acceptable since the renderer
219
+ // would otherwise re-wrap and could exceed the row cap.
220
+ toolBlockColumns() {
221
+ const transcriptColumnPadding = 4;
222
+ return Math.max(20, this.opts.renderer.terminalWidth - SIDEBAR_WIDTH - transcriptColumnPadding);
223
+ }
224
+ }
225
+ function updateStreamingBlock(block) {
226
+ const body = block.truncate ? truncateToolText(block.body) : block.body;
227
+ block.line.content = block.label ? `${block.label}\n${body}` : body;
228
+ }
229
+ /**
230
+ * Format the body of a memory phase event for display. Returns the empty
231
+ * string when there are no observations or usage bumps to show, so the
232
+ * caller can elide the block entirely.
233
+ */
234
+ export function formatMemoryEventBody(event) {
235
+ const hasObservations = Boolean(event.observations && event.observations.length > 0);
236
+ const hasBumps = Boolean(event.usageBumpedObservations && event.usageBumpedObservations.length > 0);
237
+ if (!hasObservations && !hasBumps) {
238
+ return "";
239
+ }
240
+ const sections = [event.message];
241
+ if (hasObservations) {
242
+ sections.push(event.observations.map((observation) => observation.content).join("\n\n"));
243
+ }
244
+ return truncateToolText(sections.join("\n"));
245
+ }
246
+ //# sourceMappingURL=step-renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"step-renderer.js","sourceRoot":"","sources":["../../../src/tui/step-renderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import { type CliRenderer, type KeyEvent, type ScrollBoxRenderable, TextRenderable } from "@opentui/core";
2
+ import type { TranscriptEntry } from "./transcript-log.js";
3
+ export interface TranscriptWriterOptions {
4
+ /** Reads the last drag-selected text; surfaced in `/diag` key dumps so we
5
+ * can correlate "key fired" with "selection state at that moment". */
6
+ getLastSelectionText: () => string;
7
+ /** Invoked when the underlying TextBuffer is observed to be destroyed
8
+ * mid-mutation (the OpenTUI race the v0.1.68 fix targets). The caller
9
+ * uses this to tear down timers (working ticker) that would otherwise
10
+ * fire again on a dead buffer. */
11
+ onBufferDestroyed?: () => void;
12
+ }
13
+ /**
14
+ * Owns every write to the transcript ScrollBox plus the `/diag` key+selection
15
+ * event log. Centralizing here keeps the post-teardown try/catch that swallows
16
+ * "TextBuffer is destroyed" in exactly one place (v0.1.68 fix), and gives the
17
+ * status/hint controllers a single `isDestroyed()` source of truth.
18
+ */
19
+ export declare class TranscriptWriter {
20
+ private readonly renderer;
21
+ private readonly transcript;
22
+ private readonly options;
23
+ private destroyed;
24
+ private hasRenderedAnyBlock;
25
+ private keyDiagnostics;
26
+ private readonly log;
27
+ constructor(renderer: CliRenderer, transcript: ScrollBoxRenderable, options: TranscriptWriterOptions);
28
+ /** Append a single line. Empty content is ignored so callers can guard with
29
+ * raw input without producing blank rows; use `addLine` instead when a
30
+ * spacer is wanted and a renderable handle is needed. */
31
+ appendLine(content: string, fg: string): void;
32
+ /** Append a labelled block. `beginBlock()` inserts a blank separator before
33
+ * every block except the first so distinct steps stay visually distinct. */
34
+ appendBlock(label: string | null, body: string, fg: string): void;
35
+ beginBlock(): void;
36
+ /** Append a line and return the renderable handle. Used by the starter
37
+ * block (which destroys its own refs on dismiss) and substitutes a single
38
+ * space for empty content so the renderable still occupies a row. */
39
+ addLine(content: string, fg: string): TextRenderable;
40
+ /** Records a user/agent message body in the parallel log read by `/copy`
41
+ * and the copy keystroke. Trims and drops empty payloads so noisy
42
+ * presentation-only writes (spacers, etc.) do not pollute the log. */
43
+ recordEntry(kind: TranscriptEntry["kind"], text: string): void;
44
+ entries(): readonly TranscriptEntry[];
45
+ /** Flipped from the renderer `destroy` handler. Subsequent transcript
46
+ * writes short-circuit instead of throwing from a destroyed buffer. */
47
+ markDestroyed(): void;
48
+ isDestroyed(): boolean;
49
+ setKeyDiagnosticsEnabled(enabled: boolean): void;
50
+ isKeyDiagnosticsEnabled(): boolean;
51
+ logKey(label: string, key: KeyEvent): void;
52
+ logSelection(text: string): void;
53
+ private handleBufferDestroyed;
54
+ }
55
+ /**
56
+ * OpenTUI's TextBuffer throws a plain Error with this exact message from its
57
+ * `guard()` method when any setter is touched after destroy. We sniff the
58
+ * message to distinguish post-teardown races (swallow) from real bugs
59
+ * (rethrow). Exported so the status/hint controllers can share the same
60
+ * sniff without re-importing OpenTUI internals.
61
+ */
62
+ export declare function isTextBufferDestroyedError(error: unknown): boolean;
63
+ //# sourceMappingURL=transcript-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript-writer.d.ts","sourceRoot":"","sources":["../../../src/tui/transcript-writer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,QAAQ,EACb,KAAK,mBAAmB,EACxB,cAAc,EACf,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAE3D,MAAM,WAAW,uBAAuB;IACtC;2EACuE;IACvE,oBAAoB,EAAE,MAAM,MAAM,CAAC;IACnC;;;uCAGmC;IACnC,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAC;CAChC;AAED;;;;;GAKG;AACH,qBAAa,gBAAgB;IAOzB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAR1B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAyB;gBAG1B,QAAQ,EAAE,WAAW,EACrB,UAAU,EAAE,mBAAmB,EAC/B,OAAO,EAAE,uBAAuB;IAKnD;;8DAE0D;IAC1D,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAe7C;iFAC6E;IAC7E,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;IAMjE,UAAU,IAAI,IAAI;IAKlB;;0EAEsE;IACtE,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,cAAc;IAQpD;;2EAEuE;IACvE,WAAW,CAAC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAM9D,OAAO,IAAI,SAAS,eAAe,EAAE;IAMrC;4EACwE;IACxE,aAAa,IAAI,IAAI;IAIrB,WAAW,IAAI,OAAO;IAMtB,wBAAwB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAIhD,uBAAuB,IAAI,OAAO;IAIlC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI;IAgB1C,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAShC,OAAO,CAAC,qBAAqB;CAI9B;AAED;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAElE"}