@parall/daemon 1.37.0 → 1.39.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 +72 -1
- package/bundle/parall-claude-agent.js +887 -224
- package/bundle/parall-codex-agent.js +894 -233
- package/bundle/parall-daemon.js +100 -1
- 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`,
|
|
@@ -27439,7 +27457,13 @@ var ENDPOINTS = {
|
|
|
27439
27457
|
// the machine control-plane request/reply bridge that lives in api-server.
|
|
27440
27458
|
BROWSER_PROFILE_VIEWER_COMMAND: (orgId, profileId) => `${API_BASE}/orgs/${orgId}/browser-profiles/${profileId}/viewer/command`,
|
|
27441
27459
|
// Clip registry (global, served by clip-service → Pinix Hub proxy)
|
|
27442
|
-
CLIP_REGISTRY: () => `${CLIP_BASE}/registry/clips
|
|
27460
|
+
CLIP_REGISTRY: () => `${CLIP_BASE}/registry/clips`,
|
|
27461
|
+
// Edge device endpoints
|
|
27462
|
+
ORG_EDGE_DEVICES: (orgId) => `/api/v1/orgs/${orgId}/edge/devices`,
|
|
27463
|
+
ORG_EDGE_ONBOARDING: (orgId) => `/api/v1/orgs/${orgId}/edge/onboarding`,
|
|
27464
|
+
ORG_EDGE_PROFILES: (orgId, edgeId) => `/api/v1/orgs/${orgId}/edge/${edgeId}/profiles`,
|
|
27465
|
+
CLIP_CONNECTIONS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/connections`,
|
|
27466
|
+
CLIP_CONNECTION: (orgId, connId) => `/api/v1/orgs/${orgId}/clip-connections/${connId}`
|
|
27443
27467
|
};
|
|
27444
27468
|
var WS_EVENTS = {
|
|
27445
27469
|
// Client -> Server
|
|
@@ -27498,6 +27522,7 @@ var WS_EVENTS = {
|
|
|
27498
27522
|
READ_POSITION_UPDATED: "read_position.updated",
|
|
27499
27523
|
DISPATCH_NEW: "dispatch.new",
|
|
27500
27524
|
DISPATCH_RECEIVED: "dispatch.received",
|
|
27525
|
+
DISPATCH_RESOLVED: "dispatch.resolved",
|
|
27501
27526
|
SCHEDULE_CREATED: "schedule.created",
|
|
27502
27527
|
SCHEDULE_UPDATED: "schedule.updated",
|
|
27503
27528
|
SCHEDULE_DELETED: "schedule.deleted",
|
|
@@ -27593,8 +27618,8 @@ var ParallClient = class _ParallClient {
|
|
|
27593
27618
|
* is authoritative, so wiki vs api routing can't drift from how a caller
|
|
27594
27619
|
* happens to invoke the client.
|
|
27595
27620
|
*/
|
|
27596
|
-
baseUrlFor(
|
|
27597
|
-
return
|
|
27621
|
+
baseUrlFor(path10) {
|
|
27622
|
+
return path10.startsWith(WIKI_BASE) ? this.wikiBaseUrl : this.baseUrl;
|
|
27598
27623
|
}
|
|
27599
27624
|
setToken(token) {
|
|
27600
27625
|
this.token = token;
|
|
@@ -27621,10 +27646,10 @@ var ParallClient = class _ParallClient {
|
|
|
27621
27646
|
* REFRESH_THRESHOLD_S, refresh it **before** sending the request.
|
|
27622
27647
|
* No-op when the token is still fresh, missing, or un-parseable.
|
|
27623
27648
|
*/
|
|
27624
|
-
async ensureFreshToken(
|
|
27649
|
+
async ensureFreshToken(path10) {
|
|
27625
27650
|
if (!this.token || !this.getRefreshToken)
|
|
27626
27651
|
return;
|
|
27627
|
-
const pathSuffix =
|
|
27652
|
+
const pathSuffix = path10.replace(/^\/api\/v1/, "");
|
|
27628
27653
|
if (_ParallClient.AUTH_PATHS.has(pathSuffix))
|
|
27629
27654
|
return;
|
|
27630
27655
|
const exp = _ParallClient.decodeJwtExp(this.token);
|
|
@@ -27656,11 +27681,11 @@ var ParallClient = class _ParallClient {
|
|
|
27656
27681
|
this.refreshPromise = null;
|
|
27657
27682
|
}
|
|
27658
27683
|
}
|
|
27659
|
-
async request(method,
|
|
27684
|
+
async request(method, path10, body, query, retried = false, opts) {
|
|
27660
27685
|
if (!retried) {
|
|
27661
|
-
await this.ensureFreshToken(
|
|
27686
|
+
await this.ensureFreshToken(path10);
|
|
27662
27687
|
}
|
|
27663
|
-
let url = `${this.baseUrlFor(
|
|
27688
|
+
let url = `${this.baseUrlFor(path10)}${path10}`;
|
|
27664
27689
|
if (query) {
|
|
27665
27690
|
const params = new URLSearchParams();
|
|
27666
27691
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -27690,12 +27715,12 @@ var ParallClient = class _ParallClient {
|
|
|
27690
27715
|
throw _ParallClient.normalizeFetchError(err);
|
|
27691
27716
|
}
|
|
27692
27717
|
if (res.status === 401) {
|
|
27693
|
-
const pathSuffix =
|
|
27718
|
+
const pathSuffix = path10.replace(/^\/api\/v1/, "");
|
|
27694
27719
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
27695
27720
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
27696
27721
|
const refreshed = await this.tryRefresh();
|
|
27697
27722
|
if (refreshed) {
|
|
27698
|
-
return this.request(method,
|
|
27723
|
+
return this.request(method, path10, body, query, true, opts);
|
|
27699
27724
|
}
|
|
27700
27725
|
}
|
|
27701
27726
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -27706,6 +27731,7 @@ var ParallClient = class _ParallClient {
|
|
|
27706
27731
|
const rawErrorBody = await res.json().catch(() => ({}));
|
|
27707
27732
|
throw buildApiError(res, rawErrorBody);
|
|
27708
27733
|
}
|
|
27734
|
+
opts?.onStatus?.(res.status);
|
|
27709
27735
|
if (res.status === 204)
|
|
27710
27736
|
return void 0;
|
|
27711
27737
|
if (res.status === 202) {
|
|
@@ -27724,15 +27750,15 @@ var ParallClient = class _ParallClient {
|
|
|
27724
27750
|
* hit the 100 MiB cap, so a longer 5-minute timeout is used so a
|
|
27725
27751
|
* 50 MiB blob on a slow connection doesn't get chopped at 15 s.
|
|
27726
27752
|
*/
|
|
27727
|
-
async multipartRequest(method,
|
|
27753
|
+
async multipartRequest(method, path10, body, retried = false) {
|
|
27728
27754
|
if (!retried) {
|
|
27729
|
-
await this.ensureFreshToken(
|
|
27755
|
+
await this.ensureFreshToken(path10);
|
|
27730
27756
|
}
|
|
27731
27757
|
const { "Content-Type": _drop, ...headers } = this.buildHeaders();
|
|
27732
27758
|
void _drop;
|
|
27733
27759
|
let res;
|
|
27734
27760
|
try {
|
|
27735
|
-
res = await fetch(`${this.baseUrlFor(
|
|
27761
|
+
res = await fetch(`${this.baseUrlFor(path10)}${path10}`, {
|
|
27736
27762
|
method,
|
|
27737
27763
|
headers,
|
|
27738
27764
|
body,
|
|
@@ -27742,12 +27768,12 @@ var ParallClient = class _ParallClient {
|
|
|
27742
27768
|
throw _ParallClient.normalizeFetchError(err);
|
|
27743
27769
|
}
|
|
27744
27770
|
if (res.status === 401) {
|
|
27745
|
-
const pathSuffix =
|
|
27771
|
+
const pathSuffix = path10.replace(/^\/api\/v1/, "");
|
|
27746
27772
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
27747
27773
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
27748
27774
|
const refreshed = await this.tryRefresh();
|
|
27749
27775
|
if (refreshed) {
|
|
27750
|
-
return this.multipartRequest(method,
|
|
27776
|
+
return this.multipartRequest(method, path10, body, true);
|
|
27751
27777
|
}
|
|
27752
27778
|
}
|
|
27753
27779
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -28039,6 +28065,21 @@ var ParallClient = class _ParallClient {
|
|
|
28039
28065
|
async sendMessage(orgId, chatId, req) {
|
|
28040
28066
|
return this.request("POST", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), req);
|
|
28041
28067
|
}
|
|
28068
|
+
/**
|
|
28069
|
+
* sendMessage variant that also reports whether the server answered with an
|
|
28070
|
+
* idempotent replay (HTTP 200 — the message already existed for this
|
|
28071
|
+
* idempotency/effect key) instead of a fresh create (201). Used by the CLI
|
|
28072
|
+
* to surface "already sent by a previous run (deduplicated)".
|
|
28073
|
+
*/
|
|
28074
|
+
async sendMessageDetailed(orgId, chatId, req) {
|
|
28075
|
+
let status = 0;
|
|
28076
|
+
const message = await this.request("POST", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), req, void 0, false, {
|
|
28077
|
+
onStatus: (s) => {
|
|
28078
|
+
status = s;
|
|
28079
|
+
}
|
|
28080
|
+
});
|
|
28081
|
+
return { message, deduplicated: status === 200 };
|
|
28082
|
+
}
|
|
28042
28083
|
async getMessages(orgId, chatId, params) {
|
|
28043
28084
|
return this.request("GET", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), void 0, params);
|
|
28044
28085
|
}
|
|
@@ -28426,8 +28467,8 @@ var ParallClient = class _ParallClient {
|
|
|
28426
28467
|
async requestMachineUpdate(orgId, machineId, mandatory = false) {
|
|
28427
28468
|
await this.request("POST", ENDPOINTS.MACHINE_REQUEST_UPDATE(orgId, machineId), { mandatory });
|
|
28428
28469
|
}
|
|
28429
|
-
async browseMachineFilesystem(orgId, machineId,
|
|
28430
|
-
return this.request("POST", ENDPOINTS.MACHINE_BROWSE(orgId, machineId), { path:
|
|
28470
|
+
async browseMachineFilesystem(orgId, machineId, path10) {
|
|
28471
|
+
return this.request("POST", ENDPOINTS.MACHINE_BROWSE(orgId, machineId), { path: path10 }, void 0, false, { timeoutMs: 15e3 });
|
|
28431
28472
|
}
|
|
28432
28473
|
/** Create a new machine key. Returns the raw key string (shown once) + metadata. */
|
|
28433
28474
|
async createMachineKey(orgId, machineId, name) {
|
|
@@ -28500,6 +28541,30 @@ var ParallClient = class _ParallClient {
|
|
|
28500
28541
|
const params = maxAgeHours !== void 0 ? { max_age_hours: String(maxAgeHours) } : void 0;
|
|
28501
28542
|
return this.request("POST", ENDPOINTS.DISPATCH_EXPIRE(orgId), void 0, params);
|
|
28502
28543
|
}
|
|
28544
|
+
/**
|
|
28545
|
+
* Claim a dispatch lane (explicit consume endpoint). Occupies the
|
|
28546
|
+
* (agent, target, thread) lane and folds claimable WorkItems into it;
|
|
28547
|
+
* `claimed: false` means a healthy incumbent holds the lane.
|
|
28548
|
+
*/
|
|
28549
|
+
async claimDispatch(orgId, req) {
|
|
28550
|
+
return this.request("POST", ENDPOINTS.DISPATCH_CLAIM(orgId), req);
|
|
28551
|
+
}
|
|
28552
|
+
/** Fold a pending same-target WorkItem into a live lane (409 STALE_LANE when dethroned). */
|
|
28553
|
+
async steerDispatch(orgId, req) {
|
|
28554
|
+
return this.request("POST", ENDPOINTS.DISPATCH_STEER(orgId), req);
|
|
28555
|
+
}
|
|
28556
|
+
/** End a turn: no_action sweep of the lane's members + lane release + re-drive check. */
|
|
28557
|
+
async completeDispatch(orgId, req) {
|
|
28558
|
+
return this.request("POST", ENDPOINTS.DISPATCH_COMPLETE(orgId), req);
|
|
28559
|
+
}
|
|
28560
|
+
/** Release a lane on graceful shutdown — members return to pending immediately. */
|
|
28561
|
+
async releaseDispatchLane(orgId, lane) {
|
|
28562
|
+
return this.request("POST", ENDPOINTS.DISPATCH_RELEASE(orgId), { lane });
|
|
28563
|
+
}
|
|
28564
|
+
/** Renew a live lane's lease (long-turn keepalive). 409 STALE_LANE when dethroned. */
|
|
28565
|
+
async heartbeatDispatchLane(orgId, req) {
|
|
28566
|
+
return this.request("POST", ENDPOINTS.DISPATCH_HEARTBEAT(orgId), req);
|
|
28567
|
+
}
|
|
28503
28568
|
async getDispatchByMessages(orgId, chatId, messageIds) {
|
|
28504
28569
|
const params = { chat_id: chatId, message_ids: messageIds.join(",") };
|
|
28505
28570
|
return this.request("GET", ENDPOINTS.DISPATCH_BY_MESSAGES(orgId), void 0, params);
|
|
@@ -28964,8 +29029,8 @@ var ParallClient = class _ParallClient {
|
|
|
28964
29029
|
async deleteWikiRestriction(orgId, wikiId, restrictionId) {
|
|
28965
29030
|
await this.request("DELETE", ENDPOINTS.WIKI_RESTRICTION(orgId, wikiId, restrictionId));
|
|
28966
29031
|
}
|
|
28967
|
-
async getWikiAccessStatus(orgId, wikiId,
|
|
28968
|
-
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0,
|
|
29032
|
+
async getWikiAccessStatus(orgId, wikiId, path10) {
|
|
29033
|
+
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0, path10 ? { path: path10 } : void 0);
|
|
28969
29034
|
}
|
|
28970
29035
|
async createWikiAccessRequest(orgId, wikiId, data) {
|
|
28971
29036
|
await this.request("POST", ENDPOINTS.WIKI_ACCESS_REQUESTS(orgId, wikiId), data);
|
|
@@ -28974,14 +29039,14 @@ var ParallClient = class _ParallClient {
|
|
|
28974
29039
|
async getWikiCommits(orgId, wikiId, params) {
|
|
28975
29040
|
return this.request("GET", ENDPOINTS.WIKI_COMMITS(orgId, wikiId), void 0, params);
|
|
28976
29041
|
}
|
|
28977
|
-
async getWikiFileCommits(orgId, wikiId,
|
|
29042
|
+
async getWikiFileCommits(orgId, wikiId, path10, params) {
|
|
28978
29043
|
return this.request("GET", ENDPOINTS.WIKI_FILE_COMMITS(orgId, wikiId), void 0, {
|
|
28979
|
-
path:
|
|
29044
|
+
path: path10,
|
|
28980
29045
|
...params
|
|
28981
29046
|
});
|
|
28982
29047
|
}
|
|
28983
|
-
async getWikiBlame(orgId, wikiId,
|
|
28984
|
-
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path:
|
|
29048
|
+
async getWikiBlame(orgId, wikiId, path10, ref) {
|
|
29049
|
+
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path: path10, ref });
|
|
28985
29050
|
}
|
|
28986
29051
|
// ---- Wiki Operations (audit log) ----
|
|
28987
29052
|
async getWikiOperations(orgId, wikiId, params) {
|
|
@@ -29227,6 +29292,26 @@ var ParallClient = class _ParallClient {
|
|
|
29227
29292
|
const resp = await this.request("GET", url);
|
|
29228
29293
|
return resp.data;
|
|
29229
29294
|
}
|
|
29295
|
+
// ---- Edge devices ----
|
|
29296
|
+
async listEdgeDevices(orgId) {
|
|
29297
|
+
return this.request("GET", ENDPOINTS.ORG_EDGE_DEVICES(orgId));
|
|
29298
|
+
}
|
|
29299
|
+
async getEdgeOnboarding(orgId) {
|
|
29300
|
+
return this.request("GET", ENDPOINTS.ORG_EDGE_ONBOARDING(orgId));
|
|
29301
|
+
}
|
|
29302
|
+
async listEdgeProfiles(orgId, edgeId) {
|
|
29303
|
+
return this.request("GET", ENDPOINTS.ORG_EDGE_PROFILES(orgId, edgeId));
|
|
29304
|
+
}
|
|
29305
|
+
// ---- Clip connections ----
|
|
29306
|
+
async listClipConnections(orgId, clipId) {
|
|
29307
|
+
return this.request("GET", ENDPOINTS.CLIP_CONNECTIONS(orgId, clipId));
|
|
29308
|
+
}
|
|
29309
|
+
async createClipConnection(orgId, clipId, input) {
|
|
29310
|
+
return this.request("POST", ENDPOINTS.CLIP_CONNECTIONS(orgId, clipId), input);
|
|
29311
|
+
}
|
|
29312
|
+
async deleteClipConnection(orgId, connId) {
|
|
29313
|
+
return this.request("DELETE", ENDPOINTS.CLIP_CONNECTION(orgId, connId));
|
|
29314
|
+
}
|
|
29230
29315
|
};
|
|
29231
29316
|
function normalizeWikiChangeset(changeset) {
|
|
29232
29317
|
return {
|
|
@@ -29613,6 +29698,404 @@ var ParallWs = class {
|
|
|
29613
29698
|
}
|
|
29614
29699
|
};
|
|
29615
29700
|
|
|
29701
|
+
// ts/agent-core/dist/lane-ledger.js
|
|
29702
|
+
import * as fs from "node:fs";
|
|
29703
|
+
var LedgerUnsupportedError = class extends Error {
|
|
29704
|
+
};
|
|
29705
|
+
function isStaleLane(err) {
|
|
29706
|
+
return err instanceof ApiError && err.status === 409 && err.code === "STALE_LANE";
|
|
29707
|
+
}
|
|
29708
|
+
function isEndpointMissing(err) {
|
|
29709
|
+
return err instanceof ApiError && err.status === 404 && !err.code;
|
|
29710
|
+
}
|
|
29711
|
+
var LaneLedger = class {
|
|
29712
|
+
opts;
|
|
29713
|
+
lanes = /* @__PURE__ */ new Map();
|
|
29714
|
+
constructor(opts) {
|
|
29715
|
+
this.opts = opts;
|
|
29716
|
+
}
|
|
29717
|
+
get contextDir() {
|
|
29718
|
+
return this.opts.contextDir;
|
|
29719
|
+
}
|
|
29720
|
+
/** Only chat message events ride the lane ledger; typed events stay on the legacy ack path. */
|
|
29721
|
+
handles(event) {
|
|
29722
|
+
return event.type === "message" && event.targetId.startsWith("cht_");
|
|
29723
|
+
}
|
|
29724
|
+
laneKeyFor(event) {
|
|
29725
|
+
if (event.type !== "message" && event.dispatchEventId) {
|
|
29726
|
+
return laneKeyForTarget(`dsp:${event.dispatchEventId}`);
|
|
29727
|
+
}
|
|
29728
|
+
return laneKeyForTarget(`prll://${event.targetId}`, event.threadRootId);
|
|
29729
|
+
}
|
|
29730
|
+
getForEvent(event) {
|
|
29731
|
+
return this.lanes.get(this.laneKeyFor(event));
|
|
29732
|
+
}
|
|
29733
|
+
laneContextPath(lane) {
|
|
29734
|
+
return laneContextFilePath(this.opts.contextDir, lane.targetUri, lane.threadRootId);
|
|
29735
|
+
}
|
|
29736
|
+
/**
|
|
29737
|
+
* Claim (or reuse) the lane for a group of same-lane message events and
|
|
29738
|
+
* fold every group member into it. Returns 'foreign' when a healthy
|
|
29739
|
+
* incumbent (another pod) holds the resource — the caller must not
|
|
29740
|
+
* dispatch; the events stay pending server-side and re-drive after the
|
|
29741
|
+
* incumbent completes.
|
|
29742
|
+
*/
|
|
29743
|
+
async ensureLane(events) {
|
|
29744
|
+
const trigger = events[events.length - 1];
|
|
29745
|
+
const laneKey = this.laneKeyFor(trigger);
|
|
29746
|
+
let lane = this.lanes.get(laneKey);
|
|
29747
|
+
if (!lane) {
|
|
29748
|
+
const targetUri = `prll://${trigger.targetId}`;
|
|
29749
|
+
let res;
|
|
29750
|
+
try {
|
|
29751
|
+
res = await this.opts.client.claimDispatch(this.opts.orgId, {
|
|
29752
|
+
target_uri: targetUri,
|
|
29753
|
+
thread_root_id: trigger.threadRootId,
|
|
29754
|
+
limit: 100
|
|
29755
|
+
});
|
|
29756
|
+
} catch (err) {
|
|
29757
|
+
if (isEndpointMissing(err))
|
|
29758
|
+
throw new LedgerUnsupportedError("claim endpoint unavailable");
|
|
29759
|
+
throw err;
|
|
29760
|
+
}
|
|
29761
|
+
if (!res.claimed || !res.lane) {
|
|
29762
|
+
this.opts.log?.info(`lane for ${targetUri} held by a healthy incumbent \u2014 leaving events pending for re-drive`);
|
|
29763
|
+
return null;
|
|
29764
|
+
}
|
|
29765
|
+
const leaseUntilMs = Date.parse(res.lease_until ?? "");
|
|
29766
|
+
lane = {
|
|
29767
|
+
laneKey,
|
|
29768
|
+
lane: res.lane,
|
|
29769
|
+
targetUri,
|
|
29770
|
+
threadRootId: trigger.threadRootId,
|
|
29771
|
+
folded: /* @__PURE__ */ new Map(),
|
|
29772
|
+
...Number.isNaN(leaseUntilMs) ? {} : { leaseUntilMs, leaseTtlMs: Math.max(leaseUntilMs - Date.now(), 6e4) }
|
|
29773
|
+
};
|
|
29774
|
+
for (const ev of res.events ?? []) {
|
|
29775
|
+
lane.folded.set(ev.source_id, ev.id);
|
|
29776
|
+
}
|
|
29777
|
+
this.lanes.set(laneKey, lane);
|
|
29778
|
+
}
|
|
29779
|
+
for (const ev of events) {
|
|
29780
|
+
if (lane.folded.has(ev.messageId))
|
|
29781
|
+
continue;
|
|
29782
|
+
try {
|
|
29783
|
+
const res = await this.opts.client.steerDispatch(this.opts.orgId, {
|
|
29784
|
+
lane: lane.lane,
|
|
29785
|
+
target_uri: lane.targetUri,
|
|
29786
|
+
thread_root_id: lane.threadRootId,
|
|
29787
|
+
...ev.dispatchEventId ? { dispatch_event_id: ev.dispatchEventId } : { source_type: "message", source_id: ev.messageId }
|
|
29788
|
+
});
|
|
29789
|
+
lane.folded.set(ev.messageId, res.dispatch_event_id);
|
|
29790
|
+
} catch (err) {
|
|
29791
|
+
if (isStaleLane(err)) {
|
|
29792
|
+
this.lanes.delete(laneKey);
|
|
29793
|
+
return null;
|
|
29794
|
+
}
|
|
29795
|
+
this.opts.log?.warn(`steer fold failed for ${ev.messageId} \u2014 failing closed, releasing lane: ${String(err)}`);
|
|
29796
|
+
await this.release(laneKey);
|
|
29797
|
+
return null;
|
|
29798
|
+
}
|
|
29799
|
+
}
|
|
29800
|
+
return lane;
|
|
29801
|
+
}
|
|
29802
|
+
/**
|
|
29803
|
+
* Fold a live mid-turn message into its active lane BEFORE injecting it
|
|
29804
|
+
* into the running turn. Injection without a successful fold is forbidden —
|
|
29805
|
+
* an un-folded injected message would be re-driven after complete and the
|
|
29806
|
+
* model would handle it twice.
|
|
29807
|
+
*/
|
|
29808
|
+
async steerLive(event) {
|
|
29809
|
+
const laneKey = this.laneKeyFor(event);
|
|
29810
|
+
const lane = this.lanes.get(laneKey);
|
|
29811
|
+
if (!lane)
|
|
29812
|
+
return false;
|
|
29813
|
+
if (lane.folded.has(event.messageId))
|
|
29814
|
+
return true;
|
|
29815
|
+
try {
|
|
29816
|
+
const res = await this.opts.client.steerDispatch(this.opts.orgId, {
|
|
29817
|
+
lane: lane.lane,
|
|
29818
|
+
target_uri: lane.targetUri,
|
|
29819
|
+
thread_root_id: lane.threadRootId,
|
|
29820
|
+
...event.dispatchEventId ? { dispatch_event_id: event.dispatchEventId } : { source_type: "message", source_id: event.messageId }
|
|
29821
|
+
});
|
|
29822
|
+
lane.folded.set(event.messageId, res.dispatch_event_id);
|
|
29823
|
+
return true;
|
|
29824
|
+
} catch (err) {
|
|
29825
|
+
if (isStaleLane(err)) {
|
|
29826
|
+
this.lanes.delete(laneKey);
|
|
29827
|
+
} else {
|
|
29828
|
+
this.opts.log?.warn(`live steer failed for ${event.messageId}: ${String(err)}`);
|
|
29829
|
+
}
|
|
29830
|
+
return false;
|
|
29831
|
+
}
|
|
29832
|
+
}
|
|
29833
|
+
/**
|
|
29834
|
+
* Complete the lane when no local work remains for it: the server sweeps
|
|
29835
|
+
* still-leased members as no_action, releases the occupancy row, and
|
|
29836
|
+
* re-drives any same-target pending work. A STALE_LANE answer means a
|
|
29837
|
+
* takeover already owns the resource — local state is dropped either way.
|
|
29838
|
+
*/
|
|
29839
|
+
async completeIfIdle(laneKey, hasMoreLocal) {
|
|
29840
|
+
const lane = this.lanes.get(laneKey);
|
|
29841
|
+
if (!lane || hasMoreLocal)
|
|
29842
|
+
return;
|
|
29843
|
+
this.lanes.delete(laneKey);
|
|
29844
|
+
this.removeLaneContext(lane);
|
|
29845
|
+
try {
|
|
29846
|
+
const res = await this.opts.client.completeDispatch(this.opts.orgId, {
|
|
29847
|
+
lane: lane.lane,
|
|
29848
|
+
target_uri: lane.targetUri,
|
|
29849
|
+
thread_root_id: lane.threadRootId
|
|
29850
|
+
});
|
|
29851
|
+
if (res.swept_no_action > 0 || res.redriven) {
|
|
29852
|
+
this.opts.log?.info(`lane complete for ${lane.targetUri}: swept ${res.swept_no_action} no_action, redriven=${res.redriven}`);
|
|
29853
|
+
}
|
|
29854
|
+
} catch (err) {
|
|
29855
|
+
if (isStaleLane(err)) {
|
|
29856
|
+
this.opts.log?.info(`lane complete skipped for ${lane.targetUri} \u2014 taken over`);
|
|
29857
|
+
return;
|
|
29858
|
+
}
|
|
29859
|
+
this.opts.log?.warn(`lane complete failed for ${lane.targetUri}: ${String(err)}`);
|
|
29860
|
+
}
|
|
29861
|
+
}
|
|
29862
|
+
/**
|
|
29863
|
+
* Long-turn keepalive: renew the lane's lease on runtime activity, throttled
|
|
29864
|
+
* so a chatty turn doesn't spam the server. Without this, a legitimately
|
|
29865
|
+
* long turn (> lane TTL) would be dethroned mid-flight and every subsequent
|
|
29866
|
+
* write misfired with STALE_LANE — the design doc's "long turns renew via
|
|
29867
|
+
* step writes". Fire-and-forget: a failed renewal is surfaced by the next
|
|
29868
|
+
* write's incumbency check anyway.
|
|
29869
|
+
*/
|
|
29870
|
+
maybeRenew(lane) {
|
|
29871
|
+
const now = Date.now();
|
|
29872
|
+
const ttl = lane.leaseTtlMs ?? 10 * 6e4;
|
|
29873
|
+
const until = lane.leaseUntilMs ?? now;
|
|
29874
|
+
if (until - now > ttl / 2)
|
|
29875
|
+
return;
|
|
29876
|
+
lane.leaseUntilMs = now + ttl;
|
|
29877
|
+
void this.opts.client.heartbeatDispatchLane(this.opts.orgId, {
|
|
29878
|
+
lane: lane.lane,
|
|
29879
|
+
target_uri: lane.targetUri,
|
|
29880
|
+
thread_root_id: lane.threadRootId
|
|
29881
|
+
}).then((res) => {
|
|
29882
|
+
const until2 = Date.parse(res?.lease_until ?? "");
|
|
29883
|
+
if (!Number.isNaN(until2))
|
|
29884
|
+
lane.leaseUntilMs = until2;
|
|
29885
|
+
}).catch((err) => {
|
|
29886
|
+
if (isStaleLane(err)) {
|
|
29887
|
+
this.lanes.delete(lane.laneKey);
|
|
29888
|
+
this.opts.log?.warn(`lane ${lane.targetUri} was taken over during the turn`);
|
|
29889
|
+
return;
|
|
29890
|
+
}
|
|
29891
|
+
this.opts.log?.warn(`lane heartbeat failed for ${lane.targetUri}: ${String(err)}`);
|
|
29892
|
+
});
|
|
29893
|
+
}
|
|
29894
|
+
/**
|
|
29895
|
+
* Release a lane's unresolved members back to pending (dispatch error /
|
|
29896
|
+
* shutdown) so the next pod re-claims immediately instead of waiting out
|
|
29897
|
+
* the lease.
|
|
29898
|
+
*/
|
|
29899
|
+
async release(laneKey) {
|
|
29900
|
+
const lane = this.lanes.get(laneKey);
|
|
29901
|
+
if (!lane)
|
|
29902
|
+
return;
|
|
29903
|
+
this.lanes.delete(laneKey);
|
|
29904
|
+
this.removeLaneContext(lane);
|
|
29905
|
+
try {
|
|
29906
|
+
await this.opts.client.releaseDispatchLane(this.opts.orgId, lane.lane);
|
|
29907
|
+
} catch (err) {
|
|
29908
|
+
this.opts.log?.warn(`lane release failed for ${lane.targetUri}: ${String(err)}`);
|
|
29909
|
+
}
|
|
29910
|
+
}
|
|
29911
|
+
async releaseAll() {
|
|
29912
|
+
const keys = [...this.lanes.keys()];
|
|
29913
|
+
for (const key of keys) {
|
|
29914
|
+
await this.release(key);
|
|
29915
|
+
}
|
|
29916
|
+
}
|
|
29917
|
+
/** True when any lane is currently active (used by shutdown logging). */
|
|
29918
|
+
get activeCount() {
|
|
29919
|
+
return this.lanes.size;
|
|
29920
|
+
}
|
|
29921
|
+
/**
|
|
29922
|
+
* Claim the single-member lane of one typed WorkItem (resource = dsp:<id>),
|
|
29923
|
+
* by WorkItem id or by source identity (the live task.assigned event has no
|
|
29924
|
+
* WorkItem id). Returns null when a healthy incumbent (another pod) holds
|
|
29925
|
+
* it or the WorkItem is already resolved — the caller must skip processing.
|
|
29926
|
+
*/
|
|
29927
|
+
async claimTyped(ref) {
|
|
29928
|
+
let res;
|
|
29929
|
+
try {
|
|
29930
|
+
res = await this.opts.client.claimDispatch(this.opts.orgId, {
|
|
29931
|
+
dispatch_event_id: ref.dispatchEventId,
|
|
29932
|
+
source_type: ref.dispatchEventId ? void 0 : ref.sourceType,
|
|
29933
|
+
source_id: ref.dispatchEventId ? void 0 : ref.sourceId
|
|
29934
|
+
});
|
|
29935
|
+
} catch (err) {
|
|
29936
|
+
if (isEndpointMissing(err))
|
|
29937
|
+
throw new LedgerUnsupportedError("claim endpoint unavailable");
|
|
29938
|
+
throw err;
|
|
29939
|
+
}
|
|
29940
|
+
if (!res.claimed || !res.lane || !res.events?.length)
|
|
29941
|
+
return null;
|
|
29942
|
+
const workItem = res.events[0];
|
|
29943
|
+
const targetUri = `dsp:${workItem.id}`;
|
|
29944
|
+
const leaseUntilMs = Date.parse(res.lease_until ?? "");
|
|
29945
|
+
const lane = {
|
|
29946
|
+
laneKey: laneKeyForTarget(targetUri),
|
|
29947
|
+
lane: res.lane,
|
|
29948
|
+
targetUri,
|
|
29949
|
+
folded: /* @__PURE__ */ new Map([[workItem.source_id, workItem.id]]),
|
|
29950
|
+
typedDispatchEventId: workItem.id,
|
|
29951
|
+
...Number.isNaN(leaseUntilMs) ? {} : { leaseUntilMs, leaseTtlMs: Math.max(leaseUntilMs - Date.now(), 6e4) }
|
|
29952
|
+
};
|
|
29953
|
+
this.lanes.set(lane.laneKey, lane);
|
|
29954
|
+
return lane;
|
|
29955
|
+
}
|
|
29956
|
+
/**
|
|
29957
|
+
* Remove the per-lane context file (and its CLI sidecar) when the lane
|
|
29958
|
+
* ends. A leftover file would make a later cross-context send to the same
|
|
29959
|
+
* target bind a dead lane token and misfire with STALE_LANE instead of
|
|
29960
|
+
* taking the plain non-ledger path.
|
|
29961
|
+
*/
|
|
29962
|
+
removeLaneContext(lane) {
|
|
29963
|
+
const contextPath = this.laneContextPath(lane);
|
|
29964
|
+
for (const p of [contextPath, contextPath.replace(/\.json$/, ".reply-state.json")]) {
|
|
29965
|
+
try {
|
|
29966
|
+
fs.rmSync(p, { force: true });
|
|
29967
|
+
} catch {
|
|
29968
|
+
}
|
|
29969
|
+
}
|
|
29970
|
+
}
|
|
29971
|
+
};
|
|
29972
|
+
|
|
29973
|
+
// ts/agent-core/dist/gateway-lane-flow.js
|
|
29974
|
+
async function dispatchLaneGroup(host, opts) {
|
|
29975
|
+
const ledger = host.laneLedger;
|
|
29976
|
+
const event = opts.events[opts.events.length - 1];
|
|
29977
|
+
let lane;
|
|
29978
|
+
try {
|
|
29979
|
+
lane = await ledger.ensureLane(opts.events);
|
|
29980
|
+
} catch (err) {
|
|
29981
|
+
if (!(err instanceof LedgerUnsupportedError))
|
|
29982
|
+
throw err;
|
|
29983
|
+
host.disableLedger("claim endpoint missing");
|
|
29984
|
+
await host.emitDispatchReceived(event);
|
|
29985
|
+
const dispatched2 = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText);
|
|
29986
|
+
if (!dispatched2)
|
|
29987
|
+
return "shutdown";
|
|
29988
|
+
for (const ev of opts.events) {
|
|
29989
|
+
host.opts.client.ackDispatch(host.opts.config.org_id, {
|
|
29990
|
+
source_type: ev.ackSourceType ?? "message",
|
|
29991
|
+
source_id: ev.ackSourceId ?? ev.messageId
|
|
29992
|
+
}).catch(() => {
|
|
29993
|
+
});
|
|
29994
|
+
}
|
|
29995
|
+
return "dispatched";
|
|
29996
|
+
}
|
|
29997
|
+
if (!lane) {
|
|
29998
|
+
for (const ev of opts.events) {
|
|
29999
|
+
host.dispatchedMessages.delete(ev.messageId);
|
|
30000
|
+
}
|
|
30001
|
+
return "foreign";
|
|
30002
|
+
}
|
|
30003
|
+
let dispatched = false;
|
|
30004
|
+
try {
|
|
30005
|
+
dispatched = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText);
|
|
30006
|
+
} catch (err) {
|
|
30007
|
+
await ledger.release(lane.laneKey).catch(() => {
|
|
30008
|
+
});
|
|
30009
|
+
throw err;
|
|
30010
|
+
}
|
|
30011
|
+
if (!dispatched) {
|
|
30012
|
+
return "shutdown";
|
|
30013
|
+
}
|
|
30014
|
+
const pendingInjections = host.opts.dispatchAdapter.hasPendingInjections?.(opts.sessionKey) ?? false;
|
|
30015
|
+
await ledger.completeIfIdle(lane.laneKey, pendingInjections || opts.hasMoreLocal());
|
|
30016
|
+
return "dispatched";
|
|
30017
|
+
}
|
|
30018
|
+
async function consumeTypedDispatch(host, ref, run, ack) {
|
|
30019
|
+
if (!host.laneLedger || host.ledgerDisabled) {
|
|
30020
|
+
if (await run(ref.dispatchEventId))
|
|
30021
|
+
ack(ref.dispatchEventId);
|
|
30022
|
+
return;
|
|
30023
|
+
}
|
|
30024
|
+
let lane;
|
|
30025
|
+
try {
|
|
30026
|
+
lane = await host.laneLedger.claimTyped(ref);
|
|
30027
|
+
} catch (err) {
|
|
30028
|
+
if (err instanceof LedgerUnsupportedError) {
|
|
30029
|
+
host.disableLedger("claim endpoint missing");
|
|
30030
|
+
if (await run(ref.dispatchEventId))
|
|
30031
|
+
ack(ref.dispatchEventId);
|
|
30032
|
+
return;
|
|
30033
|
+
}
|
|
30034
|
+
throw err;
|
|
30035
|
+
}
|
|
30036
|
+
if (!lane) {
|
|
30037
|
+
host.opts.log?.info(`typed dispatch ${ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`} not claimable (held elsewhere or already resolved) \u2014 skipping`);
|
|
30038
|
+
return;
|
|
30039
|
+
}
|
|
30040
|
+
try {
|
|
30041
|
+
if (await run(lane.typedDispatchEventId))
|
|
30042
|
+
ack(lane.typedDispatchEventId);
|
|
30043
|
+
} finally {
|
|
30044
|
+
await host.laneLedger.completeIfIdle(lane.laneKey, false).catch(() => {
|
|
30045
|
+
});
|
|
30046
|
+
}
|
|
30047
|
+
}
|
|
30048
|
+
async function consumeMessageWorkItem(host, item) {
|
|
30049
|
+
if (host.shuttingDown)
|
|
30050
|
+
return;
|
|
30051
|
+
if (!host.tryClaimMessage(item.source_id))
|
|
30052
|
+
return;
|
|
30053
|
+
const ackItem = () => {
|
|
30054
|
+
host.opts.client.ackDispatchByID(host.opts.config.org_id, item.id).catch(() => {
|
|
30055
|
+
});
|
|
30056
|
+
};
|
|
30057
|
+
let msg = null;
|
|
30058
|
+
try {
|
|
30059
|
+
msg = await host.opts.client.getMessage(item.source_id);
|
|
30060
|
+
} catch (err) {
|
|
30061
|
+
const status = err?.status;
|
|
30062
|
+
if (status !== 404) {
|
|
30063
|
+
host.opts.log?.warn(`message fetch failed for ${item.source_id}, leaving pending: ${String(err)}`);
|
|
30064
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30065
|
+
return;
|
|
30066
|
+
}
|
|
30067
|
+
}
|
|
30068
|
+
if (!msg || msg.sender_id === host.opts.agentUserId) {
|
|
30069
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30070
|
+
ackItem();
|
|
30071
|
+
return;
|
|
30072
|
+
}
|
|
30073
|
+
const decision = await host.buildMessageDispatchDecision(item.chat_id, msg);
|
|
30074
|
+
if (decision.action === "retry") {
|
|
30075
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30076
|
+
return;
|
|
30077
|
+
}
|
|
30078
|
+
if (decision.action === "skip") {
|
|
30079
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30080
|
+
ackItem();
|
|
30081
|
+
return;
|
|
30082
|
+
}
|
|
30083
|
+
decision.event.dispatchEventId = item.id;
|
|
30084
|
+
const laneResolved = host.usesLaneLedger(decision.event);
|
|
30085
|
+
try {
|
|
30086
|
+
const dispatched = await host.handleInboundEvent(decision.event);
|
|
30087
|
+
if (dispatched) {
|
|
30088
|
+
if (!laneResolved)
|
|
30089
|
+
ackItem();
|
|
30090
|
+
} else {
|
|
30091
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30092
|
+
}
|
|
30093
|
+
} catch (err) {
|
|
30094
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30095
|
+
throw err;
|
|
30096
|
+
}
|
|
30097
|
+
}
|
|
30098
|
+
|
|
29616
30099
|
// ts/agent-core/dist/telemetry.js
|
|
29617
30100
|
init_esm();
|
|
29618
30101
|
var import_api_logs = __toESM(require_src(), 1);
|
|
@@ -29914,6 +30397,14 @@ var ParallAgentGateway = class {
|
|
|
29914
30397
|
inFlightDispatches = 0;
|
|
29915
30398
|
drainResolvers = [];
|
|
29916
30399
|
pendingRestartNotification = null;
|
|
30400
|
+
laneLedger;
|
|
30401
|
+
// Sticky fallback: flipped when the server predates the ledger (claim
|
|
30402
|
+
// endpoint 404) so every subsequent dispatch uses the legacy flow.
|
|
30403
|
+
ledgerDisabled = false;
|
|
30404
|
+
// Group key of the group currently being dispatched on main — lane-aware
|
|
30405
|
+
// (targetId + thread), unlike mainCurrentTargetId which stays chat-level
|
|
30406
|
+
// for fork routing decisions.
|
|
30407
|
+
mainCurrentGroupKey;
|
|
29917
30408
|
DISPATCHED_MESSAGES_CAP = 5e3;
|
|
29918
30409
|
// SHUTDOWN_DEADLINE_MS is read by waitForDrain via the configured value
|
|
29919
30410
|
// below — kept as instance state so per-runtime configs can override it
|
|
@@ -29923,6 +30414,14 @@ var ParallAgentGateway = class {
|
|
|
29923
30414
|
DISPATCH_DEADLINE_MS;
|
|
29924
30415
|
constructor(opts) {
|
|
29925
30416
|
this.opts = opts;
|
|
30417
|
+
if (opts.dispatchContextDir) {
|
|
30418
|
+
this.laneLedger = new LaneLedger({
|
|
30419
|
+
client: opts.client,
|
|
30420
|
+
orgId: opts.config.org_id,
|
|
30421
|
+
contextDir: opts.dispatchContextDir,
|
|
30422
|
+
log: opts.log
|
|
30423
|
+
});
|
|
30424
|
+
}
|
|
29926
30425
|
this.SHUTDOWN_DEADLINE_MS = opts.shutdownDeadlineMs ?? 6e4;
|
|
29927
30426
|
this.FORK_DEADLINE_MS = opts.forkDeadlineMs ?? 2 * 60 * 6e4;
|
|
29928
30427
|
this.DISPATCH_DEADLINE_MS = opts.dispatchDeadlineMs ?? 20 * 6e4;
|
|
@@ -29992,14 +30491,18 @@ var ParallAgentGateway = class {
|
|
|
29992
30491
|
if (data.status !== "todo" && data.status !== "in_progress")
|
|
29993
30492
|
return;
|
|
29994
30493
|
try {
|
|
29995
|
-
|
|
29996
|
-
|
|
30494
|
+
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) => {
|
|
30495
|
+
if (dispatchEventId) {
|
|
30496
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, dispatchEventId).catch(() => {
|
|
30497
|
+
});
|
|
30498
|
+
return;
|
|
30499
|
+
}
|
|
29997
30500
|
this.opts.client.ackDispatch(this.opts.config.org_id, {
|
|
29998
30501
|
source_type: "task_activity",
|
|
29999
30502
|
source_id: data.id
|
|
30000
30503
|
}).catch(() => {
|
|
30001
30504
|
});
|
|
30002
|
-
}
|
|
30505
|
+
});
|
|
30003
30506
|
} catch (err) {
|
|
30004
30507
|
this.opts.log?.error(`task dispatch failed for ${data.id}: ${String(err)}`);
|
|
30005
30508
|
}
|
|
@@ -30009,11 +30512,10 @@ var ParallAgentGateway = class {
|
|
|
30009
30512
|
if (!data.source_id || !data.task_id)
|
|
30010
30513
|
return;
|
|
30011
30514
|
try {
|
|
30012
|
-
|
|
30013
|
-
if (dispatched) {
|
|
30515
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleTaskComment(data.source_id, data.task_id ?? "", data.actor_id, data.delivery_reason), () => {
|
|
30014
30516
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30015
30517
|
});
|
|
30016
|
-
}
|
|
30518
|
+
});
|
|
30017
30519
|
} catch (err) {
|
|
30018
30520
|
this.opts.log?.error(`task comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30019
30521
|
}
|
|
@@ -30021,11 +30523,10 @@ var ParallAgentGateway = class {
|
|
|
30021
30523
|
if (!data.source_id)
|
|
30022
30524
|
return;
|
|
30023
30525
|
try {
|
|
30024
|
-
|
|
30025
|
-
if (dispatched) {
|
|
30526
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleWikiComment(data.source_id, data.actor_id, data.delivery_reason), () => {
|
|
30026
30527
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30027
30528
|
});
|
|
30028
|
-
}
|
|
30529
|
+
});
|
|
30029
30530
|
} catch (err) {
|
|
30030
30531
|
this.opts.log?.error(`wiki comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30031
30532
|
}
|
|
@@ -30033,11 +30534,13 @@ var ParallAgentGateway = class {
|
|
|
30033
30534
|
if (!data.task_id)
|
|
30034
30535
|
return;
|
|
30035
30536
|
try {
|
|
30036
|
-
|
|
30037
|
-
|
|
30537
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.handleTaskDispatch(data.task_id ?? "", data.source_id ?? data.task_id ?? "", {
|
|
30538
|
+
allowCreator: true,
|
|
30539
|
+
dispatchEventId
|
|
30540
|
+
}), () => {
|
|
30038
30541
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30039
30542
|
});
|
|
30040
|
-
}
|
|
30543
|
+
});
|
|
30041
30544
|
} catch (err) {
|
|
30042
30545
|
this.opts.log?.error(`task update dispatch failed for ${data.task_id}: ${String(err)}`);
|
|
30043
30546
|
}
|
|
@@ -30045,11 +30548,10 @@ var ParallAgentGateway = class {
|
|
|
30045
30548
|
if (!data.source_id)
|
|
30046
30549
|
return;
|
|
30047
30550
|
try {
|
|
30048
|
-
|
|
30049
|
-
if (dispatched) {
|
|
30551
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleScheduleFire(data.source_id, data.actor_id), () => {
|
|
30050
30552
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30051
30553
|
});
|
|
30052
|
-
}
|
|
30554
|
+
});
|
|
30053
30555
|
} catch (err) {
|
|
30054
30556
|
this.opts.log?.error(`schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30055
30557
|
}
|
|
@@ -30057,11 +30559,10 @@ var ParallAgentGateway = class {
|
|
|
30057
30559
|
if (!data.source_id)
|
|
30058
30560
|
return;
|
|
30059
30561
|
try {
|
|
30060
|
-
|
|
30061
|
-
if (dispatched) {
|
|
30562
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleExternalTriggerRun(data.source_id), () => {
|
|
30062
30563
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30063
30564
|
});
|
|
30064
|
-
}
|
|
30565
|
+
});
|
|
30065
30566
|
} catch (err) {
|
|
30066
30567
|
this.opts.log?.error(`external trigger dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30067
30568
|
}
|
|
@@ -30069,11 +30570,10 @@ var ParallAgentGateway = class {
|
|
|
30069
30570
|
if (!data.source_id)
|
|
30070
30571
|
return;
|
|
30071
30572
|
try {
|
|
30072
|
-
|
|
30073
|
-
if (dispatched) {
|
|
30573
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleChannelMessage(data.source_id), () => {
|
|
30074
30574
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30075
30575
|
});
|
|
30076
|
-
}
|
|
30576
|
+
});
|
|
30077
30577
|
} catch (err) {
|
|
30078
30578
|
this.opts.log?.error(`channel message dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30079
30579
|
}
|
|
@@ -30081,14 +30581,19 @@ var ParallAgentGateway = class {
|
|
|
30081
30581
|
if (!data.source_id)
|
|
30082
30582
|
return;
|
|
30083
30583
|
try {
|
|
30084
|
-
|
|
30085
|
-
if (dispatched) {
|
|
30584
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleApprovalDecided(data.source_id, data.actor_id, data.chat_id ?? null), () => {
|
|
30086
30585
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30087
30586
|
});
|
|
30088
|
-
}
|
|
30587
|
+
});
|
|
30089
30588
|
} catch (err) {
|
|
30090
30589
|
this.opts.log?.error(`approval decided dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30091
30590
|
}
|
|
30591
|
+
} else if (data.event_type === "message" && this.laneLedger && data.source_id && data.chat_id) {
|
|
30592
|
+
try {
|
|
30593
|
+
await this.handleMessageRedrive(data);
|
|
30594
|
+
} catch (err) {
|
|
30595
|
+
this.opts.log?.error(`message re-drive failed for ${data.source_id}: ${String(err)}`);
|
|
30596
|
+
}
|
|
30092
30597
|
} else if (data.event_type !== "message" && data.event_type !== "task_assign") {
|
|
30093
30598
|
this.opts.log?.info(`dispatch.new with unhandled event_type=${String(data.event_type)} (id=${data.id}) \u2014 no-op`);
|
|
30094
30599
|
}
|
|
@@ -30124,6 +30629,39 @@ var ParallAgentGateway = class {
|
|
|
30124
30629
|
source_id: sourceId
|
|
30125
30630
|
});
|
|
30126
30631
|
}
|
|
30632
|
+
/** True when this event's lifecycle is owned by the dispatch lane ledger. */
|
|
30633
|
+
usesLaneLedger(event) {
|
|
30634
|
+
return this.laneLedger != null && !this.ledgerDisabled && this.laneLedger.handles(event);
|
|
30635
|
+
}
|
|
30636
|
+
disableLedger(reason) {
|
|
30637
|
+
if (this.ledgerDisabled)
|
|
30638
|
+
return;
|
|
30639
|
+
this.ledgerDisabled = true;
|
|
30640
|
+
this.opts.log?.warn(`dispatch ledger unavailable (${reason}) \u2014 falling back to legacy received/ack flow`);
|
|
30641
|
+
}
|
|
30642
|
+
/**
|
|
30643
|
+
* Buffer grouping key. Lane-ledger message events group by full lane
|
|
30644
|
+
* identity (chat + thread) so a channel lane and a thread lane in the same
|
|
30645
|
+
* chat dispatch as separate turns with separate claims; everything else
|
|
30646
|
+
* keeps the historical chat-level grouping.
|
|
30647
|
+
*/
|
|
30648
|
+
dispatchGroupKey(event) {
|
|
30649
|
+
if (this.usesLaneLedger(event)) {
|
|
30650
|
+
return this.laneLedger.laneKeyFor(event);
|
|
30651
|
+
}
|
|
30652
|
+
return event.targetId;
|
|
30653
|
+
}
|
|
30654
|
+
// Lane-flow protocols live in gateway-lane-flow.ts; these thin delegates
|
|
30655
|
+
// keep call sites and tests on the class surface.
|
|
30656
|
+
laneFlowHost() {
|
|
30657
|
+
return this;
|
|
30658
|
+
}
|
|
30659
|
+
dispatchLaneGroup(opts) {
|
|
30660
|
+
return dispatchLaneGroup(this.laneFlowHost(), opts);
|
|
30661
|
+
}
|
|
30662
|
+
consumeTypedDispatch(ref, run, ack) {
|
|
30663
|
+
return consumeTypedDispatch(this.laneFlowHost(), ref, run, ack);
|
|
30664
|
+
}
|
|
30127
30665
|
buildDispatchContext(event, sessionKey) {
|
|
30128
30666
|
const binding = this.sessionBindings.get(sessionKey);
|
|
30129
30667
|
return {
|
|
@@ -30140,6 +30678,7 @@ var ParallAgentGateway = class {
|
|
|
30140
30678
|
noReply: event.noReply ?? false,
|
|
30141
30679
|
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey),
|
|
30142
30680
|
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey),
|
|
30681
|
+
contextDirPath: this.opts.dispatchContextDir,
|
|
30143
30682
|
client: this.opts.client,
|
|
30144
30683
|
log: this.opts.log
|
|
30145
30684
|
};
|
|
@@ -30179,7 +30718,7 @@ var ParallAgentGateway = class {
|
|
|
30179
30718
|
this.opts.log?.warn(`failed to create input step: ${String(err)}`);
|
|
30180
30719
|
}
|
|
30181
30720
|
}
|
|
30182
|
-
async createRuntimeStep(sessionId, event, runtimeEvent, stepIdFilePath, contextFilePath) {
|
|
30721
|
+
async createRuntimeStep(sessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2) {
|
|
30183
30722
|
const target = resolveStepTarget(event);
|
|
30184
30723
|
try {
|
|
30185
30724
|
switch (runtimeEvent.type) {
|
|
@@ -30225,6 +30764,9 @@ var ParallAgentGateway = class {
|
|
|
30225
30764
|
} else if (stepIdFilePath) {
|
|
30226
30765
|
this.writeStepIdFile(stepIdFilePath, step.id);
|
|
30227
30766
|
}
|
|
30767
|
+
if (laneContextFilePath2) {
|
|
30768
|
+
this.updateContextFileStepId(laneContextFilePath2, step.id);
|
|
30769
|
+
}
|
|
30228
30770
|
break;
|
|
30229
30771
|
}
|
|
30230
30772
|
case "tool_result":
|
|
@@ -30247,6 +30789,9 @@ var ParallAgentGateway = class {
|
|
|
30247
30789
|
} else if (stepIdFilePath) {
|
|
30248
30790
|
this.clearStepIdFile(stepIdFilePath);
|
|
30249
30791
|
}
|
|
30792
|
+
if (laneContextFilePath2) {
|
|
30793
|
+
this.updateContextFileStepId(laneContextFilePath2, null);
|
|
30794
|
+
}
|
|
30250
30795
|
break;
|
|
30251
30796
|
case "error":
|
|
30252
30797
|
await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
|
|
@@ -30266,28 +30811,28 @@ var ParallAgentGateway = class {
|
|
|
30266
30811
|
}
|
|
30267
30812
|
writeContextFile(filePath, ctx) {
|
|
30268
30813
|
try {
|
|
30269
|
-
|
|
30270
|
-
|
|
30814
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
30815
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30271
30816
|
} catch (err) {
|
|
30272
30817
|
this.opts.log?.warn(`failed to write context file ${filePath}: ${String(err)}`);
|
|
30273
30818
|
}
|
|
30274
30819
|
}
|
|
30275
30820
|
updateContextFileStepId(filePath, stepId) {
|
|
30276
30821
|
try {
|
|
30277
|
-
const raw =
|
|
30822
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
30278
30823
|
const ctx = JSON.parse(raw);
|
|
30279
30824
|
ctx.step_id = stepId;
|
|
30280
|
-
|
|
30825
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30281
30826
|
} catch (err) {
|
|
30282
30827
|
this.opts.log?.warn(`failed to update context file step_id ${filePath}: ${String(err)}`);
|
|
30283
30828
|
}
|
|
30284
30829
|
}
|
|
30285
30830
|
updateContextFileSessionId(filePath, sessionId) {
|
|
30286
30831
|
try {
|
|
30287
|
-
const raw =
|
|
30832
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
30288
30833
|
const ctx = JSON.parse(raw);
|
|
30289
30834
|
ctx.session_id = sessionId;
|
|
30290
|
-
|
|
30835
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30291
30836
|
} catch (err) {
|
|
30292
30837
|
this.opts.log?.warn(`failed to update context file session_id ${filePath}: ${String(err)}`);
|
|
30293
30838
|
}
|
|
@@ -30295,8 +30840,8 @@ var ParallAgentGateway = class {
|
|
|
30295
30840
|
/** @deprecated Use writeContextFile / updateContextFileStepId. */
|
|
30296
30841
|
writeStepIdFile(filePath, stepId) {
|
|
30297
30842
|
try {
|
|
30298
|
-
|
|
30299
|
-
|
|
30843
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
30844
|
+
fs2.writeFileSync(filePath, stepId, "utf8");
|
|
30300
30845
|
} catch (err) {
|
|
30301
30846
|
this.opts.log?.warn(`failed to write step id file ${filePath}: ${String(err)}`);
|
|
30302
30847
|
}
|
|
@@ -30304,7 +30849,7 @@ var ParallAgentGateway = class {
|
|
|
30304
30849
|
/** @deprecated Use writeContextFile / updateContextFileStepId. */
|
|
30305
30850
|
clearStepIdFile(filePath) {
|
|
30306
30851
|
try {
|
|
30307
|
-
|
|
30852
|
+
fs2.writeFileSync(filePath, "", "utf8");
|
|
30308
30853
|
} catch {
|
|
30309
30854
|
}
|
|
30310
30855
|
}
|
|
@@ -30313,7 +30858,7 @@ var ParallAgentGateway = class {
|
|
|
30313
30858
|
await this.createInputStep(sessionId, event);
|
|
30314
30859
|
}
|
|
30315
30860
|
}
|
|
30316
|
-
async bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath) {
|
|
30861
|
+
async bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2) {
|
|
30317
30862
|
const runtimeLaneKey = runtimeEvent.runtimeLaneKey || sessionKey;
|
|
30318
30863
|
const existing = this.sessionBindings.get(sessionKey);
|
|
30319
30864
|
if (existing && existing.runtimeLaneKey === runtimeLaneKey && existing.runtimeSessionId === runtimeEvent.runtimeSessionId) {
|
|
@@ -30357,6 +30902,9 @@ var ParallAgentGateway = class {
|
|
|
30357
30902
|
if (contextFilePath) {
|
|
30358
30903
|
this.updateContextFileSessionId(contextFilePath, session.id);
|
|
30359
30904
|
}
|
|
30905
|
+
if (laneContextFilePath2) {
|
|
30906
|
+
this.updateContextFileSessionId(laneContextFilePath2, session.id);
|
|
30907
|
+
}
|
|
30360
30908
|
await this.opts.onSessionBinding?.(binding);
|
|
30361
30909
|
return binding;
|
|
30362
30910
|
}
|
|
@@ -30383,14 +30931,27 @@ var ParallAgentGateway = class {
|
|
|
30383
30931
|
const dispatchContext = this.buildDispatchContext(event, sessionKey);
|
|
30384
30932
|
const contextFilePath = dispatchContext.contextFilePath;
|
|
30385
30933
|
const stepIdFilePath = dispatchContext.stepIdFilePath;
|
|
30934
|
+
const activeLane = this.ledgerDisabled ? void 0 : this.laneLedger?.getForEvent(event);
|
|
30935
|
+
const laneContextFilePath2 = activeLane ? this.laneLedger?.laneContextPath(activeLane) : void 0;
|
|
30936
|
+
const contextBody = {
|
|
30937
|
+
session_id: dispatchContext.sessionId ?? null,
|
|
30938
|
+
chat_id: dispatchContext.chatId ?? null,
|
|
30939
|
+
trigger_message_id: dispatchContext.triggerMessageId ?? null,
|
|
30940
|
+
no_reply: dispatchContext.noReply,
|
|
30941
|
+
step_id: null,
|
|
30942
|
+
dispatch_event_id: activeLane?.typedDispatchEventId ?? activeLane?.folded.get(event.messageId) ?? null,
|
|
30943
|
+
lane: activeLane?.lane ?? null,
|
|
30944
|
+
target_uri: activeLane?.targetUri ?? null,
|
|
30945
|
+
thread_root_id: activeLane?.threadRootId ?? null,
|
|
30946
|
+
// Typed binding hint for the CLI: which task this dispatch is about
|
|
30947
|
+
// (parall task update attaches the typed effect only on a match).
|
|
30948
|
+
task_id: event.type === "task" ? event.targetId : null
|
|
30949
|
+
};
|
|
30386
30950
|
if (contextFilePath) {
|
|
30387
|
-
this.writeContextFile(contextFilePath,
|
|
30388
|
-
|
|
30389
|
-
|
|
30390
|
-
|
|
30391
|
-
no_reply: dispatchContext.noReply,
|
|
30392
|
-
step_id: null
|
|
30393
|
-
});
|
|
30951
|
+
this.writeContextFile(contextFilePath, contextBody);
|
|
30952
|
+
}
|
|
30953
|
+
if (laneContextFilePath2) {
|
|
30954
|
+
this.writeContextFile(laneContextFilePath2, contextBody);
|
|
30394
30955
|
}
|
|
30395
30956
|
this.inFlightDispatches++;
|
|
30396
30957
|
const deadlineTimer = this.DISPATCH_DEADLINE_MS > 0 ? setTimeout(() => {
|
|
@@ -30417,7 +30978,7 @@ var ParallAgentGateway = class {
|
|
|
30417
30978
|
})) {
|
|
30418
30979
|
if (runtimeEvent.type === "runtime_session") {
|
|
30419
30980
|
const priorAgentSessionId = binding?.agentSessionId;
|
|
30420
|
-
binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath);
|
|
30981
|
+
binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2);
|
|
30421
30982
|
if (event.targetType === "channel_conversation" && binding.agentSessionId !== priorAgentSessionId) {
|
|
30422
30983
|
try {
|
|
30423
30984
|
await this.opts.client.setChannelConversationSession(this.opts.config.org_id, event.targetId, binding.agentSessionId);
|
|
@@ -30449,6 +31010,9 @@ var ParallAgentGateway = class {
|
|
|
30449
31010
|
await this.createInputStep(binding.agentSessionId, event);
|
|
30450
31011
|
inputStepsCreated = true;
|
|
30451
31012
|
}
|
|
31013
|
+
if (activeLane && !this.ledgerDisabled) {
|
|
31014
|
+
this.laneLedger?.maybeRenew(activeLane);
|
|
31015
|
+
}
|
|
30452
31016
|
if (captureText && runtimeEvent.type === "text" && runtimeEvent.text) {
|
|
30453
31017
|
captureText.push(runtimeEvent.text);
|
|
30454
31018
|
}
|
|
@@ -30465,7 +31029,7 @@ var ParallAgentGateway = class {
|
|
|
30465
31029
|
} else if (runtimeEvent.type === "tool_result" && pendingSendCallIds.delete(runtimeEvent.callId)) {
|
|
30466
31030
|
recordMessageSend(sessionKey, !runtimeEvent.error);
|
|
30467
31031
|
}
|
|
30468
|
-
await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath);
|
|
31032
|
+
await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
30469
31033
|
}
|
|
30470
31034
|
if (!binding) {
|
|
30471
31035
|
binding = this.sessionBindings.get(sessionKey);
|
|
@@ -30487,7 +31051,7 @@ var ParallAgentGateway = class {
|
|
|
30487
31051
|
await this.createRuntimeStep(binding.agentSessionId, event, {
|
|
30488
31052
|
type: "error",
|
|
30489
31053
|
message: `Dispatch failed: ${String(err)}`
|
|
30490
|
-
}, stepIdFilePath, contextFilePath);
|
|
31054
|
+
}, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
30491
31055
|
} catch (stepErr) {
|
|
30492
31056
|
if (this.isSessionNotLiveError(stepErr))
|
|
30493
31057
|
staleDetected = true;
|
|
@@ -30530,6 +31094,9 @@ var ParallAgentGateway = class {
|
|
|
30530
31094
|
} else if (stepIdFilePath) {
|
|
30531
31095
|
this.clearStepIdFile(stepIdFilePath);
|
|
30532
31096
|
}
|
|
31097
|
+
if (laneContextFilePath2) {
|
|
31098
|
+
this.updateContextFileStepId(laneContextFilePath2, null);
|
|
31099
|
+
}
|
|
30533
31100
|
this.inFlightDispatches--;
|
|
30534
31101
|
if (this.inFlightDispatches === 0 && this.drainResolvers.length > 0) {
|
|
30535
31102
|
const resolvers = this.drainResolvers.splice(0);
|
|
@@ -30587,13 +31154,39 @@ var ParallAgentGateway = class {
|
|
|
30587
31154
|
item.resolve(false);
|
|
30588
31155
|
break;
|
|
30589
31156
|
}
|
|
30590
|
-
|
|
31157
|
+
let items;
|
|
31158
|
+
const head = fork.queue[0];
|
|
31159
|
+
if (head && this.usesLaneLedger(head.event)) {
|
|
31160
|
+
const headKey = this.dispatchGroupKey(head.event);
|
|
31161
|
+
const splitAt = fork.queue.findIndex((it) => this.dispatchGroupKey(it.event) !== headKey);
|
|
31162
|
+
items = splitAt === -1 ? fork.queue.splice(0) : fork.queue.splice(0, splitAt);
|
|
31163
|
+
} else {
|
|
31164
|
+
items = fork.queue.splice(0);
|
|
31165
|
+
}
|
|
30591
31166
|
const events = items.map((item) => item.event);
|
|
30592
31167
|
const last = events[events.length - 1];
|
|
30593
31168
|
const earlier = events.slice(0, -1);
|
|
30594
31169
|
try {
|
|
30595
31170
|
const batchText = [];
|
|
30596
|
-
|
|
31171
|
+
let dispatched;
|
|
31172
|
+
if (this.usesLaneLedger(last)) {
|
|
31173
|
+
const outcome = await this.dispatchLaneGroup({
|
|
31174
|
+
events,
|
|
31175
|
+
sessionKey: fork.fork.sessionKey,
|
|
31176
|
+
body: buildForkScopePrefix(last) + buildEventBody(last),
|
|
31177
|
+
earlier,
|
|
31178
|
+
captureText: batchText,
|
|
31179
|
+
hasMoreLocal: () => fork.queue.length > 0
|
|
31180
|
+
});
|
|
31181
|
+
if (outcome === "foreign") {
|
|
31182
|
+
for (const item of items)
|
|
31183
|
+
item.resolve(false);
|
|
31184
|
+
break;
|
|
31185
|
+
}
|
|
31186
|
+
dispatched = outcome === "dispatched";
|
|
31187
|
+
} else {
|
|
31188
|
+
dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildForkScopePrefix(last) + buildEventBody(last), earlier, batchText);
|
|
31189
|
+
}
|
|
30597
31190
|
if (!dispatched) {
|
|
30598
31191
|
for (const item of items) {
|
|
30599
31192
|
item.resolve(false);
|
|
@@ -30696,9 +31289,9 @@ var ParallAgentGateway = class {
|
|
|
30696
31289
|
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
31290
|
break;
|
|
30698
31291
|
}
|
|
30699
|
-
const
|
|
31292
|
+
const groupKey = this.dispatchGroupKey(this.dispatchState.mainBuffer[0]);
|
|
30700
31293
|
const events = [];
|
|
30701
|
-
while (this.dispatchState.mainBuffer[0]
|
|
31294
|
+
while (this.dispatchState.mainBuffer[0] && this.dispatchGroupKey(this.dispatchState.mainBuffer[0]) === groupKey) {
|
|
30702
31295
|
events.push(this.dispatchState.mainBuffer.shift());
|
|
30703
31296
|
}
|
|
30704
31297
|
const event = events[events.length - 1];
|
|
@@ -30707,7 +31300,36 @@ var ParallAgentGateway = class {
|
|
|
30707
31300
|
const pendingFork = hasPendingInjections ? [] : this.dispatchState.pendingForkResults.splice(0);
|
|
30708
31301
|
const forkPrefix = buildForkResultPrefix(pendingFork);
|
|
30709
31302
|
this.dispatchState.mainCurrentTargetId = event.targetId;
|
|
31303
|
+
this.mainCurrentGroupKey = groupKey;
|
|
30710
31304
|
this.dispatchState.mainPreDispatchBranchPoint = this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
|
|
31305
|
+
if (this.usesLaneLedger(event)) {
|
|
31306
|
+
let outcome;
|
|
31307
|
+
try {
|
|
31308
|
+
outcome = await this.dispatchLaneGroup({
|
|
31309
|
+
events,
|
|
31310
|
+
sessionKey: this.opts.runtimeKey,
|
|
31311
|
+
body: forkPrefix + buildEventBody(event),
|
|
31312
|
+
earlier,
|
|
31313
|
+
hasMoreLocal: () => this.dispatchState.mainBuffer.some((e) => this.dispatchGroupKey(e) === groupKey)
|
|
31314
|
+
});
|
|
31315
|
+
} catch (err) {
|
|
31316
|
+
this.opts.log?.error(`lane dispatch failed for ${event.messageId}: ${String(err)}`);
|
|
31317
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31318
|
+
for (const ev of events)
|
|
31319
|
+
this.dispatchedMessages.delete(ev.messageId);
|
|
31320
|
+
continue;
|
|
31321
|
+
}
|
|
31322
|
+
if (outcome === "shutdown") {
|
|
31323
|
+
this.dispatchState.mainBuffer.unshift(...events);
|
|
31324
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31325
|
+
break;
|
|
31326
|
+
}
|
|
31327
|
+
if (outcome === "foreign") {
|
|
31328
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31329
|
+
continue;
|
|
31330
|
+
}
|
|
31331
|
+
continue;
|
|
31332
|
+
}
|
|
30711
31333
|
try {
|
|
30712
31334
|
await this.emitDispatchReceived(event);
|
|
30713
31335
|
} catch (err) {
|
|
@@ -30736,6 +31358,7 @@ var ParallAgentGateway = class {
|
|
|
30736
31358
|
this.draining = false;
|
|
30737
31359
|
this.dispatchState.mainDispatching = false;
|
|
30738
31360
|
this.dispatchState.mainCurrentTargetId = void 0;
|
|
31361
|
+
this.mainCurrentGroupKey = void 0;
|
|
30739
31362
|
this.dispatchState.mainPreDispatchBranchPoint = void 0;
|
|
30740
31363
|
if (!this.shuttingDown && this.dispatchState.mainBuffer.length > 0) {
|
|
30741
31364
|
setTimeout(() => {
|
|
@@ -30755,13 +31378,38 @@ var ParallAgentGateway = class {
|
|
|
30755
31378
|
const forkPrefix = buildForkResultPrefix(pendingFork);
|
|
30756
31379
|
this.dispatchState.mainDispatching = true;
|
|
30757
31380
|
this.dispatchState.mainCurrentTargetId = event.targetId;
|
|
31381
|
+
this.mainCurrentGroupKey = this.dispatchGroupKey(event);
|
|
30758
31382
|
this.dispatchState.mainPreDispatchBranchPoint = this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
|
|
31383
|
+
if (this.usesLaneLedger(event)) {
|
|
31384
|
+
let outcome = "shutdown";
|
|
31385
|
+
try {
|
|
31386
|
+
try {
|
|
31387
|
+
outcome = await this.dispatchLaneGroup({
|
|
31388
|
+
events: [event],
|
|
31389
|
+
sessionKey: this.opts.runtimeKey,
|
|
31390
|
+
body: forkPrefix + buildEventBody(event),
|
|
31391
|
+
earlier: [],
|
|
31392
|
+
hasMoreLocal: () => this.dispatchState.mainBuffer.some((e) => this.dispatchGroupKey(e) === this.dispatchGroupKey(event))
|
|
31393
|
+
});
|
|
31394
|
+
} catch (err) {
|
|
31395
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31396
|
+
throw err;
|
|
31397
|
+
}
|
|
31398
|
+
if (outcome !== "dispatched") {
|
|
31399
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31400
|
+
}
|
|
31401
|
+
} finally {
|
|
31402
|
+
await this.drainMainBuffer();
|
|
31403
|
+
}
|
|
31404
|
+
return outcome === "dispatched";
|
|
31405
|
+
}
|
|
30759
31406
|
try {
|
|
30760
31407
|
await this.emitDispatchReceived(event);
|
|
30761
31408
|
} catch (err) {
|
|
30762
31409
|
this.opts.log?.warn?.(`mark-received failed, leaving unacked for retry: ${String(err)}`);
|
|
30763
31410
|
this.dispatchState.mainDispatching = false;
|
|
30764
31411
|
this.dispatchState.mainCurrentTargetId = void 0;
|
|
31412
|
+
this.mainCurrentGroupKey = void 0;
|
|
30765
31413
|
this.dispatchState.mainPreDispatchBranchPoint = void 0;
|
|
30766
31414
|
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
30767
31415
|
return false;
|
|
@@ -30782,7 +31430,11 @@ var ParallAgentGateway = class {
|
|
|
30782
31430
|
return false;
|
|
30783
31431
|
}
|
|
30784
31432
|
this.dispatchState.mainBuffer.push(event);
|
|
30785
|
-
if (this.
|
|
31433
|
+
if (this.usesLaneLedger(event)) {
|
|
31434
|
+
if (this.mainCurrentGroupKey === this.dispatchGroupKey(event) && await this.laneLedger?.steerLive(event) && await this.opts.dispatchAdapter.enqueueDuringDispatch?.(this.opts.runtimeKey, buildEventBody(event))) {
|
|
31435
|
+
this.opts.log?.info(`steer folded+injected for ${event.messageId} (will drain for bookkeeping)`);
|
|
31436
|
+
}
|
|
31437
|
+
} else if (this.dispatchState.mainCurrentTargetId === event.targetId && await this.opts.dispatchAdapter.enqueueDuringDispatch?.(this.opts.runtimeKey, buildEventBody(event))) {
|
|
30786
31438
|
this.opts.log?.info(`steer injected for ${event.messageId} (will drain for bookkeeping)`);
|
|
30787
31439
|
}
|
|
30788
31440
|
if (!this.dispatchState.mainDispatching && !this.draining && this.dispatchState.mainBuffer.length > 0) {
|
|
@@ -30953,8 +31605,10 @@ var ParallAgentGateway = class {
|
|
|
30953
31605
|
try {
|
|
30954
31606
|
const dispatched = await this.handleInboundEvent(event);
|
|
30955
31607
|
if (dispatched) {
|
|
30956
|
-
|
|
30957
|
-
|
|
31608
|
+
if (!this.usesLaneLedger(event)) {
|
|
31609
|
+
this.opts.client.ackDispatch(this.opts.config.org_id, { source_type: "message", source_id: data.id }).catch(() => {
|
|
31610
|
+
});
|
|
31611
|
+
}
|
|
30958
31612
|
} else {
|
|
30959
31613
|
this.dispatchedMessages.delete(data.id);
|
|
30960
31614
|
}
|
|
@@ -30963,7 +31617,21 @@ var ParallAgentGateway = class {
|
|
|
30963
31617
|
this.dispatchedMessages.delete(data.id);
|
|
30964
31618
|
}
|
|
30965
31619
|
}
|
|
30966
|
-
|
|
31620
|
+
// Ledger re-drive consumption: dispatch.new message hints re-enter the
|
|
31621
|
+
// shared WorkItem consumption path (same protocol as catch-up).
|
|
31622
|
+
async handleMessageRedrive(item) {
|
|
31623
|
+
if (!item.chat_id || !item.source_id)
|
|
31624
|
+
return;
|
|
31625
|
+
await this.consumeMessageWorkItem({
|
|
31626
|
+
id: item.id,
|
|
31627
|
+
source_id: item.source_id,
|
|
31628
|
+
chat_id: item.chat_id
|
|
31629
|
+
});
|
|
31630
|
+
}
|
|
31631
|
+
consumeMessageWorkItem(item) {
|
|
31632
|
+
return consumeMessageWorkItem(this.laneFlowHost(), item);
|
|
31633
|
+
}
|
|
31634
|
+
async handleTaskAssignment(task, ackSourceId, dispatchEventId) {
|
|
30967
31635
|
if (this.shuttingDown)
|
|
30968
31636
|
return false;
|
|
30969
31637
|
const dedupeKey = `${task.id}:${task.updated_at}`;
|
|
@@ -30992,7 +31660,8 @@ var ParallAgentGateway = class {
|
|
|
30992
31660
|
body: parts.join("\n"),
|
|
30993
31661
|
sentAt: task.updated_at ?? task.created_at,
|
|
30994
31662
|
ackSourceType: "task_activity",
|
|
30995
|
-
ackSourceId
|
|
31663
|
+
ackSourceId,
|
|
31664
|
+
dispatchEventId
|
|
30996
31665
|
};
|
|
30997
31666
|
const dispatched = await this.handleInboundEvent(event);
|
|
30998
31667
|
if (!dispatched) {
|
|
@@ -31016,7 +31685,7 @@ var ParallAgentGateway = class {
|
|
|
31016
31685
|
this.opts.log?.info(`skipping stale task dispatch ${ackSourceId ?? taskId} \u2014 assigned to ${task.assignee_id}, creator ${task.creator_id}`);
|
|
31017
31686
|
return true;
|
|
31018
31687
|
}
|
|
31019
|
-
return this.handleTaskAssignment(task, ackSourceId);
|
|
31688
|
+
return this.handleTaskAssignment(task, ackSourceId, opts.dispatchEventId);
|
|
31020
31689
|
}
|
|
31021
31690
|
async handleTaskComment(commentId, taskId, actorId, deliveryReason) {
|
|
31022
31691
|
if (this.shuttingDown)
|
|
@@ -31424,74 +32093,46 @@ var ParallAgentGateway = class {
|
|
|
31424
32093
|
}
|
|
31425
32094
|
processed++;
|
|
31426
32095
|
try {
|
|
31427
|
-
|
|
32096
|
+
const ackItem = () => {
|
|
32097
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
32098
|
+
});
|
|
32099
|
+
};
|
|
31428
32100
|
if (item.event_type === "task_assign" && item.task_id) {
|
|
31429
32101
|
try {
|
|
31430
|
-
|
|
32102
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
|
|
32103
|
+
dispatchEventId
|
|
32104
|
+
}), ackItem);
|
|
31431
32105
|
} catch (err) {
|
|
31432
32106
|
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
31433
32107
|
continue;
|
|
31434
32108
|
}
|
|
31435
32109
|
} else if (item.event_type === "task_update" && item.task_id) {
|
|
31436
32110
|
try {
|
|
31437
|
-
|
|
32111
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
|
|
32112
|
+
allowCreator: true,
|
|
32113
|
+
dispatchEventId
|
|
32114
|
+
}), ackItem);
|
|
31438
32115
|
} catch (err) {
|
|
31439
32116
|
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
31440
32117
|
continue;
|
|
31441
32118
|
}
|
|
31442
32119
|
} else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
|
|
31443
|
-
|
|
32120
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleTaskComment(item.source_id, item.task_id ?? "", item.actor_id, item.delivery_reason), ackItem);
|
|
31444
32121
|
} else if (item.event_type === "wiki_comment" && item.source_id) {
|
|
31445
|
-
|
|
32122
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleWikiComment(item.source_id, item.actor_id, item.delivery_reason), ackItem);
|
|
31446
32123
|
} else if (item.event_type === "schedule.fire" && item.source_id) {
|
|
31447
|
-
|
|
32124
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleScheduleFire(item.source_id, item.actor_id), ackItem);
|
|
31448
32125
|
} else if (item.event_type === "external_trigger" && item.source_id) {
|
|
31449
|
-
|
|
32126
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleExternalTriggerRun(item.source_id), ackItem);
|
|
31450
32127
|
} else if (item.event_type === "channel_message" && item.source_id) {
|
|
31451
|
-
|
|
32128
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleChannelMessage(item.source_id), ackItem);
|
|
31452
32129
|
} else if (item.event_type === "approval_decided" && item.source_id) {
|
|
31453
|
-
|
|
32130
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null), ackItem);
|
|
31454
32131
|
} 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(() => {
|
|
32132
|
+
await this.consumeMessageWorkItem({
|
|
32133
|
+
id: item.id,
|
|
32134
|
+
source_id: item.source_id,
|
|
32135
|
+
chat_id: item.chat_id
|
|
31495
32136
|
});
|
|
31496
32137
|
}
|
|
31497
32138
|
} catch (err) {
|
|
@@ -31557,6 +32198,14 @@ ${fullSummary}` : fullSummary;
|
|
|
31557
32198
|
this.abortFork(targetId, "ws reconnect");
|
|
31558
32199
|
}
|
|
31559
32200
|
}
|
|
32201
|
+
if (this.laneLedger && this.inFlightDispatches === 0 && this.laneLedger.activeCount > 0) {
|
|
32202
|
+
log2?.info(`releasing ${this.laneLedger.activeCount} stale lane(s) on reconnect`);
|
|
32203
|
+
await this.laneLedger.releaseAll();
|
|
32204
|
+
}
|
|
32205
|
+
if (this.laneLedger && this.ledgerDisabled) {
|
|
32206
|
+
log2?.info("re-probing dispatch ledger after reconnect (was disabled)");
|
|
32207
|
+
this.ledgerDisabled = false;
|
|
32208
|
+
}
|
|
31560
32209
|
const intervalSec = data.heartbeat_interval > 0 ? data.heartbeat_interval : 30;
|
|
31561
32210
|
try {
|
|
31562
32211
|
const count = await fetchAllChats(client, config.org_id, this.chatInfoMap);
|
|
@@ -31627,6 +32276,10 @@ ${fullSummary}` : fullSummary;
|
|
|
31627
32276
|
}
|
|
31628
32277
|
if (this.heartbeatTimer)
|
|
31629
32278
|
clearInterval(this.heartbeatTimer);
|
|
32279
|
+
if (this.laneLedger && this.laneLedger.activeCount > 0) {
|
|
32280
|
+
this.opts.log?.info(`releasing ${this.laneLedger.activeCount} lane(s) on shutdown`);
|
|
32281
|
+
await this.laneLedger.releaseAll();
|
|
32282
|
+
}
|
|
31630
32283
|
await this.opts.onBeforeDisconnect?.();
|
|
31631
32284
|
this.opts.ws.disconnect();
|
|
31632
32285
|
this.opts.log?.info(`disconnected`);
|
|
@@ -31634,8 +32287,8 @@ ${fullSummary}` : fullSummary;
|
|
|
31634
32287
|
};
|
|
31635
32288
|
|
|
31636
32289
|
// ts/agent-core/dist/platform-config.js
|
|
31637
|
-
import * as
|
|
31638
|
-
import * as
|
|
32290
|
+
import * as fs3 from "node:fs";
|
|
32291
|
+
import * as path3 from "node:path";
|
|
31639
32292
|
function isPlatformManagedProfile(profile) {
|
|
31640
32293
|
return profile?.machine_id != null || profile?.model_management === "platform";
|
|
31641
32294
|
}
|
|
@@ -31652,11 +32305,11 @@ function deriveModelIsPin(defaults, profile) {
|
|
|
31652
32305
|
var CACHE_FILENAME = "parall-platform-config.json";
|
|
31653
32306
|
var SUPPORTED_SCHEMA_VERSION = 1;
|
|
31654
32307
|
function cachePath(stateDir) {
|
|
31655
|
-
return
|
|
32308
|
+
return path3.join(stateDir, CACHE_FILENAME);
|
|
31656
32309
|
}
|
|
31657
32310
|
function loadCache(stateDir) {
|
|
31658
32311
|
try {
|
|
31659
|
-
const raw =
|
|
32312
|
+
const raw = fs3.readFileSync(cachePath(stateDir), "utf-8");
|
|
31660
32313
|
return JSON.parse(raw);
|
|
31661
32314
|
} catch {
|
|
31662
32315
|
return null;
|
|
@@ -31671,9 +32324,9 @@ function saveCache(stateDir, response) {
|
|
|
31671
32324
|
};
|
|
31672
32325
|
const filePath = cachePath(stateDir);
|
|
31673
32326
|
const tmpPath = `${filePath}.tmp`;
|
|
31674
|
-
|
|
31675
|
-
|
|
31676
|
-
|
|
32327
|
+
fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
32328
|
+
fs3.writeFileSync(tmpPath, JSON.stringify(cached, null, 2), "utf-8");
|
|
32329
|
+
fs3.renameSync(tmpPath, filePath);
|
|
31677
32330
|
}
|
|
31678
32331
|
function runtimeModelName(canonicalModel, runtimeType, config) {
|
|
31679
32332
|
const runtime = runtimeType?.trim();
|
|
@@ -31764,8 +32417,8 @@ function createPlatformConfigManager(opts) {
|
|
|
31764
32417
|
}
|
|
31765
32418
|
|
|
31766
32419
|
// ts/agent-core/dist/skills/index.js
|
|
31767
|
-
import * as
|
|
31768
|
-
import * as
|
|
32420
|
+
import * as fs4 from "node:fs";
|
|
32421
|
+
import * as path4 from "node:path";
|
|
31769
32422
|
|
|
31770
32423
|
// ts/agent-core/dist/skills/parall-platform.js
|
|
31771
32424
|
var PARALL_PLATFORM_SKILL = `# Parall Platform
|
|
@@ -32538,13 +33191,13 @@ var SKILLS = [
|
|
|
32538
33191
|
}
|
|
32539
33192
|
];
|
|
32540
33193
|
function writeSkillFiles(targetDir) {
|
|
32541
|
-
|
|
33194
|
+
fs4.mkdirSync(targetDir, { recursive: true });
|
|
32542
33195
|
for (const skill of SKILLS) {
|
|
32543
|
-
|
|
33196
|
+
fs4.writeFileSync(path4.join(targetDir, `${skill.name}.md`), skill.content, "utf8");
|
|
32544
33197
|
}
|
|
32545
33198
|
}
|
|
32546
33199
|
function buildSkillReferences(workspaceDir) {
|
|
32547
|
-
const dir =
|
|
33200
|
+
const dir = path4.join(workspaceDir, ".parall", "skills");
|
|
32548
33201
|
const lines = SKILLS.map((s) => `- ${s.description.split(":")[0]}: \`${dir}/${s.name}.md\``);
|
|
32549
33202
|
return `## Platform Skills (read on demand)
|
|
32550
33203
|
|
|
@@ -32554,7 +33207,7 @@ ${lines.join("\n")}
|
|
|
32554
33207
|
|
|
32555
33208
|
// ts/claude-agent/dist/config.js
|
|
32556
33209
|
import * as os2 from "node:os";
|
|
32557
|
-
import * as
|
|
33210
|
+
import * as path5 from "node:path";
|
|
32558
33211
|
function requireEnv(env, name) {
|
|
32559
33212
|
const value = env[name]?.trim();
|
|
32560
33213
|
if (!value) {
|
|
@@ -32568,15 +33221,15 @@ function parseList(value) {
|
|
|
32568
33221
|
return value.split(/[,\n]/).map((item) => item.trim()).filter(Boolean);
|
|
32569
33222
|
}
|
|
32570
33223
|
function resolvePath(value) {
|
|
32571
|
-
return
|
|
33224
|
+
return path5.isAbsolute(value) ? value : path5.resolve(process.cwd(), value);
|
|
32572
33225
|
}
|
|
32573
33226
|
function resolveClaudeAgentConfig(env = process.env) {
|
|
32574
33227
|
const apiUrl = requireEnv(env, "PRLL_API_URL");
|
|
32575
33228
|
const apiKey = requireEnv(env, "PRLL_API_KEY");
|
|
32576
33229
|
const orgId = requireEnv(env, "PRLL_ORG_ID");
|
|
32577
33230
|
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() ||
|
|
33231
|
+
const stateDir = resolvePath(env.PRLL_STATE_DIR?.trim() || path5.join(claudeHome, ".parall-agent"));
|
|
33232
|
+
const workspaceDir = resolvePath(env.PRLL_WORKSPACE_DIR?.trim() || path5.join(stateDir, "workspace"));
|
|
32580
33233
|
const additionalDirs = parseList(env.PRLL_CLAUDE_ADD_DIRS).map(resolvePath);
|
|
32581
33234
|
return {
|
|
32582
33235
|
apiUrl,
|
|
@@ -32611,20 +33264,23 @@ function buildClaudeRuntimeKey(agentUserId) {
|
|
|
32611
33264
|
}
|
|
32612
33265
|
function sessionStateFilePathForRuntime(stateDir, runtimeKey) {
|
|
32613
33266
|
const fileName = Buffer.from(runtimeKey).toString("base64url");
|
|
32614
|
-
return
|
|
33267
|
+
return path5.join(stateDir, "sessions", `${fileName}.json`);
|
|
32615
33268
|
}
|
|
32616
33269
|
function contextFilePathForSession(stateDir, sessionKey) {
|
|
32617
33270
|
const fileName = Buffer.from(sessionKey).toString("base64url");
|
|
32618
|
-
return
|
|
33271
|
+
return path5.join(stateDir, "dispatch-context", `${fileName}.json`);
|
|
33272
|
+
}
|
|
33273
|
+
function dispatchContextDirPath(stateDir) {
|
|
33274
|
+
return dispatchLaneContextDir(stateDir);
|
|
32619
33275
|
}
|
|
32620
33276
|
function stepIdFilePathForSession(stateDir, sessionKey) {
|
|
32621
33277
|
const fileName = Buffer.from(sessionKey).toString("base64url");
|
|
32622
|
-
return
|
|
33278
|
+
return path5.join(stateDir, "step-ids", `${fileName}.txt`);
|
|
32623
33279
|
}
|
|
32624
33280
|
|
|
32625
33281
|
// ts/claude-agent/dist/dispatch.js
|
|
32626
|
-
import * as
|
|
32627
|
-
import * as
|
|
33282
|
+
import * as fs6 from "node:fs";
|
|
33283
|
+
import * as path7 from "node:path";
|
|
32628
33284
|
import { randomUUID } from "node:crypto";
|
|
32629
33285
|
import { execSync as execSync2, spawn } from "node:child_process";
|
|
32630
33286
|
|
|
@@ -32632,8 +33288,8 @@ import { execSync as execSync2, spawn } from "node:child_process";
|
|
|
32632
33288
|
import { execSync } from "node:child_process";
|
|
32633
33289
|
import { constants } from "node:fs";
|
|
32634
33290
|
import * as fsSync from "node:fs";
|
|
32635
|
-
import * as
|
|
32636
|
-
import * as
|
|
33291
|
+
import * as fs5 from "node:fs/promises";
|
|
33292
|
+
import * as path6 from "node:path";
|
|
32637
33293
|
var DEFAULT_MAX_TOTAL_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
32638
33294
|
var DEFAULT_ATTACHMENT_CACHE_MAX_BYTES = 512 * 1024 * 1024;
|
|
32639
33295
|
var DEFAULT_ATTACHMENT_DOWNLOAD_TIMEOUT_MS = 3e4;
|
|
@@ -32663,11 +33319,11 @@ async function prepareLocalImageAttachments(event, context2, opts) {
|
|
|
32663
33319
|
};
|
|
32664
33320
|
}
|
|
32665
33321
|
const rootDir = await ensureAttachmentRootDir(opts.workspaceDir);
|
|
32666
|
-
const messageDir =
|
|
33322
|
+
const messageDir = path6.join(rootDir, sanitizePathSegment(event.messageId));
|
|
32667
33323
|
await ensurePathIsNotSymlink(messageDir);
|
|
32668
|
-
await
|
|
33324
|
+
await fs5.mkdir(messageDir, { recursive: true });
|
|
32669
33325
|
await ensurePathIsNotSymlink(messageDir);
|
|
32670
|
-
const activeMessageDir =
|
|
33326
|
+
const activeMessageDir = path6.resolve(messageDir);
|
|
32671
33327
|
activeAttachmentDirs.add(activeMessageDir);
|
|
32672
33328
|
const maintenanceCooldownMs = opts.maintenanceCooldownMs ?? DEFAULT_MAINTENANCE_COOLDOWN_MS;
|
|
32673
33329
|
const maintenancePromise = scheduleAttachmentMaintenance(rootDir, {
|
|
@@ -32684,7 +33340,7 @@ async function prepareLocalImageAttachments(event, context2, opts) {
|
|
|
32684
33340
|
const notes = [];
|
|
32685
33341
|
let downloadedBytes = 0;
|
|
32686
33342
|
for (const att of imageAttachments) {
|
|
32687
|
-
const localPath =
|
|
33343
|
+
const localPath = path6.join(messageDir, localFileName(att.id, att.fileName, att.mimeType));
|
|
32688
33344
|
const downloadTimeoutMs = opts.downloadTimeoutMs ?? DEFAULT_ATTACHMENT_DOWNLOAD_TIMEOUT_MS;
|
|
32689
33345
|
const fetchFresh = async () => {
|
|
32690
33346
|
const fileInfo = await withTimeout(context2.client.getFileUrl(att.id), downloadTimeoutMs, `file URL lookup timed out after ${downloadTimeoutMs}ms`);
|
|
@@ -32741,7 +33397,7 @@ async function appendPreparedLocalAttachmentRefs(body, event, context2, opts) {
|
|
|
32741
33397
|
return { body: appendLocalAttachmentRefs(body, attachments), attachments };
|
|
32742
33398
|
}
|
|
32743
33399
|
function pinLocalAttachmentPaths(images) {
|
|
32744
|
-
const dirs = new Set(images.map((image) =>
|
|
33400
|
+
const dirs = new Set(images.map((image) => path6.resolve(path6.dirname(image.localPath))));
|
|
32745
33401
|
for (const dir of dirs) {
|
|
32746
33402
|
activeAttachmentDirs.add(dir);
|
|
32747
33403
|
}
|
|
@@ -32756,7 +33412,7 @@ function pinLocalAttachmentPaths(images) {
|
|
|
32756
33412
|
};
|
|
32757
33413
|
}
|
|
32758
33414
|
function attachmentRootDir(workspaceDir) {
|
|
32759
|
-
return
|
|
33415
|
+
return path6.join(path6.resolve(workspaceDir), ".parall", "attachments");
|
|
32760
33416
|
}
|
|
32761
33417
|
function ensureLocalAttachmentGitExclude(workingDirectory) {
|
|
32762
33418
|
try {
|
|
@@ -32765,8 +33421,8 @@ function ensureLocalAttachmentGitExclude(workingDirectory) {
|
|
|
32765
33421
|
encoding: "utf8",
|
|
32766
33422
|
stdio: ["ignore", "pipe", "ignore"]
|
|
32767
33423
|
}).trim();
|
|
32768
|
-
const excludePath =
|
|
32769
|
-
fsSync.mkdirSync(
|
|
33424
|
+
const excludePath = path6.isAbsolute(rel) ? rel : path6.join(workingDirectory, rel);
|
|
33425
|
+
fsSync.mkdirSync(path6.dirname(excludePath), { recursive: true });
|
|
32770
33426
|
const existing = fsSync.existsSync(excludePath) ? fsSync.readFileSync(excludePath, "utf8") : "";
|
|
32771
33427
|
if (existing.split(/\r?\n/).some((line) => line.trim() === ".parall/"))
|
|
32772
33428
|
return;
|
|
@@ -32802,18 +33458,18 @@ function scheduleAttachmentMaintenance(rootDir, opts) {
|
|
|
32802
33458
|
return run;
|
|
32803
33459
|
}
|
|
32804
33460
|
async function ensureAttachmentRootDir(workspaceDir) {
|
|
32805
|
-
const workspaceRoot =
|
|
32806
|
-
const parallDir =
|
|
33461
|
+
const workspaceRoot = path6.resolve(workspaceDir);
|
|
33462
|
+
const parallDir = path6.join(workspaceRoot, ".parall");
|
|
32807
33463
|
const rootDir = attachmentRootDir(workspaceRoot);
|
|
32808
|
-
await
|
|
33464
|
+
await fs5.mkdir(workspaceRoot, { recursive: true });
|
|
32809
33465
|
await ensurePathIsNotSymlink(parallDir);
|
|
32810
|
-
await
|
|
33466
|
+
await fs5.mkdir(parallDir, { recursive: true, mode: 448 });
|
|
32811
33467
|
await ensurePathIsNotSymlink(parallDir);
|
|
32812
33468
|
await ensurePathIsNotSymlink(rootDir);
|
|
32813
|
-
await
|
|
33469
|
+
await fs5.mkdir(rootDir, { recursive: true, mode: 448 });
|
|
32814
33470
|
await ensurePathIsNotSymlink(rootDir);
|
|
32815
|
-
const realWorkspace = await
|
|
32816
|
-
const realRoot = await
|
|
33471
|
+
const realWorkspace = await fs5.realpath(workspaceRoot);
|
|
33472
|
+
const realRoot = await fs5.realpath(rootDir);
|
|
32817
33473
|
if (!isPathInside(realRoot, realWorkspace)) {
|
|
32818
33474
|
throw new Error(`attachment root escapes workspace: ${rootDir}`);
|
|
32819
33475
|
}
|
|
@@ -32821,7 +33477,7 @@ async function ensureAttachmentRootDir(workspaceDir) {
|
|
|
32821
33477
|
}
|
|
32822
33478
|
async function ensurePathIsNotSymlink(filePath) {
|
|
32823
33479
|
try {
|
|
32824
|
-
const stat = await
|
|
33480
|
+
const stat = await fs5.lstat(filePath);
|
|
32825
33481
|
if (stat.isSymbolicLink()) {
|
|
32826
33482
|
throw new Error(`refusing to use symlinked attachment path ${filePath}`);
|
|
32827
33483
|
}
|
|
@@ -32832,8 +33488,8 @@ async function ensurePathIsNotSymlink(filePath) {
|
|
|
32832
33488
|
}
|
|
32833
33489
|
}
|
|
32834
33490
|
function isPathInside(childPath, parentPath) {
|
|
32835
|
-
const rel =
|
|
32836
|
-
return rel === "" || !!rel && !rel.startsWith("..") && !
|
|
33491
|
+
const rel = path6.relative(parentPath, childPath);
|
|
33492
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !path6.isAbsolute(rel);
|
|
32837
33493
|
}
|
|
32838
33494
|
async function existingUsableFile(filePath, expectedSize, rootDir) {
|
|
32839
33495
|
try {
|
|
@@ -32844,30 +33500,30 @@ async function existingUsableFile(filePath, expectedSize, rootDir) {
|
|
|
32844
33500
|
}
|
|
32845
33501
|
}
|
|
32846
33502
|
async function localFileStatInsideRoot(filePath, rootDir) {
|
|
32847
|
-
const stat = await
|
|
33503
|
+
const stat = await fs5.lstat(filePath);
|
|
32848
33504
|
if (stat.isSymbolicLink()) {
|
|
32849
33505
|
throw new Error(`refusing to use symlinked attachment file ${filePath}`);
|
|
32850
33506
|
}
|
|
32851
33507
|
if (!stat.isFile()) {
|
|
32852
33508
|
throw new Error(`attachment path is not a file ${filePath}`);
|
|
32853
33509
|
}
|
|
32854
|
-
const realRoot = await
|
|
32855
|
-
const realFile = await
|
|
33510
|
+
const realRoot = await fs5.realpath(rootDir);
|
|
33511
|
+
const realFile = await fs5.realpath(filePath);
|
|
32856
33512
|
if (!isPathInside(realFile, realRoot)) {
|
|
32857
33513
|
throw new Error(`attachment file escapes workspace: ${filePath}`);
|
|
32858
33514
|
}
|
|
32859
33515
|
return stat;
|
|
32860
33516
|
}
|
|
32861
33517
|
async function localDirectoryStatInsideRoot(dirPath, rootDir) {
|
|
32862
|
-
const stat = await
|
|
33518
|
+
const stat = await fs5.lstat(dirPath);
|
|
32863
33519
|
if (stat.isSymbolicLink()) {
|
|
32864
33520
|
throw new Error(`refusing to use symlinked attachment directory ${dirPath}`);
|
|
32865
33521
|
}
|
|
32866
33522
|
if (!stat.isDirectory()) {
|
|
32867
33523
|
throw new Error(`attachment path is not a directory ${dirPath}`);
|
|
32868
33524
|
}
|
|
32869
|
-
const realRoot = await
|
|
32870
|
-
const realDir = await
|
|
33525
|
+
const realRoot = await fs5.realpath(rootDir);
|
|
33526
|
+
const realDir = await fs5.realpath(dirPath);
|
|
32871
33527
|
if (!isPathInside(realDir, realRoot)) {
|
|
32872
33528
|
throw new Error(`attachment directory escapes workspace: ${dirPath}`);
|
|
32873
33529
|
}
|
|
@@ -32875,7 +33531,7 @@ async function localDirectoryStatInsideRoot(dirPath, rootDir) {
|
|
|
32875
33531
|
}
|
|
32876
33532
|
async function openLocalFileInsideRoot(filePath, rootDir) {
|
|
32877
33533
|
const checkedStat = await localFileStatInsideRoot(filePath, rootDir);
|
|
32878
|
-
const file = await
|
|
33534
|
+
const file = await fs5.open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
32879
33535
|
let keepOpen = false;
|
|
32880
33536
|
try {
|
|
32881
33537
|
const openedStat = await file.stat();
|
|
@@ -32891,8 +33547,8 @@ async function openLocalFileInsideRoot(filePath, rootDir) {
|
|
|
32891
33547
|
}
|
|
32892
33548
|
}
|
|
32893
33549
|
async function openLocalTempFileInsideRoot(filePath, rootDir) {
|
|
32894
|
-
await localDirectoryStatInsideRoot(
|
|
32895
|
-
const file = await
|
|
33550
|
+
await localDirectoryStatInsideRoot(path6.dirname(filePath), rootDir);
|
|
33551
|
+
const file = await fs5.open(filePath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
|
|
32896
33552
|
let keepOpen = false;
|
|
32897
33553
|
try {
|
|
32898
33554
|
const checkedStat = await localFileStatInsideRoot(filePath, rootDir);
|
|
@@ -32917,7 +33573,7 @@ async function assertLocalFileIdentity(filePath, rootDir, expected) {
|
|
|
32917
33573
|
async function removeLocalFileIfInside(filePath, rootDir) {
|
|
32918
33574
|
try {
|
|
32919
33575
|
await localFileStatInsideRoot(filePath, rootDir);
|
|
32920
|
-
await
|
|
33576
|
+
await fs5.rm(filePath, { force: true });
|
|
32921
33577
|
} catch {
|
|
32922
33578
|
}
|
|
32923
33579
|
}
|
|
@@ -32930,7 +33586,7 @@ function sameFile(a, b) {
|
|
|
32930
33586
|
async function cleanupOldAttachmentFiles(rootDir, ttlMs, log2, preserveDirs) {
|
|
32931
33587
|
let entries;
|
|
32932
33588
|
try {
|
|
32933
|
-
entries = await
|
|
33589
|
+
entries = await fs5.readdir(rootDir, { withFileTypes: true });
|
|
32934
33590
|
} catch {
|
|
32935
33591
|
return;
|
|
32936
33592
|
}
|
|
@@ -32938,15 +33594,15 @@ async function cleanupOldAttachmentFiles(rootDir, ttlMs, log2, preserveDirs) {
|
|
|
32938
33594
|
await Promise.all(entries.map(async (entry) => {
|
|
32939
33595
|
if (!entry.isDirectory())
|
|
32940
33596
|
return;
|
|
32941
|
-
const fullPath =
|
|
33597
|
+
const fullPath = path6.join(rootDir, entry.name);
|
|
32942
33598
|
try {
|
|
32943
|
-
if (preserveDirs?.has(
|
|
33599
|
+
if (preserveDirs?.has(path6.resolve(fullPath)))
|
|
32944
33600
|
return;
|
|
32945
|
-
const stat = await
|
|
33601
|
+
const stat = await fs5.lstat(fullPath);
|
|
32946
33602
|
if (!stat.isDirectory())
|
|
32947
33603
|
return;
|
|
32948
33604
|
if (stat.mtimeMs < cutoff) {
|
|
32949
|
-
await
|
|
33605
|
+
await fs5.rm(fullPath, { recursive: true, force: true });
|
|
32950
33606
|
}
|
|
32951
33607
|
} catch (err) {
|
|
32952
33608
|
log2?.warn?.(`agent-core: failed to clean attachment temp dir ${fullPath}: ${String(err)}`);
|
|
@@ -32958,7 +33614,7 @@ async function pruneAttachmentCache(rootDir, maxBytes, log2, preserveDirs) {
|
|
|
32958
33614
|
return;
|
|
32959
33615
|
let entries;
|
|
32960
33616
|
try {
|
|
32961
|
-
entries = await
|
|
33617
|
+
entries = await fs5.readdir(rootDir, { withFileTypes: true });
|
|
32962
33618
|
} catch {
|
|
32963
33619
|
return;
|
|
32964
33620
|
}
|
|
@@ -32967,9 +33623,9 @@ async function pruneAttachmentCache(rootDir, maxBytes, log2, preserveDirs) {
|
|
|
32967
33623
|
for (const entry of entries) {
|
|
32968
33624
|
if (!entry.isDirectory())
|
|
32969
33625
|
continue;
|
|
32970
|
-
const fullPath =
|
|
33626
|
+
const fullPath = path6.join(rootDir, entry.name);
|
|
32971
33627
|
try {
|
|
32972
|
-
const stat = await
|
|
33628
|
+
const stat = await fs5.lstat(fullPath);
|
|
32973
33629
|
if (!stat.isDirectory())
|
|
32974
33630
|
continue;
|
|
32975
33631
|
const size = await directorySize(fullPath);
|
|
@@ -32985,10 +33641,10 @@ async function pruneAttachmentCache(rootDir, maxBytes, log2, preserveDirs) {
|
|
|
32985
33641
|
for (const dir of dirs) {
|
|
32986
33642
|
if (total <= maxBytes)
|
|
32987
33643
|
break;
|
|
32988
|
-
if (preserveDirs?.has(
|
|
33644
|
+
if (preserveDirs?.has(path6.resolve(dir.path)))
|
|
32989
33645
|
continue;
|
|
32990
33646
|
try {
|
|
32991
|
-
await
|
|
33647
|
+
await fs5.rm(dir.path, { recursive: true, force: true });
|
|
32992
33648
|
total -= dir.size;
|
|
32993
33649
|
} catch (err) {
|
|
32994
33650
|
log2?.warn?.(`agent-core: failed to prune attachment cache dir ${dir.path}: ${String(err)}`);
|
|
@@ -32997,12 +33653,12 @@ async function pruneAttachmentCache(rootDir, maxBytes, log2, preserveDirs) {
|
|
|
32997
33653
|
}
|
|
32998
33654
|
async function directorySize(dirPath) {
|
|
32999
33655
|
let total = 0;
|
|
33000
|
-
const entries = await
|
|
33656
|
+
const entries = await fs5.readdir(dirPath, { withFileTypes: true });
|
|
33001
33657
|
for (const entry of entries) {
|
|
33002
|
-
const fullPath =
|
|
33658
|
+
const fullPath = path6.join(dirPath, entry.name);
|
|
33003
33659
|
let stat;
|
|
33004
33660
|
try {
|
|
33005
|
-
stat = await
|
|
33661
|
+
stat = await fs5.lstat(fullPath);
|
|
33006
33662
|
} catch {
|
|
33007
33663
|
continue;
|
|
33008
33664
|
}
|
|
@@ -33017,10 +33673,10 @@ async function directorySize(dirPath) {
|
|
|
33017
33673
|
return total;
|
|
33018
33674
|
}
|
|
33019
33675
|
function activeDirsForRoot(rootDir) {
|
|
33020
|
-
const root =
|
|
33676
|
+
const root = path6.resolve(rootDir);
|
|
33021
33677
|
const dirs = /* @__PURE__ */ new Set();
|
|
33022
33678
|
for (const dir of activeAttachmentDirs) {
|
|
33023
|
-
if (dir === root || dir.startsWith(`${root}${
|
|
33679
|
+
if (dir === root || dir.startsWith(`${root}${path6.sep}`)) {
|
|
33024
33680
|
dirs.add(dir);
|
|
33025
33681
|
}
|
|
33026
33682
|
}
|
|
@@ -33117,9 +33773,9 @@ async function writeResponseToFileWithLimit(res, filePath, maxBytes, rootDir) {
|
|
|
33117
33773
|
}
|
|
33118
33774
|
writtenStat = await file.stat();
|
|
33119
33775
|
await closeFile();
|
|
33120
|
-
await localDirectoryStatInsideRoot(
|
|
33776
|
+
await localDirectoryStatInsideRoot(path6.dirname(filePath), rootDir);
|
|
33121
33777
|
await assertLocalFileIdentity(tmpPath, rootDir, writtenStat);
|
|
33122
|
-
await
|
|
33778
|
+
await fs5.rename(tmpPath, filePath);
|
|
33123
33779
|
completed = true;
|
|
33124
33780
|
return written;
|
|
33125
33781
|
} finally {
|
|
@@ -33135,9 +33791,9 @@ async function writeResponseToFileWithLimit(res, filePath, maxBytes, rootDir) {
|
|
|
33135
33791
|
}
|
|
33136
33792
|
}
|
|
33137
33793
|
function localFileName(attachmentId, fileName, mimeType) {
|
|
33138
|
-
const safeName = sanitizePathSegment(
|
|
33139
|
-
const ext =
|
|
33140
|
-
const stem =
|
|
33794
|
+
const safeName = sanitizePathSegment(path6.basename(fileName || attachmentId));
|
|
33795
|
+
const ext = path6.extname(safeName) || extensionForMime(mimeType);
|
|
33796
|
+
const stem = path6.basename(safeName, path6.extname(safeName)) || attachmentId;
|
|
33141
33797
|
return `${sanitizePathSegment(attachmentId)}-${stem}${ext}`;
|
|
33142
33798
|
}
|
|
33143
33799
|
function extensionForMime(mimeType) {
|
|
@@ -33417,7 +34073,11 @@ function buildSpawnEnv(parentEnv, claudeHome, context2, opts) {
|
|
|
33417
34073
|
PRLL_TRIGGER_MESSAGE_ID: context2.triggerMessageId ?? "",
|
|
33418
34074
|
PRLL_NO_REPLY: context2.noReply ? "1" : "",
|
|
33419
34075
|
PRLL_CONTEXT_FILE: context2.contextFilePath ?? "",
|
|
33420
|
-
PRLL_STEP_ID_FILE: context2.stepIdFilePath ?? ""
|
|
34076
|
+
PRLL_STEP_ID_FILE: context2.stepIdFilePath ?? "",
|
|
34077
|
+
// Per-lane dispatch context directory (stable for the bridge's lifetime,
|
|
34078
|
+
// so pinning it at spawn is safe even for long-lived subprocesses). The
|
|
34079
|
+
// CLI keys into it by send target to derive dispatch effect keys.
|
|
34080
|
+
PRLL_CONTEXT_DIR: context2.contextDirPath ?? ""
|
|
33421
34081
|
};
|
|
33422
34082
|
if (!result.PRLL_WIKI_MOUNT_ROOT?.trim() && opts.wikiMountRoot) {
|
|
33423
34083
|
result.PRLL_WIKI_MOUNT_ROOT = opts.wikiMountRoot;
|
|
@@ -33523,8 +34183,8 @@ var ClaudeCodeAdapter = class {
|
|
|
33523
34183
|
if (!sessionId)
|
|
33524
34184
|
return void 0;
|
|
33525
34185
|
const projectSlug = this.opts.workspaceDir.replace(/[/.]/g, "-");
|
|
33526
|
-
const filePath =
|
|
33527
|
-
return
|
|
34186
|
+
const filePath = path7.join(this.opts.claudeHome, ".claude", "projects", projectSlug, `${sessionId}.jsonl`);
|
|
34187
|
+
return fs6.existsSync(filePath) ? filePath : void 0;
|
|
33528
34188
|
}
|
|
33529
34189
|
forkSession({ sessionKey }) {
|
|
33530
34190
|
return this.opts.sessionManager.createForkSession(sessionKey);
|
|
@@ -33810,7 +34470,7 @@ var ClaudeCodeAdapter = class {
|
|
|
33810
34470
|
if (this.opts.disallowedTools.length > 0) {
|
|
33811
34471
|
args.push("--disallowedTools", this.opts.disallowedTools.join(","));
|
|
33812
34472
|
}
|
|
33813
|
-
args.push("--append-system-prompt-file",
|
|
34473
|
+
args.push("--append-system-prompt-file", path7.join(this.opts.workspaceDir, ".parall", "system-prompt.md"));
|
|
33814
34474
|
if (this.opts.appendSystemPrompt) {
|
|
33815
34475
|
args.push("--append-system-prompt", this.opts.appendSystemPrompt);
|
|
33816
34476
|
}
|
|
@@ -33835,14 +34495,15 @@ var ClaudeCodeAdapter = class {
|
|
|
33835
34495
|
noReply: false,
|
|
33836
34496
|
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey) ?? "",
|
|
33837
34497
|
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey) ?? "",
|
|
34498
|
+
contextDirPath: this.opts.contextDirPath ?? "",
|
|
33838
34499
|
client: void 0
|
|
33839
34500
|
};
|
|
33840
34501
|
}
|
|
33841
34502
|
};
|
|
33842
34503
|
|
|
33843
34504
|
// ts/claude-agent/dist/session-manager.js
|
|
33844
|
-
import * as
|
|
33845
|
-
import * as
|
|
34505
|
+
import * as fs7 from "node:fs";
|
|
34506
|
+
import * as path8 from "node:path";
|
|
33846
34507
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
33847
34508
|
var ClaudeSessionManager = class _ClaudeSessionManager {
|
|
33848
34509
|
mainSessionKey;
|
|
@@ -33991,13 +34652,13 @@ var ClaudeSessionManager = class _ClaudeSessionManager {
|
|
|
33991
34652
|
clearMainSession() {
|
|
33992
34653
|
this.sessionIds.delete(this.mainSessionKey);
|
|
33993
34654
|
try {
|
|
33994
|
-
|
|
34655
|
+
fs7.unlinkSync(this.stateFilePath);
|
|
33995
34656
|
} catch {
|
|
33996
34657
|
}
|
|
33997
34658
|
}
|
|
33998
34659
|
restore() {
|
|
33999
34660
|
try {
|
|
34000
|
-
const raw =
|
|
34661
|
+
const raw = fs7.readFileSync(this.stateFilePath, "utf8");
|
|
34001
34662
|
const parsed = JSON.parse(raw);
|
|
34002
34663
|
if (parsed.runtimeKey === this.mainSessionKey && typeof parsed.sessionId === "string" && parsed.sessionId.trim()) {
|
|
34003
34664
|
this.sessionIds.set(this.mainSessionKey, parsed.sessionId.trim());
|
|
@@ -34007,8 +34668,8 @@ var ClaudeSessionManager = class _ClaudeSessionManager {
|
|
|
34007
34668
|
}
|
|
34008
34669
|
persist(sessionId) {
|
|
34009
34670
|
try {
|
|
34010
|
-
|
|
34011
|
-
|
|
34671
|
+
fs7.mkdirSync(path8.dirname(this.stateFilePath), { recursive: true });
|
|
34672
|
+
fs7.writeFileSync(this.stateFilePath, JSON.stringify({
|
|
34012
34673
|
runtimeKey: this.mainSessionKey,
|
|
34013
34674
|
sessionId
|
|
34014
34675
|
}, null, 2));
|
|
@@ -34019,8 +34680,8 @@ var ClaudeSessionManager = class _ClaudeSessionManager {
|
|
|
34019
34680
|
};
|
|
34020
34681
|
|
|
34021
34682
|
// ts/claude-agent/dist/workspace.js
|
|
34022
|
-
import * as
|
|
34023
|
-
import * as
|
|
34683
|
+
import * as fs8 from "node:fs";
|
|
34684
|
+
import * as path9 from "node:path";
|
|
34024
34685
|
function ensureClaudeWorkspace(workspaceDir, log2, agentIdentity) {
|
|
34025
34686
|
const systemPrompt = [
|
|
34026
34687
|
buildIdentity(agentIdentity),
|
|
@@ -34029,12 +34690,12 @@ function ensureClaudeWorkspace(workspaceDir, log2, agentIdentity) {
|
|
|
34029
34690
|
PRLL_REFERENCE_GUIDE,
|
|
34030
34691
|
buildSkillReferences(workspaceDir)
|
|
34031
34692
|
].join("\n\n");
|
|
34032
|
-
|
|
34033
|
-
|
|
34034
|
-
const parallDir =
|
|
34035
|
-
|
|
34036
|
-
|
|
34037
|
-
writeSkillFiles(
|
|
34693
|
+
fs8.mkdirSync(workspaceDir, { recursive: true });
|
|
34694
|
+
fs8.mkdirSync(path9.join(workspaceDir, ".claude"), { recursive: true });
|
|
34695
|
+
const parallDir = path9.join(workspaceDir, ".parall");
|
|
34696
|
+
fs8.mkdirSync(parallDir, { recursive: true });
|
|
34697
|
+
fs8.writeFileSync(path9.join(parallDir, "system-prompt.md"), systemPrompt, "utf8");
|
|
34698
|
+
writeSkillFiles(path9.join(parallDir, "skills"));
|
|
34038
34699
|
ensureLocalAttachmentGitExclude(workspaceDir);
|
|
34039
34700
|
}
|
|
34040
34701
|
|
|
@@ -34135,7 +34796,8 @@ async function main() {
|
|
|
34135
34796
|
apiKey: config.apiKey,
|
|
34136
34797
|
orgId: config.orgId,
|
|
34137
34798
|
contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
|
|
34138
|
-
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey)
|
|
34799
|
+
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
34800
|
+
contextDirPath: dispatchContextDirPath(config.stateDir)
|
|
34139
34801
|
});
|
|
34140
34802
|
if (resolvedEffort) {
|
|
34141
34803
|
adapter.updateConfig({ effort: resolvedEffort });
|
|
@@ -34183,6 +34845,7 @@ async function main() {
|
|
|
34183
34845
|
dispatchDeadlineMs: parseDispatchDeadlineMs(process.env.PRLL_DISPATCH_DEADLINE_MS),
|
|
34184
34846
|
contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
|
|
34185
34847
|
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
34848
|
+
dispatchContextDir: dispatchContextDirPath(config.stateDir),
|
|
34186
34849
|
onConfigUpdate: refreshPlatformConfig,
|
|
34187
34850
|
onSessionReady: refreshPlatformConfig,
|
|
34188
34851
|
// Long-lived `claude` subprocesses outlive a single dispatch, so the
|