@parall/claude-agent 1.64.0 → 1.65.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/dispatch.js +1 -2
- package/dist/output-parser.d.ts.map +1 -1
- package/dist/output-parser.js +7 -0
- package/dist/process-pump.d.ts +4 -1
- package/dist/process-pump.d.ts.map +1 -1
- package/dist/process-pump.js +31 -1
- package/dist/runtime-observations.d.ts +4 -0
- package/dist/runtime-observations.d.ts.map +1 -0
- package/dist/runtime-observations.js +72 -0
- package/dist/spawn-env.d.ts.map +1 -1
- package/dist/spawn-env.js +3 -0
- package/package.json +4 -4
- package/src/dispatch.ts +2 -2
- package/src/output-parser.ts +5 -0
- package/src/process-pump.ts +29 -1
- package/src/runtime-observations.ts +80 -0
- package/src/spawn-env.ts +3 -0
package/dist/dispatch.js
CHANGED
|
@@ -540,10 +540,9 @@ export class ClaudeCodeAdapter {
|
|
|
540
540
|
this.processes.delete(sessionKey);
|
|
541
541
|
this.retiring.delete(state);
|
|
542
542
|
},
|
|
543
|
-
// Main-session turns go to the gateway (steps + session activity);
|
|
544
|
-
// fork-session turns only count as busy (fork-scope invariant).
|
|
545
543
|
onRuntimeTurnOpened: (turn) => this.activity.surfaceTurn(turn, this.opts.sessionManager.isMain(sessionKey), log),
|
|
546
544
|
onRuntimeTurnClosed: () => this.maybeApplyRestart(sessionKey, state),
|
|
545
|
+
onObservation: (event) => this.activity.emit({ kind: 'observation', sessionKey, event }, log),
|
|
547
546
|
},
|
|
548
547
|
}),
|
|
549
548
|
done: false,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"output-parser.d.ts","sourceRoot":"","sources":["../src/output-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"output-parser.d.ts","sourceRoot":"","sources":["../src/output-parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGvD;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EACH,0BAA0B,GAC1B,cAAc,GACd,eAAe,GACf,cAAc,GACd,mBAAmB,GACnB,QAAQ,GACR,uBAAuB,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAC/F,CAAC;AAYF,MAAM,MAAM,iBAAiB,GACzB,YAAY,GACZ;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,EAAE,CAAA;CAAE,GACpE,sBAAsB,GAItB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAKnC;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,GAC5B;IACE,IAAI,EAAE,mBAAmB,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;CACvE,GAID;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAI5C;IAAE,IAAI,EAAE,kBAAkB,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAqBlE;IACE,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B,CAAC;AAgFN,wBAAuB,qBAAqB,CAC1C,QAAQ,EAAE,QAAQ,GACjB,cAAc,CAAC,iBAAiB,CAAC,CAiOnC"}
|
package/dist/output-parser.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { claudeObservation } from './runtime-observations.js';
|
|
1
2
|
const RUNTIME_TASK_SUBTYPES = new Set([
|
|
2
3
|
'background_tasks_changed',
|
|
3
4
|
'task_started',
|
|
@@ -100,6 +101,10 @@ export async function* parseClaudeStreamJson(readable) {
|
|
|
100
101
|
const now = Date.now();
|
|
101
102
|
const eventTimestampMs = parseEventTimestampMs(event) ?? now;
|
|
102
103
|
const eventRecord = event;
|
|
104
|
+
const observation = claudeObservation(eventRecord);
|
|
105
|
+
// Init must announce the native session before its observations drain.
|
|
106
|
+
if (observation && observation.content.event !== 'session_initialized')
|
|
107
|
+
yield observation;
|
|
103
108
|
if (eventRecord.type === 'system' && eventRecord.subtype === 'compact_boundary') {
|
|
104
109
|
const meta = eventRecord.compact_metadata && typeof eventRecord.compact_metadata === 'object'
|
|
105
110
|
? eventRecord.compact_metadata
|
|
@@ -126,6 +131,8 @@ export async function* parseClaudeStreamJson(readable) {
|
|
|
126
131
|
...(sessionId ? { sessionId } : {}),
|
|
127
132
|
capabilities,
|
|
128
133
|
};
|
|
134
|
+
if (observation)
|
|
135
|
+
yield observation;
|
|
129
136
|
continue;
|
|
130
137
|
}
|
|
131
138
|
const subtype = asTrimmedString(eventRecord.subtype);
|
package/dist/process-pump.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type GatewayLogger, type RuntimeBusyState } from '@parall/agent-core';
|
|
1
|
+
import { type GatewayLogger, type RuntimeBusyState, type RuntimeObservationEvent } from '@parall/agent-core';
|
|
2
2
|
import { ClaudeBusyTracker } from './busy-state.js';
|
|
3
3
|
import type { ClaudeInputDelivery, ClaudeInputRegistry } from './input-lifecycle.js';
|
|
4
4
|
import type { ClaudeParsedEvent } from './output-parser.js';
|
|
@@ -17,6 +17,7 @@ export type ClaudeProcessPumpHooks = {
|
|
|
17
17
|
onEof(): void;
|
|
18
18
|
onRuntimeTurnOpened(turn: ClaudeRuntimeTurn): void;
|
|
19
19
|
onRuntimeTurnClosed(): void;
|
|
20
|
+
onObservation?(event: RuntimeObservationEvent): void;
|
|
20
21
|
};
|
|
21
22
|
export type ClaudeProcessPumpOptions = {
|
|
22
23
|
sessionKey: string;
|
|
@@ -59,6 +60,7 @@ export declare class ClaudeProcessPump {
|
|
|
59
60
|
private idleTimer;
|
|
60
61
|
private activeDrain;
|
|
61
62
|
private readonly now;
|
|
63
|
+
private pendingObservations;
|
|
62
64
|
constructor(opts: ClaudeProcessPumpOptions);
|
|
63
65
|
start(): void;
|
|
64
66
|
/** The dispatch generator currently draining `delivery` (owner preference). */
|
|
@@ -92,6 +94,7 @@ export declare class ClaudeProcessPump {
|
|
|
92
94
|
private run;
|
|
93
95
|
private route;
|
|
94
96
|
private touchAll;
|
|
97
|
+
private routeObservation;
|
|
95
98
|
private liveDeliveries;
|
|
96
99
|
private ownerFor;
|
|
97
100
|
private openRuntimeTurn;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-pump.d.ts","sourceRoot":"","sources":["../src/process-pump.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,gBAAgB,
|
|
1
|
+
{"version":3,"file":"process-pump.d.ts","sourceRoot":"","sources":["../src/process-pump.ts"],"names":[],"mappings":"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"}
|
package/dist/process-pump.js
CHANGED
|
@@ -33,6 +33,7 @@ export class ClaudeProcessPump {
|
|
|
33
33
|
idleTimer = null;
|
|
34
34
|
activeDrain = null;
|
|
35
35
|
now;
|
|
36
|
+
pendingObservations = [];
|
|
36
37
|
constructor(opts) {
|
|
37
38
|
this.opts = opts;
|
|
38
39
|
this.now = opts.now ?? Date.now;
|
|
@@ -153,7 +154,8 @@ export class ClaudeProcessPump {
|
|
|
153
154
|
for await (const parsed of parser) {
|
|
154
155
|
if (this.closed)
|
|
155
156
|
break;
|
|
156
|
-
|
|
157
|
+
if (parsed.type !== 'observation')
|
|
158
|
+
this.touchAll();
|
|
157
159
|
await this.route(parsed);
|
|
158
160
|
}
|
|
159
161
|
}
|
|
@@ -168,6 +170,9 @@ export class ClaudeProcessPump {
|
|
|
168
170
|
switch (parsed.type) {
|
|
169
171
|
case 'runtime_activity':
|
|
170
172
|
return;
|
|
173
|
+
case 'observation':
|
|
174
|
+
this.routeObservation(parsed);
|
|
175
|
+
return;
|
|
171
176
|
case 'runtime_init':
|
|
172
177
|
this.handleInit(parsed);
|
|
173
178
|
return;
|
|
@@ -222,6 +227,27 @@ export class ClaudeProcessPump {
|
|
|
222
227
|
}
|
|
223
228
|
this.runtimeTurn?.touch();
|
|
224
229
|
}
|
|
230
|
+
routeObservation(event) {
|
|
231
|
+
if (!this.sessionId) {
|
|
232
|
+
// SessionStart hooks precede init. Keep a bounded startup buffer and
|
|
233
|
+
// release only records whose native session matches the eventual init.
|
|
234
|
+
if (this.pendingObservations.length < 64)
|
|
235
|
+
this.pendingObservations.push(event);
|
|
236
|
+
else
|
|
237
|
+
this.opts.log?.warn('Claude startup observation buffer full; dropping observation');
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (event.runtimeSessionId !== this.sessionId) {
|
|
241
|
+
this.opts.log?.warn('Claude observation does not match the initialized session');
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
const owner = this.ownerFor(false) ??
|
|
245
|
+
(this.activeDrain && !this.activeDrain.terminal ? this.activeDrain : null);
|
|
246
|
+
if (owner)
|
|
247
|
+
owner.sink.push({ kind: 'runtime', event });
|
|
248
|
+
else
|
|
249
|
+
this.opts.hooks.onObservation?.(event);
|
|
250
|
+
}
|
|
225
251
|
liveDeliveries() {
|
|
226
252
|
const live = [];
|
|
227
253
|
for (const delivery of this.opts.inputs.values())
|
|
@@ -293,6 +319,10 @@ export class ClaudeProcessPump {
|
|
|
293
319
|
delivery.sessionAnnounced = true;
|
|
294
320
|
delivery.sink.push({ kind: 'runtime', event: announce });
|
|
295
321
|
}
|
|
322
|
+
const pending = this.pendingObservations;
|
|
323
|
+
this.pendingObservations = [];
|
|
324
|
+
for (const event of pending)
|
|
325
|
+
this.routeObservation(event);
|
|
296
326
|
}
|
|
297
327
|
async handleLifecycle(parsed) {
|
|
298
328
|
if (!this.capabilities.has('msg_lifecycle_v1')) {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type RuntimeObservationEvent } from '@parall/agent-core';
|
|
2
|
+
/** Only facts the CLI emitted. A result UUID is not a native turn identity. */
|
|
3
|
+
export declare function claudeObservation(frame: Record<string, unknown>): RuntimeObservationEvent | undefined;
|
|
4
|
+
//# sourceMappingURL=runtime-observations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-observations.d.ts","sourceRoot":"","sources":["../src/runtime-observations.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,oBAAoB,CAAC;AAG5B,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,uBAAuB,GAAG,SAAS,CAqErC"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { observationField, runtimeObservation, } from '@parall/agent-core';
|
|
2
|
+
/** Only facts the CLI emitted. A result UUID is not a native turn identity. */
|
|
3
|
+
export function claudeObservation(frame) {
|
|
4
|
+
const sessionId = observationField(frame.session_id);
|
|
5
|
+
if (!sessionId || frame.parent_tool_use_id)
|
|
6
|
+
return;
|
|
7
|
+
const native = {
|
|
8
|
+
session_id: sessionId,
|
|
9
|
+
event_id: observationField(frame.uuid),
|
|
10
|
+
};
|
|
11
|
+
let event;
|
|
12
|
+
let status = 'unknown';
|
|
13
|
+
if (frame.type === 'result') {
|
|
14
|
+
event = 'request_completed';
|
|
15
|
+
status =
|
|
16
|
+
frame.is_error === true ? 'failed' : frame.is_error === false ? 'completed' : 'unknown';
|
|
17
|
+
native.status = observationField(frame.subtype);
|
|
18
|
+
if (typeof frame.num_turns === 'number' &&
|
|
19
|
+
Number.isSafeInteger(frame.num_turns) &&
|
|
20
|
+
frame.num_turns >= 0) {
|
|
21
|
+
native.num_turns = frame.num_turns;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else if (frame.type === 'system') {
|
|
25
|
+
switch (frame.subtype) {
|
|
26
|
+
case 'init':
|
|
27
|
+
event = 'session_initialized';
|
|
28
|
+
status = 'initialized';
|
|
29
|
+
break;
|
|
30
|
+
case 'hook_started':
|
|
31
|
+
case 'hook_response':
|
|
32
|
+
event = frame.subtype === 'hook_started' ? 'hook_started' : 'hook_completed';
|
|
33
|
+
status =
|
|
34
|
+
frame.subtype === 'hook_started'
|
|
35
|
+
? 'running'
|
|
36
|
+
: frame.outcome === 'success'
|
|
37
|
+
? 'completed'
|
|
38
|
+
: frame.outcome === 'error'
|
|
39
|
+
? 'failed'
|
|
40
|
+
: frame.outcome === 'cancelled'
|
|
41
|
+
? 'stopped'
|
|
42
|
+
: 'unknown';
|
|
43
|
+
native.hook_id = observationField(frame.hook_id);
|
|
44
|
+
native.hook_event = observationField(frame.hook_event);
|
|
45
|
+
native.status = observationField(frame.outcome);
|
|
46
|
+
break;
|
|
47
|
+
case 'status':
|
|
48
|
+
case 'session_state_changed':
|
|
49
|
+
event = 'session_status';
|
|
50
|
+
native.status = observationField(frame.status) ?? observationField(frame.state);
|
|
51
|
+
// Null does not prove the runtime is idle; only explicit known states
|
|
52
|
+
// can become public statuses.
|
|
53
|
+
if (native.status === 'compacting' ||
|
|
54
|
+
native.status === 'running' ||
|
|
55
|
+
native.status === 'idle') {
|
|
56
|
+
status = native.status;
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
default:
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else
|
|
64
|
+
return;
|
|
65
|
+
return runtimeObservation(sessionId, {
|
|
66
|
+
runtime: 'claude-code',
|
|
67
|
+
source: 'stream_json',
|
|
68
|
+
event,
|
|
69
|
+
status,
|
|
70
|
+
native,
|
|
71
|
+
});
|
|
72
|
+
}
|
package/dist/spawn-env.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawn-env.d.ts","sourceRoot":"","sources":["../src/spawn-env.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,CAAC,UAAU,EAC5B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,EAChC,IAAI,EAAE;IACJ,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GACA,MAAM,CAAC,UAAU,
|
|
1
|
+
{"version":3,"file":"spawn-env.d.ts","sourceRoot":"","sources":["../src/spawn-env.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,CAAC,UAAU,EAC5B,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,YAAY,CAAC,SAAS,CAAC,EAChC,IAAI,EAAE;IACJ,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,GACA,MAAM,CAAC,UAAU,CAoFnB"}
|
package/dist/spawn-env.js
CHANGED
|
@@ -8,6 +8,9 @@ export function buildSpawnEnv(parentEnv, claudeHome, context, opts) {
|
|
|
8
8
|
const result = {
|
|
9
9
|
...env,
|
|
10
10
|
HOME: claudeHome,
|
|
11
|
+
// Request native state frames for observation without changing user files.
|
|
12
|
+
// Preserve an operator's explicit opt-out; older CLIs can ignore the flag.
|
|
13
|
+
CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS: env.CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS ?? '1',
|
|
11
14
|
PRLL_API_URL: context.apiUrl,
|
|
12
15
|
PRLL_API_KEY: context.apiKey,
|
|
13
16
|
PRLL_ORG_ID: context.orgId,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/claude-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.65.0",
|
|
4
4
|
"description": "Claude Code bridge runtime for self-hosted Parall agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"src"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@parall/agent-core": "1.
|
|
29
|
-
"@parall/cli": "1.
|
|
30
|
-
"@parall/sdk": "1.
|
|
28
|
+
"@parall/agent-core": "1.65.0",
|
|
29
|
+
"@parall/cli": "1.65.0",
|
|
30
|
+
"@parall/sdk": "1.65.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^22.0.0",
|
package/src/dispatch.ts
CHANGED
|
@@ -681,11 +681,11 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
681
681
|
if (this.processes.get(sessionKey) === state) this.processes.delete(sessionKey);
|
|
682
682
|
this.retiring.delete(state);
|
|
683
683
|
},
|
|
684
|
-
// Main-session turns go to the gateway (steps + session activity);
|
|
685
|
-
// fork-session turns only count as busy (fork-scope invariant).
|
|
686
684
|
onRuntimeTurnOpened: (turn) =>
|
|
687
685
|
this.activity.surfaceTurn(turn, this.opts.sessionManager.isMain(sessionKey), log),
|
|
688
686
|
onRuntimeTurnClosed: () => this.maybeApplyRestart(sessionKey, state),
|
|
687
|
+
onObservation: (event) =>
|
|
688
|
+
this.activity.emit({ kind: 'observation', sessionKey, event }, log),
|
|
689
689
|
},
|
|
690
690
|
}),
|
|
691
691
|
done: false,
|
package/src/output-parser.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Readable } from 'node:stream';
|
|
2
2
|
import type { RuntimeEvent } from '@parall/agent-core';
|
|
3
|
+
import { claudeObservation } from './runtime-observations.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Structured snapshot of the `result` frame's LLM-layer discriminators and
|
|
@@ -215,6 +216,9 @@ export async function* parseClaudeStreamJson(
|
|
|
215
216
|
const now = Date.now();
|
|
216
217
|
const eventTimestampMs = parseEventTimestampMs(event) ?? now;
|
|
217
218
|
const eventRecord = event as Record<string, unknown>;
|
|
219
|
+
const observation = claudeObservation(eventRecord);
|
|
220
|
+
// Init must announce the native session before its observations drain.
|
|
221
|
+
if (observation && observation.content.event !== 'session_initialized') yield observation;
|
|
218
222
|
|
|
219
223
|
if (eventRecord.type === 'system' && eventRecord.subtype === 'compact_boundary') {
|
|
220
224
|
const meta =
|
|
@@ -244,6 +248,7 @@ export async function* parseClaudeStreamJson(
|
|
|
244
248
|
...(sessionId ? { sessionId } : {}),
|
|
245
249
|
capabilities,
|
|
246
250
|
};
|
|
251
|
+
if (observation) yield observation;
|
|
247
252
|
continue;
|
|
248
253
|
}
|
|
249
254
|
const subtype = asTrimmedString(eventRecord.subtype) as
|
package/src/process-pump.ts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type GatewayLogger,
|
|
4
4
|
type RuntimeBusyState,
|
|
5
5
|
type RuntimeTurnTrigger,
|
|
6
|
+
type RuntimeObservationEvent,
|
|
6
7
|
} from '@parall/agent-core';
|
|
7
8
|
import { ClaudeBusyTracker } from './busy-state.js';
|
|
8
9
|
import type { ClaudeInputDelivery, ClaudeInputRegistry } from './input-lifecycle.js';
|
|
@@ -24,6 +25,7 @@ export type ClaudeProcessPumpHooks = {
|
|
|
24
25
|
onEof(): void;
|
|
25
26
|
onRuntimeTurnOpened(turn: ClaudeRuntimeTurn): void;
|
|
26
27
|
onRuntimeTurnClosed(): void;
|
|
28
|
+
onObservation?(event: RuntimeObservationEvent): void;
|
|
27
29
|
};
|
|
28
30
|
|
|
29
31
|
export type ClaudeProcessPumpOptions = {
|
|
@@ -67,6 +69,7 @@ export class ClaudeProcessPump {
|
|
|
67
69
|
private idleTimer: NodeJS.Timeout | null = null;
|
|
68
70
|
private activeDrain: ClaudeInputDelivery | null = null;
|
|
69
71
|
private readonly now: () => number;
|
|
72
|
+
private pendingObservations: RuntimeObservationEvent[] = [];
|
|
70
73
|
|
|
71
74
|
constructor(private readonly opts: ClaudeProcessPumpOptions) {
|
|
72
75
|
this.now = opts.now ?? Date.now;
|
|
@@ -194,7 +197,7 @@ export class ClaudeProcessPump {
|
|
|
194
197
|
try {
|
|
195
198
|
for await (const parsed of parser) {
|
|
196
199
|
if (this.closed) break;
|
|
197
|
-
this.touchAll();
|
|
200
|
+
if (parsed.type !== 'observation') this.touchAll();
|
|
198
201
|
await this.route(parsed);
|
|
199
202
|
}
|
|
200
203
|
} catch (err) {
|
|
@@ -208,6 +211,9 @@ export class ClaudeProcessPump {
|
|
|
208
211
|
switch (parsed.type) {
|
|
209
212
|
case 'runtime_activity':
|
|
210
213
|
return;
|
|
214
|
+
case 'observation':
|
|
215
|
+
this.routeObservation(parsed);
|
|
216
|
+
return;
|
|
211
217
|
case 'runtime_init':
|
|
212
218
|
this.handleInit(parsed);
|
|
213
219
|
return;
|
|
@@ -261,6 +267,25 @@ export class ClaudeProcessPump {
|
|
|
261
267
|
this.runtimeTurn?.touch();
|
|
262
268
|
}
|
|
263
269
|
|
|
270
|
+
private routeObservation(event: RuntimeObservationEvent): void {
|
|
271
|
+
if (!this.sessionId) {
|
|
272
|
+
// SessionStart hooks precede init. Keep a bounded startup buffer and
|
|
273
|
+
// release only records whose native session matches the eventual init.
|
|
274
|
+
if (this.pendingObservations.length < 64) this.pendingObservations.push(event);
|
|
275
|
+
else this.opts.log?.warn('Claude startup observation buffer full; dropping observation');
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
if (event.runtimeSessionId !== this.sessionId) {
|
|
279
|
+
this.opts.log?.warn('Claude observation does not match the initialized session');
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
const owner =
|
|
283
|
+
this.ownerFor(false) ??
|
|
284
|
+
(this.activeDrain && !this.activeDrain.terminal ? this.activeDrain : null);
|
|
285
|
+
if (owner) owner.sink.push({ kind: 'runtime', event });
|
|
286
|
+
else this.opts.hooks.onObservation?.(event);
|
|
287
|
+
}
|
|
288
|
+
|
|
264
289
|
private liveDeliveries(): ClaudeInputDelivery[] {
|
|
265
290
|
const live: ClaudeInputDelivery[] = [];
|
|
266
291
|
for (const delivery of this.opts.inputs.values()) if (isLive(delivery)) live.push(delivery);
|
|
@@ -328,6 +353,9 @@ export class ClaudeProcessPump {
|
|
|
328
353
|
delivery.sessionAnnounced = true;
|
|
329
354
|
delivery.sink.push({ kind: 'runtime', event: announce });
|
|
330
355
|
}
|
|
356
|
+
const pending = this.pendingObservations;
|
|
357
|
+
this.pendingObservations = [];
|
|
358
|
+
for (const event of pending) this.routeObservation(event);
|
|
331
359
|
}
|
|
332
360
|
|
|
333
361
|
private async handleLifecycle(parsed: {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import {
|
|
2
|
+
observationField,
|
|
3
|
+
runtimeObservation,
|
|
4
|
+
type RuntimeObservationEvent,
|
|
5
|
+
} from '@parall/agent-core';
|
|
6
|
+
import type { RuntimeObservationContent } from '@parall/sdk';
|
|
7
|
+
|
|
8
|
+
/** Only facts the CLI emitted. A result UUID is not a native turn identity. */
|
|
9
|
+
export function claudeObservation(
|
|
10
|
+
frame: Record<string, unknown>,
|
|
11
|
+
): RuntimeObservationEvent | undefined {
|
|
12
|
+
const sessionId = observationField(frame.session_id);
|
|
13
|
+
if (!sessionId || frame.parent_tool_use_id) return;
|
|
14
|
+
const native: NonNullable<RuntimeObservationContent['native']> = {
|
|
15
|
+
session_id: sessionId,
|
|
16
|
+
event_id: observationField(frame.uuid),
|
|
17
|
+
};
|
|
18
|
+
let event: RuntimeObservationContent['event'];
|
|
19
|
+
let status: RuntimeObservationContent['status'] = 'unknown';
|
|
20
|
+
if (frame.type === 'result') {
|
|
21
|
+
event = 'request_completed';
|
|
22
|
+
status =
|
|
23
|
+
frame.is_error === true ? 'failed' : frame.is_error === false ? 'completed' : 'unknown';
|
|
24
|
+
native.status = observationField(frame.subtype);
|
|
25
|
+
if (
|
|
26
|
+
typeof frame.num_turns === 'number' &&
|
|
27
|
+
Number.isSafeInteger(frame.num_turns) &&
|
|
28
|
+
frame.num_turns >= 0
|
|
29
|
+
) {
|
|
30
|
+
native.num_turns = frame.num_turns;
|
|
31
|
+
}
|
|
32
|
+
} else if (frame.type === 'system') {
|
|
33
|
+
switch (frame.subtype) {
|
|
34
|
+
case 'init':
|
|
35
|
+
event = 'session_initialized';
|
|
36
|
+
status = 'initialized';
|
|
37
|
+
break;
|
|
38
|
+
case 'hook_started':
|
|
39
|
+
case 'hook_response':
|
|
40
|
+
event = frame.subtype === 'hook_started' ? 'hook_started' : 'hook_completed';
|
|
41
|
+
status =
|
|
42
|
+
frame.subtype === 'hook_started'
|
|
43
|
+
? 'running'
|
|
44
|
+
: frame.outcome === 'success'
|
|
45
|
+
? 'completed'
|
|
46
|
+
: frame.outcome === 'error'
|
|
47
|
+
? 'failed'
|
|
48
|
+
: frame.outcome === 'cancelled'
|
|
49
|
+
? 'stopped'
|
|
50
|
+
: 'unknown';
|
|
51
|
+
native.hook_id = observationField(frame.hook_id);
|
|
52
|
+
native.hook_event = observationField(frame.hook_event);
|
|
53
|
+
native.status = observationField(frame.outcome);
|
|
54
|
+
break;
|
|
55
|
+
case 'status':
|
|
56
|
+
case 'session_state_changed':
|
|
57
|
+
event = 'session_status';
|
|
58
|
+
native.status = observationField(frame.status) ?? observationField(frame.state);
|
|
59
|
+
// Null does not prove the runtime is idle; only explicit known states
|
|
60
|
+
// can become public statuses.
|
|
61
|
+
if (
|
|
62
|
+
native.status === 'compacting' ||
|
|
63
|
+
native.status === 'running' ||
|
|
64
|
+
native.status === 'idle'
|
|
65
|
+
) {
|
|
66
|
+
status = native.status;
|
|
67
|
+
}
|
|
68
|
+
break;
|
|
69
|
+
default:
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
} else return;
|
|
73
|
+
return runtimeObservation(sessionId, {
|
|
74
|
+
runtime: 'claude-code',
|
|
75
|
+
source: 'stream_json',
|
|
76
|
+
event,
|
|
77
|
+
status,
|
|
78
|
+
native,
|
|
79
|
+
});
|
|
80
|
+
}
|
package/src/spawn-env.ts
CHANGED
|
@@ -22,6 +22,9 @@ export function buildSpawnEnv(
|
|
|
22
22
|
const result: NodeJS.ProcessEnv = {
|
|
23
23
|
...env,
|
|
24
24
|
HOME: claudeHome,
|
|
25
|
+
// Request native state frames for observation without changing user files.
|
|
26
|
+
// Preserve an operator's explicit opt-out; older CLIs can ignore the flag.
|
|
27
|
+
CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS: env.CLAUDE_CODE_EMIT_SESSION_STATE_EVENTS ?? '1',
|
|
25
28
|
PRLL_API_URL: context.apiUrl,
|
|
26
29
|
PRLL_API_KEY: context.apiKey,
|
|
27
30
|
PRLL_ORG_ID: context.orgId,
|