@adecore/agents 0.38.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.d.ts +1 -0
- package/dist/chat/chat-core.js +9 -2
- package/dist/chat/chat-process.d.ts +1 -0
- package/dist/chat/chat-process.js +1 -1
- package/dist/chat/chat-session.d.ts +2 -0
- package/dist/chat/chat-session.js +9 -10
- 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 +218 -7
- package/src/chat/chat-log.ts +19 -0
- package/src/chat/chat-process.ts +52 -1
- package/src/chat/chat-session.ts +414 -13
- 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
|
@@ -10,27 +10,22 @@ import type {
|
|
|
10
10
|
} from '@adecore/agent-contracts';
|
|
11
11
|
import { isRecord } from '@adecore/agent-contracts';
|
|
12
12
|
import { readClaudeEvent } from '../usage/limits/normalize.ts';
|
|
13
|
+
import { num, str } from '../fields.ts';
|
|
13
14
|
import { claudeRuntimeMode } from '../providers/claude.ts';
|
|
14
15
|
import type { ApprovalDecision, BackendEvent } from './backend.ts';
|
|
15
16
|
|
|
16
17
|
type Frame = Record<string, unknown>;
|
|
17
18
|
|
|
18
|
-
function str(value: unknown): string | null {
|
|
19
|
-
return typeof value === 'string' ? value : null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function num(value: unknown): number {
|
|
23
|
-
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
19
|
// A tool result is a string or a list of content blocks; only the text is worth showing.
|
|
27
20
|
function resultText(content: unknown): string {
|
|
28
21
|
if (typeof content === 'string') {
|
|
29
22
|
return content;
|
|
30
23
|
}
|
|
24
|
+
|
|
31
25
|
if (!Array.isArray(content)) {
|
|
32
26
|
return '';
|
|
33
27
|
}
|
|
28
|
+
|
|
34
29
|
return content
|
|
35
30
|
.map((block) => (isRecord(block) && block.type === 'text' ? str(block.text) : null))
|
|
36
31
|
.filter((text): text is string => text !== null)
|
|
@@ -55,10 +50,13 @@ const CACHE_TTL_MS: Record<string, number> = { ephemeral_5m_input_tokens: 5 * 60
|
|
|
55
50
|
*/
|
|
56
51
|
function cacheTtlWritten(usage: unknown): number | null {
|
|
57
52
|
const creation = isRecord(usage) && isRecord(usage.cache_creation) ? usage.cache_creation : null;
|
|
53
|
+
|
|
58
54
|
if (creation === null) {
|
|
59
55
|
return null;
|
|
60
56
|
}
|
|
57
|
+
|
|
61
58
|
const written = Object.entries(CACHE_TTL_MS).filter(([field]) => num(creation[field]) > 0);
|
|
59
|
+
|
|
62
60
|
return written.length === 0 ? null : Math.min(...written.map(([, ttl]) => ttl));
|
|
63
61
|
}
|
|
64
62
|
|
|
@@ -83,14 +81,18 @@ function intOrNull(value: unknown): number | null {
|
|
|
83
81
|
function parseWorkflowProgress(entries: unknown[]): Pick<ChatWorkflow, 'phases' | 'agents'> {
|
|
84
82
|
const phases = new Map<number, ChatWorkflowPhase>();
|
|
85
83
|
const agents = new Map<number, ChatWorkflowAgent>();
|
|
84
|
+
|
|
86
85
|
for (const entry of entries) {
|
|
87
86
|
if (!isRecord(entry)) {
|
|
88
87
|
continue;
|
|
89
88
|
}
|
|
89
|
+
|
|
90
90
|
const index = intOrNull(entry.index);
|
|
91
|
+
|
|
91
92
|
if (index === null) {
|
|
92
93
|
continue;
|
|
93
94
|
}
|
|
95
|
+
|
|
94
96
|
if (entry.type === 'workflow_phase') {
|
|
95
97
|
phases.set(index, { index, title: str(entry.title) ?? '' });
|
|
96
98
|
} else if (entry.type === 'workflow_agent') {
|
|
@@ -99,6 +101,7 @@ function parseWorkflowProgress(entries: unknown[]): Pick<ChatWorkflow, 'phases'
|
|
|
99
101
|
const promptPreview = str(entry.promptPreview);
|
|
100
102
|
const resultPreview = str(entry.resultPreview);
|
|
101
103
|
const error = str(entry.error);
|
|
104
|
+
|
|
102
105
|
agents.set(index, {
|
|
103
106
|
index,
|
|
104
107
|
label: str(entry.label) ?? '',
|
|
@@ -115,7 +118,9 @@ function parseWorkflowProgress(entries: unknown[]): Pick<ChatWorkflow, 'phases'
|
|
|
115
118
|
});
|
|
116
119
|
}
|
|
117
120
|
}
|
|
121
|
+
|
|
118
122
|
const byIndex = (a: { index: number }, b: { index: number }): number => a.index - b.index;
|
|
123
|
+
|
|
119
124
|
return { phases: [...phases.values()].sort(byIndex), agents: [...agents.values()].sort(byIndex) };
|
|
120
125
|
}
|
|
121
126
|
|
|
@@ -124,6 +129,7 @@ function parseQuestions(input: unknown): ChatQuestion[] {
|
|
|
124
129
|
if (!isRecord(input) || !Array.isArray(input.questions)) {
|
|
125
130
|
return [];
|
|
126
131
|
}
|
|
132
|
+
|
|
127
133
|
return input.questions.filter(isRecord).map((question, index) => ({
|
|
128
134
|
id: String(index),
|
|
129
135
|
header: str(question.header) ?? '',
|
|
@@ -151,20 +157,27 @@ const GOAL_GONE = ['Goal cleared: ', 'Goal cleared after ', 'No goal set'];
|
|
|
151
157
|
|
|
152
158
|
export function claudeGoalFromCommand(text: string, current: ChatGoal | null): ChatGoal | null | undefined {
|
|
153
159
|
const said = text.trim();
|
|
160
|
+
|
|
154
161
|
if (said.startsWith(GOAL_SET)) {
|
|
155
162
|
const objective = said.slice(GOAL_SET.length).trim();
|
|
163
|
+
|
|
156
164
|
return objective === '' ? undefined : { objective, status: 'active', checks: 0 };
|
|
157
165
|
}
|
|
166
|
+
|
|
158
167
|
if (GOAL_GONE.some((prefix) => said.startsWith(prefix))) {
|
|
159
168
|
return null;
|
|
160
169
|
}
|
|
170
|
+
|
|
161
171
|
const active = GOAL_ACTIVE.exec(said);
|
|
172
|
+
|
|
162
173
|
if (!active) {
|
|
163
174
|
return undefined;
|
|
164
175
|
}
|
|
176
|
+
|
|
165
177
|
const objective = active[1]!;
|
|
166
178
|
const checks = /\d+/.exec(active[2]!)?.[0];
|
|
167
179
|
const lastCheck = active[3]?.trim();
|
|
180
|
+
|
|
168
181
|
return {
|
|
169
182
|
...(current?.objective === objective ? current : {}),
|
|
170
183
|
objective,
|
|
@@ -182,9 +195,11 @@ function nextOrdinal(counts: Map<string, number>, messageId: string): number {
|
|
|
182
195
|
const ordinal = counts.get(messageId) ?? 0;
|
|
183
196
|
counts.delete(messageId);
|
|
184
197
|
counts.set(messageId, ordinal + 1);
|
|
198
|
+
|
|
185
199
|
if (counts.size > COUNTED_MESSAGES) {
|
|
186
200
|
counts.delete(counts.keys().next().value!);
|
|
187
201
|
}
|
|
202
|
+
|
|
188
203
|
return ordinal;
|
|
189
204
|
}
|
|
190
205
|
|
|
@@ -196,11 +211,15 @@ export function claudeGoalCheck(text: string, current: ChatGoal | null): ChatGoa
|
|
|
196
211
|
if (current === null) {
|
|
197
212
|
return undefined;
|
|
198
213
|
}
|
|
214
|
+
|
|
199
215
|
const prefix = `Stop hook feedback:\n[${current.objective}]: `;
|
|
216
|
+
|
|
200
217
|
if (!text.startsWith(prefix)) {
|
|
201
218
|
return undefined;
|
|
202
219
|
}
|
|
220
|
+
|
|
203
221
|
const lastCheck = text.slice(prefix.length).trim();
|
|
222
|
+
|
|
204
223
|
return { ...current, status: 'active', checks: (current.checks ?? 0) + 1, ...(lastCheck === '' ? {} : { lastCheck }) };
|
|
205
224
|
}
|
|
206
225
|
|
|
@@ -284,65 +303,83 @@ export class ClaudeProtocol {
|
|
|
284
303
|
|
|
285
304
|
handle(frame: unknown): BackendEvent[] {
|
|
286
305
|
const events: BackendEvent[] = [];
|
|
306
|
+
|
|
287
307
|
if (!isRecord(frame)) {
|
|
288
308
|
return events;
|
|
289
309
|
}
|
|
310
|
+
|
|
290
311
|
switch (frame.type) {
|
|
291
312
|
case 'system':
|
|
292
313
|
this.handleSystem(frame, events);
|
|
314
|
+
|
|
293
315
|
break;
|
|
294
316
|
case 'stream_event':
|
|
295
317
|
this.handleStreamEvent(frame, events);
|
|
318
|
+
|
|
296
319
|
break;
|
|
297
320
|
case 'assistant':
|
|
298
321
|
this.handleAssistant(frame, events);
|
|
322
|
+
|
|
299
323
|
break;
|
|
300
324
|
case 'user':
|
|
301
325
|
this.handleUser(frame, events);
|
|
326
|
+
|
|
302
327
|
break;
|
|
303
328
|
case 'result':
|
|
304
329
|
this.handleResult(frame, events);
|
|
330
|
+
|
|
305
331
|
break;
|
|
306
332
|
case 'control_request':
|
|
307
333
|
this.handleControlRequest(frame, events);
|
|
334
|
+
|
|
308
335
|
break;
|
|
309
336
|
case 'control_response':
|
|
310
337
|
this.handleControlResponse(frame, events);
|
|
338
|
+
|
|
311
339
|
break;
|
|
312
340
|
case 'control_cancel_request': {
|
|
313
341
|
const requestId = str(frame.request_id);
|
|
342
|
+
|
|
314
343
|
if (requestId) {
|
|
315
344
|
this.pending.delete(requestId);
|
|
316
345
|
events.push({ type: 'request.withdrawn', requestId });
|
|
317
346
|
}
|
|
347
|
+
|
|
318
348
|
break;
|
|
319
349
|
}
|
|
320
350
|
case 'tool_progress':
|
|
321
351
|
this.handleToolProgress(frame, events);
|
|
352
|
+
|
|
322
353
|
break;
|
|
323
354
|
case 'rate_limit_event': {
|
|
324
355
|
this.noteRefusal(frame.rate_limit_info);
|
|
325
356
|
// The plan's own numbers, streamed while a turn runs; the usage monitor keeps them.
|
|
326
357
|
const update = readClaudeEvent(frame.rate_limit_info);
|
|
358
|
+
|
|
327
359
|
if (update !== null) {
|
|
328
360
|
events.push({ type: 'limits', update });
|
|
329
361
|
}
|
|
362
|
+
|
|
330
363
|
break;
|
|
331
364
|
}
|
|
332
365
|
default:
|
|
333
366
|
break;
|
|
334
367
|
}
|
|
368
|
+
|
|
335
369
|
return events;
|
|
336
370
|
}
|
|
337
371
|
|
|
338
372
|
/* The `control_response` that answers a permission request, or null when nothing waits under that id. */
|
|
339
373
|
approvalResponse(requestId: string, decision: ApprovalDecision, message?: string): unknown | null {
|
|
340
374
|
const pending = this.pending.get(requestId);
|
|
375
|
+
|
|
341
376
|
if (pending?.type !== 'approval') {
|
|
342
377
|
return null;
|
|
343
378
|
}
|
|
379
|
+
|
|
344
380
|
this.pending.delete(requestId);
|
|
345
381
|
const toolUseID = pending.toolUseId ?? undefined;
|
|
382
|
+
|
|
346
383
|
const response =
|
|
347
384
|
decision === 'deny'
|
|
348
385
|
? { behavior: 'deny', message: message?.trim() || 'The user declined this action', toolUseID }
|
|
@@ -353,25 +390,32 @@ export class ClaudeProtocol {
|
|
|
353
390
|
// The CLI's own suggestion of a rule; sending it back is what makes "always" stick.
|
|
354
391
|
...(decision === 'allow-always' && pending.suggestions.length > 0 ? { updatedPermissions: pending.suggestions } : {})
|
|
355
392
|
};
|
|
393
|
+
|
|
356
394
|
return { type: 'control_response', response: { subtype: 'success', request_id: requestId, response } };
|
|
357
395
|
}
|
|
358
396
|
|
|
359
397
|
/* The `control_response` that answers an AskUserQuestion, or null when nothing waits under that id. */
|
|
360
398
|
questionResponse(requestId: string, answers: Record<string, string>): unknown | null {
|
|
361
399
|
const pending = this.pending.get(requestId);
|
|
400
|
+
|
|
362
401
|
if (pending?.type !== 'question') {
|
|
363
402
|
return null;
|
|
364
403
|
}
|
|
404
|
+
|
|
365
405
|
this.pending.delete(requestId);
|
|
366
406
|
// The CLI keys answers by the question text, the client by the question's id.
|
|
367
407
|
const byText: Record<string, string> = {};
|
|
408
|
+
|
|
368
409
|
for (const question of parseQuestions(pending.input)) {
|
|
369
410
|
const given = answers[question.id];
|
|
411
|
+
|
|
370
412
|
if (given !== undefined) {
|
|
371
413
|
byText[question.question] = given;
|
|
372
414
|
}
|
|
373
415
|
}
|
|
416
|
+
|
|
374
417
|
const input = isRecord(pending.input) ? pending.input : {};
|
|
418
|
+
|
|
375
419
|
return {
|
|
376
420
|
type: 'control_response',
|
|
377
421
|
response: {
|
|
@@ -385,10 +429,13 @@ export class ClaudeProtocol {
|
|
|
385
429
|
/* The `control_response` that turns down an approval or a question the CLI still holds, or null when nothing waits under that id. */
|
|
386
430
|
declineResponse(requestId: string, message: string): unknown | null {
|
|
387
431
|
const pending = this.pending.get(requestId);
|
|
432
|
+
|
|
388
433
|
if (pending === undefined) {
|
|
389
434
|
return null;
|
|
390
435
|
}
|
|
436
|
+
|
|
391
437
|
this.pending.delete(requestId);
|
|
438
|
+
|
|
392
439
|
return {
|
|
393
440
|
type: 'control_response',
|
|
394
441
|
response: { subtype: 'success', request_id: requestId, response: { behavior: 'deny', message, toolUseID: pending.toolUseId ?? undefined } }
|
|
@@ -404,9 +451,11 @@ export class ClaudeProtocol {
|
|
|
404
451
|
const commands = Array.isArray(frame.slash_commands)
|
|
405
452
|
? frame.slash_commands.filter((command): command is string => typeof command === 'string')
|
|
406
453
|
: [];
|
|
454
|
+
|
|
407
455
|
const skills = Array.isArray(frame.skills) ? frame.skills.filter((skill): skill is string => typeof skill === 'string') : [];
|
|
408
456
|
const permissionMode = str(frame.permissionMode);
|
|
409
457
|
const effectiveRuntimeMode = permissionMode === null ? undefined : claudeRuntimeMode(permissionMode);
|
|
458
|
+
|
|
410
459
|
events.push({
|
|
411
460
|
type: 'session',
|
|
412
461
|
agentSessionId: str(frame.session_id),
|
|
@@ -425,9 +474,11 @@ export class ClaudeProtocol {
|
|
|
425
474
|
this.handleTaskUpdated(frame, events);
|
|
426
475
|
} else if (frame.subtype === 'task_progress') {
|
|
427
476
|
const ref = str(frame.tool_use_id);
|
|
477
|
+
|
|
428
478
|
if (!ref) {
|
|
429
479
|
return;
|
|
430
480
|
}
|
|
481
|
+
|
|
431
482
|
// Only some of a workflow's progress frames carry it; the rest only count what it spent.
|
|
432
483
|
if (Array.isArray(frame.workflow_progress)) {
|
|
433
484
|
events.push({
|
|
@@ -440,6 +491,7 @@ export class ClaudeProtocol {
|
|
|
440
491
|
}
|
|
441
492
|
});
|
|
442
493
|
}
|
|
494
|
+
|
|
443
495
|
// Only a subagent reports what it spent; a task without that is a command with a description.
|
|
444
496
|
if (frame.task_type === 'local_agent' || str(frame.subagent_type)) {
|
|
445
497
|
events.push({
|
|
@@ -452,21 +504,26 @@ export class ClaudeProtocol {
|
|
|
452
504
|
});
|
|
453
505
|
} else {
|
|
454
506
|
const description = str(frame.description);
|
|
507
|
+
|
|
455
508
|
if (description) {
|
|
456
509
|
events.push({ type: 'tool.progress', ref, startedAt: null, description });
|
|
457
510
|
}
|
|
458
511
|
}
|
|
459
512
|
} else if (frame.subtype === 'task_notification') {
|
|
460
513
|
const ref = str(frame.tool_use_id);
|
|
514
|
+
|
|
461
515
|
if (ref) {
|
|
462
516
|
this.subagentModels.delete(ref);
|
|
463
517
|
}
|
|
518
|
+
|
|
464
519
|
const taskId = str(frame.task_id);
|
|
520
|
+
|
|
465
521
|
if (taskId) {
|
|
466
522
|
this.endBackground(taskId, events);
|
|
467
523
|
this.workflows.delete(taskId);
|
|
468
524
|
this.backgroundAgents.delete(taskId);
|
|
469
525
|
}
|
|
526
|
+
|
|
470
527
|
// The CLI wakes the main agent itself when a background task settles; this frame is the only
|
|
471
528
|
// thing that says what it was about, and it arrives before the turn nobody asked for.
|
|
472
529
|
events.push({
|
|
@@ -487,11 +544,14 @@ export class ClaudeProtocol {
|
|
|
487
544
|
|
|
488
545
|
private handleTaskStarted(frame: Frame, events: BackendEvent[]): void {
|
|
489
546
|
const ref = str(frame.tool_use_id);
|
|
547
|
+
|
|
490
548
|
if (frame.task_type === 'local_agent') {
|
|
491
549
|
const agentTask = str(frame.task_id);
|
|
550
|
+
|
|
492
551
|
if (agentTask && frame.is_backgrounded === true) {
|
|
493
552
|
this.backgroundAgents.add(agentTask);
|
|
494
553
|
}
|
|
554
|
+
|
|
495
555
|
if (ref) {
|
|
496
556
|
events.push({
|
|
497
557
|
type: 'task.started',
|
|
@@ -504,14 +564,18 @@ export class ClaudeProtocol {
|
|
|
504
564
|
...(typeof frame.spawn_depth === 'number' ? { depth: frame.spawn_depth } : {})
|
|
505
565
|
});
|
|
506
566
|
}
|
|
567
|
+
|
|
507
568
|
return;
|
|
508
569
|
}
|
|
570
|
+
|
|
509
571
|
const taskId = str(frame.task_id);
|
|
510
572
|
const description = str(frame.description);
|
|
573
|
+
|
|
511
574
|
if (taskId && ref && frame.task_type === 'local_workflow') {
|
|
512
575
|
this.workflows.set(taskId, ref);
|
|
513
576
|
events.push({ type: 'workflow.progress', ref, workflow: { name: str(frame.workflow_name), taskId, phases: [], agents: [] } });
|
|
514
577
|
}
|
|
578
|
+
|
|
515
579
|
if (taskId && typeof frame.task_type === 'string' && BACKGROUND_TASK_TYPES.has(frame.task_type) && frame.ambient !== true) {
|
|
516
580
|
if (frame.is_backgrounded === false) {
|
|
517
581
|
this.foregroundShells.set(taskId, { ref, description });
|
|
@@ -519,6 +583,7 @@ export class ClaudeProtocol {
|
|
|
519
583
|
this.startBackground(taskId, ref, frame.task_type !== 'local_bash', description, events);
|
|
520
584
|
}
|
|
521
585
|
}
|
|
586
|
+
|
|
522
587
|
// A Bash call became a task; its description is the CLI's own words for the work.
|
|
523
588
|
if (ref && description) {
|
|
524
589
|
events.push({ type: 'tool.progress', ref, startedAt: null, description });
|
|
@@ -529,15 +594,20 @@ export class ClaudeProtocol {
|
|
|
529
594
|
private handleTaskUpdated(frame: Frame, events: BackendEvent[]): void {
|
|
530
595
|
const taskId = str(frame.task_id);
|
|
531
596
|
const patch = isRecord(frame.patch) ? frame.patch : {};
|
|
597
|
+
|
|
532
598
|
if (!taskId) {
|
|
533
599
|
return;
|
|
534
600
|
}
|
|
601
|
+
|
|
535
602
|
const shell = this.foregroundShells.get(taskId);
|
|
603
|
+
|
|
536
604
|
if (shell && patch.is_backgrounded === true) {
|
|
537
605
|
this.foregroundShells.delete(taskId);
|
|
538
606
|
this.startBackground(taskId, shell.ref, false, str(patch.description) ?? shell.description, events);
|
|
539
607
|
}
|
|
608
|
+
|
|
540
609
|
const status = str(patch.status);
|
|
610
|
+
|
|
541
611
|
if (status !== null && ENDED_TASK_STATUSES.has(status)) {
|
|
542
612
|
this.foregroundShells.delete(taskId);
|
|
543
613
|
this.backgroundAgents.delete(taskId);
|
|
@@ -550,6 +620,7 @@ export class ClaudeProtocol {
|
|
|
550
620
|
if (this.backgroundTasks.has(taskId)) {
|
|
551
621
|
return;
|
|
552
622
|
}
|
|
623
|
+
|
|
553
624
|
this.backgroundTasks.add(taskId);
|
|
554
625
|
events.push({ type: 'background.started', taskId, ref, monitor, description });
|
|
555
626
|
}
|
|
@@ -566,9 +637,11 @@ export class ClaudeProtocol {
|
|
|
566
637
|
*/
|
|
567
638
|
private endWorkflow(taskId: string, summary: string | null, ok: boolean, events: BackendEvent[]): void {
|
|
568
639
|
const ref = this.workflows.get(taskId);
|
|
640
|
+
|
|
569
641
|
if (ref === undefined) {
|
|
570
642
|
return;
|
|
571
643
|
}
|
|
644
|
+
|
|
572
645
|
this.workflows.delete(taskId);
|
|
573
646
|
events.push({ type: 'task.done', ref, taskId, summary, ok });
|
|
574
647
|
}
|
|
@@ -577,6 +650,7 @@ export class ClaudeProtocol {
|
|
|
577
650
|
stopTaskRequest(taskId: string): unknown {
|
|
578
651
|
const requestId = `stop-task-${taskId}-${Date.now()}`;
|
|
579
652
|
this.stopRequests.set(requestId, taskId);
|
|
653
|
+
|
|
580
654
|
return { type: 'control_request', request_id: requestId, request: { subtype: 'stop_task', task_id: taskId } };
|
|
581
655
|
}
|
|
582
656
|
|
|
@@ -585,10 +659,13 @@ export class ClaudeProtocol {
|
|
|
585
659
|
const response = isRecord(frame.response) ? frame.response : null;
|
|
586
660
|
const requestId = str(response?.request_id);
|
|
587
661
|
const taskId = requestId === null ? undefined : this.stopRequests.get(requestId);
|
|
662
|
+
|
|
588
663
|
if (response === null || requestId === null || taskId === undefined) {
|
|
589
664
|
return;
|
|
590
665
|
}
|
|
666
|
+
|
|
591
667
|
this.stopRequests.delete(requestId);
|
|
668
|
+
|
|
592
669
|
if (response.subtype === 'error') {
|
|
593
670
|
events.push({ type: 'stop.refused', taskId, message: str(response.error) ?? 'The CLI refused to stop it' });
|
|
594
671
|
}
|
|
@@ -596,9 +673,11 @@ export class ClaudeProtocol {
|
|
|
596
673
|
|
|
597
674
|
private handleStreamEvent(frame: Frame, events: BackendEvent[]): void {
|
|
598
675
|
const event = frame.event;
|
|
676
|
+
|
|
599
677
|
if (!isRecord(event) || str(frame.parent_tool_use_id)) {
|
|
600
678
|
return;
|
|
601
679
|
}
|
|
680
|
+
|
|
602
681
|
if (event.type === 'message_start') {
|
|
603
682
|
const message = isRecord(event.message) ? event.message : {};
|
|
604
683
|
this.noteCache(str(message.id), message.usage, events);
|
|
@@ -606,29 +685,38 @@ export class ClaudeProtocol {
|
|
|
606
685
|
this.streamTextCount = 0;
|
|
607
686
|
this.streamThinkingCount = 0;
|
|
608
687
|
this.streamBlocks.clear();
|
|
688
|
+
|
|
609
689
|
return;
|
|
610
690
|
}
|
|
691
|
+
|
|
611
692
|
const index = num(event.index);
|
|
693
|
+
|
|
612
694
|
if (event.type === 'content_block_start' && isRecord(event.content_block) && this.streamMessageId) {
|
|
613
695
|
if (event.content_block.type === 'text') {
|
|
614
696
|
const ref = this.textRef(this.streamMessageId, this.streamTextCount++);
|
|
615
697
|
this.streamBlocks.set(index, ref);
|
|
616
698
|
events.push({ type: 'text.delta', ref, text: str(event.content_block.text) ?? '' });
|
|
699
|
+
|
|
617
700
|
return;
|
|
618
701
|
}
|
|
702
|
+
|
|
619
703
|
if (event.content_block.type === 'thinking') {
|
|
620
704
|
const ref = this.thinkingRef(this.streamMessageId, this.streamThinkingCount++);
|
|
621
705
|
this.streamBlocks.set(index, ref);
|
|
622
706
|
events.push({ type: 'thinking.delta', ref, text: str(event.content_block.thinking) ?? '' });
|
|
707
|
+
|
|
623
708
|
return;
|
|
624
709
|
}
|
|
625
710
|
}
|
|
711
|
+
|
|
626
712
|
if (event.type === 'content_block_delta' && isRecord(event.delta)) {
|
|
627
713
|
const ref = this.streamBlocks.get(index);
|
|
628
714
|
const text = str(event.delta.text) ?? str(event.delta.thinking);
|
|
715
|
+
|
|
629
716
|
if (!ref || !text) {
|
|
630
717
|
return;
|
|
631
718
|
}
|
|
719
|
+
|
|
632
720
|
if (event.delta.type === 'text_delta') {
|
|
633
721
|
events.push({ type: 'text.delta', ref, text });
|
|
634
722
|
} else if (event.delta.type === 'thinking_delta') {
|
|
@@ -646,13 +734,16 @@ export class ClaudeProtocol {
|
|
|
646
734
|
if (messageId === null || messageId === this.cacheMessageId || !isRecord(usage)) {
|
|
647
735
|
return;
|
|
648
736
|
}
|
|
737
|
+
|
|
649
738
|
this.cacheMessageId = messageId;
|
|
650
739
|
const written = cacheTtlWritten(usage);
|
|
740
|
+
|
|
651
741
|
if (written !== null) {
|
|
652
742
|
this.cacheTtlMs = written;
|
|
653
743
|
} else if (num(usage.cache_read_input_tokens) === 0) {
|
|
654
744
|
return;
|
|
655
745
|
}
|
|
746
|
+
|
|
656
747
|
if (this.cacheTtlMs !== null) {
|
|
657
748
|
events.push({ type: 'usage', cacheTtlMs: this.cacheTtlMs, cacheExpiresAt: this.now() + this.cacheTtlMs });
|
|
658
749
|
}
|
|
@@ -664,25 +755,32 @@ export class ClaudeProtocol {
|
|
|
664
755
|
const parentRef = str(frame.parent_tool_use_id);
|
|
665
756
|
const content = Array.isArray(message.content) ? message.content : [];
|
|
666
757
|
const uuid = str(frame.uuid);
|
|
758
|
+
|
|
667
759
|
if (!parentRef && uuid) {
|
|
668
760
|
this.lastUuid = uuid;
|
|
669
761
|
}
|
|
762
|
+
|
|
670
763
|
if (!parentRef && typeof frame.error === 'string') {
|
|
671
764
|
this.apiError = frame.error;
|
|
672
765
|
}
|
|
766
|
+
|
|
673
767
|
const model = str(message.model);
|
|
768
|
+
|
|
674
769
|
if (!parentRef) {
|
|
675
770
|
this.trackGoal(model, content, events);
|
|
676
771
|
}
|
|
772
|
+
|
|
677
773
|
// The CLI answers some frames itself and names them `<synthetic>`; no model wrote those.
|
|
678
774
|
if (parentRef && model && model !== '<synthetic>' && this.subagentModels.get(parentRef) !== model) {
|
|
679
775
|
this.subagentModels.set(parentRef, model);
|
|
680
776
|
events.push({ type: 'task.model', ref: parentRef, model });
|
|
681
777
|
}
|
|
778
|
+
|
|
682
779
|
for (const block of content) {
|
|
683
780
|
if (!isRecord(block)) {
|
|
684
781
|
continue;
|
|
685
782
|
}
|
|
783
|
+
|
|
686
784
|
if (block.type === 'text') {
|
|
687
785
|
const ordinal = nextOrdinal(this.frameTextCount, messageId);
|
|
688
786
|
// A subagent's text is its own report, not the thread's answer; the parent says whose row it is.
|
|
@@ -700,14 +798,19 @@ export class ClaudeProtocol {
|
|
|
700
798
|
});
|
|
701
799
|
}
|
|
702
800
|
}
|
|
801
|
+
|
|
703
802
|
const tokens = parentRef ? 0 : contextTokens(message.usage);
|
|
803
|
+
|
|
704
804
|
if (tokens > 0) {
|
|
705
805
|
events.push({ type: 'usage', contextTokens: tokens });
|
|
706
806
|
}
|
|
807
|
+
|
|
707
808
|
if (!parentRef) {
|
|
708
809
|
this.noteCache(str(message.id), message.usage, events);
|
|
709
810
|
}
|
|
811
|
+
|
|
710
812
|
const error = str(frame.error);
|
|
813
|
+
|
|
711
814
|
if (error) {
|
|
712
815
|
events.push({ type: 'note', level: 'error', text: `The request failed: ${error.replaceAll('_', ' ')}` });
|
|
713
816
|
}
|
|
@@ -716,15 +819,20 @@ export class ClaudeProtocol {
|
|
|
716
819
|
private trackGoal(model: string | null, content: unknown[], events: BackendEvent[]): void {
|
|
717
820
|
if (model === '<synthetic>') {
|
|
718
821
|
const next = claudeGoalFromCommand(resultText(content), this.goal);
|
|
822
|
+
|
|
719
823
|
if (next !== undefined) {
|
|
720
824
|
this.setGoal(next, events);
|
|
721
825
|
}
|
|
826
|
+
|
|
722
827
|
return;
|
|
723
828
|
}
|
|
829
|
+
|
|
724
830
|
if (this.goal === null) {
|
|
725
831
|
return;
|
|
726
832
|
}
|
|
833
|
+
|
|
727
834
|
this.goalAnswered = true;
|
|
835
|
+
|
|
728
836
|
// A message after a stop takes the goal up again; its hook never went away.
|
|
729
837
|
if (this.goal.status === 'paused') {
|
|
730
838
|
this.goal = { ...this.goal, status: 'active' };
|
|
@@ -742,9 +850,11 @@ export class ClaudeProtocol {
|
|
|
742
850
|
const goal = this.goal;
|
|
743
851
|
const interrupted = this.interrupting;
|
|
744
852
|
this.interrupting = false;
|
|
853
|
+
|
|
745
854
|
if (goal?.status !== 'active') {
|
|
746
855
|
return;
|
|
747
856
|
}
|
|
857
|
+
|
|
748
858
|
if (failed || interrupted) {
|
|
749
859
|
this.setGoal({ ...goal, status: 'paused' }, events);
|
|
750
860
|
} else if (this.goalAnswered && this.backgroundTasks.size === 0 && this.workflows.size === 0 && this.backgroundAgents.size === 0) {
|
|
@@ -760,9 +870,11 @@ export class ClaudeProtocol {
|
|
|
760
870
|
private handleToolProgress(frame: Frame, events: BackendEvent[]): void {
|
|
761
871
|
const elapsed = frame.elapsed_time_seconds;
|
|
762
872
|
const ref = str(frame.tool_use_id);
|
|
873
|
+
|
|
763
874
|
if (!ref || typeof elapsed !== 'number' || !Number.isFinite(elapsed) || elapsed < 0) {
|
|
764
875
|
return;
|
|
765
876
|
}
|
|
877
|
+
|
|
766
878
|
events.push({ type: 'tool.progress', ref, startedAt: Date.now() - Math.round(elapsed * 1000), description: null });
|
|
767
879
|
}
|
|
768
880
|
|
|
@@ -770,28 +882,36 @@ export class ClaudeProtocol {
|
|
|
770
882
|
if (this.promptId !== null && frame.uuid === this.promptId) {
|
|
771
883
|
events.push({ type: 'turn.accepted', promptId: this.promptId });
|
|
772
884
|
}
|
|
885
|
+
|
|
773
886
|
const message = isRecord(frame.message) ? frame.message : {};
|
|
774
887
|
const content = Array.isArray(message.content) ? message.content : [];
|
|
888
|
+
|
|
775
889
|
if (frame.isSynthetic === true && !str(frame.parent_tool_use_id)) {
|
|
776
890
|
const checked = claudeGoalCheck(resultText(message.content), this.goal);
|
|
891
|
+
|
|
777
892
|
if (checked !== undefined) {
|
|
778
893
|
this.setGoal(checked, events);
|
|
779
894
|
events.push({ type: 'note', level: 'info', text: `Goal not met yet: ${checked.lastCheck ?? checked.objective}` });
|
|
780
895
|
}
|
|
781
896
|
}
|
|
897
|
+
|
|
782
898
|
for (const block of content) {
|
|
783
899
|
if (!isRecord(block) || block.type !== 'tool_result') {
|
|
784
900
|
continue;
|
|
785
901
|
}
|
|
902
|
+
|
|
786
903
|
const ref = str(block.tool_use_id);
|
|
904
|
+
|
|
787
905
|
if (ref) {
|
|
788
906
|
this.subagentModels.delete(ref);
|
|
789
907
|
const output = resultText(block.content);
|
|
790
908
|
const workflow = WORKFLOW_LAUNCHED.exec(output)?.[1];
|
|
909
|
+
|
|
791
910
|
if (workflow) {
|
|
792
911
|
this.workflows.set(workflow, ref);
|
|
793
912
|
events.push({ type: 'workflow.progress', ref, workflow: { name: null, taskId: workflow, phases: [], agents: [] } });
|
|
794
913
|
}
|
|
914
|
+
|
|
795
915
|
events.push({ type: 'tool.done', ref, output, state: block.is_error === true ? 'error' : 'done' });
|
|
796
916
|
}
|
|
797
917
|
}
|
|
@@ -800,6 +920,7 @@ export class ClaudeProtocol {
|
|
|
800
920
|
private handleResult(frame: Frame, events: BackendEvent[]): void {
|
|
801
921
|
const echoed = Array.isArray(frame.user_message_uuids) ? frame.user_message_uuids.filter((value): value is string => typeof value === 'string') : [];
|
|
802
922
|
const origin = str(frame.origin) ?? (isRecord(frame.origin) ? str(frame.origin.kind) : null);
|
|
923
|
+
|
|
803
924
|
if (
|
|
804
925
|
this.promptId !== null &&
|
|
805
926
|
(echoed.length > 0
|
|
@@ -809,29 +930,37 @@ export class ClaudeProtocol {
|
|
|
809
930
|
if (typeof frame.total_cost_usd === 'number') {
|
|
810
931
|
events.push({ type: 'usage', costUsd: num(frame.total_cost_usd) });
|
|
811
932
|
}
|
|
933
|
+
|
|
812
934
|
if (frame.is_error === true) {
|
|
813
935
|
events.push({ type: 'note', level: 'warning', text: 'Claude could not finish a turn in the background. Your message is still waiting.' });
|
|
814
936
|
}
|
|
937
|
+
|
|
815
938
|
return;
|
|
816
939
|
}
|
|
940
|
+
|
|
817
941
|
const promptId = this.promptId !== null && echoed.includes(this.promptId) ? this.promptId : null;
|
|
818
942
|
this.promptId = null;
|
|
943
|
+
|
|
819
944
|
for (const id of echoed) {
|
|
820
945
|
this.steerIds.delete(id);
|
|
821
946
|
}
|
|
947
|
+
|
|
822
948
|
const failed = frame.is_error === true || (typeof frame.subtype === 'string' && frame.subtype.startsWith('error'));
|
|
823
949
|
const errors = Array.isArray(frame.errors) ? frame.errors.filter((error): error is string => typeof error === 'string') : [];
|
|
824
950
|
const lastUuid = this.lastUuid;
|
|
825
951
|
this.lastUuid = null;
|
|
826
952
|
const limit = failed ? this.limit() : null;
|
|
827
953
|
const replaceProcess = failed && this.apiError === 'authentication_failed';
|
|
954
|
+
|
|
828
955
|
if (limit?.kind !== 'usage') {
|
|
829
956
|
this.refusedResets.clear();
|
|
830
957
|
}
|
|
958
|
+
|
|
831
959
|
this.apiError = null;
|
|
832
960
|
this.refusedWindows.clear();
|
|
833
961
|
// Before the turn ends, so whoever settles work on that end already reads where the goal stands.
|
|
834
962
|
this.settleGoal(failed, events);
|
|
963
|
+
|
|
835
964
|
events.push({
|
|
836
965
|
type: 'turn.done',
|
|
837
966
|
...(promptId === null ? {} : { promptId }),
|
|
@@ -848,16 +977,20 @@ export class ClaudeProtocol {
|
|
|
848
977
|
if (!isRecord(info) || typeof info.status !== 'string') {
|
|
849
978
|
return;
|
|
850
979
|
}
|
|
980
|
+
|
|
851
981
|
this.expireRefusedResets();
|
|
852
982
|
const namedWindow = str(info.rateLimitType);
|
|
853
983
|
const window = namedWindow ?? 'unknown';
|
|
984
|
+
|
|
854
985
|
const overageAllowed =
|
|
855
986
|
info.overageStatus === 'allowed' || info.overageStatus === 'allowed_warning' || info.isUsingOverage === true || info.overageInUse === true;
|
|
987
|
+
|
|
856
988
|
if (info.status === 'rejected' && !overageAllowed) {
|
|
857
989
|
const resetsAt = typeof info.resetsAt === 'number' ? info.resetsAt * 1000 : NaN;
|
|
858
990
|
const validReset = Number.isFinite(resetsAt) && resetsAt > this.now() && resetsAt < 8.64e15 ? resetsAt : null;
|
|
859
991
|
const reset = validReset ?? (info.resetsAt === undefined && namedWindow ? (this.refusedResets.get(window) ?? null) : null);
|
|
860
992
|
this.refusedWindows.set(window, reset);
|
|
993
|
+
|
|
861
994
|
if (namedWindow && reset !== null && reset > this.now()) {
|
|
862
995
|
this.refusedResets.set(window, reset);
|
|
863
996
|
} else {
|
|
@@ -871,6 +1004,7 @@ export class ClaudeProtocol {
|
|
|
871
1004
|
|
|
872
1005
|
private expireRefusedResets(): void {
|
|
873
1006
|
const now = this.now();
|
|
1007
|
+
|
|
874
1008
|
for (const [window, reset] of this.refusedResets) {
|
|
875
1009
|
if (reset <= now) {
|
|
876
1010
|
this.refusedResets.delete(window);
|
|
@@ -886,40 +1020,53 @@ export class ClaudeProtocol {
|
|
|
886
1020
|
private limit(): ChatTurnLimit | null {
|
|
887
1021
|
if (this.refusedWindows.size > 0) {
|
|
888
1022
|
let resetsAt = 0;
|
|
1023
|
+
|
|
889
1024
|
for (const reset of this.refusedWindows.values()) {
|
|
890
1025
|
if (reset === null || reset <= this.now()) {
|
|
891
1026
|
return { kind: 'usage' };
|
|
892
1027
|
}
|
|
1028
|
+
|
|
893
1029
|
resetsAt = Math.max(resetsAt, reset);
|
|
894
1030
|
}
|
|
1031
|
+
|
|
895
1032
|
return { kind: 'usage', resetsAt };
|
|
896
1033
|
}
|
|
1034
|
+
|
|
897
1035
|
if (this.apiError === 'rate_limit' || this.apiError === 'overloaded') {
|
|
898
1036
|
return { kind: 'overload' };
|
|
899
1037
|
}
|
|
1038
|
+
|
|
900
1039
|
return null;
|
|
901
1040
|
}
|
|
902
1041
|
|
|
903
1042
|
private handleControlRequest(frame: Frame, events: BackendEvent[]): void {
|
|
904
1043
|
const request = isRecord(frame.request) ? frame.request : {};
|
|
905
1044
|
const requestId = str(frame.request_id);
|
|
1045
|
+
|
|
906
1046
|
if (!requestId || request.subtype !== 'can_use_tool') {
|
|
907
1047
|
return;
|
|
908
1048
|
}
|
|
1049
|
+
|
|
909
1050
|
const toolUseId = str(request.tool_use_id);
|
|
910
1051
|
const toolName = str(request.tool_name) ?? 'tool';
|
|
911
1052
|
const background = Boolean(str(request.agent_id));
|
|
1053
|
+
|
|
912
1054
|
if (toolName === 'AskUserQuestion') {
|
|
913
1055
|
const questions = parseQuestions(request.input);
|
|
1056
|
+
|
|
914
1057
|
if (questions.length === 0) {
|
|
915
1058
|
return;
|
|
916
1059
|
}
|
|
1060
|
+
|
|
917
1061
|
this.pending.set(requestId, { type: 'question', toolUseId, input: request.input });
|
|
918
1062
|
events.push({ type: 'question.requested', requestId, questions, ...(background ? { background: true } : {}) });
|
|
1063
|
+
|
|
919
1064
|
return;
|
|
920
1065
|
}
|
|
1066
|
+
|
|
921
1067
|
const suggestions = Array.isArray(request.permission_suggestions) ? request.permission_suggestions : [];
|
|
922
1068
|
this.pending.set(requestId, { type: 'approval', toolUseId, input: request.input ?? {}, suggestions });
|
|
1069
|
+
|
|
923
1070
|
events.push({
|
|
924
1071
|
type: 'approval.requested',
|
|
925
1072
|
...(background ? { background: true } : {}),
|