@duetso/agent 0.1.81 → 0.1.83

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 (82) hide show
  1. package/dist/package.json +1 -1
  2. package/dist/src/memory/observational.d.ts +7 -0
  3. package/dist/src/memory/observational.d.ts.map +1 -1
  4. package/dist/src/memory/observational.js +7 -1
  5. package/dist/src/memory/observational.js.map +1 -1
  6. package/dist/src/tui/app.d.ts +25 -35
  7. package/dist/src/tui/app.d.ts.map +1 -1
  8. package/dist/src/tui/app.js +195 -2308
  9. package/dist/src/tui/app.js.map +1 -1
  10. package/dist/src/tui/autocomplete-controller.d.ts +83 -0
  11. package/dist/src/tui/autocomplete-controller.d.ts.map +1 -0
  12. package/dist/src/tui/autocomplete-controller.js +308 -0
  13. package/dist/src/tui/autocomplete-controller.js.map +1 -0
  14. package/dist/src/tui/boot-screen.d.ts +64 -0
  15. package/dist/src/tui/boot-screen.d.ts.map +1 -0
  16. package/dist/src/tui/boot-screen.js +137 -0
  17. package/dist/src/tui/boot-screen.js.map +1 -0
  18. package/dist/src/tui/controllers.d.ts +39 -0
  19. package/dist/src/tui/controllers.d.ts.map +1 -0
  20. package/dist/src/tui/controllers.js +53 -0
  21. package/dist/src/tui/controllers.js.map +1 -0
  22. package/dist/src/tui/copy-controller.d.ts +75 -0
  23. package/dist/src/tui/copy-controller.d.ts.map +1 -0
  24. package/dist/src/tui/copy-controller.js +140 -0
  25. package/dist/src/tui/copy-controller.js.map +1 -0
  26. package/dist/src/tui/history-replay.d.ts +33 -0
  27. package/dist/src/tui/history-replay.d.ts.map +1 -0
  28. package/dist/src/tui/history-replay.js +47 -0
  29. package/dist/src/tui/history-replay.js.map +1 -0
  30. package/dist/src/tui/initial-prompt.d.ts +24 -0
  31. package/dist/src/tui/initial-prompt.d.ts.map +1 -0
  32. package/dist/src/tui/initial-prompt.js +29 -0
  33. package/dist/src/tui/initial-prompt.js.map +1 -0
  34. package/dist/src/tui/key-handlers.d.ts +49 -0
  35. package/dist/src/tui/key-handlers.d.ts.map +1 -0
  36. package/dist/src/tui/key-handlers.js +192 -0
  37. package/dist/src/tui/key-handlers.js.map +1 -0
  38. package/dist/src/tui/layout.d.ts +71 -0
  39. package/dist/src/tui/layout.d.ts.map +1 -0
  40. package/dist/src/tui/layout.js +245 -0
  41. package/dist/src/tui/layout.js.map +1 -0
  42. package/dist/src/tui/paste-controller.d.ts +90 -0
  43. package/dist/src/tui/paste-controller.d.ts.map +1 -0
  44. package/dist/src/tui/paste-controller.js +234 -0
  45. package/dist/src/tui/paste-controller.js.map +1 -0
  46. package/dist/src/tui/question-picker.d.ts +111 -0
  47. package/dist/src/tui/question-picker.d.ts.map +1 -0
  48. package/dist/src/tui/question-picker.js +308 -0
  49. package/dist/src/tui/question-picker.js.map +1 -0
  50. package/dist/src/tui/renderer-lifecycle.d.ts +20 -0
  51. package/dist/src/tui/renderer-lifecycle.d.ts.map +1 -0
  52. package/dist/src/tui/renderer-lifecycle.js +50 -0
  53. package/dist/src/tui/renderer-lifecycle.js.map +1 -0
  54. package/dist/src/tui/session-subscription.d.ts +30 -0
  55. package/dist/src/tui/session-subscription.d.ts.map +1 -0
  56. package/dist/src/tui/session-subscription.js +78 -0
  57. package/dist/src/tui/session-subscription.js.map +1 -0
  58. package/dist/src/tui/slash-commands.d.ts +23 -0
  59. package/dist/src/tui/slash-commands.d.ts.map +1 -0
  60. package/dist/src/tui/slash-commands.js +79 -0
  61. package/dist/src/tui/slash-commands.js.map +1 -0
  62. package/dist/src/tui/starter-section.d.ts +99 -0
  63. package/dist/src/tui/starter-section.d.ts.map +1 -0
  64. package/dist/src/tui/starter-section.js +254 -0
  65. package/dist/src/tui/starter-section.js.map +1 -0
  66. package/dist/src/tui/status-controller.d.ts +94 -0
  67. package/dist/src/tui/status-controller.d.ts.map +1 -0
  68. package/dist/src/tui/status-controller.js +186 -0
  69. package/dist/src/tui/status-controller.js.map +1 -0
  70. package/dist/src/tui/step-renderer.d.ts +55 -0
  71. package/dist/src/tui/step-renderer.d.ts.map +1 -0
  72. package/dist/src/tui/step-renderer.js +246 -0
  73. package/dist/src/tui/step-renderer.js.map +1 -0
  74. package/dist/src/tui/transcript-writer.d.ts +63 -0
  75. package/dist/src/tui/transcript-writer.d.ts.map +1 -0
  76. package/dist/src/tui/transcript-writer.js +130 -0
  77. package/dist/src/tui/transcript-writer.js.map +1 -0
  78. package/dist/src/turn-runner/turn-runner.d.ts +69 -3
  79. package/dist/src/turn-runner/turn-runner.d.ts.map +1 -1
  80. package/dist/src/turn-runner/turn-runner.js +123 -20
  81. package/dist/src/turn-runner/turn-runner.js.map +1 -1
  82. package/package.json +1 -1
