@parall/agent-core 1.26.0 → 1.26.2

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.
@@ -65,6 +65,7 @@ export type ForkSessionHandle = {
65
65
  export type ForkOpts = {
66
66
  sessionKey: string;
67
67
  context: DispatchContext;
68
+ preDispatchBranchPoint?: string;
68
69
  };
69
70
  export type CleanupForkOpts = {
70
71
  fork: ForkSessionHandle;
@@ -77,5 +78,7 @@ export interface DispatchAdapter {
77
78
  forkSession?(opts: ForkOpts): Promise<ForkSessionHandle | null> | ForkSessionHandle | null;
78
79
  /** Clean up a fork session created by forkSession(). */
79
80
  cleanupFork?(opts: CleanupForkOpts): Promise<void> | void;
81
+ /** Return the current on-disk branch point (leaf ID) for the given session, if available. */
82
+ getBranchPoint?(sessionKey: string): string | undefined;
80
83
  }
81
84
  //# 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,4 +1,5 @@
1
1
  import type { ForkResult, ParallEvent } from "./types.js";
2
2
  export declare function buildEventBody(event: ParallEvent): string;
3
+ export declare function buildForkScopePrefix(event: ParallEvent): string;
3
4
  export declare function buildForkResultPrefix(results: ForkResult[]): string;
4
5
  //# sourceMappingURL=event-format.d.ts.map
@@ -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,CAgDzD;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,CASnE"}
@@ -37,6 +37,13 @@ export function buildEventBody(event) {
37
37
  lines.push(`[Comment ID: prll://${event.messageId}]`);
38
38
  lines.push("", event.body);
39
39
  }
40
+ else if (event.type === "approval") {
41
+ lines.push(`[Event: approval.decided]`);
42
+ lines.push(`[Approval: prll://${event.messageId}]`);
43
+ lines.push(`[Chat: prll://${event.targetId}]`);
44
+ lines.push(`[Decided by: ${event.senderName} (prll://${event.senderId})]`);
45
+ lines.push("", event.body);
46
+ }
40
47
  else if (event.type === "schedule") {
41
48
  lines.push(`[Event: schedule.fired]`);
42
49
  lines.push(`[Schedule: prll://${event.targetId}]`);
@@ -58,6 +65,12 @@ export function buildEventBody(event) {
58
65
  }
59
66
  return lines.join("\n");
60
67
  }
68
+ export function buildForkScopePrefix(event) {
69
+ const targetLabel = event.targetName
70
+ ? `"${event.targetName}" (prll://${event.targetId})`
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`;
73
+ }
61
74
  export function buildForkResultPrefix(results) {
62
75
  if (!results.length)
63
76
  return "";
@@ -86,6 +86,7 @@ export declare class ParallAgentGateway {
86
86
  */
87
87
  private fetchAndHandleScheduleFire;
88
88
  private handleScheduleFire;
89
+ private fetchAndHandleApprovalDecided;
89
90
  private catchUpFromDispatch;
90
91
  private handleHello;
91
92
  private waitForDrain;
@@ -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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"}
@@ -3,7 +3,7 @@ import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { MENTION_ALL_USER_ID } from "@parall/sdk";
6
- import { buildEventBody, buildForkResultPrefix } from "./event-format.js";
6
+ import { buildEventBody, buildForkResultPrefix, buildForkScopePrefix } from "./event-format.js";
7
7
  import { routeTrigger } from "./routing.js";
8
8
  import { clearDispatchMessageId, clearDispatchNoReply, clearSessionMessageId, setDispatchMessageId, setDispatchNoReply, setSessionChatId, setSessionMessageId, } from "./session-state.js";
9
9
  // Parse PRLL_SHUTDOWN_DEADLINE_MS (or any string env value) into a positive
@@ -192,6 +192,19 @@ export class ParallAgentGateway {
192
192
  this.opts.log?.error(`parall[${this.opts.accountId}]: schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
193
193
  }
194
194
  }
195
+ else if (data.event_type === "approval_decided") {
196
+ if (!data.source_id)
197
+ return;
198
+ try {
199
+ const dispatched = await this.fetchAndHandleApprovalDecided(data.source_id, data.actor_id, data.chat_id ?? null);
200
+ if (dispatched) {
201
+ this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => { });
202
+ }
203
+ }
204
+ catch (err) {
205
+ this.opts.log?.error(`parall[${this.opts.accountId}]: approval decided dispatch failed for ${data.source_id}: ${String(err)}`);
206
+ }
207
+ }
195
208
  else if (data.event_type !== "message" && data.event_type !== "task_assign") {
196
209
  // Truly unknown event_type — log so a newly-added dispatch type
197
210
  // not yet wired here surfaces during runtime testing. "message"
@@ -262,7 +275,7 @@ export class ParallAgentGateway {
262
275
  runtimeType: this.opts.runtimeType,
263
276
  runtimeKey: this.opts.runtimeKey,
264
277
  sessionId: this.activeSessionId,
265
- chatId: event.type === "message" ? event.targetId : undefined,
278
+ chatId: (event.type === "message" || event.type === "approval") ? event.targetId : undefined,
266
279
  triggerMessageId: event.messageId,
267
280
  noReply: event.noReply ?? false,
268
281
  contextFilePath: this.opts.contextFilePathForSession?.(sessionKey),
@@ -284,11 +297,13 @@ export class ParallAgentGateway {
284
297
  trigger_type: event.type === "task" ? "task_assign" :
285
298
  event.type === "task_comment" ? "task_comment" :
286
299
  event.type === "schedule" ? "schedule_fire" :
287
- "mention",
300
+ event.type === "approval" ? "approval_decided" :
301
+ "mention",
288
302
  trigger_ref: event.type === "task" ? { task_id: event.targetId } :
289
303
  event.type === "task_comment" ? { comment_id: event.messageId, task_id: event.targetId } :
290
304
  event.type === "schedule" ? { schedule_id: event.targetId, run_id: event.messageId } :
291
- { message_id: event.messageId },
305
+ event.type === "approval" ? { approval_id: event.messageId } :
306
+ { message_id: event.messageId },
292
307
  sender_id: event.senderId,
293
308
  sender_name: event.senderName,
294
309
  summary: event.body.substring(0, 200),
@@ -543,7 +558,7 @@ export class ParallAgentGateway {
543
558
  const last = events[events.length - 1];
544
559
  const earlier = events.slice(0, -1);
545
560
  try {
546
- const dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildEventBody(last), earlier);
561
+ const dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildForkScopePrefix(last) + buildEventBody(last), earlier);
547
562
  if (!dispatched) {
548
563
  // Shutdown short-circuit — resolve un-acked so the server requeues
549
564
  // for the replacement pod and stop draining further items.
@@ -634,6 +649,8 @@ export class ParallAgentGateway {
634
649
  body: "[Orchestrator: fork session(s) completed — review results above]",
635
650
  };
636
651
  this.dispatchState.mainCurrentTargetId = undefined;
652
+ this.dispatchState.mainPreDispatchBranchPoint =
653
+ this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
637
654
  const dispatched = await this.runDispatch(syntheticEvent, this.opts.runtimeKey, forkPrefix + buildEventBody(syntheticEvent));
638
655
  if (!dispatched) {
639
656
  // Shutdown: put the fork results back at the head so the synthetic
@@ -654,8 +671,8 @@ export class ParallAgentGateway {
654
671
  const pendingFork = this.dispatchState.pendingForkResults.splice(0);
655
672
  const forkPrefix = buildForkResultPrefix(pendingFork);
656
673
  this.dispatchState.mainCurrentTargetId = event.targetId;
657
- // Earlier-event input steps are persisted inside runDispatch (behind
658
- // its shutdown gate) so a shutdown short-circuit cannot leave orphans.
674
+ this.dispatchState.mainPreDispatchBranchPoint =
675
+ this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
659
676
  const dispatched = await this.runDispatch(event, this.opts.runtimeKey, forkPrefix + buildEventBody(event), earlier);
660
677
  if (!dispatched) {
661
678
  // Shutdown: skip the ack so the server redelivers these buffered
@@ -679,6 +696,7 @@ export class ParallAgentGateway {
679
696
  this.draining = false;
680
697
  this.dispatchState.mainDispatching = false;
681
698
  this.dispatchState.mainCurrentTargetId = undefined;
699
+ this.dispatchState.mainPreDispatchBranchPoint = undefined;
682
700
  }
683
701
  }
684
702
  async handleInboundEvent(event) {
@@ -689,6 +707,11 @@ export class ParallAgentGateway {
689
707
  const forkPrefix = buildForkResultPrefix(pendingFork);
690
708
  this.dispatchState.mainDispatching = true;
691
709
  this.dispatchState.mainCurrentTargetId = event.targetId;
710
+ // Snapshot the on-disk branch point BEFORE runDispatch starts writing
711
+ // to the session file. Fork sessions created while main is in-flight
712
+ // use this to branch from the clean pre-dispatch state.
713
+ this.dispatchState.mainPreDispatchBranchPoint =
714
+ this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
692
715
  let dispatched = false;
693
716
  try {
694
717
  dispatched = await this.runDispatch(event, this.opts.runtimeKey, forkPrefix + buildEventBody(event));
@@ -728,6 +751,7 @@ export class ParallAgentGateway {
728
751
  const fork = await this.opts.dispatchAdapter.forkSession({
729
752
  sessionKey: this.opts.runtimeKey,
730
753
  context: this.buildDispatchContext(event, this.opts.runtimeKey),
754
+ preDispatchBranchPoint: this.dispatchState.mainPreDispatchBranchPoint,
731
755
  });
732
756
  if (!fork) {
733
757
  this.opts.log?.warn(`parall[${this.opts.accountId}]: fork failed, buffering event for main session`);
@@ -1050,6 +1074,53 @@ export class ParallAgentGateway {
1050
1074
  }
1051
1075
  return dispatched;
1052
1076
  }
1077
+ async fetchAndHandleApprovalDecided(approvalId, actorId, chatId) {
1078
+ let approval = null;
1079
+ try {
1080
+ approval = await this.opts.client.getApproval(approvalId);
1081
+ }
1082
+ catch (err) {
1083
+ const status = err?.status;
1084
+ if (status === 404 || status === 403) {
1085
+ this.opts.log?.warn(`parall[${this.opts.accountId}]: approval ${approvalId} not accessible (${status}), acking stale dispatch`);
1086
+ return true;
1087
+ }
1088
+ this.opts.log?.warn(`parall[${this.opts.accountId}]: approval fetch failed for ${approvalId}, leaving pending: ${String(err)}`);
1089
+ return false;
1090
+ }
1091
+ if (!approval)
1092
+ return true;
1093
+ if (this.shuttingDown)
1094
+ return false;
1095
+ const dedupeKey = `approval:${approval.id}`;
1096
+ if (this.dispatchedTasks.has(dedupeKey))
1097
+ return false;
1098
+ this.dispatchedTasks.add(dedupeKey);
1099
+ this.opts.log?.info(`parall[${this.opts.accountId}]: approval decided: ${approval.id} (${approval.status})`);
1100
+ const statusLabel = approval.status === "approved" ? "Approved" : "Rejected";
1101
+ const execInfo = approval.execution_status ? ` | execution: ${approval.execution_status}` : "";
1102
+ const body = `${statusLabel}: ${approval.title}${execInfo}`;
1103
+ const event = {
1104
+ type: "approval",
1105
+ targetId: chatId ?? approval.chat_id,
1106
+ senderId: actorId ?? approval.decided_by ?? "system",
1107
+ senderName: "approver",
1108
+ messageId: approval.id,
1109
+ body,
1110
+ };
1111
+ let dispatched;
1112
+ try {
1113
+ dispatched = await this.handleInboundEvent(event);
1114
+ }
1115
+ catch (err) {
1116
+ this.dispatchedTasks.delete(dedupeKey);
1117
+ throw err;
1118
+ }
1119
+ if (!dispatched) {
1120
+ this.dispatchedTasks.delete(dedupeKey);
1121
+ }
1122
+ return dispatched;
1123
+ }
1053
1124
  async catchUpFromDispatch(coldStart = false) {
1054
1125
  const minAge = coldStart ? Date.now() - this.COLD_START_WINDOW_MS : 0;
1055
1126
  let cursor;
@@ -1097,6 +1168,9 @@ export class ParallAgentGateway {
1097
1168
  else if (item.event_type === "schedule.fire" && item.source_id) {
1098
1169
  dispatched = await this.fetchAndHandleScheduleFire(item.source_id, item.actor_id);
1099
1170
  }
1171
+ else if (item.event_type === "approval_decided" && item.source_id) {
1172
+ dispatched = await this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null);
1173
+ }
1100
1174
  else if (item.event_type === "message" && item.source_id && item.chat_id) {
1101
1175
  if (!this.tryClaimMessage(item.source_id))
1102
1176
  continue;
package/dist/types.d.ts CHANGED
@@ -15,10 +15,12 @@ export type DispatchState = {
15
15
  activeForks: Map<string, string>;
16
16
  pendingForkResults: ForkResult[];
17
17
  mainBuffer: ParallEvent[];
18
+ /** Snapshot of the session branch point taken BEFORE runDispatch starts on main. */
19
+ mainPreDispatchBranchPoint?: string;
18
20
  };
19
21
  /** Normalized inbound event from Parall. */
20
22
  export type ParallEvent = {
21
- type: "message" | "task" | "task_comment" | "schedule";
23
+ type: "message" | "task" | "task_comment" | "schedule" | "approval";
22
24
  targetId: string;
23
25
  targetName?: string;
24
26
  targetType?: string;
@@ -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;CAC3B,CAAC;AAEF,4CAA4C;AAC5C,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,cAAc,GAAG,UAAU,CAAC;IACvD,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;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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/agent-core",
3
- "version": "1.26.0",
3
+ "version": "1.26.2",
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.0"
29
+ "@parall/sdk": "1.26.2"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^22.0.0",
@@ -49,6 +49,7 @@ export type ForkSessionHandle = {
49
49
  export type ForkOpts = {
50
50
  sessionKey: string;
51
51
  context: DispatchContext;
52
+ preDispatchBranchPoint?: string;
52
53
  };
53
54
 
54
55
  export type CleanupForkOpts = {
@@ -65,4 +66,7 @@ export interface DispatchAdapter {
65
66
 
66
67
  /** Clean up a fork session created by forkSession(). */
67
68
  cleanupFork?(opts: CleanupForkOpts): Promise<void> | void;
69
+
70
+ /** Return the current on-disk branch point (leaf ID) for the given session, if available. */
71
+ getBranchPoint?(sessionKey: string): string | undefined;
68
72
  }
@@ -35,6 +35,12 @@ export function buildEventBody(event: ParallEvent): string {
35
35
  lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
36
36
  lines.push(`[Comment ID: prll://${event.messageId}]`);
37
37
  lines.push("", event.body);
38
+ } else if (event.type === "approval") {
39
+ lines.push(`[Event: approval.decided]`);
40
+ lines.push(`[Approval: prll://${event.messageId}]`);
41
+ lines.push(`[Chat: prll://${event.targetId}]`);
42
+ lines.push(`[Decided by: ${event.senderName} (prll://${event.senderId})]`);
43
+ lines.push("", event.body);
38
44
  } else if (event.type === "schedule") {
39
45
  lines.push(`[Event: schedule.fired]`);
40
46
  lines.push(`[Schedule: prll://${event.targetId}]`);
@@ -54,6 +60,13 @@ export function buildEventBody(event: ParallEvent): string {
54
60
  return lines.join("\n");
55
61
  }
56
62
 
63
+ export function buildForkScopePrefix(event: ParallEvent): string {
64
+ const targetLabel = event.targetName
65
+ ? `"${event.targetName}" (prll://${event.targetId})`
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`;
68
+ }
69
+
57
70
  export function buildForkResultPrefix(results: ForkResult[]): string {
58
71
  if (!results.length) return "";
59
72
  const blocks = results.map((result) => {
@@ -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
+ Approval,
8
9
  Chat,
9
10
  ChatUpdateData,
10
11
  Comment,
@@ -17,7 +18,7 @@ import type {
17
18
  TaskAssignedData,
18
19
  TextContent,
19
20
  } from "@parall/sdk";
20
- import { buildEventBody, buildForkResultPrefix } from "./event-format.js";
21
+ import { buildEventBody, buildForkResultPrefix, buildForkScopePrefix } from "./event-format.js";
21
22
  import type {
22
23
  CleanupForkOpts,
23
24
  DispatchAdapter,
@@ -294,6 +295,16 @@ export class ParallAgentGateway {
294
295
  } catch (err) {
295
296
  this.opts.log?.error(`parall[${this.opts.accountId}]: schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
296
297
  }
298
+ } else if (data.event_type === "approval_decided") {
299
+ if (!data.source_id) return;
300
+ try {
301
+ const dispatched = await this.fetchAndHandleApprovalDecided(data.source_id, data.actor_id, data.chat_id ?? null);
302
+ if (dispatched) {
303
+ this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {});
304
+ }
305
+ } catch (err) {
306
+ this.opts.log?.error(`parall[${this.opts.accountId}]: approval decided dispatch failed for ${data.source_id}: ${String(err)}`);
307
+ }
297
308
  } else if (data.event_type !== "message" && data.event_type !== "task_assign") {
298
309
  // Truly unknown event_type — log so a newly-added dispatch type
299
310
  // not yet wired here surfaces during runtime testing. "message"
@@ -365,7 +376,7 @@ export class ParallAgentGateway {
365
376
  runtimeType: this.opts.runtimeType,
366
377
  runtimeKey: this.opts.runtimeKey,
367
378
  sessionId: this.activeSessionId,
368
- chatId: event.type === "message" ? event.targetId : undefined,
379
+ chatId: (event.type === "message" || event.type === "approval") ? event.targetId : undefined,
369
380
  triggerMessageId: event.messageId,
370
381
  noReply: event.noReply ?? false,
371
382
  contextFilePath: this.opts.contextFilePathForSession?.(sessionKey),
@@ -388,11 +399,13 @@ export class ParallAgentGateway {
388
399
  event.type === "task" ? "task_assign" :
389
400
  event.type === "task_comment" ? "task_comment" :
390
401
  event.type === "schedule" ? "schedule_fire" :
402
+ event.type === "approval" ? "approval_decided" :
391
403
  "mention",
392
404
  trigger_ref:
393
405
  event.type === "task" ? { task_id: event.targetId } :
394
406
  event.type === "task_comment" ? { comment_id: event.messageId, task_id: event.targetId } :
395
407
  event.type === "schedule" ? { schedule_id: event.targetId, run_id: event.messageId } :
408
+ event.type === "approval" ? { approval_id: event.messageId } :
396
409
  { message_id: event.messageId },
397
410
  sender_id: event.senderId,
398
411
  sender_name: event.senderName,
@@ -658,7 +671,7 @@ export class ParallAgentGateway {
658
671
  const last = events[events.length - 1];
659
672
  const earlier = events.slice(0, -1);
660
673
  try {
661
- const dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildEventBody(last), earlier);
674
+ const dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildForkScopePrefix(last) + buildEventBody(last), earlier);
662
675
  if (!dispatched) {
663
676
  // Shutdown short-circuit — resolve un-acked so the server requeues
664
677
  // for the replacement pod and stop draining further items.
@@ -750,6 +763,8 @@ export class ParallAgentGateway {
750
763
  body: "[Orchestrator: fork session(s) completed — review results above]",
751
764
  };
752
765
  this.dispatchState.mainCurrentTargetId = undefined;
766
+ this.dispatchState.mainPreDispatchBranchPoint =
767
+ this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
753
768
  const dispatched = await this.runDispatch(syntheticEvent, this.opts.runtimeKey, forkPrefix + buildEventBody(syntheticEvent));
754
769
  if (!dispatched) {
755
770
  // Shutdown: put the fork results back at the head so the synthetic
@@ -772,8 +787,8 @@ export class ParallAgentGateway {
772
787
  const pendingFork = this.dispatchState.pendingForkResults.splice(0);
773
788
  const forkPrefix = buildForkResultPrefix(pendingFork);
774
789
  this.dispatchState.mainCurrentTargetId = event.targetId;
775
- // Earlier-event input steps are persisted inside runDispatch (behind
776
- // its shutdown gate) so a shutdown short-circuit cannot leave orphans.
790
+ this.dispatchState.mainPreDispatchBranchPoint =
791
+ this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
777
792
  const dispatched = await this.runDispatch(event, this.opts.runtimeKey, forkPrefix + buildEventBody(event), earlier);
778
793
  if (!dispatched) {
779
794
  // Shutdown: skip the ack so the server redelivers these buffered
@@ -796,6 +811,7 @@ export class ParallAgentGateway {
796
811
  this.draining = false;
797
812
  this.dispatchState.mainDispatching = false;
798
813
  this.dispatchState.mainCurrentTargetId = undefined;
814
+ this.dispatchState.mainPreDispatchBranchPoint = undefined;
799
815
  }
800
816
  }
801
817
 
@@ -808,6 +824,11 @@ export class ParallAgentGateway {
808
824
  const forkPrefix = buildForkResultPrefix(pendingFork);
809
825
  this.dispatchState.mainDispatching = true;
810
826
  this.dispatchState.mainCurrentTargetId = event.targetId;
827
+ // Snapshot the on-disk branch point BEFORE runDispatch starts writing
828
+ // to the session file. Fork sessions created while main is in-flight
829
+ // use this to branch from the clean pre-dispatch state.
830
+ this.dispatchState.mainPreDispatchBranchPoint =
831
+ this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
811
832
  let dispatched = false;
812
833
  try {
813
834
  dispatched = await this.runDispatch(event, this.opts.runtimeKey, forkPrefix + buildEventBody(event));
@@ -850,6 +871,7 @@ export class ParallAgentGateway {
850
871
  const fork = await this.opts.dispatchAdapter.forkSession({
851
872
  sessionKey: this.opts.runtimeKey,
852
873
  context: this.buildDispatchContext(event, this.opts.runtimeKey),
874
+ preDispatchBranchPoint: this.dispatchState.mainPreDispatchBranchPoint,
853
875
  });
854
876
 
855
877
  if (!fork) {
@@ -1188,6 +1210,53 @@ export class ParallAgentGateway {
1188
1210
  return dispatched;
1189
1211
  }
1190
1212
 
1213
+ private async fetchAndHandleApprovalDecided(approvalId: string, actorId: string | null, chatId: string | null): Promise<boolean> {
1214
+ let approval: Approval | null = null;
1215
+ try {
1216
+ approval = await this.opts.client.getApproval(approvalId);
1217
+ } catch (err: unknown) {
1218
+ const status = (err as { status?: number })?.status;
1219
+ if (status === 404 || status === 403) {
1220
+ this.opts.log?.warn(`parall[${this.opts.accountId}]: approval ${approvalId} not accessible (${status}), acking stale dispatch`);
1221
+ return true;
1222
+ }
1223
+ this.opts.log?.warn(`parall[${this.opts.accountId}]: approval fetch failed for ${approvalId}, leaving pending: ${String(err)}`);
1224
+ return false;
1225
+ }
1226
+ if (!approval) return true;
1227
+
1228
+ if (this.shuttingDown) return false;
1229
+ const dedupeKey = `approval:${approval.id}`;
1230
+ if (this.dispatchedTasks.has(dedupeKey)) return false;
1231
+ this.dispatchedTasks.add(dedupeKey);
1232
+ this.opts.log?.info(`parall[${this.opts.accountId}]: approval decided: ${approval.id} (${approval.status})`);
1233
+
1234
+ const statusLabel = approval.status === "approved" ? "Approved" : "Rejected";
1235
+ const execInfo = approval.execution_status ? ` | execution: ${approval.execution_status}` : "";
1236
+ const body = `${statusLabel}: ${approval.title}${execInfo}`;
1237
+
1238
+ const event: ParallEvent = {
1239
+ type: "approval",
1240
+ targetId: chatId ?? approval.chat_id,
1241
+ senderId: actorId ?? approval.decided_by ?? "system",
1242
+ senderName: "approver",
1243
+ messageId: approval.id,
1244
+ body,
1245
+ };
1246
+
1247
+ let dispatched: boolean;
1248
+ try {
1249
+ dispatched = await this.handleInboundEvent(event);
1250
+ } catch (err) {
1251
+ this.dispatchedTasks.delete(dedupeKey);
1252
+ throw err;
1253
+ }
1254
+ if (!dispatched) {
1255
+ this.dispatchedTasks.delete(dedupeKey);
1256
+ }
1257
+ return dispatched;
1258
+ }
1259
+
1191
1260
  private async catchUpFromDispatch(coldStart = false) {
1192
1261
  const minAge = coldStart ? Date.now() - this.COLD_START_WINDOW_MS : 0;
1193
1262
  let cursor: string | undefined;
@@ -1231,6 +1300,8 @@ export class ParallAgentGateway {
1231
1300
  dispatched = await this.handleTaskComment(item.source_id, item.task_id, item.actor_id, item.delivery_reason);
1232
1301
  } else if (item.event_type === "schedule.fire" && item.source_id) {
1233
1302
  dispatched = await this.fetchAndHandleScheduleFire(item.source_id, item.actor_id);
1303
+ } else if (item.event_type === "approval_decided" && item.source_id) {
1304
+ dispatched = await this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null);
1234
1305
  } else if (item.event_type === "message" && item.source_id && item.chat_id) {
1235
1306
  if (!this.tryClaimMessage(item.source_id)) continue;
1236
1307
  let msg: Awaited<ReturnType<typeof this.opts.client.getMessage>> | null = null;
package/src/types.ts CHANGED
@@ -12,11 +12,13 @@ export type DispatchState = {
12
12
  activeForks: Map<string, string>;
13
13
  pendingForkResults: ForkResult[];
14
14
  mainBuffer: ParallEvent[];
15
+ /** Snapshot of the session branch point taken BEFORE runDispatch starts on main. */
16
+ mainPreDispatchBranchPoint?: string;
15
17
  };
16
18
 
17
19
  /** Normalized inbound event from Parall. */
18
20
  export type ParallEvent = {
19
- type: "message" | "task" | "task_comment" | "schedule";
21
+ type: "message" | "task" | "task_comment" | "schedule" | "approval";
20
22
  targetId: string;
21
23
  targetName?: string;
22
24
  targetType?: string;