@parall/daemon 1.37.0 → 1.38.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/bundle/manifest.json +11 -11
- package/bundle/parall-browser-pod.js +45 -0
- package/bundle/parall-claude-agent.js +860 -223
- package/bundle/parall-codex-agent.js +867 -232
- package/bundle/parall-daemon.js +73 -0
- package/package.json +6 -6
|
@@ -17718,9 +17718,9 @@ var require_getMachineId_linux = __commonJS({
|
|
|
17718
17718
|
var api_1 = (init_esm(), __toCommonJS(esm_exports));
|
|
17719
17719
|
async function getMachineId() {
|
|
17720
17720
|
const paths = ["/etc/machine-id", "/var/lib/dbus/machine-id"];
|
|
17721
|
-
for (const
|
|
17721
|
+
for (const path10 of paths) {
|
|
17722
17722
|
try {
|
|
17723
|
-
const result = await fs_1.promises.readFile(
|
|
17723
|
+
const result = await fs_1.promises.readFile(path10, { encoding: "utf8" });
|
|
17724
17724
|
return result.trim();
|
|
17725
17725
|
} catch (e) {
|
|
17726
17726
|
api_1.diag.debug(`error reading machine id: ${e}`);
|
|
@@ -21123,7 +21123,7 @@ function appendRootPathToUrlIfNeeded(url) {
|
|
|
21123
21123
|
return void 0;
|
|
21124
21124
|
}
|
|
21125
21125
|
}
|
|
21126
|
-
function appendResourcePathToUrl(url,
|
|
21126
|
+
function appendResourcePathToUrl(url, path10) {
|
|
21127
21127
|
try {
|
|
21128
21128
|
new URL(url);
|
|
21129
21129
|
} catch (_a) {
|
|
@@ -21133,11 +21133,11 @@ function appendResourcePathToUrl(url, path9) {
|
|
|
21133
21133
|
if (!url.endsWith("/")) {
|
|
21134
21134
|
url = url + "/";
|
|
21135
21135
|
}
|
|
21136
|
-
url +=
|
|
21136
|
+
url += path10;
|
|
21137
21137
|
try {
|
|
21138
21138
|
new URL(url);
|
|
21139
21139
|
} catch (_b) {
|
|
21140
|
-
diag2.warn("Configuration: Provided URL appended with '" +
|
|
21140
|
+
diag2.warn("Configuration: Provided URL appended with '" + path10 + "' is not a valid URL, using 'undefined' instead of '" + url + "'");
|
|
21141
21141
|
return void 0;
|
|
21142
21142
|
}
|
|
21143
21143
|
return url;
|
|
@@ -26474,6 +26474,19 @@ function parseProviderConfig(env) {
|
|
|
26474
26474
|
}
|
|
26475
26475
|
}
|
|
26476
26476
|
|
|
26477
|
+
// ts/agent-core/dist/lane-key.js
|
|
26478
|
+
import * as path from "node:path";
|
|
26479
|
+
function laneKeyForTarget(targetUri, threadRootId) {
|
|
26480
|
+
return Buffer.from(`${targetUri}
|
|
26481
|
+
${threadRootId ?? ""}`, "utf8").toString("base64url");
|
|
26482
|
+
}
|
|
26483
|
+
function dispatchLaneContextDir(stateDir) {
|
|
26484
|
+
return path.join(stateDir, "dispatch-lane-context");
|
|
26485
|
+
}
|
|
26486
|
+
function laneContextFilePath(contextDir, targetUri, threadRootId) {
|
|
26487
|
+
return path.join(contextDir, `${laneKeyForTarget(targetUri, threadRootId)}.json`);
|
|
26488
|
+
}
|
|
26489
|
+
|
|
26477
26490
|
// ts/agent-core/dist/session-state.js
|
|
26478
26491
|
function normalizeSessionKey(sessionKey) {
|
|
26479
26492
|
return sessionKey.toLowerCase();
|
|
@@ -27102,8 +27115,8 @@ function childLogger(logger, sub) {
|
|
|
27102
27115
|
|
|
27103
27116
|
// ts/agent-core/dist/gateway-base.js
|
|
27104
27117
|
import * as os from "node:os";
|
|
27105
|
-
import * as
|
|
27106
|
-
import * as
|
|
27118
|
+
import * as fs2 from "node:fs";
|
|
27119
|
+
import * as path2 from "node:path";
|
|
27107
27120
|
|
|
27108
27121
|
// ts/sdk/dist/types.js
|
|
27109
27122
|
var MENTION_ALL_USER_ID = "all";
|
|
@@ -27378,6 +27391,11 @@ var ENDPOINTS = {
|
|
|
27378
27391
|
DISPATCH_ACK_BY_ID: (orgId, id) => `${API_BASE}/orgs/${orgId}/dispatch/${id}/ack`,
|
|
27379
27392
|
DISPATCH_EXPIRE: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/expire`,
|
|
27380
27393
|
DISPATCH_BY_MESSAGES: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/by-messages`,
|
|
27394
|
+
DISPATCH_CLAIM: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/claim`,
|
|
27395
|
+
DISPATCH_STEER: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/steer`,
|
|
27396
|
+
DISPATCH_COMPLETE: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/complete`,
|
|
27397
|
+
DISPATCH_RELEASE: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/release`,
|
|
27398
|
+
DISPATCH_HEARTBEAT: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/heartbeat`,
|
|
27381
27399
|
// Unread
|
|
27382
27400
|
UNREAD: `${API_BASE}/me/unread`,
|
|
27383
27401
|
ORG_UNREAD: (orgId) => `${API_BASE}/orgs/${orgId}/unread`,
|
|
@@ -27498,6 +27516,7 @@ var WS_EVENTS = {
|
|
|
27498
27516
|
READ_POSITION_UPDATED: "read_position.updated",
|
|
27499
27517
|
DISPATCH_NEW: "dispatch.new",
|
|
27500
27518
|
DISPATCH_RECEIVED: "dispatch.received",
|
|
27519
|
+
DISPATCH_RESOLVED: "dispatch.resolved",
|
|
27501
27520
|
SCHEDULE_CREATED: "schedule.created",
|
|
27502
27521
|
SCHEDULE_UPDATED: "schedule.updated",
|
|
27503
27522
|
SCHEDULE_DELETED: "schedule.deleted",
|
|
@@ -27593,8 +27612,8 @@ var ParallClient = class _ParallClient {
|
|
|
27593
27612
|
* is authoritative, so wiki vs api routing can't drift from how a caller
|
|
27594
27613
|
* happens to invoke the client.
|
|
27595
27614
|
*/
|
|
27596
|
-
baseUrlFor(
|
|
27597
|
-
return
|
|
27615
|
+
baseUrlFor(path10) {
|
|
27616
|
+
return path10.startsWith(WIKI_BASE) ? this.wikiBaseUrl : this.baseUrl;
|
|
27598
27617
|
}
|
|
27599
27618
|
setToken(token) {
|
|
27600
27619
|
this.token = token;
|
|
@@ -27621,10 +27640,10 @@ var ParallClient = class _ParallClient {
|
|
|
27621
27640
|
* REFRESH_THRESHOLD_S, refresh it **before** sending the request.
|
|
27622
27641
|
* No-op when the token is still fresh, missing, or un-parseable.
|
|
27623
27642
|
*/
|
|
27624
|
-
async ensureFreshToken(
|
|
27643
|
+
async ensureFreshToken(path10) {
|
|
27625
27644
|
if (!this.token || !this.getRefreshToken)
|
|
27626
27645
|
return;
|
|
27627
|
-
const pathSuffix =
|
|
27646
|
+
const pathSuffix = path10.replace(/^\/api\/v1/, "");
|
|
27628
27647
|
if (_ParallClient.AUTH_PATHS.has(pathSuffix))
|
|
27629
27648
|
return;
|
|
27630
27649
|
const exp = _ParallClient.decodeJwtExp(this.token);
|
|
@@ -27656,11 +27675,11 @@ var ParallClient = class _ParallClient {
|
|
|
27656
27675
|
this.refreshPromise = null;
|
|
27657
27676
|
}
|
|
27658
27677
|
}
|
|
27659
|
-
async request(method,
|
|
27678
|
+
async request(method, path10, body, query, retried = false, opts) {
|
|
27660
27679
|
if (!retried) {
|
|
27661
|
-
await this.ensureFreshToken(
|
|
27680
|
+
await this.ensureFreshToken(path10);
|
|
27662
27681
|
}
|
|
27663
|
-
let url = `${this.baseUrlFor(
|
|
27682
|
+
let url = `${this.baseUrlFor(path10)}${path10}`;
|
|
27664
27683
|
if (query) {
|
|
27665
27684
|
const params = new URLSearchParams();
|
|
27666
27685
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -27690,12 +27709,12 @@ var ParallClient = class _ParallClient {
|
|
|
27690
27709
|
throw _ParallClient.normalizeFetchError(err);
|
|
27691
27710
|
}
|
|
27692
27711
|
if (res.status === 401) {
|
|
27693
|
-
const pathSuffix =
|
|
27712
|
+
const pathSuffix = path10.replace(/^\/api\/v1/, "");
|
|
27694
27713
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
27695
27714
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
27696
27715
|
const refreshed = await this.tryRefresh();
|
|
27697
27716
|
if (refreshed) {
|
|
27698
|
-
return this.request(method,
|
|
27717
|
+
return this.request(method, path10, body, query, true, opts);
|
|
27699
27718
|
}
|
|
27700
27719
|
}
|
|
27701
27720
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -27706,6 +27725,7 @@ var ParallClient = class _ParallClient {
|
|
|
27706
27725
|
const rawErrorBody = await res.json().catch(() => ({}));
|
|
27707
27726
|
throw buildApiError(res, rawErrorBody);
|
|
27708
27727
|
}
|
|
27728
|
+
opts?.onStatus?.(res.status);
|
|
27709
27729
|
if (res.status === 204)
|
|
27710
27730
|
return void 0;
|
|
27711
27731
|
if (res.status === 202) {
|
|
@@ -27724,15 +27744,15 @@ var ParallClient = class _ParallClient {
|
|
|
27724
27744
|
* hit the 100 MiB cap, so a longer 5-minute timeout is used so a
|
|
27725
27745
|
* 50 MiB blob on a slow connection doesn't get chopped at 15 s.
|
|
27726
27746
|
*/
|
|
27727
|
-
async multipartRequest(method,
|
|
27747
|
+
async multipartRequest(method, path10, body, retried = false) {
|
|
27728
27748
|
if (!retried) {
|
|
27729
|
-
await this.ensureFreshToken(
|
|
27749
|
+
await this.ensureFreshToken(path10);
|
|
27730
27750
|
}
|
|
27731
27751
|
const { "Content-Type": _drop, ...headers } = this.buildHeaders();
|
|
27732
27752
|
void _drop;
|
|
27733
27753
|
let res;
|
|
27734
27754
|
try {
|
|
27735
|
-
res = await fetch(`${this.baseUrlFor(
|
|
27755
|
+
res = await fetch(`${this.baseUrlFor(path10)}${path10}`, {
|
|
27736
27756
|
method,
|
|
27737
27757
|
headers,
|
|
27738
27758
|
body,
|
|
@@ -27742,12 +27762,12 @@ var ParallClient = class _ParallClient {
|
|
|
27742
27762
|
throw _ParallClient.normalizeFetchError(err);
|
|
27743
27763
|
}
|
|
27744
27764
|
if (res.status === 401) {
|
|
27745
|
-
const pathSuffix =
|
|
27765
|
+
const pathSuffix = path10.replace(/^\/api\/v1/, "");
|
|
27746
27766
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
27747
27767
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
27748
27768
|
const refreshed = await this.tryRefresh();
|
|
27749
27769
|
if (refreshed) {
|
|
27750
|
-
return this.multipartRequest(method,
|
|
27770
|
+
return this.multipartRequest(method, path10, body, true);
|
|
27751
27771
|
}
|
|
27752
27772
|
}
|
|
27753
27773
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -28039,6 +28059,21 @@ var ParallClient = class _ParallClient {
|
|
|
28039
28059
|
async sendMessage(orgId, chatId, req) {
|
|
28040
28060
|
return this.request("POST", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), req);
|
|
28041
28061
|
}
|
|
28062
|
+
/**
|
|
28063
|
+
* sendMessage variant that also reports whether the server answered with an
|
|
28064
|
+
* idempotent replay (HTTP 200 — the message already existed for this
|
|
28065
|
+
* idempotency/effect key) instead of a fresh create (201). Used by the CLI
|
|
28066
|
+
* to surface "already sent by a previous run (deduplicated)".
|
|
28067
|
+
*/
|
|
28068
|
+
async sendMessageDetailed(orgId, chatId, req) {
|
|
28069
|
+
let status = 0;
|
|
28070
|
+
const message = await this.request("POST", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), req, void 0, false, {
|
|
28071
|
+
onStatus: (s) => {
|
|
28072
|
+
status = s;
|
|
28073
|
+
}
|
|
28074
|
+
});
|
|
28075
|
+
return { message, deduplicated: status === 200 };
|
|
28076
|
+
}
|
|
28042
28077
|
async getMessages(orgId, chatId, params) {
|
|
28043
28078
|
return this.request("GET", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), void 0, params);
|
|
28044
28079
|
}
|
|
@@ -28426,8 +28461,8 @@ var ParallClient = class _ParallClient {
|
|
|
28426
28461
|
async requestMachineUpdate(orgId, machineId, mandatory = false) {
|
|
28427
28462
|
await this.request("POST", ENDPOINTS.MACHINE_REQUEST_UPDATE(orgId, machineId), { mandatory });
|
|
28428
28463
|
}
|
|
28429
|
-
async browseMachineFilesystem(orgId, machineId,
|
|
28430
|
-
return this.request("POST", ENDPOINTS.MACHINE_BROWSE(orgId, machineId), { path:
|
|
28464
|
+
async browseMachineFilesystem(orgId, machineId, path10) {
|
|
28465
|
+
return this.request("POST", ENDPOINTS.MACHINE_BROWSE(orgId, machineId), { path: path10 }, void 0, false, { timeoutMs: 15e3 });
|
|
28431
28466
|
}
|
|
28432
28467
|
/** Create a new machine key. Returns the raw key string (shown once) + metadata. */
|
|
28433
28468
|
async createMachineKey(orgId, machineId, name) {
|
|
@@ -28500,6 +28535,30 @@ var ParallClient = class _ParallClient {
|
|
|
28500
28535
|
const params = maxAgeHours !== void 0 ? { max_age_hours: String(maxAgeHours) } : void 0;
|
|
28501
28536
|
return this.request("POST", ENDPOINTS.DISPATCH_EXPIRE(orgId), void 0, params);
|
|
28502
28537
|
}
|
|
28538
|
+
/**
|
|
28539
|
+
* Claim a dispatch lane (explicit consume endpoint). Occupies the
|
|
28540
|
+
* (agent, target, thread) lane and folds claimable WorkItems into it;
|
|
28541
|
+
* `claimed: false` means a healthy incumbent holds the lane.
|
|
28542
|
+
*/
|
|
28543
|
+
async claimDispatch(orgId, req) {
|
|
28544
|
+
return this.request("POST", ENDPOINTS.DISPATCH_CLAIM(orgId), req);
|
|
28545
|
+
}
|
|
28546
|
+
/** Fold a pending same-target WorkItem into a live lane (409 STALE_LANE when dethroned). */
|
|
28547
|
+
async steerDispatch(orgId, req) {
|
|
28548
|
+
return this.request("POST", ENDPOINTS.DISPATCH_STEER(orgId), req);
|
|
28549
|
+
}
|
|
28550
|
+
/** End a turn: no_action sweep of the lane's members + lane release + re-drive check. */
|
|
28551
|
+
async completeDispatch(orgId, req) {
|
|
28552
|
+
return this.request("POST", ENDPOINTS.DISPATCH_COMPLETE(orgId), req);
|
|
28553
|
+
}
|
|
28554
|
+
/** Release a lane on graceful shutdown — members return to pending immediately. */
|
|
28555
|
+
async releaseDispatchLane(orgId, lane) {
|
|
28556
|
+
return this.request("POST", ENDPOINTS.DISPATCH_RELEASE(orgId), { lane });
|
|
28557
|
+
}
|
|
28558
|
+
/** Renew a live lane's lease (long-turn keepalive). 409 STALE_LANE when dethroned. */
|
|
28559
|
+
async heartbeatDispatchLane(orgId, req) {
|
|
28560
|
+
return this.request("POST", ENDPOINTS.DISPATCH_HEARTBEAT(orgId), req);
|
|
28561
|
+
}
|
|
28503
28562
|
async getDispatchByMessages(orgId, chatId, messageIds) {
|
|
28504
28563
|
const params = { chat_id: chatId, message_ids: messageIds.join(",") };
|
|
28505
28564
|
return this.request("GET", ENDPOINTS.DISPATCH_BY_MESSAGES(orgId), void 0, params);
|
|
@@ -28964,8 +29023,8 @@ var ParallClient = class _ParallClient {
|
|
|
28964
29023
|
async deleteWikiRestriction(orgId, wikiId, restrictionId) {
|
|
28965
29024
|
await this.request("DELETE", ENDPOINTS.WIKI_RESTRICTION(orgId, wikiId, restrictionId));
|
|
28966
29025
|
}
|
|
28967
|
-
async getWikiAccessStatus(orgId, wikiId,
|
|
28968
|
-
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0,
|
|
29026
|
+
async getWikiAccessStatus(orgId, wikiId, path10) {
|
|
29027
|
+
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0, path10 ? { path: path10 } : void 0);
|
|
28969
29028
|
}
|
|
28970
29029
|
async createWikiAccessRequest(orgId, wikiId, data) {
|
|
28971
29030
|
await this.request("POST", ENDPOINTS.WIKI_ACCESS_REQUESTS(orgId, wikiId), data);
|
|
@@ -28974,14 +29033,14 @@ var ParallClient = class _ParallClient {
|
|
|
28974
29033
|
async getWikiCommits(orgId, wikiId, params) {
|
|
28975
29034
|
return this.request("GET", ENDPOINTS.WIKI_COMMITS(orgId, wikiId), void 0, params);
|
|
28976
29035
|
}
|
|
28977
|
-
async getWikiFileCommits(orgId, wikiId,
|
|
29036
|
+
async getWikiFileCommits(orgId, wikiId, path10, params) {
|
|
28978
29037
|
return this.request("GET", ENDPOINTS.WIKI_FILE_COMMITS(orgId, wikiId), void 0, {
|
|
28979
|
-
path:
|
|
29038
|
+
path: path10,
|
|
28980
29039
|
...params
|
|
28981
29040
|
});
|
|
28982
29041
|
}
|
|
28983
|
-
async getWikiBlame(orgId, wikiId,
|
|
28984
|
-
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path:
|
|
29042
|
+
async getWikiBlame(orgId, wikiId, path10, ref) {
|
|
29043
|
+
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path: path10, ref });
|
|
28985
29044
|
}
|
|
28986
29045
|
// ---- Wiki Operations (audit log) ----
|
|
28987
29046
|
async getWikiOperations(orgId, wikiId, params) {
|
|
@@ -29613,6 +29672,404 @@ var ParallWs = class {
|
|
|
29613
29672
|
}
|
|
29614
29673
|
};
|
|
29615
29674
|
|
|
29675
|
+
// ts/agent-core/dist/lane-ledger.js
|
|
29676
|
+
import * as fs from "node:fs";
|
|
29677
|
+
var LedgerUnsupportedError = class extends Error {
|
|
29678
|
+
};
|
|
29679
|
+
function isStaleLane(err) {
|
|
29680
|
+
return err instanceof ApiError && err.status === 409 && err.code === "STALE_LANE";
|
|
29681
|
+
}
|
|
29682
|
+
function isEndpointMissing(err) {
|
|
29683
|
+
return err instanceof ApiError && err.status === 404 && !err.code;
|
|
29684
|
+
}
|
|
29685
|
+
var LaneLedger = class {
|
|
29686
|
+
opts;
|
|
29687
|
+
lanes = /* @__PURE__ */ new Map();
|
|
29688
|
+
constructor(opts) {
|
|
29689
|
+
this.opts = opts;
|
|
29690
|
+
}
|
|
29691
|
+
get contextDir() {
|
|
29692
|
+
return this.opts.contextDir;
|
|
29693
|
+
}
|
|
29694
|
+
/** Only chat message events ride the lane ledger; typed events stay on the legacy ack path. */
|
|
29695
|
+
handles(event) {
|
|
29696
|
+
return event.type === "message" && event.targetId.startsWith("cht_");
|
|
29697
|
+
}
|
|
29698
|
+
laneKeyFor(event) {
|
|
29699
|
+
if (event.type !== "message" && event.dispatchEventId) {
|
|
29700
|
+
return laneKeyForTarget(`dsp:${event.dispatchEventId}`);
|
|
29701
|
+
}
|
|
29702
|
+
return laneKeyForTarget(`prll://${event.targetId}`, event.threadRootId);
|
|
29703
|
+
}
|
|
29704
|
+
getForEvent(event) {
|
|
29705
|
+
return this.lanes.get(this.laneKeyFor(event));
|
|
29706
|
+
}
|
|
29707
|
+
laneContextPath(lane) {
|
|
29708
|
+
return laneContextFilePath(this.opts.contextDir, lane.targetUri, lane.threadRootId);
|
|
29709
|
+
}
|
|
29710
|
+
/**
|
|
29711
|
+
* Claim (or reuse) the lane for a group of same-lane message events and
|
|
29712
|
+
* fold every group member into it. Returns 'foreign' when a healthy
|
|
29713
|
+
* incumbent (another pod) holds the resource — the caller must not
|
|
29714
|
+
* dispatch; the events stay pending server-side and re-drive after the
|
|
29715
|
+
* incumbent completes.
|
|
29716
|
+
*/
|
|
29717
|
+
async ensureLane(events) {
|
|
29718
|
+
const trigger = events[events.length - 1];
|
|
29719
|
+
const laneKey = this.laneKeyFor(trigger);
|
|
29720
|
+
let lane = this.lanes.get(laneKey);
|
|
29721
|
+
if (!lane) {
|
|
29722
|
+
const targetUri = `prll://${trigger.targetId}`;
|
|
29723
|
+
let res;
|
|
29724
|
+
try {
|
|
29725
|
+
res = await this.opts.client.claimDispatch(this.opts.orgId, {
|
|
29726
|
+
target_uri: targetUri,
|
|
29727
|
+
thread_root_id: trigger.threadRootId,
|
|
29728
|
+
limit: 100
|
|
29729
|
+
});
|
|
29730
|
+
} catch (err) {
|
|
29731
|
+
if (isEndpointMissing(err))
|
|
29732
|
+
throw new LedgerUnsupportedError("claim endpoint unavailable");
|
|
29733
|
+
throw err;
|
|
29734
|
+
}
|
|
29735
|
+
if (!res.claimed || !res.lane) {
|
|
29736
|
+
this.opts.log?.info(`lane for ${targetUri} held by a healthy incumbent \u2014 leaving events pending for re-drive`);
|
|
29737
|
+
return null;
|
|
29738
|
+
}
|
|
29739
|
+
const leaseUntilMs = Date.parse(res.lease_until ?? "");
|
|
29740
|
+
lane = {
|
|
29741
|
+
laneKey,
|
|
29742
|
+
lane: res.lane,
|
|
29743
|
+
targetUri,
|
|
29744
|
+
threadRootId: trigger.threadRootId,
|
|
29745
|
+
folded: /* @__PURE__ */ new Map(),
|
|
29746
|
+
...Number.isNaN(leaseUntilMs) ? {} : { leaseUntilMs, leaseTtlMs: Math.max(leaseUntilMs - Date.now(), 6e4) }
|
|
29747
|
+
};
|
|
29748
|
+
for (const ev of res.events ?? []) {
|
|
29749
|
+
lane.folded.set(ev.source_id, ev.id);
|
|
29750
|
+
}
|
|
29751
|
+
this.lanes.set(laneKey, lane);
|
|
29752
|
+
}
|
|
29753
|
+
for (const ev of events) {
|
|
29754
|
+
if (lane.folded.has(ev.messageId))
|
|
29755
|
+
continue;
|
|
29756
|
+
try {
|
|
29757
|
+
const res = await this.opts.client.steerDispatch(this.opts.orgId, {
|
|
29758
|
+
lane: lane.lane,
|
|
29759
|
+
target_uri: lane.targetUri,
|
|
29760
|
+
thread_root_id: lane.threadRootId,
|
|
29761
|
+
...ev.dispatchEventId ? { dispatch_event_id: ev.dispatchEventId } : { source_type: "message", source_id: ev.messageId }
|
|
29762
|
+
});
|
|
29763
|
+
lane.folded.set(ev.messageId, res.dispatch_event_id);
|
|
29764
|
+
} catch (err) {
|
|
29765
|
+
if (isStaleLane(err)) {
|
|
29766
|
+
this.lanes.delete(laneKey);
|
|
29767
|
+
return null;
|
|
29768
|
+
}
|
|
29769
|
+
this.opts.log?.warn(`steer fold failed for ${ev.messageId} \u2014 failing closed, releasing lane: ${String(err)}`);
|
|
29770
|
+
await this.release(laneKey);
|
|
29771
|
+
return null;
|
|
29772
|
+
}
|
|
29773
|
+
}
|
|
29774
|
+
return lane;
|
|
29775
|
+
}
|
|
29776
|
+
/**
|
|
29777
|
+
* Fold a live mid-turn message into its active lane BEFORE injecting it
|
|
29778
|
+
* into the running turn. Injection without a successful fold is forbidden —
|
|
29779
|
+
* an un-folded injected message would be re-driven after complete and the
|
|
29780
|
+
* model would handle it twice.
|
|
29781
|
+
*/
|
|
29782
|
+
async steerLive(event) {
|
|
29783
|
+
const laneKey = this.laneKeyFor(event);
|
|
29784
|
+
const lane = this.lanes.get(laneKey);
|
|
29785
|
+
if (!lane)
|
|
29786
|
+
return false;
|
|
29787
|
+
if (lane.folded.has(event.messageId))
|
|
29788
|
+
return true;
|
|
29789
|
+
try {
|
|
29790
|
+
const res = await this.opts.client.steerDispatch(this.opts.orgId, {
|
|
29791
|
+
lane: lane.lane,
|
|
29792
|
+
target_uri: lane.targetUri,
|
|
29793
|
+
thread_root_id: lane.threadRootId,
|
|
29794
|
+
...event.dispatchEventId ? { dispatch_event_id: event.dispatchEventId } : { source_type: "message", source_id: event.messageId }
|
|
29795
|
+
});
|
|
29796
|
+
lane.folded.set(event.messageId, res.dispatch_event_id);
|
|
29797
|
+
return true;
|
|
29798
|
+
} catch (err) {
|
|
29799
|
+
if (isStaleLane(err)) {
|
|
29800
|
+
this.lanes.delete(laneKey);
|
|
29801
|
+
} else {
|
|
29802
|
+
this.opts.log?.warn(`live steer failed for ${event.messageId}: ${String(err)}`);
|
|
29803
|
+
}
|
|
29804
|
+
return false;
|
|
29805
|
+
}
|
|
29806
|
+
}
|
|
29807
|
+
/**
|
|
29808
|
+
* Complete the lane when no local work remains for it: the server sweeps
|
|
29809
|
+
* still-leased members as no_action, releases the occupancy row, and
|
|
29810
|
+
* re-drives any same-target pending work. A STALE_LANE answer means a
|
|
29811
|
+
* takeover already owns the resource — local state is dropped either way.
|
|
29812
|
+
*/
|
|
29813
|
+
async completeIfIdle(laneKey, hasMoreLocal) {
|
|
29814
|
+
const lane = this.lanes.get(laneKey);
|
|
29815
|
+
if (!lane || hasMoreLocal)
|
|
29816
|
+
return;
|
|
29817
|
+
this.lanes.delete(laneKey);
|
|
29818
|
+
this.removeLaneContext(lane);
|
|
29819
|
+
try {
|
|
29820
|
+
const res = await this.opts.client.completeDispatch(this.opts.orgId, {
|
|
29821
|
+
lane: lane.lane,
|
|
29822
|
+
target_uri: lane.targetUri,
|
|
29823
|
+
thread_root_id: lane.threadRootId
|
|
29824
|
+
});
|
|
29825
|
+
if (res.swept_no_action > 0 || res.redriven) {
|
|
29826
|
+
this.opts.log?.info(`lane complete for ${lane.targetUri}: swept ${res.swept_no_action} no_action, redriven=${res.redriven}`);
|
|
29827
|
+
}
|
|
29828
|
+
} catch (err) {
|
|
29829
|
+
if (isStaleLane(err)) {
|
|
29830
|
+
this.opts.log?.info(`lane complete skipped for ${lane.targetUri} \u2014 taken over`);
|
|
29831
|
+
return;
|
|
29832
|
+
}
|
|
29833
|
+
this.opts.log?.warn(`lane complete failed for ${lane.targetUri}: ${String(err)}`);
|
|
29834
|
+
}
|
|
29835
|
+
}
|
|
29836
|
+
/**
|
|
29837
|
+
* Long-turn keepalive: renew the lane's lease on runtime activity, throttled
|
|
29838
|
+
* so a chatty turn doesn't spam the server. Without this, a legitimately
|
|
29839
|
+
* long turn (> lane TTL) would be dethroned mid-flight and every subsequent
|
|
29840
|
+
* write misfired with STALE_LANE — the design doc's "long turns renew via
|
|
29841
|
+
* step writes". Fire-and-forget: a failed renewal is surfaced by the next
|
|
29842
|
+
* write's incumbency check anyway.
|
|
29843
|
+
*/
|
|
29844
|
+
maybeRenew(lane) {
|
|
29845
|
+
const now = Date.now();
|
|
29846
|
+
const ttl = lane.leaseTtlMs ?? 10 * 6e4;
|
|
29847
|
+
const until = lane.leaseUntilMs ?? now;
|
|
29848
|
+
if (until - now > ttl / 2)
|
|
29849
|
+
return;
|
|
29850
|
+
lane.leaseUntilMs = now + ttl;
|
|
29851
|
+
void this.opts.client.heartbeatDispatchLane(this.opts.orgId, {
|
|
29852
|
+
lane: lane.lane,
|
|
29853
|
+
target_uri: lane.targetUri,
|
|
29854
|
+
thread_root_id: lane.threadRootId
|
|
29855
|
+
}).then((res) => {
|
|
29856
|
+
const until2 = Date.parse(res?.lease_until ?? "");
|
|
29857
|
+
if (!Number.isNaN(until2))
|
|
29858
|
+
lane.leaseUntilMs = until2;
|
|
29859
|
+
}).catch((err) => {
|
|
29860
|
+
if (isStaleLane(err)) {
|
|
29861
|
+
this.lanes.delete(lane.laneKey);
|
|
29862
|
+
this.opts.log?.warn(`lane ${lane.targetUri} was taken over during the turn`);
|
|
29863
|
+
return;
|
|
29864
|
+
}
|
|
29865
|
+
this.opts.log?.warn(`lane heartbeat failed for ${lane.targetUri}: ${String(err)}`);
|
|
29866
|
+
});
|
|
29867
|
+
}
|
|
29868
|
+
/**
|
|
29869
|
+
* Release a lane's unresolved members back to pending (dispatch error /
|
|
29870
|
+
* shutdown) so the next pod re-claims immediately instead of waiting out
|
|
29871
|
+
* the lease.
|
|
29872
|
+
*/
|
|
29873
|
+
async release(laneKey) {
|
|
29874
|
+
const lane = this.lanes.get(laneKey);
|
|
29875
|
+
if (!lane)
|
|
29876
|
+
return;
|
|
29877
|
+
this.lanes.delete(laneKey);
|
|
29878
|
+
this.removeLaneContext(lane);
|
|
29879
|
+
try {
|
|
29880
|
+
await this.opts.client.releaseDispatchLane(this.opts.orgId, lane.lane);
|
|
29881
|
+
} catch (err) {
|
|
29882
|
+
this.opts.log?.warn(`lane release failed for ${lane.targetUri}: ${String(err)}`);
|
|
29883
|
+
}
|
|
29884
|
+
}
|
|
29885
|
+
async releaseAll() {
|
|
29886
|
+
const keys = [...this.lanes.keys()];
|
|
29887
|
+
for (const key of keys) {
|
|
29888
|
+
await this.release(key);
|
|
29889
|
+
}
|
|
29890
|
+
}
|
|
29891
|
+
/** True when any lane is currently active (used by shutdown logging). */
|
|
29892
|
+
get activeCount() {
|
|
29893
|
+
return this.lanes.size;
|
|
29894
|
+
}
|
|
29895
|
+
/**
|
|
29896
|
+
* Claim the single-member lane of one typed WorkItem (resource = dsp:<id>),
|
|
29897
|
+
* by WorkItem id or by source identity (the live task.assigned event has no
|
|
29898
|
+
* WorkItem id). Returns null when a healthy incumbent (another pod) holds
|
|
29899
|
+
* it or the WorkItem is already resolved — the caller must skip processing.
|
|
29900
|
+
*/
|
|
29901
|
+
async claimTyped(ref) {
|
|
29902
|
+
let res;
|
|
29903
|
+
try {
|
|
29904
|
+
res = await this.opts.client.claimDispatch(this.opts.orgId, {
|
|
29905
|
+
dispatch_event_id: ref.dispatchEventId,
|
|
29906
|
+
source_type: ref.dispatchEventId ? void 0 : ref.sourceType,
|
|
29907
|
+
source_id: ref.dispatchEventId ? void 0 : ref.sourceId
|
|
29908
|
+
});
|
|
29909
|
+
} catch (err) {
|
|
29910
|
+
if (isEndpointMissing(err))
|
|
29911
|
+
throw new LedgerUnsupportedError("claim endpoint unavailable");
|
|
29912
|
+
throw err;
|
|
29913
|
+
}
|
|
29914
|
+
if (!res.claimed || !res.lane || !res.events?.length)
|
|
29915
|
+
return null;
|
|
29916
|
+
const workItem = res.events[0];
|
|
29917
|
+
const targetUri = `dsp:${workItem.id}`;
|
|
29918
|
+
const leaseUntilMs = Date.parse(res.lease_until ?? "");
|
|
29919
|
+
const lane = {
|
|
29920
|
+
laneKey: laneKeyForTarget(targetUri),
|
|
29921
|
+
lane: res.lane,
|
|
29922
|
+
targetUri,
|
|
29923
|
+
folded: /* @__PURE__ */ new Map([[workItem.source_id, workItem.id]]),
|
|
29924
|
+
typedDispatchEventId: workItem.id,
|
|
29925
|
+
...Number.isNaN(leaseUntilMs) ? {} : { leaseUntilMs, leaseTtlMs: Math.max(leaseUntilMs - Date.now(), 6e4) }
|
|
29926
|
+
};
|
|
29927
|
+
this.lanes.set(lane.laneKey, lane);
|
|
29928
|
+
return lane;
|
|
29929
|
+
}
|
|
29930
|
+
/**
|
|
29931
|
+
* Remove the per-lane context file (and its CLI sidecar) when the lane
|
|
29932
|
+
* ends. A leftover file would make a later cross-context send to the same
|
|
29933
|
+
* target bind a dead lane token and misfire with STALE_LANE instead of
|
|
29934
|
+
* taking the plain non-ledger path.
|
|
29935
|
+
*/
|
|
29936
|
+
removeLaneContext(lane) {
|
|
29937
|
+
const contextPath = this.laneContextPath(lane);
|
|
29938
|
+
for (const p of [contextPath, contextPath.replace(/\.json$/, ".reply-state.json")]) {
|
|
29939
|
+
try {
|
|
29940
|
+
fs.rmSync(p, { force: true });
|
|
29941
|
+
} catch {
|
|
29942
|
+
}
|
|
29943
|
+
}
|
|
29944
|
+
}
|
|
29945
|
+
};
|
|
29946
|
+
|
|
29947
|
+
// ts/agent-core/dist/gateway-lane-flow.js
|
|
29948
|
+
async function dispatchLaneGroup(host, opts) {
|
|
29949
|
+
const ledger = host.laneLedger;
|
|
29950
|
+
const event = opts.events[opts.events.length - 1];
|
|
29951
|
+
let lane;
|
|
29952
|
+
try {
|
|
29953
|
+
lane = await ledger.ensureLane(opts.events);
|
|
29954
|
+
} catch (err) {
|
|
29955
|
+
if (!(err instanceof LedgerUnsupportedError))
|
|
29956
|
+
throw err;
|
|
29957
|
+
host.disableLedger("claim endpoint missing");
|
|
29958
|
+
await host.emitDispatchReceived(event);
|
|
29959
|
+
const dispatched2 = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText);
|
|
29960
|
+
if (!dispatched2)
|
|
29961
|
+
return "shutdown";
|
|
29962
|
+
for (const ev of opts.events) {
|
|
29963
|
+
host.opts.client.ackDispatch(host.opts.config.org_id, {
|
|
29964
|
+
source_type: ev.ackSourceType ?? "message",
|
|
29965
|
+
source_id: ev.ackSourceId ?? ev.messageId
|
|
29966
|
+
}).catch(() => {
|
|
29967
|
+
});
|
|
29968
|
+
}
|
|
29969
|
+
return "dispatched";
|
|
29970
|
+
}
|
|
29971
|
+
if (!lane) {
|
|
29972
|
+
for (const ev of opts.events) {
|
|
29973
|
+
host.dispatchedMessages.delete(ev.messageId);
|
|
29974
|
+
}
|
|
29975
|
+
return "foreign";
|
|
29976
|
+
}
|
|
29977
|
+
let dispatched = false;
|
|
29978
|
+
try {
|
|
29979
|
+
dispatched = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText);
|
|
29980
|
+
} catch (err) {
|
|
29981
|
+
await ledger.release(lane.laneKey).catch(() => {
|
|
29982
|
+
});
|
|
29983
|
+
throw err;
|
|
29984
|
+
}
|
|
29985
|
+
if (!dispatched) {
|
|
29986
|
+
return "shutdown";
|
|
29987
|
+
}
|
|
29988
|
+
const pendingInjections = host.opts.dispatchAdapter.hasPendingInjections?.(opts.sessionKey) ?? false;
|
|
29989
|
+
await ledger.completeIfIdle(lane.laneKey, pendingInjections || opts.hasMoreLocal());
|
|
29990
|
+
return "dispatched";
|
|
29991
|
+
}
|
|
29992
|
+
async function consumeTypedDispatch(host, ref, run, ack) {
|
|
29993
|
+
if (!host.laneLedger || host.ledgerDisabled) {
|
|
29994
|
+
if (await run(ref.dispatchEventId))
|
|
29995
|
+
ack(ref.dispatchEventId);
|
|
29996
|
+
return;
|
|
29997
|
+
}
|
|
29998
|
+
let lane;
|
|
29999
|
+
try {
|
|
30000
|
+
lane = await host.laneLedger.claimTyped(ref);
|
|
30001
|
+
} catch (err) {
|
|
30002
|
+
if (err instanceof LedgerUnsupportedError) {
|
|
30003
|
+
host.disableLedger("claim endpoint missing");
|
|
30004
|
+
if (await run(ref.dispatchEventId))
|
|
30005
|
+
ack(ref.dispatchEventId);
|
|
30006
|
+
return;
|
|
30007
|
+
}
|
|
30008
|
+
throw err;
|
|
30009
|
+
}
|
|
30010
|
+
if (!lane) {
|
|
30011
|
+
host.opts.log?.info(`typed dispatch ${ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`} not claimable (held elsewhere or already resolved) \u2014 skipping`);
|
|
30012
|
+
return;
|
|
30013
|
+
}
|
|
30014
|
+
try {
|
|
30015
|
+
if (await run(lane.typedDispatchEventId))
|
|
30016
|
+
ack(lane.typedDispatchEventId);
|
|
30017
|
+
} finally {
|
|
30018
|
+
await host.laneLedger.completeIfIdle(lane.laneKey, false).catch(() => {
|
|
30019
|
+
});
|
|
30020
|
+
}
|
|
30021
|
+
}
|
|
30022
|
+
async function consumeMessageWorkItem(host, item) {
|
|
30023
|
+
if (host.shuttingDown)
|
|
30024
|
+
return;
|
|
30025
|
+
if (!host.tryClaimMessage(item.source_id))
|
|
30026
|
+
return;
|
|
30027
|
+
const ackItem = () => {
|
|
30028
|
+
host.opts.client.ackDispatchByID(host.opts.config.org_id, item.id).catch(() => {
|
|
30029
|
+
});
|
|
30030
|
+
};
|
|
30031
|
+
let msg = null;
|
|
30032
|
+
try {
|
|
30033
|
+
msg = await host.opts.client.getMessage(item.source_id);
|
|
30034
|
+
} catch (err) {
|
|
30035
|
+
const status = err?.status;
|
|
30036
|
+
if (status !== 404) {
|
|
30037
|
+
host.opts.log?.warn(`message fetch failed for ${item.source_id}, leaving pending: ${String(err)}`);
|
|
30038
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30039
|
+
return;
|
|
30040
|
+
}
|
|
30041
|
+
}
|
|
30042
|
+
if (!msg || msg.sender_id === host.opts.agentUserId) {
|
|
30043
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30044
|
+
ackItem();
|
|
30045
|
+
return;
|
|
30046
|
+
}
|
|
30047
|
+
const decision = await host.buildMessageDispatchDecision(item.chat_id, msg);
|
|
30048
|
+
if (decision.action === "retry") {
|
|
30049
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30050
|
+
return;
|
|
30051
|
+
}
|
|
30052
|
+
if (decision.action === "skip") {
|
|
30053
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30054
|
+
ackItem();
|
|
30055
|
+
return;
|
|
30056
|
+
}
|
|
30057
|
+
decision.event.dispatchEventId = item.id;
|
|
30058
|
+
const laneResolved = host.usesLaneLedger(decision.event);
|
|
30059
|
+
try {
|
|
30060
|
+
const dispatched = await host.handleInboundEvent(decision.event);
|
|
30061
|
+
if (dispatched) {
|
|
30062
|
+
if (!laneResolved)
|
|
30063
|
+
ackItem();
|
|
30064
|
+
} else {
|
|
30065
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30066
|
+
}
|
|
30067
|
+
} catch (err) {
|
|
30068
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30069
|
+
throw err;
|
|
30070
|
+
}
|
|
30071
|
+
}
|
|
30072
|
+
|
|
29616
30073
|
// ts/agent-core/dist/telemetry.js
|
|
29617
30074
|
init_esm();
|
|
29618
30075
|
var import_api_logs = __toESM(require_src(), 1);
|
|
@@ -29914,6 +30371,14 @@ var ParallAgentGateway = class {
|
|
|
29914
30371
|
inFlightDispatches = 0;
|
|
29915
30372
|
drainResolvers = [];
|
|
29916
30373
|
pendingRestartNotification = null;
|
|
30374
|
+
laneLedger;
|
|
30375
|
+
// Sticky fallback: flipped when the server predates the ledger (claim
|
|
30376
|
+
// endpoint 404) so every subsequent dispatch uses the legacy flow.
|
|
30377
|
+
ledgerDisabled = false;
|
|
30378
|
+
// Group key of the group currently being dispatched on main — lane-aware
|
|
30379
|
+
// (targetId + thread), unlike mainCurrentTargetId which stays chat-level
|
|
30380
|
+
// for fork routing decisions.
|
|
30381
|
+
mainCurrentGroupKey;
|
|
29917
30382
|
DISPATCHED_MESSAGES_CAP = 5e3;
|
|
29918
30383
|
// SHUTDOWN_DEADLINE_MS is read by waitForDrain via the configured value
|
|
29919
30384
|
// below — kept as instance state so per-runtime configs can override it
|
|
@@ -29923,6 +30388,14 @@ var ParallAgentGateway = class {
|
|
|
29923
30388
|
DISPATCH_DEADLINE_MS;
|
|
29924
30389
|
constructor(opts) {
|
|
29925
30390
|
this.opts = opts;
|
|
30391
|
+
if (opts.dispatchContextDir) {
|
|
30392
|
+
this.laneLedger = new LaneLedger({
|
|
30393
|
+
client: opts.client,
|
|
30394
|
+
orgId: opts.config.org_id,
|
|
30395
|
+
contextDir: opts.dispatchContextDir,
|
|
30396
|
+
log: opts.log
|
|
30397
|
+
});
|
|
30398
|
+
}
|
|
29926
30399
|
this.SHUTDOWN_DEADLINE_MS = opts.shutdownDeadlineMs ?? 6e4;
|
|
29927
30400
|
this.FORK_DEADLINE_MS = opts.forkDeadlineMs ?? 2 * 60 * 6e4;
|
|
29928
30401
|
this.DISPATCH_DEADLINE_MS = opts.dispatchDeadlineMs ?? 20 * 6e4;
|
|
@@ -29992,14 +30465,18 @@ var ParallAgentGateway = class {
|
|
|
29992
30465
|
if (data.status !== "todo" && data.status !== "in_progress")
|
|
29993
30466
|
return;
|
|
29994
30467
|
try {
|
|
29995
|
-
|
|
29996
|
-
|
|
30468
|
+
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) => {
|
|
30469
|
+
if (dispatchEventId) {
|
|
30470
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, dispatchEventId).catch(() => {
|
|
30471
|
+
});
|
|
30472
|
+
return;
|
|
30473
|
+
}
|
|
29997
30474
|
this.opts.client.ackDispatch(this.opts.config.org_id, {
|
|
29998
30475
|
source_type: "task_activity",
|
|
29999
30476
|
source_id: data.id
|
|
30000
30477
|
}).catch(() => {
|
|
30001
30478
|
});
|
|
30002
|
-
}
|
|
30479
|
+
});
|
|
30003
30480
|
} catch (err) {
|
|
30004
30481
|
this.opts.log?.error(`task dispatch failed for ${data.id}: ${String(err)}`);
|
|
30005
30482
|
}
|
|
@@ -30009,11 +30486,10 @@ var ParallAgentGateway = class {
|
|
|
30009
30486
|
if (!data.source_id || !data.task_id)
|
|
30010
30487
|
return;
|
|
30011
30488
|
try {
|
|
30012
|
-
|
|
30013
|
-
if (dispatched) {
|
|
30489
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleTaskComment(data.source_id, data.task_id ?? "", data.actor_id, data.delivery_reason), () => {
|
|
30014
30490
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30015
30491
|
});
|
|
30016
|
-
}
|
|
30492
|
+
});
|
|
30017
30493
|
} catch (err) {
|
|
30018
30494
|
this.opts.log?.error(`task comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30019
30495
|
}
|
|
@@ -30021,11 +30497,10 @@ var ParallAgentGateway = class {
|
|
|
30021
30497
|
if (!data.source_id)
|
|
30022
30498
|
return;
|
|
30023
30499
|
try {
|
|
30024
|
-
|
|
30025
|
-
if (dispatched) {
|
|
30500
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleWikiComment(data.source_id, data.actor_id, data.delivery_reason), () => {
|
|
30026
30501
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30027
30502
|
});
|
|
30028
|
-
}
|
|
30503
|
+
});
|
|
30029
30504
|
} catch (err) {
|
|
30030
30505
|
this.opts.log?.error(`wiki comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30031
30506
|
}
|
|
@@ -30033,11 +30508,13 @@ var ParallAgentGateway = class {
|
|
|
30033
30508
|
if (!data.task_id)
|
|
30034
30509
|
return;
|
|
30035
30510
|
try {
|
|
30036
|
-
|
|
30037
|
-
|
|
30511
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.handleTaskDispatch(data.task_id ?? "", data.source_id ?? data.task_id ?? "", {
|
|
30512
|
+
allowCreator: true,
|
|
30513
|
+
dispatchEventId
|
|
30514
|
+
}), () => {
|
|
30038
30515
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30039
30516
|
});
|
|
30040
|
-
}
|
|
30517
|
+
});
|
|
30041
30518
|
} catch (err) {
|
|
30042
30519
|
this.opts.log?.error(`task update dispatch failed for ${data.task_id}: ${String(err)}`);
|
|
30043
30520
|
}
|
|
@@ -30045,11 +30522,10 @@ var ParallAgentGateway = class {
|
|
|
30045
30522
|
if (!data.source_id)
|
|
30046
30523
|
return;
|
|
30047
30524
|
try {
|
|
30048
|
-
|
|
30049
|
-
if (dispatched) {
|
|
30525
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleScheduleFire(data.source_id, data.actor_id), () => {
|
|
30050
30526
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30051
30527
|
});
|
|
30052
|
-
}
|
|
30528
|
+
});
|
|
30053
30529
|
} catch (err) {
|
|
30054
30530
|
this.opts.log?.error(`schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30055
30531
|
}
|
|
@@ -30057,11 +30533,10 @@ var ParallAgentGateway = class {
|
|
|
30057
30533
|
if (!data.source_id)
|
|
30058
30534
|
return;
|
|
30059
30535
|
try {
|
|
30060
|
-
|
|
30061
|
-
if (dispatched) {
|
|
30536
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleExternalTriggerRun(data.source_id), () => {
|
|
30062
30537
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30063
30538
|
});
|
|
30064
|
-
}
|
|
30539
|
+
});
|
|
30065
30540
|
} catch (err) {
|
|
30066
30541
|
this.opts.log?.error(`external trigger dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30067
30542
|
}
|
|
@@ -30069,11 +30544,10 @@ var ParallAgentGateway = class {
|
|
|
30069
30544
|
if (!data.source_id)
|
|
30070
30545
|
return;
|
|
30071
30546
|
try {
|
|
30072
|
-
|
|
30073
|
-
if (dispatched) {
|
|
30547
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleChannelMessage(data.source_id), () => {
|
|
30074
30548
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30075
30549
|
});
|
|
30076
|
-
}
|
|
30550
|
+
});
|
|
30077
30551
|
} catch (err) {
|
|
30078
30552
|
this.opts.log?.error(`channel message dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30079
30553
|
}
|
|
@@ -30081,14 +30555,19 @@ var ParallAgentGateway = class {
|
|
|
30081
30555
|
if (!data.source_id)
|
|
30082
30556
|
return;
|
|
30083
30557
|
try {
|
|
30084
|
-
|
|
30085
|
-
if (dispatched) {
|
|
30558
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleApprovalDecided(data.source_id, data.actor_id, data.chat_id ?? null), () => {
|
|
30086
30559
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30087
30560
|
});
|
|
30088
|
-
}
|
|
30561
|
+
});
|
|
30089
30562
|
} catch (err) {
|
|
30090
30563
|
this.opts.log?.error(`approval decided dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30091
30564
|
}
|
|
30565
|
+
} else if (data.event_type === "message" && this.laneLedger && data.source_id && data.chat_id) {
|
|
30566
|
+
try {
|
|
30567
|
+
await this.handleMessageRedrive(data);
|
|
30568
|
+
} catch (err) {
|
|
30569
|
+
this.opts.log?.error(`message re-drive failed for ${data.source_id}: ${String(err)}`);
|
|
30570
|
+
}
|
|
30092
30571
|
} else if (data.event_type !== "message" && data.event_type !== "task_assign") {
|
|
30093
30572
|
this.opts.log?.info(`dispatch.new with unhandled event_type=${String(data.event_type)} (id=${data.id}) \u2014 no-op`);
|
|
30094
30573
|
}
|
|
@@ -30124,6 +30603,39 @@ var ParallAgentGateway = class {
|
|
|
30124
30603
|
source_id: sourceId
|
|
30125
30604
|
});
|
|
30126
30605
|
}
|
|
30606
|
+
/** True when this event's lifecycle is owned by the dispatch lane ledger. */
|
|
30607
|
+
usesLaneLedger(event) {
|
|
30608
|
+
return this.laneLedger != null && !this.ledgerDisabled && this.laneLedger.handles(event);
|
|
30609
|
+
}
|
|
30610
|
+
disableLedger(reason) {
|
|
30611
|
+
if (this.ledgerDisabled)
|
|
30612
|
+
return;
|
|
30613
|
+
this.ledgerDisabled = true;
|
|
30614
|
+
this.opts.log?.warn(`dispatch ledger unavailable (${reason}) \u2014 falling back to legacy received/ack flow`);
|
|
30615
|
+
}
|
|
30616
|
+
/**
|
|
30617
|
+
* Buffer grouping key. Lane-ledger message events group by full lane
|
|
30618
|
+
* identity (chat + thread) so a channel lane and a thread lane in the same
|
|
30619
|
+
* chat dispatch as separate turns with separate claims; everything else
|
|
30620
|
+
* keeps the historical chat-level grouping.
|
|
30621
|
+
*/
|
|
30622
|
+
dispatchGroupKey(event) {
|
|
30623
|
+
if (this.usesLaneLedger(event)) {
|
|
30624
|
+
return this.laneLedger.laneKeyFor(event);
|
|
30625
|
+
}
|
|
30626
|
+
return event.targetId;
|
|
30627
|
+
}
|
|
30628
|
+
// Lane-flow protocols live in gateway-lane-flow.ts; these thin delegates
|
|
30629
|
+
// keep call sites and tests on the class surface.
|
|
30630
|
+
laneFlowHost() {
|
|
30631
|
+
return this;
|
|
30632
|
+
}
|
|
30633
|
+
dispatchLaneGroup(opts) {
|
|
30634
|
+
return dispatchLaneGroup(this.laneFlowHost(), opts);
|
|
30635
|
+
}
|
|
30636
|
+
consumeTypedDispatch(ref, run, ack) {
|
|
30637
|
+
return consumeTypedDispatch(this.laneFlowHost(), ref, run, ack);
|
|
30638
|
+
}
|
|
30127
30639
|
buildDispatchContext(event, sessionKey) {
|
|
30128
30640
|
const binding = this.sessionBindings.get(sessionKey);
|
|
30129
30641
|
return {
|
|
@@ -30140,6 +30652,7 @@ var ParallAgentGateway = class {
|
|
|
30140
30652
|
noReply: event.noReply ?? false,
|
|
30141
30653
|
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey),
|
|
30142
30654
|
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey),
|
|
30655
|
+
contextDirPath: this.opts.dispatchContextDir,
|
|
30143
30656
|
client: this.opts.client,
|
|
30144
30657
|
log: this.opts.log
|
|
30145
30658
|
};
|
|
@@ -30179,7 +30692,7 @@ var ParallAgentGateway = class {
|
|
|
30179
30692
|
this.opts.log?.warn(`failed to create input step: ${String(err)}`);
|
|
30180
30693
|
}
|
|
30181
30694
|
}
|
|
30182
|
-
async createRuntimeStep(sessionId, event, runtimeEvent, stepIdFilePath, contextFilePath) {
|
|
30695
|
+
async createRuntimeStep(sessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2) {
|
|
30183
30696
|
const target = resolveStepTarget(event);
|
|
30184
30697
|
try {
|
|
30185
30698
|
switch (runtimeEvent.type) {
|
|
@@ -30225,6 +30738,9 @@ var ParallAgentGateway = class {
|
|
|
30225
30738
|
} else if (stepIdFilePath) {
|
|
30226
30739
|
this.writeStepIdFile(stepIdFilePath, step.id);
|
|
30227
30740
|
}
|
|
30741
|
+
if (laneContextFilePath2) {
|
|
30742
|
+
this.updateContextFileStepId(laneContextFilePath2, step.id);
|
|
30743
|
+
}
|
|
30228
30744
|
break;
|
|
30229
30745
|
}
|
|
30230
30746
|
case "tool_result":
|
|
@@ -30247,6 +30763,9 @@ var ParallAgentGateway = class {
|
|
|
30247
30763
|
} else if (stepIdFilePath) {
|
|
30248
30764
|
this.clearStepIdFile(stepIdFilePath);
|
|
30249
30765
|
}
|
|
30766
|
+
if (laneContextFilePath2) {
|
|
30767
|
+
this.updateContextFileStepId(laneContextFilePath2, null);
|
|
30768
|
+
}
|
|
30250
30769
|
break;
|
|
30251
30770
|
case "error":
|
|
30252
30771
|
await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
|
|
@@ -30266,28 +30785,28 @@ var ParallAgentGateway = class {
|
|
|
30266
30785
|
}
|
|
30267
30786
|
writeContextFile(filePath, ctx) {
|
|
30268
30787
|
try {
|
|
30269
|
-
|
|
30270
|
-
|
|
30788
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
30789
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30271
30790
|
} catch (err) {
|
|
30272
30791
|
this.opts.log?.warn(`failed to write context file ${filePath}: ${String(err)}`);
|
|
30273
30792
|
}
|
|
30274
30793
|
}
|
|
30275
30794
|
updateContextFileStepId(filePath, stepId) {
|
|
30276
30795
|
try {
|
|
30277
|
-
const raw =
|
|
30796
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
30278
30797
|
const ctx = JSON.parse(raw);
|
|
30279
30798
|
ctx.step_id = stepId;
|
|
30280
|
-
|
|
30799
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30281
30800
|
} catch (err) {
|
|
30282
30801
|
this.opts.log?.warn(`failed to update context file step_id ${filePath}: ${String(err)}`);
|
|
30283
30802
|
}
|
|
30284
30803
|
}
|
|
30285
30804
|
updateContextFileSessionId(filePath, sessionId) {
|
|
30286
30805
|
try {
|
|
30287
|
-
const raw =
|
|
30806
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
30288
30807
|
const ctx = JSON.parse(raw);
|
|
30289
30808
|
ctx.session_id = sessionId;
|
|
30290
|
-
|
|
30809
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30291
30810
|
} catch (err) {
|
|
30292
30811
|
this.opts.log?.warn(`failed to update context file session_id ${filePath}: ${String(err)}`);
|
|
30293
30812
|
}
|
|
@@ -30295,8 +30814,8 @@ var ParallAgentGateway = class {
|
|
|
30295
30814
|
/** @deprecated Use writeContextFile / updateContextFileStepId. */
|
|
30296
30815
|
writeStepIdFile(filePath, stepId) {
|
|
30297
30816
|
try {
|
|
30298
|
-
|
|
30299
|
-
|
|
30817
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
30818
|
+
fs2.writeFileSync(filePath, stepId, "utf8");
|
|
30300
30819
|
} catch (err) {
|
|
30301
30820
|
this.opts.log?.warn(`failed to write step id file ${filePath}: ${String(err)}`);
|
|
30302
30821
|
}
|
|
@@ -30304,7 +30823,7 @@ var ParallAgentGateway = class {
|
|
|
30304
30823
|
/** @deprecated Use writeContextFile / updateContextFileStepId. */
|
|
30305
30824
|
clearStepIdFile(filePath) {
|
|
30306
30825
|
try {
|
|
30307
|
-
|
|
30826
|
+
fs2.writeFileSync(filePath, "", "utf8");
|
|
30308
30827
|
} catch {
|
|
30309
30828
|
}
|
|
30310
30829
|
}
|
|
@@ -30313,7 +30832,7 @@ var ParallAgentGateway = class {
|
|
|
30313
30832
|
await this.createInputStep(sessionId, event);
|
|
30314
30833
|
}
|
|
30315
30834
|
}
|
|
30316
|
-
async bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath) {
|
|
30835
|
+
async bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2) {
|
|
30317
30836
|
const runtimeLaneKey = runtimeEvent.runtimeLaneKey || sessionKey;
|
|
30318
30837
|
const existing = this.sessionBindings.get(sessionKey);
|
|
30319
30838
|
if (existing && existing.runtimeLaneKey === runtimeLaneKey && existing.runtimeSessionId === runtimeEvent.runtimeSessionId) {
|
|
@@ -30357,6 +30876,9 @@ var ParallAgentGateway = class {
|
|
|
30357
30876
|
if (contextFilePath) {
|
|
30358
30877
|
this.updateContextFileSessionId(contextFilePath, session.id);
|
|
30359
30878
|
}
|
|
30879
|
+
if (laneContextFilePath2) {
|
|
30880
|
+
this.updateContextFileSessionId(laneContextFilePath2, session.id);
|
|
30881
|
+
}
|
|
30360
30882
|
await this.opts.onSessionBinding?.(binding);
|
|
30361
30883
|
return binding;
|
|
30362
30884
|
}
|
|
@@ -30383,14 +30905,27 @@ var ParallAgentGateway = class {
|
|
|
30383
30905
|
const dispatchContext = this.buildDispatchContext(event, sessionKey);
|
|
30384
30906
|
const contextFilePath = dispatchContext.contextFilePath;
|
|
30385
30907
|
const stepIdFilePath = dispatchContext.stepIdFilePath;
|
|
30908
|
+
const activeLane = this.ledgerDisabled ? void 0 : this.laneLedger?.getForEvent(event);
|
|
30909
|
+
const laneContextFilePath2 = activeLane ? this.laneLedger?.laneContextPath(activeLane) : void 0;
|
|
30910
|
+
const contextBody = {
|
|
30911
|
+
session_id: dispatchContext.sessionId ?? null,
|
|
30912
|
+
chat_id: dispatchContext.chatId ?? null,
|
|
30913
|
+
trigger_message_id: dispatchContext.triggerMessageId ?? null,
|
|
30914
|
+
no_reply: dispatchContext.noReply,
|
|
30915
|
+
step_id: null,
|
|
30916
|
+
dispatch_event_id: activeLane?.typedDispatchEventId ?? activeLane?.folded.get(event.messageId) ?? null,
|
|
30917
|
+
lane: activeLane?.lane ?? null,
|
|
30918
|
+
target_uri: activeLane?.targetUri ?? null,
|
|
30919
|
+
thread_root_id: activeLane?.threadRootId ?? null,
|
|
30920
|
+
// Typed binding hint for the CLI: which task this dispatch is about
|
|
30921
|
+
// (parall task update attaches the typed effect only on a match).
|
|
30922
|
+
task_id: event.type === "task" ? event.targetId : null
|
|
30923
|
+
};
|
|
30386
30924
|
if (contextFilePath) {
|
|
30387
|
-
this.writeContextFile(contextFilePath,
|
|
30388
|
-
|
|
30389
|
-
|
|
30390
|
-
|
|
30391
|
-
no_reply: dispatchContext.noReply,
|
|
30392
|
-
step_id: null
|
|
30393
|
-
});
|
|
30925
|
+
this.writeContextFile(contextFilePath, contextBody);
|
|
30926
|
+
}
|
|
30927
|
+
if (laneContextFilePath2) {
|
|
30928
|
+
this.writeContextFile(laneContextFilePath2, contextBody);
|
|
30394
30929
|
}
|
|
30395
30930
|
this.inFlightDispatches++;
|
|
30396
30931
|
const deadlineTimer = this.DISPATCH_DEADLINE_MS > 0 ? setTimeout(() => {
|
|
@@ -30417,7 +30952,7 @@ var ParallAgentGateway = class {
|
|
|
30417
30952
|
})) {
|
|
30418
30953
|
if (runtimeEvent.type === "runtime_session") {
|
|
30419
30954
|
const priorAgentSessionId = binding?.agentSessionId;
|
|
30420
|
-
binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath);
|
|
30955
|
+
binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2);
|
|
30421
30956
|
if (event.targetType === "channel_conversation" && binding.agentSessionId !== priorAgentSessionId) {
|
|
30422
30957
|
try {
|
|
30423
30958
|
await this.opts.client.setChannelConversationSession(this.opts.config.org_id, event.targetId, binding.agentSessionId);
|
|
@@ -30449,6 +30984,9 @@ var ParallAgentGateway = class {
|
|
|
30449
30984
|
await this.createInputStep(binding.agentSessionId, event);
|
|
30450
30985
|
inputStepsCreated = true;
|
|
30451
30986
|
}
|
|
30987
|
+
if (activeLane && !this.ledgerDisabled) {
|
|
30988
|
+
this.laneLedger?.maybeRenew(activeLane);
|
|
30989
|
+
}
|
|
30452
30990
|
if (captureText && runtimeEvent.type === "text" && runtimeEvent.text) {
|
|
30453
30991
|
captureText.push(runtimeEvent.text);
|
|
30454
30992
|
}
|
|
@@ -30465,7 +31003,7 @@ var ParallAgentGateway = class {
|
|
|
30465
31003
|
} else if (runtimeEvent.type === "tool_result" && pendingSendCallIds.delete(runtimeEvent.callId)) {
|
|
30466
31004
|
recordMessageSend(sessionKey, !runtimeEvent.error);
|
|
30467
31005
|
}
|
|
30468
|
-
await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath);
|
|
31006
|
+
await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
30469
31007
|
}
|
|
30470
31008
|
if (!binding) {
|
|
30471
31009
|
binding = this.sessionBindings.get(sessionKey);
|
|
@@ -30487,7 +31025,7 @@ var ParallAgentGateway = class {
|
|
|
30487
31025
|
await this.createRuntimeStep(binding.agentSessionId, event, {
|
|
30488
31026
|
type: "error",
|
|
30489
31027
|
message: `Dispatch failed: ${String(err)}`
|
|
30490
|
-
}, stepIdFilePath, contextFilePath);
|
|
31028
|
+
}, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
30491
31029
|
} catch (stepErr) {
|
|
30492
31030
|
if (this.isSessionNotLiveError(stepErr))
|
|
30493
31031
|
staleDetected = true;
|
|
@@ -30530,6 +31068,9 @@ var ParallAgentGateway = class {
|
|
|
30530
31068
|
} else if (stepIdFilePath) {
|
|
30531
31069
|
this.clearStepIdFile(stepIdFilePath);
|
|
30532
31070
|
}
|
|
31071
|
+
if (laneContextFilePath2) {
|
|
31072
|
+
this.updateContextFileStepId(laneContextFilePath2, null);
|
|
31073
|
+
}
|
|
30533
31074
|
this.inFlightDispatches--;
|
|
30534
31075
|
if (this.inFlightDispatches === 0 && this.drainResolvers.length > 0) {
|
|
30535
31076
|
const resolvers = this.drainResolvers.splice(0);
|
|
@@ -30587,13 +31128,39 @@ var ParallAgentGateway = class {
|
|
|
30587
31128
|
item.resolve(false);
|
|
30588
31129
|
break;
|
|
30589
31130
|
}
|
|
30590
|
-
|
|
31131
|
+
let items;
|
|
31132
|
+
const head = fork.queue[0];
|
|
31133
|
+
if (head && this.usesLaneLedger(head.event)) {
|
|
31134
|
+
const headKey = this.dispatchGroupKey(head.event);
|
|
31135
|
+
const splitAt = fork.queue.findIndex((it) => this.dispatchGroupKey(it.event) !== headKey);
|
|
31136
|
+
items = splitAt === -1 ? fork.queue.splice(0) : fork.queue.splice(0, splitAt);
|
|
31137
|
+
} else {
|
|
31138
|
+
items = fork.queue.splice(0);
|
|
31139
|
+
}
|
|
30591
31140
|
const events = items.map((item) => item.event);
|
|
30592
31141
|
const last = events[events.length - 1];
|
|
30593
31142
|
const earlier = events.slice(0, -1);
|
|
30594
31143
|
try {
|
|
30595
31144
|
const batchText = [];
|
|
30596
|
-
|
|
31145
|
+
let dispatched;
|
|
31146
|
+
if (this.usesLaneLedger(last)) {
|
|
31147
|
+
const outcome = await this.dispatchLaneGroup({
|
|
31148
|
+
events,
|
|
31149
|
+
sessionKey: fork.fork.sessionKey,
|
|
31150
|
+
body: buildForkScopePrefix(last) + buildEventBody(last),
|
|
31151
|
+
earlier,
|
|
31152
|
+
captureText: batchText,
|
|
31153
|
+
hasMoreLocal: () => fork.queue.length > 0
|
|
31154
|
+
});
|
|
31155
|
+
if (outcome === "foreign") {
|
|
31156
|
+
for (const item of items)
|
|
31157
|
+
item.resolve(false);
|
|
31158
|
+
break;
|
|
31159
|
+
}
|
|
31160
|
+
dispatched = outcome === "dispatched";
|
|
31161
|
+
} else {
|
|
31162
|
+
dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildForkScopePrefix(last) + buildEventBody(last), earlier, batchText);
|
|
31163
|
+
}
|
|
30597
31164
|
if (!dispatched) {
|
|
30598
31165
|
for (const item of items) {
|
|
30599
31166
|
item.resolve(false);
|
|
@@ -30696,9 +31263,9 @@ var ParallAgentGateway = class {
|
|
|
30696
31263
|
this.opts.log?.info(`drainMainBuffer halted (shutting down) \u2014 ${this.dispatchState.mainBuffer.length} buffered, ${this.dispatchState.pendingForkResults.length} pending fork results left for catch-up`);
|
|
30697
31264
|
break;
|
|
30698
31265
|
}
|
|
30699
|
-
const
|
|
31266
|
+
const groupKey = this.dispatchGroupKey(this.dispatchState.mainBuffer[0]);
|
|
30700
31267
|
const events = [];
|
|
30701
|
-
while (this.dispatchState.mainBuffer[0]
|
|
31268
|
+
while (this.dispatchState.mainBuffer[0] && this.dispatchGroupKey(this.dispatchState.mainBuffer[0]) === groupKey) {
|
|
30702
31269
|
events.push(this.dispatchState.mainBuffer.shift());
|
|
30703
31270
|
}
|
|
30704
31271
|
const event = events[events.length - 1];
|
|
@@ -30707,7 +31274,36 @@ var ParallAgentGateway = class {
|
|
|
30707
31274
|
const pendingFork = hasPendingInjections ? [] : this.dispatchState.pendingForkResults.splice(0);
|
|
30708
31275
|
const forkPrefix = buildForkResultPrefix(pendingFork);
|
|
30709
31276
|
this.dispatchState.mainCurrentTargetId = event.targetId;
|
|
31277
|
+
this.mainCurrentGroupKey = groupKey;
|
|
30710
31278
|
this.dispatchState.mainPreDispatchBranchPoint = this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
|
|
31279
|
+
if (this.usesLaneLedger(event)) {
|
|
31280
|
+
let outcome;
|
|
31281
|
+
try {
|
|
31282
|
+
outcome = await this.dispatchLaneGroup({
|
|
31283
|
+
events,
|
|
31284
|
+
sessionKey: this.opts.runtimeKey,
|
|
31285
|
+
body: forkPrefix + buildEventBody(event),
|
|
31286
|
+
earlier,
|
|
31287
|
+
hasMoreLocal: () => this.dispatchState.mainBuffer.some((e) => this.dispatchGroupKey(e) === groupKey)
|
|
31288
|
+
});
|
|
31289
|
+
} catch (err) {
|
|
31290
|
+
this.opts.log?.error(`lane dispatch failed for ${event.messageId}: ${String(err)}`);
|
|
31291
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31292
|
+
for (const ev of events)
|
|
31293
|
+
this.dispatchedMessages.delete(ev.messageId);
|
|
31294
|
+
continue;
|
|
31295
|
+
}
|
|
31296
|
+
if (outcome === "shutdown") {
|
|
31297
|
+
this.dispatchState.mainBuffer.unshift(...events);
|
|
31298
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31299
|
+
break;
|
|
31300
|
+
}
|
|
31301
|
+
if (outcome === "foreign") {
|
|
31302
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31303
|
+
continue;
|
|
31304
|
+
}
|
|
31305
|
+
continue;
|
|
31306
|
+
}
|
|
30711
31307
|
try {
|
|
30712
31308
|
await this.emitDispatchReceived(event);
|
|
30713
31309
|
} catch (err) {
|
|
@@ -30736,6 +31332,7 @@ var ParallAgentGateway = class {
|
|
|
30736
31332
|
this.draining = false;
|
|
30737
31333
|
this.dispatchState.mainDispatching = false;
|
|
30738
31334
|
this.dispatchState.mainCurrentTargetId = void 0;
|
|
31335
|
+
this.mainCurrentGroupKey = void 0;
|
|
30739
31336
|
this.dispatchState.mainPreDispatchBranchPoint = void 0;
|
|
30740
31337
|
if (!this.shuttingDown && this.dispatchState.mainBuffer.length > 0) {
|
|
30741
31338
|
setTimeout(() => {
|
|
@@ -30755,13 +31352,38 @@ var ParallAgentGateway = class {
|
|
|
30755
31352
|
const forkPrefix = buildForkResultPrefix(pendingFork);
|
|
30756
31353
|
this.dispatchState.mainDispatching = true;
|
|
30757
31354
|
this.dispatchState.mainCurrentTargetId = event.targetId;
|
|
31355
|
+
this.mainCurrentGroupKey = this.dispatchGroupKey(event);
|
|
30758
31356
|
this.dispatchState.mainPreDispatchBranchPoint = this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
|
|
31357
|
+
if (this.usesLaneLedger(event)) {
|
|
31358
|
+
let outcome = "shutdown";
|
|
31359
|
+
try {
|
|
31360
|
+
try {
|
|
31361
|
+
outcome = await this.dispatchLaneGroup({
|
|
31362
|
+
events: [event],
|
|
31363
|
+
sessionKey: this.opts.runtimeKey,
|
|
31364
|
+
body: forkPrefix + buildEventBody(event),
|
|
31365
|
+
earlier: [],
|
|
31366
|
+
hasMoreLocal: () => this.dispatchState.mainBuffer.some((e) => this.dispatchGroupKey(e) === this.dispatchGroupKey(event))
|
|
31367
|
+
});
|
|
31368
|
+
} catch (err) {
|
|
31369
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31370
|
+
throw err;
|
|
31371
|
+
}
|
|
31372
|
+
if (outcome !== "dispatched") {
|
|
31373
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31374
|
+
}
|
|
31375
|
+
} finally {
|
|
31376
|
+
await this.drainMainBuffer();
|
|
31377
|
+
}
|
|
31378
|
+
return outcome === "dispatched";
|
|
31379
|
+
}
|
|
30759
31380
|
try {
|
|
30760
31381
|
await this.emitDispatchReceived(event);
|
|
30761
31382
|
} catch (err) {
|
|
30762
31383
|
this.opts.log?.warn?.(`mark-received failed, leaving unacked for retry: ${String(err)}`);
|
|
30763
31384
|
this.dispatchState.mainDispatching = false;
|
|
30764
31385
|
this.dispatchState.mainCurrentTargetId = void 0;
|
|
31386
|
+
this.mainCurrentGroupKey = void 0;
|
|
30765
31387
|
this.dispatchState.mainPreDispatchBranchPoint = void 0;
|
|
30766
31388
|
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
30767
31389
|
return false;
|
|
@@ -30782,7 +31404,11 @@ var ParallAgentGateway = class {
|
|
|
30782
31404
|
return false;
|
|
30783
31405
|
}
|
|
30784
31406
|
this.dispatchState.mainBuffer.push(event);
|
|
30785
|
-
if (this.
|
|
31407
|
+
if (this.usesLaneLedger(event)) {
|
|
31408
|
+
if (this.mainCurrentGroupKey === this.dispatchGroupKey(event) && await this.laneLedger?.steerLive(event) && await this.opts.dispatchAdapter.enqueueDuringDispatch?.(this.opts.runtimeKey, buildEventBody(event))) {
|
|
31409
|
+
this.opts.log?.info(`steer folded+injected for ${event.messageId} (will drain for bookkeeping)`);
|
|
31410
|
+
}
|
|
31411
|
+
} else if (this.dispatchState.mainCurrentTargetId === event.targetId && await this.opts.dispatchAdapter.enqueueDuringDispatch?.(this.opts.runtimeKey, buildEventBody(event))) {
|
|
30786
31412
|
this.opts.log?.info(`steer injected for ${event.messageId} (will drain for bookkeeping)`);
|
|
30787
31413
|
}
|
|
30788
31414
|
if (!this.dispatchState.mainDispatching && !this.draining && this.dispatchState.mainBuffer.length > 0) {
|
|
@@ -30953,8 +31579,10 @@ var ParallAgentGateway = class {
|
|
|
30953
31579
|
try {
|
|
30954
31580
|
const dispatched = await this.handleInboundEvent(event);
|
|
30955
31581
|
if (dispatched) {
|
|
30956
|
-
|
|
30957
|
-
|
|
31582
|
+
if (!this.usesLaneLedger(event)) {
|
|
31583
|
+
this.opts.client.ackDispatch(this.opts.config.org_id, { source_type: "message", source_id: data.id }).catch(() => {
|
|
31584
|
+
});
|
|
31585
|
+
}
|
|
30958
31586
|
} else {
|
|
30959
31587
|
this.dispatchedMessages.delete(data.id);
|
|
30960
31588
|
}
|
|
@@ -30963,7 +31591,21 @@ var ParallAgentGateway = class {
|
|
|
30963
31591
|
this.dispatchedMessages.delete(data.id);
|
|
30964
31592
|
}
|
|
30965
31593
|
}
|
|
30966
|
-
|
|
31594
|
+
// Ledger re-drive consumption: dispatch.new message hints re-enter the
|
|
31595
|
+
// shared WorkItem consumption path (same protocol as catch-up).
|
|
31596
|
+
async handleMessageRedrive(item) {
|
|
31597
|
+
if (!item.chat_id || !item.source_id)
|
|
31598
|
+
return;
|
|
31599
|
+
await this.consumeMessageWorkItem({
|
|
31600
|
+
id: item.id,
|
|
31601
|
+
source_id: item.source_id,
|
|
31602
|
+
chat_id: item.chat_id
|
|
31603
|
+
});
|
|
31604
|
+
}
|
|
31605
|
+
consumeMessageWorkItem(item) {
|
|
31606
|
+
return consumeMessageWorkItem(this.laneFlowHost(), item);
|
|
31607
|
+
}
|
|
31608
|
+
async handleTaskAssignment(task, ackSourceId, dispatchEventId) {
|
|
30967
31609
|
if (this.shuttingDown)
|
|
30968
31610
|
return false;
|
|
30969
31611
|
const dedupeKey = `${task.id}:${task.updated_at}`;
|
|
@@ -30992,7 +31634,8 @@ var ParallAgentGateway = class {
|
|
|
30992
31634
|
body: parts.join("\n"),
|
|
30993
31635
|
sentAt: task.updated_at ?? task.created_at,
|
|
30994
31636
|
ackSourceType: "task_activity",
|
|
30995
|
-
ackSourceId
|
|
31637
|
+
ackSourceId,
|
|
31638
|
+
dispatchEventId
|
|
30996
31639
|
};
|
|
30997
31640
|
const dispatched = await this.handleInboundEvent(event);
|
|
30998
31641
|
if (!dispatched) {
|
|
@@ -31016,7 +31659,7 @@ var ParallAgentGateway = class {
|
|
|
31016
31659
|
this.opts.log?.info(`skipping stale task dispatch ${ackSourceId ?? taskId} \u2014 assigned to ${task.assignee_id}, creator ${task.creator_id}`);
|
|
31017
31660
|
return true;
|
|
31018
31661
|
}
|
|
31019
|
-
return this.handleTaskAssignment(task, ackSourceId);
|
|
31662
|
+
return this.handleTaskAssignment(task, ackSourceId, opts.dispatchEventId);
|
|
31020
31663
|
}
|
|
31021
31664
|
async handleTaskComment(commentId, taskId, actorId, deliveryReason) {
|
|
31022
31665
|
if (this.shuttingDown)
|
|
@@ -31424,74 +32067,46 @@ var ParallAgentGateway = class {
|
|
|
31424
32067
|
}
|
|
31425
32068
|
processed++;
|
|
31426
32069
|
try {
|
|
31427
|
-
|
|
32070
|
+
const ackItem = () => {
|
|
32071
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
32072
|
+
});
|
|
32073
|
+
};
|
|
31428
32074
|
if (item.event_type === "task_assign" && item.task_id) {
|
|
31429
32075
|
try {
|
|
31430
|
-
|
|
32076
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
|
|
32077
|
+
dispatchEventId
|
|
32078
|
+
}), ackItem);
|
|
31431
32079
|
} catch (err) {
|
|
31432
32080
|
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
31433
32081
|
continue;
|
|
31434
32082
|
}
|
|
31435
32083
|
} else if (item.event_type === "task_update" && item.task_id) {
|
|
31436
32084
|
try {
|
|
31437
|
-
|
|
32085
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
|
|
32086
|
+
allowCreator: true,
|
|
32087
|
+
dispatchEventId
|
|
32088
|
+
}), ackItem);
|
|
31438
32089
|
} catch (err) {
|
|
31439
32090
|
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
31440
32091
|
continue;
|
|
31441
32092
|
}
|
|
31442
32093
|
} else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
|
|
31443
|
-
|
|
32094
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleTaskComment(item.source_id, item.task_id ?? "", item.actor_id, item.delivery_reason), ackItem);
|
|
31444
32095
|
} else if (item.event_type === "wiki_comment" && item.source_id) {
|
|
31445
|
-
|
|
32096
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleWikiComment(item.source_id, item.actor_id, item.delivery_reason), ackItem);
|
|
31446
32097
|
} else if (item.event_type === "schedule.fire" && item.source_id) {
|
|
31447
|
-
|
|
32098
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleScheduleFire(item.source_id, item.actor_id), ackItem);
|
|
31448
32099
|
} else if (item.event_type === "external_trigger" && item.source_id) {
|
|
31449
|
-
|
|
32100
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleExternalTriggerRun(item.source_id), ackItem);
|
|
31450
32101
|
} else if (item.event_type === "channel_message" && item.source_id) {
|
|
31451
|
-
|
|
32102
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleChannelMessage(item.source_id), ackItem);
|
|
31452
32103
|
} else if (item.event_type === "approval_decided" && item.source_id) {
|
|
31453
|
-
|
|
32104
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null), ackItem);
|
|
31454
32105
|
} else if (item.event_type === "message" && item.source_id && item.chat_id) {
|
|
31455
|
-
|
|
31456
|
-
|
|
31457
|
-
|
|
31458
|
-
|
|
31459
|
-
try {
|
|
31460
|
-
msg = await this.opts.client.getMessage(item.source_id);
|
|
31461
|
-
} catch (err) {
|
|
31462
|
-
const status = err?.status;
|
|
31463
|
-
if (status === 404) {
|
|
31464
|
-
msg = null;
|
|
31465
|
-
} else {
|
|
31466
|
-
msgFetchFailed = true;
|
|
31467
|
-
this.opts.log?.warn(`catch-up message fetch failed for ${item.source_id}, leaving pending: ${String(err)}`);
|
|
31468
|
-
}
|
|
31469
|
-
}
|
|
31470
|
-
if (msgFetchFailed) {
|
|
31471
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31472
|
-
continue;
|
|
31473
|
-
}
|
|
31474
|
-
if (!msg || msg.sender_id === this.opts.agentUserId) {
|
|
31475
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31476
|
-
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
31477
|
-
});
|
|
31478
|
-
continue;
|
|
31479
|
-
}
|
|
31480
|
-
const decision = await this.buildMessageDispatchDecision(item.chat_id, msg);
|
|
31481
|
-
if (decision.action === "retry") {
|
|
31482
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31483
|
-
continue;
|
|
31484
|
-
}
|
|
31485
|
-
if (decision.action === "skip") {
|
|
31486
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31487
|
-
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
31488
|
-
});
|
|
31489
|
-
continue;
|
|
31490
|
-
}
|
|
31491
|
-
dispatched = await this.handleInboundEvent(decision.event);
|
|
31492
|
-
}
|
|
31493
|
-
if (dispatched) {
|
|
31494
|
-
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
32106
|
+
await this.consumeMessageWorkItem({
|
|
32107
|
+
id: item.id,
|
|
32108
|
+
source_id: item.source_id,
|
|
32109
|
+
chat_id: item.chat_id
|
|
31495
32110
|
});
|
|
31496
32111
|
}
|
|
31497
32112
|
} catch (err) {
|
|
@@ -31557,6 +32172,14 @@ ${fullSummary}` : fullSummary;
|
|
|
31557
32172
|
this.abortFork(targetId, "ws reconnect");
|
|
31558
32173
|
}
|
|
31559
32174
|
}
|
|
32175
|
+
if (this.laneLedger && this.inFlightDispatches === 0 && this.laneLedger.activeCount > 0) {
|
|
32176
|
+
log2?.info(`releasing ${this.laneLedger.activeCount} stale lane(s) on reconnect`);
|
|
32177
|
+
await this.laneLedger.releaseAll();
|
|
32178
|
+
}
|
|
32179
|
+
if (this.laneLedger && this.ledgerDisabled) {
|
|
32180
|
+
log2?.info("re-probing dispatch ledger after reconnect (was disabled)");
|
|
32181
|
+
this.ledgerDisabled = false;
|
|
32182
|
+
}
|
|
31560
32183
|
const intervalSec = data.heartbeat_interval > 0 ? data.heartbeat_interval : 30;
|
|
31561
32184
|
try {
|
|
31562
32185
|
const count = await fetchAllChats(client, config.org_id, this.chatInfoMap);
|
|
@@ -31627,6 +32250,10 @@ ${fullSummary}` : fullSummary;
|
|
|
31627
32250
|
}
|
|
31628
32251
|
if (this.heartbeatTimer)
|
|
31629
32252
|
clearInterval(this.heartbeatTimer);
|
|
32253
|
+
if (this.laneLedger && this.laneLedger.activeCount > 0) {
|
|
32254
|
+
this.opts.log?.info(`releasing ${this.laneLedger.activeCount} lane(s) on shutdown`);
|
|
32255
|
+
await this.laneLedger.releaseAll();
|
|
32256
|
+
}
|
|
31630
32257
|
await this.opts.onBeforeDisconnect?.();
|
|
31631
32258
|
this.opts.ws.disconnect();
|
|
31632
32259
|
this.opts.log?.info(`disconnected`);
|
|
@@ -31634,8 +32261,8 @@ ${fullSummary}` : fullSummary;
|
|
|
31634
32261
|
};
|
|
31635
32262
|
|
|
31636
32263
|
// ts/agent-core/dist/platform-config.js
|
|
31637
|
-
import * as
|
|
31638
|
-
import * as
|
|
32264
|
+
import * as fs3 from "node:fs";
|
|
32265
|
+
import * as path3 from "node:path";
|
|
31639
32266
|
function isPlatformManagedProfile(profile) {
|
|
31640
32267
|
return profile?.machine_id != null || profile?.model_management === "platform";
|
|
31641
32268
|
}
|
|
@@ -31652,11 +32279,11 @@ function deriveModelIsPin(defaults, profile) {
|
|
|
31652
32279
|
var CACHE_FILENAME = "parall-platform-config.json";
|
|
31653
32280
|
var SUPPORTED_SCHEMA_VERSION = 1;
|
|
31654
32281
|
function cachePath(stateDir) {
|
|
31655
|
-
return
|
|
32282
|
+
return path3.join(stateDir, CACHE_FILENAME);
|
|
31656
32283
|
}
|
|
31657
32284
|
function loadCache(stateDir) {
|
|
31658
32285
|
try {
|
|
31659
|
-
const raw =
|
|
32286
|
+
const raw = fs3.readFileSync(cachePath(stateDir), "utf-8");
|
|
31660
32287
|
return JSON.parse(raw);
|
|
31661
32288
|
} catch {
|
|
31662
32289
|
return null;
|
|
@@ -31671,9 +32298,9 @@ function saveCache(stateDir, response) {
|
|
|
31671
32298
|
};
|
|
31672
32299
|
const filePath = cachePath(stateDir);
|
|
31673
32300
|
const tmpPath = `${filePath}.tmp`;
|
|
31674
|
-
|
|
31675
|
-
|
|
31676
|
-
|
|
32301
|
+
fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
32302
|
+
fs3.writeFileSync(tmpPath, JSON.stringify(cached, null, 2), "utf-8");
|
|
32303
|
+
fs3.renameSync(tmpPath, filePath);
|
|
31677
32304
|
}
|
|
31678
32305
|
function runtimeModelName(canonicalModel, runtimeType, config) {
|
|
31679
32306
|
const runtime = runtimeType?.trim();
|
|
@@ -31764,8 +32391,8 @@ function createPlatformConfigManager(opts) {
|
|
|
31764
32391
|
}
|
|
31765
32392
|
|
|
31766
32393
|
// ts/agent-core/dist/skills/index.js
|
|
31767
|
-
import * as
|
|
31768
|
-
import * as
|
|
32394
|
+
import * as fs4 from "node:fs";
|
|
32395
|
+
import * as path4 from "node:path";
|
|
31769
32396
|
|
|
31770
32397
|
// ts/agent-core/dist/skills/parall-platform.js
|
|
31771
32398
|
var PARALL_PLATFORM_SKILL = `# Parall Platform
|
|
@@ -32538,13 +33165,13 @@ var SKILLS = [
|
|
|
32538
33165
|
}
|
|
32539
33166
|
];
|
|
32540
33167
|
function writeSkillFiles(targetDir) {
|
|
32541
|
-
|
|
33168
|
+
fs4.mkdirSync(targetDir, { recursive: true });
|
|
32542
33169
|
for (const skill of SKILLS) {
|
|
32543
|
-
|
|
33170
|
+
fs4.writeFileSync(path4.join(targetDir, `${skill.name}.md`), skill.content, "utf8");
|
|
32544
33171
|
}
|
|
32545
33172
|
}
|
|
32546
33173
|
function buildSkillReferences(workspaceDir) {
|
|
32547
|
-
const dir =
|
|
33174
|
+
const dir = path4.join(workspaceDir, ".parall", "skills");
|
|
32548
33175
|
const lines = SKILLS.map((s) => `- ${s.description.split(":")[0]}: \`${dir}/${s.name}.md\``);
|
|
32549
33176
|
return `## Platform Skills (read on demand)
|
|
32550
33177
|
|
|
@@ -32554,7 +33181,7 @@ ${lines.join("\n")}
|
|
|
32554
33181
|
|
|
32555
33182
|
// ts/claude-agent/dist/config.js
|
|
32556
33183
|
import * as os2 from "node:os";
|
|
32557
|
-
import * as
|
|
33184
|
+
import * as path5 from "node:path";
|
|
32558
33185
|
function requireEnv(env, name) {
|
|
32559
33186
|
const value = env[name]?.trim();
|
|
32560
33187
|
if (!value) {
|
|
@@ -32568,15 +33195,15 @@ function parseList(value) {
|
|
|
32568
33195
|
return value.split(/[,\n]/).map((item) => item.trim()).filter(Boolean);
|
|
32569
33196
|
}
|
|
32570
33197
|
function resolvePath(value) {
|
|
32571
|
-
return
|
|
33198
|
+
return path5.isAbsolute(value) ? value : path5.resolve(process.cwd(), value);
|
|
32572
33199
|
}
|
|
32573
33200
|
function resolveClaudeAgentConfig(env = process.env) {
|
|
32574
33201
|
const apiUrl = requireEnv(env, "PRLL_API_URL");
|
|
32575
33202
|
const apiKey = requireEnv(env, "PRLL_API_KEY");
|
|
32576
33203
|
const orgId = requireEnv(env, "PRLL_ORG_ID");
|
|
32577
33204
|
const claudeHome = resolvePath(env.PRLL_CLAUDE_HOME?.trim() || env.HOME || os2.homedir());
|
|
32578
|
-
const stateDir = resolvePath(env.PRLL_STATE_DIR?.trim() ||
|
|
32579
|
-
const workspaceDir = resolvePath(env.PRLL_WORKSPACE_DIR?.trim() ||
|
|
33205
|
+
const stateDir = resolvePath(env.PRLL_STATE_DIR?.trim() || path5.join(claudeHome, ".parall-agent"));
|
|
33206
|
+
const workspaceDir = resolvePath(env.PRLL_WORKSPACE_DIR?.trim() || path5.join(stateDir, "workspace"));
|
|
32580
33207
|
const additionalDirs = parseList(env.PRLL_CLAUDE_ADD_DIRS).map(resolvePath);
|
|
32581
33208
|
return {
|
|
32582
33209
|
apiUrl,
|
|
@@ -32611,20 +33238,23 @@ function buildClaudeRuntimeKey(agentUserId) {
|
|
|
32611
33238
|
}
|
|
32612
33239
|
function sessionStateFilePathForRuntime(stateDir, runtimeKey) {
|
|
32613
33240
|
const fileName = Buffer.from(runtimeKey).toString("base64url");
|
|
32614
|
-
return
|
|
33241
|
+
return path5.join(stateDir, "sessions", `${fileName}.json`);
|
|
32615
33242
|
}
|
|
32616
33243
|
function contextFilePathForSession(stateDir, sessionKey) {
|
|
32617
33244
|
const fileName = Buffer.from(sessionKey).toString("base64url");
|
|
32618
|
-
return
|
|
33245
|
+
return path5.join(stateDir, "dispatch-context", `${fileName}.json`);
|
|
33246
|
+
}
|
|
33247
|
+
function dispatchContextDirPath(stateDir) {
|
|
33248
|
+
return dispatchLaneContextDir(stateDir);
|
|
32619
33249
|
}
|
|
32620
33250
|
function stepIdFilePathForSession(stateDir, sessionKey) {
|
|
32621
33251
|
const fileName = Buffer.from(sessionKey).toString("base64url");
|
|
32622
|
-
return
|
|
33252
|
+
return path5.join(stateDir, "step-ids", `${fileName}.txt`);
|
|
32623
33253
|
}
|
|
32624
33254
|
|
|
32625
33255
|
// ts/claude-agent/dist/dispatch.js
|
|
32626
|
-
import * as
|
|
32627
|
-
import * as
|
|
33256
|
+
import * as fs6 from "node:fs";
|
|
33257
|
+
import * as path7 from "node:path";
|
|
32628
33258
|
import { randomUUID } from "node:crypto";
|
|
32629
33259
|
import { execSync as execSync2, spawn } from "node:child_process";
|
|
32630
33260
|
|
|
@@ -32632,8 +33262,8 @@ import { execSync as execSync2, spawn } from "node:child_process";
|
|
|
32632
33262
|
import { execSync } from "node:child_process";
|
|
32633
33263
|
import { constants } from "node:fs";
|
|
32634
33264
|
import * as fsSync from "node:fs";
|
|
32635
|
-
import * as
|
|
32636
|
-
import * as
|
|
33265
|
+
import * as fs5 from "node:fs/promises";
|
|
33266
|
+
import * as path6 from "node:path";
|
|
32637
33267
|
var DEFAULT_MAX_TOTAL_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
32638
33268
|
var DEFAULT_ATTACHMENT_CACHE_MAX_BYTES = 512 * 1024 * 1024;
|
|
32639
33269
|
var DEFAULT_ATTACHMENT_DOWNLOAD_TIMEOUT_MS = 3e4;
|
|
@@ -32663,11 +33293,11 @@ async function prepareLocalImageAttachments(event, context2, opts) {
|
|
|
32663
33293
|
};
|
|
32664
33294
|
}
|
|
32665
33295
|
const rootDir = await ensureAttachmentRootDir(opts.workspaceDir);
|
|
32666
|
-
const messageDir =
|
|
33296
|
+
const messageDir = path6.join(rootDir, sanitizePathSegment(event.messageId));
|
|
32667
33297
|
await ensurePathIsNotSymlink(messageDir);
|
|
32668
|
-
await
|
|
33298
|
+
await fs5.mkdir(messageDir, { recursive: true });
|
|
32669
33299
|
await ensurePathIsNotSymlink(messageDir);
|
|
32670
|
-
const activeMessageDir =
|
|
33300
|
+
const activeMessageDir = path6.resolve(messageDir);
|
|
32671
33301
|
activeAttachmentDirs.add(activeMessageDir);
|
|
32672
33302
|
const maintenanceCooldownMs = opts.maintenanceCooldownMs ?? DEFAULT_MAINTENANCE_COOLDOWN_MS;
|
|
32673
33303
|
const maintenancePromise = scheduleAttachmentMaintenance(rootDir, {
|
|
@@ -32684,7 +33314,7 @@ async function prepareLocalImageAttachments(event, context2, opts) {
|
|
|
32684
33314
|
const notes = [];
|
|
32685
33315
|
let downloadedBytes = 0;
|
|
32686
33316
|
for (const att of imageAttachments) {
|
|
32687
|
-
const localPath =
|
|
33317
|
+
const localPath = path6.join(messageDir, localFileName(att.id, att.fileName, att.mimeType));
|
|
32688
33318
|
const downloadTimeoutMs = opts.downloadTimeoutMs ?? DEFAULT_ATTACHMENT_DOWNLOAD_TIMEOUT_MS;
|
|
32689
33319
|
const fetchFresh = async () => {
|
|
32690
33320
|
const fileInfo = await withTimeout(context2.client.getFileUrl(att.id), downloadTimeoutMs, `file URL lookup timed out after ${downloadTimeoutMs}ms`);
|
|
@@ -32741,7 +33371,7 @@ async function appendPreparedLocalAttachmentRefs(body, event, context2, opts) {
|
|
|
32741
33371
|
return { body: appendLocalAttachmentRefs(body, attachments), attachments };
|
|
32742
33372
|
}
|
|
32743
33373
|
function pinLocalAttachmentPaths(images) {
|
|
32744
|
-
const dirs = new Set(images.map((image) =>
|
|
33374
|
+
const dirs = new Set(images.map((image) => path6.resolve(path6.dirname(image.localPath))));
|
|
32745
33375
|
for (const dir of dirs) {
|
|
32746
33376
|
activeAttachmentDirs.add(dir);
|
|
32747
33377
|
}
|
|
@@ -32756,7 +33386,7 @@ function pinLocalAttachmentPaths(images) {
|
|
|
32756
33386
|
};
|
|
32757
33387
|
}
|
|
32758
33388
|
function attachmentRootDir(workspaceDir) {
|
|
32759
|
-
return
|
|
33389
|
+
return path6.join(path6.resolve(workspaceDir), ".parall", "attachments");
|
|
32760
33390
|
}
|
|
32761
33391
|
function ensureLocalAttachmentGitExclude(workingDirectory) {
|
|
32762
33392
|
try {
|
|
@@ -32765,8 +33395,8 @@ function ensureLocalAttachmentGitExclude(workingDirectory) {
|
|
|
32765
33395
|
encoding: "utf8",
|
|
32766
33396
|
stdio: ["ignore", "pipe", "ignore"]
|
|
32767
33397
|
}).trim();
|
|
32768
|
-
const excludePath =
|
|
32769
|
-
fsSync.mkdirSync(
|
|
33398
|
+
const excludePath = path6.isAbsolute(rel) ? rel : path6.join(workingDirectory, rel);
|
|
33399
|
+
fsSync.mkdirSync(path6.dirname(excludePath), { recursive: true });
|
|
32770
33400
|
const existing = fsSync.existsSync(excludePath) ? fsSync.readFileSync(excludePath, "utf8") : "";
|
|
32771
33401
|
if (existing.split(/\r?\n/).some((line) => line.trim() === ".parall/"))
|
|
32772
33402
|
return;
|
|
@@ -32802,18 +33432,18 @@ function scheduleAttachmentMaintenance(rootDir, opts) {
|
|
|
32802
33432
|
return run;
|
|
32803
33433
|
}
|
|
32804
33434
|
async function ensureAttachmentRootDir(workspaceDir) {
|
|
32805
|
-
const workspaceRoot =
|
|
32806
|
-
const parallDir =
|
|
33435
|
+
const workspaceRoot = path6.resolve(workspaceDir);
|
|
33436
|
+
const parallDir = path6.join(workspaceRoot, ".parall");
|
|
32807
33437
|
const rootDir = attachmentRootDir(workspaceRoot);
|
|
32808
|
-
await
|
|
33438
|
+
await fs5.mkdir(workspaceRoot, { recursive: true });
|
|
32809
33439
|
await ensurePathIsNotSymlink(parallDir);
|
|
32810
|
-
await
|
|
33440
|
+
await fs5.mkdir(parallDir, { recursive: true, mode: 448 });
|
|
32811
33441
|
await ensurePathIsNotSymlink(parallDir);
|
|
32812
33442
|
await ensurePathIsNotSymlink(rootDir);
|
|
32813
|
-
await
|
|
33443
|
+
await fs5.mkdir(rootDir, { recursive: true, mode: 448 });
|
|
32814
33444
|
await ensurePathIsNotSymlink(rootDir);
|
|
32815
|
-
const realWorkspace = await
|
|
32816
|
-
const realRoot = await
|
|
33445
|
+
const realWorkspace = await fs5.realpath(workspaceRoot);
|
|
33446
|
+
const realRoot = await fs5.realpath(rootDir);
|
|
32817
33447
|
if (!isPathInside(realRoot, realWorkspace)) {
|
|
32818
33448
|
throw new Error(`attachment root escapes workspace: ${rootDir}`);
|
|
32819
33449
|
}
|
|
@@ -32821,7 +33451,7 @@ async function ensureAttachmentRootDir(workspaceDir) {
|
|
|
32821
33451
|
}
|
|
32822
33452
|
async function ensurePathIsNotSymlink(filePath) {
|
|
32823
33453
|
try {
|
|
32824
|
-
const stat = await
|
|
33454
|
+
const stat = await fs5.lstat(filePath);
|
|
32825
33455
|
if (stat.isSymbolicLink()) {
|
|
32826
33456
|
throw new Error(`refusing to use symlinked attachment path ${filePath}`);
|
|
32827
33457
|
}
|
|
@@ -32832,8 +33462,8 @@ async function ensurePathIsNotSymlink(filePath) {
|
|
|
32832
33462
|
}
|
|
32833
33463
|
}
|
|
32834
33464
|
function isPathInside(childPath, parentPath) {
|
|
32835
|
-
const rel =
|
|
32836
|
-
return rel === "" || !!rel && !rel.startsWith("..") && !
|
|
33465
|
+
const rel = path6.relative(parentPath, childPath);
|
|
33466
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !path6.isAbsolute(rel);
|
|
32837
33467
|
}
|
|
32838
33468
|
async function existingUsableFile(filePath, expectedSize, rootDir) {
|
|
32839
33469
|
try {
|
|
@@ -32844,30 +33474,30 @@ async function existingUsableFile(filePath, expectedSize, rootDir) {
|
|
|
32844
33474
|
}
|
|
32845
33475
|
}
|
|
32846
33476
|
async function localFileStatInsideRoot(filePath, rootDir) {
|
|
32847
|
-
const stat = await
|
|
33477
|
+
const stat = await fs5.lstat(filePath);
|
|
32848
33478
|
if (stat.isSymbolicLink()) {
|
|
32849
33479
|
throw new Error(`refusing to use symlinked attachment file ${filePath}`);
|
|
32850
33480
|
}
|
|
32851
33481
|
if (!stat.isFile()) {
|
|
32852
33482
|
throw new Error(`attachment path is not a file ${filePath}`);
|
|
32853
33483
|
}
|
|
32854
|
-
const realRoot = await
|
|
32855
|
-
const realFile = await
|
|
33484
|
+
const realRoot = await fs5.realpath(rootDir);
|
|
33485
|
+
const realFile = await fs5.realpath(filePath);
|
|
32856
33486
|
if (!isPathInside(realFile, realRoot)) {
|
|
32857
33487
|
throw new Error(`attachment file escapes workspace: ${filePath}`);
|
|
32858
33488
|
}
|
|
32859
33489
|
return stat;
|
|
32860
33490
|
}
|
|
32861
33491
|
async function localDirectoryStatInsideRoot(dirPath, rootDir) {
|
|
32862
|
-
const stat = await
|
|
33492
|
+
const stat = await fs5.lstat(dirPath);
|
|
32863
33493
|
if (stat.isSymbolicLink()) {
|
|
32864
33494
|
throw new Error(`refusing to use symlinked attachment directory ${dirPath}`);
|
|
32865
33495
|
}
|
|
32866
33496
|
if (!stat.isDirectory()) {
|
|
32867
33497
|
throw new Error(`attachment path is not a directory ${dirPath}`);
|
|
32868
33498
|
}
|
|
32869
|
-
const realRoot = await
|
|
32870
|
-
const realDir = await
|
|
33499
|
+
const realRoot = await fs5.realpath(rootDir);
|
|
33500
|
+
const realDir = await fs5.realpath(dirPath);
|
|
32871
33501
|
if (!isPathInside(realDir, realRoot)) {
|
|
32872
33502
|
throw new Error(`attachment directory escapes workspace: ${dirPath}`);
|
|
32873
33503
|
}
|
|
@@ -32875,7 +33505,7 @@ async function localDirectoryStatInsideRoot(dirPath, rootDir) {
|
|
|
32875
33505
|
}
|
|
32876
33506
|
async function openLocalFileInsideRoot(filePath, rootDir) {
|
|
32877
33507
|
const checkedStat = await localFileStatInsideRoot(filePath, rootDir);
|
|
32878
|
-
const file = await
|
|
33508
|
+
const file = await fs5.open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
32879
33509
|
let keepOpen = false;
|
|
32880
33510
|
try {
|
|
32881
33511
|
const openedStat = await file.stat();
|
|
@@ -32891,8 +33521,8 @@ async function openLocalFileInsideRoot(filePath, rootDir) {
|
|
|
32891
33521
|
}
|
|
32892
33522
|
}
|
|
32893
33523
|
async function openLocalTempFileInsideRoot(filePath, rootDir) {
|
|
32894
|
-
await localDirectoryStatInsideRoot(
|
|
32895
|
-
const file = await
|
|
33524
|
+
await localDirectoryStatInsideRoot(path6.dirname(filePath), rootDir);
|
|
33525
|
+
const file = await fs5.open(filePath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
|
|
32896
33526
|
let keepOpen = false;
|
|
32897
33527
|
try {
|
|
32898
33528
|
const checkedStat = await localFileStatInsideRoot(filePath, rootDir);
|
|
@@ -32917,7 +33547,7 @@ async function assertLocalFileIdentity(filePath, rootDir, expected) {
|
|
|
32917
33547
|
async function removeLocalFileIfInside(filePath, rootDir) {
|
|
32918
33548
|
try {
|
|
32919
33549
|
await localFileStatInsideRoot(filePath, rootDir);
|
|
32920
|
-
await
|
|
33550
|
+
await fs5.rm(filePath, { force: true });
|
|
32921
33551
|
} catch {
|
|
32922
33552
|
}
|
|
32923
33553
|
}
|
|
@@ -32930,7 +33560,7 @@ function sameFile(a, b) {
|
|
|
32930
33560
|
async function cleanupOldAttachmentFiles(rootDir, ttlMs, log2, preserveDirs) {
|
|
32931
33561
|
let entries;
|
|
32932
33562
|
try {
|
|
32933
|
-
entries = await
|
|
33563
|
+
entries = await fs5.readdir(rootDir, { withFileTypes: true });
|
|
32934
33564
|
} catch {
|
|
32935
33565
|
return;
|
|
32936
33566
|
}
|
|
@@ -32938,15 +33568,15 @@ async function cleanupOldAttachmentFiles(rootDir, ttlMs, log2, preserveDirs) {
|
|
|
32938
33568
|
await Promise.all(entries.map(async (entry) => {
|
|
32939
33569
|
if (!entry.isDirectory())
|
|
32940
33570
|
return;
|
|
32941
|
-
const fullPath =
|
|
33571
|
+
const fullPath = path6.join(rootDir, entry.name);
|
|
32942
33572
|
try {
|
|
32943
|
-
if (preserveDirs?.has(
|
|
33573
|
+
if (preserveDirs?.has(path6.resolve(fullPath)))
|
|
32944
33574
|
return;
|
|
32945
|
-
const stat = await
|
|
33575
|
+
const stat = await fs5.lstat(fullPath);
|
|
32946
33576
|
if (!stat.isDirectory())
|
|
32947
33577
|
return;
|
|
32948
33578
|
if (stat.mtimeMs < cutoff) {
|
|
32949
|
-
await
|
|
33579
|
+
await fs5.rm(fullPath, { recursive: true, force: true });
|
|
32950
33580
|
}
|
|
32951
33581
|
} catch (err) {
|
|
32952
33582
|
log2?.warn?.(`agent-core: failed to clean attachment temp dir ${fullPath}: ${String(err)}`);
|
|
@@ -32958,7 +33588,7 @@ async function pruneAttachmentCache(rootDir, maxBytes, log2, preserveDirs) {
|
|
|
32958
33588
|
return;
|
|
32959
33589
|
let entries;
|
|
32960
33590
|
try {
|
|
32961
|
-
entries = await
|
|
33591
|
+
entries = await fs5.readdir(rootDir, { withFileTypes: true });
|
|
32962
33592
|
} catch {
|
|
32963
33593
|
return;
|
|
32964
33594
|
}
|
|
@@ -32967,9 +33597,9 @@ async function pruneAttachmentCache(rootDir, maxBytes, log2, preserveDirs) {
|
|
|
32967
33597
|
for (const entry of entries) {
|
|
32968
33598
|
if (!entry.isDirectory())
|
|
32969
33599
|
continue;
|
|
32970
|
-
const fullPath =
|
|
33600
|
+
const fullPath = path6.join(rootDir, entry.name);
|
|
32971
33601
|
try {
|
|
32972
|
-
const stat = await
|
|
33602
|
+
const stat = await fs5.lstat(fullPath);
|
|
32973
33603
|
if (!stat.isDirectory())
|
|
32974
33604
|
continue;
|
|
32975
33605
|
const size = await directorySize(fullPath);
|
|
@@ -32985,10 +33615,10 @@ async function pruneAttachmentCache(rootDir, maxBytes, log2, preserveDirs) {
|
|
|
32985
33615
|
for (const dir of dirs) {
|
|
32986
33616
|
if (total <= maxBytes)
|
|
32987
33617
|
break;
|
|
32988
|
-
if (preserveDirs?.has(
|
|
33618
|
+
if (preserveDirs?.has(path6.resolve(dir.path)))
|
|
32989
33619
|
continue;
|
|
32990
33620
|
try {
|
|
32991
|
-
await
|
|
33621
|
+
await fs5.rm(dir.path, { recursive: true, force: true });
|
|
32992
33622
|
total -= dir.size;
|
|
32993
33623
|
} catch (err) {
|
|
32994
33624
|
log2?.warn?.(`agent-core: failed to prune attachment cache dir ${dir.path}: ${String(err)}`);
|
|
@@ -32997,12 +33627,12 @@ async function pruneAttachmentCache(rootDir, maxBytes, log2, preserveDirs) {
|
|
|
32997
33627
|
}
|
|
32998
33628
|
async function directorySize(dirPath) {
|
|
32999
33629
|
let total = 0;
|
|
33000
|
-
const entries = await
|
|
33630
|
+
const entries = await fs5.readdir(dirPath, { withFileTypes: true });
|
|
33001
33631
|
for (const entry of entries) {
|
|
33002
|
-
const fullPath =
|
|
33632
|
+
const fullPath = path6.join(dirPath, entry.name);
|
|
33003
33633
|
let stat;
|
|
33004
33634
|
try {
|
|
33005
|
-
stat = await
|
|
33635
|
+
stat = await fs5.lstat(fullPath);
|
|
33006
33636
|
} catch {
|
|
33007
33637
|
continue;
|
|
33008
33638
|
}
|
|
@@ -33017,10 +33647,10 @@ async function directorySize(dirPath) {
|
|
|
33017
33647
|
return total;
|
|
33018
33648
|
}
|
|
33019
33649
|
function activeDirsForRoot(rootDir) {
|
|
33020
|
-
const root =
|
|
33650
|
+
const root = path6.resolve(rootDir);
|
|
33021
33651
|
const dirs = /* @__PURE__ */ new Set();
|
|
33022
33652
|
for (const dir of activeAttachmentDirs) {
|
|
33023
|
-
if (dir === root || dir.startsWith(`${root}${
|
|
33653
|
+
if (dir === root || dir.startsWith(`${root}${path6.sep}`)) {
|
|
33024
33654
|
dirs.add(dir);
|
|
33025
33655
|
}
|
|
33026
33656
|
}
|
|
@@ -33117,9 +33747,9 @@ async function writeResponseToFileWithLimit(res, filePath, maxBytes, rootDir) {
|
|
|
33117
33747
|
}
|
|
33118
33748
|
writtenStat = await file.stat();
|
|
33119
33749
|
await closeFile();
|
|
33120
|
-
await localDirectoryStatInsideRoot(
|
|
33750
|
+
await localDirectoryStatInsideRoot(path6.dirname(filePath), rootDir);
|
|
33121
33751
|
await assertLocalFileIdentity(tmpPath, rootDir, writtenStat);
|
|
33122
|
-
await
|
|
33752
|
+
await fs5.rename(tmpPath, filePath);
|
|
33123
33753
|
completed = true;
|
|
33124
33754
|
return written;
|
|
33125
33755
|
} finally {
|
|
@@ -33135,9 +33765,9 @@ async function writeResponseToFileWithLimit(res, filePath, maxBytes, rootDir) {
|
|
|
33135
33765
|
}
|
|
33136
33766
|
}
|
|
33137
33767
|
function localFileName(attachmentId, fileName, mimeType) {
|
|
33138
|
-
const safeName = sanitizePathSegment(
|
|
33139
|
-
const ext =
|
|
33140
|
-
const stem =
|
|
33768
|
+
const safeName = sanitizePathSegment(path6.basename(fileName || attachmentId));
|
|
33769
|
+
const ext = path6.extname(safeName) || extensionForMime(mimeType);
|
|
33770
|
+
const stem = path6.basename(safeName, path6.extname(safeName)) || attachmentId;
|
|
33141
33771
|
return `${sanitizePathSegment(attachmentId)}-${stem}${ext}`;
|
|
33142
33772
|
}
|
|
33143
33773
|
function extensionForMime(mimeType) {
|
|
@@ -33417,7 +34047,11 @@ function buildSpawnEnv(parentEnv, claudeHome, context2, opts) {
|
|
|
33417
34047
|
PRLL_TRIGGER_MESSAGE_ID: context2.triggerMessageId ?? "",
|
|
33418
34048
|
PRLL_NO_REPLY: context2.noReply ? "1" : "",
|
|
33419
34049
|
PRLL_CONTEXT_FILE: context2.contextFilePath ?? "",
|
|
33420
|
-
PRLL_STEP_ID_FILE: context2.stepIdFilePath ?? ""
|
|
34050
|
+
PRLL_STEP_ID_FILE: context2.stepIdFilePath ?? "",
|
|
34051
|
+
// Per-lane dispatch context directory (stable for the bridge's lifetime,
|
|
34052
|
+
// so pinning it at spawn is safe even for long-lived subprocesses). The
|
|
34053
|
+
// CLI keys into it by send target to derive dispatch effect keys.
|
|
34054
|
+
PRLL_CONTEXT_DIR: context2.contextDirPath ?? ""
|
|
33421
34055
|
};
|
|
33422
34056
|
if (!result.PRLL_WIKI_MOUNT_ROOT?.trim() && opts.wikiMountRoot) {
|
|
33423
34057
|
result.PRLL_WIKI_MOUNT_ROOT = opts.wikiMountRoot;
|
|
@@ -33523,8 +34157,8 @@ var ClaudeCodeAdapter = class {
|
|
|
33523
34157
|
if (!sessionId)
|
|
33524
34158
|
return void 0;
|
|
33525
34159
|
const projectSlug = this.opts.workspaceDir.replace(/[/.]/g, "-");
|
|
33526
|
-
const filePath =
|
|
33527
|
-
return
|
|
34160
|
+
const filePath = path7.join(this.opts.claudeHome, ".claude", "projects", projectSlug, `${sessionId}.jsonl`);
|
|
34161
|
+
return fs6.existsSync(filePath) ? filePath : void 0;
|
|
33528
34162
|
}
|
|
33529
34163
|
forkSession({ sessionKey }) {
|
|
33530
34164
|
return this.opts.sessionManager.createForkSession(sessionKey);
|
|
@@ -33810,7 +34444,7 @@ var ClaudeCodeAdapter = class {
|
|
|
33810
34444
|
if (this.opts.disallowedTools.length > 0) {
|
|
33811
34445
|
args.push("--disallowedTools", this.opts.disallowedTools.join(","));
|
|
33812
34446
|
}
|
|
33813
|
-
args.push("--append-system-prompt-file",
|
|
34447
|
+
args.push("--append-system-prompt-file", path7.join(this.opts.workspaceDir, ".parall", "system-prompt.md"));
|
|
33814
34448
|
if (this.opts.appendSystemPrompt) {
|
|
33815
34449
|
args.push("--append-system-prompt", this.opts.appendSystemPrompt);
|
|
33816
34450
|
}
|
|
@@ -33835,14 +34469,15 @@ var ClaudeCodeAdapter = class {
|
|
|
33835
34469
|
noReply: false,
|
|
33836
34470
|
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey) ?? "",
|
|
33837
34471
|
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey) ?? "",
|
|
34472
|
+
contextDirPath: this.opts.contextDirPath ?? "",
|
|
33838
34473
|
client: void 0
|
|
33839
34474
|
};
|
|
33840
34475
|
}
|
|
33841
34476
|
};
|
|
33842
34477
|
|
|
33843
34478
|
// ts/claude-agent/dist/session-manager.js
|
|
33844
|
-
import * as
|
|
33845
|
-
import * as
|
|
34479
|
+
import * as fs7 from "node:fs";
|
|
34480
|
+
import * as path8 from "node:path";
|
|
33846
34481
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
33847
34482
|
var ClaudeSessionManager = class _ClaudeSessionManager {
|
|
33848
34483
|
mainSessionKey;
|
|
@@ -33991,13 +34626,13 @@ var ClaudeSessionManager = class _ClaudeSessionManager {
|
|
|
33991
34626
|
clearMainSession() {
|
|
33992
34627
|
this.sessionIds.delete(this.mainSessionKey);
|
|
33993
34628
|
try {
|
|
33994
|
-
|
|
34629
|
+
fs7.unlinkSync(this.stateFilePath);
|
|
33995
34630
|
} catch {
|
|
33996
34631
|
}
|
|
33997
34632
|
}
|
|
33998
34633
|
restore() {
|
|
33999
34634
|
try {
|
|
34000
|
-
const raw =
|
|
34635
|
+
const raw = fs7.readFileSync(this.stateFilePath, "utf8");
|
|
34001
34636
|
const parsed = JSON.parse(raw);
|
|
34002
34637
|
if (parsed.runtimeKey === this.mainSessionKey && typeof parsed.sessionId === "string" && parsed.sessionId.trim()) {
|
|
34003
34638
|
this.sessionIds.set(this.mainSessionKey, parsed.sessionId.trim());
|
|
@@ -34007,8 +34642,8 @@ var ClaudeSessionManager = class _ClaudeSessionManager {
|
|
|
34007
34642
|
}
|
|
34008
34643
|
persist(sessionId) {
|
|
34009
34644
|
try {
|
|
34010
|
-
|
|
34011
|
-
|
|
34645
|
+
fs7.mkdirSync(path8.dirname(this.stateFilePath), { recursive: true });
|
|
34646
|
+
fs7.writeFileSync(this.stateFilePath, JSON.stringify({
|
|
34012
34647
|
runtimeKey: this.mainSessionKey,
|
|
34013
34648
|
sessionId
|
|
34014
34649
|
}, null, 2));
|
|
@@ -34019,8 +34654,8 @@ var ClaudeSessionManager = class _ClaudeSessionManager {
|
|
|
34019
34654
|
};
|
|
34020
34655
|
|
|
34021
34656
|
// ts/claude-agent/dist/workspace.js
|
|
34022
|
-
import * as
|
|
34023
|
-
import * as
|
|
34657
|
+
import * as fs8 from "node:fs";
|
|
34658
|
+
import * as path9 from "node:path";
|
|
34024
34659
|
function ensureClaudeWorkspace(workspaceDir, log2, agentIdentity) {
|
|
34025
34660
|
const systemPrompt = [
|
|
34026
34661
|
buildIdentity(agentIdentity),
|
|
@@ -34029,12 +34664,12 @@ function ensureClaudeWorkspace(workspaceDir, log2, agentIdentity) {
|
|
|
34029
34664
|
PRLL_REFERENCE_GUIDE,
|
|
34030
34665
|
buildSkillReferences(workspaceDir)
|
|
34031
34666
|
].join("\n\n");
|
|
34032
|
-
|
|
34033
|
-
|
|
34034
|
-
const parallDir =
|
|
34035
|
-
|
|
34036
|
-
|
|
34037
|
-
writeSkillFiles(
|
|
34667
|
+
fs8.mkdirSync(workspaceDir, { recursive: true });
|
|
34668
|
+
fs8.mkdirSync(path9.join(workspaceDir, ".claude"), { recursive: true });
|
|
34669
|
+
const parallDir = path9.join(workspaceDir, ".parall");
|
|
34670
|
+
fs8.mkdirSync(parallDir, { recursive: true });
|
|
34671
|
+
fs8.writeFileSync(path9.join(parallDir, "system-prompt.md"), systemPrompt, "utf8");
|
|
34672
|
+
writeSkillFiles(path9.join(parallDir, "skills"));
|
|
34038
34673
|
ensureLocalAttachmentGitExclude(workspaceDir);
|
|
34039
34674
|
}
|
|
34040
34675
|
|
|
@@ -34135,7 +34770,8 @@ async function main() {
|
|
|
34135
34770
|
apiKey: config.apiKey,
|
|
34136
34771
|
orgId: config.orgId,
|
|
34137
34772
|
contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
|
|
34138
|
-
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey)
|
|
34773
|
+
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
34774
|
+
contextDirPath: dispatchContextDirPath(config.stateDir)
|
|
34139
34775
|
});
|
|
34140
34776
|
if (resolvedEffort) {
|
|
34141
34777
|
adapter.updateConfig({ effort: resolvedEffort });
|
|
@@ -34183,6 +34819,7 @@ async function main() {
|
|
|
34183
34819
|
dispatchDeadlineMs: parseDispatchDeadlineMs(process.env.PRLL_DISPATCH_DEADLINE_MS),
|
|
34184
34820
|
contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
|
|
34185
34821
|
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
34822
|
+
dispatchContextDir: dispatchContextDirPath(config.stateDir),
|
|
34186
34823
|
onConfigUpdate: refreshPlatformConfig,
|
|
34187
34824
|
onSessionReady: refreshPlatformConfig,
|
|
34188
34825
|
// Long-lived `claude` subprocesses outlive a single dispatch, so the
|