@@ -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"}
@@ -0,0 +1,130 @@
1
+ import { TextRenderable, } from "@opentui/core";
2
+ import { COLORS } from "./theme.js";
3
+ /**
4
+ * Owns every write to the transcript ScrollBox plus the `/diag` key+selection
5
+ * event log. Centralizing here keeps the post-teardown try/catch that swallows
6
+ * "TextBuffer is destroyed" in exactly one place (v0.1.68 fix), and gives the
7
+ * status/hint controllers a single `isDestroyed()` source of truth.
8
+ */
9
+ export class TranscriptWriter {
10
+ renderer;
11
+ transcript;
12
+ options;
13
+ destroyed = false;
14
+ hasRenderedAnyBlock = false;
15
+ keyDiagnostics = false;
16
+ log = [];
17
+ constructor(renderer, transcript, options) {
18
+ this.renderer = renderer;
19
+ this.transcript = transcript;
20
+ this.options = options;
21
+ }
22
+ // ---- transcript writes ---------------------------------------------------
23
+ /** Append a single line. Empty content is ignored so callers can guard with
24
+ * raw input without producing blank rows; use `addLine` instead when a
25
+ * spacer is wanted and a renderable handle is needed. */
26
+ appendLine(content, fg) {
27
+ if (!content)
28
+ return;
29
+ if (this.destroyed)
30
+ return;
31
+ try {
32
+ const line = new TextRenderable(this.renderer, { content, fg });
33
+ this.transcript.add(line);
34
+ }
35
+ catch (error) {
36
+ if (isTextBufferDestroyedError(error)) {
37
+ this.handleBufferDestroyed();
38
+ return;
39
+ }
40
+ throw error;
41
+ }
42
+ }
43
+ /** Append a labelled block. `beginBlock()` inserts a blank separator before
44
+ * every block except the first so distinct steps stay visually distinct. */
45
+ appendBlock(label, body, fg) {
46
+ this.beginBlock();
47
+ const text = label ? `${label}\n${body}` : body;
48
+ for (const line of text.split("\n"))
49
+ this.appendLine(line, fg);
50
+ }
51
+ beginBlock() {
52
+ if (this.hasRenderedAnyBlock)
53
+ this.appendLine(" ", COLORS.hint);
54
+ this.hasRenderedAnyBlock = true;
55
+ }
56
+ /** Append a line and return the renderable handle. Used by the starter
57
+ * block (which destroys its own refs on dismiss) and substitutes a single
58
+ * space for empty content so the renderable still occupies a row. */
59
+ addLine(content, fg) {
60
+ const line = new TextRenderable(this.renderer, { content: content || " ", fg });
61
+ this.transcript.add(line);
62
+ return line;
63
+ }
64
+ // ---- transcript log ------------------------------------------------------
65
+ /** Records a user/agent message body in the parallel log read by `/copy`
66
+ * and the copy keystroke. Trims and drops empty payloads so noisy
67
+ * presentation-only writes (spacers, etc.) do not pollute the log. */
68
+ recordEntry(kind, text) {
69
+ const trimmed = text.trim();
70
+ if (!trimmed)
71
+ return;
72
+ this.log.push({ kind, text: trimmed });
73
+ }
74
+ entries() {
75
+ return this.log;
76
+ }
77
+ // ---- teardown ------------------------------------------------------------
78
+ /** Flipped from the renderer `destroy` handler. Subsequent transcript
79
+ * writes short-circuit instead of throwing from a destroyed buffer. */
80
+ markDestroyed() {
81
+ this.destroyed = true;
82
+ }
83
+ isDestroyed() {
84
+ return this.destroyed;
85
+ }
86
+ // ---- /diag key + selection logging ---------------------------------------
87
+ setKeyDiagnosticsEnabled(enabled) {
88
+ this.keyDiagnostics = enabled;
89
+ }
90
+ isKeyDiagnosticsEnabled() {
91
+ return this.keyDiagnostics;
92
+ }
93
+ logKey(label, key) {
94
+ if (!this.keyDiagnostics)
95
+ return;
96
+ const flags = [];
97
+ if (key.ctrl)
98
+ flags.push("ctrl");
99
+ if (key.shift)
100
+ flags.push("shift");
101
+ if (key.meta)
102
+ flags.push("meta");
103
+ if (key.super)
104
+ flags.push("super");
105
+ if (key.option)
106
+ flags.push("option");
107
+ const selection = this.options.getLastSelectionText();
108
+ this.appendBlock("[diag]", `${label} name=${JSON.stringify(key.name)} flags=[${flags.join(",")}] sequence=${JSON.stringify(key.sequence)} source=${key.source} | lastSelection=${selection.length}c rendererSel=${this.renderer.hasSelection ? "yes" : "no"}`, COLORS.hint);
109
+ }
110
+ logSelection(text) {
111
+ if (!this.keyDiagnostics)
112
+ return;
113
+ this.appendBlock("[diag]", `selection event: ${text.length} chars — ${JSON.stringify(text.slice(0, 80))}`, COLORS.hint);
114
+ }
115
+ handleBufferDestroyed() {
116
+ this.destroyed = true;
117
+ this.options.onBufferDestroyed?.();
118
+ }
119
+ }
120
+ /**
121
+ * OpenTUI's TextBuffer throws a plain Error with this exact message from its
122
+ * `guard()` method when any setter is touched after destroy. We sniff the
123
+ * message to distinguish post-teardown races (swallow) from real bugs
124
+ * (rethrow). Exported so the status/hint controllers can share the same
125
+ * sniff without re-importing OpenTUI internals.
126
+ */
127
+ export function isTextBufferDestroyedError(error) {
128
+ return error instanceof Error && error.message === "TextBuffer is destroyed";
129
+ }
130
+ //# sourceMappingURL=transcript-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript-writer.js","sourceRoot":"","sources":["../../../src/tui/transcript-writer.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,7 @@ import { MemoryContextCache } from "../memory/store.js";
6
6
  import type { TurnRunnerConfig } from "../types/config.js";
