@parall/parall 1.41.0 → 1.42.1

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.
@@ -27412,6 +27412,7 @@ var ENDPOINTS = {
27412
27412
  DISPATCH_CLAIM: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/claim`,
27413
27413
  DISPATCH_STEER: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/steer`,
27414
27414
  DISPATCH_COMPLETE: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/complete`,
27415
+ DISPATCH_COMPLETE_SOURCES: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/complete-sources`,
27415
27416
  DISPATCH_RELEASE: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/release`,
27416
27417
  DISPATCH_HEARTBEAT: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/heartbeat`,
27417
27418
  // Unread
@@ -28577,7 +28578,7 @@ var ParallClient = class _ParallClient {
28577
28578
  async claimDispatch(orgId, req) {
28578
28579
  return this.request("POST", ENDPOINTS.DISPATCH_CLAIM(orgId), req);
28579
28580
  }
28580
- /** Fold a pending same-target WorkItem into a live lane (409 STALE_LANE when dethroned). */
28581
+ /** Fold into a live lane: 409 STALE_LANE when dethroned; 409 INELIGIBLE_REF for a bad ref. */
28581
28582
  async steerDispatch(orgId, req) {
28582
28583
  return this.request("POST", ENDPOINTS.DISPATCH_STEER(orgId), req);
28583
28584
  }
@@ -28585,6 +28586,14 @@ var ParallClient = class _ParallClient {
28585
28586
  async completeDispatch(orgId, req) {
28586
28587
  return this.request("POST", ENDPOINTS.DISPATCH_COMPLETE(orgId), req);
28587
28588
  }
28589
+ /**
28590
+ * End a turn for a lane-less runtime: resolve the turn's folded WorkItems
28591
+ * by source — broad-cover to the turn's reply Effect when one exists,
28592
+ * no_action sweep otherwise. Idempotent.
28593
+ */
28594
+ async completeDispatchSources(orgId, req) {
28595
+ return this.request("POST", ENDPOINTS.DISPATCH_COMPLETE_SOURCES(orgId), req);
28596
+ }
28588
28597
  /** Release a lane on graceful shutdown — members return to pending immediately. */
28589
28598
  async releaseDispatchLane(orgId, lane) {
28590
28599
  return this.request("POST", ENDPOINTS.DISPATCH_RELEASE(orgId), { lane });
@@ -30046,7 +30055,7 @@ async function dispatchLaneGroup(host, opts) {
30046
30055
  async function consumeTypedDispatch(host, ref, run, ack) {
30047
30056
  if (!host.laneLedger || host.ledgerDisabled) {
30048
30057
  if (await run(ref.dispatchEventId))
30049
- ack(ref.dispatchEventId);
30058
+ await ack(ref.dispatchEventId);
30050
30059
  return;
30051
30060
  }
30052
30061
  let lane;
@@ -30056,7 +30065,7 @@ async function consumeTypedDispatch(host, ref, run, ack) {
30056
30065
  if (err instanceof LedgerUnsupportedError) {
30057
30066
  host.disableLedger("claim endpoint missing");
30058
30067
  if (await run(ref.dispatchEventId))
30059
- ack(ref.dispatchEventId);
30068
+ await ack(ref.dispatchEventId);
30060
30069
  return;
30061
30070
  }
30062
30071
  throw err;
@@ -30067,7 +30076,7 @@ async function consumeTypedDispatch(host, ref, run, ack) {
30067
30076
  }
30068
30077
  try {
30069
30078
  if (await run(lane.typedDispatchEventId))
30070
- ack(lane.typedDispatchEventId);
30079
+ await ack(lane.typedDispatchEventId);
30071
30080
  } finally {
30072
30081
  await host.laneLedger.completeIfIdle(lane.laneKey, false).catch(() => {
30073
30082
  });
@@ -30521,29 +30530,33 @@ var ParallAgentGateway = class {
30521
30530
  try {
30522
30531
  await this.consumeTypedDispatch(data.dispatch_event_id ? { dispatchEventId: data.dispatch_event_id } : { sourceType: "task_activity", sourceId: data.id }, (dispatchEventId) => this.handleTaskAssignment(data, data.id, dispatchEventId), (dispatchEventId) => {
30523
30532
  if (dispatchEventId) {
30524
- this.opts.client.ackDispatchByID(this.opts.config.org_id, dispatchEventId).catch(() => {
30533
+ return this.ackDispatchEvent(dispatchEventId, () => {
30534
+ this.dispatchedTasks.delete(`${data.id}:${data.updated_at}`);
30525
30535
  });
30526
- return;
30527
30536
  }
30528
- this.opts.client.ackDispatch(this.opts.config.org_id, {
30537
+ return this.opts.client.ackDispatch(this.opts.config.org_id, {
30529
30538
  source_type: "task_activity",
30530
30539
  source_id: data.id
30531
- }).catch(() => {
30532
- });
30540
+ }).then(() => void 0, () => void 0);
30533
30541
  });
30534
30542
  } catch (err) {
30535
30543
  this.opts.log?.error(`task dispatch failed for ${data.id}: ${String(err)}`);
30536
30544
  }
30537
30545
  });
30538
30546
  ws.on("dispatch.new", async (data) => {
30539
- if (data.event_type === "task_comment") {
30547
+ if (data.event_type === "task_assign") {
30548
+ if (!data.task_id)
30549
+ return;
30550
+ try {
30551
+ await this.handleTaskAssignmentRedrive(data);
30552
+ } catch (err) {
30553
+ this.opts.log?.error(`task assignment re-drive failed for ${data.task_id}: ${String(err)}`);
30554
+ }
30555
+ } else if (data.event_type === "task_comment") {
30540
30556
  if (!data.source_id || !data.task_id)
30541
30557
  return;
30542
30558
  try {
30543
- await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleTaskComment(data.source_id, data.task_id ?? "", data.actor_id, data.delivery_reason), () => {
30544
- this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
30545
- });
30546
- });
30559
+ await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleTaskComment(data.source_id, data.task_id ?? "", data.actor_id, data.delivery_reason), () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)));
30547
30560
  } catch (err) {
30548
30561
  this.opts.log?.error(`task comment dispatch failed for ${data.source_id}: ${String(err)}`);
30549
30562
  }
@@ -30551,10 +30564,7 @@ var ParallAgentGateway = class {
30551
30564
  if (!data.source_id)
30552
30565
  return;
30553
30566
  try {
30554
- await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleWikiComment(data.source_id, data.actor_id, data.delivery_reason), () => {
30555
- this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
30556
- });
30557
- });
30567
+ await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleWikiComment(data.source_id, data.actor_id, data.delivery_reason), () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)));
30558
30568
  } catch (err) {
30559
30569
  this.opts.log?.error(`wiki comment dispatch failed for ${data.source_id}: ${String(err)}`);
30560
30570
  }
@@ -30565,10 +30575,7 @@ var ParallAgentGateway = class {
30565
30575
  await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.handleTaskDispatch(data.task_id ?? "", data.source_id ?? data.task_id ?? "", {
30566
30576
  allowCreator: true,
30567
30577
  dispatchEventId
30568
- }), () => {
30569
- this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
30570
- });
30571
- });
30578
+ }), () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)));
30572
30579
  } catch (err) {
30573
30580
  this.opts.log?.error(`task update dispatch failed for ${data.task_id}: ${String(err)}`);
30574
30581
  }
