@parall/claude-agent 1.59.0 → 1.60.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/busy-state.d.ts +56 -0
- package/dist/busy-state.d.ts.map +1 -0
- package/dist/busy-state.js +140 -0
- package/dist/compact.d.ts +29 -0
- package/dist/compact.d.ts.map +1 -0
- package/dist/compact.js +98 -0
- package/dist/dispatch.d.ts +52 -6
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +179 -191
- package/dist/index.js +6 -2
- package/dist/input-lifecycle.d.ts +10 -1
- package/dist/input-lifecycle.d.ts.map +1 -1
- package/dist/input-lifecycle.js +11 -2
- package/dist/output-parser.d.ts +32 -0
- package/dist/output-parser.d.ts.map +1 -1
- package/dist/output-parser.js +106 -10
- package/dist/process-pump.d.ts +105 -0
- package/dist/process-pump.d.ts.map +1 -0
- package/dist/process-pump.js +436 -0
- package/dist/runtime-turn.d.ts +19 -0
- package/dist/runtime-turn.d.ts.map +1 -0
- package/dist/runtime-turn.js +59 -0
- package/dist/session-manager.d.ts +1 -0
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +3 -0
- package/dist/turn-sink.d.ts +49 -0
- package/dist/turn-sink.d.ts.map +1 -0
- package/dist/turn-sink.js +9 -0
- package/package.json +4 -4
- package/src/busy-state.ts +159 -0
- package/src/compact.ts +132 -0
- package/src/dispatch.ts +201 -224
- package/src/index.ts +6 -1
- package/src/input-lifecycle.ts +25 -1
- package/src/output-parser.ts +140 -10
- package/src/process-pump.ts +493 -0
- package/src/runtime-turn.ts +80 -0
- package/src/session-manager.ts +4 -0
- package/src/turn-sink.ts +59 -0
package/src/input-lifecycle.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import type { DispatchInputLifecycle, GatewayLogger, RuntimeInputState } from '@parall/agent-core';
|
|
3
|
+
import {
|
|
4
|
+
type ClaudeTurnSink,
|
|
5
|
+
newTurnEvidence,
|
|
6
|
+
newTurnSink,
|
|
7
|
+
type TurnEvidence,
|
|
8
|
+
} from './turn-sink.js';
|
|
3
9
|
|
|
4
10
|
export type ClaudeInputDelivery = {
|
|
5
11
|
deliveryKey: string;
|
|
@@ -10,6 +16,14 @@ export type ClaudeInputDelivery = {
|
|
|
10
16
|
terminal?: 'completed' | 'failed' | 'settled';
|
|
11
17
|
reportedState?: RuntimeInputState;
|
|
12
18
|
resultFailed: boolean;
|
|
19
|
+
/** Envelopes the stdout pump routed to this delivery; drained by its dispatch. */
|
|
20
|
+
sink: ClaudeTurnSink;
|
|
21
|
+
/** Turn-outcome evidence gathered while this delivery owned stdout. */
|
|
22
|
+
evidence: TurnEvidence;
|
|
23
|
+
/** Dispatch inactivity signal of the dispatch draining (or about to drain) this delivery. */
|
|
24
|
+
noteActivity?: () => void;
|
|
25
|
+
/** runtime_session already pushed to this delivery's sink. */
|
|
26
|
+
sessionAnnounced: boolean;
|
|
13
27
|
/**
|
|
14
28
|
* The turn settled as usage_limit: the lane-level deferred complete will
|
|
15
29
|
* re-deliver this input at retry_at without burning redrive budget. A
|
|
@@ -73,17 +87,24 @@ export class ClaudeInputRegistry {
|
|
|
73
87
|
deliveryKey: string,
|
|
74
88
|
lifecycle: DispatchInputLifecycle | undefined,
|
|
75
89
|
injected: boolean,
|
|
90
|
+
noteActivity?: () => void,
|
|
91
|
+
log?: GatewayLogger,
|
|
76
92
|
): ClaudeInputDelivery {
|
|
77
93
|
if (this.byKey.has(deliveryKey)) {
|
|
78
94
|
throw new Error(`duplicate Claude delivery key ${deliveryKey}`);
|
|
79
95
|
}
|
|
96
|
+
const commandUuid = randomUUID();
|
|
80
97
|
const delivery: ClaudeInputDelivery = {
|
|
81
98
|
deliveryKey,
|
|
82
|
-
commandUuid
|
|
99
|
+
commandUuid,
|
|
83
100
|
lifecycle,
|
|
84
101
|
injected,
|
|
85
102
|
drained: !injected,
|
|
86
103
|
resultFailed: false,
|
|
104
|
+
sink: newTurnSink(`delivery ${deliveryKey}`, log),
|
|
105
|
+
evidence: newTurnEvidence(),
|
|
106
|
+
noteActivity,
|
|
107
|
+
sessionAnnounced: false,
|
|
87
108
|
};
|
|
88
109
|
this.byKey.set(deliveryKey, delivery);
|
|
89
110
|
this.byCommand.set(delivery.commandUuid, delivery);
|
|
@@ -97,6 +118,9 @@ export class ClaudeInputRegistry {
|
|
|
97
118
|
if (this.byCommand.get(delivery.commandUuid) === delivery) {
|
|
98
119
|
this.byCommand.delete(delivery.commandUuid);
|
|
99
120
|
}
|
|
121
|
+
// The sink stays open: apply() removes a drained delivery at its
|
|
122
|
+
// terminal BEFORE the pump pushes the terminal envelope its drain is
|
|
123
|
+
// waiting for. Only the pump closes sinks (process gone).
|
|
100
124
|
}
|
|
101
125
|
|
|
102
126
|
/**
|
package/src/output-parser.ts
CHANGED
|
@@ -27,9 +27,52 @@ export type ClaudeResultMeta = {
|
|
|
27
27
|
model?: string;
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* The CLI's background-work frames (`system` subtypes). Every field is
|
|
32
|
+
* optional except the subtype.
|
|
33
|
+
* Observed on 2.1.227/2.1.258: `background_tasks_changed` (REPLACE
|
|
34
|
+
* semantics — every live task after the change), `task_started`,
|
|
35
|
+
* `task_progress`, `task_updated`, `task_notification` (a task finished or
|
|
36
|
+
* was stopped), `status`, `session_state_changed` (env-gated).
|
|
37
|
+
*/
|
|
38
|
+
export type ClaudeRuntimeTaskEvent = {
|
|
39
|
+
type: 'runtime_task';
|
|
40
|
+
subtype:
|
|
41
|
+
| 'background_tasks_changed'
|
|
42
|
+
| 'task_started'
|
|
43
|
+
| 'task_progress'
|
|
44
|
+
| 'task_updated'
|
|
45
|
+
| 'task_notification'
|
|
46
|
+
| 'status'
|
|
47
|
+
| 'session_state_changed';
|
|
48
|
+
taskId?: string;
|
|
49
|
+
toolUseId?: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
taskType?: string;
|
|
52
|
+
status?: string;
|
|
53
|
+
summary?: string;
|
|
54
|
+
isBackgrounded?: boolean;
|
|
55
|
+
tasks?: Array<{ taskId: string; taskType?: string; description?: string; ambient?: boolean }>;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const RUNTIME_TASK_SUBTYPES = new Set<ClaudeRuntimeTaskEvent['subtype']>([
|
|
59
|
+
'background_tasks_changed',
|
|
60
|
+
'task_started',
|
|
61
|
+
'task_progress',
|
|
62
|
+
'task_updated',
|
|
63
|
+
'task_notification',
|
|
64
|
+
'status',
|
|
65
|
+
'session_state_changed',
|
|
66
|
+
]);
|
|
67
|
+
|
|
30
68
|
export type ClaudeParsedEvent =
|
|
31
69
|
| RuntimeEvent
|
|
32
70
|
| { type: 'runtime_init'; sessionId?: string; capabilities: string[] }
|
|
71
|
+
| ClaudeRuntimeTaskEvent
|
|
72
|
+
// A root `user` frame that carries text instead of tool results (the
|
|
73
|
+
// CLI's own injected notices, e.g. a `<task-notification>`). Never a
|
|
74
|
+
// RuntimeEvent: the model's turn already carries its consequence.
|
|
75
|
+
| { type: 'user_text'; text: string }
|
|
33
76
|
// Internal-only progress marker for a valid CLI frame whose content must
|
|
34
77
|
// not be projected into the root RuntimeEvent stream (for example, nested
|
|
35
78
|
// subagent output). The dispatch consumer uses it only to refresh runtime
|
|
@@ -44,6 +87,10 @@ export type ClaudeParsedEvent =
|
|
|
44
87
|
// is not a model message — surfaced so the turn-outcome classifier can see
|
|
45
88
|
// the text without it entering the RuntimeEvent stream.
|
|
46
89
|
| { type: 'assistant_error'; message: string }
|
|
90
|
+
// `system` frame with subtype `compact_boundary`: the CLI folded history
|
|
91
|
+
// (a manual `/compact` — the idle auto-compact path — or its own
|
|
92
|
+
// auto-compact mid-turn). Evidence only; never a RuntimeEvent.
|
|
93
|
+
| { type: 'compact_boundary'; trigger?: string; preTokens?: number }
|
|
47
94
|
// turn_end is emitted for every `result` frame (both `is_error: true` and
|
|
48
95
|
// `is_error: false`); `isError` carries the frame's status so the consumer
|
|
49
96
|
// can tell a clean turn boundary from a failed one without inspecting the
|
|
@@ -169,21 +216,49 @@ export async function* parseClaudeStreamJson(
|
|
|
169
216
|
const eventTimestampMs = parseEventTimestampMs(event) ?? now;
|
|
170
217
|
const eventRecord = event as Record<string, unknown>;
|
|
171
218
|
|
|
172
|
-
if (eventRecord.type === 'system' && eventRecord.subtype === '
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
219
|
+
if (eventRecord.type === 'system' && eventRecord.subtype === 'compact_boundary') {
|
|
220
|
+
const meta =
|
|
221
|
+
eventRecord.compact_metadata && typeof eventRecord.compact_metadata === 'object'
|
|
222
|
+
? (eventRecord.compact_metadata as Record<string, unknown>)
|
|
223
|
+
: undefined;
|
|
224
|
+
const trigger = asTrimmedString(meta?.trigger);
|
|
225
|
+
const preTokens = asFiniteNumber(meta?.pre_tokens);
|
|
179
226
|
yield {
|
|
180
|
-
type: '
|
|
181
|
-
...(
|
|
182
|
-
|
|
227
|
+
type: 'compact_boundary',
|
|
228
|
+
...(trigger ? { trigger } : {}),
|
|
229
|
+
...(preTokens !== undefined ? { preTokens } : {}),
|
|
183
230
|
};
|
|
184
231
|
continue;
|
|
185
232
|
}
|
|
186
233
|
|
|
234
|
+
if (eventRecord.type === 'system') {
|
|
235
|
+
if (eventRecord.subtype === 'init') {
|
|
236
|
+
const sessionId = asTrimmedString(eventRecord.session_id);
|
|
237
|
+
const capabilities = Array.isArray(eventRecord.capabilities)
|
|
238
|
+
? eventRecord.capabilities
|
|
239
|
+
.map((capability) => asTrimmedString(capability))
|
|
240
|
+
.filter((capability): capability is string => Boolean(capability))
|
|
241
|
+
: [];
|
|
242
|
+
yield {
|
|
243
|
+
type: 'runtime_init',
|
|
244
|
+
...(sessionId ? { sessionId } : {}),
|
|
245
|
+
capabilities,
|
|
246
|
+
};
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const subtype = asTrimmedString(eventRecord.subtype) as
|
|
250
|
+
| ClaudeRuntimeTaskEvent['subtype']
|
|
251
|
+
| undefined;
|
|
252
|
+
if (subtype && RUNTIME_TASK_SUBTYPES.has(subtype)) {
|
|
253
|
+
yield parseRuntimeTask(subtype, eventRecord);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
// hook_started / hook_response / thinking_tokens /
|
|
257
|
+
// ...: valid CLI progress with no projection.
|
|
258
|
+
yield { type: 'runtime_activity' };
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
|
|
187
262
|
if (eventRecord.type === 'command_lifecycle') {
|
|
188
263
|
const commandUuid = asTrimmedString(eventRecord.command_uuid);
|
|
189
264
|
const state = asTrimmedString(eventRecord.state);
|
|
@@ -277,7 +352,17 @@ export async function* parseClaudeStreamJson(
|
|
|
277
352
|
message && typeof message === 'object'
|
|
278
353
|
? (message as { content?: unknown }).content
|
|
279
354
|
: undefined;
|
|
355
|
+
if (typeof content === 'string') {
|
|
356
|
+
const text = content.trim();
|
|
357
|
+
if (text) yield { type: 'user_text', text };
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
280
360
|
if (!Array.isArray(content)) continue;
|
|
361
|
+
if (!content.some((block) => (block as { type?: unknown })?.type === 'tool_result')) {
|
|
362
|
+
const text = stringifyContent(content).trim();
|
|
363
|
+
if (text) yield { type: 'user_text', text };
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
281
366
|
|
|
282
367
|
for (const block of content) {
|
|
283
368
|
if (!block || typeof block !== 'object') continue;
|
|
@@ -329,8 +414,53 @@ export async function* parseClaudeStreamJson(
|
|
|
329
414
|
...(numTurns !== undefined ? { numTurns } : {}),
|
|
330
415
|
resultMeta: extractResultMeta(eventRecord, isError, numTurns),
|
|
331
416
|
};
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Any other well-formed frame (rate_limit_event, stream_event, ...) is
|
|
421
|
+
// CLI progress: refresh activity, never project.
|
|
422
|
+
yield { type: 'runtime_activity' };
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function parseRuntimeTask(
|
|
427
|
+
subtype: ClaudeRuntimeTaskEvent['subtype'],
|
|
428
|
+
frame: Record<string, unknown>,
|
|
429
|
+
): ClaudeRuntimeTaskEvent {
|
|
430
|
+
const event: ClaudeRuntimeTaskEvent = { type: 'runtime_task', subtype };
|
|
431
|
+
const taskId = asTrimmedString(frame.task_id);
|
|
432
|
+
if (taskId) event.taskId = taskId;
|
|
433
|
+
const toolUseId = asTrimmedString(frame.tool_use_id);
|
|
434
|
+
if (toolUseId) event.toolUseId = toolUseId;
|
|
435
|
+
const description = asTrimmedString(frame.description);
|
|
436
|
+
if (description) event.description = description;
|
|
437
|
+
const taskType = asTrimmedString(frame.task_type);
|
|
438
|
+
if (taskType) event.taskType = taskType;
|
|
439
|
+
const status =
|
|
440
|
+
asTrimmedString(frame.status) ??
|
|
441
|
+
asTrimmedString((frame.patch as { status?: unknown } | undefined)?.status) ??
|
|
442
|
+
asTrimmedString(frame.state);
|
|
443
|
+
if (status) event.status = status;
|
|
444
|
+
const summary = asTrimmedString(frame.summary);
|
|
445
|
+
if (summary) event.summary = summary;
|
|
446
|
+
if (typeof frame.is_backgrounded === 'boolean') event.isBackgrounded = frame.is_backgrounded;
|
|
447
|
+
if (Array.isArray(frame.tasks)) {
|
|
448
|
+
event.tasks = [];
|
|
449
|
+
for (const task of frame.tasks) {
|
|
450
|
+
if (!task || typeof task !== 'object') continue;
|
|
451
|
+
const record = task as Record<string, unknown>;
|
|
452
|
+
const id = asTrimmedString(record.task_id);
|
|
453
|
+
if (!id) continue;
|
|
454
|
+
const entry: NonNullable<ClaudeRuntimeTaskEvent['tasks']>[number] = { taskId: id };
|
|
455
|
+
const entryType = asTrimmedString(record.task_type);
|
|
456
|
+
if (entryType) entry.taskType = entryType;
|
|
457
|
+
const entryDescription = asTrimmedString(record.description);
|
|
458
|
+
if (entryDescription) entry.description = entryDescription;
|
|
459
|
+
if (record.ambient === true) entry.ambient = true;
|
|
460
|
+
event.tasks.push(entry);
|
|
332
461
|
}
|
|
333
462
|
}
|
|
463
|
+
return event;
|
|
334
464
|
}
|
|
335
465
|
|
|
336
466
|
/** Snapshot the result frame's discriminator + usage fields (all optional). */
|