7
7
  import type { TurnAgentFile, TurnAnswerCommand, TurnContextWindowUsage, TurnEditFollowUpQueueCommand, TurnEvent, TurnInterruptCommand, TurnMode, TurnPromptCommand, TurnState, TurnTokenUsage, TurnStartCommand, TurnTerminalEvent, TurnCommand, TurnOptions } from "../types/protocol.js";
8
8
  import type { StateMachineAgentState } from "../types/state-machine.js";
9
+ import { type WireGuardHorizon } from "./wire-shaping.js";
9
10
  import { type TurnRunnerControlResult } from "./tools.js";
10
11
  import { SkillContext } from "./skill-context.js";
11
12
  import { type StateAgentHandle } from "./state-machine-controller.js";
@@ -26,9 +27,26 @@ export interface AgentConfigInput {
26
27
  skills?: Skill[];
27
28
  tools: AgentTool[];
28
29
  }
30
+ /**
31
+ * Internal outcome of a single `runAgentWorker` call. Narrower than the
32
+ * public `TurnTerminalEvent`: a worker only produces `complete` or
33
+ * `interrupted`. `ask` is synthesized later from `control`; `sleep` is
34
+ * synthesized by the state-machine controller.
35
+ */
36
+ export type AgentWorkerOutcome = {
37
+ type: "complete";
38
+ status: "completed" | "failed";
39
+ result?: string;
40
+ error?: string;
41
+ state: TurnState;
42
+ } | {
43
+ type: "interrupted";
44
+ state: TurnState;
45
+ };
29
46
  export interface AgentWorkerResult {
30
- terminal: TurnTerminalEvent;
47
+ outcome: AgentWorkerOutcome;
31
48
  control: TurnRunnerControlResult;
49
+ parentUsage?: TurnTokenUsage;
32
50
  }
