@inhuman.tools/harness-tui 0.0.0-bootstrap.0 → 0.2.0

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 (167) hide show
  1. package/LICENSE.md +7 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +2 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/interpreter/ComputedExecBox.d.ts +12 -0
  7. package/dist/interpreter/ComputedExecBox.d.ts.map +1 -0
  8. package/dist/interpreter/ComputedExecBox.js +88 -0
  9. package/dist/interpreter/ComputedExecBox.js.map +1 -0
  10. package/dist/interpreter/imageCapability.d.ts +9 -0
  11. package/dist/interpreter/imageCapability.d.ts.map +1 -0
  12. package/dist/interpreter/imageCapability.js +49 -0
  13. package/dist/interpreter/imageCapability.js.map +1 -0
  14. package/dist/interpreter/rpcCapability.d.ts +11 -0
  15. package/dist/interpreter/rpcCapability.d.ts.map +1 -0
  16. package/dist/interpreter/rpcCapability.js +91 -0
  17. package/dist/interpreter/rpcCapability.js.map +1 -0
  18. package/dist/interpreter/uiCapability.d.ts +34 -0
  19. package/dist/interpreter/uiCapability.d.ts.map +1 -0
  20. package/dist/interpreter/uiCapability.js +207 -0
  21. package/dist/interpreter/uiCapability.js.map +1 -0
  22. package/dist/schemas/index.d.ts +2 -0
  23. package/dist/schemas/index.d.ts.map +1 -0
  24. package/dist/schemas/index.js +2 -0
  25. package/dist/schemas/index.js.map +1 -0
  26. package/dist/schemas/worker-boot.d.ts +14 -0
  27. package/dist/schemas/worker-boot.d.ts.map +1 -0
  28. package/dist/schemas/worker-boot.js +15 -0
  29. package/dist/schemas/worker-boot.js.map +1 -0
  30. package/dist/tui/ack-prompt.d.ts +53 -0
  31. package/dist/tui/ack-prompt.d.ts.map +1 -0
  32. package/dist/tui/ack-prompt.js +61 -0
  33. package/dist/tui/ack-prompt.js.map +1 -0
  34. package/dist/tui/agents-drawer.d.ts +47 -0
  35. package/dist/tui/agents-drawer.d.ts.map +1 -0
  36. package/dist/tui/agents-drawer.js +314 -0
  37. package/dist/tui/agents-drawer.js.map +1 -0
  38. package/dist/tui/ask-shortcuts.d.ts +59 -0
  39. package/dist/tui/ask-shortcuts.d.ts.map +1 -0
  40. package/dist/tui/ask-shortcuts.js +96 -0
  41. package/dist/tui/ask-shortcuts.js.map +1 -0
  42. package/dist/tui/channel.d.ts +59 -0
  43. package/dist/tui/channel.d.ts.map +1 -0
  44. package/dist/tui/channel.js +27 -0
  45. package/dist/tui/channel.js.map +1 -0
  46. package/dist/tui/host-verbs.d.ts +7 -0
  47. package/dist/tui/host-verbs.d.ts.map +1 -0
  48. package/dist/tui/host-verbs.js +19 -0
  49. package/dist/tui/host-verbs.js.map +1 -0
  50. package/dist/tui/host.d.ts +25 -0
  51. package/dist/tui/host.d.ts.map +1 -0
  52. package/dist/tui/host.js +380 -0
  53. package/dist/tui/host.js.map +1 -0
  54. package/dist/tui/omnibox-seat.d.ts +132 -0
  55. package/dist/tui/omnibox-seat.d.ts.map +1 -0
  56. package/dist/tui/omnibox-seat.js +182 -0
  57. package/dist/tui/omnibox-seat.js.map +1 -0
  58. package/dist/tui/paint-controller.d.ts +170 -0
  59. package/dist/tui/paint-controller.d.ts.map +1 -0
  60. package/dist/tui/paint-controller.js +965 -0
  61. package/dist/tui/paint-controller.js.map +1 -0
  62. package/dist/tui/paint-dom.d.ts +336 -0
  63. package/dist/tui/paint-dom.d.ts.map +1 -0
  64. package/dist/tui/paint-dom.js +709 -0
  65. package/dist/tui/paint-dom.js.map +1 -0
  66. package/dist/tui/paint-source.d.ts +57 -0
  67. package/dist/tui/paint-source.d.ts.map +1 -0
  68. package/dist/tui/paint-source.js +570 -0
  69. package/dist/tui/paint-source.js.map +1 -0
  70. package/dist/tui/paint-transport.d.ts +42 -0
  71. package/dist/tui/paint-transport.d.ts.map +1 -0
  72. package/dist/tui/paint-transport.js +207 -0
  73. package/dist/tui/paint-transport.js.map +1 -0
  74. package/dist/tui/paint-types.d.ts +15 -0
  75. package/dist/tui/paint-types.d.ts.map +1 -0
  76. package/dist/tui/paint-types.js +6 -0
  77. package/dist/tui/paint-types.js.map +1 -0
  78. package/dist/tui/paint-widgets.d.ts +94 -0
  79. package/dist/tui/paint-widgets.d.ts.map +1 -0
  80. package/dist/tui/paint-widgets.js +877 -0
  81. package/dist/tui/paint-widgets.js.map +1 -0
  82. package/dist/tui/parse-args.d.ts +3 -0
  83. package/dist/tui/parse-args.d.ts.map +1 -0
  84. package/dist/tui/parse-args.js +13 -0
  85. package/dist/tui/parse-args.js.map +1 -0
  86. package/dist/tui/permissions-view.d.ts +93 -0
  87. package/dist/tui/permissions-view.d.ts.map +1 -0
  88. package/dist/tui/permissions-view.js +174 -0
  89. package/dist/tui/permissions-view.js.map +1 -0
  90. package/dist/tui/present-visual/approve.d.ts +10 -0
  91. package/dist/tui/present-visual/approve.d.ts.map +1 -0
  92. package/dist/tui/present-visual/approve.js +156 -0
  93. package/dist/tui/present-visual/approve.js.map +1 -0
  94. package/dist/tui/present-visual/dump.d.ts +47 -0
  95. package/dist/tui/present-visual/dump.d.ts.map +1 -0
  96. package/dist/tui/present-visual/dump.js +144 -0
  97. package/dist/tui/present-visual/dump.js.map +1 -0
  98. package/dist/tui/present-visual/journey-teardown.d.ts +3 -0
  99. package/dist/tui/present-visual/journey-teardown.d.ts.map +1 -0
  100. package/dist/tui/present-visual/journey-teardown.js +188 -0
  101. package/dist/tui/present-visual/journey-teardown.js.map +1 -0
  102. package/dist/tui/present-visual/journey.d.ts +15 -0
  103. package/dist/tui/present-visual/journey.d.ts.map +1 -0
  104. package/dist/tui/present-visual/journey.js +88 -0
  105. package/dist/tui/present-visual/journey.js.map +1 -0
  106. package/dist/tui/present-visual/raster.d.ts +3 -0
  107. package/dist/tui/present-visual/raster.d.ts.map +1 -0
  108. package/dist/tui/present-visual/raster.js +75 -0
  109. package/dist/tui/present-visual/raster.js.map +1 -0
  110. package/dist/tui/queue-shortcuts.d.ts +13 -0
  111. package/dist/tui/queue-shortcuts.d.ts.map +1 -0
  112. package/dist/tui/queue-shortcuts.js +23 -0
  113. package/dist/tui/queue-shortcuts.js.map +1 -0
  114. package/dist/tui/read-widen.d.ts +14 -0
  115. package/dist/tui/read-widen.d.ts.map +1 -0
  116. package/dist/tui/read-widen.js +45 -0
  117. package/dist/tui/read-widen.js.map +1 -0
  118. package/dist/tui/rewind-chrome.d.ts +29 -0
  119. package/dist/tui/rewind-chrome.d.ts.map +1 -0
  120. package/dist/tui/rewind-chrome.js +77 -0
  121. package/dist/tui/rewind-chrome.js.map +1 -0
  122. package/dist/tui/rust-present.d.ts +37 -0
  123. package/dist/tui/rust-present.d.ts.map +1 -0
  124. package/dist/tui/rust-present.js +205 -0
  125. package/dist/tui/rust-present.js.map +1 -0
  126. package/dist/tui/spinner-phase.d.ts +24 -0
  127. package/dist/tui/spinner-phase.d.ts.map +1 -0
  128. package/dist/tui/spinner-phase.js +25 -0
  129. package/dist/tui/spinner-phase.js.map +1 -0
  130. package/dist/tui/statusline-path.d.ts +10 -0
  131. package/dist/tui/statusline-path.d.ts.map +1 -0
  132. package/dist/tui/statusline-path.js +42 -0
  133. package/dist/tui/statusline-path.js.map +1 -0
  134. package/dist/tui/sugarcoat-paint.d.ts +14 -0
  135. package/dist/tui/sugarcoat-paint.d.ts.map +1 -0
  136. package/dist/tui/sugarcoat-paint.js +146 -0
  137. package/dist/tui/sugarcoat-paint.js.map +1 -0
  138. package/dist/tui/tape-spans.d.ts +64 -0
  139. package/dist/tui/tape-spans.d.ts.map +1 -0
  140. package/dist/tui/tape-spans.js +93 -0
  141. package/dist/tui/tape-spans.js.map +1 -0
  142. package/dist/tui/tea.d.ts +75 -0
  143. package/dist/tui/tea.d.ts.map +1 -0
  144. package/dist/tui/tea.js +2952 -0
  145. package/dist/tui/tea.js.map +1 -0
  146. package/dist/tui/vt-paint.d.ts +12 -0
  147. package/dist/tui/vt-paint.d.ts.map +1 -0
  148. package/dist/tui/vt-paint.js +123 -0
  149. package/dist/tui/vt-paint.js.map +1 -0
  150. package/dist/tui/wait-phrase.d.ts +31 -0
  151. package/dist/tui/wait-phrase.d.ts.map +1 -0
  152. package/dist/tui/wait-phrase.js +26 -0
  153. package/dist/tui/wait-phrase.js.map +1 -0
  154. package/dist/tui/widget-host.d.ts +34 -0
  155. package/dist/tui/widget-host.d.ts.map +1 -0
  156. package/dist/tui/widget-host.js +80 -0
  157. package/dist/tui/widget-host.js.map +1 -0
  158. package/dist/tui/work-cells.d.ts +47 -0
  159. package/dist/tui/work-cells.d.ts.map +1 -0
  160. package/dist/tui/work-cells.js +606 -0
  161. package/dist/tui/work-cells.js.map +1 -0
  162. package/dist/tui/worker-entry.d.ts +2 -0
  163. package/dist/tui/worker-entry.d.ts.map +1 -0
  164. package/dist/tui/worker-entry.js +98 -0
  165. package/dist/tui/worker-entry.js.map +1 -0
  166. package/package.json +84 -4
  167. package/README.md +0 -1
