@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 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;AAEvD;;;;;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,CA6NnC"}
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"}
@@ -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);
@@ -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":"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"}
1
+ {"version":3,"file":"process-pump.d.ts","sourceRoot":"","sources":["../src/process-pump.ts"],"names":[],"mappings":"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"}
@@ -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
- this.touchAll();
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
+ }
@@ -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,CAiFnB"}
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.64.0",
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.64.0",
29
- "@parall/cli": "1.64.0",
30
- "@parall/sdk": "1.64.0"
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,
@@ -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
@@ -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,