33
51
  /**
34
52
  * Rescale the four segment estimates so they sum exactly to the
@@ -182,10 +200,51 @@ export declare class TurnRunner {
182
200
  * aggregate and emits a `usage` event so consumers see cost tick after
183
201
  * each parent agent boundary. Recording at the worker boundary (instead
184
202
  * of per `message_end`) keeps the same behavior when test harnesses
185
- * stub `runAgentWorker` and supply `terminal.usage` directly without
203
+ * stub `runAgentWorker` and supply `parentUsage` directly without
186
204
  * running a real pi `Agent`.
187
205
  */
188
206
  private runAgentWorkerWithUsage;
207
+ /**
208
+ * Inspect the most recent assistant message after a parent prompt and,
209
+ * when it carries a provider context-overflow error, mutate the agent
210
+ * state and the sticky `wireGuardHorizon` so that a subsequent re-prompt
211
+ * sends roughly the newer half of observable history.
212
+ *
213
+ * Returns `true` only when recovery actually happened — meaning the
214
+ * horizon advanced past at least one observable message. Callers use
215
+ * the return value as a single-shot gate: invoke once after the first
216
+ * `agent.prompt(...)` and, on `true`, resume the turn via
217
+ * `agent.continue()` exactly once. The helper never inspects the
218
+ * failing message again, so a second overflow on the retry falls
219
+ * through naturally as a `failed` turn.
220
+ *
221
+ * Mutations on success:
222
+ * - pops the failure assistant message that pi-agent pushed for the
223
+ * overflow (an empty-text marker with `stopReason: "error"` and
224
+ * `errorMessage`) so the retry's transcript does not carry it.
225
+ * `agent.continue()`'s own run lifecycle resets `errorMessage`
226
+ * before the retry, so we do not clear it here;
227
+ * - advances `this.wireGuardHorizon.evictionHorizon` to the smallest
228
+ * value at which `findEvictionHorizon` reports the dispatched list
229
+ * shrinks to at most `floor(n / 2)` observable messages, where `n`
230
+ * excludes the failure message itself.
231
+ *
232
+ * Emits one informational `system` event describing the compaction so
233
+ * surfaces can show a notice. The message reports the *actual*
234
+ * post-eviction drop count (after `MIN_HISTORY_TAIL` clamping and
235
+ * orphan-head skipping), not the half-of-n target.
236
+ *
237
+ * Returns `false` when:
238
+ * - the parent agent did not fail (`errorMessage` is unset);
239
+ * - the failure is not a context overflow (rate limit, transport
240
+ * error, etc. — left as-is for the caller to surface);
241
+ * - no horizon advance is possible (e.g. the existing horizon already
242
+ * covers every evictable message, or the history is too short to
243
+ * satisfy `MIN_HISTORY_TAIL`). In those cases retrying with the
244
+ * same context would still overflow, so we accept the failure
245
+ * instead of paying a second pointless request.
246
+ */
247
+ protected tryRecoverFromContextOverflow(agent: Agent): boolean;
189
248
  protected updateMemoryAfterAgentRun(messages: AgentMessage[], options: TurnOptions | undefined): Promise<void>;
190
249
  private refreshMemoryContextPack;
191
250
  /**
@@ -196,7 +255,7 @@ export declare class TurnRunner {
196
255
  resolveMemoryActorModel(options: TurnOptions | undefined): string;
197
256
  protected createAgent(input: AgentConfigInput, onControlResult?: (result: TurnRunnerControlResult) => void): Agent;
198
257
  protected createMemoryTransform(): (messages: AgentMessage[]) => Promise<AgentMessage[]>;
199
- private readonly wireGuardHorizon;
258
+ protected readonly wireGuardHorizon: WireGuardHorizon;
200
259
  getSkills(): Promise<readonly Skill[]>;
201
260
  /** System-prompt files (AGENTS.md by default) that resolved on disk for this session. */
202
261
  getResolvedAgentFiles(): Promise<readonly TurnAgentFile[]>;
@@ -216,6 +275,13 @@ export declare class TurnRunner {
216
275
  skills?: readonly Skill[];
217
276
  }): string;
218
277
  private askUserQuestion;
278
+ /**
279
+ * Lifts the internal `AgentWorkerOutcome` shape into the public
280
+ * `TurnTerminalEvent` shape returned by `runTurnChain`. `runTurnChain`
281
+ * attaches `usage` / `effectiveContextWindow` / `contextWindowUsage`
282
+ * at the chain boundary, so this conversion intentionally omits them.
283
+ */
284
+ private outcomeToTerminal;
219
285
  /**
220
286
  * Single source of truth for per-turn option precedence:
221
287
  * explicit turn options → carried-over state base → runner config → defaults.