@@ -0,0 +1,965 @@
1
+ /**
2
+ * Worker write tree. Tea reactions call into this; declared MST actions
3
+ * equality-before-write. Attach the writer after the first populate.
4
+ */
5
+ import { DefaultedMap } from "@here.build/collections";
6
+ import { trace } from "@inhuman.tools/harness-diagnostics";
7
+ import { CallJob, cacheStatusNote, CompactJob, contextNeedFromChunks, debugMode, FileMutateToolJob, isNoteClaimed, ProgrammaticJob, reactChunks, SessionTurn, tapeCompletion, ToolCallJob, shellTailOf, TurnApprovalNoteChunk, TurnNotificationChunk, TurnSteerChunk, } from "@inhuman.tools/harness-model";
8
+ import { shellFormatGeneration, agentCallName, compactJobPaintText, notificationPaintText, askPaintHead, createAskTapeCut, } from "@inhuman.tools/harness-sdk/text";
9
+ import { comparer, computed, reaction } from "mobx";
10
+ import { destroy, isAlive, isStateTreeNode } from "mobx-state-tree";
11
+ import { createAgents, createCompletion, createComposer, createPaintRoot, createPreview, createRewind, createStatusline, createCompact, createRecall, createSpeech, createStack, createTurn, createWorkCell, failureOf, speechOf, thinkingOf, } from "./paint-dom.js";
12
+ import { createPaintWriter } from "./paint-transport.js";
13
+ import { workCellFace, workCellFuse } from "./work-cells.js";
14
+ export function paintComposerFace(input) {
15
+ const dropPicker = input.isolated;
16
+ const chrome = {
17
+ placeholder: input.placeholder,
18
+ annotation: input.annotation,
19
+ joinAnnotation: input.joinAnnotation,
20
+ hotkeys: input.hotkeys,
21
+ errorAnnotation: input.errorAnnotation ?? null,
22
+ statusItems: input.statusItems,
23
+ quotaWindows: input.quotaWindows,
24
+ contextUsed: input.contextUsed,
25
+ contextLimit: input.contextLimit,
26
+ contourRole: input.contourRole,
27
+ progress: input.progress,
28
+ progressColor: input.progressColor,
29
+ progressGlowAt: input.progressGlowAt,
30
+ progressAnnotation: input.progressAnnotation,
31
+ commandChoices: dropPicker ? undefined : input.commandChoices,
32
+ commandAction: dropPicker ? undefined : input.commandAction,
33
+ selectedCommandKey: dropPicker ? null : (input.selectedCommandKey ?? null),
34
+ scrollOffset: input.scrollOffset ?? 0,
35
+ stackedBody: input.stackedBody,
36
+ stackedViewport: input.stackedViewport,
37
+ stackedNumbered: input.stackedNumbered,
38
+ stackedLang: input.stackedLang,
39
+ };
40
+ if (input.oauthWaiting) {
41
+ return {
42
+ mode: "waiting",
43
+ interactive: false,
44
+ draft: "Waiting for the browser…",
45
+ cursor: 0,
46
+ mark: null,
47
+ ...chrome,
48
+ };
49
+ }
50
+ if (input.readOnly === true) {
51
+ return {
52
+ mode: "normal",
53
+ interactive: false,
54
+ readOnly: true,
55
+ draft: input.overlayBody ?? input.draft.draft,
56
+ cursor: 0,
57
+ mark: null,
58
+ ...chrome,
59
+ };
60
+ }
61
+ if ((input.busy && !input.inputting) || input.processing) {
62
+ return {
63
+ mode: "waiting",
64
+ interactive: false,
65
+ draft: input.overlayBody ?? input.draft.draft,
66
+ cursor: input.draft.cursor,
67
+ mark: input.draft.mark,
68
+ ...chrome,
69
+ };
70
+ }
71
+ if (input.secret) {
72
+ return {
73
+ mode: "secret",
74
+ interactive: true,
75
+ draft: "•".repeat(input.draft.draft.length),
76
+ cursor: input.draft.cursor,
77
+ mark: input.draft.mark,
78
+ ...chrome,
79
+ };
80
+ }
81
+ if (input.isolated) {
82
+ return {
83
+ mode: "isolated",
84
+ interactive: true,
85
+ draft: input.coverDraft.draft,
86
+ cursor: input.coverDraft.cursor,
87
+ mark: input.coverDraft.mark,
88
+ ...chrome,
89
+ };
90
+ }
91
+ return {
92
+ mode: "normal",
93
+ interactive: true,
94
+ draft: input.overlayBody ?? input.draft.draft,
95
+ cursor: input.draft.cursor,
96
+ mark: input.draft.mark,
97
+ ...chrome,
98
+ };
99
+ }
100
+ /** User cutoff. `terminate` is the cancellation proposal, not a crash to paint. */
101
+ export function isCompletionCutoff(chunk) {
102
+ if (chunk.outcome !== "failed")
103
+ return false;
104
+ return chunk.endCause === "cancel" || (chunk.endDetail ?? "").trim() === "terminate";
105
+ }
106
+ export function completionPaintFields(chunk, cutOff = false) {
107
+ const pewCutoff = isCompletionCutoff(chunk);
108
+ const live = chunk.outcome == null && !cutOff;
109
+ const cutoff = pewCutoff || (cutOff && chunk.outcome == null);
110
+ const failed = chunk.outcome === "failed" && !pewCutoff && !cutOff;
111
+ const homed = chunk.plexus != null;
112
+ const report = live && homed ? chunk.report : null;
113
+ const text = live ? (typeof report?.text === "string" ? report.text : "") || chunk.responseText : chunk.responseText;
114
+ const thought = live
115
+ ? (typeof report?.thought === "string" ? report.thought : "") || chunk.responseThought
116
+ : chunk.responseThought;
117
+ const thoughtKind = live
118
+ ? typeof report?.thoughtKind === "string"
119
+ ? report.thoughtKind
120
+ : (chunk.responseThoughtKind ?? "")
121
+ : (chunk.responseThoughtKind ?? "");
122
+ const startedAt = chunk.startedAtMs != null && Number.isFinite(chunk.startedAtMs)
123
+ ? chunk.startedAtMs
124
+ : live && typeof report?.startedAt === "number" && Number.isFinite(report.startedAt)
125
+ ? report.startedAt
126
+ : null;
127
+ const thoughtLive = live && chunk.thoughtEndedAtMs == null && text.trim() === "";
128
+ const retryUntilMs = live && typeof report?.retryUntilMs === "number" && Number.isFinite(report.retryUntilMs)
129
+ ? report.retryUntilMs
130
+ : null;
131
+ return {
132
+ state: failed ? "failed" : live ? "live" : "successful",
133
+ workerPresent: live ? chunk.workerPresent : false,
134
+ model: chunk.model,
135
+ startedAt,
136
+ durationMs: thoughtLive
137
+ ? null
138
+ : (chunk.thoughtDurationMs() ??
139
+ (startedAt == null ? null : Math.max(0, (chunk.thoughtEndedAtMs ?? startedAt) - startedAt))),
140
+ thoughtLive,
141
+ retryUntilMs,
142
+ text,
143
+ thought,
144
+ thoughtKind,
145
+ endDetail: cutoff ? "" : (chunk.endDetail ?? ""),
146
+ failReason: cutoff ? "" : (chunk.failReason ?? ""),
147
+ debugNote: debugMode() ? cacheStatusNote(chunk) : "",
148
+ atMs: speechAtMs(chunk, startedAt),
149
+ };
150
+ }
151
+ function speechAtMs(chunk, startedAt) {
152
+ if (chunk.thoughtEndedAtMs != null && Number.isFinite(chunk.thoughtEndedAtMs)) {
153
+ return chunk.thoughtEndedAtMs;
154
+ }
155
+ if (chunk.endedAtMs != null && Number.isFinite(chunk.endedAtMs))
156
+ return chunk.endedAtMs;
157
+ return startedAt;
158
+ }
159
+ function promptAtMs(turn) {
160
+ if (turn.startedAtMs != null && Number.isFinite(turn.startedAtMs))
161
+ return turn.startedAtMs;
162
+ for (const chunk of reactChunks(turn)) {
163
+ const pew = tapeCompletion(chunk);
164
+ if (pew == null)
165
+ continue;
166
+ if (pew.startedAtMs != null && Number.isFinite(pew.startedAtMs))
167
+ return pew.startedAtMs;
168
+ }
169
+ return null;
170
+ }
171
+ export function createPaintController(post, epoch = "paint", opts = {}) {
172
+ const root = createPaintRoot();
173
+ const tape = createStack();
174
+ const composer = createComposer();
175
+ const statusline = createStatusline();
176
+ root.appendChild(tape);
177
+ root.appendChild(composer);
178
+ root.appendChild(statusline);
179
+ const writer = createPaintWriter(epoch, post);
180
+ const turns = new DefaultedMap(mintTurnHandle);
181
+ const previewTurns = new DefaultedMap(mintTurnHandle);
182
+ let attached = false;
183
+ let ask = null;
184
+ let wait = null;
185
+ let agents = null;
186
+ let preview = null;
187
+ let rewind = null;
188
+ let recappedSpeech = null;
189
+ return {
190
+ root,
191
+ composer,
192
+ attach() {
193
+ if (attached)
194
+ return;
195
+ attached = true;
196
+ writer.attach(root);
197
+ },
198
+ projectAsk(face) {
199
+ if (face === null) {
200
+ if (ask === null)
201
+ return;
202
+ const at = root.children.indexOf(ask);
203
+ if (at !== -1)
204
+ root.removeChildAt(at);
205
+ ask = null;
206
+ return;
207
+ }
208
+ if (ask === null) {
209
+ ask = createWorkCell("ask");
210
+ const composerAt = root.children.indexOf(composer);
211
+ root.insertChildAt(composerAt === -1 ? root.children.length : composerAt, ask);
212
+ }
213
+ applyWorkCell(ask, face, face.key.startsWith("ask:") ? face.key.slice(4) : face.key);
214
+ },
215
+ projectWait(chrome) {
216
+ trace("harness:paint", chrome === null
217
+ ? "projectWait clear"
218
+ : `projectWait ${chrome.occupancy == null ? "error" : chrome.occupancy.kind}`);
219
+ if (chrome === null) {
220
+ if (wait === null)
221
+ return;
222
+ const at = root.children.indexOf(wait);
223
+ if (at !== -1)
224
+ root.removeChildAt(at);
225
+ wait = null;
226
+ return;
227
+ }
228
+ if (wait === null) {
229
+ wait = createWorkCell("wait");
230
+ const composerAt = root.children.indexOf(composer);
231
+ root.insertChildAt(composerAt === -1 ? root.children.length : composerAt, wait);
232
+ }
233
+ const occupancy = chrome.occupancy;
234
+ wait.setFace({
235
+ name: occupancy == null ? "error" : occupancy.kind,
236
+ object: chrome.error,
237
+ status: occupancy?.label ?? "",
238
+ state: occupancy == null ? "fail" : "live",
239
+ indent: false,
240
+ receiptKind: "none",
241
+ receiptText: "",
242
+ diffOld: "",
243
+ diffNew: "",
244
+ diffPath: "",
245
+ startedAt: occupancy == null || occupancy.elapsed !== true ? null : (occupancy.startedAt ?? Date.now()),
246
+ retryUntilMs: occupancy?.retryUntilMs,
247
+ });
248
+ },
249
+ projectStatusline(face) {
250
+ statusline.setCwd(face.cwd);
251
+ statusline.setCwdRoot(face.cwdRoot);
252
+ statusline.setLeft(face.left);
253
+ statusline.setNote(face.note);
254
+ },
255
+ projectAgents(face) {
256
+ if (face == null) {
257
+ if (agents === null)
258
+ return;
259
+ const at = root.children.indexOf(agents);
260
+ if (at !== -1)
261
+ root.removeChildAt(at);
262
+ agents = null;
263
+ return;
264
+ }
265
+ if (agents === null) {
266
+ agents = createAgents();
267
+ root.appendChild(agents);
268
+ }
269
+ agents.setFace(face);
270
+ },
271
+ projectRewind(face) {
272
+ if (rewind === null) {
273
+ rewind = createRewind();
274
+ root.appendChild(rewind);
275
+ }
276
+ rewind.setFace(face.atTip, face.past, face.future, face.pastTotal, face.futureTotal, face.seatRim);
277
+ },
278
+ projectComposer(face) {
279
+ composer.setDraft(face.draft);
280
+ composer.setCursor(face.cursor);
281
+ composer.setMark(face.mark);
282
+ composer.setInteractive(face.interactive);
283
+ composer.setReadOnly(face.readOnly === true);
284
+ composer.setMode(face.mode);
285
+ composer.setPlaceholder(face.placeholder ?? "");
286
+ composer.setAnnotation(face.annotation ?? "");
287
+ composer.setJoinAnnotation(face.joinAnnotation ?? "");
288
+ composer.setHotkeys(face.hotkeys ?? []);
289
+ composer.setError(face.errorAnnotation?.level ?? null, face.errorAnnotation?.message ?? "");
290
+ composer.setStatusItems(face.statusItems ?? []);
291
+ composer.setQuotaWindows(face.quotaWindows ?? []);
292
+ composer.setContextUsed(face.contextUsed ?? 0);
293
+ composer.setContextLimit(face.contextLimit ?? 0);
294
+ composer.setContourRole(face.contourRole ?? "");
295
+ composer.setProgress(face.progress ?? null);
296
+ composer.setProgressColor(face.progressColor ?? "");
297
+ composer.setProgressGlowAt(face.progressGlowAt ?? null);
298
+ composer.setProgressAnnotation(face.progressAnnotation ?? "");
299
+ composer.setCommandChoices(face.commandChoices === undefined ? null : face.commandChoices);
300
+ composer.setCommandAction(face.commandAction === undefined ? null : face.commandAction);
301
+ composer.setSelectedCommandKey(face.selectedCommandKey);
302
+ composer.setScrollOffset(face.scrollOffset);
303
+ composer.setStackedBody(face.stackedBody ?? "");
304
+ composer.setStackedViewport(face.stackedViewport ?? 0);
305
+ composer.setStackedNumbered(face.stackedNumbered === true);
306
+ composer.setStackedLang(face.stackedLang ?? "");
307
+ },
308
+ flush() {
309
+ writer.flush();
310
+ },
311
+ syncSpine(nextTurns, pendingSteers = []) {
312
+ syncSpineInto(tape, turns, nextTurns, opts.agentGlance, opts.kernel, pendingSteers, opts.cancelAsked, opts.blobs);
313
+ },
314
+ projectPinRecap(turn, title = "", hotkeys = []) {
315
+ const nextSpeech = turn !== null && turns.has(turn) ? turns.get(turn).prompt : null;
316
+ if (recappedSpeech !== null &&
317
+ recappedSpeech !== nextSpeech &&
318
+ (!isStateTreeNode(recappedSpeech) || isAlive(recappedSpeech))) {
319
+ recappedSpeech.setRecap(false, "", []);
320
+ }
321
+ if (nextSpeech !== null && (!isStateTreeNode(nextSpeech) || isAlive(nextSpeech))) {
322
+ nextSpeech.setRecap(true, title, hotkeys);
323
+ }
324
+ recappedSpeech = nextSpeech;
325
+ },
326
+ syncPreviewSpine(nextTurns, parentModel) {
327
+ if (nextTurns === null) {
328
+ if (preview === null)
329
+ return;
330
+ const at = root.children.indexOf(preview);
331
+ if (at !== -1)
332
+ root.removeChildAt(at);
333
+ preview = null;
334
+ for (const handle of previewTurns.values())
335
+ disposeHandle(handle);
336
+ previewTurns.clear();
337
+ return;
338
+ }
339
+ if (preview === null) {
340
+ preview = createPreview();
341
+ root.appendChild(preview);
342
+ }
343
+ const glance = parentModel === undefined
344
+ ? opts.agentGlance
345
+ : (job) => {
346
+ const base = opts.agentGlance?.(job);
347
+ if (base == null)
348
+ return { status: "", model: parentModel };
349
+ return { status: base.status, model: (base.model ?? "").trim() || parentModel };
350
+ };
351
+ syncSpineInto(preview, previewTurns, nextTurns, glance, opts.kernel, [], undefined, opts.blobs);
352
+ },
353
+ [Symbol.dispose]() {
354
+ writer.dispose();
355
+ for (const handle of turns.values())
356
+ disposeHandle(handle);
357
+ for (const handle of previewTurns.values())
358
+ disposeHandle(handle);
359
+ turns.clear();
360
+ previewTurns.clear();
361
+ destroy(root);
362
+ },
363
+ };
364
+ }
365
+ function pushLeftoverJudgeCompletions(handle, work, livingCompletions, wanted, cutOff) {
366
+ for (const child of work.children) {
367
+ const pew = tapeCompletion(child);
368
+ if (pew == null || livingCompletions.has(pew))
369
+ continue;
370
+ livingCompletions.add(pew);
371
+ const node = handle.completions.get(pew);
372
+ watchCompletion(handle, pew, node, cutOff, true);
373
+ wanted.push(node);
374
+ }
375
+ }
376
+ function compactFocus(turn, job) {
377
+ if (job.reason === "user") {
378
+ const prompt = turn.prompt.trim();
379
+ if (prompt !== "")
380
+ return prompt;
381
+ }
382
+ const chunks = reactChunks(turn);
383
+ const index = chunks.indexOf(job);
384
+ const before = index === -1 ? chunks : chunks.slice(0, index);
385
+ const tail = turn.turnTail instanceof SessionTurn ? turn.turnTail.contextNeed : null;
386
+ return contextNeedFromChunks(before, tail)?.focus ?? "";
387
+ }
388
+ function childrenIntact(node, expected) {
389
+ if (expected === null || node.children.length !== expected.length)
390
+ return false;
391
+ for (let i = 0; i < expected.length; i += 1) {
392
+ const child = expected[i];
393
+ if (node.children[i] !== child)
394
+ return false;
395
+ if (isStateTreeNode(child) && !isAlive(child))
396
+ return false;
397
+ }
398
+ return true;
399
+ }
400
+ function syncTurn(handle, turn, pendingSteers, agentGlance, kernel, cancelAsked = false, blobs) {
401
+ const sig = [
402
+ trackTurn(turn),
403
+ cancelAsked,
404
+ pendingSteers.map((row) => [row, row.text.trim() === ""]),
405
+ agentGlance,
406
+ kernel?.hideFloorTools,
407
+ ];
408
+ if (handle.spineSig !== null &&
409
+ trackEqual(handle.spineSig, sig) &&
410
+ (!isStateTreeNode(handle.node) || isAlive(handle.node)) &&
411
+ (handle.prompt === null || !isStateTreeNode(handle.prompt) || isAlive(handle.prompt)) &&
412
+ childrenIntact(handle.node, handle.expectedChildren)) {
413
+ return;
414
+ }
415
+ const wanted = [];
416
+ watchPrompt(handle, turn);
417
+ if (handle.prompt != null)
418
+ wanted.push(handle.prompt);
419
+ const livingSteers = new Set();
420
+ const livingCompletions = new Set();
421
+ const livingNotifications = new Set();
422
+ const livingCompact = new Set();
423
+ const livingTools = new Set();
424
+ const livingSchemes = new Set();
425
+ const askCut = createAskTapeCut(turn);
426
+ const cutOff = turn.cutOff || cancelAsked;
427
+ for (const chunk of reactChunks(turn)) {
428
+ if (chunk instanceof TurnSteerChunk) {
429
+ livingSteers.add(chunk);
430
+ const node = handle.steers.get(chunk);
431
+ watchSteer(handle, chunk, node, () => [chunk.steerText(), chunk.composedAtMs]);
432
+ wanted.push(node);
433
+ continue;
434
+ }
435
+ const pew = tapeCompletion(chunk);
436
+ if (pew != null) {
437
+ livingCompletions.add(pew);
438
+ const node = handle.completions.get(pew);
439
+ watchCompletion(handle, pew, node, cutOff, false);
440
+ wanted.push(node);
441
+ }
442
+ else if (chunk instanceof TurnNotificationChunk) {
443
+ livingNotifications.add(chunk);
444
+ const node = handle.notifications.get(chunk);
445
+ watchRecall(handle, chunk, node, () => notificationPaintText(chunk));
446
+ wanted.push(node);
447
+ }
448
+ else if (chunk instanceof CompactJob) {
449
+ if (!chunk.cutoff && !chunk.failed)
450
+ continue;
451
+ livingCompact.add(chunk);
452
+ const node = handle.compact.get(chunk);
453
+ watchRecall(handle, chunk, node, () => compactJobPaintText(chunk.notice, compactFocus(turn, chunk), chunk.failed));
454
+ wanted.push(node);
455
+ }
456
+ else if (chunk instanceof ToolCallJob) {
457
+ if (chunk.tapeHidden)
458
+ continue;
459
+ if (askCut.skipTool(chunk))
460
+ continue;
461
+ livingTools.add(chunk);
462
+ wanted.push(retainWorkCell(handle, handle.tools, chunk, false, agentGlance, cutOff, kernel, false, blobs));
463
+ pushLeftoverJudgeCompletions(handle, chunk, livingCompletions, wanted, cutOff);
464
+ }
465
+ else if (chunk instanceof ProgrammaticJob) {
466
+ if (askCut.skipScheme())
467
+ continue;
468
+ livingSchemes.add(chunk);
469
+ wanted.push(retainWorkCell(handle, handle.schemes, chunk, false, agentGlance, cutOff, kernel, false, blobs));
470
+ for (const inner of chunk.toolCalls) {
471
+ if (inner.tapeHidden)
472
+ continue;
473
+ if (askCut.skipTool(inner))
474
+ continue;
475
+ livingTools.add(inner);
476
+ wanted.push(retainWorkCell(handle, handle.tools, inner, true, agentGlance, cutOff, kernel, true, blobs));
477
+ pushLeftoverJudgeCompletions(handle, inner, livingCompletions, wanted, cutOff);
478
+ }
479
+ }
480
+ }
481
+ // Sitting steers are the next user prompt, not queue rows. Claim removes
482
+ // the admission and the chunk above takes the same seat.
483
+ for (const row of pendingSteers) {
484
+ const text = row.text.trim();
485
+ if (text === "")
486
+ continue;
487
+ livingSteers.add(row);
488
+ const node = handle.steers.get(row);
489
+ watchSteer(handle, row, node, () => [row.text, row.composedAtMs]);
490
+ wanted.push(node);
491
+ }
492
+ for (const key of handle.steers.keys()) {
493
+ if (livingSteers.has(key))
494
+ continue;
495
+ forgetWatch(handle, key);
496
+ handle.steers.delete(key);
497
+ }
498
+ const livingNotifyKeys = new Set();
499
+ for (const work of livingTools) {
500
+ if (!(work instanceof CallJob))
501
+ continue;
502
+ for (const note of work.notifications) {
503
+ if (isNoteClaimed(note) || note.message.trim() === "")
504
+ continue;
505
+ livingNotifyKeys.add(note);
506
+ const node = handle.liveNotifies.get(note);
507
+ watchText(handle, note, node, () => `from ${agentCallName(work)}: ${note.message}`);
508
+ wanted.push(node);
509
+ }
510
+ }
511
+ for (const note of handle.liveNotifies.keys()) {
512
+ if (livingNotifyKeys.has(note))
513
+ continue;
514
+ forgetWatch(handle, note);
515
+ handle.liveNotifies.delete(note);
516
+ }
517
+ for (const chunk of handle.completions.keys()) {
518
+ if (!livingCompletions.has(chunk)) {
519
+ forgetWatch(handle, chunk);
520
+ handle.completions.delete(chunk);
521
+ }
522
+ }
523
+ for (const chunk of handle.notifications.keys()) {
524
+ if (!livingNotifications.has(chunk)) {
525
+ forgetWatch(handle, chunk);
526
+ handle.notifications.delete(chunk);
527
+ }
528
+ }
529
+ for (const chunk of handle.compact.keys()) {
530
+ if (!livingCompact.has(chunk)) {
531
+ forgetWatch(handle, chunk);
532
+ handle.compact.delete(chunk);
533
+ }
534
+ }
535
+ for (const chunk of handle.tools.keys()) {
536
+ if (!livingTools.has(chunk)) {
537
+ forgetWatch(handle, chunk);
538
+ handle.cellSig.delete(chunk);
539
+ handle.tools.delete(chunk);
540
+ }
541
+ }
542
+ for (const chunk of handle.schemes.keys()) {
543
+ if (!livingSchemes.has(chunk)) {
544
+ forgetWatch(handle, chunk);
545
+ handle.cellSig.delete(chunk);
546
+ handle.schemes.delete(chunk);
547
+ }
548
+ }
549
+ syncChildren(handle.node, wanted);
550
+ handle.spineSig = sig;
551
+ handle.expectedChildren = wanted;
552
+ }
553
+ function sameSig(a, b) {
554
+ if (a?.length !== b.length)
555
+ return false;
556
+ for (let i = 0; i < a.length; i++) {
557
+ const x = a[i];
558
+ const y = b[i];
559
+ if (x === y)
560
+ continue;
561
+ if (Array.isArray(x) && Array.isArray(y) && sameSig(x, y))
562
+ continue;
563
+ return false;
564
+ }
565
+ return true;
566
+ }
567
+ function liveShellTail(work) {
568
+ if (work.name !== "shell" || work.completed)
569
+ return "";
570
+ return shellTailOf(work)?.text ?? "";
571
+ }
572
+ /** The face differs before and after the text behind a blob prior is in hand. */
573
+ function priorBlobResolved(work, blobs) {
574
+ const prior = work.prior;
575
+ return blobs !== undefined && prior?.kind === "blob" && blobs.peek(prior.blob.sha256) !== null;
576
+ }
577
+ function workCellSig(work, indent, nested, kernel, glance, cutOff, blobs) {
578
+ const fuse = workCellFuse(work, nested, kernel?.hideFloorTools === true);
579
+ const agent = work instanceof CallJob ? [glance.status ?? "", glance.model ?? ""] : [];
580
+ if (work instanceof ProgrammaticJob) {
581
+ return [
582
+ "scheme",
583
+ work.toolCallId,
584
+ work.args,
585
+ work.receiptJson,
586
+ work.completed,
587
+ work.cancelled,
588
+ indent,
589
+ fuse,
590
+ cutOff,
591
+ work.toolCalls.map((inner) => [
592
+ inner.toolCallId,
593
+ inner.args,
594
+ inner.receiptJson,
595
+ inner.completed,
596
+ inner.cancelled,
597
+ inner.policyResult,
598
+ inner.humanApprove,
599
+ inner.classifierResult,
600
+ ]),
601
+ ];
602
+ }
603
+ const mutate = work instanceof FileMutateToolJob
604
+ ? [
605
+ work.priorContent ?? "",
606
+ work.priorBlobJson ?? "",
607
+ priorBlobResolved(work, blobs),
608
+ work.existed,
609
+ work.priorLineStart,
610
+ ]
611
+ : [];
612
+ return [
613
+ work.toolCallId,
614
+ work.name,
615
+ work.args,
616
+ work.receiptJson,
617
+ work.completed,
618
+ work.cancelled,
619
+ work.policyResult,
620
+ work.policyMessage,
621
+ work.classifierResult,
622
+ work.classifierAbstain,
623
+ work.humanApprove,
624
+ work.approvalComment,
625
+ indent,
626
+ nested,
627
+ fuse,
628
+ cutOff,
629
+ work.name === "shell" ? shellFormatGeneration() : 0,
630
+ work.name === "shell" ? liveShellTail(work) : "",
631
+ mutate,
632
+ agent,
633
+ ];
634
+ }
635
+ function retainWorkCell(handle, map, work, indent, agentGlance, cutOff = false, kernel, nested = false, blobs) {
636
+ let node = map.get(work);
637
+ if (isStateTreeNode(node) && !isAlive(node)) {
638
+ map.delete(work);
639
+ handle.cellSig.delete(work);
640
+ forgetWatch(handle, work);
641
+ node = map.get(work);
642
+ }
643
+ const glance = work instanceof CallJob ? (agentGlance?.(work) ?? {}) : {};
644
+ const existing = handle.watches.get(work);
645
+ if (existing == null) {
646
+ const created = {
647
+ dispose: (() => undefined),
648
+ cutOff,
649
+ indent,
650
+ nested,
651
+ judge: false,
652
+ glance: agentGlance,
653
+ kernel,
654
+ blobs,
655
+ };
656
+ const apply = () => {
657
+ const nextGlance = work instanceof CallJob ? (created.glance?.(work) ?? {}) : {};
658
+ applyWorkCellFromSig(handle, node, work, created.indent, created.nested, created.kernel, nextGlance, created.cutOff, created.blobs);
659
+ };
660
+ created.dispose = reaction(() => {
661
+ const nextGlance = work instanceof CallJob ? (created.glance?.(work) ?? {}) : {};
662
+ return workCellSig(work, created.indent, created.nested, created.kernel, nextGlance, created.cutOff, created.blobs);
663
+ }, () => apply(), {
664
+ equals: (a, b) => sameSig(a, b),
665
+ });
666
+ apply();
667
+ handle.watches.set(work, created);
668
+ return node;
669
+ }
670
+ existing.indent = indent;
671
+ existing.nested = nested;
672
+ existing.cutOff = cutOff;
673
+ existing.glance = agentGlance;
674
+ existing.kernel = kernel;
675
+ existing.blobs = blobs;
676
+ applyWorkCellFromSig(handle, node, work, indent, nested, kernel, glance, cutOff, blobs);
677
+ return node;
678
+ }
679
+ function applyWorkCellFromSig(handle, node, work, indent, nested, kernel, glance, cutOff, blobs) {
680
+ const sig = workCellSig(work, indent, nested, kernel, glance, cutOff, blobs);
681
+ if (node.name !== "" && sameSig(handle.cellSig.get(work), sig))
682
+ return;
683
+ handle.cellSig.set(work, sig);
684
+ const face = workCellFace(work, work.toolCallId, indent, true, glance, kernel, blobs);
685
+ applyWorkCell(node, cutOff && face.state === "live" ? { ...face, state: "fail" } : face, work.toolCallId, workCellFuse(work, nested, kernel?.hideFloorTools === true));
686
+ }
687
+ function receiptTextForReplica(receipt) {
688
+ if (receipt.kind === "none" || receipt.kind === "diff")
689
+ return "";
690
+ if (receipt.kind === "parts")
691
+ return JSON.stringify(receipt.parts);
692
+ return receipt.text;
693
+ }
694
+ function applyWorkCell(node, face, toolCallId, fuse = false) {
695
+ const receipt = face.receipt;
696
+ node.setFace({
697
+ toolCallId,
698
+ name: face.name,
699
+ object: face.object,
700
+ status: face.status,
701
+ caption: face.caption,
702
+ state: face.state,
703
+ indent: face.indent,
704
+ split: face.split,
705
+ agent: face.agent,
706
+ pretty: face.pretty,
707
+ prettyBlock: face.prettyBlock,
708
+ response: face.response,
709
+ receiptKind: receipt.kind,
710
+ receiptText: receiptTextForReplica(receipt),
711
+ diffOld: receipt.kind === "diff" ? receipt.old : "",
712
+ diffNew: receipt.kind === "diff" ? receipt.new : "",
713
+ diffPath: receipt.kind === "diff" ? receipt.path : "",
714
+ diffLineStart: receipt.kind === "diff" ? (receipt.lineStart ?? 0) : 0,
715
+ fuse,
716
+ });
717
+ }
718
+ /**
719
+ * Remainder-classifier PEWs hang on the leaf tool. Their sealed `responseText`
720
+ * is the vote JSON (`{decision, uncertainty, severity, retrieve}`) — machine
721
+ * output, not a message, so it never paints as speech. Liveness, thought, and
722
+ * failure still paint; the leaf tool already holds the judging spinner.
723
+ */
724
+ function judgeCompletionPaintFields(pew, cutOff) {
725
+ const fields = completionPaintFields(pew, cutOff);
726
+ return fields.text === "" ? fields : { ...fields, text: "" };
727
+ }
728
+ function applyCompletion(node, fields) {
729
+ node.setState(fields.state);
730
+ node.setWorkerPresent(fields.workerPresent);
731
+ node.setModel(fields.model);
732
+ node.setStartedAt(fields.startedAt);
733
+ node.setDurationMs(fields.durationMs);
734
+ node.setThoughtLive(fields.thoughtLive);
735
+ node.setRetryUntilMs(fields.retryUntilMs);
736
+ node.setThoughtKind(fields.thoughtKind);
737
+ speechOf(node).updateText(fields.text);
738
+ speechOf(node).setAtMs(fields.atMs);
739
+ thinkingOf(node).updateText(fields.thought);
740
+ failureOf(node).updateEndDetail(fields.endDetail);
741
+ failureOf(node).updateFailReason(fields.failReason);
742
+ node.setDebugNote(fields.debugNote);
743
+ }
744
+ function mintTurnHandle() {
745
+ return {
746
+ node: createTurn(),
747
+ prompt: null,
748
+ promptWatch: null,
749
+ spineSig: null,
750
+ expectedChildren: null,
751
+ steers: new DefaultedMap(() => createSpeech("", "user")),
752
+ completions: new DefaultedMap(() => createCompletion()),
753
+ notifications: new DefaultedMap(() => createRecall()),
754
+ compact: new DefaultedMap(() => createCompact()),
755
+ liveNotifies: new DefaultedMap(() => createRecall()),
756
+ tools: new DefaultedMap(() => createWorkCell("tool")),
757
+ schemes: new DefaultedMap(() => createWorkCell("scheme")),
758
+ cellSig: new WeakMap(),
759
+ watches: new Map(),
760
+ };
761
+ }
762
+ function forgetWatch(handle, key) {
763
+ const watch = handle.watches.get(key);
764
+ if (watch == null)
765
+ return;
766
+ watch.dispose();
767
+ handle.watches.delete(key);
768
+ }
769
+ function disposeHandle(handle) {
770
+ handle.promptWatch?.();
771
+ handle.promptWatch = null;
772
+ for (const watch of handle.watches.values())
773
+ watch.dispose();
774
+ handle.watches.clear();
775
+ }
776
+ function watchPrompt(handle, turn) {
777
+ if (turn.prompt === "") {
778
+ handle.promptWatch?.();
779
+ handle.promptWatch = null;
780
+ handle.prompt = null;
781
+ return;
782
+ }
783
+ const apply = (text, atMs) => {
784
+ if (handle.prompt === null || (isStateTreeNode(handle.prompt) && !isAlive(handle.prompt))) {
785
+ handle.prompt = createSpeech(text, "user", atMs);
786
+ }
787
+ else {
788
+ handle.prompt.updateText(text);
789
+ handle.prompt.setAtMs(atMs);
790
+ }
791
+ };
792
+ if (handle.promptWatch != null) {
793
+ if (handle.prompt === null || (isStateTreeNode(handle.prompt) && !isAlive(handle.prompt))) {
794
+ apply(turn.prompt, promptAtMs(turn));
795
+ }
796
+ return;
797
+ }
798
+ // Apply before returning so syncTurn can place the node. Nested
799
+ // `fireImmediately` reactions stay queued until the outer spine
800
+ // reaction finishes, which left the tape with no user speech.
801
+ handle.promptWatch = reaction(() => [turn.prompt, promptAtMs(turn)], ([text, atMs]) => apply(text, atMs), { equals: comparer.shallow });
802
+ apply(turn.prompt, promptAtMs(turn));
803
+ }
804
+ /** Upcoming or claimed steer. Same user floor as the turn prompt. */
805
+ function watchSteer(handle, key, node, read) {
806
+ const apply = (text, atMs) => {
807
+ node.updateText(text);
808
+ node.setAtMs(Number.isFinite(atMs) && atMs > 0 ? atMs : null);
809
+ };
810
+ if (handle.watches.has(key)) {
811
+ const [text, atMs] = read();
812
+ apply(text, atMs);
813
+ return;
814
+ }
815
+ const dispose = reaction(read, ([text, atMs]) => apply(text, atMs), { equals: comparer.shallow });
816
+ const [text, atMs] = read();
817
+ apply(text, atMs);
818
+ handle.watches.set(key, {
819
+ dispose,
820
+ cutOff: false,
821
+ indent: false,
822
+ nested: false,
823
+ judge: false,
824
+ });
825
+ }
826
+ function watchCompletion(handle, pew, node, cutOff, judge) {
827
+ const existing = handle.watches.get(pew);
828
+ if (existing == null) {
829
+ const created = {
830
+ dispose: (() => undefined),
831
+ cutOff,
832
+ indent: false,
833
+ nested: false,
834
+ judge,
835
+ };
836
+ created.dispose = reaction(() => created.judge ? judgeCompletionPaintFields(pew, created.cutOff) : completionPaintFields(pew, created.cutOff), (fields) => applyCompletion(node, fields), { equals: comparer.shallow });
837
+ applyCompletion(node, created.judge ? judgeCompletionPaintFields(pew, created.cutOff) : completionPaintFields(pew, created.cutOff));
838
+ handle.watches.set(pew, created);
839
+ return;
840
+ }
841
+ if (existing.cutOff === cutOff && existing.judge === judge)
842
+ return;
843
+ existing.cutOff = cutOff;
844
+ existing.judge = judge;
845
+ applyCompletion(node, judge ? judgeCompletionPaintFields(pew, cutOff) : completionPaintFields(pew, cutOff));
846
+ }
847
+ function watchRecall(handle, key, node, text) {
848
+ watchText(handle, key, node, text);
849
+ }
850
+ function watchText(handle, key, node, text) {
851
+ if (handle.watches.has(key))
852
+ return;
853
+ const apply = (next) => {
854
+ node.updateText(next);
855
+ };
856
+ const dispose = reaction(text, apply);
857
+ apply(text());
858
+ handle.watches.set(key, {
859
+ dispose,
860
+ cutOff: false,
861
+ indent: false,
862
+ nested: false,
863
+ judge: false,
864
+ });
865
+ }
866
+ function syncSpineInto(host, handles, nextTurns, agentGlance, kernel, pendingSteers = [], cancelAsked, blobs) {
867
+ const wanted = [];
868
+ const living = new Set();
869
+ const head = nextTurns.at(-1);
870
+ for (const turn of nextTurns) {
871
+ living.add(turn);
872
+ const handle = handles.get(turn);
873
+ syncTurn(handle, turn, turn === head ? pendingSteers : [], agentGlance, kernel, cancelAsked?.(turn) === true, blobs);
874
+ wanted.push(handle.node);
875
+ }
876
+ for (const [turn, handle] of handles.entries()) {
877
+ if (living.has(turn))
878
+ continue;
879
+ disposeHandle(handle);
880
+ handles.delete(turn);
881
+ }
882
+ syncChildren(host, wanted);
883
+ }
884
+ function syncChildren(parent, wanted) {
885
+ for (const [i, element] of wanted.entries()) {
886
+ const node = element;
887
+ if (isStateTreeNode(node) && !isAlive(node))
888
+ continue;
889
+ parent.placeChild(i, node);
890
+ }
891
+ while (parent.children.length > wanted.length) {
892
+ parent.removeChildAt(parent.children.length - 1);
893
+ }
894
+ }
895
+ const turnTrackCache = new WeakMap();
896
+ /**
897
+ * Structure only — turn/chunk identity, cutoff, ask pin, hidden tools.
898
+ * PEW field values belong to per-cell reactions in PaintController.
899
+ */
900
+ export function trackTurn(turn) {
901
+ let box = turnTrackCache.get(turn);
902
+ if (box === undefined) {
903
+ box = computed(() => [
904
+ turn,
905
+ turn.reducerExited,
906
+ turn.cancelled,
907
+ turn.cancelRequested,
908
+ turn.prompt === "",
909
+ askPaintHead(turn),
910
+ reactChunks(turn).map((chunk) => trackChunk(chunk)),
911
+ ], { equals: trackEqual });
912
+ turnTrackCache.set(turn, box);
913
+ }
914
+ return box.get();
915
+ }
916
+ function leftoverIdentities(chunk) {
917
+ return chunk.children.map((child) => tapeCompletion(child) ?? child);
918
+ }
919
+ function trackTool(chunk) {
920
+ return [
921
+ chunk,
922
+ leftoverIdentities(chunk),
923
+ chunk instanceof CallJob
924
+ ? chunk.notifications.map((note) => [note, isNoteClaimed(note), note.message.trim() === ""])
925
+ : null,
926
+ ];
927
+ }
928
+ function trackChunk(chunk) {
929
+ const pew = tapeCompletion(chunk);
930
+ if (pew != null)
931
+ return pew;
932
+ if (chunk instanceof TurnNotificationChunk)
933
+ return chunk;
934
+ if (chunk instanceof TurnSteerChunk)
935
+ return [chunk, chunk.text];
936
+ if (chunk instanceof TurnApprovalNoteChunk)
937
+ return null;
938
+ if (chunk instanceof CompactJob)
939
+ return [chunk, chunk.cutoff, chunk.failed];
940
+ if (chunk instanceof ToolCallJob) {
941
+ if (chunk.tapeHidden)
942
+ return null;
943
+ return trackTool(chunk);
944
+ }
945
+ if (chunk instanceof ProgrammaticJob) {
946
+ return [chunk, chunk.toolCalls.map((inner) => (inner.tapeHidden ? null : trackTool(inner)))];
947
+ }
948
+ return chunk;
949
+ }
950
+ /** Identity-deep for arrays; `===` for turns, chunks, and primitives. */
951
+ export function trackEqual(a, b) {
952
+ if (a === b)
953
+ return true;
954
+ if (Array.isArray(a) && Array.isArray(b)) {
955
+ if (a.length !== b.length)
956
+ return false;
957
+ for (let i = 0; i < a.length; i++) {
958
+ if (!trackEqual(a[i], b[i]))
959
+ return false;
960
+ }
961
+ return true;
962
+ }
963
+ return false;
964
+ }
965
+ //# sourceMappingURL=paint-controller.js.map