@parall/parall 1.43.0 → 1.45.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +28 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +58 -0
- package/dist/index.bundle.mjs +498 -97
- package/dist/runtime.d.ts +17 -0
- package/dist/runtime.d.ts.map +1 -1
- package/package.json +3 -3
- package/skills/parall-schedules/SKILL.md +1 -1
- package/skills/parall-tasks/SKILL.md +21 -5
- package/src/gateway.ts +31 -0
- package/src/hooks.ts +60 -0
- package/src/runtime.ts +17 -0
package/dist/index.bundle.mjs
CHANGED
|
@@ -26479,13 +26479,13 @@ function laneKeyForTarget(targetUri, threadRootId) {
|
|
|
26479
26479
|
return Buffer.from(`${targetUri}
|
|
26480
26480
|
${threadRootId ?? ""}`, "utf8").toString("base64url");
|
|
26481
26481
|
}
|
|
26482
|
+
function dispatchLaneContextDir(stateDir) {
|
|
26483
|
+
return path.join(stateDir, "dispatch-lane-context");
|
|
26484
|
+
}
|
|
26482
26485
|
function laneContextFilePath(contextDir, targetUri, threadRootId) {
|
|
26483
26486
|
return path.join(contextDir, `${laneKeyForTarget(targetUri, threadRootId)}.json`);
|
|
26484
26487
|
}
|
|
26485
26488
|
|
|
26486
|
-
// ../agent-core/dist/lane-ledger.js
|
|
26487
|
-
import * as fs from "node:fs";
|
|
26488
|
-
|
|
26489
26489
|
// ../sdk/dist/types.js
|
|
26490
26490
|
var MENTION_ALL_USER_ID = "all";
|
|
26491
26491
|
|
|
@@ -27940,7 +27940,6 @@ var ParallClient = class _ParallClient {
|
|
|
27940
27940
|
async steerDispatch(orgId, req) {
|
|
27941
27941
|
return this.request("POST", ENDPOINTS.DISPATCH_STEER(orgId), req);
|
|
27942
27942
|
}
|
|
27943
|
-
/** End a turn: no_action sweep of the lane's members + lane release + re-drive check. */
|
|
27944
27943
|
async completeDispatch(orgId, req) {
|
|
27945
27944
|
return this.request("POST", ENDPOINTS.DISPATCH_COMPLETE(orgId), req);
|
|
27946
27945
|
}
|
|
@@ -27948,6 +27947,10 @@ var ParallClient = class _ParallClient {
|
|
|
27948
27947
|
* End a turn for a lane-less runtime: resolve the turn's folded WorkItems
|
|
27949
27948
|
* by source — broad-cover to the turn's reply Effect when one exists,
|
|
27950
27949
|
* no_action sweep otherwise. Idempotent.
|
|
27950
|
+
*
|
|
27951
|
+
* @deprecated Legacy ok-only alias — use {@link completeDispatch} with the
|
|
27952
|
+
* `sources` form, which also carries `turn_outcome`. The endpoint retires
|
|
27953
|
+
* at S3b (dispatch-convergence-design.md §3).
|
|
27951
27954
|
*/
|
|
27952
27955
|
async completeDispatchSources(orgId, req) {
|
|
27953
27956
|
return this.request("POST", ENDPOINTS.DISPATCH_COMPLETE_SOURCES(orgId), req);
|
|
@@ -29094,6 +29097,7 @@ var ParallWs = class {
|
|
|
29094
29097
|
};
|
|
29095
29098
|
|
|
29096
29099
|
// ../agent-core/dist/lane-ledger.js
|
|
29100
|
+
import * as fs from "node:fs";
|
|
29097
29101
|
var LedgerUnsupportedError = class extends Error {
|
|
29098
29102
|
};
|
|
29099
29103
|
function isStaleLane(err) {
|
|
@@ -29153,7 +29157,11 @@ var LaneLedger = class {
|
|
|
29153
29157
|
throw err;
|
|
29154
29158
|
}
|
|
29155
29159
|
if (!res.claimed || !res.lane) {
|
|
29156
|
-
|
|
29160
|
+
if (res.reason === "empty") {
|
|
29161
|
+
this.opts.log?.warn(`claim for ${targetUri} came back empty \u2014 nothing foldable; leaving to the reconciler`);
|
|
29162
|
+
} else {
|
|
29163
|
+
this.opts.log?.info(`lane for ${targetUri} held by a healthy incumbent \u2014 leaving events pending for re-drive`);
|
|
29164
|
+
}
|
|
29157
29165
|
return null;
|
|
29158
29166
|
}
|
|
29159
29167
|
const leaseUntilMs = Date.parse(res.lease_until ?? "");
|
|
@@ -29230,6 +29238,18 @@ var LaneLedger = class {
|
|
|
29230
29238
|
* re-drives any same-target pending work. A STALE_LANE answer means a
|
|
29231
29239
|
* takeover already owns the resource — local state is dropped either way.
|
|
29232
29240
|
*/
|
|
29241
|
+
/**
|
|
29242
|
+
* Record that the turn on this lane surfaced a runtime error. The flow
|
|
29243
|
+
* settles an errored lane immediately (dispatchLaneGroup returns 'failed'
|
|
29244
|
+
* after a forced complete), so the bit normally lives for one turn only —
|
|
29245
|
+
* it is the transport between the gateway's per-session error signal and
|
|
29246
|
+
* this lane's complete request.
|
|
29247
|
+
*/
|
|
29248
|
+
markTurnError(laneKey) {
|
|
29249
|
+
const lane = this.lanes.get(laneKey);
|
|
29250
|
+
if (lane)
|
|
29251
|
+
lane.turnError = true;
|
|
29252
|
+
}
|
|
29233
29253
|
async completeIfIdle(laneKey, hasMoreLocal) {
|
|
29234
29254
|
const lane = this.lanes.get(laneKey);
|
|
29235
29255
|
if (!lane || hasMoreLocal)
|
|
@@ -29240,7 +29260,10 @@ var LaneLedger = class {
|
|
|
29240
29260
|
const res = await this.opts.client.completeDispatch(this.opts.orgId, {
|
|
29241
29261
|
lane: lane.lane,
|
|
29242
29262
|
target_uri: lane.targetUri,
|
|
29243
|
-
thread_root_id: lane.threadRootId
|
|
29263
|
+
thread_root_id: lane.threadRootId,
|
|
29264
|
+
// An error turn releases its members for retry instead of sweeping
|
|
29265
|
+
// them as handled (ignored by older servers).
|
|
29266
|
+
turn_outcome: lane.turnError ? "error" : "ok"
|
|
29244
29267
|
});
|
|
29245
29268
|
if (res.swept_no_action > 0 || res.redriven) {
|
|
29246
29269
|
this.opts.log?.info(`lane complete for ${lane.targetUri}: swept ${res.swept_no_action} no_action, redriven=${res.redriven}`);
|
|
@@ -29253,6 +29276,17 @@ var LaneLedger = class {
|
|
|
29253
29276
|
this.opts.log?.warn(`lane complete failed for ${lane.targetUri}: ${String(err)}`);
|
|
29254
29277
|
}
|
|
29255
29278
|
}
|
|
29279
|
+
/**
|
|
29280
|
+
* Renew one lane by its key — the external runtime-activity hook for
|
|
29281
|
+
* adapters whose tool traffic bypasses the RuntimeEvent stream (openclaw
|
|
29282
|
+
* hooks). Scoped to the session's own lane: renewing every lane would let
|
|
29283
|
+
* one busy fork keep an unrelated stalled fork's lane leased forever.
|
|
29284
|
+
*/
|
|
29285
|
+
renewByKey(laneKey) {
|
|
29286
|
+
const lane = this.lanes.get(laneKey);
|
|
29287
|
+
if (lane)
|
|
29288
|
+
this.maybeRenew(lane);
|
|
29289
|
+
}
|
|
29256
29290
|
/**
|
|
29257
29291
|
* Long-turn keepalive: renew the lane's lease on runtime activity, throttled
|
|
29258
29292
|
* so a chatty turn doesn't spam the server. Without this, a legitimately
|
|
@@ -29347,6 +29381,20 @@ var LaneLedger = class {
|
|
|
29347
29381
|
this.lanes.set(lane.laneKey, lane);
|
|
29348
29382
|
return lane;
|
|
29349
29383
|
}
|
|
29384
|
+
/**
|
|
29385
|
+
* Drop a lane's local record without any server call — for a typed lane
|
|
29386
|
+
* whose member the by-id complete just resolved (the server dropped the
|
|
29387
|
+
* vacated lane row in the same transaction). Calling completeIfIdle
|
|
29388
|
+
* instead would fire a lane-form Complete at a lane that no longer exists
|
|
29389
|
+
* and burn an RPC on the guaranteed STALE_LANE answer.
|
|
29390
|
+
*/
|
|
29391
|
+
dropLocal(laneKey) {
|
|
29392
|
+
const lane = this.lanes.get(laneKey);
|
|
29393
|
+
if (!lane)
|
|
29394
|
+
return;
|
|
29395
|
+
this.lanes.delete(laneKey);
|
|
29396
|
+
this.removeLaneContext(lane);
|
|
29397
|
+
}
|
|
29350
29398
|
/**
|
|
29351
29399
|
* Remove the per-lane context file (and its CLI sidecar) when the lane
|
|
29352
29400
|
* ends. A leftover file would make a later cross-context send to the same
|
|
@@ -29394,25 +29442,136 @@ async function dispatchLaneGroup(host, opts) {
|
|
|
29394
29442
|
}
|
|
29395
29443
|
return "foreign";
|
|
29396
29444
|
}
|
|
29445
|
+
host.noteSessionLane(opts.sessionKey, lane.laneKey);
|
|
29397
29446
|
let dispatched = false;
|
|
29398
29447
|
try {
|
|
29399
29448
|
dispatched = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText);
|
|
29400
29449
|
} catch (err) {
|
|
29450
|
+
host.noteSessionLane(opts.sessionKey, null);
|
|
29401
29451
|
await ledger.release(lane.laneKey).catch(() => {
|
|
29402
29452
|
});
|
|
29403
29453
|
throw err;
|
|
29454
|
+
} finally {
|
|
29455
|
+
if (dispatched)
|
|
29456
|
+
host.noteSessionLane(opts.sessionKey, null);
|
|
29404
29457
|
}
|
|
29405
29458
|
if (!dispatched) {
|
|
29406
29459
|
return "shutdown";
|
|
29407
29460
|
}
|
|
29461
|
+
if (host.consumeTurnError(opts.sessionKey)) {
|
|
29462
|
+
ledger.markTurnError(lane.laneKey);
|
|
29463
|
+
for (const msgId of lane.folded.keys()) {
|
|
29464
|
+
host.dispatchedMessages.delete(msgId);
|
|
29465
|
+
}
|
|
29466
|
+
try {
|
|
29467
|
+
host.opts.dispatchAdapter.abortDispatch?.(opts.sessionKey);
|
|
29468
|
+
} catch {
|
|
29469
|
+
}
|
|
29470
|
+
await ledger.completeIfIdle(lane.laneKey, false);
|
|
29471
|
+
return "failed";
|
|
29472
|
+
}
|
|
29408
29473
|
const pendingInjections = host.opts.dispatchAdapter.hasPendingInjections?.(opts.sessionKey) ?? false;
|
|
29409
29474
|
await ledger.completeIfIdle(lane.laneKey, pendingInjections || opts.hasMoreLocal());
|
|
29410
29475
|
return "dispatched";
|
|
29411
29476
|
}
|
|
29477
|
+
function typedLedgerEventIds(host, events) {
|
|
29478
|
+
if (!host.laneLedger || host.ledgerDisabled)
|
|
29479
|
+
return null;
|
|
29480
|
+
const ids = [];
|
|
29481
|
+
for (const ev of events) {
|
|
29482
|
+
if (!ev.dispatchEventId || host.usesLaneLedger(ev))
|
|
29483
|
+
return null;
|
|
29484
|
+
ids.push(ev.dispatchEventId);
|
|
29485
|
+
}
|
|
29486
|
+
return ids.length > 0 ? ids : null;
|
|
29487
|
+
}
|
|
29488
|
+
function isByIDCompleteUnsupported(err) {
|
|
29489
|
+
return err instanceof ApiError && err.status === 400;
|
|
29490
|
+
}
|
|
29491
|
+
async function resolveDispatchByID(host, dispatchEventId, lane) {
|
|
29492
|
+
try {
|
|
29493
|
+
await host.opts.client.completeDispatch(host.opts.config.org_id, {
|
|
29494
|
+
dispatch_event_id: dispatchEventId,
|
|
29495
|
+
...lane ? { lane } : {},
|
|
29496
|
+
turn_outcome: "ok"
|
|
29497
|
+
});
|
|
29498
|
+
return "ok";
|
|
29499
|
+
} catch (err) {
|
|
29500
|
+
if (err instanceof ApiError && err.status === 409)
|
|
29501
|
+
return "stale";
|
|
29502
|
+
if (isByIDCompleteUnsupported(err))
|
|
29503
|
+
return "unsupported";
|
|
29504
|
+
host.opts.log?.warn(`by-id complete failed for ${dispatchEventId} \u2014 leaving for re-drive: ${String(err)}`);
|
|
29505
|
+
return "failed";
|
|
29506
|
+
}
|
|
29507
|
+
}
|
|
29508
|
+
function clearTypedDedupeForEvent(host, event) {
|
|
29509
|
+
const sourceId = event.ackSourceId;
|
|
29510
|
+
if (!sourceId)
|
|
29511
|
+
return;
|
|
29512
|
+
switch (event.ackSourceType) {
|
|
29513
|
+
case "task_activity": {
|
|
29514
|
+
const prefix = `${event.targetId}:`;
|
|
29515
|
+
for (const key of host.dispatchedTasks) {
|
|
29516
|
+
if (key.startsWith(prefix))
|
|
29517
|
+
host.dispatchedTasks.delete(key);
|
|
29518
|
+
}
|
|
29519
|
+
break;
|
|
29520
|
+
}
|
|
29521
|
+
case "comment":
|
|
29522
|
+
host.dispatchedTasks.delete(`comment:${sourceId}`);
|
|
29523
|
+
break;
|
|
29524
|
+
case "schedule_run":
|
|
29525
|
+
host.dispatchedTasks.delete(`schedule_run:${sourceId}`);
|
|
29526
|
+
break;
|
|
29527
|
+
case "external_trigger_run":
|
|
29528
|
+
host.dispatchedTasks.delete(`external_trigger_run:${sourceId}`);
|
|
29529
|
+
break;
|
|
29530
|
+
case "channel_message":
|
|
29531
|
+
host.dispatchedMessages.delete(`channel_message:${sourceId}`);
|
|
29532
|
+
break;
|
|
29533
|
+
case "approval":
|
|
29534
|
+
host.dispatchedTasks.delete(`approval:${sourceId}`);
|
|
29535
|
+
break;
|
|
29536
|
+
}
|
|
29537
|
+
}
|
|
29538
|
+
async function settleDrainedTypedGroup(host, events, ids, turnErrored) {
|
|
29539
|
+
if (turnErrored) {
|
|
29540
|
+
host.opts.log?.info(`buffered typed turn for ${events[events.length - 1]?.messageId} surfaced a runtime error \u2014 leaving for re-drive`);
|
|
29541
|
+
for (const event of events)
|
|
29542
|
+
clearTypedDedupeForEvent(host, event);
|
|
29543
|
+
return;
|
|
29544
|
+
}
|
|
29545
|
+
const legacyAckFrom = async (start) => {
|
|
29546
|
+
for (const [j, id] of ids.slice(start).entries()) {
|
|
29547
|
+
try {
|
|
29548
|
+
await host.opts.client.ackDispatchByID(host.opts.config.org_id, id);
|
|
29549
|
+
} catch {
|
|
29550
|
+
clearTypedDedupeForEvent(host, events[start + j]);
|
|
29551
|
+
}
|
|
29552
|
+
}
|
|
29553
|
+
};
|
|
29554
|
+
if (host.typedByIdCompleteUnsupported) {
|
|
29555
|
+
await legacyAckFrom(0);
|
|
29556
|
+
return;
|
|
29557
|
+
}
|
|
29558
|
+
for (const [i, id] of ids.entries()) {
|
|
29559
|
+
const outcome = await resolveDispatchByID(host, id);
|
|
29560
|
+
if (outcome === "unsupported") {
|
|
29561
|
+
host.typedByIdCompleteUnsupported = true;
|
|
29562
|
+
host.opts.log?.warn("server predates the by-id dispatch complete \u2014 falling back to legacy typed acks");
|
|
29563
|
+
await legacyAckFrom(i);
|
|
29564
|
+
return;
|
|
29565
|
+
}
|
|
29566
|
+
if (outcome !== "ok") {
|
|
29567
|
+
clearTypedDedupeForEvent(host, events[i]);
|
|
29568
|
+
}
|
|
29569
|
+
}
|
|
29570
|
+
}
|
|
29412
29571
|
var TYPED_BACKOFF_BASE_MS = 2e3;
|
|
29413
29572
|
var TYPED_BACKOFF_CAP_MS = 5 * 6e4;
|
|
29414
29573
|
var TYPED_BACKOFF_MAP_CAP = 512;
|
|
29415
|
-
async function consumeTypedDispatch(host, ref, run,
|
|
29574
|
+
async function consumeTypedDispatch(host, ref, run, hooks) {
|
|
29416
29575
|
const backoffKey = ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`;
|
|
29417
29576
|
const armed = host.typedRedriveBackoff.get(backoffKey);
|
|
29418
29577
|
if (armed) {
|
|
@@ -29428,8 +29587,8 @@ async function consumeTypedDispatch(host, ref, run, ack) {
|
|
|
29428
29587
|
return;
|
|
29429
29588
|
}
|
|
29430
29589
|
const settleAck = (ackResult) => ackResult !== false;
|
|
29431
|
-
const settle = (
|
|
29432
|
-
if (
|
|
29590
|
+
const settle = (resolved2) => {
|
|
29591
|
+
if (resolved2) {
|
|
29433
29592
|
host.typedRedriveBackoff.delete(backoffKey);
|
|
29434
29593
|
return;
|
|
29435
29594
|
}
|
|
@@ -29443,13 +29602,13 @@ async function consumeTypedDispatch(host, ref, run, ack) {
|
|
|
29443
29602
|
host.typedRedriveBackoff.set(backoffKey, { failures, until: Date.now() + backoffMs });
|
|
29444
29603
|
};
|
|
29445
29604
|
const runLegacy = async () => {
|
|
29446
|
-
let
|
|
29605
|
+
let acked = false;
|
|
29447
29606
|
try {
|
|
29448
29607
|
if (await run(ref.dispatchEventId)) {
|
|
29449
|
-
|
|
29608
|
+
acked = settleAck(await hooks.legacyAck(ref.dispatchEventId));
|
|
29450
29609
|
}
|
|
29451
29610
|
} finally {
|
|
29452
|
-
settle(
|
|
29611
|
+
settle(acked);
|
|
29453
29612
|
}
|
|
29454
29613
|
};
|
|
29455
29614
|
if (!host.laneLedger || host.ledgerDisabled) {
|
|
@@ -29471,15 +29630,39 @@ async function consumeTypedDispatch(host, ref, run, ack) {
|
|
|
29471
29630
|
host.opts.log?.info(`typed dispatch ${ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`} not claimable (held elsewhere or already resolved) \u2014 skipping`);
|
|
29472
29631
|
return;
|
|
29473
29632
|
}
|
|
29474
|
-
let
|
|
29633
|
+
let resolved = false;
|
|
29634
|
+
let viaLegacyAck = false;
|
|
29475
29635
|
try {
|
|
29476
29636
|
if (await run(lane.typedDispatchEventId)) {
|
|
29477
|
-
|
|
29637
|
+
if (host.typedByIdCompleteUnsupported || !lane.typedDispatchEventId) {
|
|
29638
|
+
viaLegacyAck = true;
|
|
29639
|
+
resolved = settleAck(await hooks.legacyAck(lane.typedDispatchEventId));
|
|
29640
|
+
} else {
|
|
29641
|
+
const outcome = await resolveDispatchByID(host, lane.typedDispatchEventId, lane.lane);
|
|
29642
|
+
if (outcome === "unsupported") {
|
|
29643
|
+
host.typedByIdCompleteUnsupported = true;
|
|
29644
|
+
host.opts.log?.warn("server predates the by-id dispatch complete \u2014 falling back to the legacy typed ack");
|
|
29645
|
+
viaLegacyAck = true;
|
|
29646
|
+
resolved = settleAck(await hooks.legacyAck(lane.typedDispatchEventId));
|
|
29647
|
+
} else if (outcome === "stale") {
|
|
29648
|
+
hooks.clearDedupe?.();
|
|
29649
|
+
resolved = true;
|
|
29650
|
+
} else {
|
|
29651
|
+
resolved = outcome === "ok";
|
|
29652
|
+
if (!resolved) {
|
|
29653
|
+
hooks.clearDedupe?.();
|
|
29654
|
+
}
|
|
29655
|
+
}
|
|
29656
|
+
}
|
|
29478
29657
|
}
|
|
29479
29658
|
} finally {
|
|
29480
|
-
settle(
|
|
29481
|
-
|
|
29482
|
-
|
|
29659
|
+
settle(resolved);
|
|
29660
|
+
if (resolved && !viaLegacyAck) {
|
|
29661
|
+
host.laneLedger.dropLocal(lane.laneKey);
|
|
29662
|
+
} else {
|
|
29663
|
+
await host.laneLedger.completeIfIdle(lane.laneKey, false).catch(() => {
|
|
29664
|
+
});
|
|
29665
|
+
}
|
|
29483
29666
|
}
|
|
29484
29667
|
}
|
|
29485
29668
|
async function consumeMessageWorkItem(host, item) {
|
|
@@ -29488,6 +29671,16 @@ async function consumeMessageWorkItem(host, item) {
|
|
|
29488
29671
|
if (!host.tryClaimMessage(item.source_id))
|
|
29489
29672
|
return;
|
|
29490
29673
|
const ackItem = () => {
|
|
29674
|
+
if (host.laneLedger && !host.ledgerDisabled && !host.typedByIdCompleteUnsupported) {
|
|
29675
|
+
void resolveDispatchByID(host, item.id).then((outcome) => {
|
|
29676
|
+
if (outcome === "unsupported") {
|
|
29677
|
+
host.typedByIdCompleteUnsupported = true;
|
|
29678
|
+
host.opts.client.ackDispatchByID(host.opts.config.org_id, item.id).catch(() => {
|
|
29679
|
+
});
|
|
29680
|
+
}
|
|
29681
|
+
});
|
|
29682
|
+
return;
|
|
29683
|
+
}
|
|
29491
29684
|
host.opts.client.ackDispatchByID(host.opts.config.org_id, item.id).catch(() => {
|
|
29492
29685
|
});
|
|
29493
29686
|
};
|
|
@@ -30608,20 +30801,23 @@ var ParallAgentGateway = class {
|
|
|
30608
30801
|
if (data.status !== "todo" && data.status !== "in_progress")
|
|
30609
30802
|
return;
|
|
30610
30803
|
try {
|
|
30611
|
-
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),
|
|
30612
|
-
|
|
30613
|
-
|
|
30804
|
+
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), {
|
|
30805
|
+
legacyAck: (dispatchEventId) => {
|
|
30806
|
+
if (dispatchEventId) {
|
|
30807
|
+
return this.ackDispatchEvent(dispatchEventId, () => {
|
|
30808
|
+
this.dispatchedTasks.delete(`${data.id}:${data.updated_at}`);
|
|
30809
|
+
});
|
|
30810
|
+
}
|
|
30811
|
+
return this.opts.client.ackDispatch(this.opts.config.org_id, {
|
|
30812
|
+
source_type: "task_activity",
|
|
30813
|
+
source_id: data.id
|
|
30814
|
+
}).then(() => true, (err) => {
|
|
30614
30815
|
this.dispatchedTasks.delete(`${data.id}:${data.updated_at}`);
|
|
30816
|
+
this.opts.log?.warn(`dispatch ack failed for task ${data.id}, releasing for re-drive: ${String(err)}`);
|
|
30817
|
+
return false;
|
|
30615
30818
|
});
|
|
30616
|
-
}
|
|
30617
|
-
|
|
30618
|
-
source_type: "task_activity",
|
|
30619
|
-
source_id: data.id
|
|
30620
|
-
}).then(() => true, (err) => {
|
|
30621
|
-
this.dispatchedTasks.delete(`${data.id}:${data.updated_at}`);
|
|
30622
|
-
this.opts.log?.warn(`dispatch ack failed for task ${data.id}, releasing for re-drive: ${String(err)}`);
|
|
30623
|
-
return false;
|
|
30624
|
-
});
|
|
30819
|
+
},
|
|
30820
|
+
clearDedupe: () => this.dispatchedTasks.delete(`${data.id}:${data.updated_at}`)
|
|
30625
30821
|
});
|
|
30626
30822
|
} catch (err) {
|
|
30627
30823
|
this.opts.log?.error(`task dispatch failed for ${data.id}: ${String(err)}`);
|
|
@@ -30640,7 +30836,10 @@ var ParallAgentGateway = class {
|
|
|
30640
30836
|
if (!data.source_id || !data.task_id)
|
|
30641
30837
|
return;
|
|
30642
30838
|
try {
|
|
30643
|
-
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleTaskComment(data.source_id, data.task_id ?? "", data.actor_id, data.delivery_reason
|
|
30839
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.handleTaskComment(data.source_id, data.task_id ?? "", data.actor_id, data.delivery_reason, dispatchEventId), {
|
|
30840
|
+
legacyAck: () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)),
|
|
30841
|
+
clearDedupe: () => this.clearTypedDispatchDedupe(data)
|
|
30842
|
+
});
|
|
30644
30843
|
} catch (err) {
|
|
30645
30844
|
this.opts.log?.error(`task comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30646
30845
|
}
|
|
@@ -30648,7 +30847,10 @@ var ParallAgentGateway = class {
|
|
|
30648
30847
|
if (!data.source_id)
|
|
30649
30848
|
return;
|
|
30650
30849
|
try {
|
|
30651
|
-
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleWikiComment(data.source_id, data.actor_id, data.delivery_reason
|
|
30850
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.handleWikiComment(data.source_id, data.actor_id, data.delivery_reason, dispatchEventId), {
|
|
30851
|
+
legacyAck: () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)),
|
|
30852
|
+
clearDedupe: () => this.clearTypedDispatchDedupe(data)
|
|
30853
|
+
});
|
|
30652
30854
|
} catch (err) {
|
|
30653
30855
|
this.opts.log?.error(`wiki comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30654
30856
|
}
|
|
@@ -30659,7 +30861,10 @@ var ParallAgentGateway = class {
|
|
|
30659
30861
|
await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.handleTaskDispatch(data.task_id ?? "", data.source_id ?? data.task_id ?? "", {
|
|
30660
30862
|
allowCreator: true,
|
|
30661
30863
|
dispatchEventId
|
|
30662
|
-
}),
|
|
30864
|
+
}), {
|
|
30865
|
+
legacyAck: () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)),
|
|
30866
|
+
clearDedupe: () => this.clearTypedDispatchDedupe(data)
|
|
30867
|
+
});
|
|
30663
30868
|
} catch (err) {
|
|
30664
30869
|
this.opts.log?.error(`task update dispatch failed for ${data.task_id}: ${String(err)}`);
|
|
30665
30870
|
}
|
|
@@ -30667,7 +30872,10 @@ var ParallAgentGateway = class {
|
|
|
30667
30872
|
if (!data.source_id)
|
|
30668
30873
|
return;
|
|
30669
30874
|
try {
|
|
30670
|
-
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleScheduleFire(data.source_id, data.actor_id
|
|
30875
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.fetchAndHandleScheduleFire(data.source_id, data.actor_id, dispatchEventId), {
|
|
30876
|
+
legacyAck: () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)),
|
|
30877
|
+
clearDedupe: () => this.clearTypedDispatchDedupe(data)
|
|
30878
|
+
});
|
|
30671
30879
|
} catch (err) {
|
|
30672
30880
|
this.opts.log?.error(`schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30673
30881
|
}
|
|
@@ -30675,7 +30883,10 @@ var ParallAgentGateway = class {
|
|
|
30675
30883
|
if (!data.source_id)
|
|
30676
30884
|
return;
|
|
30677
30885
|
try {
|
|
30678
|
-
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleExternalTriggerRun(data.source_id
|
|
30886
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.fetchAndHandleExternalTriggerRun(data.source_id, dispatchEventId), {
|
|
30887
|
+
legacyAck: () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)),
|
|
30888
|
+
clearDedupe: () => this.clearTypedDispatchDedupe(data)
|
|
30889
|
+
});
|
|
30679
30890
|
} catch (err) {
|
|
30680
30891
|
this.opts.log?.error(`external trigger dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30681
30892
|
}
|
|
@@ -30683,7 +30894,10 @@ var ParallAgentGateway = class {
|
|
|
30683
30894
|
if (!data.source_id)
|
|
30684
30895
|
return;
|
|
30685
30896
|
try {
|
|
30686
|
-
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleChannelMessage(data.source_id
|
|
30897
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.fetchAndHandleChannelMessage(data.source_id, dispatchEventId), {
|
|
30898
|
+
legacyAck: () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)),
|
|
30899
|
+
clearDedupe: () => this.clearTypedDispatchDedupe(data)
|
|
30900
|
+
});
|
|
30687
30901
|
} catch (err) {
|
|
30688
30902
|
this.opts.log?.error(`channel message dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30689
30903
|
}
|
|
@@ -30691,7 +30905,10 @@ var ParallAgentGateway = class {
|
|
|
30691
30905
|
if (!data.source_id)
|
|
30692
30906
|
return;
|
|
30693
30907
|
try {
|
|
30694
|
-
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleApprovalDecided(data.source_id, data.actor_id, data.chat_id ?? null
|
|
30908
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.fetchAndHandleApprovalDecided(data.source_id, data.actor_id, data.chat_id ?? null, dispatchEventId), {
|
|
30909
|
+
legacyAck: () => this.ackDispatchEvent(data.id, () => this.clearTypedDispatchDedupe(data)),
|
|
30910
|
+
clearDedupe: () => this.clearTypedDispatchDedupe(data)
|
|
30911
|
+
});
|
|
30695
30912
|
} catch (err) {
|
|
30696
30913
|
this.opts.log?.error(`approval decided dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30697
30914
|
}
|
|
@@ -30728,6 +30945,44 @@ var ParallAgentGateway = class {
|
|
|
30728
30945
|
this.dispatchedMessages.add(id);
|
|
30729
30946
|
return true;
|
|
30730
30947
|
}
|
|
30948
|
+
/**
|
|
30949
|
+
* Lane currently being dispatched per session — lets external activity
|
|
30950
|
+
* signals renew exactly the caller's lane (renewing all lanes would keep
|
|
30951
|
+
* an unrelated stalled fork's lane leased forever).
|
|
30952
|
+
*/
|
|
30953
|
+
sessionActiveLanes = /* @__PURE__ */ new Map();
|
|
30954
|
+
noteSessionLane(sessionKey, laneKey) {
|
|
30955
|
+
if (laneKey == null)
|
|
30956
|
+
this.sessionActiveLanes.delete(sessionKey);
|
|
30957
|
+
else
|
|
30958
|
+
this.sessionActiveLanes.set(sessionKey, laneKey);
|
|
30959
|
+
}
|
|
30960
|
+
/**
|
|
30961
|
+
* External runtime-activity signal for adapters whose tool activity does
|
|
30962
|
+
* not flow through the RuntimeEvent stream (openclaw hooks call this from
|
|
30963
|
+
* the tool-call lifecycle): renews the session's OWN active ledger lane so
|
|
30964
|
+
* a long tool call cannot outlive the lease and get dethroned mid-turn.
|
|
30965
|
+
* No-op without an active ledger lane for the session.
|
|
30966
|
+
*/
|
|
30967
|
+
touchRuntimeActivity(sessionKey) {
|
|
30968
|
+
if (this.ledgerDisabled)
|
|
30969
|
+
return;
|
|
30970
|
+
const laneKey = this.sessionActiveLanes.get(sessionKey);
|
|
30971
|
+
if (laneKey)
|
|
30972
|
+
this.laneLedger?.renewByKey(laneKey);
|
|
30973
|
+
}
|
|
30974
|
+
/** Sessions whose in-flight turn surfaced a runtime error event. */
|
|
30975
|
+
turnErrorSessions = /* @__PURE__ */ new Set();
|
|
30976
|
+
/**
|
|
30977
|
+
* Consume (read-and-clear) the error marker for sessionKey's last turn.
|
|
30978
|
+
* Feeds complete's turn_outcome so an error turn's lane members are
|
|
30979
|
+
* released for retry instead of no_action-swept (design §3). Consuming
|
|
30980
|
+
* (rather than peeking) keeps one-shot fork session keys from accumulating
|
|
30981
|
+
* in the set forever.
|
|
30982
|
+
*/
|
|
30983
|
+
consumeTurnError(sessionKey) {
|
|
30984
|
+
return this.turnErrorSessions.delete(sessionKey);
|
|
30985
|
+
}
|
|
30731
30986
|
async emitDispatchReceived(event) {
|
|
30732
30987
|
const sourceType = event.ackSourceType ?? (event.type === "task" ? "task_activity" : "message");
|
|
30733
30988
|
const sourceId = event.ackSourceId ?? event.messageId;
|
|
@@ -30736,6 +30991,20 @@ var ParallAgentGateway = class {
|
|
|
30736
30991
|
source_id: sourceId
|
|
30737
30992
|
});
|
|
30738
30993
|
}
|
|
30994
|
+
/**
|
|
30995
|
+
* Sticky: the server answered a by-id complete with 400 (predates the
|
|
30996
|
+
* form). Typed resolution falls back to the legacy ack for the rest of
|
|
30997
|
+
* the process lifetime.
|
|
30998
|
+
*/
|
|
30999
|
+
typedByIdCompleteUnsupported = false;
|
|
31000
|
+
// Typed-face ledger helpers live in gateway-lane-flow.ts; thin delegates
|
|
31001
|
+
// keep the site code and tests on the class surface.
|
|
31002
|
+
typedLedgerEventIds(events) {
|
|
31003
|
+
return typedLedgerEventIds(this.laneFlowHost(), events);
|
|
31004
|
+
}
|
|
31005
|
+
resolveDispatchByID(dispatchEventId, lane) {
|
|
31006
|
+
return resolveDispatchByID(this.laneFlowHost(), dispatchEventId, lane);
|
|
31007
|
+
}
|
|
30739
31008
|
/** True when this event's lifecycle is owned by the dispatch lane ledger. */
|
|
30740
31009
|
usesLaneLedger(event) {
|
|
30741
31010
|
return this.laneLedger != null && !this.ledgerDisabled && this.laneLedger.handles(event);
|
|
@@ -30766,15 +31035,16 @@ var ParallAgentGateway = class {
|
|
|
30766
31035
|
dispatchLaneGroup(opts) {
|
|
30767
31036
|
return dispatchLaneGroup(this.laneFlowHost(), opts);
|
|
30768
31037
|
}
|
|
30769
|
-
consumeTypedDispatch(ref, run,
|
|
30770
|
-
return consumeTypedDispatch(this.laneFlowHost(), ref, run,
|
|
31038
|
+
consumeTypedDispatch(ref, run, hooks) {
|
|
31039
|
+
return consumeTypedDispatch(this.laneFlowHost(), ref, run, hooks);
|
|
30771
31040
|
}
|
|
30772
|
-
//
|
|
30773
|
-
//
|
|
30774
|
-
//
|
|
30775
|
-
//
|
|
30776
|
-
//
|
|
30777
|
-
//
|
|
31041
|
+
// Legacy administrative ack (ledger-disabled fallback only). Typed
|
|
31042
|
+
// completion must wait until the ack has either committed or failed.
|
|
31043
|
+
// Errors stay best-effort: a failed ack leaves the row received, so
|
|
31044
|
+
// Complete releases and re-drives it safely. The boolean outcome feeds the
|
|
31045
|
+
// typed-consume backoff — an ack that failed must count as a failed
|
|
31046
|
+
// consume, or an ack outage would clear the backoff entry and let the
|
|
31047
|
+
// release re-drive spin at wire speed.
|
|
30778
31048
|
ackDispatchEvent(dispatchEventId, onFailure) {
|
|
30779
31049
|
return this.opts.client.ackDispatchByID(this.opts.config.org_id, dispatchEventId).then(() => true, (err) => {
|
|
30780
31050
|
onFailure?.();
|
|
@@ -30782,6 +31052,9 @@ var ParallAgentGateway = class {
|
|
|
30782
31052
|
return false;
|
|
30783
31053
|
});
|
|
30784
31054
|
}
|
|
31055
|
+
// PARITY: this switch and gateway-lane-flow's clearTypedDedupeForEvent must
|
|
31056
|
+
// handle the same typed source families — extend BOTH when adding a typed
|
|
31057
|
+
// event type (same dedupe entries, keyed from different event shapes).
|
|
30785
31058
|
clearTypedDispatchDedupe(item) {
|
|
30786
31059
|
switch (item.event_type) {
|
|
30787
31060
|
case "task_assign":
|
|
@@ -31077,6 +31350,7 @@ var ParallAgentGateway = class {
|
|
|
31077
31350
|
this.pendingRestartNotification = null;
|
|
31078
31351
|
}
|
|
31079
31352
|
resetDispatchMetrics(sessionKey);
|
|
31353
|
+
this.turnErrorSessions.delete(sessionKey);
|
|
31080
31354
|
return runWithSessionKey(sessionKey, async () => {
|
|
31081
31355
|
let dispatchSpan = null;
|
|
31082
31356
|
setSessionChatId(sessionKey, event.targetId);
|
|
@@ -31184,6 +31458,9 @@ var ParallAgentGateway = class {
|
|
|
31184
31458
|
} else if (runtimeEvent.type === "tool_result" && pendingSendCallIds.delete(runtimeEvent.callId)) {
|
|
31185
31459
|
recordMessageSend(sessionKey, !runtimeEvent.error);
|
|
31186
31460
|
}
|
|
31461
|
+
if (runtimeEvent.type === "error") {
|
|
31462
|
+
this.turnErrorSessions.add(sessionKey);
|
|
31463
|
+
}
|
|
31187
31464
|
await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
31188
31465
|
}
|
|
31189
31466
|
if (!binding) {
|
|
@@ -31331,16 +31608,32 @@ var ParallAgentGateway = class {
|
|
|
31331
31608
|
body: buildForkScopePrefix(last) + buildEventBody(last),
|
|
31332
31609
|
earlier,
|
|
31333
31610
|
captureText: batchText,
|
|
31334
|
-
|
|
31611
|
+
// Per-LANE residue check (parity with the main-buffer path):
|
|
31612
|
+
// the fork queue can hold several lanes (channel + thread of
|
|
31613
|
+
// the same chat). A whole-queue check would defer THIS lane's
|
|
31614
|
+
// complete behind another lane's items and never revisit it —
|
|
31615
|
+
// its members would sit received until lease expiry.
|
|
31616
|
+
hasMoreLocal: () => fork.queue.some((it) => this.dispatchGroupKey(it.event) === this.dispatchGroupKey(last))
|
|
31335
31617
|
});
|
|
31336
31618
|
if (outcome === "foreign") {
|
|
31337
31619
|
for (const item of items)
|
|
31338
31620
|
item.resolve(false);
|
|
31339
31621
|
break;
|
|
31340
31622
|
}
|
|
31623
|
+
if (outcome === "failed") {
|
|
31624
|
+
for (const item of items)
|
|
31625
|
+
item.resolve(false);
|
|
31626
|
+
continue;
|
|
31627
|
+
}
|
|
31341
31628
|
dispatched = outcome === "dispatched";
|
|
31342
31629
|
} else {
|
|
31343
31630
|
dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildForkScopePrefix(last) + buildEventBody(last), earlier, batchText);
|
|
31631
|
+
if (dispatched && this.typedLedgerEventIds(events) && this.consumeTurnError(fork.fork.sessionKey)) {
|
|
31632
|
+
this.opts.log?.info(`typed fork turn for ${last.messageId} surfaced a runtime error \u2014 releasing for retry`);
|
|
31633
|
+
for (const item of items)
|
|
31634
|
+
item.resolve(false);
|
|
31635
|
+
continue;
|
|
31636
|
+
}
|
|
31344
31637
|
}
|
|
31345
31638
|
if (!dispatched) {
|
|
31346
31639
|
for (const item of items) {
|
|
@@ -31371,6 +31664,7 @@ var ParallAgentGateway = class {
|
|
|
31371
31664
|
remaining.resolve(false);
|
|
31372
31665
|
}
|
|
31373
31666
|
} finally {
|
|
31667
|
+
this.turnErrorSessions.delete(fork.fork.sessionKey);
|
|
31374
31668
|
if (fork.deadlineTimer) {
|
|
31375
31669
|
clearTimeout(fork.deadlineTimer);
|
|
31376
31670
|
fork.deadlineTimer = null;
|
|
@@ -31483,15 +31777,22 @@ var ParallAgentGateway = class {
|
|
|
31483
31777
|
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31484
31778
|
continue;
|
|
31485
31779
|
}
|
|
31780
|
+
if (outcome === "failed") {
|
|
31781
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31782
|
+
continue;
|
|
31783
|
+
}
|
|
31486
31784
|
continue;
|
|
31487
31785
|
}
|
|
31488
|
-
|
|
31489
|
-
|
|
31490
|
-
|
|
31491
|
-
|
|
31492
|
-
|
|
31493
|
-
|
|
31494
|
-
|
|
31786
|
+
const typedRefs = this.typedLedgerEventIds(events);
|
|
31787
|
+
if (!typedRefs) {
|
|
31788
|
+
try {
|
|
31789
|
+
await this.emitDispatchReceived(event);
|
|
31790
|
+
} catch (err) {
|
|
31791
|
+
this.opts.log?.warn?.(`mark-received failed for buffered dispatch, leaving unacked for retry: ${String(err)}`);
|
|
31792
|
+
this.dispatchState.mainBuffer.unshift(...events);
|
|
31793
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31794
|
+
break;
|
|
31795
|
+
}
|
|
31495
31796
|
}
|
|
31496
31797
|
const dispatched = await this.runDispatch(event, this.opts.runtimeKey, forkPrefix + buildEventBody(event), earlier);
|
|
31497
31798
|
if (!dispatched) {
|
|
@@ -31499,6 +31800,10 @@ var ParallAgentGateway = class {
|
|
|
31499
31800
|
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31500
31801
|
break;
|
|
31501
31802
|
}
|
|
31803
|
+
if (typedRefs) {
|
|
31804
|
+
await settleDrainedTypedGroup(this.laneFlowHost(), events, typedRefs, this.consumeTurnError(this.opts.runtimeKey));
|
|
31805
|
+
continue;
|
|
31806
|
+
}
|
|
31502
31807
|
for (const bufferedEvent of events) {
|
|
31503
31808
|
const sourceType = bufferedEvent.ackSourceType ?? (bufferedEvent.type === "task" ? "task_activity" : "message");
|
|
31504
31809
|
const sourceId = bufferedEvent.ackSourceId ?? bufferedEvent.messageId;
|
|
@@ -31558,20 +31863,27 @@ var ParallAgentGateway = class {
|
|
|
31558
31863
|
}
|
|
31559
31864
|
return outcome === "dispatched";
|
|
31560
31865
|
}
|
|
31561
|
-
|
|
31562
|
-
|
|
31563
|
-
|
|
31564
|
-
|
|
31565
|
-
|
|
31566
|
-
|
|
31567
|
-
|
|
31568
|
-
|
|
31569
|
-
|
|
31570
|
-
|
|
31866
|
+
const typedRefs = this.typedLedgerEventIds([event]);
|
|
31867
|
+
if (!typedRefs) {
|
|
31868
|
+
try {
|
|
31869
|
+
await this.emitDispatchReceived(event);
|
|
31870
|
+
} catch (err) {
|
|
31871
|
+
this.opts.log?.warn?.(`mark-received failed, leaving unacked for retry: ${String(err)}`);
|
|
31872
|
+
this.dispatchState.mainDispatching = false;
|
|
31873
|
+
this.dispatchState.mainCurrentTargetId = void 0;
|
|
31874
|
+
this.mainCurrentGroupKey = void 0;
|
|
31875
|
+
this.dispatchState.mainPreDispatchBranchPoint = void 0;
|
|
31876
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31877
|
+
return false;
|
|
31878
|
+
}
|
|
31571
31879
|
}
|
|
31572
31880
|
let dispatched = false;
|
|
31573
31881
|
try {
|
|
31574
31882
|
dispatched = await this.runDispatch(event, this.opts.runtimeKey, forkPrefix + buildEventBody(event));
|
|
31883
|
+
if (dispatched && typedRefs && this.consumeTurnError(this.opts.runtimeKey)) {
|
|
31884
|
+
this.opts.log?.info(`typed dispatch turn for ${event.messageId} surfaced a runtime error \u2014 releasing for retry`);
|
|
31885
|
+
dispatched = false;
|
|
31886
|
+
}
|
|
31575
31887
|
if (!dispatched) {
|
|
31576
31888
|
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31577
31889
|
}
|
|
@@ -31586,7 +31898,7 @@ var ParallAgentGateway = class {
|
|
|
31586
31898
|
}
|
|
31587
31899
|
this.dispatchState.mainBuffer.push(event);
|
|
31588
31900
|
if (this.usesLaneLedger(event)) {
|
|
31589
|
-
if (this.mainCurrentGroupKey === this.dispatchGroupKey(event) && await this.laneLedger?.steerLive(event) && await this.opts.dispatchAdapter.enqueueDuringDispatch
|
|
31901
|
+
if (this.mainCurrentGroupKey === this.dispatchGroupKey(event) && this.opts.dispatchAdapter.enqueueDuringDispatch != null && await this.laneLedger?.steerLive(event) && await this.opts.dispatchAdapter.enqueueDuringDispatch(this.opts.runtimeKey, buildEventBody(event))) {
|
|
31590
31902
|
this.opts.log?.info(`steer folded+injected for ${event.messageId} (will drain for bookkeeping)`);
|
|
31591
31903
|
}
|
|
31592
31904
|
} else if (this.dispatchState.mainCurrentTargetId === event.targetId && await this.opts.dispatchAdapter.enqueueDuringDispatch?.(this.opts.runtimeKey, buildEventBody(event))) {
|
|
@@ -31613,12 +31925,14 @@ var ParallAgentGateway = class {
|
|
|
31613
31925
|
this.dispatchState.mainBuffer.push(event);
|
|
31614
31926
|
return false;
|
|
31615
31927
|
}
|
|
31616
|
-
|
|
31617
|
-
|
|
31618
|
-
|
|
31619
|
-
|
|
31620
|
-
|
|
31621
|
-
|
|
31928
|
+
if (!this.usesLaneLedger(event) && !this.typedLedgerEventIds([event])) {
|
|
31929
|
+
try {
|
|
31930
|
+
await this.emitDispatchReceived(event);
|
|
31931
|
+
} catch (err) {
|
|
31932
|
+
this.opts.log?.warn?.(`mark-received failed for fork dispatch, leaving unacked for retry: ${String(err)}`);
|
|
31933
|
+
this.dispatchState.mainBuffer.push(event);
|
|
31934
|
+
return false;
|
|
31935
|
+
}
|
|
31622
31936
|
}
|
|
31623
31937
|
const fork = await this.opts.dispatchAdapter.forkSession({
|
|
31624
31938
|
sessionKey: this.opts.runtimeKey,
|
|
@@ -31792,7 +32106,10 @@ var ParallAgentGateway = class {
|
|
|
31792
32106
|
return;
|
|
31793
32107
|
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
|
|
31794
32108
|
dispatchEventId
|
|
31795
|
-
}),
|
|
32109
|
+
}), {
|
|
32110
|
+
legacyAck: (dispatchEventId) => this.ackDispatchEvent(dispatchEventId ?? item.id, () => this.clearTypedDispatchDedupe(item)),
|
|
32111
|
+
clearDedupe: () => this.clearTypedDispatchDedupe(item)
|
|
32112
|
+
});
|
|
31796
32113
|
}
|
|
31797
32114
|
consumeMessageWorkItem(item) {
|
|
31798
32115
|
return consumeMessageWorkItem(this.laneFlowHost(), item);
|
|
@@ -31853,7 +32170,7 @@ var ParallAgentGateway = class {
|
|
|
31853
32170
|
}
|
|
31854
32171
|
return this.handleTaskAssignment(task, ackSourceId, opts.dispatchEventId);
|
|
31855
32172
|
}
|
|
31856
|
-
async handleTaskComment(commentId, taskId, actorId, deliveryReason) {
|
|
32173
|
+
async handleTaskComment(commentId, taskId, actorId, deliveryReason, dispatchEventId) {
|
|
31857
32174
|
if (this.shuttingDown)
|
|
31858
32175
|
return false;
|
|
31859
32176
|
const dedupeKey = `comment:${commentId}`;
|
|
@@ -31909,7 +32226,8 @@ var ParallAgentGateway = class {
|
|
|
31909
32226
|
body: parts.join("\n"),
|
|
31910
32227
|
deliveryReason: deliveryReason ?? void 0,
|
|
31911
32228
|
ackSourceType: "comment",
|
|
31912
|
-
ackSourceId: commentId
|
|
32229
|
+
ackSourceId: commentId,
|
|
32230
|
+
dispatchEventId
|
|
31913
32231
|
};
|
|
31914
32232
|
let dispatched;
|
|
31915
32233
|
try {
|
|
@@ -31923,7 +32241,7 @@ var ParallAgentGateway = class {
|
|
|
31923
32241
|
}
|
|
31924
32242
|
return dispatched;
|
|
31925
32243
|
}
|
|
31926
|
-
async handleWikiComment(commentId, actorId, deliveryReason) {
|
|
32244
|
+
async handleWikiComment(commentId, actorId, deliveryReason, dispatchEventId) {
|
|
31927
32245
|
if (this.shuttingDown)
|
|
31928
32246
|
return false;
|
|
31929
32247
|
const dedupeKey = `comment:${commentId}`;
|
|
@@ -31969,7 +32287,8 @@ var ParallAgentGateway = class {
|
|
|
31969
32287
|
deliveryReason: deliveryReason ?? void 0,
|
|
31970
32288
|
replyTargetUri: comment.target_uri,
|
|
31971
32289
|
ackSourceType: "comment",
|
|
31972
|
-
ackSourceId: commentId
|
|
32290
|
+
ackSourceId: commentId,
|
|
32291
|
+
dispatchEventId
|
|
31973
32292
|
};
|
|
31974
32293
|
let dispatched;
|
|
31975
32294
|
try {
|
|
@@ -31991,7 +32310,7 @@ var ParallAgentGateway = class {
|
|
|
31991
32310
|
* access to already-delivered run snapshots, and the runtime must not crash
|
|
31992
32311
|
* or retry forever in that case.
|
|
31993
32312
|
*/
|
|
31994
|
-
async fetchAndHandleScheduleFire(runId, actorId) {
|
|
32313
|
+
async fetchAndHandleScheduleFire(runId, actorId, dispatchEventId) {
|
|
31995
32314
|
let run = null;
|
|
31996
32315
|
try {
|
|
31997
32316
|
run = await this.opts.client.getScheduleRun(this.opts.config.org_id, runId);
|
|
@@ -32006,9 +32325,9 @@ var ParallAgentGateway = class {
|
|
|
32006
32325
|
}
|
|
32007
32326
|
if (!run)
|
|
32008
32327
|
return true;
|
|
32009
|
-
return this.handleScheduleFire(run, actorId);
|
|
32328
|
+
return this.handleScheduleFire(run, actorId, dispatchEventId);
|
|
32010
32329
|
}
|
|
32011
|
-
async handleScheduleFire(run, actorId) {
|
|
32330
|
+
async handleScheduleFire(run, actorId, dispatchEventId) {
|
|
32012
32331
|
if (this.shuttingDown)
|
|
32013
32332
|
return false;
|
|
32014
32333
|
const dedupeKey = `schedule_run:${run.id}`;
|
|
@@ -32031,7 +32350,8 @@ var ParallAgentGateway = class {
|
|
|
32031
32350
|
scheduledFireAt: run.scheduled_fire_at,
|
|
32032
32351
|
attachedUri: run.fired_attached_uri ?? void 0,
|
|
32033
32352
|
ackSourceType: "schedule_run",
|
|
32034
|
-
ackSourceId: run.id
|
|
32353
|
+
ackSourceId: run.id,
|
|
32354
|
+
dispatchEventId
|
|
32035
32355
|
};
|
|
32036
32356
|
let dispatched;
|
|
32037
32357
|
try {
|
|
@@ -32045,7 +32365,7 @@ var ParallAgentGateway = class {
|
|
|
32045
32365
|
}
|
|
32046
32366
|
return dispatched;
|
|
32047
32367
|
}
|
|
32048
|
-
async fetchAndHandleExternalTriggerRun(runId) {
|
|
32368
|
+
async fetchAndHandleExternalTriggerRun(runId, dispatchEventId) {
|
|
32049
32369
|
let run = null;
|
|
32050
32370
|
try {
|
|
32051
32371
|
run = await this.opts.client.getExternalTriggerRun(this.opts.config.org_id, runId);
|
|
@@ -32060,14 +32380,14 @@ var ParallAgentGateway = class {
|
|
|
32060
32380
|
}
|
|
32061
32381
|
if (!run)
|
|
32062
32382
|
return true;
|
|
32063
|
-
return this.handleExternalTriggerRun(run);
|
|
32383
|
+
return this.handleExternalTriggerRun(run, dispatchEventId);
|
|
32064
32384
|
}
|
|
32065
32385
|
// fetchAndHandleChannelMessage resolves a channel_message dispatch to its
|
|
32066
32386
|
// durable ChannelMessage + conversation and hands it to the inbound
|
|
32067
32387
|
// pipeline. targetId = the ChannelConversation id, so per-conversation
|
|
32068
32388
|
// multi-turn continuity rides the same per-target session mechanics as
|
|
32069
32389
|
// chats. Design: docs/engineering-design/external-im-channel-design.md.
|
|
32070
|
-
async fetchAndHandleChannelMessage(messageId) {
|
|
32390
|
+
async fetchAndHandleChannelMessage(messageId, dispatchEventId) {
|
|
32071
32391
|
if (this.shuttingDown)
|
|
32072
32392
|
return false;
|
|
32073
32393
|
const claimKey = `channel_message:${messageId}`;
|
|
@@ -32120,7 +32440,8 @@ var ParallAgentGateway = class {
|
|
|
32120
32440
|
channelExternalMessageId: msg.external_message_id,
|
|
32121
32441
|
channelCliCapable: cliCapable,
|
|
32122
32442
|
ackSourceType: "channel_message",
|
|
32123
|
-
ackSourceId: msg.id
|
|
32443
|
+
ackSourceId: msg.id,
|
|
32444
|
+
dispatchEventId
|
|
32124
32445
|
};
|
|
32125
32446
|
let dispatched;
|
|
32126
32447
|
try {
|
|
@@ -32134,7 +32455,7 @@ var ParallAgentGateway = class {
|
|
|
32134
32455
|
}
|
|
32135
32456
|
return dispatched;
|
|
32136
32457
|
}
|
|
32137
|
-
async handleExternalTriggerRun(run) {
|
|
32458
|
+
async handleExternalTriggerRun(run, dispatchEventId) {
|
|
32138
32459
|
if (this.shuttingDown)
|
|
32139
32460
|
return false;
|
|
32140
32461
|
const dedupeKey = `external_trigger_run:${run.id}`;
|
|
@@ -32159,7 +32480,8 @@ var ParallAgentGateway = class {
|
|
|
32159
32480
|
externalIngressEventType: run.ingress_event_type || void 0,
|
|
32160
32481
|
attachedUri,
|
|
32161
32482
|
ackSourceType: "external_trigger_run",
|
|
32162
|
-
ackSourceId: run.id
|
|
32483
|
+
ackSourceId: run.id,
|
|
32484
|
+
dispatchEventId
|
|
32163
32485
|
};
|
|
32164
32486
|
let dispatched;
|
|
32165
32487
|
try {
|
|
@@ -32173,7 +32495,7 @@ var ParallAgentGateway = class {
|
|
|
32173
32495
|
}
|
|
32174
32496
|
return dispatched;
|
|
32175
32497
|
}
|
|
32176
|
-
async fetchAndHandleApprovalDecided(approvalId, actorId, chatId) {
|
|
32498
|
+
async fetchAndHandleApprovalDecided(approvalId, actorId, chatId, dispatchEventId) {
|
|
32177
32499
|
let approval = null;
|
|
32178
32500
|
try {
|
|
32179
32501
|
approval = await this.opts.client.getApproval(approvalId);
|
|
@@ -32204,7 +32526,12 @@ var ParallAgentGateway = class {
|
|
|
32204
32526
|
senderId: actorId ?? approval.decided_by ?? "system",
|
|
32205
32527
|
senderName: "approver",
|
|
32206
32528
|
messageId: approval.id,
|
|
32207
|
-
body
|
|
32529
|
+
body,
|
|
32530
|
+
// The WorkItem's real source pair. Without it the legacy fallback
|
|
32531
|
+
// guessed ('message', approval_id) — a pair no row matches.
|
|
32532
|
+
ackSourceType: "approval",
|
|
32533
|
+
ackSourceId: approval.id,
|
|
32534
|
+
dispatchEventId
|
|
32208
32535
|
};
|
|
32209
32536
|
let dispatched;
|
|
32210
32537
|
try {
|
|
@@ -32251,7 +32578,17 @@ var ParallAgentGateway = class {
|
|
|
32251
32578
|
break;
|
|
32252
32579
|
if (overflowMode && processed >= CATCHUP_MAX) {
|
|
32253
32580
|
try {
|
|
32254
|
-
|
|
32581
|
+
if (this.laneLedger && !this.ledgerDisabled && !this.typedByIdCompleteUnsupported) {
|
|
32582
|
+
const outcome = await this.resolveDispatchByID(item.id);
|
|
32583
|
+
if (outcome === "unsupported") {
|
|
32584
|
+
this.typedByIdCompleteUnsupported = true;
|
|
32585
|
+
await this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id);
|
|
32586
|
+
} else if (outcome === "failed") {
|
|
32587
|
+
throw new Error("by-id complete failed");
|
|
32588
|
+
}
|
|
32589
|
+
} else {
|
|
32590
|
+
await this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id);
|
|
32591
|
+
}
|
|
32255
32592
|
skipped++;
|
|
32256
32593
|
const key = item.event_type;
|
|
32257
32594
|
skippedByType.set(key, (skippedByType.get(key) ?? 0) + 1);
|
|
@@ -32262,12 +32599,15 @@ var ParallAgentGateway = class {
|
|
|
32262
32599
|
}
|
|
32263
32600
|
processed++;
|
|
32264
32601
|
try {
|
|
32265
|
-
const
|
|
32602
|
+
const typedHooks = {
|
|
32603
|
+
legacyAck: () => this.ackDispatchEvent(item.id, () => this.clearTypedDispatchDedupe(item)),
|
|
32604
|
+
clearDedupe: () => this.clearTypedDispatchDedupe(item)
|
|
32605
|
+
};
|
|
32266
32606
|
if (item.event_type === "task_assign" && item.task_id) {
|
|
32267
32607
|
try {
|
|
32268
32608
|
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
|
|
32269
32609
|
dispatchEventId
|
|
32270
|
-
}),
|
|
32610
|
+
}), typedHooks);
|
|
32271
32611
|
} catch (err) {
|
|
32272
32612
|
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
32273
32613
|
continue;
|
|
@@ -32277,23 +32617,23 @@ var ParallAgentGateway = class {
|
|
|
32277
32617
|
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
|
|
32278
32618
|
allowCreator: true,
|
|
32279
32619
|
dispatchEventId
|
|
32280
|
-
}),
|
|
32620
|
+
}), typedHooks);
|
|
32281
32621
|
} catch (err) {
|
|
32282
32622
|
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
32283
32623
|
continue;
|
|
32284
32624
|
}
|
|
32285
32625
|
} else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
|
|
32286
|
-
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleTaskComment(item.source_id, item.task_id ?? "", item.actor_id, item.delivery_reason),
|
|
32626
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskComment(item.source_id, item.task_id ?? "", item.actor_id, item.delivery_reason, dispatchEventId), typedHooks);
|
|
32287
32627
|
} else if (item.event_type === "wiki_comment" && item.source_id) {
|
|
32288
|
-
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleWikiComment(item.source_id, item.actor_id, item.delivery_reason),
|
|
32628
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleWikiComment(item.source_id, item.actor_id, item.delivery_reason, dispatchEventId), typedHooks);
|
|
32289
32629
|
} else if (item.event_type === "schedule.fire" && item.source_id) {
|
|
32290
|
-
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleScheduleFire(item.source_id, item.actor_id),
|
|
32630
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.fetchAndHandleScheduleFire(item.source_id, item.actor_id, dispatchEventId), typedHooks);
|
|
32291
32631
|
} else if (item.event_type === "external_trigger" && item.source_id) {
|
|
32292
|
-
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleExternalTriggerRun(item.source_id),
|
|
32632
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.fetchAndHandleExternalTriggerRun(item.source_id, dispatchEventId), typedHooks);
|
|
32293
32633
|
} else if (item.event_type === "channel_message" && item.source_id) {
|
|
32294
|
-
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleChannelMessage(item.source_id),
|
|
32634
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.fetchAndHandleChannelMessage(item.source_id, dispatchEventId), typedHooks);
|
|
32295
32635
|
} else if (item.event_type === "approval_decided" && item.source_id) {
|
|
32296
|
-
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null),
|
|
32636
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null, dispatchEventId), typedHooks);
|
|
32297
32637
|
} else if (item.event_type === "message" && item.source_id && item.chat_id) {
|
|
32298
32638
|
await this.consumeMessageWorkItem({
|
|
32299
32639
|
id: item.id,
|
|
@@ -33873,6 +34213,10 @@ function cleanupForkSession(opts) {
|
|
|
33873
34213
|
}
|
|
33874
34214
|
|
|
33875
34215
|
// dist/gateway.js
|
|
34216
|
+
function sessionContextFilePath(stateDir, sessionKey) {
|
|
34217
|
+
const fileName = Buffer.from(sessionKey).toString("base64url");
|
|
34218
|
+
return path8.join(stateDir, "dispatch-context", `${fileName}.json`);
|
|
34219
|
+
}
|
|
33876
34220
|
function resolveWsUrl(account) {
|
|
33877
34221
|
if (account.config.ws_url)
|
|
33878
34222
|
return account.config.ws_url;
|
|
@@ -34242,6 +34586,18 @@ var parallGateway = {
|
|
|
34242
34586
|
runtimeKey: orchestratorKey,
|
|
34243
34587
|
runtimeRef: { hostname: os2.hostname(), pid: process.pid },
|
|
34244
34588
|
dispatchAdapter,
|
|
34589
|
+
// Opts openclaw into the dispatch ledger (claim/fold/complete +
|
|
34590
|
+
// idempotent reply effects) — the same shared-gateway machinery
|
|
34591
|
+
// claude/codex ride; openclaw stays buffer-only (no mid-turn steer),
|
|
34592
|
+
// which the ledger does not require. Replies already flow through
|
|
34593
|
+
// @parall/cli, which reads PRLL_CONTEXT_DIR (injected in hooks.ts)
|
|
34594
|
+
// to bind dispatch_lane + reply effect keys.
|
|
34595
|
+
// Design: docs/engineering-design/dispatch-convergence-design.md §7 (S2).
|
|
34596
|
+
dispatchContextDir: dispatchLaneContextDir(stateDir),
|
|
34597
|
+
// Per-session context file (PRLL_CONTEXT_FILE contract) — the CLI's
|
|
34598
|
+
// TYPED dispatch binding (parall tasks update → task_update effect)
|
|
34599
|
+
// reads lane/event/task fields from this file, not the lane dir.
|
|
34600
|
+
contextFilePathForSession: (sessionKey) => sessionContextFilePath(stateDir, sessionKey),
|
|
34245
34601
|
log: otelLog,
|
|
34246
34602
|
// Live capability view for hint routing: the channel reply hint
|
|
34247
34603
|
// points at the vendor CLI only while the grant is active.
|
|
@@ -34265,7 +34621,13 @@ var parallGateway = {
|
|
|
34265
34621
|
runtimeRef: { hostname: os2.hostname(), pid: process.pid },
|
|
34266
34622
|
wikiMountRoot,
|
|
34267
34623
|
ws,
|
|
34268
|
-
orchestratorSessionKey: orchestratorKey
|
|
34624
|
+
orchestratorSessionKey: orchestratorKey,
|
|
34625
|
+
dispatchContextDir: dispatchLaneContextDir(stateDir),
|
|
34626
|
+
contextFilePathForSession: (sessionKey) => sessionContextFilePath(stateDir, sessionKey),
|
|
34627
|
+
// Lease-renewal bridge: openclaw tool calls flow through hooks,
|
|
34628
|
+
// not the RuntimeEvent stream, so hook activity must renew the
|
|
34629
|
+
// ledger lanes or a long exec outlives the lease (STALE_LANE).
|
|
34630
|
+
touchRuntimeActivity: (sessionKey) => gateway.touchRuntimeActivity(sessionKey)
|
|
34269
34631
|
});
|
|
34270
34632
|
},
|
|
34271
34633
|
onSessionBinding: async ({ sessionKey, agentSessionId }) => {
|
|
@@ -34450,10 +34812,41 @@ function registerParallHooks(api) {
|
|
|
34450
34812
|
};
|
|
34451
34813
|
});
|
|
34452
34814
|
const pendingSendCalls = /* @__PURE__ */ new Map();
|
|
34815
|
+
const laneRenewals = /* @__PURE__ */ new Map();
|
|
34816
|
+
const LANE_RENEW_INTERVAL_MS = 18e4;
|
|
34817
|
+
const beginToolRenewal = (sessionKey) => {
|
|
34818
|
+
const accountId = extractAccountIdFromSessionKey(sessionKey);
|
|
34819
|
+
const state = accountId ? getParallAccountState(accountId) : void 0;
|
|
34820
|
+
if (!state?.touchRuntimeActivity)
|
|
34821
|
+
return;
|
|
34822
|
+
state.touchRuntimeActivity(sessionKey);
|
|
34823
|
+
const entry = laneRenewals.get(sessionKey) ?? { count: 0 };
|
|
34824
|
+
entry.count++;
|
|
34825
|
+
if (!entry.timer) {
|
|
34826
|
+
entry.timer = setInterval(() => state.touchRuntimeActivity?.(sessionKey), LANE_RENEW_INTERVAL_MS);
|
|
34827
|
+
entry.timer.unref?.();
|
|
34828
|
+
}
|
|
34829
|
+
laneRenewals.set(sessionKey, entry);
|
|
34830
|
+
};
|
|
34831
|
+
const endToolRenewal = (sessionKey) => {
|
|
34832
|
+
const entry = laneRenewals.get(sessionKey);
|
|
34833
|
+
if (entry) {
|
|
34834
|
+
entry.count--;
|
|
34835
|
+
if (entry.count <= 0) {
|
|
34836
|
+
if (entry.timer)
|
|
34837
|
+
clearInterval(entry.timer);
|
|
34838
|
+
laneRenewals.delete(sessionKey);
|
|
34839
|
+
}
|
|
34840
|
+
}
|
|
34841
|
+
const accountId = extractAccountIdFromSessionKey(sessionKey);
|
|
34842
|
+
const state = accountId ? getParallAccountState(accountId) : void 0;
|
|
34843
|
+
state?.touchRuntimeActivity?.(sessionKey);
|
|
34844
|
+
};
|
|
34453
34845
|
api.on("before_tool_call", async (event, ctx) => {
|
|
34454
34846
|
const sessionKey = ctx.sessionKey;
|
|
34455
34847
|
if (sessionKey) {
|
|
34456
34848
|
recordToolCall(sessionKey);
|
|
34849
|
+
beginToolRenewal(sessionKey);
|
|
34457
34850
|
if (event.toolName === "exec" && event.toolCallId) {
|
|
34458
34851
|
const command = event.params?.command;
|
|
34459
34852
|
if (isParallSendCommand(command)) {
|
|
@@ -34522,6 +34915,12 @@ function registerParallHooks(api) {
|
|
|
34522
34915
|
injectedEnv.PRLL_TRIGGER_MESSAGE_ID = triggerMsgId;
|
|
34523
34916
|
if (state.wikiMountRoot)
|
|
34524
34917
|
injectedEnv.PRLL_WIKI_MOUNT_ROOT = state.wikiMountRoot;
|
|
34918
|
+
if (state.dispatchContextDir)
|
|
34919
|
+
injectedEnv.PRLL_CONTEXT_DIR = state.dispatchContextDir;
|
|
34920
|
+
if (state.contextFilePathForSession) {
|
|
34921
|
+
injectedEnv.PRLL_CONTEXT_FILE = state.contextFilePathForSession(sessionKey);
|
|
34922
|
+
}
|
|
34923
|
+
state.touchRuntimeActivity?.(sessionKey);
|
|
34525
34924
|
injectedEnv.OPENCLAW_SESSION_KEY = sessionKey;
|
|
34526
34925
|
if (event.toolCallId)
|
|
34527
34926
|
injectedEnv.OPENCLAW_TOOL_CALL_ID = event.toolCallId;
|
|
@@ -34533,6 +34932,8 @@ function registerParallHooks(api) {
|
|
|
34533
34932
|
};
|
|
34534
34933
|
});
|
|
34535
34934
|
api.on("after_tool_call", async (event, ctx) => {
|
|
34935
|
+
if (ctx.sessionKey)
|
|
34936
|
+
endToolRenewal(ctx.sessionKey);
|
|
34536
34937
|
if (ctx.sessionKey && event.toolCallId && pendingSendCalls.delete(event.toolCallId)) {
|
|
34537
34938
|
recordMessageSend(ctx.sessionKey, !event.error);
|
|
34538
34939
|
}
|