@parall/agent-core 1.26.3 → 1.28.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.
@@ -24,6 +24,11 @@ export type DispatchContext = {
24
24
  log?: GatewayLogger;
25
25
  };
26
26
  export type RuntimeEvent = {
27
+ type: "runtime_session";
28
+ runtimeSessionId: string;
29
+ runtimeLaneKey?: string;
30
+ runtimeRef?: Record<string, unknown>;
31
+ } | {
27
32
  type: "thinking";
28
33
  text: string;
29
34
  groupKey?: string;
@@ -80,5 +85,7 @@ export interface DispatchAdapter {
80
85
  cleanupFork?(opts: CleanupForkOpts): Promise<void> | void;
81
86
  /** Return the current on-disk branch point (leaf ID) for the given session, if available. */
82
87
  getBranchPoint?(sessionKey: string): string | undefined;
88
+ /** Return the path to the session's history file on disk, if the runtime persists it. */
89
+ getSessionHistoryPath?(sessionKey: string): string | undefined;
83
90
  }
84
91
  //# sourceMappingURL=dispatch-adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch-adapter.d.ts","sourceRoot":"","sources":["../src/dispatch-adapter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dispatch-adapter.d.ts","sourceRoot":"","sources":["../src/dispatch-adapter.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"event-format.d.ts","sourceRoot":"","sources":["../src/event-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAsDzD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAK/D;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CASnE"}
1
+ {"version":3,"file":"event-format.d.ts","sourceRoot":"","sources":["../src/event-format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAM1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAsDzD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAK/D;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,CAWnE"}
@@ -69,7 +69,7 @@ export function buildForkScopePrefix(event) {
69
69
  const targetLabel = event.targetName
70
70
  ? `"${event.targetName}" (prll://${event.targetId})`
71
71
  : `prll://${event.targetId}`;
72
- return `[Fork scope: You are a parallel worker handling ${targetLabel} only. The conversation history above is inherited context — do NOT complete unfinished work, retry failed tool calls, or send messages to other chats from that history. Only act on the event below.]\n\n`;
72
+ return `[Fork scope: You are a parallel worker forked from the main session to handle ${targetLabel}. The conversation history above belongs to the main session's in-progress dispatch — do NOT continue it or complete it, or produce external output (comments, messages) for work visible there. The main session owns its own output.\n\nOnly act on the event below within your target scope. If your event reveals findings about the main session's work, report them in your summary — do not post them externally.\n\nEnd with a brief "Fork summary" of actions taken and decisions made.]\n\n`;
73
73
  }
74
74
  export function buildForkResultPrefix(results) {
75
75
  if (!results.length)
@@ -79,6 +79,10 @@ export function buildForkResultPrefix(results) {
79
79
  lines.push(`[Handled: ${result.sourceEvent.type} — ${result.sourceEvent.summary}]`);
80
80
  if (result.actions.length)
81
81
  lines.push(`[Actions: ${result.actions.join("; ")}]`);
82
+ if (result.agentSummary)
83
+ lines.push(`[Summary: ${sanitizeMeta(result.agentSummary)}]`);
84
+ if (result.historyPath)
85
+ lines.push(`[History: ${result.historyPath}]`);
82
86
  return lines.join("\n");
83
87
  });
84
88
  return blocks.join("\n\n") + "\n\n---\n\n";
@@ -28,6 +28,12 @@ export type ParallGatewayOptions = {
28
28
  ws: ParallWs;
29
29
  runtimeKey: string;
30
30
  }) => Promise<void> | void;
31
+ onSessionBinding?: (state: {
32
+ sessionKey: string;
33
+ agentSessionId: string;
34
+ runtimeSessionId: string;
35
+ runtimeLaneKey: string;
36
+ }) => Promise<void> | void;
31
37
  onBeforeDisconnect?: () => Promise<void> | void;
32
38
  };
33
39
  export declare function parseShutdownDeadlineMs(raw: string | undefined): number | undefined;
