@adecore/agents 0.39.0 → 0.40.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.
- package/dist/async.d.ts +5 -0
- package/dist/async.js +13 -5
- package/dist/chat/acp-protocol.js +3 -5
- package/dist/chat/chat-core.js +0 -2
- package/dist/chat/chat-process.d.ts +1 -0
- package/dist/chat/chat-process.js +1 -1
- package/dist/chat/chat-session.js +6 -8
- package/dist/chat/claude-protocol.js +1 -6
- package/dist/chat/claude-transcript.js +1 -3
- package/dist/chat/codex-protocol.js +1 -6
- package/dist/chat/projector.js +1 -3
- package/dist/chat/request-summary.js +1 -3
- package/dist/coded-error.js +1 -5
- package/dist/context/argv.js +1 -2
- package/dist/context/verb.js +1 -3
- package/dist/error-text.d.ts +1 -0
- package/dist/error-text.js +4 -0
- package/dist/fields.d.ts +3 -0
- package/dist/fields.js +12 -0
- package/dist/fs.d.ts +1 -0
- package/dist/fs.js +8 -4
- package/dist/lineage.js +11 -5
- package/dist/messages/notice-store.js +3 -4
- package/dist/providers/accounts/acp-account.js +3 -20
- package/dist/providers/accounts/service.d.ts +1 -2
- package/dist/providers/accounts/service.js +5 -9
- package/dist/providers/accounts/status.js +1 -3
- package/dist/providers/acp-agent-state.js +1 -3
- package/dist/providers/claude.js +0 -1
- package/dist/providers/codex.js +0 -1
- package/dist/providers/detect.js +1 -1
- package/dist/providers/shell-commands.js +1 -2
- package/dist/run-process.js +1 -3
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +5 -8
- package/dist/tasks/background-limit.js +3 -2
- package/dist/tasks/task-coordinator.js +11 -17
- package/dist/testing/watch.js +0 -1
- package/dist/usage/limits/monitor.js +1 -2
- package/dist/usage/readers/claude.js +3 -10
- package/dist/usage/readers/codex.js +2 -9
- package/dist/usage/readers/grok.js +3 -10
- package/dist/usage/readers/host-log.js +3 -10
- package/dist/usage/record.d.ts +2 -0
- package/dist/usage/record.js +13 -0
- package/package.json +6 -6
- package/src/async.ts +23 -5
- package/src/chat/acp-backend.ts +121 -0
- package/src/chat/acp-protocol.ts +172 -6
- package/src/chat/attachment-store.ts +21 -0
- package/src/chat/backend.ts +0 -2
- package/src/chat/bookmark-store.ts +23 -0
- package/src/chat/chat-core.ts +208 -7
- package/src/chat/chat-log.ts +19 -0
- package/src/chat/chat-process.ts +52 -1
- package/src/chat/chat-session.ts +401 -10
- package/src/chat/chat-store.ts +50 -0
- package/src/chat/chat-title.ts +9 -0
- package/src/chat/claude-backend.ts +50 -0
- package/src/chat/claude-protocol.ts +155 -8
- package/src/chat/claude-title.ts +10 -0
- package/src/chat/claude-transcript.ts +47 -4
- package/src/chat/codex-backend.ts +131 -0
- package/src/chat/codex-protocol.ts +180 -8
- package/src/chat/codex-thread.ts +23 -0
- package/src/chat/composer-preferences.ts +6 -0
- package/src/chat/context-breakdown.ts +11 -0
- package/src/chat/delta-coalescer.ts +8 -0
- package/src/chat/external-sessions.ts +28 -0
- package/src/chat/generated-image.ts +25 -0
- package/src/chat/index.ts +5 -0
- package/src/chat/input.ts +13 -0
- package/src/chat/json-rpc-transport.ts +28 -0
- package/src/chat/limit-resume.ts +13 -0
- package/src/chat/projector.ts +160 -4
- package/src/chat/readable-text.ts +3 -0
- package/src/chat/request-summary.ts +18 -4
- package/src/chat/subagent-projection.ts +3 -0
- package/src/chat/subagent-reader.ts +76 -0
- package/src/chat/subagent-settlement.ts +24 -0
- package/src/chat/thread-search.ts +13 -0
- package/src/chat/thread.ts +31 -0
- package/src/chat/ui-queries.ts +122 -0
- package/src/chat/ui-replies.ts +34 -0
- package/src/chat/visual-store.ts +49 -0
- package/src/chat/wake-chat.ts +3 -0
- package/src/client-sinks.ts +2 -0
- package/src/coded-error.ts +1 -5
- package/src/context/argv.ts +18 -2
- package/src/context/index.ts +1 -0
- package/src/context/refusal.ts +3 -0
- package/src/context/verb.ts +32 -4
- package/src/coordination/index.ts +10 -0
- package/src/error-text.ts +7 -0
- package/src/fields.ts +14 -0
- package/src/fs.ts +20 -4
- package/src/host/agent-host.ts +20 -0
- package/src/host/environment.ts +6 -0
- package/src/host/handlers.ts +18 -0
- package/src/host/memory-port.ts +4 -0
- package/src/host/wiring.ts +10 -0
- package/src/index.ts +1 -0
- package/src/lineage.ts +24 -5
- package/src/messages/deliver-message.ts +6 -0
- package/src/messages/deliver-notice.ts +13 -0
- package/src/messages/notice-store.ts +16 -4
- package/src/modes.ts +1 -0
- package/src/outbox/outbox-worker.ts +31 -0
- package/src/outbox/outbox.ts +5 -0
- package/src/providers/accounts/accounts.ts +36 -0
- package/src/providers/accounts/acp-account.ts +22 -27
- package/src/providers/accounts/launch.ts +4 -0
- package/src/providers/accounts/service.ts +110 -12
- package/src/providers/accounts/shadow-home.ts +10 -0
- package/src/providers/accounts/status.ts +13 -4
- package/src/providers/accounts/store.ts +25 -0
- package/src/providers/accounts/variables.ts +11 -1
- package/src/providers/acp-agent-state.ts +24 -3
- package/src/providers/acp-provider.ts +5 -0
- package/src/providers/acp-usage-log.ts +2 -0
- package/src/providers/catalog.ts +17 -0
- package/src/providers/claude.ts +10 -1
- package/src/providers/codex-rules.ts +3 -0
- package/src/providers/codex.ts +2 -1
- package/src/providers/detect.ts +6 -1
- package/src/providers/provider.ts +1 -1
- package/src/providers/registry.ts +18 -2
- package/src/providers/shell-commands.ts +37 -2
- package/src/record-directory.ts +19 -0
- package/src/run-process.ts +24 -3
- package/src/serializer.ts +3 -0
- package/src/skills.ts +73 -10
- package/src/tasks/background-limit.ts +9 -2
- package/src/tasks/end-children.ts +9 -0
- package/src/tasks/give-task.ts +8 -0
- package/src/tasks/task-coordinator.ts +81 -17
- package/src/tasks/task-store.ts +28 -0
- package/src/tasks/waiting-child.ts +21 -0
- package/src/tasks/wake-parent.ts +26 -0
- package/src/tasks/wiring.ts +22 -0
- package/src/testing/accounts.ts +2 -0
- package/src/testing/fake-acp-capture.ts +16 -0
- package/src/testing/fake-acp.ts +66 -0
- package/src/testing/fake-claude.ts +177 -0
- package/src/testing/fake-cli.ts +29 -0
- package/src/testing/fake-codex.ts +122 -0
- package/src/testing/manual-clock.ts +2 -0
- package/src/testing/watch.ts +7 -1
- package/src/title-file.ts +12 -0
- package/src/usage/accounts.ts +6 -0
- package/src/usage/aggregate.ts +19 -0
- package/src/usage/exchange.ts +12 -0
- package/src/usage/index-file.ts +27 -0
- package/src/usage/limits/monitor.ts +31 -3
- package/src/usage/limits/normalize.ts +30 -0
- package/src/usage/limits/probe.ts +17 -0
- package/src/usage/pricing.ts +35 -0
- package/src/usage/projects.ts +8 -0
- package/src/usage/readers/claude.ts +12 -9
- package/src/usage/readers/codex.ts +40 -8
- package/src/usage/readers/grok.ts +17 -9
- package/src/usage/readers/host-log.ts +9 -9
- package/src/usage/record.ts +22 -0
- package/src/usage/roots.ts +6 -0
- package/src/usage/scanner.ts +61 -0
- package/src/usage/usage-service.ts +14 -0
- package/src/watch-seam.ts +8 -0
- package/src/windows-command.ts +12 -0
package/src/chat/projector.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
import { isRecord } from '@adecore/agent-contracts';
|
|
12
12
|
import { clipText } from '@adecore/agent-contracts/text';
|
|
13
13
|
import type { BackendEvent } from './backend.ts';
|
|
14
|
+
import { str } from '../fields.ts';
|
|
14
15
|
import { estimateContextBreakdown } from './context-breakdown.ts';
|
|
15
16
|
import type { ChatThread } from './thread.ts';
|
|
16
17
|
|
|
@@ -41,14 +42,11 @@ function startsAgentTurn(event: BackendEvent): boolean {
|
|
|
41
42
|
return isMainAgentOutput(event) || ((event.type === 'approval.requested' || event.type === 'question.requested') && event.background !== true);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
function str(value: unknown): string | null {
|
|
45
|
-
return typeof value === 'string' ? value : null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
45
|
/* A markdown code block around text that may hold backticks of its own. */
|
|
49
46
|
function fenced(text: string): string {
|
|
50
47
|
const longest = Math.max(0, ...(text.match(/`+/g) ?? []).map((run) => run.length));
|
|
51
48
|
const fence = '`'.repeat(Math.max(3, longest + 1));
|
|
49
|
+
|
|
52
50
|
return `${fence}\n${text}\n${fence}`;
|
|
53
51
|
}
|
|
54
52
|
|
|
@@ -81,16 +79,20 @@ const MAX_SUMMARY_CHARS = 80;
|
|
|
81
79
|
export function summaryLine(summary: string): string {
|
|
82
80
|
const first = summary.split('\n').find((line) => line.trim() !== '') ?? '';
|
|
83
81
|
const text = first.replace(/\s+/g, ' ').trim();
|
|
82
|
+
|
|
84
83
|
if (text.length <= MAX_SUMMARY_CHARS) {
|
|
85
84
|
return text;
|
|
86
85
|
}
|
|
86
|
+
|
|
87
87
|
const cut = clipText(text, MAX_SUMMARY_CHARS);
|
|
88
88
|
const space = cut.lastIndexOf(' ');
|
|
89
|
+
|
|
89
90
|
return `${(space > MAX_SUMMARY_CHARS / 2 ? cut.slice(0, space) : cut).trimEnd()}...`;
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
function usageField(text: string, name: string): number {
|
|
93
94
|
const match = new RegExp(`${name}:\\s*(\\d+)`).exec(text);
|
|
95
|
+
|
|
94
96
|
return match ? Number(match[1]) : 0;
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -99,9 +101,11 @@ export function stripAgentFooter(output: string): { text: string | null; usage:
|
|
|
99
101
|
const match = AGENT_FOOTER.exec(output);
|
|
100
102
|
const text = (match ? output.slice(0, match.index) : output).trim();
|
|
101
103
|
const block = match?.[1];
|
|
104
|
+
|
|
102
105
|
const usage = block
|
|
103
106
|
? { totalTokens: usageField(block, 'subagent_tokens'), toolUses: usageField(block, 'tool_uses'), durationMs: usageField(block, 'duration_ms') }
|
|
104
107
|
: null;
|
|
108
|
+
|
|
105
109
|
return { text: text === '' || text === NO_OUTPUT ? null : text, usage };
|
|
106
110
|
}
|
|
107
111
|
|
|
@@ -155,12 +159,15 @@ export class ThreadProjector {
|
|
|
155
159
|
|
|
156
160
|
project(generation: number, event: BackendEvent): ChatEvent[] {
|
|
157
161
|
const events: ChatEvent[] = [];
|
|
162
|
+
|
|
158
163
|
// The CLI talks outside a turn when it wakes the agent itself: a background task it launched
|
|
159
164
|
// earlier settled. The work needs a turn of its own, or it lands in the thread unattached.
|
|
160
165
|
if (this.thread.info.activeTurnId === null && startsAgentTurn(event) && !this.restatesCall(generation, event)) {
|
|
161
166
|
this.openAgentTurn(events);
|
|
162
167
|
}
|
|
168
|
+
|
|
163
169
|
const info = this.thread.info;
|
|
170
|
+
|
|
164
171
|
switch (event.type) {
|
|
165
172
|
case 'session':
|
|
166
173
|
events.push(
|
|
@@ -174,30 +181,38 @@ export class ThreadProjector {
|
|
|
174
181
|
running: true
|
|
175
182
|
})
|
|
176
183
|
);
|
|
184
|
+
|
|
177
185
|
break;
|
|
178
186
|
case 'thinking.delta':
|
|
179
187
|
this.appendThinking(generation, event.ref, event.text, false, events);
|
|
188
|
+
|
|
180
189
|
break;
|
|
181
190
|
case 'permissions':
|
|
182
191
|
events.push(this.thread.patchInfo({ permissionMode: event.permissionMode, effectiveRuntimeMode: event.effectiveRuntimeMode }));
|
|
192
|
+
|
|
183
193
|
break;
|
|
184
194
|
case 'thinking.done':
|
|
185
195
|
this.appendThinking(generation, event.ref, event.text, true, events);
|
|
196
|
+
|
|
186
197
|
break;
|
|
187
198
|
case 'text.delta':
|
|
188
199
|
this.closeThinking(events);
|
|
189
200
|
this.appendText(this.itemId(generation, event.ref), event.text, events, event.phase);
|
|
201
|
+
|
|
190
202
|
break;
|
|
191
203
|
case 'text.done': {
|
|
192
204
|
if (event.parentRef) {
|
|
193
205
|
if (!this.insideNested(generation, event.parentRef)) {
|
|
194
206
|
this.appendSubagentText(generation, event.parentRef, event.ref, event.text, events);
|
|
195
207
|
}
|
|
208
|
+
|
|
196
209
|
break;
|
|
197
210
|
}
|
|
211
|
+
|
|
198
212
|
this.closeThinking(events);
|
|
199
213
|
const id = this.itemId(generation, event.ref);
|
|
200
214
|
const existing = this.thread.get(id);
|
|
215
|
+
|
|
201
216
|
events.push(
|
|
202
217
|
this.thread.upsert({
|
|
203
218
|
id,
|
|
@@ -214,6 +229,7 @@ export class ThreadProjector {
|
|
|
214
229
|
streaming: false
|
|
215
230
|
})
|
|
216
231
|
);
|
|
232
|
+
|
|
217
233
|
break;
|
|
218
234
|
}
|
|
219
235
|
case 'tool.started':
|
|
@@ -221,23 +237,31 @@ export class ThreadProjector {
|
|
|
221
237
|
if (event.parentRef === null) {
|
|
222
238
|
this.closeThinking(events);
|
|
223
239
|
}
|
|
240
|
+
|
|
224
241
|
this.startSubagent(generation, event, events);
|
|
242
|
+
|
|
225
243
|
break;
|
|
226
244
|
}
|
|
245
|
+
|
|
227
246
|
if (event.parentRef === null) {
|
|
228
247
|
this.closeThinking(events);
|
|
229
248
|
} else if (this.insideNested(generation, event.parentRef)) {
|
|
230
249
|
break;
|
|
231
250
|
}
|
|
251
|
+
|
|
232
252
|
this.startTool(generation, event, events);
|
|
253
|
+
|
|
233
254
|
break;
|
|
234
255
|
case 'tool.progress':
|
|
235
256
|
this.patchProgress(generation, event, events);
|
|
257
|
+
|
|
236
258
|
break;
|
|
237
259
|
case 'workflow.progress': {
|
|
238
260
|
const tool = this.thread.get(this.itemId(generation, event.ref));
|
|
261
|
+
|
|
239
262
|
if (tool?.kind === 'tool') {
|
|
240
263
|
const name = event.workflow.name ?? tool.workflow?.name ?? null;
|
|
264
|
+
|
|
241
265
|
events.push(
|
|
242
266
|
this.thread.upsert({
|
|
243
267
|
...tool,
|
|
@@ -245,32 +269,42 @@ export class ThreadProjector {
|
|
|
245
269
|
})
|
|
246
270
|
);
|
|
247
271
|
}
|
|
272
|
+
|
|
248
273
|
break;
|
|
249
274
|
}
|
|
250
275
|
case 'tool.output': {
|
|
251
276
|
const delta = this.thread.appendText(this.itemId(generation, event.ref), event.text);
|
|
277
|
+
|
|
252
278
|
if (delta) {
|
|
253
279
|
events.push(delta);
|
|
254
280
|
}
|
|
281
|
+
|
|
255
282
|
break;
|
|
256
283
|
}
|
|
257
284
|
case 'tool.done': {
|
|
258
285
|
const target = this.thread.get(this.itemId(generation, event.ref));
|
|
286
|
+
|
|
259
287
|
if (target?.kind === 'subagent') {
|
|
260
288
|
this.settleSubagent(target, event, events);
|
|
289
|
+
|
|
261
290
|
break;
|
|
262
291
|
}
|
|
292
|
+
|
|
263
293
|
if (target?.kind === 'tool' && target.name === 'Workflow' && event.state === 'done' && event.output?.startsWith(WORKFLOW_LAUNCHED)) {
|
|
264
294
|
// The call only launched the workflow; the row runs on past the turn until the workflow's task ends.
|
|
265
295
|
this.backgroundCalls.add(target.id);
|
|
266
296
|
events.push(this.thread.upsert({ ...target, output: event.output }));
|
|
297
|
+
|
|
267
298
|
break;
|
|
268
299
|
}
|
|
300
|
+
|
|
269
301
|
this.settleTool(generation, event, events);
|
|
302
|
+
|
|
270
303
|
break;
|
|
271
304
|
}
|
|
272
305
|
case 'approval.requested':
|
|
273
306
|
this.closeThinking(events);
|
|
307
|
+
|
|
274
308
|
events.push(
|
|
275
309
|
this.thread.upsert({
|
|
276
310
|
id: `approval-${event.requestId}`,
|
|
@@ -287,10 +321,13 @@ export class ThreadProjector {
|
|
|
287
321
|
decision: 'pending'
|
|
288
322
|
})
|
|
289
323
|
);
|
|
324
|
+
|
|
290
325
|
events.push(this.thread.setStatus('needs-you'));
|
|
326
|
+
|
|
291
327
|
break;
|
|
292
328
|
case 'question.requested':
|
|
293
329
|
this.closeThinking(events);
|
|
330
|
+
|
|
294
331
|
events.push(
|
|
295
332
|
this.thread.upsert({
|
|
296
333
|
id: `question-${event.requestId}`,
|
|
@@ -304,55 +341,72 @@ export class ThreadProjector {
|
|
|
304
341
|
state: 'pending'
|
|
305
342
|
})
|
|
306
343
|
);
|
|
344
|
+
|
|
307
345
|
events.push(this.thread.setStatus('needs-you'));
|
|
346
|
+
|
|
308
347
|
break;
|
|
309
348
|
case 'request.withdrawn':
|
|
310
349
|
this.withdraw(event.requestId, events);
|
|
350
|
+
|
|
311
351
|
break;
|
|
312
352
|
case 'task.started':
|
|
313
353
|
this.patchSubagentStart(generation, event, events);
|
|
354
|
+
|
|
314
355
|
break;
|
|
315
356
|
case 'task.progress':
|
|
316
357
|
this.patchSubagentProgress(generation, event, events);
|
|
358
|
+
|
|
317
359
|
break;
|
|
318
360
|
case 'task.model':
|
|
319
361
|
this.patchSubagentModel(generation, event, events);
|
|
362
|
+
|
|
320
363
|
break;
|
|
321
364
|
case 'task.done': {
|
|
322
365
|
if (event.ref !== null) {
|
|
323
366
|
this.pendingMetadata.delete(this.itemId(generation, event.ref));
|
|
324
367
|
}
|
|
368
|
+
|
|
325
369
|
// Keep one summary line for the next turn's header; the tool row already has the result. A nested
|
|
326
370
|
// agent reports to the agent that opened it, so it only names that turn when no row of the thread's own does.
|
|
327
371
|
const row = this.taskSubagent(generation, event.ref, event.taskId);
|
|
328
372
|
const nested = row?.parentToolUseId !== undefined || (event.ref !== null && this.unplacedCalls.has(event.ref));
|
|
373
|
+
|
|
329
374
|
if (event.ref !== null) {
|
|
330
375
|
this.unplacedCalls.delete(event.ref);
|
|
331
376
|
}
|
|
377
|
+
|
|
332
378
|
const line = event.summary === null ? '' : summaryLine(event.summary);
|
|
379
|
+
|
|
333
380
|
if (line !== '' && (!nested || this.taskSummary === null)) {
|
|
334
381
|
this.taskSummary = line;
|
|
335
382
|
}
|
|
383
|
+
|
|
336
384
|
if (row && (!nested || this.taskToolUseId === null)) {
|
|
337
385
|
this.taskToolUseId = row.toolUseId;
|
|
338
386
|
}
|
|
387
|
+
|
|
339
388
|
this.settleBackgroundSubagent(row, event, events);
|
|
340
389
|
this.settleLaunchedCall(generation, event, events);
|
|
390
|
+
|
|
341
391
|
break;
|
|
342
392
|
}
|
|
343
393
|
case 'background.started':
|
|
344
394
|
this.startBackground(generation, event, events);
|
|
395
|
+
|
|
345
396
|
break;
|
|
346
397
|
case 'background.ended': {
|
|
347
398
|
const background = info.background ?? [];
|
|
399
|
+
|
|
348
400
|
if (background.some((task) => task.id === event.taskId)) {
|
|
349
401
|
events.push(this.thread.patchInfo({ background: background.filter((task) => task.id !== event.taskId) }));
|
|
350
402
|
}
|
|
403
|
+
|
|
351
404
|
break;
|
|
352
405
|
}
|
|
353
406
|
case 'usage': {
|
|
354
407
|
const usage = info.usage;
|
|
355
408
|
const contextTokens = event.contextTokens ?? usage.contextTokens;
|
|
409
|
+
|
|
356
410
|
const next = {
|
|
357
411
|
...usage,
|
|
358
412
|
contextTokens,
|
|
@@ -366,6 +420,7 @@ export class ThreadProjector {
|
|
|
366
420
|
? {}
|
|
367
421
|
: { cacheTtlMs: event.cacheTtlMs, cacheExpiresAt: event.cacheExpiresAt })
|
|
368
422
|
};
|
|
423
|
+
|
|
369
424
|
if (
|
|
370
425
|
next.contextTokens !== usage.contextTokens ||
|
|
371
426
|
next.contextWindow !== usage.contextWindow ||
|
|
@@ -375,6 +430,7 @@ export class ThreadProjector {
|
|
|
375
430
|
) {
|
|
376
431
|
events.push(this.thread.patchInfo({ usage: next }));
|
|
377
432
|
}
|
|
433
|
+
|
|
378
434
|
break;
|
|
379
435
|
}
|
|
380
436
|
case 'compaction':
|
|
@@ -388,35 +444,44 @@ export class ThreadProjector {
|
|
|
388
444
|
preTokens: event.preTokens ?? (info.usage.contextTokens > 0 ? info.usage.contextTokens : null)
|
|
389
445
|
})
|
|
390
446
|
);
|
|
447
|
+
|
|
391
448
|
break;
|
|
392
449
|
case 'model':
|
|
393
450
|
events.push(this.thread.patchInfo({ model: event.model }));
|
|
451
|
+
|
|
394
452
|
break;
|
|
395
453
|
case 'goal': {
|
|
396
454
|
const goal = event.goal ?? undefined;
|
|
455
|
+
|
|
397
456
|
if (JSON.stringify(goal) !== JSON.stringify(info.goal)) {
|
|
398
457
|
events.push(this.thread.patchInfo({ goal }));
|
|
399
458
|
}
|
|
459
|
+
|
|
400
460
|
break;
|
|
401
461
|
}
|
|
402
462
|
case 'note':
|
|
403
463
|
events.push(this.note(event.level, event.text));
|
|
464
|
+
|
|
404
465
|
break;
|
|
405
466
|
case 'turn.done':
|
|
406
467
|
this.finishTurn(event.state, event.costUsd, event.error, events, event.native, event.limit);
|
|
468
|
+
|
|
407
469
|
break;
|
|
408
470
|
case 'failed':
|
|
409
471
|
events.push(this.note('error', event.message));
|
|
410
472
|
this.settleOpenItems(events, !event.processAlive, true);
|
|
411
473
|
this.closeTurn('error', 0, events);
|
|
412
474
|
events.push(this.thread.patchInfo({ status: 'error', activeTurnId: null }));
|
|
475
|
+
|
|
413
476
|
break;
|
|
414
477
|
case 'exit':
|
|
415
478
|
this.pendingMetadata.clear();
|
|
416
479
|
events.push(this.thread.patchInfo({ effectiveRuntimeMode: undefined, permissionMode: undefined }));
|
|
417
480
|
this.finishProcess(event.exitCode, event.stderr ?? null, events);
|
|
481
|
+
|
|
418
482
|
break;
|
|
419
483
|
}
|
|
484
|
+
|
|
420
485
|
return events;
|
|
421
486
|
}
|
|
422
487
|
|
|
@@ -428,6 +493,7 @@ export class ThreadProjector {
|
|
|
428
493
|
const taskToolUseId = this.taskToolUseId;
|
|
429
494
|
this.taskSummary = null;
|
|
430
495
|
this.taskToolUseId = null;
|
|
496
|
+
|
|
431
497
|
events.push(
|
|
432
498
|
this.thread.upsert({
|
|
433
499
|
id: turnId,
|
|
@@ -442,6 +508,7 @@ export class ThreadProjector {
|
|
|
442
508
|
costUsd: 0
|
|
443
509
|
})
|
|
444
510
|
);
|
|
511
|
+
|
|
445
512
|
events.push(this.thread.patchInfo({ status: this.thread.statusFor(turnId), activeTurnId: turnId }));
|
|
446
513
|
}
|
|
447
514
|
|
|
@@ -453,6 +520,7 @@ export class ThreadProjector {
|
|
|
453
520
|
*/
|
|
454
521
|
private settleOpenItems(events: ChatEvent[], processGone = false, dropRequests = processGone): void {
|
|
455
522
|
this.thinking = null;
|
|
523
|
+
|
|
456
524
|
for (const item of this.thread.list()) {
|
|
457
525
|
if (item.kind === 'assistant' && item.streaming) {
|
|
458
526
|
events.push(this.thread.upsert({ ...item, streaming: false }));
|
|
@@ -475,8 +543,10 @@ export class ThreadProjector {
|
|
|
475
543
|
if (item.background) {
|
|
476
544
|
return true;
|
|
477
545
|
}
|
|
546
|
+
|
|
478
547
|
const parentRef = item.parentToolUseId;
|
|
479
548
|
const parent = parentRef === undefined ? undefined : this.thread.find('subagent', (candidate) => candidate.toolUseId === parentRef);
|
|
549
|
+
|
|
480
550
|
return parent?.status === 'running' && this.outlivesTurn(parent);
|
|
481
551
|
}
|
|
482
552
|
|
|
@@ -503,24 +573,31 @@ export class ThreadProjector {
|
|
|
503
573
|
private appendThinking(generation: number, ref: string, text: string, done: boolean, events: ChatEvent[]): void {
|
|
504
574
|
const key = this.itemId(generation, ref);
|
|
505
575
|
const open = this.thinking;
|
|
576
|
+
|
|
506
577
|
if (open?.refs.has(key) && done) {
|
|
507
578
|
return;
|
|
508
579
|
}
|
|
580
|
+
|
|
509
581
|
if (!open && text.trim() === '') {
|
|
510
582
|
return;
|
|
511
583
|
}
|
|
584
|
+
|
|
512
585
|
if (!open) {
|
|
513
586
|
const id = `${key}:think`;
|
|
514
587
|
this.thinking = { id, refs: new Set([key]), last: key };
|
|
588
|
+
|
|
515
589
|
events.push(
|
|
516
590
|
this.thread.upsert({ id, kind: 'thinking', createdAt: this.now(), turnId: this.thread.info.activeTurnId, text, streaming: true, endedAt: null })
|
|
517
591
|
);
|
|
592
|
+
|
|
518
593
|
return;
|
|
519
594
|
}
|
|
595
|
+
|
|
520
596
|
const separator = open.last !== null && open.last !== key ? '\n\n' : '';
|
|
521
597
|
open.refs.add(key);
|
|
522
598
|
open.last = key;
|
|
523
599
|
const delta = this.thread.appendText(open.id, `${separator}${text}`);
|
|
600
|
+
|
|
524
601
|
if (delta) {
|
|
525
602
|
events.push(delta);
|
|
526
603
|
}
|
|
@@ -531,6 +608,7 @@ export class ThreadProjector {
|
|
|
531
608
|
const open = this.thinking;
|
|
532
609
|
this.thinking = null;
|
|
533
610
|
const item = open ? this.thread.get(open.id) : undefined;
|
|
611
|
+
|
|
534
612
|
if (item?.kind === 'thinking' && item.streaming) {
|
|
535
613
|
events.push(this.thread.upsert({ ...item, streaming: false, endedAt: this.now() }));
|
|
536
614
|
}
|
|
@@ -538,6 +616,7 @@ export class ThreadProjector {
|
|
|
538
616
|
|
|
539
617
|
private appendText(id: string, text: string, events: ChatEvent[], phase?: string): void {
|
|
540
618
|
const existing = this.thread.get(id);
|
|
619
|
+
|
|
541
620
|
if (!existing) {
|
|
542
621
|
events.push(
|
|
543
622
|
this.thread.upsert({
|
|
@@ -550,9 +629,12 @@ export class ThreadProjector {
|
|
|
550
629
|
streaming: true
|
|
551
630
|
})
|
|
552
631
|
);
|
|
632
|
+
|
|
553
633
|
return;
|
|
554
634
|
}
|
|
635
|
+
|
|
555
636
|
const delta = text === '' ? null : this.thread.appendText(id, text);
|
|
637
|
+
|
|
556
638
|
if (delta) {
|
|
557
639
|
events.push(delta);
|
|
558
640
|
}
|
|
@@ -563,9 +645,11 @@ export class ThreadProjector {
|
|
|
563
645
|
const existing = this.thread.get(id);
|
|
564
646
|
const previous = existing?.kind === 'tool' ? existing : undefined;
|
|
565
647
|
const parent = event.parentRef === null ? null : this.subagent(generation, event.parentRef);
|
|
648
|
+
|
|
566
649
|
if (parent && !existing && !this.takeChildSlot(parent, 0, events)) {
|
|
567
650
|
return;
|
|
568
651
|
}
|
|
652
|
+
|
|
569
653
|
const item: ChatToolItem = {
|
|
570
654
|
id,
|
|
571
655
|
kind: 'tool',
|
|
@@ -578,54 +662,69 @@ export class ThreadProjector {
|
|
|
578
662
|
state: previous?.state ?? 'running',
|
|
579
663
|
parentToolUseId: event.parentRef
|
|
580
664
|
};
|
|
665
|
+
|
|
581
666
|
if (previous?.progress) {
|
|
582
667
|
item.progress = previous.progress;
|
|
583
668
|
}
|
|
669
|
+
|
|
584
670
|
const changes = event.changes ?? previous?.changes;
|
|
671
|
+
|
|
585
672
|
if (changes) {
|
|
586
673
|
item.changes = changes;
|
|
587
674
|
}
|
|
675
|
+
|
|
588
676
|
events.push(this.thread.upsert(item));
|
|
589
677
|
}
|
|
590
678
|
|
|
591
679
|
private settleTool(generation: number, event: Extract<BackendEvent, { type: 'tool.done' }>, events: ChatEvent[]): void {
|
|
592
680
|
const tool = this.thread.get(this.itemId(generation, event.ref));
|
|
681
|
+
|
|
593
682
|
if (tool?.kind !== 'tool') {
|
|
594
683
|
return;
|
|
595
684
|
}
|
|
685
|
+
|
|
596
686
|
this.backgroundCalls.delete(tool.id);
|
|
687
|
+
|
|
597
688
|
if (tool.parentToolUseId !== null) {
|
|
598
689
|
const parent = this.subagent(generation, tool.parentToolUseId);
|
|
690
|
+
|
|
599
691
|
if (parent) {
|
|
600
692
|
this.budgetFor(parent).textBytes += (event.output ?? '').length;
|
|
601
693
|
}
|
|
602
694
|
}
|
|
695
|
+
|
|
603
696
|
// The result supersedes whatever progress said; a settled call carries none.
|
|
604
697
|
const settled: ChatToolItem = { ...tool, output: event.output ?? tool.output, state: event.state };
|
|
605
698
|
delete settled.progress;
|
|
699
|
+
|
|
606
700
|
if (event.changes) {
|
|
607
701
|
settled.changes = event.changes;
|
|
608
702
|
}
|
|
703
|
+
|
|
609
704
|
events.push(this.thread.upsert(settled));
|
|
610
705
|
}
|
|
611
706
|
|
|
612
707
|
private patchProgress(generation: number, event: Extract<BackendEvent, { type: 'tool.progress' }>, events: ChatEvent[]): void {
|
|
613
708
|
const tool = this.thread.get(this.itemId(generation, event.ref));
|
|
709
|
+
|
|
614
710
|
if (tool?.kind !== 'tool' || tool.state !== 'running') {
|
|
615
711
|
return;
|
|
616
712
|
}
|
|
713
|
+
|
|
617
714
|
// A null field means the CLI said nothing about it this time, so what it said before stands.
|
|
618
715
|
const progress: ChatToolProgress = {
|
|
619
716
|
startedAt: event.startedAt ?? tool.progress?.startedAt ?? null,
|
|
620
717
|
description: event.description ?? tool.progress?.description ?? null,
|
|
621
718
|
output: tool.progress?.output ?? null
|
|
622
719
|
};
|
|
720
|
+
|
|
623
721
|
events.push(this.thread.upsert({ ...tool, progress }));
|
|
624
722
|
}
|
|
625
723
|
|
|
626
724
|
private subagent(generation: number, ref: string): ChatSubagentItem | null {
|
|
627
725
|
const id = this.itemId(generation, ref);
|
|
628
726
|
const item = this.thread.get(id) ?? this.thread.get(this.wokenRows.get(id) ?? '');
|
|
727
|
+
|
|
629
728
|
return item?.kind === 'subagent' ? item : null;
|
|
630
729
|
}
|
|
631
730
|
|
|
@@ -635,9 +734,11 @@ export class ThreadProjector {
|
|
|
635
734
|
*/
|
|
636
735
|
private taskSubagent(generation: number, ref: string | null, taskId: string | null | undefined): ChatSubagentItem | null {
|
|
637
736
|
const byRef = ref === null ? null : this.subagent(generation, ref);
|
|
737
|
+
|
|
638
738
|
if (byRef || !taskId) {
|
|
639
739
|
return byRef;
|
|
640
740
|
}
|
|
741
|
+
|
|
641
742
|
return this.thread.list().findLast((item): item is ChatSubagentItem => item.kind === 'subagent' && item.native?.agentId === taskId) ?? null;
|
|
642
743
|
}
|
|
643
744
|
|
|
@@ -652,6 +753,7 @@ export class ThreadProjector {
|
|
|
652
753
|
private budgetFor(parent: ChatSubagentItem): { items: number; textBytes: number } {
|
|
653
754
|
const budget = this.budgets.get(parent.id) ?? { items: 0, textBytes: 0 };
|
|
654
755
|
this.budgets.set(parent.id, budget);
|
|
756
|
+
|
|
655
757
|
return budget;
|
|
656
758
|
}
|
|
657
759
|
|
|
@@ -661,14 +763,18 @@ export class ThreadProjector {
|
|
|
661
763
|
*/
|
|
662
764
|
private takeChildSlot(parent: ChatSubagentItem, textBytes: number, events: ChatEvent[]): boolean {
|
|
663
765
|
const budget = this.budgetFor(parent);
|
|
766
|
+
|
|
664
767
|
if (budget.items >= MAX_SUBAGENT_ITEMS || budget.textBytes >= MAX_SUBAGENT_TEXT) {
|
|
665
768
|
if (!parent.itemsTruncated) {
|
|
666
769
|
events.push(this.thread.upsert({ ...parent, itemsTruncated: true }));
|
|
667
770
|
}
|
|
771
|
+
|
|
668
772
|
return false;
|
|
669
773
|
}
|
|
774
|
+
|
|
670
775
|
budget.items += 1;
|
|
671
776
|
budget.textBytes += textBytes;
|
|
777
|
+
|
|
672
778
|
return true;
|
|
673
779
|
}
|
|
674
780
|
|
|
@@ -687,6 +793,7 @@ export class ThreadProjector {
|
|
|
687
793
|
const model = metadata?.model ?? previous?.model ?? (str(input.model) || undefined);
|
|
688
794
|
this.pendingMetadata.delete(id);
|
|
689
795
|
this.unplacedCalls.delete(event.ref);
|
|
796
|
+
|
|
690
797
|
const item: ChatSubagentItem = {
|
|
691
798
|
id,
|
|
692
799
|
kind: 'subagent',
|
|
@@ -710,7 +817,9 @@ export class ThreadProjector {
|
|
|
710
817
|
...(previous?.native ? { native: previous.native } : {}),
|
|
711
818
|
...(parentToolUseId === undefined ? {} : { parentToolUseId })
|
|
712
819
|
};
|
|
820
|
+
|
|
713
821
|
events.push(this.thread.upsert(item));
|
|
822
|
+
|
|
714
823
|
if (metadata?.start) {
|
|
715
824
|
this.patchSubagentStart(generation, metadata.start, events);
|
|
716
825
|
}
|
|
@@ -719,25 +828,33 @@ export class ThreadProjector {
|
|
|
719
828
|
/* What the CLI itself says about the delegation: which agent it is, and whether it blocks the turn. */
|
|
720
829
|
private patchSubagentStart(generation: number, event: Extract<BackendEvent, { type: 'task.started' }>, events: ChatEvent[]): void {
|
|
721
830
|
const existing = this.thread.get(this.itemId(generation, event.ref));
|
|
831
|
+
|
|
722
832
|
// An agent a subagent opened, whose call the stream never showed, has no row to hang under.
|
|
723
833
|
if (!existing && (event.depth ?? 1) > 1) {
|
|
724
834
|
this.unplacedCalls.add(event.ref);
|
|
725
835
|
this.deferMetadata(generation, event.ref, { start: event });
|
|
836
|
+
|
|
726
837
|
return;
|
|
727
838
|
}
|
|
839
|
+
|
|
728
840
|
if (!existing) {
|
|
729
841
|
this.startSubagent(generation, { type: 'tool.started', ref: event.ref, name: 'Agent', input: {}, parentRef: null }, events);
|
|
730
842
|
}
|
|
843
|
+
|
|
731
844
|
const item = this.taskSubagent(generation, event.ref, event.taskId);
|
|
845
|
+
|
|
732
846
|
if (!item) {
|
|
733
847
|
return;
|
|
734
848
|
}
|
|
849
|
+
|
|
735
850
|
// A settled agent that starts again under another call was woken by a message, and works on in the background.
|
|
736
851
|
const resumed = event.resumed === true || (item.status !== 'running' && item.toolUseId !== event.ref);
|
|
737
852
|
const ownCall = this.itemId(generation, item.toolUseId);
|
|
853
|
+
|
|
738
854
|
if (resumed && item.id !== ownCall) {
|
|
739
855
|
this.wokenRows.set(ownCall, item.id);
|
|
740
856
|
}
|
|
857
|
+
|
|
741
858
|
events.push(
|
|
742
859
|
this.thread.upsert({
|
|
743
860
|
...item,
|
|
@@ -762,6 +879,7 @@ export class ThreadProjector {
|
|
|
762
879
|
private deferMetadata(generation: number, ref: string, metadata: { model?: string; start?: Extract<BackendEvent, { type: 'task.started' }> }): void {
|
|
763
880
|
const id = this.itemId(generation, ref);
|
|
764
881
|
this.pendingMetadata.set(id, { ...this.pendingMetadata.get(id), ...metadata });
|
|
882
|
+
|
|
765
883
|
// Only unplaced snapshots are bounded; established child identities stay with their rows.
|
|
766
884
|
if (this.pendingMetadata.size > 512) {
|
|
767
885
|
this.pendingMetadata.delete(this.pendingMetadata.keys().next().value!);
|
|
@@ -770,10 +888,13 @@ export class ThreadProjector {
|
|
|
770
888
|
|
|
771
889
|
private patchSubagentModel(generation: number, event: Extract<BackendEvent, { type: 'task.model' }>, events: ChatEvent[]): void {
|
|
772
890
|
const item = this.subagent(generation, event.ref);
|
|
891
|
+
|
|
773
892
|
if (!item) {
|
|
774
893
|
this.deferMetadata(generation, event.ref, { model: event.model });
|
|
894
|
+
|
|
775
895
|
return;
|
|
776
896
|
}
|
|
897
|
+
|
|
777
898
|
if (item && item.model !== event.model) {
|
|
778
899
|
events.push(this.thread.upsert({ ...item, model: event.model }));
|
|
779
900
|
}
|
|
@@ -781,9 +902,11 @@ export class ThreadProjector {
|
|
|
781
902
|
|
|
782
903
|
private patchSubagentProgress(generation: number, event: Extract<BackendEvent, { type: 'task.progress' }>, events: ChatEvent[]): void {
|
|
783
904
|
const item = this.taskSubagent(generation, event.ref, event.taskId);
|
|
905
|
+
|
|
784
906
|
if (!item || item.status !== 'running') {
|
|
785
907
|
return;
|
|
786
908
|
}
|
|
909
|
+
|
|
787
910
|
events.push(
|
|
788
911
|
this.thread.upsert({
|
|
789
912
|
...item,
|
|
@@ -797,14 +920,18 @@ export class ThreadProjector {
|
|
|
797
920
|
/* Text a subagent wrote: part of its row, and the candidate for the report it ends with. */
|
|
798
921
|
private appendSubagentText(generation: number, parentRef: string, ref: string, text: string, events: ChatEvent[]): void {
|
|
799
922
|
const parent = this.subagent(generation, parentRef);
|
|
923
|
+
|
|
800
924
|
if (!parent) {
|
|
801
925
|
return;
|
|
802
926
|
}
|
|
927
|
+
|
|
803
928
|
const id = this.itemId(generation, ref);
|
|
804
929
|
const existing = this.thread.get(id);
|
|
930
|
+
|
|
805
931
|
if (!existing && !this.takeChildSlot(parent, text.length, events)) {
|
|
806
932
|
return;
|
|
807
933
|
}
|
|
934
|
+
|
|
808
935
|
events.push(
|
|
809
936
|
this.thread.upsert({
|
|
810
937
|
id,
|
|
@@ -816,7 +943,9 @@ export class ThreadProjector {
|
|
|
816
943
|
parentToolUseId: parentRef
|
|
817
944
|
})
|
|
818
945
|
);
|
|
946
|
+
|
|
819
947
|
const open = this.subagent(generation, parentRef);
|
|
948
|
+
|
|
820
949
|
if (open && text.trim() !== '') {
|
|
821
950
|
events.push(this.thread.upsert({ ...open, result: text }));
|
|
822
951
|
}
|
|
@@ -825,12 +954,16 @@ export class ThreadProjector {
|
|
|
825
954
|
/* The Agent call answered: a foreground agent is done, a background one has only been launched. */
|
|
826
955
|
private settleSubagent(item: ChatSubagentItem, event: Extract<BackendEvent, { type: 'tool.done' }>, events: ChatEvent[]): void {
|
|
827
956
|
const output = event.output ?? '';
|
|
957
|
+
|
|
828
958
|
if (output.startsWith(LAUNCH_PLACEHOLDER)) {
|
|
829
959
|
const outputFile = /output_file:\s*(\S+)/.exec(output)?.[1];
|
|
830
960
|
events.push(this.thread.upsert({ ...item, background: true, ...(outputFile ? { outputFile } : {}) }));
|
|
961
|
+
|
|
831
962
|
return;
|
|
832
963
|
}
|
|
964
|
+
|
|
833
965
|
const report = stripAgentFooter(output);
|
|
966
|
+
|
|
834
967
|
events.push(
|
|
835
968
|
this.thread.upsert({
|
|
836
969
|
...item,
|
|
@@ -847,16 +980,20 @@ export class ThreadProjector {
|
|
|
847
980
|
if (!item) {
|
|
848
981
|
return;
|
|
849
982
|
}
|
|
983
|
+
|
|
850
984
|
const full = event.summary?.trim() ?? '';
|
|
851
985
|
const line = full === '' ? null : summaryLine(full);
|
|
852
986
|
// The row says in one line what the agent did. When the CLI put its whole report in the
|
|
853
987
|
// summary, that report is the best one this row will ever get: it goes behind the fold.
|
|
854
988
|
const result = item.result ?? (line !== null && full !== line ? full : null);
|
|
989
|
+
|
|
855
990
|
// A resumed agent notifies more than once; one that already settled only learns what came of it.
|
|
856
991
|
if (item.status !== 'running') {
|
|
857
992
|
events.push(this.thread.upsert({ ...item, summary: line ?? item.summary, result }));
|
|
993
|
+
|
|
858
994
|
return;
|
|
859
995
|
}
|
|
996
|
+
|
|
860
997
|
events.push(
|
|
861
998
|
this.thread.upsert({
|
|
862
999
|
...item,
|
|
@@ -873,9 +1010,11 @@ export class ThreadProjector {
|
|
|
873
1010
|
/* A call whose work ran on past its own answer, such as a workflow, settled by the task it ran as. */
|
|
874
1011
|
private settleLaunchedCall(generation: number, event: Extract<BackendEvent, { type: 'task.done' }>, events: ChatEvent[]): void {
|
|
875
1012
|
const call = event.ref === null ? undefined : this.thread.get(this.itemId(generation, event.ref));
|
|
1013
|
+
|
|
876
1014
|
if (call?.kind !== 'tool' || call.state !== 'running' || !this.backgroundCalls.has(call.id)) {
|
|
877
1015
|
return;
|
|
878
1016
|
}
|
|
1017
|
+
|
|
879
1018
|
this.backgroundCalls.delete(call.id);
|
|
880
1019
|
const summary = event.summary?.trim() ?? '';
|
|
881
1020
|
const output = summary === '' ? call.output : [call.output, summary].filter((part) => part !== null && part !== '').join('\n\n');
|
|
@@ -887,15 +1026,20 @@ export class ThreadProjector {
|
|
|
887
1026
|
/* A shell or a monitor, told apart by the call that started it when the CLI's frame does not say. */
|
|
888
1027
|
private startBackground(generation: number, event: Extract<BackendEvent, { type: 'background.started' }>, events: ChatEvent[]): void {
|
|
889
1028
|
const background = this.thread.info.background ?? [];
|
|
1029
|
+
|
|
890
1030
|
if (background.some((task) => task.id === event.taskId)) {
|
|
891
1031
|
return;
|
|
892
1032
|
}
|
|
1033
|
+
|
|
893
1034
|
const call = event.ref === null ? undefined : this.thread.get(this.itemId(generation, event.ref));
|
|
894
1035
|
const tool = call?.kind === 'tool' ? call : null;
|
|
1036
|
+
|
|
895
1037
|
if (tool !== null) {
|
|
896
1038
|
this.backgroundCalls.add(tool.id);
|
|
897
1039
|
}
|
|
1040
|
+
|
|
898
1041
|
const input = tool && isRecord(tool.input) ? tool.input : {};
|
|
1042
|
+
|
|
899
1043
|
const task: ChatBackgroundTask = {
|
|
900
1044
|
id: event.taskId,
|
|
901
1045
|
kind: event.monitor || event.ref === null || tool?.name === 'Monitor' ? 'monitor' : 'shell',
|
|
@@ -903,16 +1047,20 @@ export class ThreadProjector {
|
|
|
903
1047
|
command: str(input.command),
|
|
904
1048
|
startedAt: this.now()
|
|
905
1049
|
};
|
|
1050
|
+
|
|
906
1051
|
events.push(this.thread.patchInfo({ background: [...background, task] }));
|
|
907
1052
|
}
|
|
908
1053
|
|
|
909
1054
|
private withdraw(requestId: string, events: ChatEvent[]): void {
|
|
910
1055
|
const approval = this.thread.get(`approval-${requestId}`);
|
|
1056
|
+
|
|
911
1057
|
if (approval?.kind === 'approval' && approval.decision === 'pending') {
|
|
912
1058
|
events.push(this.thread.upsert({ ...approval, decision: 'cancelled' }));
|
|
913
1059
|
events.push(this.thread.setStatus(this.thread.statusFor(this.thread.info.activeTurnId)));
|
|
914
1060
|
}
|
|
1061
|
+
|
|
915
1062
|
const question = this.thread.get(`question-${requestId}`);
|
|
1063
|
+
|
|
916
1064
|
if (question?.kind === 'question' && question.state === 'pending') {
|
|
917
1065
|
events.push(this.thread.upsert({ ...question, state: 'cancelled' }));
|
|
918
1066
|
events.push(this.thread.setStatus(this.thread.statusFor(this.thread.info.activeTurnId)));
|
|
@@ -930,9 +1078,11 @@ export class ThreadProjector {
|
|
|
930
1078
|
if (error) {
|
|
931
1079
|
events.push(this.note('error', error));
|
|
932
1080
|
}
|
|
1081
|
+
|
|
933
1082
|
this.settleOpenItems(events, false, state === 'aborted');
|
|
934
1083
|
this.closeTurn(state, costUsd, events, native, limit);
|
|
935
1084
|
const usage = this.thread.info.usage;
|
|
1085
|
+
|
|
936
1086
|
events.push(
|
|
937
1087
|
this.thread.patchInfo({
|
|
938
1088
|
status: this.thread.statusFor(null),
|
|
@@ -948,12 +1098,15 @@ export class ThreadProjector {
|
|
|
948
1098
|
// Waiting on a person between turns is not a turn the process took down with it.
|
|
949
1099
|
const { status, activeTurnId } = this.thread.info;
|
|
950
1100
|
const busy = status === 'running' || (status === 'needs-you' && activeTurnId !== null);
|
|
1101
|
+
|
|
951
1102
|
if (busy || (exitCode !== null && exitCode !== 0)) {
|
|
952
1103
|
const reason = exitCode === null ? `${this.providerName} stopped` : `${this.providerName} exited with code ${exitCode}`;
|
|
953
1104
|
// A note shows its first line and folds the rest open as markdown.
|
|
954
1105
|
events.push(this.note('error', stderr === null ? reason : `${reason}\n\n${fenced(stderr)}`));
|
|
955
1106
|
}
|
|
1107
|
+
|
|
956
1108
|
this.closeTurn(busy ? 'error' : 'done', 0, events);
|
|
1109
|
+
|
|
957
1110
|
// Whatever ran beside the turns went with the process.
|
|
958
1111
|
events.push(
|
|
959
1112
|
this.thread.patchInfo({
|
|
@@ -974,9 +1127,11 @@ export class ThreadProjector {
|
|
|
974
1127
|
): void {
|
|
975
1128
|
const turnId = this.thread.info.activeTurnId;
|
|
976
1129
|
const turn = turnId ? this.thread.get(turnId) : undefined;
|
|
1130
|
+
|
|
977
1131
|
if (turn?.kind !== 'turn') {
|
|
978
1132
|
return;
|
|
979
1133
|
}
|
|
1134
|
+
|
|
980
1135
|
// The CLI reports what the whole chat cost so far; the turn keeps what it added.
|
|
981
1136
|
events.push(
|
|
982
1137
|
this.thread.upsert({
|
|
@@ -999,6 +1154,7 @@ export class ThreadProjector {
|
|
|
999
1154
|
level,
|
|
1000
1155
|
text
|
|
1001
1156
|
};
|
|
1157
|
+
|
|
1002
1158
|
return this.thread.upsert(item);
|
|
1003
1159
|
}
|
|
1004
1160
|
}
|