@@ -30576,10 +30583,7 @@ var ParallAgentGateway = class {
30576
30583
  if (!data.source_id)
30577
30584
  return;
30578
30585
  try {
30579
- await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleScheduleFire(data.source_id, data.actor_id), () => {
30580
- this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
30581
- });
30582
- });
30586
+ await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleScheduleFire(data.source_id, data.actor_id), () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)));
30583
30587
  } catch (err) {
30584
30588
  this.opts.log?.error(`schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
30585
30589
  }
@@ -30587,10 +30591,7 @@ var ParallAgentGateway = class {
30587
30591
  if (!data.source_id)
30588
30592
  return;
30589
30593
  try {
30590
- await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleExternalTriggerRun(data.source_id), () => {
30591
- this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
30592
- });
30593
- });
30594
+ await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleExternalTriggerRun(data.source_id), () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)));
30594
30595
  } catch (err) {
30595
30596
  this.opts.log?.error(`external trigger dispatch failed for ${data.source_id}: ${String(err)}`);
30596
30597
  }
@@ -30598,10 +30599,7 @@ var ParallAgentGateway = class {
30598
30599
  if (!data.source_id)
30599
30600
  return;
30600
30601
  try {
30601
- await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleChannelMessage(data.source_id), () => {
30602
- this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
30603
- });
30604
- });
30602
+ await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleChannelMessage(data.source_id), () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)));
30605
30603
  } catch (err) {
30606
30604
  this.opts.log?.error(`channel message dispatch failed for ${data.source_id}: ${String(err)}`);
30607
30605
  }
@@ -30609,10 +30607,7 @@ var ParallAgentGateway = class {
30609
30607
  if (!data.source_id)
30610
30608
  return;
30611
30609
  try {
30612
- await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleApprovalDecided(data.source_id, data.actor_id, data.chat_id ?? null), () => {
30613
- this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
30614
- });
30615
- });
30610
+ await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleApprovalDecided(data.source_id, data.actor_id, data.chat_id ?? null), () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)));
30616
30611
  } catch (err) {
30617
30612
  this.opts.log?.error(`approval decided dispatch failed for ${data.source_id}: ${String(err)}`);
30618
30613
  }
@@ -30622,7 +30617,7 @@ var ParallAgentGateway = class {
30622
30617
  } catch (err) {
30623
30618
  this.opts.log?.error(`message re-drive failed for ${data.source_id}: ${String(err)}`);
30624
30619
  }
30625
- } else if (data.event_type !== "message" && data.event_type !== "task_assign") {
30620
+ } else if (data.event_type !== "message") {
30626
30621
  this.opts.log?.info(`dispatch.new with unhandled event_type=${String(data.event_type)} (id=${data.id}) \u2014 no-op`);
30627
30622
  }
30628
30623
  });
@@ -30690,6 +30685,50 @@ var ParallAgentGateway = class {
30690
30685
  consumeTypedDispatch(ref, run, ack) {
30691
30686
  return consumeTypedDispatch(this.laneFlowHost(), ref, run, ack);
30692
30687
  }
30688
+ // Typed completion must wait until the administrative ack has either
30689
+ // committed or failed. Errors stay best-effort: a failed ack leaves the row
30690
+ // received, so Complete releases and re-drives it safely.
30691
+ ackDispatchEvent(dispatchEventId, onFailure) {
30692
+ return this.opts.client.ackDispatchByID(this.opts.config.org_id, dispatchEventId).then(() => void 0, (err) => {
30693
+ onFailure?.();
30694
+ this.opts.log?.warn(`dispatch ack failed for ${dispatchEventId}, releasing for re-drive: ${String(err)}`);
30695
+ });
30696
+ }
30697
+ clearTypedDispatchDedupe(item) {
30698
+ switch (item.event_type) {
30699
+ case "task_assign":
30700
+ case "task_update":
30701
+ if (item.task_id) {
30702
+ const prefix = `${item.task_id}:`;
30703
+ for (const key of this.dispatchedTasks) {
30704
+ if (key.startsWith(prefix))
30705
+ this.dispatchedTasks.delete(key);
30706
+ }
30707
+ }
30708
+ break;
30709
+ case "task_comment":
30710
+ case "wiki_comment":
30711
+ if (item.source_id)
30712
+ this.dispatchedTasks.delete(`comment:${item.source_id}`);
30713
+ break;
30714
+ case "schedule.fire":
30715
+ if (item.source_id)
30716
+ this.dispatchedTasks.delete(`schedule_run:${item.source_id}`);
30717
+ break;
30718
+ case "external_trigger":
30719
+ if (item.source_id)
30720
+ this.dispatchedTasks.delete(`external_trigger_run:${item.source_id}`);
30721
+ break;
30722
+ case "channel_message":
30723
+ if (item.source_id)
30724
+ this.dispatchedMessages.delete(`channel_message:${item.source_id}`);
30725
+ break;
30726
+ case "approval_decided":
30727
+ if (item.source_id)
30728
+ this.dispatchedTasks.delete(`approval:${item.source_id}`);
30729
+ break;
30730
+ }
30731
+ }
30693
30732
  buildDispatchContext(event, sessionKey) {
30694
30733
  const binding = this.sessionBindings.get(sessionKey);
30695
30734
  return {
@@ -31656,6 +31695,17 @@ var ParallAgentGateway = class {
31656
31695
  chat_id: item.chat_id
31657
31696
  });
31658
31697
  }
31698
+ // A task assignment's first delivery rides task.assigned, but a typed lane
31699
+ // released without an effect is re-driven as dispatch.new. Reuse the exact
31700
+ // typed claim path used by catch-up so that recovery does not require a
31701
+ // runtime reconnect.
31702
+ async handleTaskAssignmentRedrive(item) {
31703
+ if (!item.task_id)
31704
+ return;
31705
+ await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
31706
+ dispatchEventId
31707
+ }), (dispatchEventId) => this.ackDispatchEvent(dispatchEventId ?? item.id, () => this.clearTypedDispatchDedupe(item)));
31708
+ }
31659
31709
  consumeMessageWorkItem(item) {
31660
31710
  return consumeMessageWorkItem(this.laneFlowHost(), item);
31661
31711
  }
@@ -32121,10 +32171,7 @@ var ParallAgentGateway = class {
32121
32171
  }
32122
32172
  processed++;
32123
32173
  try {
32124
- const ackItem = () => {
32125
- this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
32126
- });
32127
- };
32174
+ const ackItem = () => this.ackDispatchEvent(item.id, () => this.clearTypedDispatchDedupe(item));
32128
32175
  if (item.event_type === "task_assign" && item.task_id) {
32129
32176
  try {
32130
32177
  await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parall/parall",
3
- "version": "1.41.0",
3
+ "version": "1.42.1",
4
4
  "description": "OpenClaw channel plugin for Parall IM",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -16,8 +16,8 @@
16
16
  "openclaw.plugin.json"
17
17
  ],
18
18
  "dependencies": {
19
- "@parall/agent-core": "1.41.0",
20
- "@parall/sdk": "1.41.0"
19
+ "@parall/agent-core": "1.42.1",
20
+ "@parall/sdk": "1.42.1"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^22.0.0",