@@ -41,6 +47,7 @@ export declare class ParallAgentGateway {
41
47
  private readonly dispatchState;
42
48
  private sessionId;
43
49
  private activeSessionId;
50
+ private readonly sessionBindings;
44
51
  private heartbeatTimer;
45
52
  private hadSuccessfulHello;
46
53
  private lastHeartbeatAt;
@@ -61,11 +68,13 @@ export declare class ParallAgentGateway {
61
68
  private createRuntimeStep;
62
69
  private writeContextFile;
63
70
  private updateContextFileStepId;
71
+ private updateContextFileSessionId;
64
72
  /** @deprecated Use writeContextFile / updateContextFileStepId. */
65
73
  private writeStepIdFile;
66
74
  /** @deprecated Use writeContextFile / updateContextFileStepId. */
67
75
  private clearStepIdFile;
68
76
  private createInputStepsForEarlierEvents;
77
+ private bindRuntimeSession;
69
78
  private runDispatch;
70
79
  private runForkDrainLoop;
71
80
  private drainMainBuffer;
@@ -1 +1 @@
1
- {"version":3,"file":"gateway-base.d.ts","sourceRoot":"","sources":["../src/gateway-base.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"gateway-base.d.ts","sourceRoot":"","sources":["../src/gateway-base.ts"],"names":[],"mappings":"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"}
@@ -66,6 +66,7 @@ export class ParallAgentGateway {
66
66
  };
67
67
  sessionId = "";
68
68
  activeSessionId;
69
+ sessionBindings = new Map();
69
70
  heartbeatTimer = null;
70
71
  hadSuccessfulHello = false;
71
72
  lastHeartbeatAt = Date.now();
@@ -266,6 +267,7 @@ export class ParallAgentGateway {
266
267
  this.opts.ws.sendTyping(chatId, "stop");
267
268
  }
268
269
  buildDispatchContext(event, sessionKey) {
270
+ const binding = this.sessionBindings.get(sessionKey);
269
271
  return {
270
272
  accountId: this.opts.accountId,
271
273
  apiUrl: this.opts.config.parall_url,
@@ -274,7 +276,7 @@ export class ParallAgentGateway {
274
276
  agentUserId: this.opts.agentUserId,
275
277
  runtimeType: this.opts.runtimeType,
276
278
  runtimeKey: this.opts.runtimeKey,
277
- sessionId: this.activeSessionId,
279
+ sessionId: binding?.agentSessionId,
278
280
  chatId: (event.type === "message" || event.type === "approval") ? event.targetId : undefined,
279
281
  triggerMessageId: event.messageId,
280
282
  noReply: event.noReply ?? false,
@@ -284,12 +286,10 @@ export class ParallAgentGateway {
284
286
  log: this.opts.log,
285
287
  };
286
288
  }
287
- async createInputStep(event) {
288
- if (!this.activeSessionId)
289
- return;
289
+ async createInputStep(sessionId, event) {
290
290
  const target = resolveStepTarget(event);
291
291
  try {
292
- await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, {
292
+ await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
293
293
  step_type: "input",
294
294
  target_type: target.target_type,
295
295
  target_id: target.target_id,
@@ -314,14 +314,12 @@ export class ParallAgentGateway {
314
314
  this.opts.log?.warn(`parall[${this.opts.accountId}]: failed to create input step: ${String(err)}`);
315
315
  }
316
316
  }
317
- async createRuntimeStep(event, runtimeEvent, stepIdFilePath, contextFilePath) {
318
- if (!this.activeSessionId)
319
- return;
317
+ async createRuntimeStep(sessionId, event, runtimeEvent, stepIdFilePath, contextFilePath) {
320
318
  const target = resolveStepTarget(event);
321
319
  try {
322
320
  switch (runtimeEvent.type) {
323
321
  case "thinking":
324
- await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, {
322
+ await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
325
323
  step_type: "thinking",
326
324
  target_type: target.target_type,
327
325
  target_id: target.target_id,
@@ -330,7 +328,7 @@ export class ParallAgentGateway {
330
328
  });
331
329
  break;
332
330
  case "text":
333
- await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, {
331
+ await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
334
332
  step_type: "text",
335
333
  target_type: target.target_type,
336
334
  target_id: target.target_id,
@@ -343,7 +341,7 @@ export class ParallAgentGateway {
343
341
  });
344
342
  break;
345
343
  case "tool_call": {
346
- const step = await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, {
344
+ const step = await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
347
345
  step_type: "tool_call",
348
346
  target_type: target.target_type,
349
347
  target_id: target.target_id,
@@ -366,7 +364,7 @@ export class ParallAgentGateway {
366
364
  break;
367
365
  }
368
366
  case "tool_result":
369
- await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, {
367
+ await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
370
368
  step_type: "tool_result",
371
369
  target_type: target.target_type,
372
370
  target_id: target.target_id,
@@ -388,7 +386,7 @@ export class ParallAgentGateway {
388
386
  }
389
387
  break;
390
388
  case "error":
391
- await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, {
389
+ await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
392
390
  step_type: "text",
393
391
  target_type: target.target_type,
394
392
  target_id: target.target_id,
@@ -422,6 +420,17 @@ export class ParallAgentGateway {
422
420
  this.opts.log?.warn(`parall[${this.opts.accountId}]: failed to update context file step_id ${filePath}: ${String(err)}`);
423
421
  }
424
422
  }
423
+ updateContextFileSessionId(filePath, sessionId) {
424
+ try {
425
+ const raw = fs.readFileSync(filePath, "utf8");
426
+ const ctx = JSON.parse(raw);
427
+ ctx.session_id = sessionId;
428
+ fs.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
429
+ }
430
+ catch (err) {
431
+ this.opts.log?.warn(`parall[${this.opts.accountId}]: failed to update context file session_id ${filePath}: ${String(err)}`);
432
+ }
433
+ }
425
434
  /** @deprecated Use writeContextFile / updateContextFileStepId. */
426
435
  writeStepIdFile(filePath, stepId) {
427
436
  try {
@@ -441,16 +450,56 @@ export class ParallAgentGateway {
441
450
  // Best-effort cleanup.
442
451
  }
443
452
  }
444
- async createInputStepsForEarlierEvents(events) {
453
+ async createInputStepsForEarlierEvents(sessionId, events) {
445
454
  for (const event of events) {
446
- await this.createInputStep(event);
455
+ await this.createInputStep(sessionId, event);
447
456
  }
448
457
  }
458
+ async bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath) {
459
+ const runtimeLaneKey = runtimeEvent.runtimeLaneKey || sessionKey;
460
+ const existing = this.sessionBindings.get(sessionKey);
461
+ if (existing &&
462
+ existing.runtimeLaneKey === runtimeLaneKey &&
463
+ existing.runtimeSessionId === runtimeEvent.runtimeSessionId) {
464
+ return existing;
465
+ }
466
+ const parentSessionId = sessionKey === this.opts.runtimeKey
467
+ ? undefined
468
+ : this.sessionBindings.get(this.opts.runtimeKey)?.agentSessionId;
469
+ const runtimeRef = {
470
+ ...(this.opts.runtimeRef ?? {}),
471
+ ...(runtimeEvent.runtimeRef ?? {}),
472
+ };
473
+ const session = await this.opts.client.createAgentSession(this.opts.config.org_id, this.opts.agentUserId, {
474
+ runtime_type: this.opts.runtimeType,
475
+ runtime_key: runtimeLaneKey,
476
+ runtime_lane_key: runtimeLaneKey,
477
+ runtime_session_id: runtimeEvent.runtimeSessionId,
478
+ parent_session_id: parentSessionId,
479
+ runtime_ref: Object.keys(runtimeRef).length > 0 ? runtimeRef : undefined,
480
+ });
481
+ const binding = {
482
+ sessionKey,
483
+ agentSessionId: session.id,
484
+ runtimeLaneKey,
485
+ runtimeSessionId: runtimeEvent.runtimeSessionId,
486
+ parentSessionId,
487
+ };
488
+ this.sessionBindings.set(sessionKey, binding);
489
+ if (sessionKey === this.opts.runtimeKey) {
490
+ this.activeSessionId = session.id;
491
+ }
492
+ if (contextFilePath) {
493
+ this.updateContextFileSessionId(contextFilePath, session.id);
494
+ }
495
+ await this.opts.onSessionBinding?.(binding);
496
+ return binding;
497
+ }
449
498
  // Returns true if the dispatch actually ran; false if skipped because we
450
499
  // are shutting down. Callers MUST treat `false` as "not dispatched" and
451
500
  // skip the server-side ack so the event stays in the dispatch queue for
452
501
  // catch-up on the replacement pod — otherwise we silently drop work.
453
- async runDispatch(event, sessionKey, bodyForAgent, earlierEvents = []) {
502
+ async runDispatch(event, sessionKey, bodyForAgent, earlierEvents = [], captureText) {
454
503
  // Graceful shutdown: once SIGTERM has fired we stop accepting new work.
455
504
  // In-flight dispatches that started before the flag flipped keep running
456
505
  // and are awaited by shutdown() up to SHUTDOWN_DEADLINE_MS.
@@ -478,23 +527,10 @@ export class ParallAgentGateway {
478
527
  // — JS event loop is single-threaded, so shutdown() cannot interleave
479
528
  // here and miss our in-flight count.
480
529
  this.inFlightDispatches++;
530
+ let binding = this.sessionBindings.get(sessionKey);
531
+ let inputStepsCreated = false;
532
+ let triggerMessageSet = false;
481
533
  try {
482
- if (this.activeSessionId) {
483
- try {
484
- await this.opts.client.updateAgentSession(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, { status: "active", trigger_message_id: event.messageId });
485
- }
486
- catch (err) {
487
- this.opts.log?.warn(`parall[${this.opts.accountId}]: failed to set session active: ${String(err)}`);
488
- }
489
- }
490
- // Persist input steps for "earlier events" (batched events that arrived
491
- // while a dispatch was in flight) inside the in-flight window so a
492
- // shutdown short-circuit BEFORE this point cannot leave orphan input
493
- // steps that the replacement pod would duplicate on replay.
494
- if (earlierEvents.length > 0) {
495
- await this.createInputStepsForEarlierEvents(earlierEvents);
496
- }
497
- await this.createInputStep(event);
498
534
  for await (const runtimeEvent of this.opts.dispatchAdapter.dispatch({
499
535
  event,
500
536
  earlierEvents,
@@ -502,17 +538,57 @@ export class ParallAgentGateway {
502
538
  sessionKey,
503
539
  context: dispatchContext,
504
540
  })) {
505
- await this.createRuntimeStep(event, runtimeEvent, stepIdFilePath, contextFilePath);
541
+ if (runtimeEvent.type === "runtime_session") {
542
+ binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath);
543
+ if (!inputStepsCreated) {
544
+ // Persist input steps for "earlier events" (batched events that arrived
545
+ // while a dispatch was in flight) inside the in-flight window so a
546
+ // shutdown short-circuit BEFORE this point cannot leave orphan input
547
+ // steps that the replacement pod would duplicate on replay.
548
+ if (earlierEvents.length > 0) {
549
+ await this.createInputStepsForEarlierEvents(binding.agentSessionId, earlierEvents);
550
+ }
551
+ await this.createInputStep(binding.agentSessionId, event);
552
+ inputStepsCreated = true;
553
+ }
554
+ continue;
555
+ }
556
+ if (!binding) {
557
+ throw new Error(`runtime emitted ${runtimeEvent.type} before runtime_session`);
558
+ }
559
+ if (!triggerMessageSet) {
560
+ triggerMessageSet = true;
561
+ this.opts.client.updateAgentSession(this.opts.config.org_id, this.opts.agentUserId, binding.agentSessionId, { status: "active", trigger_message_id: event.messageId }).catch((err) => this.opts.log?.warn?.(`failed to set session active: ${err}`));
562
+ }
563
+ if (!inputStepsCreated) {
564
+ if (earlierEvents.length > 0) {
565
+ await this.createInputStepsForEarlierEvents(binding.agentSessionId, earlierEvents);
566
+ }
567
+ await this.createInputStep(binding.agentSessionId, event);
568
+ inputStepsCreated = true;
569
+ }
570
+ if (captureText && runtimeEvent.type === "text" && runtimeEvent.text) {
571
+ captureText.push(runtimeEvent.text);
572
+ }
573
+ await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath);
574
+ }
575
+ if (!binding) {
576
+ throw new Error("runtime completed without runtime_session");
506
577
  }
507
578
  }
508
579
  catch (err) {
509
- await this.createRuntimeStep(event, {
510
- type: "error",
511
- message: `Dispatch failed: ${String(err)}`,
512
- }, stepIdFilePath, contextFilePath);
580
+ if (binding) {
581
+ await this.createRuntimeStep(binding.agentSessionId, event, {
582
+ type: "error",
583
+ message: `Dispatch failed: ${String(err)}`,
584
+ }, stepIdFilePath, contextFilePath);
585
+ }
513
586
  throw err;
514
587
  }
515
588
  finally {
589
+ if (triggerMessageSet && binding) {
590
+ this.opts.client.updateAgentSession(this.opts.config.org_id, this.opts.agentUserId, binding.agentSessionId, { status: "idle" }).catch((err) => this.opts.log?.warn?.(`failed to set session idle: ${err}`));
591
+ }
516
592
  clearSessionMessageId(sessionKey);
517
593
  clearDispatchMessageId(sessionKey);
518
594
  clearDispatchNoReply(sessionKey);
@@ -522,14 +598,6 @@ export class ParallAgentGateway {
522
598
  else if (stepIdFilePath) {
523
599
  this.clearStepIdFile(stepIdFilePath);
524
600
  }
525
- if (this.activeSessionId) {
526
- try {
527
- await this.opts.client.updateAgentSession(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, { status: "idle" });
528
- }
529
- catch (err) {
530
- this.opts.log?.warn(`parall[${this.opts.accountId}]: failed to set session idle: ${String(err)}`);
531
- }
532
- }
533
601
  this.inFlightDispatches--;
534
602
  if (this.inFlightDispatches === 0 && this.drainResolvers.length > 0) {
535
603
  const resolvers = this.drainResolvers.splice(0);
@@ -540,6 +608,7 @@ export class ParallAgentGateway {
540
608
  return true;
541
609
  }
542
610
  async runForkDrainLoop(fork) {
611
+ let lastCapturedText = [];
543
612
  try {
544
613
  while (fork.queue.length > 0) {
545
614
  // Early shutdown guard — pure optimization to avoid wasted draining
@@ -558,7 +627,8 @@ export class ParallAgentGateway {
558
627
  const last = events[events.length - 1];
559
628
  const earlier = events.slice(0, -1);
560
629
  try {
561
- const dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildForkScopePrefix(last) + buildEventBody(last), earlier);
630
+ const batchText = [];
631
+ const dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildForkScopePrefix(last) + buildEventBody(last), earlier, batchText);
562
632
  if (!dispatched) {
563
633
  // Shutdown short-circuit — resolve un-acked so the server requeues
564
634
  // for the replacement pod and stop draining further items.
@@ -567,6 +637,8 @@ export class ParallAgentGateway {
567
637
  }
568
638
  break;
569
639
  }
640
+ if (batchText.length > 0)
641
+ lastCapturedText = batchText;
570
642
  fork.processedEvents.push(...events);
571
643
  for (const item of items) {
572
644
  item.resolve(true);
@@ -587,6 +659,14 @@ export class ParallAgentGateway {
587
659
  finally {
588
660
  if (fork.processedEvents.length > 0) {
589
661
  const first = fork.processedEvents[0];
662
+ const agentSummary = lastCapturedText.join("").trim() || undefined;
663
+ let historyPath;
664
+ try {
665
+ historyPath = this.opts.dispatchAdapter.getSessionHistoryPath?.(fork.fork.sessionKey);
666
+ }
667
+ catch (err) {
668
+ this.opts.log?.warn?.(`parall[${this.opts.accountId}]: failed to resolve fork history path: ${String(err)}`);
669
+ }
590
670
  this.dispatchState.pendingForkResults.push({
591
671
  forkSessionKey: fork.fork.sessionKey,
592
672
  sourceEvent: {
@@ -597,10 +677,13 @@ export class ParallAgentGateway {
597
677
  : `${fork.processedEvents.length} events in ${first.targetName ?? fork.targetId}`,
598
678
  },
599
679
  actions: [],
680
+ agentSummary,
681
+ historyPath,
600
682
  });
601
683
  }
602
684
  this.forkStates.delete(fork.targetId);
603
685
  this.dispatchState.activeForks.delete(fork.targetId);
686
+ const forkBinding = this.sessionBindings.get(fork.fork.sessionKey);
604
687
  if (this.opts.dispatchAdapter.cleanupFork) {
605
688
  const cleanupOpts = {
606
689
  fork: fork.fork,
@@ -612,7 +695,7 @@ export class ParallAgentGateway {
612
695
  agentUserId: this.opts.agentUserId,
613
696
  runtimeType: this.opts.runtimeType,
614
697
  runtimeKey: this.opts.runtimeKey,
615
- sessionId: this.activeSessionId,
698
+ sessionId: forkBinding?.agentSessionId,
616
699
  noReply: false,
617
700
  client: this.opts.client,
618
701
  log: this.opts.log,
@@ -620,6 +703,10 @@ export class ParallAgentGateway {
620
703
  };
621
704
  await this.opts.dispatchAdapter.cleanupFork(cleanupOpts);
622
705
  }
706
+ if (forkBinding) {
707
+ this.opts.client.updateAgentSession(this.opts.config.org_id, this.opts.agentUserId, forkBinding.agentSessionId, { status: "closed" }).catch(() => { });
708
+ this.sessionBindings.delete(fork.fork.sessionKey);
709
+ }
623
710
  }
624
711
  }
625
712
  async drainMainBuffer() {
@@ -1233,20 +1320,6 @@ export class ParallAgentGateway {
1233
1320
  try {
1234
1321
  const count = await fetchAllChats(client, config.org_id, this.chatInfoMap);
1235
1322
  log?.info(`parall[${this.opts.accountId}]: WebSocket connected, ${count} chats cached`);
1236
- if (!this.activeSessionId) {
1237
- try {
1238
- const session = await client.createAgentSession(config.org_id, this.opts.agentUserId, {
1239
- runtime_type: this.opts.runtimeType,
1240
- runtime_key: this.opts.runtimeKey,
1241
- runtime_ref: this.opts.runtimeRef,
1242
- });
1243
- this.activeSessionId = session.id;
1244
- log?.info(`parall[${this.opts.accountId}]: created agent session ${session.id}`);
1245
- }
1246
- catch (err) {
1247
- log?.warn(`parall[${this.opts.accountId}]: failed to create agent session: ${String(err)}`);
1248
- }
1249
- }
1250
1323
  await this.opts.onSessionReady?.({
1251
1324
  activeSessionId: this.activeSessionId,
1252
1325
  ws: this.opts.ws,
@@ -1329,16 +1402,6 @@ export class ParallAgentGateway {
1329
1402
  }
1330
1403
  this.activeDispatches.clear();
1331
1404
  await this.opts.onBeforeDisconnect?.();
1332
- if (this.activeSessionId) {
1333
- try {
1334
- await this.opts.client.updateAgentSession(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, {
1335
- status: "completed",
1336
- });
1337
- }
1338
- catch (err) {
1339
- this.opts.log?.warn(`parall[${this.opts.accountId}]: failed to complete session: ${String(err)}`);
1340
- }
1341
- }
1342
1405
  this.opts.ws.disconnect();
1343
1406
  this.opts.log?.info(`parall[${this.opts.accountId}]: disconnected`);
1344
1407
  }
package/dist/types.d.ts CHANGED
@@ -7,6 +7,10 @@ export type ForkResult = {
7
7
  summary: string;
8
8
  };
9
9
  actions: string[];
10
+ /** Agent's own summary of what it did (captured from the fork's last text output). */
11
+ agentSummary?: string;
12
+ /** Path to the fork session's history file on disk, when the runtime persists it. */
13
+ historyPath?: string;
10
14
  };
11
15
  /** Mutable dispatch state for the orchestrator gateway. */
12
16
  export type DispatchState = {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,MAAM,MAAM,UAAU,GAAG;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,MAAM,aAAa,GAAG;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kBAAkB,EAAE,UAAU,EAAE,CAAC;IACjC,UAAU,EAAE,WAAW,EAAE,CAAC;IAC1B,oFAAoF;IACpF,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,cAAc,GAAG,UAAU,GAAG,UAAU,CAAC;IACpE,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,KAAK,CAAC;QAClB,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,8DAA8D;IAC9D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,SAAS,GAAG,eAAe,GAAG,SAAS,GAAG,cAAc,CAAC;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,2FAA2F;AAC3F,MAAM,MAAM,UAAU,GAAG;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,sFAAsF;IACtF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,MAAM,aAAa,GAAG;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,kBAAkB,EAAE,UAAU,EAAE,CAAC;IACjC,UAAU,EAAE,WAAW,EAAE,CAAC;IAC1B,oFAAoF;IACpF,0BAA0B,CAAC,EAAE,MAAM,CAAC;CACrC,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,cAAc,GAAG,UAAU,GAAG,UAAU,CAAC;IACpE,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,KAAK,CAAC;QAClB,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC,CAAC;IACH,8DAA8D;IAC9D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oEAAoE;IACpE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,SAAS,GAAG,eAAe,GAAG,SAAS,GAAG,cAAc,CAAC;IACzE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/agent-core",
3
- "version": "1.26.3",
3
+ "version": "1.28.0",
4
4
  "description": "Shared agent runtime orchestration helpers for Parall",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -26,7 +26,7 @@
26
26
  "src"
27
27
  ],
28
28
  "dependencies": {
29
- "@parall/sdk": "1.26.3"
29
+ "@parall/sdk": "1.28.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^22.0.0",
@@ -27,6 +27,12 @@ export type DispatchContext = {
27
27
  };
28
28
 
29
29
  export type RuntimeEvent =
30
+ | {
31
+ type: "runtime_session";
32
+ runtimeSessionId: string;
33
+ runtimeLaneKey?: string;
34
+ runtimeRef?: Record<string, unknown>;
35
+ }
30
36
  | { type: "thinking"; text: string; groupKey?: string }
31
37
  | { type: "tool_call"; callId: string; toolName: string; input: unknown; startedAt?: string; groupKey?: string }
32
38
  | { type: "tool_result"; callId: string; toolName: string; output: string; error?: string; durationMs?: number; groupKey?: string }
@@ -69,4 +75,7 @@ export interface DispatchAdapter {
69
75
 
70
76
  /** Return the current on-disk branch point (leaf ID) for the given session, if available. */
71
77
  getBranchPoint?(sessionKey: string): string | undefined;
78
+
79
+ /** Return the path to the session's history file on disk, if the runtime persists it. */
80
+ getSessionHistoryPath?(sessionKey: string): string | undefined;
72
81
  }
@@ -64,7 +64,7 @@ export function buildForkScopePrefix(event: ParallEvent): string {
64
64
  const targetLabel = event.targetName
65
65
  ? `"${event.targetName}" (prll://${event.targetId})`
66
66
  : `prll://${event.targetId}`;
67
- return `[Fork scope: You are a parallel worker handling ${targetLabel} only. The conversation history above is inherited context — do NOT complete unfinished work, retry failed tool calls, or send messages to other chats from that history. Only act on the event below.]\n\n`;
67
+ return `[Fork scope: You are a parallel worker forked from the main session to handle ${targetLabel}. The conversation history above belongs to the main session's in-progress dispatch — do NOT continue it or complete it, or produce external output (comments, messages) for work visible there. The main session owns its own output.\n\nOnly act on the event below within your target scope. If your event reveals findings about the main session's work, report them in your summary — do not post them externally.\n\nEnd with a brief "Fork summary" of actions taken and decisions made.]\n\n`;
68
68
  }
69
69
 
70
70
  export function buildForkResultPrefix(results: ForkResult[]): string {
@@ -73,6 +73,8 @@ export function buildForkResultPrefix(results: ForkResult[]): string {
73
73
  const lines = [`[Fork result]`];
74
74
  lines.push(`[Handled: ${result.sourceEvent.type} — ${result.sourceEvent.summary}]`);
75
75
  if (result.actions.length) lines.push(`[Actions: ${result.actions.join("; ")}]`);
76
+ if (result.agentSummary) lines.push(`[Summary: ${sanitizeMeta(result.agentSummary)}]`);
77
+ if (result.historyPath) lines.push(`[History: ${result.historyPath}]`);
76
78
  return lines.join("\n");
77
79
  });
78
80
  return blocks.join("\n\n") + "\n\n---\n\n";
@@ -5,6 +5,7 @@ import { randomUUID } from "node:crypto";
5
5
  import { MENTION_ALL_USER_ID, ParallClient, ParallWs } from "@parall/sdk";
6
6
  import type {
7
7
  AgentConfigUpdateData,
8
+ AgentSessionDB,
8
9
  Approval,
9
10
  Chat,
10
11
  ChatUpdateData,
@@ -104,9 +105,18 @@ export type ParallGatewayOptions = {
104
105
  stepIdFilePathForSession?: (sessionKey: string) => string | undefined;
105
106
  onConfigUpdate?: (data: AgentConfigUpdateData) => Promise<void> | void;
106
107
  onSessionReady?: (state: { activeSessionId?: string; ws: ParallWs; runtimeKey: string }) => Promise<void> | void;
108
+ onSessionBinding?: (state: { sessionKey: string; agentSessionId: string; runtimeSessionId: string; runtimeLaneKey: string }) => Promise<void> | void;
107
109
  onBeforeDisconnect?: () => Promise<void> | void;
108
110
  };
109
111
 
112
+ type AgentSessionBinding = {
113
+ sessionKey: string;
114
+ agentSessionId: string;
115
+ runtimeLaneKey: string;
116
+ runtimeSessionId: string;
117
+ parentSessionId?: string;
118
+ };
119
+
110
120
  // Parse PRLL_SHUTDOWN_DEADLINE_MS (or any string env value) into a positive
111
121
  // integer milliseconds value, or undefined if unset/invalid. Runtimes pass
112
122
  // the result into ParallGatewayOptions.shutdownDeadlineMs; leaving it
@@ -172,6 +182,7 @@ export class ParallAgentGateway {
172
182
 
173
183
  private sessionId = "";
174
184
  private activeSessionId: string | undefined;
185
+ private readonly sessionBindings = new Map<string, AgentSessionBinding>();
175
186
  private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
176
187
  private hadSuccessfulHello = false;
177
188
  private lastHeartbeatAt = Date.now();
@@ -367,6 +378,7 @@ export class ParallAgentGateway {
367
378
  }
368
379
 
369
380
  private buildDispatchContext(event: ParallEvent, sessionKey: string): DispatchContext {
381
+ const binding = this.sessionBindings.get(sessionKey);
370
382
  return {
371
383
  accountId: this.opts.accountId,
372
384
  apiUrl: this.opts.config.parall_url,
@@ -375,7 +387,7 @@ export class ParallAgentGateway {
375
387
  agentUserId: this.opts.agentUserId,
376
388
  runtimeType: this.opts.runtimeType,
377
389
  runtimeKey: this.opts.runtimeKey,
378
- sessionId: this.activeSessionId,
390
+ sessionId: binding?.agentSessionId,
379
391
  chatId: (event.type === "message" || event.type === "approval") ? event.targetId : undefined,
380
392
  triggerMessageId: event.messageId,
381
393
  noReply: event.noReply ?? false,
@@ -386,11 +398,10 @@ export class ParallAgentGateway {
386
398
  };
387
399
  }
388
400
 
389
- private async createInputStep(event: ParallEvent) {
390
- if (!this.activeSessionId) return;
401
+ private async createInputStep(sessionId: string, event: ParallEvent) {
391
402
  const target = resolveStepTarget(event);
392
403
  try {
393
- await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, {
404
+ await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
394
405
  step_type: "input",
395
406
  target_type: target.target_type,
396
407
  target_id: target.target_id,
@@ -417,14 +428,13 @@ export class ParallAgentGateway {
417
428
  }
418
429
  }
419
430
 
420
- private async createRuntimeStep(event: ParallEvent, runtimeEvent: RuntimeEvent, stepIdFilePath?: string, contextFilePath?: string) {
421
- if (!this.activeSessionId) return;
431
+ private async createRuntimeStep(sessionId: string, event: ParallEvent, runtimeEvent: RuntimeEvent, stepIdFilePath?: string, contextFilePath?: string) {
422
432
 
423
433
  const target = resolveStepTarget(event);
424
434
  try {
425
435
  switch (runtimeEvent.type) {
426
436
  case "thinking":
427
- await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, {
437
+ await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
428
438
  step_type: "thinking",
429
439
  target_type: target.target_type,
430
440
  target_id: target.target_id,
@@ -434,7 +444,7 @@ export class ParallAgentGateway {
434
444
  break;
435
445
 
436
446
  case "text":
437
- await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, {
447
+ await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
438
448
  step_type: "text",
439
449
  target_type: target.target_type,
440
450
  target_id: target.target_id,
@@ -448,7 +458,7 @@ export class ParallAgentGateway {
448
458
  break;
449
459
 
450
460
  case "tool_call": {
451
- const step = await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, {
461
+ const step = await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
452
462
  step_type: "tool_call",
453
463
  target_type: target.target_type,
454
464
  target_id: target.target_id,
@@ -471,7 +481,7 @@ export class ParallAgentGateway {
471
481
  }
472
482
 
473
483
  case "tool_result":
474
- await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, {
484
+ await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
475
485
  step_type: "tool_result",
476
486
  target_type: target.target_type,
477
487
  target_id: target.target_id,
@@ -493,7 +503,7 @@ export class ParallAgentGateway {
493
503
  break;
494
504
 
495
505
  case "error":
496
- await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, {
506
+ await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
497
507
  step_type: "text",
498
508
  target_type: target.target_type,
499
509
  target_id: target.target_id,
@@ -527,6 +537,17 @@ export class ParallAgentGateway {
527
537
  }
528
538
  }
529
539
 
540
+ private updateContextFileSessionId(filePath: string, sessionId: string) {
541
+ try {
542
+ const raw = fs.readFileSync(filePath, "utf8");
543
+ const ctx = JSON.parse(raw);
544
+ ctx.session_id = sessionId;
545
+ fs.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
546
+ } catch (err) {
547
+ this.opts.log?.warn(`parall[${this.opts.accountId}]: failed to update context file session_id ${filePath}: ${String(err)}`);
548
+ }
549
+ }
550
+
530
551
  /** @deprecated Use writeContextFile / updateContextFileStepId. */
531
552
  private writeStepIdFile(filePath: string, stepId: string) {
532
553
  try {
@@ -546,12 +567,60 @@ export class ParallAgentGateway {
546
567
  }
547
568
  }
548
569
 
549
- private async createInputStepsForEarlierEvents(events: ParallEvent[]) {
570
+ private async createInputStepsForEarlierEvents(sessionId: string, events: ParallEvent[]) {
550
571
  for (const event of events) {
551
- await this.createInputStep(event);
572
+ await this.createInputStep(sessionId, event);
552
573
  }
553
574
  }
554
575
 
576
+ private async bindRuntimeSession(
577
+ sessionKey: string,
578
+ runtimeEvent: Extract<RuntimeEvent, { type: "runtime_session" }>,
579
+ contextFilePath?: string,
580
+ ): Promise<AgentSessionBinding> {
581
+ const runtimeLaneKey = runtimeEvent.runtimeLaneKey || sessionKey;
582
+ const existing = this.sessionBindings.get(sessionKey);
583
+ if (
584
+ existing &&
585
+ existing.runtimeLaneKey === runtimeLaneKey &&
586
+ existing.runtimeSessionId === runtimeEvent.runtimeSessionId
587
+ ) {
588
+ return existing;
589
+ }
590
+
591
+ const parentSessionId = sessionKey === this.opts.runtimeKey
592
+ ? undefined
593
+ : this.sessionBindings.get(this.opts.runtimeKey)?.agentSessionId;
594
+ const runtimeRef = {
595
+ ...(this.opts.runtimeRef ?? {}),
596
+ ...(runtimeEvent.runtimeRef ?? {}),
597
+ };
598
+ const session: AgentSessionDB = await this.opts.client.createAgentSession(this.opts.config.org_id, this.opts.agentUserId, {
599
+ runtime_type: this.opts.runtimeType,
600
+ runtime_key: runtimeLaneKey,
601
+ runtime_lane_key: runtimeLaneKey,
602
+ runtime_session_id: runtimeEvent.runtimeSessionId,
603
+ parent_session_id: parentSessionId,
604
+ runtime_ref: Object.keys(runtimeRef).length > 0 ? runtimeRef : undefined,
605
+ });
606
+ const binding: AgentSessionBinding = {
607
+ sessionKey,
608
+ agentSessionId: session.id,
609
+ runtimeLaneKey,
610
+ runtimeSessionId: runtimeEvent.runtimeSessionId,
611
+ parentSessionId,
612
+ };
613
+ this.sessionBindings.set(sessionKey, binding);
614
+ if (sessionKey === this.opts.runtimeKey) {
615
+ this.activeSessionId = session.id;
616
+ }
617
+ if (contextFilePath) {
618
+ this.updateContextFileSessionId(contextFilePath, session.id);
619
+ }
620
+ await this.opts.onSessionBinding?.(binding);
621
+ return binding;
622
+ }
623
+
555
624
  // Returns true if the dispatch actually ran; false if skipped because we
556
625
  // are shutting down. Callers MUST treat `false` as "not dispatched" and
557
626
  // skip the server-side ack so the event stays in the dispatch queue for
@@ -561,6 +630,7 @@ export class ParallAgentGateway {
561
630
  sessionKey: string,
562
631
  bodyForAgent: string,
563
632
  earlierEvents: ParallEvent[] = [],
633
+ captureText?: string[],
564
634
  ): Promise<boolean> {
565
635
  // Graceful shutdown: once SIGTERM has fired we stop accepting new work.
566
636
  // In-flight dispatches that started before the flag flipped keep running
@@ -595,24 +665,10 @@ export class ParallAgentGateway {
595
665
  // — JS event loop is single-threaded, so shutdown() cannot interleave
596
666
  // here and miss our in-flight count.
597
667
  this.inFlightDispatches++;
668
+ let binding = this.sessionBindings.get(sessionKey);
669
+ let inputStepsCreated = false;
670
+ let triggerMessageSet = false;
598
671
  try {
599
- if (this.activeSessionId) {
600
- try {
601
- await this.opts.client.updateAgentSession(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, { status: "active", trigger_message_id: event.messageId });
602
- } catch (err) {
603
- this.opts.log?.warn(`parall[${this.opts.accountId}]: failed to set session active: ${String(err)}`);
604
- }
605
- }
606
-
607
- // Persist input steps for "earlier events" (batched events that arrived
608
- // while a dispatch was in flight) inside the in-flight window so a
609
- // shutdown short-circuit BEFORE this point cannot leave orphan input
610
- // steps that the replacement pod would duplicate on replay.
611
- if (earlierEvents.length > 0) {
612
- await this.createInputStepsForEarlierEvents(earlierEvents);
613
- }
614
- await this.createInputStep(event);
615
-
616
672
  for await (const runtimeEvent of this.opts.dispatchAdapter.dispatch({
617
673
  event,
618
674
  earlierEvents,
@@ -620,15 +676,62 @@ export class ParallAgentGateway {
620
676
  sessionKey,
621
677
  context: dispatchContext,
622
678
  })) {
623
- await this.createRuntimeStep(event, runtimeEvent, stepIdFilePath, contextFilePath);
679
+ if (runtimeEvent.type === "runtime_session") {
680
+ binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath);
681
+ if (!inputStepsCreated) {
682
+ // Persist input steps for "earlier events" (batched events that arrived
683
+ // while a dispatch was in flight) inside the in-flight window so a
684
+ // shutdown short-circuit BEFORE this point cannot leave orphan input
685
+ // steps that the replacement pod would duplicate on replay.
686
+ if (earlierEvents.length > 0) {
687
+ await this.createInputStepsForEarlierEvents(binding.agentSessionId, earlierEvents);
688
+ }
689
+ await this.createInputStep(binding.agentSessionId, event);
690
+ inputStepsCreated = true;
691
+ }
692
+ continue;
693
+ }
694
+
695
+ if (!binding) {
696
+ throw new Error(`runtime emitted ${runtimeEvent.type} before runtime_session`);
697
+ }
698
+ if (!triggerMessageSet) {
699
+ triggerMessageSet = true;
700
+ this.opts.client.updateAgentSession(
701
+ this.opts.config.org_id, this.opts.agentUserId, binding.agentSessionId,
702
+ { status: "active", trigger_message_id: event.messageId },
703
+ ).catch((err) => this.opts.log?.warn?.(`failed to set session active: ${err}`));
704
+ }
705
+ if (!inputStepsCreated) {
706
+ if (earlierEvents.length > 0) {
707
+ await this.createInputStepsForEarlierEvents(binding.agentSessionId, earlierEvents);
708
+ }
709
+ await this.createInputStep(binding.agentSessionId, event);
710
+ inputStepsCreated = true;
711
+ }
712
+ if (captureText && runtimeEvent.type === "text" && runtimeEvent.text) {
713
+ captureText.push(runtimeEvent.text);
714
+ }
715
+ await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath);
716
+ }
717
+ if (!binding) {
718
+ throw new Error("runtime completed without runtime_session");
624
719
  }
625
720
  } catch (err) {
626
- await this.createRuntimeStep(event, {
627
- type: "error",
628
- message: `Dispatch failed: ${String(err)}`,
629
- }, stepIdFilePath, contextFilePath);
721
+ if (binding) {
722
+ await this.createRuntimeStep(binding.agentSessionId, event, {
723
+ type: "error",
724
+ message: `Dispatch failed: ${String(err)}`,
725
+ }, stepIdFilePath, contextFilePath);
726
+ }
630
727
  throw err;
631
728
  } finally {
729
+ if (triggerMessageSet && binding) {
730
+ this.opts.client.updateAgentSession(
731
+ this.opts.config.org_id, this.opts.agentUserId, binding.agentSessionId,
732
+ { status: "idle" },
733
+ ).catch((err) => this.opts.log?.warn?.(`failed to set session idle: ${err}`));
734
+ }
632
735
  clearSessionMessageId(sessionKey);
633
736
  clearDispatchMessageId(sessionKey);
634
737
  clearDispatchNoReply(sessionKey);
@@ -637,13 +740,6 @@ export class ParallAgentGateway {
637
740
  } else if (stepIdFilePath) {
638
741
  this.clearStepIdFile(stepIdFilePath);
639
742
  }
640
- if (this.activeSessionId) {
641
- try {
642
- await this.opts.client.updateAgentSession(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, { status: "idle" });
643
- } catch (err) {
644
- this.opts.log?.warn(`parall[${this.opts.accountId}]: failed to set session idle: ${String(err)}`);
645
- }
646
- }
647
743
  this.inFlightDispatches--;
648
744
  if (this.inFlightDispatches === 0 && this.drainResolvers.length > 0) {
649
745
  const resolvers = this.drainResolvers.splice(0);
@@ -654,6 +750,7 @@ export class ParallAgentGateway {
654
750
  }
655
751
 
656
752
  private async runForkDrainLoop(fork: ActiveForkState) {
753
+ let lastCapturedText: string[] = [];
657
754
  try {
658
755
  while (fork.queue.length > 0) {
659
756
  // Early shutdown guard — pure optimization to avoid wasted draining
@@ -671,7 +768,8 @@ export class ParallAgentGateway {
671
768
  const last = events[events.length - 1];
672
769
  const earlier = events.slice(0, -1);
673
770
  try {
674
- const dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildForkScopePrefix(last) + buildEventBody(last), earlier);
771
+ const batchText: string[] = [];
772
+ const dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildForkScopePrefix(last) + buildEventBody(last), earlier, batchText);
675
773
  if (!dispatched) {
676
774
  // Shutdown short-circuit — resolve un-acked so the server requeues
677
775
  // for the replacement pod and stop draining further items.
@@ -680,6 +778,7 @@ export class ParallAgentGateway {
680
778
  }
681
779
  break;
682
780
  }
781
+ if (batchText.length > 0) lastCapturedText = batchText;
683
782
  fork.processedEvents.push(...events);
684
783
  for (const item of items) {
685
784
  item.resolve(true);
@@ -699,6 +798,13 @@ export class ParallAgentGateway {
699
798
  } finally {
700
799
  if (fork.processedEvents.length > 0) {
701
800
  const first = fork.processedEvents[0];
801
+ const agentSummary = lastCapturedText.join("").trim() || undefined;
802
+ let historyPath: string | undefined;
803
+ try {
804
+ historyPath = this.opts.dispatchAdapter.getSessionHistoryPath?.(fork.fork.sessionKey);
805
+ } catch (err) {
806
+ this.opts.log?.warn?.(`parall[${this.opts.accountId}]: failed to resolve fork history path: ${String(err)}`);
807
+ }
702
808
  this.dispatchState.pendingForkResults.push({
703
809
  forkSessionKey: fork.fork.sessionKey,
704
810
  sourceEvent: {
@@ -709,10 +815,13 @@ export class ParallAgentGateway {
709
815
  : `${fork.processedEvents.length} events in ${first.targetName ?? fork.targetId}`,
710
816
  },
711
817
  actions: [],
818
+ agentSummary,
819
+ historyPath,
712
820
  });
713
821
  }
714
822
  this.forkStates.delete(fork.targetId);
715
823
  this.dispatchState.activeForks.delete(fork.targetId);
824
+ const forkBinding = this.sessionBindings.get(fork.fork.sessionKey);
716
825
  if (this.opts.dispatchAdapter.cleanupFork) {
717
826
  const cleanupOpts: CleanupForkOpts = {
718
827
  fork: fork.fork,
@@ -724,7 +833,7 @@ export class ParallAgentGateway {
724
833
  agentUserId: this.opts.agentUserId,
725
834
  runtimeType: this.opts.runtimeType,
726
835
  runtimeKey: this.opts.runtimeKey,
727
- sessionId: this.activeSessionId,
836
+ sessionId: forkBinding?.agentSessionId,
728
837
  noReply: false,
729
838
  client: this.opts.client,
730
839
  log: this.opts.log,
@@ -732,6 +841,13 @@ export class ParallAgentGateway {
732
841
  };
733
842
  await this.opts.dispatchAdapter.cleanupFork(cleanupOpts);
734
843
  }
844
+ if (forkBinding) {
845
+ this.opts.client.updateAgentSession(
846
+ this.opts.config.org_id, this.opts.agentUserId, forkBinding.agentSessionId,
847
+ { status: "closed" },
848
+ ).catch(() => {});
849
+ this.sessionBindings.delete(fork.fork.sessionKey);
850
+ }
735
851
  }
736
852
  }
737
853
 
@@ -1365,20 +1481,6 @@ export class ParallAgentGateway {
1365
1481
  const count = await fetchAllChats(client, config.org_id, this.chatInfoMap);
1366
1482
  log?.info(`parall[${this.opts.accountId}]: WebSocket connected, ${count} chats cached`);
1367
1483
 
1368
- if (!this.activeSessionId) {
1369
- try {
1370
- const session = await client.createAgentSession(config.org_id, this.opts.agentUserId, {
1371
- runtime_type: this.opts.runtimeType,
1372
- runtime_key: this.opts.runtimeKey,
1373
- runtime_ref: this.opts.runtimeRef,
1374
- });
1375
- this.activeSessionId = session.id;
1376
- log?.info(`parall[${this.opts.accountId}]: created agent session ${session.id}`);
1377
- } catch (err) {
1378
- log?.warn(`parall[${this.opts.accountId}]: failed to create agent session: ${String(err)}`);
1379
- }
1380
- }
1381
-
1382
1484
  await this.opts.onSessionReady?.({
1383
1485
  activeSessionId: this.activeSessionId,
1384
1486
  ws: this.opts.ws,
@@ -1466,16 +1568,6 @@ export class ParallAgentGateway {
1466
1568
 
1467
1569
  await this.opts.onBeforeDisconnect?.();
1468
1570
 
1469
- if (this.activeSessionId) {
1470
- try {
1471
- await this.opts.client.updateAgentSession(this.opts.config.org_id, this.opts.agentUserId, this.activeSessionId, {
1472
- status: "completed",
1473
- });
1474
- } catch (err) {
1475
- this.opts.log?.warn(`parall[${this.opts.accountId}]: failed to complete session: ${String(err)}`);
1476
- }
1477
- }
1478
-
1479
1571
  this.opts.ws.disconnect();
1480
1572
  this.opts.log?.info(`parall[${this.opts.accountId}]: disconnected`);
1481
1573
  }
package/src/types.ts CHANGED
@@ -3,6 +3,10 @@ export type ForkResult = {
3
3
  forkSessionKey: string;
4
4
  sourceEvent: { type: string; targetId: string; summary: string };
5
5
  actions: string[];
6
+ /** Agent's own summary of what it did (captured from the fork's last text output). */
7
+ agentSummary?: string;
8
+ /** Path to the fork session's history file on disk, when the runtime persists it. */
9
+ historyPath?: string;
6
10
  };
7
11
 
8
12
  /** Mutable dispatch state for the orchestrator gateway. */