@parall/parall 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/dist/index.bundle.mjs +883 -233
- package/package.json +3 -3
package/dist/index.bundle.mjs
CHANGED
|
@@ -17717,9 +17717,9 @@ var require_getMachineId_linux = __commonJS({
|
|
|
17717
17717
|
var api_1 = (init_esm(), __toCommonJS(esm_exports));
|
|
17718
17718
|
async function getMachineId() {
|
|
17719
17719
|
const paths = ["/etc/machine-id", "/var/lib/dbus/machine-id"];
|
|
17720
|
-
for (const
|
|
17720
|
+
for (const path8 of paths) {
|
|
17721
17721
|
try {
|
|
17722
|
-
const result = await fs_1.promises.readFile(
|
|
17722
|
+
const result = await fs_1.promises.readFile(path8, { encoding: "utf8" });
|
|
17723
17723
|
return result.trim();
|
|
17724
17724
|
} catch (e) {
|
|
17725
17725
|
api_1.diag.debug(`error reading machine id: ${e}`);
|
|
@@ -21122,7 +21122,7 @@ function appendRootPathToUrlIfNeeded(url) {
|
|
|
21122
21122
|
return void 0;
|
|
21123
21123
|
}
|
|
21124
21124
|
}
|
|
21125
|
-
function appendResourcePathToUrl(url,
|
|
21125
|
+
function appendResourcePathToUrl(url, path8) {
|
|
21126
21126
|
try {
|
|
21127
21127
|
new URL(url);
|
|
21128
21128
|
} catch (_a) {
|
|
@@ -21132,11 +21132,11 @@ function appendResourcePathToUrl(url, path7) {
|
|
|
21132
21132
|
if (!url.endsWith("/")) {
|
|
21133
21133
|
url = url + "/";
|
|
21134
21134
|
}
|
|
21135
|
-
url +=
|
|
21135
|
+
url += path8;
|
|
21136
21136
|
try {
|
|
21137
21137
|
new URL(url);
|
|
21138
21138
|
} catch (_b) {
|
|
21139
|
-
diag2.warn("Configuration: Provided URL appended with '" +
|
|
21139
|
+
diag2.warn("Configuration: Provided URL appended with '" + path8 + "' is not a valid URL, using 'undefined' instead of '" + url + "'");
|
|
21140
21140
|
return void 0;
|
|
21141
21141
|
}
|
|
21142
21142
|
return url;
|
|
@@ -26473,6 +26473,16 @@ function resolveParallAccount(params) {
|
|
|
26473
26473
|
};
|
|
26474
26474
|
}
|
|
26475
26475
|
|
|
26476
|
+
// ../agent-core/dist/lane-key.js
|
|
26477
|
+
import * as path from "node:path";
|
|
26478
|
+
function laneKeyForTarget(targetUri, threadRootId) {
|
|
26479
|
+
return Buffer.from(`${targetUri}
|
|
26480
|
+
${threadRootId ?? ""}`, "utf8").toString("base64url");
|
|
26481
|
+
}
|
|
26482
|
+
function laneContextFilePath(contextDir, targetUri, threadRootId) {
|
|
26483
|
+
return path.join(contextDir, `${laneKeyForTarget(targetUri, threadRootId)}.json`);
|
|
26484
|
+
}
|
|
26485
|
+
|
|
26476
26486
|
// ../agent-core/dist/session-state.js
|
|
26477
26487
|
function normalizeSessionKey(sessionKey) {
|
|
26478
26488
|
return sessionKey.toLowerCase();
|
|
@@ -27021,8 +27031,8 @@ function isParallNoReplyCommand(command) {
|
|
|
27021
27031
|
|
|
27022
27032
|
// ../agent-core/dist/gateway-base.js
|
|
27023
27033
|
import * as os from "node:os";
|
|
27024
|
-
import * as
|
|
27025
|
-
import * as
|
|
27034
|
+
import * as fs2 from "node:fs";
|
|
27035
|
+
import * as path2 from "node:path";
|
|
27026
27036
|
|
|
27027
27037
|
// ../sdk/dist/types.js
|
|
27028
27038
|
var MENTION_ALL_USER_ID = "all";
|
|
@@ -27297,6 +27307,11 @@ var ENDPOINTS = {
|
|
|
27297
27307
|
DISPATCH_ACK_BY_ID: (orgId, id) => `${API_BASE}/orgs/${orgId}/dispatch/${id}/ack`,
|
|
27298
27308
|
DISPATCH_EXPIRE: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/expire`,
|
|
27299
27309
|
DISPATCH_BY_MESSAGES: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/by-messages`,
|
|
27310
|
+
DISPATCH_CLAIM: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/claim`,
|
|
27311
|
+
DISPATCH_STEER: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/steer`,
|
|
27312
|
+
DISPATCH_COMPLETE: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/complete`,
|
|
27313
|
+
DISPATCH_RELEASE: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/release`,
|
|
27314
|
+
DISPATCH_HEARTBEAT: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/heartbeat`,
|
|
27300
27315
|
// Unread
|
|
27301
27316
|
UNREAD: `${API_BASE}/me/unread`,
|
|
27302
27317
|
ORG_UNREAD: (orgId) => `${API_BASE}/orgs/${orgId}/unread`,
|
|
@@ -27358,7 +27373,13 @@ var ENDPOINTS = {
|
|
|
27358
27373
|
// the machine control-plane request/reply bridge that lives in api-server.
|
|
27359
27374
|
BROWSER_PROFILE_VIEWER_COMMAND: (orgId, profileId) => `${API_BASE}/orgs/${orgId}/browser-profiles/${profileId}/viewer/command`,
|
|
27360
27375
|
// Clip registry (global, served by clip-service → Pinix Hub proxy)
|
|
27361
|
-
CLIP_REGISTRY: () => `${CLIP_BASE}/registry/clips
|
|
27376
|
+
CLIP_REGISTRY: () => `${CLIP_BASE}/registry/clips`,
|
|
27377
|
+
// Edge device endpoints
|
|
27378
|
+
ORG_EDGE_DEVICES: (orgId) => `/api/v1/orgs/${orgId}/edge/devices`,
|
|
27379
|
+
ORG_EDGE_ONBOARDING: (orgId) => `/api/v1/orgs/${orgId}/edge/onboarding`,
|
|
27380
|
+
ORG_EDGE_PROFILES: (orgId, edgeId) => `/api/v1/orgs/${orgId}/edge/${edgeId}/profiles`,
|
|
27381
|
+
CLIP_CONNECTIONS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/connections`,
|
|
27382
|
+
CLIP_CONNECTION: (orgId, connId) => `/api/v1/orgs/${orgId}/clip-connections/${connId}`
|
|
27362
27383
|
};
|
|
27363
27384
|
var WS_EVENTS = {
|
|
27364
27385
|
// Client -> Server
|
|
@@ -27417,6 +27438,7 @@ var WS_EVENTS = {
|
|
|
27417
27438
|
READ_POSITION_UPDATED: "read_position.updated",
|
|
27418
27439
|
DISPATCH_NEW: "dispatch.new",
|
|
27419
27440
|
DISPATCH_RECEIVED: "dispatch.received",
|
|
27441
|
+
DISPATCH_RESOLVED: "dispatch.resolved",
|
|
27420
27442
|
SCHEDULE_CREATED: "schedule.created",
|
|
27421
27443
|
SCHEDULE_UPDATED: "schedule.updated",
|
|
27422
27444
|
SCHEDULE_DELETED: "schedule.deleted",
|
|
@@ -27512,8 +27534,8 @@ var ParallClient = class _ParallClient {
|
|
|
27512
27534
|
* is authoritative, so wiki vs api routing can't drift from how a caller
|
|
27513
27535
|
* happens to invoke the client.
|
|
27514
27536
|
*/
|
|
27515
|
-
baseUrlFor(
|
|
27516
|
-
return
|
|
27537
|
+
baseUrlFor(path8) {
|
|
27538
|
+
return path8.startsWith(WIKI_BASE) ? this.wikiBaseUrl : this.baseUrl;
|
|
27517
27539
|
}
|
|
27518
27540
|
setToken(token) {
|
|
27519
27541
|
this.token = token;
|
|
@@ -27540,10 +27562,10 @@ var ParallClient = class _ParallClient {
|
|
|
27540
27562
|
* REFRESH_THRESHOLD_S, refresh it **before** sending the request.
|
|
27541
27563
|
* No-op when the token is still fresh, missing, or un-parseable.
|
|
27542
27564
|
*/
|
|
27543
|
-
async ensureFreshToken(
|
|
27565
|
+
async ensureFreshToken(path8) {
|
|
27544
27566
|
if (!this.token || !this.getRefreshToken)
|
|
27545
27567
|
return;
|
|
27546
|
-
const pathSuffix =
|
|
27568
|
+
const pathSuffix = path8.replace(/^\/api\/v1/, "");
|
|
27547
27569
|
if (_ParallClient.AUTH_PATHS.has(pathSuffix))
|
|
27548
27570
|
return;
|
|
27549
27571
|
const exp = _ParallClient.decodeJwtExp(this.token);
|
|
@@ -27575,11 +27597,11 @@ var ParallClient = class _ParallClient {
|
|
|
27575
27597
|
this.refreshPromise = null;
|
|
27576
27598
|
}
|
|
27577
27599
|
}
|
|
27578
|
-
async request(method,
|
|
27600
|
+
async request(method, path8, body, query, retried = false, opts) {
|
|
27579
27601
|
if (!retried) {
|
|
27580
|
-
await this.ensureFreshToken(
|
|
27602
|
+
await this.ensureFreshToken(path8);
|
|
27581
27603
|
}
|
|
27582
|
-
let url = `${this.baseUrlFor(
|
|
27604
|
+
let url = `${this.baseUrlFor(path8)}${path8}`;
|
|
27583
27605
|
if (query) {
|
|
27584
27606
|
const params = new URLSearchParams();
|
|
27585
27607
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -27609,12 +27631,12 @@ var ParallClient = class _ParallClient {
|
|
|
27609
27631
|
throw _ParallClient.normalizeFetchError(err);
|
|
27610
27632
|
}
|
|
27611
27633
|
if (res.status === 401) {
|
|
27612
|
-
const pathSuffix =
|
|
27634
|
+
const pathSuffix = path8.replace(/^\/api\/v1/, "");
|
|
27613
27635
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
27614
27636
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
27615
27637
|
const refreshed = await this.tryRefresh();
|
|
27616
27638
|
if (refreshed) {
|
|
27617
|
-
return this.request(method,
|
|
27639
|
+
return this.request(method, path8, body, query, true, opts);
|
|
27618
27640
|
}
|
|
27619
27641
|
}
|
|
27620
27642
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -27625,6 +27647,7 @@ var ParallClient = class _ParallClient {
|
|
|
27625
27647
|
const rawErrorBody = await res.json().catch(() => ({}));
|
|
27626
27648
|
throw buildApiError(res, rawErrorBody);
|
|
27627
27649
|
}
|
|
27650
|
+
opts?.onStatus?.(res.status);
|
|
27628
27651
|
if (res.status === 204)
|
|
27629
27652
|
return void 0;
|
|
27630
27653
|
if (res.status === 202) {
|
|
@@ -27643,15 +27666,15 @@ var ParallClient = class _ParallClient {
|
|
|
27643
27666
|
* hit the 100 MiB cap, so a longer 5-minute timeout is used so a
|
|
27644
27667
|
* 50 MiB blob on a slow connection doesn't get chopped at 15 s.
|
|
27645
27668
|
*/
|
|
27646
|
-
async multipartRequest(method,
|
|
27669
|
+
async multipartRequest(method, path8, body, retried = false) {
|
|
27647
27670
|
if (!retried) {
|
|
27648
|
-
await this.ensureFreshToken(
|
|
27671
|
+
await this.ensureFreshToken(path8);
|
|
27649
27672
|
}
|
|
27650
27673
|
const { "Content-Type": _drop, ...headers } = this.buildHeaders();
|
|
27651
27674
|
void _drop;
|
|
27652
27675
|
let res;
|
|
27653
27676
|
try {
|
|
27654
|
-
res = await fetch(`${this.baseUrlFor(
|
|
27677
|
+
res = await fetch(`${this.baseUrlFor(path8)}${path8}`, {
|
|
27655
27678
|
method,
|
|
27656
27679
|
headers,
|
|
27657
27680
|
body,
|
|
@@ -27661,12 +27684,12 @@ var ParallClient = class _ParallClient {
|
|
|
27661
27684
|
throw _ParallClient.normalizeFetchError(err);
|
|
27662
27685
|
}
|
|
27663
27686
|
if (res.status === 401) {
|
|
27664
|
-
const pathSuffix =
|
|
27687
|
+
const pathSuffix = path8.replace(/^\/api\/v1/, "");
|
|
27665
27688
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
27666
27689
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
27667
27690
|
const refreshed = await this.tryRefresh();
|
|
27668
27691
|
if (refreshed) {
|
|
27669
|
-
return this.multipartRequest(method,
|
|
27692
|
+
return this.multipartRequest(method, path8, body, true);
|
|
27670
27693
|
}
|
|
27671
27694
|
}
|
|
27672
27695
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -27958,6 +27981,21 @@ var ParallClient = class _ParallClient {
|
|
|
27958
27981
|
async sendMessage(orgId, chatId, req) {
|
|
27959
27982
|
return this.request("POST", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), req);
|
|
27960
27983
|
}
|
|
27984
|
+
/**
|
|
27985
|
+
* sendMessage variant that also reports whether the server answered with an
|
|
27986
|
+
* idempotent replay (HTTP 200 — the message already existed for this
|
|
27987
|
+
* idempotency/effect key) instead of a fresh create (201). Used by the CLI
|
|
27988
|
+
* to surface "already sent by a previous run (deduplicated)".
|
|
27989
|
+
*/
|
|
27990
|
+
async sendMessageDetailed(orgId, chatId, req) {
|
|
27991
|
+
let status = 0;
|
|
27992
|
+
const message = await this.request("POST", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), req, void 0, false, {
|
|
27993
|
+
onStatus: (s) => {
|
|
27994
|
+
status = s;
|
|
27995
|
+
}
|
|
27996
|
+
});
|
|
27997
|
+
return { message, deduplicated: status === 200 };
|
|
27998
|
+
}
|
|
27961
27999
|
async getMessages(orgId, chatId, params) {
|
|
27962
28000
|
return this.request("GET", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), void 0, params);
|
|
27963
28001
|
}
|
|
@@ -28345,8 +28383,8 @@ var ParallClient = class _ParallClient {
|
|
|
28345
28383
|
async requestMachineUpdate(orgId, machineId, mandatory = false) {
|
|
28346
28384
|
await this.request("POST", ENDPOINTS.MACHINE_REQUEST_UPDATE(orgId, machineId), { mandatory });
|
|
28347
28385
|
}
|
|
28348
|
-
async browseMachineFilesystem(orgId, machineId,
|
|
28349
|
-
return this.request("POST", ENDPOINTS.MACHINE_BROWSE(orgId, machineId), { path:
|
|
28386
|
+
async browseMachineFilesystem(orgId, machineId, path8) {
|
|
28387
|
+
return this.request("POST", ENDPOINTS.MACHINE_BROWSE(orgId, machineId), { path: path8 }, void 0, false, { timeoutMs: 15e3 });
|
|
28350
28388
|
}
|
|
28351
28389
|
/** Create a new machine key. Returns the raw key string (shown once) + metadata. */
|
|
28352
28390
|
async createMachineKey(orgId, machineId, name) {
|
|
@@ -28419,6 +28457,30 @@ var ParallClient = class _ParallClient {
|
|
|
28419
28457
|
const params = maxAgeHours !== void 0 ? { max_age_hours: String(maxAgeHours) } : void 0;
|
|
28420
28458
|
return this.request("POST", ENDPOINTS.DISPATCH_EXPIRE(orgId), void 0, params);
|
|
28421
28459
|
}
|
|
28460
|
+
/**
|
|
28461
|
+
* Claim a dispatch lane (explicit consume endpoint). Occupies the
|
|
28462
|
+
* (agent, target, thread) lane and folds claimable WorkItems into it;
|
|
28463
|
+
* `claimed: false` means a healthy incumbent holds the lane.
|
|
28464
|
+
*/
|
|
28465
|
+
async claimDispatch(orgId, req) {
|
|
28466
|
+
return this.request("POST", ENDPOINTS.DISPATCH_CLAIM(orgId), req);
|
|
28467
|
+
}
|
|
28468
|
+
/** Fold a pending same-target WorkItem into a live lane (409 STALE_LANE when dethroned). */
|
|
28469
|
+
async steerDispatch(orgId, req) {
|
|
28470
|
+
return this.request("POST", ENDPOINTS.DISPATCH_STEER(orgId), req);
|
|
28471
|
+
}
|
|
28472
|
+
/** End a turn: no_action sweep of the lane's members + lane release + re-drive check. */
|
|
28473
|
+
async completeDispatch(orgId, req) {
|
|
28474
|
+
return this.request("POST", ENDPOINTS.DISPATCH_COMPLETE(orgId), req);
|
|
28475
|
+
}
|
|
28476
|
+
/** Release a lane on graceful shutdown — members return to pending immediately. */
|
|
28477
|
+
async releaseDispatchLane(orgId, lane) {
|
|
28478
|
+
return this.request("POST", ENDPOINTS.DISPATCH_RELEASE(orgId), { lane });
|
|
28479
|
+
}
|
|
28480
|
+
/** Renew a live lane's lease (long-turn keepalive). 409 STALE_LANE when dethroned. */
|
|
28481
|
+
async heartbeatDispatchLane(orgId, req) {
|
|
28482
|
+
return this.request("POST", ENDPOINTS.DISPATCH_HEARTBEAT(orgId), req);
|
|
28483
|
+
}
|
|
28422
28484
|
async getDispatchByMessages(orgId, chatId, messageIds) {
|
|
28423
28485
|
const params = { chat_id: chatId, message_ids: messageIds.join(",") };
|
|
28424
28486
|
return this.request("GET", ENDPOINTS.DISPATCH_BY_MESSAGES(orgId), void 0, params);
|
|
@@ -28883,8 +28945,8 @@ var ParallClient = class _ParallClient {
|
|
|
28883
28945
|
async deleteWikiRestriction(orgId, wikiId, restrictionId) {
|
|
28884
28946
|
await this.request("DELETE", ENDPOINTS.WIKI_RESTRICTION(orgId, wikiId, restrictionId));
|
|
28885
28947
|
}
|
|
28886
|
-
async getWikiAccessStatus(orgId, wikiId,
|
|
28887
|
-
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0,
|
|
28948
|
+
async getWikiAccessStatus(orgId, wikiId, path8) {
|
|
28949
|
+
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0, path8 ? { path: path8 } : void 0);
|
|
28888
28950
|
}
|
|
28889
28951
|
async createWikiAccessRequest(orgId, wikiId, data) {
|
|
28890
28952
|
await this.request("POST", ENDPOINTS.WIKI_ACCESS_REQUESTS(orgId, wikiId), data);
|
|
@@ -28893,14 +28955,14 @@ var ParallClient = class _ParallClient {
|
|
|
28893
28955
|
async getWikiCommits(orgId, wikiId, params) {
|
|
28894
28956
|
return this.request("GET", ENDPOINTS.WIKI_COMMITS(orgId, wikiId), void 0, params);
|
|
28895
28957
|
}
|
|
28896
|
-
async getWikiFileCommits(orgId, wikiId,
|
|
28958
|
+
async getWikiFileCommits(orgId, wikiId, path8, params) {
|
|
28897
28959
|
return this.request("GET", ENDPOINTS.WIKI_FILE_COMMITS(orgId, wikiId), void 0, {
|
|
28898
|
-
path:
|
|
28960
|
+
path: path8,
|
|
28899
28961
|
...params
|
|
28900
28962
|
});
|
|
28901
28963
|
}
|
|
28902
|
-
async getWikiBlame(orgId, wikiId,
|
|
28903
|
-
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path:
|
|
28964
|
+
async getWikiBlame(orgId, wikiId, path8, ref) {
|
|
28965
|
+
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path: path8, ref });
|
|
28904
28966
|
}
|
|
28905
28967
|
// ---- Wiki Operations (audit log) ----
|
|
28906
28968
|
async getWikiOperations(orgId, wikiId, params) {
|
|
@@ -29146,6 +29208,26 @@ var ParallClient = class _ParallClient {
|
|
|
29146
29208
|
const resp = await this.request("GET", url);
|
|
29147
29209
|
return resp.data;
|
|
29148
29210
|
}
|
|
29211
|
+
// ---- Edge devices ----
|
|
29212
|
+
async listEdgeDevices(orgId) {
|
|
29213
|
+
return this.request("GET", ENDPOINTS.ORG_EDGE_DEVICES(orgId));
|
|
29214
|
+
}
|
|
29215
|
+
async getEdgeOnboarding(orgId) {
|
|
29216
|
+
return this.request("GET", ENDPOINTS.ORG_EDGE_ONBOARDING(orgId));
|
|
29217
|
+
}
|
|
29218
|
+
async listEdgeProfiles(orgId, edgeId) {
|
|
29219
|
+
return this.request("GET", ENDPOINTS.ORG_EDGE_PROFILES(orgId, edgeId));
|
|
29220
|
+
}
|
|
29221
|
+
// ---- Clip connections ----
|
|
29222
|
+
async listClipConnections(orgId, clipId) {
|
|
29223
|
+
return this.request("GET", ENDPOINTS.CLIP_CONNECTIONS(orgId, clipId));
|
|
29224
|
+
}
|
|
29225
|
+
async createClipConnection(orgId, clipId, input) {
|
|
29226
|
+
return this.request("POST", ENDPOINTS.CLIP_CONNECTIONS(orgId, clipId), input);
|
|
29227
|
+
}
|
|
29228
|
+
async deleteClipConnection(orgId, connId) {
|
|
29229
|
+
return this.request("DELETE", ENDPOINTS.CLIP_CONNECTION(orgId, connId));
|
|
29230
|
+
}
|
|
29149
29231
|
};
|
|
29150
29232
|
function normalizeWikiChangeset(changeset) {
|
|
29151
29233
|
return {
|
|
@@ -29532,6 +29614,404 @@ var ParallWs = class {
|
|
|
29532
29614
|
}
|
|
29533
29615
|
};
|
|
29534
29616
|
|
|
29617
|
+
// ../agent-core/dist/lane-ledger.js
|
|
29618
|
+
import * as fs from "node:fs";
|
|
29619
|
+
var LedgerUnsupportedError = class extends Error {
|
|
29620
|
+
};
|
|
29621
|
+
function isStaleLane(err) {
|
|
29622
|
+
return err instanceof ApiError && err.status === 409 && err.code === "STALE_LANE";
|
|
29623
|
+
}
|
|
29624
|
+
function isEndpointMissing(err) {
|
|
29625
|
+
return err instanceof ApiError && err.status === 404 && !err.code;
|
|
29626
|
+
}
|
|
29627
|
+
var LaneLedger = class {
|
|
29628
|
+
opts;
|
|
29629
|
+
lanes = /* @__PURE__ */ new Map();
|
|
29630
|
+
constructor(opts) {
|
|
29631
|
+
this.opts = opts;
|
|
29632
|
+
}
|
|
29633
|
+
get contextDir() {
|
|
29634
|
+
return this.opts.contextDir;
|
|
29635
|
+
}
|
|
29636
|
+
/** Only chat message events ride the lane ledger; typed events stay on the legacy ack path. */
|
|
29637
|
+
handles(event) {
|
|
29638
|
+
return event.type === "message" && event.targetId.startsWith("cht_");
|
|
29639
|
+
}
|
|
29640
|
+
laneKeyFor(event) {
|
|
29641
|
+
if (event.type !== "message" && event.dispatchEventId) {
|
|
29642
|
+
return laneKeyForTarget(`dsp:${event.dispatchEventId}`);
|
|
29643
|
+
}
|
|
29644
|
+
return laneKeyForTarget(`prll://${event.targetId}`, event.threadRootId);
|
|
29645
|
+
}
|
|
29646
|
+
getForEvent(event) {
|
|
29647
|
+
return this.lanes.get(this.laneKeyFor(event));
|
|
29648
|
+
}
|
|
29649
|
+
laneContextPath(lane) {
|
|
29650
|
+
return laneContextFilePath(this.opts.contextDir, lane.targetUri, lane.threadRootId);
|
|
29651
|
+
}
|
|
29652
|
+
/**
|
|
29653
|
+
* Claim (or reuse) the lane for a group of same-lane message events and
|
|
29654
|
+
* fold every group member into it. Returns 'foreign' when a healthy
|
|
29655
|
+
* incumbent (another pod) holds the resource — the caller must not
|
|
29656
|
+
* dispatch; the events stay pending server-side and re-drive after the
|
|
29657
|
+
* incumbent completes.
|
|
29658
|
+
*/
|
|
29659
|
+
async ensureLane(events) {
|
|
29660
|
+
const trigger = events[events.length - 1];
|
|
29661
|
+
const laneKey = this.laneKeyFor(trigger);
|
|
29662
|
+
let lane = this.lanes.get(laneKey);
|
|
29663
|
+
if (!lane) {
|
|
29664
|
+
const targetUri = `prll://${trigger.targetId}`;
|
|
29665
|
+
let res;
|
|
29666
|
+
try {
|
|
29667
|
+
res = await this.opts.client.claimDispatch(this.opts.orgId, {
|
|
29668
|
+
target_uri: targetUri,
|
|
29669
|
+
thread_root_id: trigger.threadRootId,
|
|
29670
|
+
limit: 100
|
|
29671
|
+
});
|
|
29672
|
+
} catch (err) {
|
|
29673
|
+
if (isEndpointMissing(err))
|
|
29674
|
+
throw new LedgerUnsupportedError("claim endpoint unavailable");
|
|
29675
|
+
throw err;
|
|
29676
|
+
}
|
|
29677
|
+
if (!res.claimed || !res.lane) {
|
|
29678
|
+
this.opts.log?.info(`lane for ${targetUri} held by a healthy incumbent \u2014 leaving events pending for re-drive`);
|
|
29679
|
+
return null;
|
|
29680
|
+
}
|
|
29681
|
+
const leaseUntilMs = Date.parse(res.lease_until ?? "");
|
|
29682
|
+
lane = {
|
|
29683
|
+
laneKey,
|
|
29684
|
+
lane: res.lane,
|
|
29685
|
+
targetUri,
|
|
29686
|
+
threadRootId: trigger.threadRootId,
|
|
29687
|
+
folded: /* @__PURE__ */ new Map(),
|
|
29688
|
+
...Number.isNaN(leaseUntilMs) ? {} : { leaseUntilMs, leaseTtlMs: Math.max(leaseUntilMs - Date.now(), 6e4) }
|
|
29689
|
+
};
|
|
29690
|
+
for (const ev of res.events ?? []) {
|
|
29691
|
+
lane.folded.set(ev.source_id, ev.id);
|
|
29692
|
+
}
|
|
29693
|
+
this.lanes.set(laneKey, lane);
|
|
29694
|
+
}
|
|
29695
|
+
for (const ev of events) {
|
|
29696
|
+
if (lane.folded.has(ev.messageId))
|
|
29697
|
+
continue;
|
|
29698
|
+
try {
|
|
29699
|
+
const res = await this.opts.client.steerDispatch(this.opts.orgId, {
|
|
29700
|
+
lane: lane.lane,
|
|
29701
|
+
target_uri: lane.targetUri,
|
|
29702
|
+
thread_root_id: lane.threadRootId,
|
|
29703
|
+
...ev.dispatchEventId ? { dispatch_event_id: ev.dispatchEventId } : { source_type: "message", source_id: ev.messageId }
|
|
29704
|
+
});
|
|
29705
|
+
lane.folded.set(ev.messageId, res.dispatch_event_id);
|
|
29706
|
+
} catch (err) {
|
|
29707
|
+
if (isStaleLane(err)) {
|
|
29708
|
+
this.lanes.delete(laneKey);
|
|
29709
|
+
return null;
|
|
29710
|
+
}
|
|
29711
|
+
this.opts.log?.warn(`steer fold failed for ${ev.messageId} \u2014 failing closed, releasing lane: ${String(err)}`);
|
|
29712
|
+
await this.release(laneKey);
|
|
29713
|
+
return null;
|
|
29714
|
+
}
|
|
29715
|
+
}
|
|
29716
|
+
return lane;
|
|
29717
|
+
}
|
|
29718
|
+
/**
|
|
29719
|
+
* Fold a live mid-turn message into its active lane BEFORE injecting it
|
|
29720
|
+
* into the running turn. Injection without a successful fold is forbidden —
|
|
29721
|
+
* an un-folded injected message would be re-driven after complete and the
|
|
29722
|
+
* model would handle it twice.
|
|
29723
|
+
*/
|
|
29724
|
+
async steerLive(event) {
|
|
29725
|
+
const laneKey = this.laneKeyFor(event);
|
|
29726
|
+
const lane = this.lanes.get(laneKey);
|
|
29727
|
+
if (!lane)
|
|
29728
|
+
return false;
|
|
29729
|
+
if (lane.folded.has(event.messageId))
|
|
29730
|
+
return true;
|
|
29731
|
+
try {
|
|
29732
|
+
const res = await this.opts.client.steerDispatch(this.opts.orgId, {
|
|
29733
|
+
lane: lane.lane,
|
|
29734
|
+
target_uri: lane.targetUri,
|
|
29735
|
+
thread_root_id: lane.threadRootId,
|
|
29736
|
+
...event.dispatchEventId ? { dispatch_event_id: event.dispatchEventId } : { source_type: "message", source_id: event.messageId }
|
|
29737
|
+
});
|
|
29738
|
+
lane.folded.set(event.messageId, res.dispatch_event_id);
|
|
29739
|
+
return true;
|
|
29740
|
+
} catch (err) {
|
|
29741
|
+
if (isStaleLane(err)) {
|
|
29742
|
+
this.lanes.delete(laneKey);
|
|
29743
|
+
} else {
|
|
29744
|
+
this.opts.log?.warn(`live steer failed for ${event.messageId}: ${String(err)}`);
|
|
29745
|
+
}
|
|
29746
|
+
return false;
|
|
29747
|
+
}
|
|
29748
|
+
}
|
|
29749
|
+
/**
|
|
29750
|
+
* Complete the lane when no local work remains for it: the server sweeps
|
|
29751
|
+
* still-leased members as no_action, releases the occupancy row, and
|
|
29752
|
+
* re-drives any same-target pending work. A STALE_LANE answer means a
|
|
29753
|
+
* takeover already owns the resource — local state is dropped either way.
|
|
29754
|
+
*/
|
|
29755
|
+
async completeIfIdle(laneKey, hasMoreLocal) {
|
|
29756
|
+
const lane = this.lanes.get(laneKey);
|
|
29757
|
+
if (!lane || hasMoreLocal)
|
|
29758
|
+
return;
|
|
29759
|
+
this.lanes.delete(laneKey);
|
|
29760
|
+
this.removeLaneContext(lane);
|
|
29761
|
+
try {
|
|
29762
|
+
const res = await this.opts.client.completeDispatch(this.opts.orgId, {
|
|
29763
|
+
lane: lane.lane,
|
|
29764
|
+
target_uri: lane.targetUri,
|
|
29765
|
+
thread_root_id: lane.threadRootId
|
|
29766
|
+
});
|
|
29767
|
+
if (res.swept_no_action > 0 || res.redriven) {
|
|
29768
|
+
this.opts.log?.info(`lane complete for ${lane.targetUri}: swept ${res.swept_no_action} no_action, redriven=${res.redriven}`);
|
|
29769
|
+
}
|
|
29770
|
+
} catch (err) {
|
|
29771
|
+
if (isStaleLane(err)) {
|
|
29772
|
+
this.opts.log?.info(`lane complete skipped for ${lane.targetUri} \u2014 taken over`);
|
|
29773
|
+
return;
|
|
29774
|
+
}
|
|
29775
|
+
this.opts.log?.warn(`lane complete failed for ${lane.targetUri}: ${String(err)}`);
|
|
29776
|
+
}
|
|
29777
|
+
}
|
|
29778
|
+
/**
|
|
29779
|
+
* Long-turn keepalive: renew the lane's lease on runtime activity, throttled
|
|
29780
|
+
* so a chatty turn doesn't spam the server. Without this, a legitimately
|
|
29781
|
+
* long turn (> lane TTL) would be dethroned mid-flight and every subsequent
|
|
29782
|
+
* write misfired with STALE_LANE — the design doc's "long turns renew via
|
|
29783
|
+
* step writes". Fire-and-forget: a failed renewal is surfaced by the next
|
|
29784
|
+
* write's incumbency check anyway.
|
|
29785
|
+
*/
|
|
29786
|
+
maybeRenew(lane) {
|
|
29787
|
+
const now = Date.now();
|
|
29788
|
+
const ttl = lane.leaseTtlMs ?? 10 * 6e4;
|
|
29789
|
+
const until = lane.leaseUntilMs ?? now;
|
|
29790
|
+
if (until - now > ttl / 2)
|
|
29791
|
+
return;
|
|
29792
|
+
lane.leaseUntilMs = now + ttl;
|
|
29793
|
+
void this.opts.client.heartbeatDispatchLane(this.opts.orgId, {
|
|
29794
|
+
lane: lane.lane,
|
|
29795
|
+
target_uri: lane.targetUri,
|
|
29796
|
+
thread_root_id: lane.threadRootId
|
|
29797
|
+
}).then((res) => {
|
|
29798
|
+
const until2 = Date.parse(res?.lease_until ?? "");
|
|
29799
|
+
if (!Number.isNaN(until2))
|
|
29800
|
+
lane.leaseUntilMs = until2;
|
|
29801
|
+
}).catch((err) => {
|
|
29802
|
+
if (isStaleLane(err)) {
|
|
29803
|
+
this.lanes.delete(lane.laneKey);
|
|
29804
|
+
this.opts.log?.warn(`lane ${lane.targetUri} was taken over during the turn`);
|
|
29805
|
+
return;
|
|
29806
|
+
}
|
|
29807
|
+
this.opts.log?.warn(`lane heartbeat failed for ${lane.targetUri}: ${String(err)}`);
|
|
29808
|
+
});
|
|
29809
|
+
}
|
|
29810
|
+
/**
|
|
29811
|
+
* Release a lane's unresolved members back to pending (dispatch error /
|
|
29812
|
+
* shutdown) so the next pod re-claims immediately instead of waiting out
|
|
29813
|
+
* the lease.
|
|
29814
|
+
*/
|
|
29815
|
+
async release(laneKey) {
|
|
29816
|
+
const lane = this.lanes.get(laneKey);
|
|
29817
|
+
if (!lane)
|
|
29818
|
+
return;
|
|
29819
|
+
this.lanes.delete(laneKey);
|
|
29820
|
+
this.removeLaneContext(lane);
|
|
29821
|
+
try {
|
|
29822
|
+
await this.opts.client.releaseDispatchLane(this.opts.orgId, lane.lane);
|
|
29823
|
+
} catch (err) {
|
|
29824
|
+
this.opts.log?.warn(`lane release failed for ${lane.targetUri}: ${String(err)}`);
|
|
29825
|
+
}
|
|
29826
|
+
}
|
|
29827
|
+
async releaseAll() {
|
|
29828
|
+
const keys = [...this.lanes.keys()];
|
|
29829
|
+
for (const key of keys) {
|
|
29830
|
+
await this.release(key);
|
|
29831
|
+
}
|
|
29832
|
+
}
|
|
29833
|
+
/** True when any lane is currently active (used by shutdown logging). */
|
|
29834
|
+
get activeCount() {
|
|
29835
|
+
return this.lanes.size;
|
|
29836
|
+
}
|
|
29837
|
+
/**
|
|
29838
|
+
* Claim the single-member lane of one typed WorkItem (resource = dsp:<id>),
|
|
29839
|
+
* by WorkItem id or by source identity (the live task.assigned event has no
|
|
29840
|
+
* WorkItem id). Returns null when a healthy incumbent (another pod) holds
|
|
29841
|
+
* it or the WorkItem is already resolved — the caller must skip processing.
|
|
29842
|
+
*/
|
|
29843
|
+
async claimTyped(ref) {
|
|
29844
|
+
let res;
|
|
29845
|
+
try {
|
|
29846
|
+
res = await this.opts.client.claimDispatch(this.opts.orgId, {
|
|
29847
|
+
dispatch_event_id: ref.dispatchEventId,
|
|
29848
|
+
source_type: ref.dispatchEventId ? void 0 : ref.sourceType,
|
|
29849
|
+
source_id: ref.dispatchEventId ? void 0 : ref.sourceId
|
|
29850
|
+
});
|
|
29851
|
+
} catch (err) {
|
|
29852
|
+
if (isEndpointMissing(err))
|
|
29853
|
+
throw new LedgerUnsupportedError("claim endpoint unavailable");
|
|
29854
|
+
throw err;
|
|
29855
|
+
}
|
|
29856
|
+
if (!res.claimed || !res.lane || !res.events?.length)
|
|
29857
|
+
return null;
|
|
29858
|
+
const workItem = res.events[0];
|
|
29859
|
+
const targetUri = `dsp:${workItem.id}`;
|
|
29860
|
+
const leaseUntilMs = Date.parse(res.lease_until ?? "");
|
|
29861
|
+
const lane = {
|
|
29862
|
+
laneKey: laneKeyForTarget(targetUri),
|
|
29863
|
+
lane: res.lane,
|
|
29864
|
+
targetUri,
|
|
29865
|
+
folded: /* @__PURE__ */ new Map([[workItem.source_id, workItem.id]]),
|
|
29866
|
+
typedDispatchEventId: workItem.id,
|
|
29867
|
+
...Number.isNaN(leaseUntilMs) ? {} : { leaseUntilMs, leaseTtlMs: Math.max(leaseUntilMs - Date.now(), 6e4) }
|
|
29868
|
+
};
|
|
29869
|
+
this.lanes.set(lane.laneKey, lane);
|
|
29870
|
+
return lane;
|
|
29871
|
+
}
|
|
29872
|
+
/**
|
|
29873
|
+
* Remove the per-lane context file (and its CLI sidecar) when the lane
|
|
29874
|
+
* ends. A leftover file would make a later cross-context send to the same
|
|
29875
|
+
* target bind a dead lane token and misfire with STALE_LANE instead of
|
|
29876
|
+
* taking the plain non-ledger path.
|
|
29877
|
+
*/
|
|
29878
|
+
removeLaneContext(lane) {
|
|
29879
|
+
const contextPath = this.laneContextPath(lane);
|
|
29880
|
+
for (const p of [contextPath, contextPath.replace(/\.json$/, ".reply-state.json")]) {
|
|
29881
|
+
try {
|
|
29882
|
+
fs.rmSync(p, { force: true });
|
|
29883
|
+
} catch {
|
|
29884
|
+
}
|
|
29885
|
+
}
|
|
29886
|
+
}
|
|
29887
|
+
};
|
|
29888
|
+
|
|
29889
|
+
// ../agent-core/dist/gateway-lane-flow.js
|
|
29890
|
+
async function dispatchLaneGroup(host, opts) {
|
|
29891
|
+
const ledger = host.laneLedger;
|
|
29892
|
+
const event = opts.events[opts.events.length - 1];
|
|
29893
|
+
let lane;
|
|
29894
|
+
try {
|
|
29895
|
+
lane = await ledger.ensureLane(opts.events);
|
|
29896
|
+
} catch (err) {
|
|
29897
|
+
if (!(err instanceof LedgerUnsupportedError))
|
|
29898
|
+
throw err;
|
|
29899
|
+
host.disableLedger("claim endpoint missing");
|
|
29900
|
+
await host.emitDispatchReceived(event);
|
|
29901
|
+
const dispatched2 = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText);
|
|
29902
|
+
if (!dispatched2)
|
|
29903
|
+
return "shutdown";
|
|
29904
|
+
for (const ev of opts.events) {
|
|
29905
|
+
host.opts.client.ackDispatch(host.opts.config.org_id, {
|
|
29906
|
+
source_type: ev.ackSourceType ?? "message",
|
|
29907
|
+
source_id: ev.ackSourceId ?? ev.messageId
|
|
29908
|
+
}).catch(() => {
|
|
29909
|
+
});
|
|
29910
|
+
}
|
|
29911
|
+
return "dispatched";
|
|
29912
|
+
}
|
|
29913
|
+
if (!lane) {
|
|
29914
|
+
for (const ev of opts.events) {
|
|
29915
|
+
host.dispatchedMessages.delete(ev.messageId);
|
|
29916
|
+
}
|
|
29917
|
+
return "foreign";
|
|
29918
|
+
}
|
|
29919
|
+
let dispatched = false;
|
|
29920
|
+
try {
|
|
29921
|
+
dispatched = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText);
|
|
29922
|
+
} catch (err) {
|
|
29923
|
+
await ledger.release(lane.laneKey).catch(() => {
|
|
29924
|
+
});
|
|
29925
|
+
throw err;
|
|
29926
|
+
}
|
|
29927
|
+
if (!dispatched) {
|
|
29928
|
+
return "shutdown";
|
|
29929
|
+
}
|
|
29930
|
+
const pendingInjections = host.opts.dispatchAdapter.hasPendingInjections?.(opts.sessionKey) ?? false;
|
|
29931
|
+
await ledger.completeIfIdle(lane.laneKey, pendingInjections || opts.hasMoreLocal());
|
|
29932
|
+
return "dispatched";
|
|
29933
|
+
}
|
|
29934
|
+
async function consumeTypedDispatch(host, ref, run, ack) {
|
|
29935
|
+
if (!host.laneLedger || host.ledgerDisabled) {
|
|
29936
|
+
if (await run(ref.dispatchEventId))
|
|
29937
|
+
ack(ref.dispatchEventId);
|
|
29938
|
+
return;
|
|
29939
|
+
}
|
|
29940
|
+
let lane;
|
|
29941
|
+
try {
|
|
29942
|
+
lane = await host.laneLedger.claimTyped(ref);
|
|
29943
|
+
} catch (err) {
|
|
29944
|
+
if (err instanceof LedgerUnsupportedError) {
|
|
29945
|
+
host.disableLedger("claim endpoint missing");
|
|
29946
|
+
if (await run(ref.dispatchEventId))
|
|
29947
|
+
ack(ref.dispatchEventId);
|
|
29948
|
+
return;
|
|
29949
|
+
}
|
|
29950
|
+
throw err;
|
|
29951
|
+
}
|
|
29952
|
+
if (!lane) {
|
|
29953
|
+
host.opts.log?.info(`typed dispatch ${ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`} not claimable (held elsewhere or already resolved) \u2014 skipping`);
|
|
29954
|
+
return;
|
|
29955
|
+
}
|
|
29956
|
+
try {
|
|
29957
|
+
if (await run(lane.typedDispatchEventId))
|
|
29958
|
+
ack(lane.typedDispatchEventId);
|
|
29959
|
+
} finally {
|
|
29960
|
+
await host.laneLedger.completeIfIdle(lane.laneKey, false).catch(() => {
|
|
29961
|
+
});
|
|
29962
|
+
}
|
|
29963
|
+
}
|
|
29964
|
+
async function consumeMessageWorkItem(host, item) {
|
|
29965
|
+
if (host.shuttingDown)
|
|
29966
|
+
return;
|
|
29967
|
+
if (!host.tryClaimMessage(item.source_id))
|
|
29968
|
+
return;
|
|
29969
|
+
const ackItem = () => {
|
|
29970
|
+
host.opts.client.ackDispatchByID(host.opts.config.org_id, item.id).catch(() => {
|
|
29971
|
+
});
|
|
29972
|
+
};
|
|
29973
|
+
let msg = null;
|
|
29974
|
+
try {
|
|
29975
|
+
msg = await host.opts.client.getMessage(item.source_id);
|
|
29976
|
+
} catch (err) {
|
|
29977
|
+
const status = err?.status;
|
|
29978
|
+
if (status !== 404) {
|
|
29979
|
+
host.opts.log?.warn(`message fetch failed for ${item.source_id}, leaving pending: ${String(err)}`);
|
|
29980
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
29981
|
+
return;
|
|
29982
|
+
}
|
|
29983
|
+
}
|
|
29984
|
+
if (!msg || msg.sender_id === host.opts.agentUserId) {
|
|
29985
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
29986
|
+
ackItem();
|
|
29987
|
+
return;
|
|
29988
|
+
}
|
|
29989
|
+
const decision = await host.buildMessageDispatchDecision(item.chat_id, msg);
|
|
29990
|
+
if (decision.action === "retry") {
|
|
29991
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
29992
|
+
return;
|
|
29993
|
+
}
|
|
29994
|
+
if (decision.action === "skip") {
|
|
29995
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
29996
|
+
ackItem();
|
|
29997
|
+
return;
|
|
29998
|
+
}
|
|
29999
|
+
decision.event.dispatchEventId = item.id;
|
|
30000
|
+
const laneResolved = host.usesLaneLedger(decision.event);
|
|
30001
|
+
try {
|
|
30002
|
+
const dispatched = await host.handleInboundEvent(decision.event);
|
|
30003
|
+
if (dispatched) {
|
|
30004
|
+
if (!laneResolved)
|
|
30005
|
+
ackItem();
|
|
30006
|
+
} else {
|
|
30007
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30008
|
+
}
|
|
30009
|
+
} catch (err) {
|
|
30010
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30011
|
+
throw err;
|
|
30012
|
+
}
|
|
30013
|
+
}
|
|
30014
|
+
|
|
29535
30015
|
// ../agent-core/dist/telemetry.js
|
|
29536
30016
|
init_esm();
|
|
29537
30017
|
var import_api_logs = __toESM(require_src(), 1);
|
|
@@ -29833,6 +30313,14 @@ var ParallAgentGateway = class {
|
|
|
29833
30313
|
inFlightDispatches = 0;
|
|
29834
30314
|
drainResolvers = [];
|
|
29835
30315
|
pendingRestartNotification = null;
|
|
30316
|
+
laneLedger;
|
|
30317
|
+
// Sticky fallback: flipped when the server predates the ledger (claim
|
|
30318
|
+
// endpoint 404) so every subsequent dispatch uses the legacy flow.
|
|
30319
|
+
ledgerDisabled = false;
|
|
30320
|
+
// Group key of the group currently being dispatched on main — lane-aware
|
|
30321
|
+
// (targetId + thread), unlike mainCurrentTargetId which stays chat-level
|
|
30322
|
+
// for fork routing decisions.
|
|
30323
|
+
mainCurrentGroupKey;
|
|
29836
30324
|
DISPATCHED_MESSAGES_CAP = 5e3;
|
|
29837
30325
|
// SHUTDOWN_DEADLINE_MS is read by waitForDrain via the configured value
|
|
29838
30326
|
// below — kept as instance state so per-runtime configs can override it
|
|
@@ -29842,6 +30330,14 @@ var ParallAgentGateway = class {
|
|
|
29842
30330
|
DISPATCH_DEADLINE_MS;
|
|
29843
30331
|
constructor(opts) {
|
|
29844
30332
|
this.opts = opts;
|
|
30333
|
+
if (opts.dispatchContextDir) {
|
|
30334
|
+
this.laneLedger = new LaneLedger({
|
|
30335
|
+
client: opts.client,
|
|
30336
|
+
orgId: opts.config.org_id,
|
|
30337
|
+
contextDir: opts.dispatchContextDir,
|
|
30338
|
+
log: opts.log
|
|
30339
|
+
});
|
|
30340
|
+
}
|
|
29845
30341
|
this.SHUTDOWN_DEADLINE_MS = opts.shutdownDeadlineMs ?? 6e4;
|
|
29846
30342
|
this.FORK_DEADLINE_MS = opts.forkDeadlineMs ?? 2 * 60 * 6e4;
|
|
29847
30343
|
this.DISPATCH_DEADLINE_MS = opts.dispatchDeadlineMs ?? 20 * 6e4;
|
|
@@ -29911,14 +30407,18 @@ var ParallAgentGateway = class {
|
|
|
29911
30407
|
if (data.status !== "todo" && data.status !== "in_progress")
|
|
29912
30408
|
return;
|
|
29913
30409
|
try {
|
|
29914
|
-
|
|
29915
|
-
|
|
30410
|
+
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) => {
|
|
30411
|
+
if (dispatchEventId) {
|
|
30412
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, dispatchEventId).catch(() => {
|
|
30413
|
+
});
|
|
30414
|
+
return;
|
|
30415
|
+
}
|
|
29916
30416
|
this.opts.client.ackDispatch(this.opts.config.org_id, {
|
|
29917
30417
|
source_type: "task_activity",
|
|
29918
30418
|
source_id: data.id
|
|
29919
30419
|
}).catch(() => {
|
|
29920
30420
|
});
|
|
29921
|
-
}
|
|
30421
|
+
});
|
|
29922
30422
|
} catch (err) {
|
|
29923
30423
|
this.opts.log?.error(`task dispatch failed for ${data.id}: ${String(err)}`);
|
|
29924
30424
|
}
|
|
@@ -29928,11 +30428,10 @@ var ParallAgentGateway = class {
|
|
|
29928
30428
|
if (!data.source_id || !data.task_id)
|
|
29929
30429
|
return;
|
|
29930
30430
|
try {
|
|
29931
|
-
|
|
29932
|
-
if (dispatched) {
|
|
30431
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleTaskComment(data.source_id, data.task_id ?? "", data.actor_id, data.delivery_reason), () => {
|
|
29933
30432
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29934
30433
|
});
|
|
29935
|
-
}
|
|
30434
|
+
});
|
|
29936
30435
|
} catch (err) {
|
|
29937
30436
|
this.opts.log?.error(`task comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29938
30437
|
}
|
|
@@ -29940,11 +30439,10 @@ var ParallAgentGateway = class {
|
|
|
29940
30439
|
if (!data.source_id)
|
|
29941
30440
|
return;
|
|
29942
30441
|
try {
|
|
29943
|
-
|
|
29944
|
-
if (dispatched) {
|
|
30442
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleWikiComment(data.source_id, data.actor_id, data.delivery_reason), () => {
|
|
29945
30443
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29946
30444
|
});
|
|
29947
|
-
}
|
|
30445
|
+
});
|
|
29948
30446
|
} catch (err) {
|
|
29949
30447
|
this.opts.log?.error(`wiki comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29950
30448
|
}
|
|
@@ -29952,11 +30450,13 @@ var ParallAgentGateway = class {
|
|
|
29952
30450
|
if (!data.task_id)
|
|
29953
30451
|
return;
|
|
29954
30452
|
try {
|
|
29955
|
-
|
|
29956
|
-
|
|
30453
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.handleTaskDispatch(data.task_id ?? "", data.source_id ?? data.task_id ?? "", {
|
|
30454
|
+
allowCreator: true,
|
|
30455
|
+
dispatchEventId
|
|
30456
|
+
}), () => {
|
|
29957
30457
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29958
30458
|
});
|
|
29959
|
-
}
|
|
30459
|
+
});
|
|
29960
30460
|
} catch (err) {
|
|
29961
30461
|
this.opts.log?.error(`task update dispatch failed for ${data.task_id}: ${String(err)}`);
|
|
29962
30462
|
}
|
|
@@ -29964,11 +30464,10 @@ var ParallAgentGateway = class {
|
|
|
29964
30464
|
if (!data.source_id)
|
|
29965
30465
|
return;
|
|
29966
30466
|
try {
|
|
29967
|
-
|
|
29968
|
-
if (dispatched) {
|
|
30467
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleScheduleFire(data.source_id, data.actor_id), () => {
|
|
29969
30468
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29970
30469
|
});
|
|
29971
|
-
}
|
|
30470
|
+
});
|
|
29972
30471
|
} catch (err) {
|
|
29973
30472
|
this.opts.log?.error(`schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29974
30473
|
}
|
|
@@ -29976,11 +30475,10 @@ var ParallAgentGateway = class {
|
|
|
29976
30475
|
if (!data.source_id)
|
|
29977
30476
|
return;
|
|
29978
30477
|
try {
|
|
29979
|
-
|
|
29980
|
-
if (dispatched) {
|
|
30478
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleExternalTriggerRun(data.source_id), () => {
|
|
29981
30479
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29982
30480
|
});
|
|
29983
|
-
}
|
|
30481
|
+
});
|
|
29984
30482
|
} catch (err) {
|
|
29985
30483
|
this.opts.log?.error(`external trigger dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29986
30484
|
}
|
|
@@ -29988,11 +30486,10 @@ var ParallAgentGateway = class {
|
|
|
29988
30486
|
if (!data.source_id)
|
|
29989
30487
|
return;
|
|
29990
30488
|
try {
|
|
29991
|
-
|
|
29992
|
-
if (dispatched) {
|
|
30489
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleChannelMessage(data.source_id), () => {
|
|
29993
30490
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29994
30491
|
});
|
|
29995
|
-
}
|
|
30492
|
+
});
|
|
29996
30493
|
} catch (err) {
|
|
29997
30494
|
this.opts.log?.error(`channel message dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29998
30495
|
}
|
|
@@ -30000,14 +30497,19 @@ var ParallAgentGateway = class {
|
|
|
30000
30497
|
if (!data.source_id)
|
|
30001
30498
|
return;
|
|
30002
30499
|
try {
|
|
30003
|
-
|
|
30004
|
-
if (dispatched) {
|
|
30500
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleApprovalDecided(data.source_id, data.actor_id, data.chat_id ?? null), () => {
|
|
30005
30501
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30006
30502
|
});
|
|
30007
|
-
}
|
|
30503
|
+
});
|
|
30008
30504
|
} catch (err) {
|
|
30009
30505
|
this.opts.log?.error(`approval decided dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30010
30506
|
}
|
|
30507
|
+
} else if (data.event_type === "message" && this.laneLedger && data.source_id && data.chat_id) {
|
|
30508
|
+
try {
|
|
30509
|
+
await this.handleMessageRedrive(data);
|
|
30510
|
+
} catch (err) {
|
|
30511
|
+
this.opts.log?.error(`message re-drive failed for ${data.source_id}: ${String(err)}`);
|
|
30512
|
+
}
|
|
30011
30513
|
} else if (data.event_type !== "message" && data.event_type !== "task_assign") {
|
|
30012
30514
|
this.opts.log?.info(`dispatch.new with unhandled event_type=${String(data.event_type)} (id=${data.id}) \u2014 no-op`);
|
|
30013
30515
|
}
|
|
@@ -30043,6 +30545,39 @@ var ParallAgentGateway = class {
|
|
|
30043
30545
|
source_id: sourceId
|
|
30044
30546
|
});
|
|
30045
30547
|
}
|
|
30548
|
+
/** True when this event's lifecycle is owned by the dispatch lane ledger. */
|
|
30549
|
+
usesLaneLedger(event) {
|
|
30550
|
+
return this.laneLedger != null && !this.ledgerDisabled && this.laneLedger.handles(event);
|
|
30551
|
+
}
|
|
30552
|
+
disableLedger(reason) {
|
|
30553
|
+
if (this.ledgerDisabled)
|
|
30554
|
+
return;
|
|
30555
|
+
this.ledgerDisabled = true;
|
|
30556
|
+
this.opts.log?.warn(`dispatch ledger unavailable (${reason}) \u2014 falling back to legacy received/ack flow`);
|
|
30557
|
+
}
|
|
30558
|
+
/**
|
|
30559
|
+
* Buffer grouping key. Lane-ledger message events group by full lane
|
|
30560
|
+
* identity (chat + thread) so a channel lane and a thread lane in the same
|
|
30561
|
+
* chat dispatch as separate turns with separate claims; everything else
|
|
30562
|
+
* keeps the historical chat-level grouping.
|
|
30563
|
+
*/
|
|
30564
|
+
dispatchGroupKey(event) {
|
|
30565
|
+
if (this.usesLaneLedger(event)) {
|
|
30566
|
+
return this.laneLedger.laneKeyFor(event);
|
|
30567
|
+
}
|
|
30568
|
+
return event.targetId;
|
|
30569
|
+
}
|
|
30570
|
+
// Lane-flow protocols live in gateway-lane-flow.ts; these thin delegates
|
|
30571
|
+
// keep call sites and tests on the class surface.
|
|
30572
|
+
laneFlowHost() {
|
|
30573
|
+
return this;
|
|
30574
|
+
}
|
|
30575
|
+
dispatchLaneGroup(opts) {
|
|
30576
|
+
return dispatchLaneGroup(this.laneFlowHost(), opts);
|
|
30577
|
+
}
|
|
30578
|
+
consumeTypedDispatch(ref, run, ack) {
|
|
30579
|
+
return consumeTypedDispatch(this.laneFlowHost(), ref, run, ack);
|
|
30580
|
+
}
|
|
30046
30581
|
buildDispatchContext(event, sessionKey) {
|
|
30047
30582
|
const binding = this.sessionBindings.get(sessionKey);
|
|
30048
30583
|
return {
|
|
@@ -30059,6 +30594,7 @@ var ParallAgentGateway = class {
|
|
|
30059
30594
|
noReply: event.noReply ?? false,
|
|
30060
30595
|
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey),
|
|
30061
30596
|
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey),
|
|
30597
|
+
contextDirPath: this.opts.dispatchContextDir,
|
|
30062
30598
|
client: this.opts.client,
|
|
30063
30599
|
log: this.opts.log
|
|
30064
30600
|
};
|
|
@@ -30098,7 +30634,7 @@ var ParallAgentGateway = class {
|
|
|
30098
30634
|
this.opts.log?.warn(`failed to create input step: ${String(err)}`);
|
|
30099
30635
|
}
|
|
30100
30636
|
}
|
|
30101
|
-
async createRuntimeStep(sessionId, event, runtimeEvent, stepIdFilePath, contextFilePath) {
|
|
30637
|
+
async createRuntimeStep(sessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2) {
|
|
30102
30638
|
const target = resolveStepTarget(event);
|
|
30103
30639
|
try {
|
|
30104
30640
|
switch (runtimeEvent.type) {
|
|
@@ -30144,6 +30680,9 @@ var ParallAgentGateway = class {
|
|
|
30144
30680
|
} else if (stepIdFilePath) {
|
|
30145
30681
|
this.writeStepIdFile(stepIdFilePath, step.id);
|
|
30146
30682
|
}
|
|
30683
|
+
if (laneContextFilePath2) {
|
|
30684
|
+
this.updateContextFileStepId(laneContextFilePath2, step.id);
|
|
30685
|
+
}
|
|
30147
30686
|
break;
|
|
30148
30687
|
}
|
|
30149
30688
|
case "tool_result":
|
|
@@ -30166,6 +30705,9 @@ var ParallAgentGateway = class {
|
|
|
30166
30705
|
} else if (stepIdFilePath) {
|
|
30167
30706
|
this.clearStepIdFile(stepIdFilePath);
|
|
30168
30707
|
}
|
|
30708
|
+
if (laneContextFilePath2) {
|
|
30709
|
+
this.updateContextFileStepId(laneContextFilePath2, null);
|
|
30710
|
+
}
|
|
30169
30711
|
break;
|
|
30170
30712
|
case "error":
|
|
30171
30713
|
await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
|
|
@@ -30185,28 +30727,28 @@ var ParallAgentGateway = class {
|
|
|
30185
30727
|
}
|
|
30186
30728
|
writeContextFile(filePath, ctx) {
|
|
30187
30729
|
try {
|
|
30188
|
-
|
|
30189
|
-
|
|
30730
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
30731
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30190
30732
|
} catch (err) {
|
|
30191
30733
|
this.opts.log?.warn(`failed to write context file ${filePath}: ${String(err)}`);
|
|
30192
30734
|
}
|
|
30193
30735
|
}
|
|
30194
30736
|
updateContextFileStepId(filePath, stepId) {
|
|
30195
30737
|
try {
|
|
30196
|
-
const raw =
|
|
30738
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
30197
30739
|
const ctx = JSON.parse(raw);
|
|
30198
30740
|
ctx.step_id = stepId;
|
|
30199
|
-
|
|
30741
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30200
30742
|
} catch (err) {
|
|
30201
30743
|
this.opts.log?.warn(`failed to update context file step_id ${filePath}: ${String(err)}`);
|
|
30202
30744
|
}
|
|
30203
30745
|
}
|
|
30204
30746
|
updateContextFileSessionId(filePath, sessionId) {
|
|
30205
30747
|
try {
|
|
30206
|
-
const raw =
|
|
30748
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
30207
30749
|
const ctx = JSON.parse(raw);
|
|
30208
30750
|
ctx.session_id = sessionId;
|
|
30209
|
-
|
|
30751
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30210
30752
|
} catch (err) {
|
|
30211
30753
|
this.opts.log?.warn(`failed to update context file session_id ${filePath}: ${String(err)}`);
|
|
30212
30754
|
}
|
|
@@ -30214,8 +30756,8 @@ var ParallAgentGateway = class {
|
|
|
30214
30756
|
/** @deprecated Use writeContextFile / updateContextFileStepId. */
|
|
30215
30757
|
writeStepIdFile(filePath, stepId) {
|
|
30216
30758
|
try {
|
|
30217
|
-
|
|
30218
|
-
|
|
30759
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
30760
|
+
fs2.writeFileSync(filePath, stepId, "utf8");
|
|
30219
30761
|
} catch (err) {
|
|
30220
30762
|
this.opts.log?.warn(`failed to write step id file ${filePath}: ${String(err)}`);
|
|
30221
30763
|
}
|
|
@@ -30223,7 +30765,7 @@ var ParallAgentGateway = class {
|
|
|
30223
30765
|
/** @deprecated Use writeContextFile / updateContextFileStepId. */
|
|
30224
30766
|
clearStepIdFile(filePath) {
|
|
30225
30767
|
try {
|
|
30226
|
-
|
|
30768
|
+
fs2.writeFileSync(filePath, "", "utf8");
|
|
30227
30769
|
} catch {
|
|
30228
30770
|
}
|
|
30229
30771
|
}
|
|
@@ -30232,7 +30774,7 @@ var ParallAgentGateway = class {
|
|
|
30232
30774
|
await this.createInputStep(sessionId, event);
|
|
30233
30775
|
}
|
|
30234
30776
|
}
|
|
30235
|
-
async bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath) {
|
|
30777
|
+
async bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2) {
|
|
30236
30778
|
const runtimeLaneKey = runtimeEvent.runtimeLaneKey || sessionKey;
|
|
30237
30779
|
const existing = this.sessionBindings.get(sessionKey);
|
|
30238
30780
|
if (existing && existing.runtimeLaneKey === runtimeLaneKey && existing.runtimeSessionId === runtimeEvent.runtimeSessionId) {
|
|
@@ -30276,6 +30818,9 @@ var ParallAgentGateway = class {
|
|
|
30276
30818
|
if (contextFilePath) {
|
|
30277
30819
|
this.updateContextFileSessionId(contextFilePath, session.id);
|
|
30278
30820
|
}
|
|
30821
|
+
if (laneContextFilePath2) {
|
|
30822
|
+
this.updateContextFileSessionId(laneContextFilePath2, session.id);
|
|
30823
|
+
}
|
|
30279
30824
|
await this.opts.onSessionBinding?.(binding);
|
|
30280
30825
|
return binding;
|
|
30281
30826
|
}
|
|
@@ -30302,14 +30847,27 @@ var ParallAgentGateway = class {
|
|
|
30302
30847
|
const dispatchContext = this.buildDispatchContext(event, sessionKey);
|
|
30303
30848
|
const contextFilePath = dispatchContext.contextFilePath;
|
|
30304
30849
|
const stepIdFilePath = dispatchContext.stepIdFilePath;
|
|
30850
|
+
const activeLane = this.ledgerDisabled ? void 0 : this.laneLedger?.getForEvent(event);
|
|
30851
|
+
const laneContextFilePath2 = activeLane ? this.laneLedger?.laneContextPath(activeLane) : void 0;
|
|
30852
|
+
const contextBody = {
|
|
30853
|
+
session_id: dispatchContext.sessionId ?? null,
|
|
30854
|
+
chat_id: dispatchContext.chatId ?? null,
|
|
30855
|
+
trigger_message_id: dispatchContext.triggerMessageId ?? null,
|
|
30856
|
+
no_reply: dispatchContext.noReply,
|
|
30857
|
+
step_id: null,
|
|
30858
|
+
dispatch_event_id: activeLane?.typedDispatchEventId ?? activeLane?.folded.get(event.messageId) ?? null,
|
|
30859
|
+
lane: activeLane?.lane ?? null,
|
|
30860
|
+
target_uri: activeLane?.targetUri ?? null,
|
|
30861
|
+
thread_root_id: activeLane?.threadRootId ?? null,
|
|
30862
|
+
// Typed binding hint for the CLI: which task this dispatch is about
|
|
30863
|
+
// (parall task update attaches the typed effect only on a match).
|
|
30864
|
+
task_id: event.type === "task" ? event.targetId : null
|
|
30865
|
+
};
|
|
30305
30866
|
if (contextFilePath) {
|
|
30306
|
-
this.writeContextFile(contextFilePath,
|
|
30307
|
-
|
|
30308
|
-
|
|
30309
|
-
|
|
30310
|
-
no_reply: dispatchContext.noReply,
|
|
30311
|
-
step_id: null
|
|
30312
|
-
});
|
|
30867
|
+
this.writeContextFile(contextFilePath, contextBody);
|
|
30868
|
+
}
|
|
30869
|
+
if (laneContextFilePath2) {
|
|
30870
|
+
this.writeContextFile(laneContextFilePath2, contextBody);
|
|
30313
30871
|
}
|
|
30314
30872
|
this.inFlightDispatches++;
|
|
30315
30873
|
const deadlineTimer = this.DISPATCH_DEADLINE_MS > 0 ? setTimeout(() => {
|
|
@@ -30336,7 +30894,7 @@ var ParallAgentGateway = class {
|
|
|
30336
30894
|
})) {
|
|
30337
30895
|
if (runtimeEvent.type === "runtime_session") {
|
|
30338
30896
|
const priorAgentSessionId = binding?.agentSessionId;
|
|
30339
|
-
binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath);
|
|
30897
|
+
binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2);
|
|
30340
30898
|
if (event.targetType === "channel_conversation" && binding.agentSessionId !== priorAgentSessionId) {
|
|
30341
30899
|
try {
|
|
30342
30900
|
await this.opts.client.setChannelConversationSession(this.opts.config.org_id, event.targetId, binding.agentSessionId);
|
|
@@ -30368,6 +30926,9 @@ var ParallAgentGateway = class {
|
|
|
30368
30926
|
await this.createInputStep(binding.agentSessionId, event);
|
|
30369
30927
|
inputStepsCreated = true;
|
|
30370
30928
|
}
|
|
30929
|
+
if (activeLane && !this.ledgerDisabled) {
|
|
30930
|
+
this.laneLedger?.maybeRenew(activeLane);
|
|
30931
|
+
}
|
|
30371
30932
|
if (captureText && runtimeEvent.type === "text" && runtimeEvent.text) {
|
|
30372
30933
|
captureText.push(runtimeEvent.text);
|
|
30373
30934
|
}
|
|
@@ -30384,7 +30945,7 @@ var ParallAgentGateway = class {
|
|
|
30384
30945
|
} else if (runtimeEvent.type === "tool_result" && pendingSendCallIds.delete(runtimeEvent.callId)) {
|
|
30385
30946
|
recordMessageSend(sessionKey, !runtimeEvent.error);
|
|
30386
30947
|
}
|
|
30387
|
-
await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath);
|
|
30948
|
+
await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
30388
30949
|
}
|
|
30389
30950
|
if (!binding) {
|
|
30390
30951
|
binding = this.sessionBindings.get(sessionKey);
|
|
@@ -30406,7 +30967,7 @@ var ParallAgentGateway = class {
|
|
|
30406
30967
|
await this.createRuntimeStep(binding.agentSessionId, event, {
|
|
30407
30968
|
type: "error",
|
|
30408
30969
|
message: `Dispatch failed: ${String(err)}`
|
|
30409
|
-
}, stepIdFilePath, contextFilePath);
|
|
30970
|
+
}, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
30410
30971
|
} catch (stepErr) {
|
|
30411
30972
|
if (this.isSessionNotLiveError(stepErr))
|
|
30412
30973
|
staleDetected = true;
|
|
@@ -30449,6 +31010,9 @@ var ParallAgentGateway = class {
|
|
|
30449
31010
|
} else if (stepIdFilePath) {
|
|
30450
31011
|
this.clearStepIdFile(stepIdFilePath);
|
|
30451
31012
|
}
|
|
31013
|
+
if (laneContextFilePath2) {
|
|
31014
|
+
this.updateContextFileStepId(laneContextFilePath2, null);
|
|
31015
|
+
}
|
|
30452
31016
|
this.inFlightDispatches--;
|
|
30453
31017
|
if (this.inFlightDispatches === 0 && this.drainResolvers.length > 0) {
|
|
30454
31018
|
const resolvers = this.drainResolvers.splice(0);
|
|
@@ -30506,13 +31070,39 @@ var ParallAgentGateway = class {
|
|
|
30506
31070
|
item.resolve(false);
|
|
30507
31071
|
break;
|
|
30508
31072
|
}
|
|
30509
|
-
|
|
31073
|
+
let items;
|
|
31074
|
+
const head = fork.queue[0];
|
|
31075
|
+
if (head && this.usesLaneLedger(head.event)) {
|
|
31076
|
+
const headKey = this.dispatchGroupKey(head.event);
|
|
31077
|
+
const splitAt = fork.queue.findIndex((it) => this.dispatchGroupKey(it.event) !== headKey);
|
|
31078
|
+
items = splitAt === -1 ? fork.queue.splice(0) : fork.queue.splice(0, splitAt);
|
|
31079
|
+
} else {
|
|
31080
|
+
items = fork.queue.splice(0);
|
|
31081
|
+
}
|
|
30510
31082
|
const events = items.map((item) => item.event);
|
|
30511
31083
|
const last = events[events.length - 1];
|
|
30512
31084
|
const earlier = events.slice(0, -1);
|
|
30513
31085
|
try {
|
|
30514
31086
|
const batchText = [];
|
|
30515
|
-
|
|
31087
|
+
let dispatched;
|
|
31088
|
+
if (this.usesLaneLedger(last)) {
|
|
31089
|
+
const outcome = await this.dispatchLaneGroup({
|
|
31090
|
+
events,
|
|
31091
|
+
sessionKey: fork.fork.sessionKey,
|
|
31092
|
+
body: buildForkScopePrefix(last) + buildEventBody(last),
|
|
31093
|
+
earlier,
|
|
31094
|
+
captureText: batchText,
|
|
31095
|
+
hasMoreLocal: () => fork.queue.length > 0
|
|
31096
|
+
});
|
|
31097
|
+
if (outcome === "foreign") {
|
|
31098
|
+
for (const item of items)
|
|
31099
|
+
item.resolve(false);
|
|
31100
|
+
break;
|
|
31101
|
+
}
|
|
31102
|
+
dispatched = outcome === "dispatched";
|
|
31103
|
+
} else {
|
|
31104
|
+
dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildForkScopePrefix(last) + buildEventBody(last), earlier, batchText);
|
|
31105
|
+
}
|
|
30516
31106
|
if (!dispatched) {
|
|
30517
31107
|
for (const item of items) {
|
|
30518
31108
|
item.resolve(false);
|
|
@@ -30615,9 +31205,9 @@ var ParallAgentGateway = class {
|
|
|
30615
31205
|
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`);
|
|
30616
31206
|
break;
|
|
30617
31207
|
}
|
|
30618
|
-
const
|
|
31208
|
+
const groupKey = this.dispatchGroupKey(this.dispatchState.mainBuffer[0]);
|
|
30619
31209
|
const events = [];
|
|
30620
|
-
while (this.dispatchState.mainBuffer[0]
|
|
31210
|
+
while (this.dispatchState.mainBuffer[0] && this.dispatchGroupKey(this.dispatchState.mainBuffer[0]) === groupKey) {
|
|
30621
31211
|
events.push(this.dispatchState.mainBuffer.shift());
|
|
30622
31212
|
}
|
|
30623
31213
|
const event = events[events.length - 1];
|
|
@@ -30626,7 +31216,36 @@ var ParallAgentGateway = class {
|
|
|
30626
31216
|
const pendingFork = hasPendingInjections ? [] : this.dispatchState.pendingForkResults.splice(0);
|
|
30627
31217
|
const forkPrefix = buildForkResultPrefix(pendingFork);
|
|
30628
31218
|
this.dispatchState.mainCurrentTargetId = event.targetId;
|
|
31219
|
+
this.mainCurrentGroupKey = groupKey;
|
|
30629
31220
|
this.dispatchState.mainPreDispatchBranchPoint = this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
|
|
31221
|
+
if (this.usesLaneLedger(event)) {
|
|
31222
|
+
let outcome;
|
|
31223
|
+
try {
|
|
31224
|
+
outcome = await this.dispatchLaneGroup({
|
|
31225
|
+
events,
|
|
31226
|
+
sessionKey: this.opts.runtimeKey,
|
|
31227
|
+
body: forkPrefix + buildEventBody(event),
|
|
31228
|
+
earlier,
|
|
31229
|
+
hasMoreLocal: () => this.dispatchState.mainBuffer.some((e) => this.dispatchGroupKey(e) === groupKey)
|
|
31230
|
+
});
|
|
31231
|
+
} catch (err) {
|
|
31232
|
+
this.opts.log?.error(`lane dispatch failed for ${event.messageId}: ${String(err)}`);
|
|
31233
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31234
|
+
for (const ev of events)
|
|
31235
|
+
this.dispatchedMessages.delete(ev.messageId);
|
|
31236
|
+
continue;
|
|
31237
|
+
}
|
|
31238
|
+
if (outcome === "shutdown") {
|
|
31239
|
+
this.dispatchState.mainBuffer.unshift(...events);
|
|
31240
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31241
|
+
break;
|
|
31242
|
+
}
|
|
31243
|
+
if (outcome === "foreign") {
|
|
31244
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31245
|
+
continue;
|
|
31246
|
+
}
|
|
31247
|
+
continue;
|
|
31248
|
+
}
|
|
30630
31249
|
try {
|
|
30631
31250
|
await this.emitDispatchReceived(event);
|
|
30632
31251
|
} catch (err) {
|
|
@@ -30655,6 +31274,7 @@ var ParallAgentGateway = class {
|
|
|
30655
31274
|
this.draining = false;
|
|
30656
31275
|
this.dispatchState.mainDispatching = false;
|
|
30657
31276
|
this.dispatchState.mainCurrentTargetId = void 0;
|
|
31277
|
+
this.mainCurrentGroupKey = void 0;
|
|
30658
31278
|
this.dispatchState.mainPreDispatchBranchPoint = void 0;
|
|
30659
31279
|
if (!this.shuttingDown && this.dispatchState.mainBuffer.length > 0) {
|
|
30660
31280
|
setTimeout(() => {
|
|
@@ -30674,13 +31294,38 @@ var ParallAgentGateway = class {
|
|
|
30674
31294
|
const forkPrefix = buildForkResultPrefix(pendingFork);
|
|
30675
31295
|
this.dispatchState.mainDispatching = true;
|
|
30676
31296
|
this.dispatchState.mainCurrentTargetId = event.targetId;
|
|
31297
|
+
this.mainCurrentGroupKey = this.dispatchGroupKey(event);
|
|
30677
31298
|
this.dispatchState.mainPreDispatchBranchPoint = this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
|
|
31299
|
+
if (this.usesLaneLedger(event)) {
|
|
31300
|
+
let outcome = "shutdown";
|
|
31301
|
+
try {
|
|
31302
|
+
try {
|
|
31303
|
+
outcome = await this.dispatchLaneGroup({
|
|
31304
|
+
events: [event],
|
|
31305
|
+
sessionKey: this.opts.runtimeKey,
|
|
31306
|
+
body: forkPrefix + buildEventBody(event),
|
|
31307
|
+
earlier: [],
|
|
31308
|
+
hasMoreLocal: () => this.dispatchState.mainBuffer.some((e) => this.dispatchGroupKey(e) === this.dispatchGroupKey(event))
|
|
31309
|
+
});
|
|
31310
|
+
} catch (err) {
|
|
31311
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31312
|
+
throw err;
|
|
31313
|
+
}
|
|
31314
|
+
if (outcome !== "dispatched") {
|
|
31315
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31316
|
+
}
|
|
31317
|
+
} finally {
|
|
31318
|
+
await this.drainMainBuffer();
|
|
31319
|
+
}
|
|
31320
|
+
return outcome === "dispatched";
|
|
31321
|
+
}
|
|
30678
31322
|
try {
|
|
30679
31323
|
await this.emitDispatchReceived(event);
|
|
30680
31324
|
} catch (err) {
|
|
30681
31325
|
this.opts.log?.warn?.(`mark-received failed, leaving unacked for retry: ${String(err)}`);
|
|
30682
31326
|
this.dispatchState.mainDispatching = false;
|
|
30683
31327
|
this.dispatchState.mainCurrentTargetId = void 0;
|
|
31328
|
+
this.mainCurrentGroupKey = void 0;
|
|
30684
31329
|
this.dispatchState.mainPreDispatchBranchPoint = void 0;
|
|
30685
31330
|
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
30686
31331
|
return false;
|
|
@@ -30701,7 +31346,11 @@ var ParallAgentGateway = class {
|
|
|
30701
31346
|
return false;
|
|
30702
31347
|
}
|
|
30703
31348
|
this.dispatchState.mainBuffer.push(event);
|
|
30704
|
-
if (this.
|
|
31349
|
+
if (this.usesLaneLedger(event)) {
|
|
31350
|
+
if (this.mainCurrentGroupKey === this.dispatchGroupKey(event) && await this.laneLedger?.steerLive(event) && await this.opts.dispatchAdapter.enqueueDuringDispatch?.(this.opts.runtimeKey, buildEventBody(event))) {
|
|
31351
|
+
this.opts.log?.info(`steer folded+injected for ${event.messageId} (will drain for bookkeeping)`);
|
|
31352
|
+
}
|
|
31353
|
+
} else if (this.dispatchState.mainCurrentTargetId === event.targetId && await this.opts.dispatchAdapter.enqueueDuringDispatch?.(this.opts.runtimeKey, buildEventBody(event))) {
|
|
30705
31354
|
this.opts.log?.info(`steer injected for ${event.messageId} (will drain for bookkeeping)`);
|
|
30706
31355
|
}
|
|
30707
31356
|
if (!this.dispatchState.mainDispatching && !this.draining && this.dispatchState.mainBuffer.length > 0) {
|
|
@@ -30872,8 +31521,10 @@ var ParallAgentGateway = class {
|
|
|
30872
31521
|
try {
|
|
30873
31522
|
const dispatched = await this.handleInboundEvent(event);
|
|
30874
31523
|
if (dispatched) {
|
|
30875
|
-
|
|
30876
|
-
|
|
31524
|
+
if (!this.usesLaneLedger(event)) {
|
|
31525
|
+
this.opts.client.ackDispatch(this.opts.config.org_id, { source_type: "message", source_id: data.id }).catch(() => {
|
|
31526
|
+
});
|
|
31527
|
+
}
|
|
30877
31528
|
} else {
|
|
30878
31529
|
this.dispatchedMessages.delete(data.id);
|
|
30879
31530
|
}
|
|
@@ -30882,7 +31533,21 @@ var ParallAgentGateway = class {
|
|
|
30882
31533
|
this.dispatchedMessages.delete(data.id);
|
|
30883
31534
|
}
|
|
30884
31535
|
}
|
|
30885
|
-
|
|
31536
|
+
// Ledger re-drive consumption: dispatch.new message hints re-enter the
|
|
31537
|
+
// shared WorkItem consumption path (same protocol as catch-up).
|
|
31538
|
+
async handleMessageRedrive(item) {
|
|
31539
|
+
if (!item.chat_id || !item.source_id)
|
|
31540
|
+
return;
|
|
31541
|
+
await this.consumeMessageWorkItem({
|
|
31542
|
+
id: item.id,
|
|
31543
|
+
source_id: item.source_id,
|
|
31544
|
+
chat_id: item.chat_id
|
|
31545
|
+
});
|
|
31546
|
+
}
|
|
31547
|
+
consumeMessageWorkItem(item) {
|
|
31548
|
+
return consumeMessageWorkItem(this.laneFlowHost(), item);
|
|
31549
|
+
}
|
|
31550
|
+
async handleTaskAssignment(task, ackSourceId, dispatchEventId) {
|
|
30886
31551
|
if (this.shuttingDown)
|
|
30887
31552
|
return false;
|
|
30888
31553
|
const dedupeKey = `${task.id}:${task.updated_at}`;
|
|
@@ -30911,7 +31576,8 @@ var ParallAgentGateway = class {
|
|
|
30911
31576
|
body: parts.join("\n"),
|
|
30912
31577
|
sentAt: task.updated_at ?? task.created_at,
|
|
30913
31578
|
ackSourceType: "task_activity",
|
|
30914
|
-
ackSourceId
|
|
31579
|
+
ackSourceId,
|
|
31580
|
+
dispatchEventId
|
|
30915
31581
|
};
|
|
30916
31582
|
const dispatched = await this.handleInboundEvent(event);
|
|
30917
31583
|
if (!dispatched) {
|
|
@@ -30935,7 +31601,7 @@ var ParallAgentGateway = class {
|
|
|
30935
31601
|
this.opts.log?.info(`skipping stale task dispatch ${ackSourceId ?? taskId} \u2014 assigned to ${task.assignee_id}, creator ${task.creator_id}`);
|
|
30936
31602
|
return true;
|
|
30937
31603
|
}
|
|
30938
|
-
return this.handleTaskAssignment(task, ackSourceId);
|
|
31604
|
+
return this.handleTaskAssignment(task, ackSourceId, opts.dispatchEventId);
|
|
30939
31605
|
}
|
|
30940
31606
|
async handleTaskComment(commentId, taskId, actorId, deliveryReason) {
|
|
30941
31607
|
if (this.shuttingDown)
|
|
@@ -31343,74 +32009,46 @@ var ParallAgentGateway = class {
|
|
|
31343
32009
|
}
|
|
31344
32010
|
processed++;
|
|
31345
32011
|
try {
|
|
31346
|
-
|
|
32012
|
+
const ackItem = () => {
|
|
32013
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
32014
|
+
});
|
|
32015
|
+
};
|
|
31347
32016
|
if (item.event_type === "task_assign" && item.task_id) {
|
|
31348
32017
|
try {
|
|
31349
|
-
|
|
32018
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
|
|
32019
|
+
dispatchEventId
|
|
32020
|
+
}), ackItem);
|
|
31350
32021
|
} catch (err) {
|
|
31351
32022
|
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
31352
32023
|
continue;
|
|
31353
32024
|
}
|
|
31354
32025
|
} else if (item.event_type === "task_update" && item.task_id) {
|
|
31355
32026
|
try {
|
|
31356
|
-
|
|
32027
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
|
|
32028
|
+
allowCreator: true,
|
|
32029
|
+
dispatchEventId
|
|
32030
|
+
}), ackItem);
|
|
31357
32031
|
} catch (err) {
|
|
31358
32032
|
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
31359
32033
|
continue;
|
|
31360
32034
|
}
|
|
31361
32035
|
} else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
|
|
31362
|
-
|
|
32036
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleTaskComment(item.source_id, item.task_id ?? "", item.actor_id, item.delivery_reason), ackItem);
|
|
31363
32037
|
} else if (item.event_type === "wiki_comment" && item.source_id) {
|
|
31364
|
-
|
|
32038
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleWikiComment(item.source_id, item.actor_id, item.delivery_reason), ackItem);
|
|
31365
32039
|
} else if (item.event_type === "schedule.fire" && item.source_id) {
|
|
31366
|
-
|
|
32040
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleScheduleFire(item.source_id, item.actor_id), ackItem);
|
|
31367
32041
|
} else if (item.event_type === "external_trigger" && item.source_id) {
|
|
31368
|
-
|
|
32042
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleExternalTriggerRun(item.source_id), ackItem);
|
|
31369
32043
|
} else if (item.event_type === "channel_message" && item.source_id) {
|
|
31370
|
-
|
|
32044
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleChannelMessage(item.source_id), ackItem);
|
|
31371
32045
|
} else if (item.event_type === "approval_decided" && item.source_id) {
|
|
31372
|
-
|
|
32046
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null), ackItem);
|
|
31373
32047
|
} else if (item.event_type === "message" && item.source_id && item.chat_id) {
|
|
31374
|
-
|
|
31375
|
-
|
|
31376
|
-
|
|
31377
|
-
|
|
31378
|
-
try {
|
|
31379
|
-
msg = await this.opts.client.getMessage(item.source_id);
|
|
31380
|
-
} catch (err) {
|
|
31381
|
-
const status = err?.status;
|
|
31382
|
-
if (status === 404) {
|
|
31383
|
-
msg = null;
|
|
31384
|
-
} else {
|
|
31385
|
-
msgFetchFailed = true;
|
|
31386
|
-
this.opts.log?.warn(`catch-up message fetch failed for ${item.source_id}, leaving pending: ${String(err)}`);
|
|
31387
|
-
}
|
|
31388
|
-
}
|
|
31389
|
-
if (msgFetchFailed) {
|
|
31390
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31391
|
-
continue;
|
|
31392
|
-
}
|
|
31393
|
-
if (!msg || msg.sender_id === this.opts.agentUserId) {
|
|
31394
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31395
|
-
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
31396
|
-
});
|
|
31397
|
-
continue;
|
|
31398
|
-
}
|
|
31399
|
-
const decision = await this.buildMessageDispatchDecision(item.chat_id, msg);
|
|
31400
|
-
if (decision.action === "retry") {
|
|
31401
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31402
|
-
continue;
|
|
31403
|
-
}
|
|
31404
|
-
if (decision.action === "skip") {
|
|
31405
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31406
|
-
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
31407
|
-
});
|
|
31408
|
-
continue;
|
|
31409
|
-
}
|
|
31410
|
-
dispatched = await this.handleInboundEvent(decision.event);
|
|
31411
|
-
}
|
|
31412
|
-
if (dispatched) {
|
|
31413
|
-
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
32048
|
+
await this.consumeMessageWorkItem({
|
|
32049
|
+
id: item.id,
|
|
32050
|
+
source_id: item.source_id,
|
|
32051
|
+
chat_id: item.chat_id
|
|
31414
32052
|
});
|
|
31415
32053
|
}
|
|
31416
32054
|
} catch (err) {
|
|
@@ -31476,6 +32114,14 @@ ${fullSummary}` : fullSummary;
|
|
|
31476
32114
|
this.abortFork(targetId, "ws reconnect");
|
|
31477
32115
|
}
|
|
31478
32116
|
}
|
|
32117
|
+
if (this.laneLedger && this.inFlightDispatches === 0 && this.laneLedger.activeCount > 0) {
|
|
32118
|
+
log?.info(`releasing ${this.laneLedger.activeCount} stale lane(s) on reconnect`);
|
|
32119
|
+
await this.laneLedger.releaseAll();
|
|
32120
|
+
}
|
|
32121
|
+
if (this.laneLedger && this.ledgerDisabled) {
|
|
32122
|
+
log?.info("re-probing dispatch ledger after reconnect (was disabled)");
|
|
32123
|
+
this.ledgerDisabled = false;
|
|
32124
|
+
}
|
|
31479
32125
|
const intervalSec = data.heartbeat_interval > 0 ? data.heartbeat_interval : 30;
|
|
31480
32126
|
try {
|
|
31481
32127
|
const count = await fetchAllChats(client, config.org_id, this.chatInfoMap);
|
|
@@ -31546,6 +32192,10 @@ ${fullSummary}` : fullSummary;
|
|
|
31546
32192
|
}
|
|
31547
32193
|
if (this.heartbeatTimer)
|
|
31548
32194
|
clearInterval(this.heartbeatTimer);
|
|
32195
|
+
if (this.laneLedger && this.laneLedger.activeCount > 0) {
|
|
32196
|
+
this.opts.log?.info(`releasing ${this.laneLedger.activeCount} lane(s) on shutdown`);
|
|
32197
|
+
await this.laneLedger.releaseAll();
|
|
32198
|
+
}
|
|
31549
32199
|
await this.opts.onBeforeDisconnect?.();
|
|
31550
32200
|
this.opts.ws.disconnect();
|
|
31551
32201
|
this.opts.log?.info(`disconnected`);
|
|
@@ -31556,8 +32206,8 @@ ${fullSummary}` : fullSummary;
|
|
|
31556
32206
|
import { execSync } from "node:child_process";
|
|
31557
32207
|
import { constants } from "node:fs";
|
|
31558
32208
|
import * as fsSync from "node:fs";
|
|
31559
|
-
import * as
|
|
31560
|
-
import * as
|
|
32209
|
+
import * as fs3 from "node:fs/promises";
|
|
32210
|
+
import * as path3 from "node:path";
|
|
31561
32211
|
var DEFAULT_MAX_TOTAL_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
31562
32212
|
var DEFAULT_ATTACHMENT_CACHE_MAX_BYTES = 512 * 1024 * 1024;
|
|
31563
32213
|
var DEFAULT_ATTACHMENT_DOWNLOAD_TIMEOUT_MS = 3e4;
|
|
@@ -31587,11 +32237,11 @@ async function prepareLocalImageAttachments(event, context2, opts) {
|
|
|
31587
32237
|
};
|
|
31588
32238
|
}
|
|
31589
32239
|
const rootDir = await ensureAttachmentRootDir(opts.workspaceDir);
|
|
31590
|
-
const messageDir =
|
|
32240
|
+
const messageDir = path3.join(rootDir, sanitizePathSegment(event.messageId));
|
|
31591
32241
|
await ensurePathIsNotSymlink(messageDir);
|
|
31592
|
-
await
|
|
32242
|
+
await fs3.mkdir(messageDir, { recursive: true });
|
|
31593
32243
|
await ensurePathIsNotSymlink(messageDir);
|
|
31594
|
-
const activeMessageDir =
|
|
32244
|
+
const activeMessageDir = path3.resolve(messageDir);
|
|
31595
32245
|
activeAttachmentDirs.add(activeMessageDir);
|
|
31596
32246
|
const maintenanceCooldownMs = opts.maintenanceCooldownMs ?? DEFAULT_MAINTENANCE_COOLDOWN_MS;
|
|
31597
32247
|
const maintenancePromise = scheduleAttachmentMaintenance(rootDir, {
|
|
@@ -31608,7 +32258,7 @@ async function prepareLocalImageAttachments(event, context2, opts) {
|
|
|
31608
32258
|
const notes = [];
|
|
31609
32259
|
let downloadedBytes = 0;
|
|
31610
32260
|
for (const att of imageAttachments) {
|
|
31611
|
-
const localPath =
|
|
32261
|
+
const localPath = path3.join(messageDir, localFileName(att.id, att.fileName, att.mimeType));
|
|
31612
32262
|
const downloadTimeoutMs = opts.downloadTimeoutMs ?? DEFAULT_ATTACHMENT_DOWNLOAD_TIMEOUT_MS;
|
|
31613
32263
|
const fetchFresh = async () => {
|
|
31614
32264
|
const fileInfo = await withTimeout(context2.client.getFileUrl(att.id), downloadTimeoutMs, `file URL lookup timed out after ${downloadTimeoutMs}ms`);
|
|
@@ -31665,7 +32315,7 @@ async function appendPreparedLocalAttachmentRefs(body, event, context2, opts) {
|
|
|
31665
32315
|
return { body: appendLocalAttachmentRefs(body, attachments), attachments };
|
|
31666
32316
|
}
|
|
31667
32317
|
function pinLocalAttachmentPaths(images) {
|
|
31668
|
-
const dirs = new Set(images.map((image) =>
|
|
32318
|
+
const dirs = new Set(images.map((image) => path3.resolve(path3.dirname(image.localPath))));
|
|
31669
32319
|
for (const dir of dirs) {
|
|
31670
32320
|
activeAttachmentDirs.add(dir);
|
|
31671
32321
|
}
|
|
@@ -31680,7 +32330,7 @@ function pinLocalAttachmentPaths(images) {
|
|
|
31680
32330
|
};
|
|
31681
32331
|
}
|
|
31682
32332
|
function attachmentRootDir(workspaceDir) {
|
|
31683
|
-
return
|
|
32333
|
+
return path3.join(path3.resolve(workspaceDir), ".parall", "attachments");
|
|
31684
32334
|
}
|
|
31685
32335
|
function ensureLocalAttachmentGitExclude(workingDirectory) {
|
|
31686
32336
|
try {
|
|
@@ -31689,8 +32339,8 @@ function ensureLocalAttachmentGitExclude(workingDirectory) {
|
|
|
31689
32339
|
encoding: "utf8",
|
|
31690
32340
|
stdio: ["ignore", "pipe", "ignore"]
|
|
31691
32341
|
}).trim();
|
|
31692
|
-
const excludePath =
|
|
31693
|
-
fsSync.mkdirSync(
|
|
32342
|
+
const excludePath = path3.isAbsolute(rel) ? rel : path3.join(workingDirectory, rel);
|
|
32343
|
+
fsSync.mkdirSync(path3.dirname(excludePath), { recursive: true });
|
|
31694
32344
|
const existing = fsSync.existsSync(excludePath) ? fsSync.readFileSync(excludePath, "utf8") : "";
|
|
31695
32345
|
if (existing.split(/\r?\n/).some((line) => line.trim() === ".parall/"))
|
|
31696
32346
|
return;
|
|
@@ -31726,18 +32376,18 @@ function scheduleAttachmentMaintenance(rootDir, opts) {
|
|
|
31726
32376
|
return run;
|
|
31727
32377
|
}
|
|
31728
32378
|
async function ensureAttachmentRootDir(workspaceDir) {
|
|
31729
|
-
const workspaceRoot =
|
|
31730
|
-
const parallDir =
|
|
32379
|
+
const workspaceRoot = path3.resolve(workspaceDir);
|
|
32380
|
+
const parallDir = path3.join(workspaceRoot, ".parall");
|
|
31731
32381
|
const rootDir = attachmentRootDir(workspaceRoot);
|
|
31732
|
-
await
|
|
32382
|
+
await fs3.mkdir(workspaceRoot, { recursive: true });
|
|
31733
32383
|
await ensurePathIsNotSymlink(parallDir);
|
|
31734
|
-
await
|
|
32384
|
+
await fs3.mkdir(parallDir, { recursive: true, mode: 448 });
|
|
31735
32385
|
await ensurePathIsNotSymlink(parallDir);
|
|
31736
32386
|
await ensurePathIsNotSymlink(rootDir);
|
|
31737
|
-
await
|
|
32387
|
+
await fs3.mkdir(rootDir, { recursive: true, mode: 448 });
|
|
31738
32388
|
await ensurePathIsNotSymlink(rootDir);
|
|
31739
|
-
const realWorkspace = await
|
|
31740
|
-
const realRoot = await
|
|
32389
|
+
const realWorkspace = await fs3.realpath(workspaceRoot);
|
|
32390
|
+
const realRoot = await fs3.realpath(rootDir);
|
|
31741
32391
|
if (!isPathInside(realRoot, realWorkspace)) {
|
|
31742
32392
|
throw new Error(`attachment root escapes workspace: ${rootDir}`);
|
|
31743
32393
|
}
|
|
@@ -31745,7 +32395,7 @@ async function ensureAttachmentRootDir(workspaceDir) {
|
|
|
31745
32395
|
}
|
|
31746
32396
|
async function ensurePathIsNotSymlink(filePath) {
|
|
31747
32397
|
try {
|
|
31748
|
-
const stat = await
|
|
32398
|
+
const stat = await fs3.lstat(filePath);
|
|
31749
32399
|
if (stat.isSymbolicLink()) {
|
|
31750
32400
|
throw new Error(`refusing to use symlinked attachment path ${filePath}`);
|
|
31751
32401
|
}
|
|
@@ -31756,8 +32406,8 @@ async function ensurePathIsNotSymlink(filePath) {
|
|
|
31756
32406
|
}
|
|
31757
32407
|
}
|
|
31758
32408
|
function isPathInside(childPath, parentPath) {
|
|
31759
|
-
const rel =
|
|
31760
|
-
return rel === "" || !!rel && !rel.startsWith("..") && !
|
|
32409
|
+
const rel = path3.relative(parentPath, childPath);
|
|
32410
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !path3.isAbsolute(rel);
|
|
31761
32411
|
}
|
|
31762
32412
|
async function existingUsableFile(filePath, expectedSize, rootDir) {
|
|
31763
32413
|
try {
|
|
@@ -31768,30 +32418,30 @@ async function existingUsableFile(filePath, expectedSize, rootDir) {
|
|
|
31768
32418
|
}
|
|
31769
32419
|
}
|
|
31770
32420
|
async function localFileStatInsideRoot(filePath, rootDir) {
|
|
31771
|
-
const stat = await
|
|
32421
|
+
const stat = await fs3.lstat(filePath);
|
|
31772
32422
|
if (stat.isSymbolicLink()) {
|
|
31773
32423
|
throw new Error(`refusing to use symlinked attachment file ${filePath}`);
|
|
31774
32424
|
}
|
|
31775
32425
|
if (!stat.isFile()) {
|
|
31776
32426
|
throw new Error(`attachment path is not a file ${filePath}`);
|
|
31777
32427
|
}
|
|
31778
|
-
const realRoot = await
|
|
31779
|
-
const realFile = await
|
|
32428
|
+
const realRoot = await fs3.realpath(rootDir);
|
|
32429
|
+
const realFile = await fs3.realpath(filePath);
|
|
31780
32430
|
if (!isPathInside(realFile, realRoot)) {
|
|
31781
32431
|
throw new Error(`attachment file escapes workspace: ${filePath}`);
|
|
31782
32432
|
}
|
|
31783
32433
|
return stat;
|
|
31784
32434
|
}
|
|
31785
32435
|
async function localDirectoryStatInsideRoot(dirPath, rootDir) {
|
|
31786
|
-
const stat = await
|
|
32436
|
+
const stat = await fs3.lstat(dirPath);
|
|
31787
32437
|
if (stat.isSymbolicLink()) {
|
|
31788
32438
|
throw new Error(`refusing to use symlinked attachment directory ${dirPath}`);
|
|
31789
32439
|
}
|
|
31790
32440
|
if (!stat.isDirectory()) {
|
|
31791
32441
|
throw new Error(`attachment path is not a directory ${dirPath}`);
|
|
31792
32442
|
}
|
|
31793
|
-
const realRoot = await
|
|
31794
|
-
const realDir = await
|
|
32443
|
+
const realRoot = await fs3.realpath(rootDir);
|
|
32444
|
+
const realDir = await fs3.realpath(dirPath);
|
|
31795
32445
|
if (!isPathInside(realDir, realRoot)) {
|
|
31796
32446
|
throw new Error(`attachment directory escapes workspace: ${dirPath}`);
|
|
31797
32447
|
}
|
|
@@ -31799,7 +32449,7 @@ async function localDirectoryStatInsideRoot(dirPath, rootDir) {
|
|
|
31799
32449
|
}
|
|
31800
32450
|
async function openLocalFileInsideRoot(filePath, rootDir) {
|
|
31801
32451
|
const checkedStat = await localFileStatInsideRoot(filePath, rootDir);
|
|
31802
|
-
const file = await
|
|
32452
|
+
const file = await fs3.open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
31803
32453
|
let keepOpen = false;
|
|
31804
32454
|
try {
|
|
31805
32455
|
const openedStat = await file.stat();
|
|
@@ -31815,8 +32465,8 @@ async function openLocalFileInsideRoot(filePath, rootDir) {
|
|
|
31815
32465
|
}
|
|
31816
32466
|
}
|
|
31817
32467
|
async function openLocalTempFileInsideRoot(filePath, rootDir) {
|
|
31818
|
-
await localDirectoryStatInsideRoot(
|
|
31819
|
-
const file = await
|
|
32468
|
+
await localDirectoryStatInsideRoot(path3.dirname(filePath), rootDir);
|
|
32469
|
+
const file = await fs3.open(filePath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
|
|
31820
32470
|
let keepOpen = false;
|
|
31821
32471
|
try {
|
|
31822
32472
|
const checkedStat = await localFileStatInsideRoot(filePath, rootDir);
|
|
@@ -31841,7 +32491,7 @@ async function assertLocalFileIdentity(filePath, rootDir, expected) {
|
|
|
31841
32491
|
async function removeLocalFileIfInside(filePath, rootDir) {
|
|
31842
32492
|
try {
|
|
31843
32493
|
await localFileStatInsideRoot(filePath, rootDir);
|
|
31844
|
-
await
|
|
32494
|
+
await fs3.rm(filePath, { force: true });
|
|
31845
32495
|
} catch {
|
|
31846
32496
|
}
|
|
31847
32497
|
}
|
|
@@ -31854,7 +32504,7 @@ function sameFile(a, b) {
|
|
|
31854
32504
|
async function cleanupOldAttachmentFiles(rootDir, ttlMs, log, preserveDirs) {
|
|
31855
32505
|
let entries;
|
|
31856
32506
|
try {
|
|
31857
|
-
entries = await
|
|
32507
|
+
entries = await fs3.readdir(rootDir, { withFileTypes: true });
|
|
31858
32508
|
} catch {
|
|
31859
32509
|
return;
|
|
31860
32510
|
}
|
|
@@ -31862,15 +32512,15 @@ async function cleanupOldAttachmentFiles(rootDir, ttlMs, log, preserveDirs) {
|
|
|
31862
32512
|
await Promise.all(entries.map(async (entry) => {
|
|
31863
32513
|
if (!entry.isDirectory())
|
|
31864
32514
|
return;
|
|
31865
|
-
const fullPath =
|
|
32515
|
+
const fullPath = path3.join(rootDir, entry.name);
|
|
31866
32516
|
try {
|
|
31867
|
-
if (preserveDirs?.has(
|
|
32517
|
+
if (preserveDirs?.has(path3.resolve(fullPath)))
|
|
31868
32518
|
return;
|
|
31869
|
-
const stat = await
|
|
32519
|
+
const stat = await fs3.lstat(fullPath);
|
|
31870
32520
|
if (!stat.isDirectory())
|
|
31871
32521
|
return;
|
|
31872
32522
|
if (stat.mtimeMs < cutoff) {
|
|
31873
|
-
await
|
|
32523
|
+
await fs3.rm(fullPath, { recursive: true, force: true });
|
|
31874
32524
|
}
|
|
31875
32525
|
} catch (err) {
|
|
31876
32526
|
log?.warn?.(`agent-core: failed to clean attachment temp dir ${fullPath}: ${String(err)}`);
|
|
@@ -31882,7 +32532,7 @@ async function pruneAttachmentCache(rootDir, maxBytes, log, preserveDirs) {
|
|
|
31882
32532
|
return;
|
|
31883
32533
|
let entries;
|
|
31884
32534
|
try {
|
|
31885
|
-
entries = await
|
|
32535
|
+
entries = await fs3.readdir(rootDir, { withFileTypes: true });
|
|
31886
32536
|
} catch {
|
|
31887
32537
|
return;
|
|
31888
32538
|
}
|
|
@@ -31891,9 +32541,9 @@ async function pruneAttachmentCache(rootDir, maxBytes, log, preserveDirs) {
|
|
|
31891
32541
|
for (const entry of entries) {
|
|
31892
32542
|
if (!entry.isDirectory())
|
|
31893
32543
|
continue;
|
|
31894
|
-
const fullPath =
|
|
32544
|
+
const fullPath = path3.join(rootDir, entry.name);
|
|
31895
32545
|
try {
|
|
31896
|
-
const stat = await
|
|
32546
|
+
const stat = await fs3.lstat(fullPath);
|
|
31897
32547
|
if (!stat.isDirectory())
|
|
31898
32548
|
continue;
|
|
31899
32549
|
const size = await directorySize(fullPath);
|
|
@@ -31909,10 +32559,10 @@ async function pruneAttachmentCache(rootDir, maxBytes, log, preserveDirs) {
|
|
|
31909
32559
|
for (const dir of dirs) {
|
|
31910
32560
|
if (total <= maxBytes)
|
|
31911
32561
|
break;
|
|
31912
|
-
if (preserveDirs?.has(
|
|
32562
|
+
if (preserveDirs?.has(path3.resolve(dir.path)))
|
|
31913
32563
|
continue;
|
|
31914
32564
|
try {
|
|
31915
|
-
await
|
|
32565
|
+
await fs3.rm(dir.path, { recursive: true, force: true });
|
|
31916
32566
|
total -= dir.size;
|
|
31917
32567
|
} catch (err) {
|
|
31918
32568
|
log?.warn?.(`agent-core: failed to prune attachment cache dir ${dir.path}: ${String(err)}`);
|
|
@@ -31921,12 +32571,12 @@ async function pruneAttachmentCache(rootDir, maxBytes, log, preserveDirs) {
|
|
|
31921
32571
|
}
|
|
31922
32572
|
async function directorySize(dirPath) {
|
|
31923
32573
|
let total = 0;
|
|
31924
|
-
const entries = await
|
|
32574
|
+
const entries = await fs3.readdir(dirPath, { withFileTypes: true });
|
|
31925
32575
|
for (const entry of entries) {
|
|
31926
|
-
const fullPath =
|
|
32576
|
+
const fullPath = path3.join(dirPath, entry.name);
|
|
31927
32577
|
let stat;
|
|
31928
32578
|
try {
|
|
31929
|
-
stat = await
|
|
32579
|
+
stat = await fs3.lstat(fullPath);
|
|
31930
32580
|
} catch {
|
|
31931
32581
|
continue;
|
|
31932
32582
|
}
|
|
@@ -31941,10 +32591,10 @@ async function directorySize(dirPath) {
|
|
|
31941
32591
|
return total;
|
|
31942
32592
|
}
|
|
31943
32593
|
function activeDirsForRoot(rootDir) {
|
|
31944
|
-
const root =
|
|
32594
|
+
const root = path3.resolve(rootDir);
|
|
31945
32595
|
const dirs = /* @__PURE__ */ new Set();
|
|
31946
32596
|
for (const dir of activeAttachmentDirs) {
|
|
31947
|
-
if (dir === root || dir.startsWith(`${root}${
|
|
32597
|
+
if (dir === root || dir.startsWith(`${root}${path3.sep}`)) {
|
|
31948
32598
|
dirs.add(dir);
|
|
31949
32599
|
}
|
|
31950
32600
|
}
|
|
@@ -32041,9 +32691,9 @@ async function writeResponseToFileWithLimit(res, filePath, maxBytes, rootDir) {
|
|
|
32041
32691
|
}
|
|
32042
32692
|
writtenStat = await file.stat();
|
|
32043
32693
|
await closeFile();
|
|
32044
|
-
await localDirectoryStatInsideRoot(
|
|
32694
|
+
await localDirectoryStatInsideRoot(path3.dirname(filePath), rootDir);
|
|
32045
32695
|
await assertLocalFileIdentity(tmpPath, rootDir, writtenStat);
|
|
32046
|
-
await
|
|
32696
|
+
await fs3.rename(tmpPath, filePath);
|
|
32047
32697
|
completed = true;
|
|
32048
32698
|
return written;
|
|
32049
32699
|
} finally {
|
|
@@ -32059,9 +32709,9 @@ async function writeResponseToFileWithLimit(res, filePath, maxBytes, rootDir) {
|
|
|
32059
32709
|
}
|
|
32060
32710
|
}
|
|
32061
32711
|
function localFileName(attachmentId, fileName, mimeType) {
|
|
32062
|
-
const safeName = sanitizePathSegment(
|
|
32063
|
-
const ext =
|
|
32064
|
-
const stem =
|
|
32712
|
+
const safeName = sanitizePathSegment(path3.basename(fileName || attachmentId));
|
|
32713
|
+
const ext = path3.extname(safeName) || extensionForMime(mimeType);
|
|
32714
|
+
const stem = path3.basename(safeName, path3.extname(safeName)) || attachmentId;
|
|
32065
32715
|
return `${sanitizePathSegment(attachmentId)}-${stem}${ext}`;
|
|
32066
32716
|
}
|
|
32067
32717
|
function extensionForMime(mimeType) {
|
|
@@ -32127,7 +32777,7 @@ function parseContentLength(value) {
|
|
|
32127
32777
|
// dist/gateway.js
|
|
32128
32778
|
import * as crypto2 from "node:crypto";
|
|
32129
32779
|
import * as os2 from "node:os";
|
|
32130
|
-
import * as
|
|
32780
|
+
import * as path7 from "node:path";
|
|
32131
32781
|
|
|
32132
32782
|
// dist/runtime.js
|
|
32133
32783
|
var runtime = null;
|
|
@@ -32185,15 +32835,15 @@ function buildOrchestratorSessionKey(accountId) {
|
|
|
32185
32835
|
}
|
|
32186
32836
|
|
|
32187
32837
|
// dist/config-manager.js
|
|
32188
|
-
import * as
|
|
32189
|
-
import * as
|
|
32838
|
+
import * as fs4 from "node:fs";
|
|
32839
|
+
import * as path4 from "node:path";
|
|
32190
32840
|
var CACHE_FILENAME = "parall-platform-config.json";
|
|
32191
32841
|
function cachePath(stateDir) {
|
|
32192
|
-
return
|
|
32842
|
+
return path4.join(stateDir, CACHE_FILENAME);
|
|
32193
32843
|
}
|
|
32194
32844
|
function loadCachedConfig(stateDir) {
|
|
32195
32845
|
try {
|
|
32196
|
-
const raw =
|
|
32846
|
+
const raw = fs4.readFileSync(cachePath(stateDir), "utf-8");
|
|
32197
32847
|
return JSON.parse(raw);
|
|
32198
32848
|
} catch {
|
|
32199
32849
|
return null;
|
|
@@ -32207,14 +32857,14 @@ function saveCachedConfig(stateDir, config) {
|
|
|
32207
32857
|
};
|
|
32208
32858
|
const filePath = cachePath(stateDir);
|
|
32209
32859
|
const tmpPath = `${filePath}.tmp`;
|
|
32210
|
-
|
|
32211
|
-
|
|
32212
|
-
|
|
32860
|
+
fs4.mkdirSync(path4.dirname(filePath), { recursive: true });
|
|
32861
|
+
fs4.writeFileSync(tmpPath, JSON.stringify(cached, null, 2), "utf-8");
|
|
32862
|
+
fs4.renameSync(tmpPath, filePath);
|
|
32213
32863
|
}
|
|
32214
32864
|
function applyToOpenClawConfig(configPath, platformConfig, credentials) {
|
|
32215
32865
|
let existing = {};
|
|
32216
32866
|
try {
|
|
32217
|
-
const raw =
|
|
32867
|
+
const raw = fs4.readFileSync(configPath, "utf-8");
|
|
32218
32868
|
existing = JSON.parse(raw);
|
|
32219
32869
|
} catch {
|
|
32220
32870
|
}
|
|
@@ -32286,9 +32936,9 @@ function applyToOpenClawConfig(configPath, platformConfig, credentials) {
|
|
|
32286
32936
|
agents.defaults = cleanedExisting;
|
|
32287
32937
|
existing.agents = agents;
|
|
32288
32938
|
const tmpPath = `${configPath}.tmp`;
|
|
32289
|
-
|
|
32290
|
-
|
|
32291
|
-
|
|
32939
|
+
fs4.mkdirSync(path4.dirname(configPath), { recursive: true });
|
|
32940
|
+
fs4.writeFileSync(tmpPath, JSON.stringify(existing, null, 2), "utf-8");
|
|
32941
|
+
fs4.renameSync(tmpPath, configPath);
|
|
32292
32942
|
}
|
|
32293
32943
|
async function fetchAndApplyPlatformConfig(opts) {
|
|
32294
32944
|
const { client, stateDir, configPath, credentials, log } = opts;
|
|
@@ -32327,7 +32977,7 @@ async function fetchAndApplyPlatformConfig(opts) {
|
|
|
32327
32977
|
|
|
32328
32978
|
// dist/wiki-helper.js
|
|
32329
32979
|
import { spawn, spawnSync } from "node:child_process";
|
|
32330
|
-
import
|
|
32980
|
+
import path5 from "node:path";
|
|
32331
32981
|
var DEFAULT_SYNC_TIMEOUT_MS = 9e4;
|
|
32332
32982
|
var DEFAULT_WATCH_INTERVAL_SEC = 30;
|
|
32333
32983
|
function isCommandMissing(error) {
|
|
@@ -32342,7 +32992,7 @@ function resolveParallCli() {
|
|
|
32342
32992
|
return _cli;
|
|
32343
32993
|
}
|
|
32344
32994
|
function resolveMountRoot(stateDir) {
|
|
32345
|
-
return process.env.PRLL_WIKI_MOUNT_ROOT?.trim() ||
|
|
32995
|
+
return process.env.PRLL_WIKI_MOUNT_ROOT?.trim() || path5.join(stateDir, "workspace");
|
|
32346
32996
|
}
|
|
32347
32997
|
function resolveWatchIntervalSec() {
|
|
32348
32998
|
const raw = process.env.PRLL_WIKI_REFRESH_INTERVAL_SEC?.trim();
|
|
@@ -32458,7 +33108,7 @@ async function startWikiHelper(params) {
|
|
|
32458
33108
|
// dist/oc-session.js
|
|
32459
33109
|
import { randomUUID } from "node:crypto";
|
|
32460
33110
|
import { existsSync as existsSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
32461
|
-
import { join as
|
|
33111
|
+
import { join as join4, resolve as resolve2 } from "node:path";
|
|
32462
33112
|
var CURRENT_SESSION_VERSION = 3;
|
|
32463
33113
|
function generateId(existing) {
|
|
32464
33114
|
for (let i = 0; i < 100; i++) {
|
|
@@ -32604,7 +33254,7 @@ var SessionManager = class _SessionManager {
|
|
|
32604
33254
|
this.leafId = null;
|
|
32605
33255
|
this.flushed = false;
|
|
32606
33256
|
const ts = timestamp.replace(/[:.]/g, "-");
|
|
32607
|
-
this.sessionFile =
|
|
33257
|
+
this.sessionFile = join4(this.sessionDir, `${ts}_${this.sessionId}.jsonl`);
|
|
32608
33258
|
}
|
|
32609
33259
|
buildIndex() {
|
|
32610
33260
|
this.byId.clear();
|
|
@@ -32651,14 +33301,14 @@ var SessionManager = class _SessionManager {
|
|
|
32651
33301
|
}
|
|
32652
33302
|
// -- Branching -------------------------------------------------------------
|
|
32653
33303
|
getBranch(fromId) {
|
|
32654
|
-
const
|
|
33304
|
+
const path8 = [];
|
|
32655
33305
|
const startId = fromId ?? this.leafId;
|
|
32656
33306
|
let current = startId ? this.byId.get(startId) : void 0;
|
|
32657
33307
|
while (current) {
|
|
32658
|
-
|
|
33308
|
+
path8.unshift(current);
|
|
32659
33309
|
current = current.parentId ? this.byId.get(current.parentId) : void 0;
|
|
32660
33310
|
}
|
|
32661
|
-
return
|
|
33311
|
+
return path8;
|
|
32662
33312
|
}
|
|
32663
33313
|
createBranchedSession(leafId) {
|
|
32664
33314
|
const branch = this.getBranch(leafId);
|
|
@@ -32668,7 +33318,7 @@ var SessionManager = class _SessionManager {
|
|
|
32668
33318
|
const newId = randomUUID();
|
|
32669
33319
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
32670
33320
|
const ts = timestamp.replace(/[:.]/g, "-");
|
|
32671
|
-
const newFile =
|
|
33321
|
+
const newFile = join4(this.sessionDir, `${ts}_${newId}.jsonl`);
|
|
32672
33322
|
const header = {
|
|
32673
33323
|
type: "session",
|
|
32674
33324
|
version: CURRENT_SESSION_VERSION,
|
|
@@ -32714,50 +33364,50 @@ var SessionManager = class _SessionManager {
|
|
|
32714
33364
|
return newFile;
|
|
32715
33365
|
}
|
|
32716
33366
|
// -- Factory ---------------------------------------------------------------
|
|
32717
|
-
static open(
|
|
32718
|
-
const entries = loadEntries(
|
|
33367
|
+
static open(path8) {
|
|
33368
|
+
const entries = loadEntries(path8);
|
|
32719
33369
|
const header = entries.find((e) => e.type === "session");
|
|
32720
33370
|
const cwd = header?.cwd ?? process.cwd();
|
|
32721
|
-
const dir = resolve2(
|
|
32722
|
-
return new _SessionManager(cwd, dir,
|
|
33371
|
+
const dir = resolve2(path8, "..");
|
|
33372
|
+
return new _SessionManager(cwd, dir, path8);
|
|
32723
33373
|
}
|
|
32724
33374
|
};
|
|
32725
33375
|
|
|
32726
33376
|
// dist/fork.js
|
|
32727
|
-
import * as
|
|
32728
|
-
import * as
|
|
33377
|
+
import * as fs5 from "node:fs";
|
|
33378
|
+
import * as path6 from "node:path";
|
|
32729
33379
|
import * as crypto from "node:crypto";
|
|
32730
33380
|
function readStoreEntry(sessionsDir, sessionKey) {
|
|
32731
|
-
const storeFile =
|
|
33381
|
+
const storeFile = path6.join(sessionsDir, "sessions.json");
|
|
32732
33382
|
try {
|
|
32733
|
-
const store = JSON.parse(
|
|
33383
|
+
const store = JSON.parse(fs5.readFileSync(storeFile, "utf-8"));
|
|
32734
33384
|
return store[sessionKey] ?? store[sessionKey.toLowerCase()] ?? null;
|
|
32735
33385
|
} catch {
|
|
32736
33386
|
return null;
|
|
32737
33387
|
}
|
|
32738
33388
|
}
|
|
32739
33389
|
function writeStoreEntry(sessionsDir, sessionKey, entry) {
|
|
32740
|
-
const storeFile =
|
|
33390
|
+
const storeFile = path6.join(sessionsDir, "sessions.json");
|
|
32741
33391
|
try {
|
|
32742
33392
|
let store = {};
|
|
32743
33393
|
try {
|
|
32744
|
-
store = JSON.parse(
|
|
33394
|
+
store = JSON.parse(fs5.readFileSync(storeFile, "utf-8"));
|
|
32745
33395
|
} catch {
|
|
32746
33396
|
}
|
|
32747
33397
|
store[sessionKey.toLowerCase()] = entry;
|
|
32748
|
-
|
|
33398
|
+
fs5.writeFileSync(storeFile, JSON.stringify(store, null, 2), { encoding: "utf-8" });
|
|
32749
33399
|
return true;
|
|
32750
33400
|
} catch {
|
|
32751
33401
|
return false;
|
|
32752
33402
|
}
|
|
32753
33403
|
}
|
|
32754
33404
|
function deleteStoreEntry(sessionsDir, sessionKey) {
|
|
32755
|
-
const storeFile =
|
|
33405
|
+
const storeFile = path6.join(sessionsDir, "sessions.json");
|
|
32756
33406
|
try {
|
|
32757
|
-
const store = JSON.parse(
|
|
33407
|
+
const store = JSON.parse(fs5.readFileSync(storeFile, "utf-8"));
|
|
32758
33408
|
delete store[sessionKey];
|
|
32759
33409
|
delete store[sessionKey.toLowerCase()];
|
|
32760
|
-
|
|
33410
|
+
fs5.writeFileSync(storeFile, JSON.stringify(store, null, 2), { encoding: "utf-8" });
|
|
32761
33411
|
} catch {
|
|
32762
33412
|
}
|
|
32763
33413
|
}
|
|
@@ -32769,17 +33419,17 @@ function resolveTranscriptFile(sessionsDir, sessionKey) {
|
|
|
32769
33419
|
if (!entry?.sessionId)
|
|
32770
33420
|
return null;
|
|
32771
33421
|
if (entry.sessionFile) {
|
|
32772
|
-
const resolved =
|
|
32773
|
-
if (
|
|
33422
|
+
const resolved = path6.isAbsolute(entry.sessionFile) ? entry.sessionFile : path6.join(sessionsDir, entry.sessionFile);
|
|
33423
|
+
if (fs5.existsSync(resolved))
|
|
32774
33424
|
return resolved;
|
|
32775
33425
|
}
|
|
32776
|
-
const conventional =
|
|
32777
|
-
if (
|
|
33426
|
+
const conventional = path6.join(sessionsDir, `${entry.sessionId}.jsonl`);
|
|
33427
|
+
if (fs5.existsSync(conventional))
|
|
32778
33428
|
return conventional;
|
|
32779
33429
|
try {
|
|
32780
|
-
const files =
|
|
33430
|
+
const files = fs5.readdirSync(sessionsDir);
|
|
32781
33431
|
const match = files.find((file) => file.includes(entry.sessionId) && file.endsWith(".jsonl"));
|
|
32782
|
-
return match ?
|
|
33432
|
+
return match ? path6.join(sessionsDir, match) : null;
|
|
32783
33433
|
} catch {
|
|
32784
33434
|
return null;
|
|
32785
33435
|
}
|
|
@@ -32790,7 +33440,7 @@ function resolveSessionId(sessionsDir, sessionKey) {
|
|
|
32790
33440
|
}
|
|
32791
33441
|
function forkOrchestratorSession(opts) {
|
|
32792
33442
|
const { orchestratorSessionKey, accountId, transcriptFile, sessionsDir } = opts;
|
|
32793
|
-
if (!
|
|
33443
|
+
if (!fs5.existsSync(transcriptFile))
|
|
32794
33444
|
return null;
|
|
32795
33445
|
try {
|
|
32796
33446
|
const manager = SessionManager.open(transcriptFile);
|
|
@@ -32810,7 +33460,7 @@ function forkOrchestratorSession(opts) {
|
|
|
32810
33460
|
sessionId = crypto.randomUUID();
|
|
32811
33461
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
32812
33462
|
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
32813
|
-
sessionFile =
|
|
33463
|
+
sessionFile = path6.join(manager.getSessionDir(), `${fileTimestamp}_${sessionId}.jsonl`);
|
|
32814
33464
|
const header = {
|
|
32815
33465
|
type: "session",
|
|
32816
33466
|
version: CURRENT_SESSION_VERSION,
|
|
@@ -32819,7 +33469,7 @@ function forkOrchestratorSession(opts) {
|
|
|
32819
33469
|
cwd: manager.getCwd(),
|
|
32820
33470
|
parentSession: transcriptFile
|
|
32821
33471
|
};
|
|
32822
|
-
|
|
33472
|
+
fs5.writeFileSync(sessionFile, `${JSON.stringify(header)}
|
|
32823
33473
|
`, {
|
|
32824
33474
|
encoding: "utf-8",
|
|
32825
33475
|
mode: 384,
|
|
@@ -32829,7 +33479,7 @@ function forkOrchestratorSession(opts) {
|
|
|
32829
33479
|
const forkSessionKey = `${orchestratorSessionKey}:fork:${sessionId}`;
|
|
32830
33480
|
const wrote = writeStoreEntry(sessionsDir, forkSessionKey, {
|
|
32831
33481
|
sessionId,
|
|
32832
|
-
sessionFile:
|
|
33482
|
+
sessionFile: path6.relative(sessionsDir, sessionFile),
|
|
32833
33483
|
updatedAt: Date.now(),
|
|
32834
33484
|
spawnedBy: orchestratorSessionKey,
|
|
32835
33485
|
parentSessionKey: orchestratorSessionKey,
|
|
@@ -32844,8 +33494,8 @@ function forkOrchestratorSession(opts) {
|
|
|
32844
33494
|
}
|
|
32845
33495
|
function cleanupForkSession(opts) {
|
|
32846
33496
|
try {
|
|
32847
|
-
if (
|
|
32848
|
-
|
|
33497
|
+
if (fs5.existsSync(opts.sessionFile)) {
|
|
33498
|
+
fs5.unlinkSync(opts.sessionFile);
|
|
32849
33499
|
}
|
|
32850
33500
|
} catch {
|
|
32851
33501
|
}
|
|
@@ -33155,8 +33805,8 @@ var parallGateway = {
|
|
|
33155
33805
|
const telemetry = await initAgentTelemetry("parall-openclaw-agent", "openclaw");
|
|
33156
33806
|
const otelLog = createOtelLogger("agent", "openclaw-agent");
|
|
33157
33807
|
try {
|
|
33158
|
-
const stateDir = process.env.OPENCLAW_STATE_DIR ||
|
|
33159
|
-
const openclawConfigPath =
|
|
33808
|
+
const stateDir = process.env.OPENCLAW_STATE_DIR || path7.join(process.env.HOME || "/data", ".openclaw");
|
|
33809
|
+
const openclawConfigPath = path7.join(stateDir, "openclaw.json");
|
|
33160
33810
|
const configManagerOpts = {
|
|
33161
33811
|
client,
|
|
33162
33812
|
stateDir,
|
|
@@ -33192,7 +33842,7 @@ var parallGateway = {
|
|
|
33192
33842
|
wsUrl
|
|
33193
33843
|
});
|
|
33194
33844
|
const orchestratorKey = buildOrchestratorSessionKey(ctx.accountId);
|
|
33195
|
-
const sessionsDir =
|
|
33845
|
+
const sessionsDir = path7.join(stateDir, "agents", "main", "sessions");
|
|
33196
33846
|
const workspaceDir = process.cwd();
|
|
33197
33847
|
ensureLocalAttachmentGitExclude(workspaceDir);
|
|
33198
33848
|
const dispatchAdapter = createOpenClawDispatchAdapter({
|