@parall/parall 1.37.0 → 1.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.bundle.mjs +856 -232
- 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`,
|
|
@@ -27417,6 +27432,7 @@ var WS_EVENTS = {
|
|
|
27417
27432
|
READ_POSITION_UPDATED: "read_position.updated",
|
|
27418
27433
|
DISPATCH_NEW: "dispatch.new",
|
|
27419
27434
|
DISPATCH_RECEIVED: "dispatch.received",
|
|
27435
|
+
DISPATCH_RESOLVED: "dispatch.resolved",
|
|
27420
27436
|
SCHEDULE_CREATED: "schedule.created",
|
|
27421
27437
|
SCHEDULE_UPDATED: "schedule.updated",
|
|
27422
27438
|
SCHEDULE_DELETED: "schedule.deleted",
|
|
@@ -27512,8 +27528,8 @@ var ParallClient = class _ParallClient {
|
|
|
27512
27528
|
* is authoritative, so wiki vs api routing can't drift from how a caller
|
|
27513
27529
|
* happens to invoke the client.
|
|
27514
27530
|
*/
|
|
27515
|
-
baseUrlFor(
|
|
27516
|
-
return
|
|
27531
|
+
baseUrlFor(path8) {
|
|
27532
|
+
return path8.startsWith(WIKI_BASE) ? this.wikiBaseUrl : this.baseUrl;
|
|
27517
27533
|
}
|
|
27518
27534
|
setToken(token) {
|
|
27519
27535
|
this.token = token;
|
|
@@ -27540,10 +27556,10 @@ var ParallClient = class _ParallClient {
|
|
|
27540
27556
|
* REFRESH_THRESHOLD_S, refresh it **before** sending the request.
|
|
27541
27557
|
* No-op when the token is still fresh, missing, or un-parseable.
|
|
27542
27558
|
*/
|
|
27543
|
-
async ensureFreshToken(
|
|
27559
|
+
async ensureFreshToken(path8) {
|
|
27544
27560
|
if (!this.token || !this.getRefreshToken)
|
|
27545
27561
|
return;
|
|
27546
|
-
const pathSuffix =
|
|
27562
|
+
const pathSuffix = path8.replace(/^\/api\/v1/, "");
|
|
27547
27563
|
if (_ParallClient.AUTH_PATHS.has(pathSuffix))
|
|
27548
27564
|
return;
|
|
27549
27565
|
const exp = _ParallClient.decodeJwtExp(this.token);
|
|
@@ -27575,11 +27591,11 @@ var ParallClient = class _ParallClient {
|
|
|
27575
27591
|
this.refreshPromise = null;
|
|
27576
27592
|
}
|
|
27577
27593
|
}
|
|
27578
|
-
async request(method,
|
|
27594
|
+
async request(method, path8, body, query, retried = false, opts) {
|
|
27579
27595
|
if (!retried) {
|
|
27580
|
-
await this.ensureFreshToken(
|
|
27596
|
+
await this.ensureFreshToken(path8);
|
|
27581
27597
|
}
|
|
27582
|
-
let url = `${this.baseUrlFor(
|
|
27598
|
+
let url = `${this.baseUrlFor(path8)}${path8}`;
|
|
27583
27599
|
if (query) {
|
|
27584
27600
|
const params = new URLSearchParams();
|
|
27585
27601
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -27609,12 +27625,12 @@ var ParallClient = class _ParallClient {
|
|
|
27609
27625
|
throw _ParallClient.normalizeFetchError(err);
|
|
27610
27626
|
}
|
|
27611
27627
|
if (res.status === 401) {
|
|
27612
|
-
const pathSuffix =
|
|
27628
|
+
const pathSuffix = path8.replace(/^\/api\/v1/, "");
|
|
27613
27629
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
27614
27630
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
27615
27631
|
const refreshed = await this.tryRefresh();
|
|
27616
27632
|
if (refreshed) {
|
|
27617
|
-
return this.request(method,
|
|
27633
|
+
return this.request(method, path8, body, query, true, opts);
|
|
27618
27634
|
}
|
|
27619
27635
|
}
|
|
27620
27636
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -27625,6 +27641,7 @@ var ParallClient = class _ParallClient {
|
|
|
27625
27641
|
const rawErrorBody = await res.json().catch(() => ({}));
|
|
27626
27642
|
throw buildApiError(res, rawErrorBody);
|
|
27627
27643
|
}
|
|
27644
|
+
opts?.onStatus?.(res.status);
|
|
27628
27645
|
if (res.status === 204)
|
|
27629
27646
|
return void 0;
|
|
27630
27647
|
if (res.status === 202) {
|
|
@@ -27643,15 +27660,15 @@ var ParallClient = class _ParallClient {
|
|
|
27643
27660
|
* hit the 100 MiB cap, so a longer 5-minute timeout is used so a
|
|
27644
27661
|
* 50 MiB blob on a slow connection doesn't get chopped at 15 s.
|
|
27645
27662
|
*/
|
|
27646
|
-
async multipartRequest(method,
|
|
27663
|
+
async multipartRequest(method, path8, body, retried = false) {
|
|
27647
27664
|
if (!retried) {
|
|
27648
|
-
await this.ensureFreshToken(
|
|
27665
|
+
await this.ensureFreshToken(path8);
|
|
27649
27666
|
}
|
|
27650
27667
|
const { "Content-Type": _drop, ...headers } = this.buildHeaders();
|
|
27651
27668
|
void _drop;
|
|
27652
27669
|
let res;
|
|
27653
27670
|
try {
|
|
27654
|
-
res = await fetch(`${this.baseUrlFor(
|
|
27671
|
+
res = await fetch(`${this.baseUrlFor(path8)}${path8}`, {
|
|
27655
27672
|
method,
|
|
27656
27673
|
headers,
|
|
27657
27674
|
body,
|
|
@@ -27661,12 +27678,12 @@ var ParallClient = class _ParallClient {
|
|
|
27661
27678
|
throw _ParallClient.normalizeFetchError(err);
|
|
27662
27679
|
}
|
|
27663
27680
|
if (res.status === 401) {
|
|
27664
|
-
const pathSuffix =
|
|
27681
|
+
const pathSuffix = path8.replace(/^\/api\/v1/, "");
|
|
27665
27682
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
27666
27683
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
27667
27684
|
const refreshed = await this.tryRefresh();
|
|
27668
27685
|
if (refreshed) {
|
|
27669
|
-
return this.multipartRequest(method,
|
|
27686
|
+
return this.multipartRequest(method, path8, body, true);
|
|
27670
27687
|
}
|
|
27671
27688
|
}
|
|
27672
27689
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -27958,6 +27975,21 @@ var ParallClient = class _ParallClient {
|
|
|
27958
27975
|
async sendMessage(orgId, chatId, req) {
|
|
27959
27976
|
return this.request("POST", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), req);
|
|
27960
27977
|
}
|
|
27978
|
+
/**
|
|
27979
|
+
* sendMessage variant that also reports whether the server answered with an
|
|
27980
|
+
* idempotent replay (HTTP 200 — the message already existed for this
|
|
27981
|
+
* idempotency/effect key) instead of a fresh create (201). Used by the CLI
|
|
27982
|
+
* to surface "already sent by a previous run (deduplicated)".
|
|
27983
|
+
*/
|
|
27984
|
+
async sendMessageDetailed(orgId, chatId, req) {
|
|
27985
|
+
let status = 0;
|
|
27986
|
+
const message = await this.request("POST", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), req, void 0, false, {
|
|
27987
|
+
onStatus: (s) => {
|
|
27988
|
+
status = s;
|
|
27989
|
+
}
|
|
27990
|
+
});
|
|
27991
|
+
return { message, deduplicated: status === 200 };
|
|
27992
|
+
}
|
|
27961
27993
|
async getMessages(orgId, chatId, params) {
|
|
27962
27994
|
return this.request("GET", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), void 0, params);
|
|
27963
27995
|
}
|
|
@@ -28345,8 +28377,8 @@ var ParallClient = class _ParallClient {
|
|
|
28345
28377
|
async requestMachineUpdate(orgId, machineId, mandatory = false) {
|
|
28346
28378
|
await this.request("POST", ENDPOINTS.MACHINE_REQUEST_UPDATE(orgId, machineId), { mandatory });
|
|
28347
28379
|
}
|
|
28348
|
-
async browseMachineFilesystem(orgId, machineId,
|
|
28349
|
-
return this.request("POST", ENDPOINTS.MACHINE_BROWSE(orgId, machineId), { path:
|
|
28380
|
+
async browseMachineFilesystem(orgId, machineId, path8) {
|
|
28381
|
+
return this.request("POST", ENDPOINTS.MACHINE_BROWSE(orgId, machineId), { path: path8 }, void 0, false, { timeoutMs: 15e3 });
|
|
28350
28382
|
}
|
|
28351
28383
|
/** Create a new machine key. Returns the raw key string (shown once) + metadata. */
|
|
28352
28384
|
async createMachineKey(orgId, machineId, name) {
|
|
@@ -28419,6 +28451,30 @@ var ParallClient = class _ParallClient {
|
|
|
28419
28451
|
const params = maxAgeHours !== void 0 ? { max_age_hours: String(maxAgeHours) } : void 0;
|
|
28420
28452
|
return this.request("POST", ENDPOINTS.DISPATCH_EXPIRE(orgId), void 0, params);
|
|
28421
28453
|
}
|
|
28454
|
+
/**
|
|
28455
|
+
* Claim a dispatch lane (explicit consume endpoint). Occupies the
|
|
28456
|
+
* (agent, target, thread) lane and folds claimable WorkItems into it;
|
|
28457
|
+
* `claimed: false` means a healthy incumbent holds the lane.
|
|
28458
|
+
*/
|
|
28459
|
+
async claimDispatch(orgId, req) {
|
|
28460
|
+
return this.request("POST", ENDPOINTS.DISPATCH_CLAIM(orgId), req);
|
|
28461
|
+
}
|
|
28462
|
+
/** Fold a pending same-target WorkItem into a live lane (409 STALE_LANE when dethroned). */
|
|
28463
|
+
async steerDispatch(orgId, req) {
|
|
28464
|
+
return this.request("POST", ENDPOINTS.DISPATCH_STEER(orgId), req);
|
|
28465
|
+
}
|
|
28466
|
+
/** End a turn: no_action sweep of the lane's members + lane release + re-drive check. */
|
|
28467
|
+
async completeDispatch(orgId, req) {
|
|
28468
|
+
return this.request("POST", ENDPOINTS.DISPATCH_COMPLETE(orgId), req);
|
|
28469
|
+
}
|
|
28470
|
+
/** Release a lane on graceful shutdown — members return to pending immediately. */
|
|
28471
|
+
async releaseDispatchLane(orgId, lane) {
|
|
28472
|
+
return this.request("POST", ENDPOINTS.DISPATCH_RELEASE(orgId), { lane });
|
|
28473
|
+
}
|
|
28474
|
+
/** Renew a live lane's lease (long-turn keepalive). 409 STALE_LANE when dethroned. */
|
|
28475
|
+
async heartbeatDispatchLane(orgId, req) {
|
|
28476
|
+
return this.request("POST", ENDPOINTS.DISPATCH_HEARTBEAT(orgId), req);
|
|
28477
|
+
}
|
|
28422
28478
|
async getDispatchByMessages(orgId, chatId, messageIds) {
|
|
28423
28479
|
const params = { chat_id: chatId, message_ids: messageIds.join(",") };
|
|
28424
28480
|
return this.request("GET", ENDPOINTS.DISPATCH_BY_MESSAGES(orgId), void 0, params);
|
|
@@ -28883,8 +28939,8 @@ var ParallClient = class _ParallClient {
|
|
|
28883
28939
|
async deleteWikiRestriction(orgId, wikiId, restrictionId) {
|
|
28884
28940
|
await this.request("DELETE", ENDPOINTS.WIKI_RESTRICTION(orgId, wikiId, restrictionId));
|
|
28885
28941
|
}
|
|
28886
|
-
async getWikiAccessStatus(orgId, wikiId,
|
|
28887
|
-
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0,
|
|
28942
|
+
async getWikiAccessStatus(orgId, wikiId, path8) {
|
|
28943
|
+
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0, path8 ? { path: path8 } : void 0);
|
|
28888
28944
|
}
|
|
28889
28945
|
async createWikiAccessRequest(orgId, wikiId, data) {
|
|
28890
28946
|
await this.request("POST", ENDPOINTS.WIKI_ACCESS_REQUESTS(orgId, wikiId), data);
|
|
@@ -28893,14 +28949,14 @@ var ParallClient = class _ParallClient {
|
|
|
28893
28949
|
async getWikiCommits(orgId, wikiId, params) {
|
|
28894
28950
|
return this.request("GET", ENDPOINTS.WIKI_COMMITS(orgId, wikiId), void 0, params);
|
|
28895
28951
|
}
|
|
28896
|
-
async getWikiFileCommits(orgId, wikiId,
|
|
28952
|
+
async getWikiFileCommits(orgId, wikiId, path8, params) {
|
|
28897
28953
|
return this.request("GET", ENDPOINTS.WIKI_FILE_COMMITS(orgId, wikiId), void 0, {
|
|
28898
|
-
path:
|
|
28954
|
+
path: path8,
|
|
28899
28955
|
...params
|
|
28900
28956
|
});
|
|
28901
28957
|
}
|
|
28902
|
-
async getWikiBlame(orgId, wikiId,
|
|
28903
|
-
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path:
|
|
28958
|
+
async getWikiBlame(orgId, wikiId, path8, ref) {
|
|
28959
|
+
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path: path8, ref });
|
|
28904
28960
|
}
|
|
28905
28961
|
// ---- Wiki Operations (audit log) ----
|
|
28906
28962
|
async getWikiOperations(orgId, wikiId, params) {
|
|
@@ -29532,6 +29588,404 @@ var ParallWs = class {
|
|
|
29532
29588
|
}
|
|
29533
29589
|
};
|
|
29534
29590
|
|
|
29591
|
+
// ../agent-core/dist/lane-ledger.js
|
|
29592
|
+
import * as fs from "node:fs";
|
|
29593
|
+
var LedgerUnsupportedError = class extends Error {
|
|
29594
|
+
};
|
|
29595
|
+
function isStaleLane(err) {
|
|
29596
|
+
return err instanceof ApiError && err.status === 409 && err.code === "STALE_LANE";
|
|
29597
|
+
}
|
|
29598
|
+
function isEndpointMissing(err) {
|
|
29599
|
+
return err instanceof ApiError && err.status === 404 && !err.code;
|
|
29600
|
+
}
|
|
29601
|
+
var LaneLedger = class {
|
|
29602
|
+
opts;
|
|
29603
|
+
lanes = /* @__PURE__ */ new Map();
|
|
29604
|
+
constructor(opts) {
|
|
29605
|
+
this.opts = opts;
|
|
29606
|
+
}
|
|
29607
|
+
get contextDir() {
|
|
29608
|
+
return this.opts.contextDir;
|
|
29609
|
+
}
|
|
29610
|
+
/** Only chat message events ride the lane ledger; typed events stay on the legacy ack path. */
|
|
29611
|
+
handles(event) {
|
|
29612
|
+
return event.type === "message" && event.targetId.startsWith("cht_");
|
|
29613
|
+
}
|
|
29614
|
+
laneKeyFor(event) {
|
|
29615
|
+
if (event.type !== "message" && event.dispatchEventId) {
|
|
29616
|
+
return laneKeyForTarget(`dsp:${event.dispatchEventId}`);
|
|
29617
|
+
}
|
|
29618
|
+
return laneKeyForTarget(`prll://${event.targetId}`, event.threadRootId);
|
|
29619
|
+
}
|
|
29620
|
+
getForEvent(event) {
|
|
29621
|
+
return this.lanes.get(this.laneKeyFor(event));
|
|
29622
|
+
}
|
|
29623
|
+
laneContextPath(lane) {
|
|
29624
|
+
return laneContextFilePath(this.opts.contextDir, lane.targetUri, lane.threadRootId);
|
|
29625
|
+
}
|
|
29626
|
+
/**
|
|
29627
|
+
* Claim (or reuse) the lane for a group of same-lane message events and
|
|
29628
|
+
* fold every group member into it. Returns 'foreign' when a healthy
|
|
29629
|
+
* incumbent (another pod) holds the resource — the caller must not
|
|
29630
|
+
* dispatch; the events stay pending server-side and re-drive after the
|
|
29631
|
+
* incumbent completes.
|
|
29632
|
+
*/
|
|
29633
|
+
async ensureLane(events) {
|
|
29634
|
+
const trigger = events[events.length - 1];
|
|
29635
|
+
const laneKey = this.laneKeyFor(trigger);
|
|
29636
|
+
let lane = this.lanes.get(laneKey);
|
|
29637
|
+
if (!lane) {
|
|
29638
|
+
const targetUri = `prll://${trigger.targetId}`;
|
|
29639
|
+
let res;
|
|
29640
|
+
try {
|
|
29641
|
+
res = await this.opts.client.claimDispatch(this.opts.orgId, {
|
|
29642
|
+
target_uri: targetUri,
|
|
29643
|
+
thread_root_id: trigger.threadRootId,
|
|
29644
|
+
limit: 100
|
|
29645
|
+
});
|
|
29646
|
+
} catch (err) {
|
|
29647
|
+
if (isEndpointMissing(err))
|
|
29648
|
+
throw new LedgerUnsupportedError("claim endpoint unavailable");
|
|
29649
|
+
throw err;
|
|
29650
|
+
}
|
|
29651
|
+
if (!res.claimed || !res.lane) {
|
|
29652
|
+
this.opts.log?.info(`lane for ${targetUri} held by a healthy incumbent \u2014 leaving events pending for re-drive`);
|
|
29653
|
+
return null;
|
|
29654
|
+
}
|
|
29655
|
+
const leaseUntilMs = Date.parse(res.lease_until ?? "");
|
|
29656
|
+
lane = {
|
|
29657
|
+
laneKey,
|
|
29658
|
+
lane: res.lane,
|
|
29659
|
+
targetUri,
|
|
29660
|
+
threadRootId: trigger.threadRootId,
|
|
29661
|
+
folded: /* @__PURE__ */ new Map(),
|
|
29662
|
+
...Number.isNaN(leaseUntilMs) ? {} : { leaseUntilMs, leaseTtlMs: Math.max(leaseUntilMs - Date.now(), 6e4) }
|
|
29663
|
+
};
|
|
29664
|
+
for (const ev of res.events ?? []) {
|
|
29665
|
+
lane.folded.set(ev.source_id, ev.id);
|
|
29666
|
+
}
|
|
29667
|
+
this.lanes.set(laneKey, lane);
|
|
29668
|
+
}
|
|
29669
|
+
for (const ev of events) {
|
|
29670
|
+
if (lane.folded.has(ev.messageId))
|
|
29671
|
+
continue;
|
|
29672
|
+
try {
|
|
29673
|
+
const res = await this.opts.client.steerDispatch(this.opts.orgId, {
|
|
29674
|
+
lane: lane.lane,
|
|
29675
|
+
target_uri: lane.targetUri,
|
|
29676
|
+
thread_root_id: lane.threadRootId,
|
|
29677
|
+
...ev.dispatchEventId ? { dispatch_event_id: ev.dispatchEventId } : { source_type: "message", source_id: ev.messageId }
|
|
29678
|
+
});
|
|
29679
|
+
lane.folded.set(ev.messageId, res.dispatch_event_id);
|
|
29680
|
+
} catch (err) {
|
|
29681
|
+
if (isStaleLane(err)) {
|
|
29682
|
+
this.lanes.delete(laneKey);
|
|
29683
|
+
return null;
|
|
29684
|
+
}
|
|
29685
|
+
this.opts.log?.warn(`steer fold failed for ${ev.messageId} \u2014 failing closed, releasing lane: ${String(err)}`);
|
|
29686
|
+
await this.release(laneKey);
|
|
29687
|
+
return null;
|
|
29688
|
+
}
|
|
29689
|
+
}
|
|
29690
|
+
return lane;
|
|
29691
|
+
}
|
|
29692
|
+
/**
|
|
29693
|
+
* Fold a live mid-turn message into its active lane BEFORE injecting it
|
|
29694
|
+
* into the running turn. Injection without a successful fold is forbidden —
|
|
29695
|
+
* an un-folded injected message would be re-driven after complete and the
|
|
29696
|
+
* model would handle it twice.
|
|
29697
|
+
*/
|
|
29698
|
+
async steerLive(event) {
|
|
29699
|
+
const laneKey = this.laneKeyFor(event);
|
|
29700
|
+
const lane = this.lanes.get(laneKey);
|
|
29701
|
+
if (!lane)
|
|
29702
|
+
return false;
|
|
29703
|
+
if (lane.folded.has(event.messageId))
|
|
29704
|
+
return true;
|
|
29705
|
+
try {
|
|
29706
|
+
const res = await this.opts.client.steerDispatch(this.opts.orgId, {
|
|
29707
|
+
lane: lane.lane,
|
|
29708
|
+
target_uri: lane.targetUri,
|
|
29709
|
+
thread_root_id: lane.threadRootId,
|
|
29710
|
+
...event.dispatchEventId ? { dispatch_event_id: event.dispatchEventId } : { source_type: "message", source_id: event.messageId }
|
|
29711
|
+
});
|
|
29712
|
+
lane.folded.set(event.messageId, res.dispatch_event_id);
|
|
29713
|
+
return true;
|
|
29714
|
+
} catch (err) {
|
|
29715
|
+
if (isStaleLane(err)) {
|
|
29716
|
+
this.lanes.delete(laneKey);
|
|
29717
|
+
} else {
|
|
29718
|
+
this.opts.log?.warn(`live steer failed for ${event.messageId}: ${String(err)}`);
|
|
29719
|
+
}
|
|
29720
|
+
return false;
|
|
29721
|
+
}
|
|
29722
|
+
}
|
|
29723
|
+
/**
|
|
29724
|
+
* Complete the lane when no local work remains for it: the server sweeps
|
|
29725
|
+
* still-leased members as no_action, releases the occupancy row, and
|
|
29726
|
+
* re-drives any same-target pending work. A STALE_LANE answer means a
|
|
29727
|
+
* takeover already owns the resource — local state is dropped either way.
|
|
29728
|
+
*/
|
|
29729
|
+
async completeIfIdle(laneKey, hasMoreLocal) {
|
|
29730
|
+
const lane = this.lanes.get(laneKey);
|
|
29731
|
+
if (!lane || hasMoreLocal)
|
|
29732
|
+
return;
|
|
29733
|
+
this.lanes.delete(laneKey);
|
|
29734
|
+
this.removeLaneContext(lane);
|
|
29735
|
+
try {
|
|
29736
|
+
const res = await this.opts.client.completeDispatch(this.opts.orgId, {
|
|
29737
|
+
lane: lane.lane,
|
|
29738
|
+
target_uri: lane.targetUri,
|
|
29739
|
+
thread_root_id: lane.threadRootId
|
|
29740
|
+
});
|
|
29741
|
+
if (res.swept_no_action > 0 || res.redriven) {
|
|
29742
|
+
this.opts.log?.info(`lane complete for ${lane.targetUri}: swept ${res.swept_no_action} no_action, redriven=${res.redriven}`);
|
|
29743
|
+
}
|
|
29744
|
+
} catch (err) {
|
|
29745
|
+
if (isStaleLane(err)) {
|
|
29746
|
+
this.opts.log?.info(`lane complete skipped for ${lane.targetUri} \u2014 taken over`);
|
|
29747
|
+
return;
|
|
29748
|
+
}
|
|
29749
|
+
this.opts.log?.warn(`lane complete failed for ${lane.targetUri}: ${String(err)}`);
|
|
29750
|
+
}
|
|
29751
|
+
}
|
|
29752
|
+
/**
|
|
29753
|
+
* Long-turn keepalive: renew the lane's lease on runtime activity, throttled
|
|
29754
|
+
* so a chatty turn doesn't spam the server. Without this, a legitimately
|
|
29755
|
+
* long turn (> lane TTL) would be dethroned mid-flight and every subsequent
|
|
29756
|
+
* write misfired with STALE_LANE — the design doc's "long turns renew via
|
|
29757
|
+
* step writes". Fire-and-forget: a failed renewal is surfaced by the next
|
|
29758
|
+
* write's incumbency check anyway.
|
|
29759
|
+
*/
|
|
29760
|
+
maybeRenew(lane) {
|
|
29761
|
+
const now = Date.now();
|
|
29762
|
+
const ttl = lane.leaseTtlMs ?? 10 * 6e4;
|
|
29763
|
+
const until = lane.leaseUntilMs ?? now;
|
|
29764
|
+
if (until - now > ttl / 2)
|
|
29765
|
+
return;
|
|
29766
|
+
lane.leaseUntilMs = now + ttl;
|
|
29767
|
+
void this.opts.client.heartbeatDispatchLane(this.opts.orgId, {
|
|
29768
|
+
lane: lane.lane,
|
|
29769
|
+
target_uri: lane.targetUri,
|
|
29770
|
+
thread_root_id: lane.threadRootId
|
|
29771
|
+
}).then((res) => {
|
|
29772
|
+
const until2 = Date.parse(res?.lease_until ?? "");
|
|
29773
|
+
if (!Number.isNaN(until2))
|
|
29774
|
+
lane.leaseUntilMs = until2;
|
|
29775
|
+
}).catch((err) => {
|
|
29776
|
+
if (isStaleLane(err)) {
|
|
29777
|
+
this.lanes.delete(lane.laneKey);
|
|
29778
|
+
this.opts.log?.warn(`lane ${lane.targetUri} was taken over during the turn`);
|
|
29779
|
+
return;
|
|
29780
|
+
}
|
|
29781
|
+
this.opts.log?.warn(`lane heartbeat failed for ${lane.targetUri}: ${String(err)}`);
|
|
29782
|
+
});
|
|
29783
|
+
}
|
|
29784
|
+
/**
|
|
29785
|
+
* Release a lane's unresolved members back to pending (dispatch error /
|
|
29786
|
+
* shutdown) so the next pod re-claims immediately instead of waiting out
|
|
29787
|
+
* the lease.
|
|
29788
|
+
*/
|
|
29789
|
+
async release(laneKey) {
|
|
29790
|
+
const lane = this.lanes.get(laneKey);
|
|
29791
|
+
if (!lane)
|
|
29792
|
+
return;
|
|
29793
|
+
this.lanes.delete(laneKey);
|
|
29794
|
+
this.removeLaneContext(lane);
|
|
29795
|
+
try {
|
|
29796
|
+
await this.opts.client.releaseDispatchLane(this.opts.orgId, lane.lane);
|
|
29797
|
+
} catch (err) {
|
|
29798
|
+
this.opts.log?.warn(`lane release failed for ${lane.targetUri}: ${String(err)}`);
|
|
29799
|
+
}
|
|
29800
|
+
}
|
|
29801
|
+
async releaseAll() {
|
|
29802
|
+
const keys = [...this.lanes.keys()];
|
|
29803
|
+
for (const key of keys) {
|
|
29804
|
+
await this.release(key);
|
|
29805
|
+
}
|
|
29806
|
+
}
|
|
29807
|
+
/** True when any lane is currently active (used by shutdown logging). */
|
|
29808
|
+
get activeCount() {
|
|
29809
|
+
return this.lanes.size;
|
|
29810
|
+
}
|
|
29811
|
+
/**
|
|
29812
|
+
* Claim the single-member lane of one typed WorkItem (resource = dsp:<id>),
|
|
29813
|
+
* by WorkItem id or by source identity (the live task.assigned event has no
|
|
29814
|
+
* WorkItem id). Returns null when a healthy incumbent (another pod) holds
|
|
29815
|
+
* it or the WorkItem is already resolved — the caller must skip processing.
|
|
29816
|
+
*/
|
|
29817
|
+
async claimTyped(ref) {
|
|
29818
|
+
let res;
|
|
29819
|
+
try {
|
|
29820
|
+
res = await this.opts.client.claimDispatch(this.opts.orgId, {
|
|
29821
|
+
dispatch_event_id: ref.dispatchEventId,
|
|
29822
|
+
source_type: ref.dispatchEventId ? void 0 : ref.sourceType,
|
|
29823
|
+
source_id: ref.dispatchEventId ? void 0 : ref.sourceId
|
|
29824
|
+
});
|
|
29825
|
+
} catch (err) {
|
|
29826
|
+
if (isEndpointMissing(err))
|
|
29827
|
+
throw new LedgerUnsupportedError("claim endpoint unavailable");
|
|
29828
|
+
throw err;
|
|
29829
|
+
}
|
|
29830
|
+
if (!res.claimed || !res.lane || !res.events?.length)
|
|
29831
|
+
return null;
|
|
29832
|
+
const workItem = res.events[0];
|
|
29833
|
+
const targetUri = `dsp:${workItem.id}`;
|
|
29834
|
+
const leaseUntilMs = Date.parse(res.lease_until ?? "");
|
|
29835
|
+
const lane = {
|
|
29836
|
+
laneKey: laneKeyForTarget(targetUri),
|
|
29837
|
+
lane: res.lane,
|
|
29838
|
+
targetUri,
|
|
29839
|
+
folded: /* @__PURE__ */ new Map([[workItem.source_id, workItem.id]]),
|
|
29840
|
+
typedDispatchEventId: workItem.id,
|
|
29841
|
+
...Number.isNaN(leaseUntilMs) ? {} : { leaseUntilMs, leaseTtlMs: Math.max(leaseUntilMs - Date.now(), 6e4) }
|
|
29842
|
+
};
|
|
29843
|
+
this.lanes.set(lane.laneKey, lane);
|
|
29844
|
+
return lane;
|
|
29845
|
+
}
|
|
29846
|
+
/**
|
|
29847
|
+
* Remove the per-lane context file (and its CLI sidecar) when the lane
|
|
29848
|
+
* ends. A leftover file would make a later cross-context send to the same
|
|
29849
|
+
* target bind a dead lane token and misfire with STALE_LANE instead of
|
|
29850
|
+
* taking the plain non-ledger path.
|
|
29851
|
+
*/
|
|
29852
|
+
removeLaneContext(lane) {
|
|
29853
|
+
const contextPath = this.laneContextPath(lane);
|
|
29854
|
+
for (const p of [contextPath, contextPath.replace(/\.json$/, ".reply-state.json")]) {
|
|
29855
|
+
try {
|
|
29856
|
+
fs.rmSync(p, { force: true });
|
|
29857
|
+
} catch {
|
|
29858
|
+
}
|
|
29859
|
+
}
|
|
29860
|
+
}
|
|
29861
|
+
};
|
|
29862
|
+
|
|
29863
|
+
// ../agent-core/dist/gateway-lane-flow.js
|
|
29864
|
+
async function dispatchLaneGroup(host, opts) {
|
|
29865
|
+
const ledger = host.laneLedger;
|
|
29866
|
+
const event = opts.events[opts.events.length - 1];
|
|
29867
|
+
let lane;
|
|
29868
|
+
try {
|
|
29869
|
+
lane = await ledger.ensureLane(opts.events);
|
|
29870
|
+
} catch (err) {
|
|
29871
|
+
if (!(err instanceof LedgerUnsupportedError))
|
|
29872
|
+
throw err;
|
|
29873
|
+
host.disableLedger("claim endpoint missing");
|
|
29874
|
+
await host.emitDispatchReceived(event);
|
|
29875
|
+
const dispatched2 = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText);
|
|
29876
|
+
if (!dispatched2)
|
|
29877
|
+
return "shutdown";
|
|
29878
|
+
for (const ev of opts.events) {
|
|
29879
|
+
host.opts.client.ackDispatch(host.opts.config.org_id, {
|
|
29880
|
+
source_type: ev.ackSourceType ?? "message",
|
|
29881
|
+
source_id: ev.ackSourceId ?? ev.messageId
|
|
29882
|
+
}).catch(() => {
|
|
29883
|
+
});
|
|
29884
|
+
}
|
|
29885
|
+
return "dispatched";
|
|
29886
|
+
}
|
|
29887
|
+
if (!lane) {
|
|
29888
|
+
for (const ev of opts.events) {
|
|
29889
|
+
host.dispatchedMessages.delete(ev.messageId);
|
|
29890
|
+
}
|
|
29891
|
+
return "foreign";
|
|
29892
|
+
}
|
|
29893
|
+
let dispatched = false;
|
|
29894
|
+
try {
|
|
29895
|
+
dispatched = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText);
|
|
29896
|
+
} catch (err) {
|
|
29897
|
+
await ledger.release(lane.laneKey).catch(() => {
|
|
29898
|
+
});
|
|
29899
|
+
throw err;
|
|
29900
|
+
}
|
|
29901
|
+
if (!dispatched) {
|
|
29902
|
+
return "shutdown";
|
|
29903
|
+
}
|
|
29904
|
+
const pendingInjections = host.opts.dispatchAdapter.hasPendingInjections?.(opts.sessionKey) ?? false;
|
|
29905
|
+
await ledger.completeIfIdle(lane.laneKey, pendingInjections || opts.hasMoreLocal());
|
|
29906
|
+
return "dispatched";
|
|
29907
|
+
}
|
|
29908
|
+
async function consumeTypedDispatch(host, ref, run, ack) {
|
|
29909
|
+
if (!host.laneLedger || host.ledgerDisabled) {
|
|
29910
|
+
if (await run(ref.dispatchEventId))
|
|
29911
|
+
ack(ref.dispatchEventId);
|
|
29912
|
+
return;
|
|
29913
|
+
}
|
|
29914
|
+
let lane;
|
|
29915
|
+
try {
|
|
29916
|
+
lane = await host.laneLedger.claimTyped(ref);
|
|
29917
|
+
} catch (err) {
|
|
29918
|
+
if (err instanceof LedgerUnsupportedError) {
|
|
29919
|
+
host.disableLedger("claim endpoint missing");
|
|
29920
|
+
if (await run(ref.dispatchEventId))
|
|
29921
|
+
ack(ref.dispatchEventId);
|
|
29922
|
+
return;
|
|
29923
|
+
}
|
|
29924
|
+
throw err;
|
|
29925
|
+
}
|
|
29926
|
+
if (!lane) {
|
|
29927
|
+
host.opts.log?.info(`typed dispatch ${ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`} not claimable (held elsewhere or already resolved) \u2014 skipping`);
|
|
29928
|
+
return;
|
|
29929
|
+
}
|
|
29930
|
+
try {
|
|
29931
|
+
if (await run(lane.typedDispatchEventId))
|
|
29932
|
+
ack(lane.typedDispatchEventId);
|
|
29933
|
+
} finally {
|
|
29934
|
+
await host.laneLedger.completeIfIdle(lane.laneKey, false).catch(() => {
|
|
29935
|
+
});
|
|
29936
|
+
}
|
|
29937
|
+
}
|
|
29938
|
+
async function consumeMessageWorkItem(host, item) {
|
|
29939
|
+
if (host.shuttingDown)
|
|
29940
|
+
return;
|
|
29941
|
+
if (!host.tryClaimMessage(item.source_id))
|
|
29942
|
+
return;
|
|
29943
|
+
const ackItem = () => {
|
|
29944
|
+
host.opts.client.ackDispatchByID(host.opts.config.org_id, item.id).catch(() => {
|
|
29945
|
+
});
|
|
29946
|
+
};
|
|
29947
|
+
let msg = null;
|
|
29948
|
+
try {
|
|
29949
|
+
msg = await host.opts.client.getMessage(item.source_id);
|
|
29950
|
+
} catch (err) {
|
|
29951
|
+
const status = err?.status;
|
|
29952
|
+
if (status !== 404) {
|
|
29953
|
+
host.opts.log?.warn(`message fetch failed for ${item.source_id}, leaving pending: ${String(err)}`);
|
|
29954
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
29955
|
+
return;
|
|
29956
|
+
}
|
|
29957
|
+
}
|
|
29958
|
+
if (!msg || msg.sender_id === host.opts.agentUserId) {
|
|
29959
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
29960
|
+
ackItem();
|
|
29961
|
+
return;
|
|
29962
|
+
}
|
|
29963
|
+
const decision = await host.buildMessageDispatchDecision(item.chat_id, msg);
|
|
29964
|
+
if (decision.action === "retry") {
|
|
29965
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
29966
|
+
return;
|
|
29967
|
+
}
|
|
29968
|
+
if (decision.action === "skip") {
|
|
29969
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
29970
|
+
ackItem();
|
|
29971
|
+
return;
|
|
29972
|
+
}
|
|
29973
|
+
decision.event.dispatchEventId = item.id;
|
|
29974
|
+
const laneResolved = host.usesLaneLedger(decision.event);
|
|
29975
|
+
try {
|
|
29976
|
+
const dispatched = await host.handleInboundEvent(decision.event);
|
|
29977
|
+
if (dispatched) {
|
|
29978
|
+
if (!laneResolved)
|
|
29979
|
+
ackItem();
|
|
29980
|
+
} else {
|
|
29981
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
29982
|
+
}
|
|
29983
|
+
} catch (err) {
|
|
29984
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
29985
|
+
throw err;
|
|
29986
|
+
}
|
|
29987
|
+
}
|
|
29988
|
+
|
|
29535
29989
|
// ../agent-core/dist/telemetry.js
|
|
29536
29990
|
init_esm();
|
|
29537
29991
|
var import_api_logs = __toESM(require_src(), 1);
|
|
@@ -29833,6 +30287,14 @@ var ParallAgentGateway = class {
|
|
|
29833
30287
|
inFlightDispatches = 0;
|
|
29834
30288
|
drainResolvers = [];
|
|
29835
30289
|
pendingRestartNotification = null;
|
|
30290
|
+
laneLedger;
|
|
30291
|
+
// Sticky fallback: flipped when the server predates the ledger (claim
|
|
30292
|
+
// endpoint 404) so every subsequent dispatch uses the legacy flow.
|
|
30293
|
+
ledgerDisabled = false;
|
|
30294
|
+
// Group key of the group currently being dispatched on main — lane-aware
|
|
30295
|
+
// (targetId + thread), unlike mainCurrentTargetId which stays chat-level
|
|
30296
|
+
// for fork routing decisions.
|
|
30297
|
+
mainCurrentGroupKey;
|
|
29836
30298
|
DISPATCHED_MESSAGES_CAP = 5e3;
|
|
29837
30299
|
// SHUTDOWN_DEADLINE_MS is read by waitForDrain via the configured value
|
|
29838
30300
|
// below — kept as instance state so per-runtime configs can override it
|
|
@@ -29842,6 +30304,14 @@ var ParallAgentGateway = class {
|
|
|
29842
30304
|
DISPATCH_DEADLINE_MS;
|
|
29843
30305
|
constructor(opts) {
|
|
29844
30306
|
this.opts = opts;
|
|
30307
|
+
if (opts.dispatchContextDir) {
|
|
30308
|
+
this.laneLedger = new LaneLedger({
|
|
30309
|
+
client: opts.client,
|
|
30310
|
+
orgId: opts.config.org_id,
|
|
30311
|
+
contextDir: opts.dispatchContextDir,
|
|
30312
|
+
log: opts.log
|
|
30313
|
+
});
|
|
30314
|
+
}
|
|
29845
30315
|
this.SHUTDOWN_DEADLINE_MS = opts.shutdownDeadlineMs ?? 6e4;
|
|
29846
30316
|
this.FORK_DEADLINE_MS = opts.forkDeadlineMs ?? 2 * 60 * 6e4;
|
|
29847
30317
|
this.DISPATCH_DEADLINE_MS = opts.dispatchDeadlineMs ?? 20 * 6e4;
|
|
@@ -29911,14 +30381,18 @@ var ParallAgentGateway = class {
|
|
|
29911
30381
|
if (data.status !== "todo" && data.status !== "in_progress")
|
|
29912
30382
|
return;
|
|
29913
30383
|
try {
|
|
29914
|
-
|
|
29915
|
-
|
|
30384
|
+
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) => {
|
|
30385
|
+
if (dispatchEventId) {
|
|
30386
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, dispatchEventId).catch(() => {
|
|
30387
|
+
});
|
|
30388
|
+
return;
|
|
30389
|
+
}
|
|
29916
30390
|
this.opts.client.ackDispatch(this.opts.config.org_id, {
|
|
29917
30391
|
source_type: "task_activity",
|
|
29918
30392
|
source_id: data.id
|
|
29919
30393
|
}).catch(() => {
|
|
29920
30394
|
});
|
|
29921
|
-
}
|
|
30395
|
+
});
|
|
29922
30396
|
} catch (err) {
|
|
29923
30397
|
this.opts.log?.error(`task dispatch failed for ${data.id}: ${String(err)}`);
|
|
29924
30398
|
}
|
|
@@ -29928,11 +30402,10 @@ var ParallAgentGateway = class {
|
|
|
29928
30402
|
if (!data.source_id || !data.task_id)
|
|
29929
30403
|
return;
|
|
29930
30404
|
try {
|
|
29931
|
-
|
|
29932
|
-
if (dispatched) {
|
|
30405
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleTaskComment(data.source_id, data.task_id ?? "", data.actor_id, data.delivery_reason), () => {
|
|
29933
30406
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29934
30407
|
});
|
|
29935
|
-
}
|
|
30408
|
+
});
|
|
29936
30409
|
} catch (err) {
|
|
29937
30410
|
this.opts.log?.error(`task comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29938
30411
|
}
|
|
@@ -29940,11 +30413,10 @@ var ParallAgentGateway = class {
|
|
|
29940
30413
|
if (!data.source_id)
|
|
29941
30414
|
return;
|
|
29942
30415
|
try {
|
|
29943
|
-
|
|
29944
|
-
if (dispatched) {
|
|
30416
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleWikiComment(data.source_id, data.actor_id, data.delivery_reason), () => {
|
|
29945
30417
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29946
30418
|
});
|
|
29947
|
-
}
|
|
30419
|
+
});
|
|
29948
30420
|
} catch (err) {
|
|
29949
30421
|
this.opts.log?.error(`wiki comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29950
30422
|
}
|
|
@@ -29952,11 +30424,13 @@ var ParallAgentGateway = class {
|
|
|
29952
30424
|
if (!data.task_id)
|
|
29953
30425
|
return;
|
|
29954
30426
|
try {
|
|
29955
|
-
|
|
29956
|
-
|
|
30427
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.handleTaskDispatch(data.task_id ?? "", data.source_id ?? data.task_id ?? "", {
|
|
30428
|
+
allowCreator: true,
|
|
30429
|
+
dispatchEventId
|
|
30430
|
+
}), () => {
|
|
29957
30431
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29958
30432
|
});
|
|
29959
|
-
}
|
|
30433
|
+
});
|
|
29960
30434
|
} catch (err) {
|
|
29961
30435
|
this.opts.log?.error(`task update dispatch failed for ${data.task_id}: ${String(err)}`);
|
|
29962
30436
|
}
|
|
@@ -29964,11 +30438,10 @@ var ParallAgentGateway = class {
|
|
|
29964
30438
|
if (!data.source_id)
|
|
29965
30439
|
return;
|
|
29966
30440
|
try {
|
|
29967
|
-
|
|
29968
|
-
if (dispatched) {
|
|
30441
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleScheduleFire(data.source_id, data.actor_id), () => {
|
|
29969
30442
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29970
30443
|
});
|
|
29971
|
-
}
|
|
30444
|
+
});
|
|
29972
30445
|
} catch (err) {
|
|
29973
30446
|
this.opts.log?.error(`schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29974
30447
|
}
|
|
@@ -29976,11 +30449,10 @@ var ParallAgentGateway = class {
|
|
|
29976
30449
|
if (!data.source_id)
|
|
29977
30450
|
return;
|
|
29978
30451
|
try {
|
|
29979
|
-
|
|
29980
|
-
if (dispatched) {
|
|
30452
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleExternalTriggerRun(data.source_id), () => {
|
|
29981
30453
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29982
30454
|
});
|
|
29983
|
-
}
|
|
30455
|
+
});
|
|
29984
30456
|
} catch (err) {
|
|
29985
30457
|
this.opts.log?.error(`external trigger dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29986
30458
|
}
|
|
@@ -29988,11 +30460,10 @@ var ParallAgentGateway = class {
|
|
|
29988
30460
|
if (!data.source_id)
|
|
29989
30461
|
return;
|
|
29990
30462
|
try {
|
|
29991
|
-
|
|
29992
|
-
if (dispatched) {
|
|
30463
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleChannelMessage(data.source_id), () => {
|
|
29993
30464
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29994
30465
|
});
|
|
29995
|
-
}
|
|
30466
|
+
});
|
|
29996
30467
|
} catch (err) {
|
|
29997
30468
|
this.opts.log?.error(`channel message dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29998
30469
|
}
|
|
@@ -30000,14 +30471,19 @@ var ParallAgentGateway = class {
|
|
|
30000
30471
|
if (!data.source_id)
|
|
30001
30472
|
return;
|
|
30002
30473
|
try {
|
|
30003
|
-
|
|
30004
|
-
if (dispatched) {
|
|
30474
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleApprovalDecided(data.source_id, data.actor_id, data.chat_id ?? null), () => {
|
|
30005
30475
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30006
30476
|
});
|
|
30007
|
-
}
|
|
30477
|
+
});
|
|
30008
30478
|
} catch (err) {
|
|
30009
30479
|
this.opts.log?.error(`approval decided dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30010
30480
|
}
|
|
30481
|
+
} else if (data.event_type === "message" && this.laneLedger && data.source_id && data.chat_id) {
|
|
30482
|
+
try {
|
|
30483
|
+
await this.handleMessageRedrive(data);
|
|
30484
|
+
} catch (err) {
|
|
30485
|
+
this.opts.log?.error(`message re-drive failed for ${data.source_id}: ${String(err)}`);
|
|
30486
|
+
}
|
|
30011
30487
|
} else if (data.event_type !== "message" && data.event_type !== "task_assign") {
|
|
30012
30488
|
this.opts.log?.info(`dispatch.new with unhandled event_type=${String(data.event_type)} (id=${data.id}) \u2014 no-op`);
|
|
30013
30489
|
}
|
|
@@ -30043,6 +30519,39 @@ var ParallAgentGateway = class {
|
|
|
30043
30519
|
source_id: sourceId
|
|
30044
30520
|
});
|
|
30045
30521
|
}
|
|
30522
|
+
/** True when this event's lifecycle is owned by the dispatch lane ledger. */
|
|
30523
|
+
usesLaneLedger(event) {
|
|
30524
|
+
return this.laneLedger != null && !this.ledgerDisabled && this.laneLedger.handles(event);
|
|
30525
|
+
}
|
|
30526
|
+
disableLedger(reason) {
|
|
30527
|
+
if (this.ledgerDisabled)
|
|
30528
|
+
return;
|
|
30529
|
+
this.ledgerDisabled = true;
|
|
30530
|
+
this.opts.log?.warn(`dispatch ledger unavailable (${reason}) \u2014 falling back to legacy received/ack flow`);
|
|
30531
|
+
}
|
|
30532
|
+
/**
|
|
30533
|
+
* Buffer grouping key. Lane-ledger message events group by full lane
|
|
30534
|
+
* identity (chat + thread) so a channel lane and a thread lane in the same
|
|
30535
|
+
* chat dispatch as separate turns with separate claims; everything else
|
|
30536
|
+
* keeps the historical chat-level grouping.
|
|
30537
|
+
*/
|
|
30538
|
+
dispatchGroupKey(event) {
|
|
30539
|
+
if (this.usesLaneLedger(event)) {
|
|
30540
|
+
return this.laneLedger.laneKeyFor(event);
|
|
30541
|
+
}
|
|
30542
|
+
return event.targetId;
|
|
30543
|
+
}
|
|
30544
|
+
// Lane-flow protocols live in gateway-lane-flow.ts; these thin delegates
|
|
30545
|
+
// keep call sites and tests on the class surface.
|
|
30546
|
+
laneFlowHost() {
|
|
30547
|
+
return this;
|
|
30548
|
+
}
|
|
30549
|
+
dispatchLaneGroup(opts) {
|
|
30550
|
+
return dispatchLaneGroup(this.laneFlowHost(), opts);
|
|
30551
|
+
}
|
|
30552
|
+
consumeTypedDispatch(ref, run, ack) {
|
|
30553
|
+
return consumeTypedDispatch(this.laneFlowHost(), ref, run, ack);
|
|
30554
|
+
}
|
|
30046
30555
|
buildDispatchContext(event, sessionKey) {
|
|
30047
30556
|
const binding = this.sessionBindings.get(sessionKey);
|
|
30048
30557
|
return {
|
|
@@ -30059,6 +30568,7 @@ var ParallAgentGateway = class {
|
|
|
30059
30568
|
noReply: event.noReply ?? false,
|
|
30060
30569
|
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey),
|
|
30061
30570
|
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey),
|
|
30571
|
+
contextDirPath: this.opts.dispatchContextDir,
|
|
30062
30572
|
client: this.opts.client,
|
|
30063
30573
|
log: this.opts.log
|
|
30064
30574
|
};
|
|
@@ -30098,7 +30608,7 @@ var ParallAgentGateway = class {
|
|
|
30098
30608
|
this.opts.log?.warn(`failed to create input step: ${String(err)}`);
|
|
30099
30609
|
}
|
|
30100
30610
|
}
|
|
30101
|
-
async createRuntimeStep(sessionId, event, runtimeEvent, stepIdFilePath, contextFilePath) {
|
|
30611
|
+
async createRuntimeStep(sessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2) {
|
|
30102
30612
|
const target = resolveStepTarget(event);
|
|
30103
30613
|
try {
|
|
30104
30614
|
switch (runtimeEvent.type) {
|
|
@@ -30144,6 +30654,9 @@ var ParallAgentGateway = class {
|
|
|
30144
30654
|
} else if (stepIdFilePath) {
|
|
30145
30655
|
this.writeStepIdFile(stepIdFilePath, step.id);
|
|
30146
30656
|
}
|
|
30657
|
+
if (laneContextFilePath2) {
|
|
30658
|
+
this.updateContextFileStepId(laneContextFilePath2, step.id);
|
|
30659
|
+
}
|
|
30147
30660
|
break;
|
|
30148
30661
|
}
|
|
30149
30662
|
case "tool_result":
|
|
@@ -30166,6 +30679,9 @@ var ParallAgentGateway = class {
|
|
|
30166
30679
|
} else if (stepIdFilePath) {
|
|
30167
30680
|
this.clearStepIdFile(stepIdFilePath);
|
|
30168
30681
|
}
|
|
30682
|
+
if (laneContextFilePath2) {
|
|
30683
|
+
this.updateContextFileStepId(laneContextFilePath2, null);
|
|
30684
|
+
}
|
|
30169
30685
|
break;
|
|
30170
30686
|
case "error":
|
|
30171
30687
|
await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
|
|
@@ -30185,28 +30701,28 @@ var ParallAgentGateway = class {
|
|
|
30185
30701
|
}
|
|
30186
30702
|
writeContextFile(filePath, ctx) {
|
|
30187
30703
|
try {
|
|
30188
|
-
|
|
30189
|
-
|
|
30704
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
30705
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30190
30706
|
} catch (err) {
|
|
30191
30707
|
this.opts.log?.warn(`failed to write context file ${filePath}: ${String(err)}`);
|
|
30192
30708
|
}
|
|
30193
30709
|
}
|
|
30194
30710
|
updateContextFileStepId(filePath, stepId) {
|
|
30195
30711
|
try {
|
|
30196
|
-
const raw =
|
|
30712
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
30197
30713
|
const ctx = JSON.parse(raw);
|
|
30198
30714
|
ctx.step_id = stepId;
|
|
30199
|
-
|
|
30715
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30200
30716
|
} catch (err) {
|
|
30201
30717
|
this.opts.log?.warn(`failed to update context file step_id ${filePath}: ${String(err)}`);
|
|
30202
30718
|
}
|
|
30203
30719
|
}
|
|
30204
30720
|
updateContextFileSessionId(filePath, sessionId) {
|
|
30205
30721
|
try {
|
|
30206
|
-
const raw =
|
|
30722
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
30207
30723
|
const ctx = JSON.parse(raw);
|
|
30208
30724
|
ctx.session_id = sessionId;
|
|
30209
|
-
|
|
30725
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30210
30726
|
} catch (err) {
|
|
30211
30727
|
this.opts.log?.warn(`failed to update context file session_id ${filePath}: ${String(err)}`);
|
|
30212
30728
|
}
|
|
@@ -30214,8 +30730,8 @@ var ParallAgentGateway = class {
|
|
|
30214
30730
|
/** @deprecated Use writeContextFile / updateContextFileStepId. */
|
|
30215
30731
|
writeStepIdFile(filePath, stepId) {
|
|
30216
30732
|
try {
|
|
30217
|
-
|
|
30218
|
-
|
|
30733
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
30734
|
+
fs2.writeFileSync(filePath, stepId, "utf8");
|
|
30219
30735
|
} catch (err) {
|
|
30220
30736
|
this.opts.log?.warn(`failed to write step id file ${filePath}: ${String(err)}`);
|
|
30221
30737
|
}
|
|
@@ -30223,7 +30739,7 @@ var ParallAgentGateway = class {
|
|
|
30223
30739
|
/** @deprecated Use writeContextFile / updateContextFileStepId. */
|
|
30224
30740
|
clearStepIdFile(filePath) {
|
|
30225
30741
|
try {
|
|
30226
|
-
|
|
30742
|
+
fs2.writeFileSync(filePath, "", "utf8");
|
|
30227
30743
|
} catch {
|
|
30228
30744
|
}
|
|
30229
30745
|
}
|
|
@@ -30232,7 +30748,7 @@ var ParallAgentGateway = class {
|
|
|
30232
30748
|
await this.createInputStep(sessionId, event);
|
|
30233
30749
|
}
|
|
30234
30750
|
}
|
|
30235
|
-
async bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath) {
|
|
30751
|
+
async bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2) {
|
|
30236
30752
|
const runtimeLaneKey = runtimeEvent.runtimeLaneKey || sessionKey;
|
|
30237
30753
|
const existing = this.sessionBindings.get(sessionKey);
|
|
30238
30754
|
if (existing && existing.runtimeLaneKey === runtimeLaneKey && existing.runtimeSessionId === runtimeEvent.runtimeSessionId) {
|
|
@@ -30276,6 +30792,9 @@ var ParallAgentGateway = class {
|
|
|
30276
30792
|
if (contextFilePath) {
|
|
30277
30793
|
this.updateContextFileSessionId(contextFilePath, session.id);
|
|
30278
30794
|
}
|
|
30795
|
+
if (laneContextFilePath2) {
|
|
30796
|
+
this.updateContextFileSessionId(laneContextFilePath2, session.id);
|
|
30797
|
+
}
|
|
30279
30798
|
await this.opts.onSessionBinding?.(binding);
|
|
30280
30799
|
return binding;
|
|
30281
30800
|
}
|
|
@@ -30302,14 +30821,27 @@ var ParallAgentGateway = class {
|
|
|
30302
30821
|
const dispatchContext = this.buildDispatchContext(event, sessionKey);
|
|
30303
30822
|
const contextFilePath = dispatchContext.contextFilePath;
|
|
30304
30823
|
const stepIdFilePath = dispatchContext.stepIdFilePath;
|
|
30824
|
+
const activeLane = this.ledgerDisabled ? void 0 : this.laneLedger?.getForEvent(event);
|
|
30825
|
+
const laneContextFilePath2 = activeLane ? this.laneLedger?.laneContextPath(activeLane) : void 0;
|
|
30826
|
+
const contextBody = {
|
|
30827
|
+
session_id: dispatchContext.sessionId ?? null,
|
|
30828
|
+
chat_id: dispatchContext.chatId ?? null,
|
|
30829
|
+
trigger_message_id: dispatchContext.triggerMessageId ?? null,
|
|
30830
|
+
no_reply: dispatchContext.noReply,
|
|
30831
|
+
step_id: null,
|
|
30832
|
+
dispatch_event_id: activeLane?.typedDispatchEventId ?? activeLane?.folded.get(event.messageId) ?? null,
|
|
30833
|
+
lane: activeLane?.lane ?? null,
|
|
30834
|
+
target_uri: activeLane?.targetUri ?? null,
|
|
30835
|
+
thread_root_id: activeLane?.threadRootId ?? null,
|
|
30836
|
+
// Typed binding hint for the CLI: which task this dispatch is about
|
|
30837
|
+
// (parall task update attaches the typed effect only on a match).
|
|
30838
|
+
task_id: event.type === "task" ? event.targetId : null
|
|
30839
|
+
};
|
|
30305
30840
|
if (contextFilePath) {
|
|
30306
|
-
this.writeContextFile(contextFilePath,
|
|
30307
|
-
|
|
30308
|
-
|
|
30309
|
-
|
|
30310
|
-
no_reply: dispatchContext.noReply,
|
|
30311
|
-
step_id: null
|
|
30312
|
-
});
|
|
30841
|
+
this.writeContextFile(contextFilePath, contextBody);
|
|
30842
|
+
}
|
|
30843
|
+
if (laneContextFilePath2) {
|
|
30844
|
+
this.writeContextFile(laneContextFilePath2, contextBody);
|
|
30313
30845
|
}
|
|
30314
30846
|
this.inFlightDispatches++;
|
|
30315
30847
|
const deadlineTimer = this.DISPATCH_DEADLINE_MS > 0 ? setTimeout(() => {
|
|
@@ -30336,7 +30868,7 @@ var ParallAgentGateway = class {
|
|
|
30336
30868
|
})) {
|
|
30337
30869
|
if (runtimeEvent.type === "runtime_session") {
|
|
30338
30870
|
const priorAgentSessionId = binding?.agentSessionId;
|
|
30339
|
-
binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath);
|
|
30871
|
+
binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2);
|
|
30340
30872
|
if (event.targetType === "channel_conversation" && binding.agentSessionId !== priorAgentSessionId) {
|
|
30341
30873
|
try {
|
|
30342
30874
|
await this.opts.client.setChannelConversationSession(this.opts.config.org_id, event.targetId, binding.agentSessionId);
|
|
@@ -30368,6 +30900,9 @@ var ParallAgentGateway = class {
|
|
|
30368
30900
|
await this.createInputStep(binding.agentSessionId, event);
|
|
30369
30901
|
inputStepsCreated = true;
|
|
30370
30902
|
}
|
|
30903
|
+
if (activeLane && !this.ledgerDisabled) {
|
|
30904
|
+
this.laneLedger?.maybeRenew(activeLane);
|
|
30905
|
+
}
|
|
30371
30906
|
if (captureText && runtimeEvent.type === "text" && runtimeEvent.text) {
|
|
30372
30907
|
captureText.push(runtimeEvent.text);
|
|
30373
30908
|
}
|
|
@@ -30384,7 +30919,7 @@ var ParallAgentGateway = class {
|
|
|
30384
30919
|
} else if (runtimeEvent.type === "tool_result" && pendingSendCallIds.delete(runtimeEvent.callId)) {
|
|
30385
30920
|
recordMessageSend(sessionKey, !runtimeEvent.error);
|
|
30386
30921
|
}
|
|
30387
|
-
await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath);
|
|
30922
|
+
await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
30388
30923
|
}
|
|
30389
30924
|
if (!binding) {
|
|
30390
30925
|
binding = this.sessionBindings.get(sessionKey);
|
|
@@ -30406,7 +30941,7 @@ var ParallAgentGateway = class {
|
|
|
30406
30941
|
await this.createRuntimeStep(binding.agentSessionId, event, {
|
|
30407
30942
|
type: "error",
|
|
30408
30943
|
message: `Dispatch failed: ${String(err)}`
|
|
30409
|
-
}, stepIdFilePath, contextFilePath);
|
|
30944
|
+
}, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
30410
30945
|
} catch (stepErr) {
|
|
30411
30946
|
if (this.isSessionNotLiveError(stepErr))
|
|
30412
30947
|
staleDetected = true;
|
|
@@ -30449,6 +30984,9 @@ var ParallAgentGateway = class {
|
|
|
30449
30984
|
} else if (stepIdFilePath) {
|
|
30450
30985
|
this.clearStepIdFile(stepIdFilePath);
|
|
30451
30986
|
}
|
|
30987
|
+
if (laneContextFilePath2) {
|
|
30988
|
+
this.updateContextFileStepId(laneContextFilePath2, null);
|
|
30989
|
+
}
|
|
30452
30990
|
this.inFlightDispatches--;
|
|
30453
30991
|
if (this.inFlightDispatches === 0 && this.drainResolvers.length > 0) {
|
|
30454
30992
|
const resolvers = this.drainResolvers.splice(0);
|
|
@@ -30506,13 +31044,39 @@ var ParallAgentGateway = class {
|
|
|
30506
31044
|
item.resolve(false);
|
|
30507
31045
|
break;
|
|
30508
31046
|
}
|
|
30509
|
-
|
|
31047
|
+
let items;
|
|
31048
|
+
const head = fork.queue[0];
|
|
31049
|
+
if (head && this.usesLaneLedger(head.event)) {
|
|
31050
|
+
const headKey = this.dispatchGroupKey(head.event);
|
|
31051
|
+
const splitAt = fork.queue.findIndex((it) => this.dispatchGroupKey(it.event) !== headKey);
|
|
31052
|
+
items = splitAt === -1 ? fork.queue.splice(0) : fork.queue.splice(0, splitAt);
|
|
31053
|
+
} else {
|
|
31054
|
+
items = fork.queue.splice(0);
|
|
31055
|
+
}
|
|
30510
31056
|
const events = items.map((item) => item.event);
|
|
30511
31057
|
const last = events[events.length - 1];
|
|
30512
31058
|
const earlier = events.slice(0, -1);
|
|
30513
31059
|
try {
|
|
30514
31060
|
const batchText = [];
|
|
30515
|
-
|
|
31061
|
+
let dispatched;
|
|
31062
|
+
if (this.usesLaneLedger(last)) {
|
|
31063
|
+
const outcome = await this.dispatchLaneGroup({
|
|
31064
|
+
events,
|
|
31065
|
+
sessionKey: fork.fork.sessionKey,
|
|
31066
|
+
body: buildForkScopePrefix(last) + buildEventBody(last),
|
|
31067
|
+
earlier,
|
|
31068
|
+
captureText: batchText,
|
|
31069
|
+
hasMoreLocal: () => fork.queue.length > 0
|
|
31070
|
+
});
|
|
31071
|
+
if (outcome === "foreign") {
|
|
31072
|
+
for (const item of items)
|
|
31073
|
+
item.resolve(false);
|
|
31074
|
+
break;
|
|
31075
|
+
}
|
|
31076
|
+
dispatched = outcome === "dispatched";
|
|
31077
|
+
} else {
|
|
31078
|
+
dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildForkScopePrefix(last) + buildEventBody(last), earlier, batchText);
|
|
31079
|
+
}
|
|
30516
31080
|
if (!dispatched) {
|
|
30517
31081
|
for (const item of items) {
|
|
30518
31082
|
item.resolve(false);
|
|
@@ -30615,9 +31179,9 @@ var ParallAgentGateway = class {
|
|
|
30615
31179
|
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
31180
|
break;
|
|
30617
31181
|
}
|
|
30618
|
-
const
|
|
31182
|
+
const groupKey = this.dispatchGroupKey(this.dispatchState.mainBuffer[0]);
|
|
30619
31183
|
const events = [];
|
|
30620
|
-
while (this.dispatchState.mainBuffer[0]
|
|
31184
|
+
while (this.dispatchState.mainBuffer[0] && this.dispatchGroupKey(this.dispatchState.mainBuffer[0]) === groupKey) {
|
|
30621
31185
|
events.push(this.dispatchState.mainBuffer.shift());
|
|
30622
31186
|
}
|
|
30623
31187
|
const event = events[events.length - 1];
|
|
@@ -30626,7 +31190,36 @@ var ParallAgentGateway = class {
|
|
|
30626
31190
|
const pendingFork = hasPendingInjections ? [] : this.dispatchState.pendingForkResults.splice(0);
|
|
30627
31191
|
const forkPrefix = buildForkResultPrefix(pendingFork);
|
|
30628
31192
|
this.dispatchState.mainCurrentTargetId = event.targetId;
|
|
31193
|
+
this.mainCurrentGroupKey = groupKey;
|
|
30629
31194
|
this.dispatchState.mainPreDispatchBranchPoint = this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
|
|
31195
|
+
if (this.usesLaneLedger(event)) {
|
|
31196
|
+
let outcome;
|
|
31197
|
+
try {
|
|
31198
|
+
outcome = await this.dispatchLaneGroup({
|
|
31199
|
+
events,
|
|
31200
|
+
sessionKey: this.opts.runtimeKey,
|
|
31201
|
+
body: forkPrefix + buildEventBody(event),
|
|
31202
|
+
earlier,
|
|
31203
|
+
hasMoreLocal: () => this.dispatchState.mainBuffer.some((e) => this.dispatchGroupKey(e) === groupKey)
|
|
31204
|
+
});
|
|
31205
|
+
} catch (err) {
|
|
31206
|
+
this.opts.log?.error(`lane dispatch failed for ${event.messageId}: ${String(err)}`);
|
|
31207
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31208
|
+
for (const ev of events)
|
|
31209
|
+
this.dispatchedMessages.delete(ev.messageId);
|
|
31210
|
+
continue;
|
|
31211
|
+
}
|
|
31212
|
+
if (outcome === "shutdown") {
|
|
31213
|
+
this.dispatchState.mainBuffer.unshift(...events);
|
|
31214
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31215
|
+
break;
|
|
31216
|
+
}
|
|
31217
|
+
if (outcome === "foreign") {
|
|
31218
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31219
|
+
continue;
|
|
31220
|
+
}
|
|
31221
|
+
continue;
|
|
31222
|
+
}
|
|
30630
31223
|
try {
|
|
30631
31224
|
await this.emitDispatchReceived(event);
|
|
30632
31225
|
} catch (err) {
|
|
@@ -30655,6 +31248,7 @@ var ParallAgentGateway = class {
|
|
|
30655
31248
|
this.draining = false;
|
|
30656
31249
|
this.dispatchState.mainDispatching = false;
|
|
30657
31250
|
this.dispatchState.mainCurrentTargetId = void 0;
|
|
31251
|
+
this.mainCurrentGroupKey = void 0;
|
|
30658
31252
|
this.dispatchState.mainPreDispatchBranchPoint = void 0;
|
|
30659
31253
|
if (!this.shuttingDown && this.dispatchState.mainBuffer.length > 0) {
|
|
30660
31254
|
setTimeout(() => {
|
|
@@ -30674,13 +31268,38 @@ var ParallAgentGateway = class {
|
|
|
30674
31268
|
const forkPrefix = buildForkResultPrefix(pendingFork);
|
|
30675
31269
|
this.dispatchState.mainDispatching = true;
|
|
30676
31270
|
this.dispatchState.mainCurrentTargetId = event.targetId;
|
|
31271
|
+
this.mainCurrentGroupKey = this.dispatchGroupKey(event);
|
|
30677
31272
|
this.dispatchState.mainPreDispatchBranchPoint = this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
|
|
31273
|
+
if (this.usesLaneLedger(event)) {
|
|
31274
|
+
let outcome = "shutdown";
|
|
31275
|
+
try {
|
|
31276
|
+
try {
|
|
31277
|
+
outcome = await this.dispatchLaneGroup({
|
|
31278
|
+
events: [event],
|
|
31279
|
+
sessionKey: this.opts.runtimeKey,
|
|
31280
|
+
body: forkPrefix + buildEventBody(event),
|
|
31281
|
+
earlier: [],
|
|
31282
|
+
hasMoreLocal: () => this.dispatchState.mainBuffer.some((e) => this.dispatchGroupKey(e) === this.dispatchGroupKey(event))
|
|
31283
|
+
});
|
|
31284
|
+
} catch (err) {
|
|
31285
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31286
|
+
throw err;
|
|
31287
|
+
}
|
|
31288
|
+
if (outcome !== "dispatched") {
|
|
31289
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31290
|
+
}
|
|
31291
|
+
} finally {
|
|
31292
|
+
await this.drainMainBuffer();
|
|
31293
|
+
}
|
|
31294
|
+
return outcome === "dispatched";
|
|
31295
|
+
}
|
|
30678
31296
|
try {
|
|
30679
31297
|
await this.emitDispatchReceived(event);
|
|
30680
31298
|
} catch (err) {
|
|
30681
31299
|
this.opts.log?.warn?.(`mark-received failed, leaving unacked for retry: ${String(err)}`);
|
|
30682
31300
|
this.dispatchState.mainDispatching = false;
|
|
30683
31301
|
this.dispatchState.mainCurrentTargetId = void 0;
|
|
31302
|
+
this.mainCurrentGroupKey = void 0;
|
|
30684
31303
|
this.dispatchState.mainPreDispatchBranchPoint = void 0;
|
|
30685
31304
|
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
30686
31305
|
return false;
|
|
@@ -30701,7 +31320,11 @@ var ParallAgentGateway = class {
|
|
|
30701
31320
|
return false;
|
|
30702
31321
|
}
|
|
30703
31322
|
this.dispatchState.mainBuffer.push(event);
|
|
30704
|
-
if (this.
|
|
31323
|
+
if (this.usesLaneLedger(event)) {
|
|
31324
|
+
if (this.mainCurrentGroupKey === this.dispatchGroupKey(event) && await this.laneLedger?.steerLive(event) && await this.opts.dispatchAdapter.enqueueDuringDispatch?.(this.opts.runtimeKey, buildEventBody(event))) {
|
|
31325
|
+
this.opts.log?.info(`steer folded+injected for ${event.messageId} (will drain for bookkeeping)`);
|
|
31326
|
+
}
|
|
31327
|
+
} else if (this.dispatchState.mainCurrentTargetId === event.targetId && await this.opts.dispatchAdapter.enqueueDuringDispatch?.(this.opts.runtimeKey, buildEventBody(event))) {
|
|
30705
31328
|
this.opts.log?.info(`steer injected for ${event.messageId} (will drain for bookkeeping)`);
|
|
30706
31329
|
}
|
|
30707
31330
|
if (!this.dispatchState.mainDispatching && !this.draining && this.dispatchState.mainBuffer.length > 0) {
|
|
@@ -30872,8 +31495,10 @@ var ParallAgentGateway = class {
|
|
|
30872
31495
|
try {
|
|
30873
31496
|
const dispatched = await this.handleInboundEvent(event);
|
|
30874
31497
|
if (dispatched) {
|
|
30875
|
-
|
|
30876
|
-
|
|
31498
|
+
if (!this.usesLaneLedger(event)) {
|
|
31499
|
+
this.opts.client.ackDispatch(this.opts.config.org_id, { source_type: "message", source_id: data.id }).catch(() => {
|
|
31500
|
+
});
|
|
31501
|
+
}
|
|
30877
31502
|
} else {
|
|
30878
31503
|
this.dispatchedMessages.delete(data.id);
|
|
30879
31504
|
}
|
|
@@ -30882,7 +31507,21 @@ var ParallAgentGateway = class {
|
|
|
30882
31507
|
this.dispatchedMessages.delete(data.id);
|
|
30883
31508
|
}
|
|
30884
31509
|
}
|
|
30885
|
-
|
|
31510
|
+
// Ledger re-drive consumption: dispatch.new message hints re-enter the
|
|
31511
|
+
// shared WorkItem consumption path (same protocol as catch-up).
|
|
31512
|
+
async handleMessageRedrive(item) {
|
|
31513
|
+
if (!item.chat_id || !item.source_id)
|
|
31514
|
+
return;
|
|
31515
|
+
await this.consumeMessageWorkItem({
|
|
31516
|
+
id: item.id,
|
|
31517
|
+
source_id: item.source_id,
|
|
31518
|
+
chat_id: item.chat_id
|
|
31519
|
+
});
|
|
31520
|
+
}
|
|
31521
|
+
consumeMessageWorkItem(item) {
|
|
31522
|
+
return consumeMessageWorkItem(this.laneFlowHost(), item);
|
|
31523
|
+
}
|
|
31524
|
+
async handleTaskAssignment(task, ackSourceId, dispatchEventId) {
|
|
30886
31525
|
if (this.shuttingDown)
|
|
30887
31526
|
return false;
|
|
30888
31527
|
const dedupeKey = `${task.id}:${task.updated_at}`;
|
|
@@ -30911,7 +31550,8 @@ var ParallAgentGateway = class {
|
|
|
30911
31550
|
body: parts.join("\n"),
|
|
30912
31551
|
sentAt: task.updated_at ?? task.created_at,
|
|
30913
31552
|
ackSourceType: "task_activity",
|
|
30914
|
-
ackSourceId
|
|
31553
|
+
ackSourceId,
|
|
31554
|
+
dispatchEventId
|
|
30915
31555
|
};
|
|
30916
31556
|
const dispatched = await this.handleInboundEvent(event);
|
|
30917
31557
|
if (!dispatched) {
|
|
@@ -30935,7 +31575,7 @@ var ParallAgentGateway = class {
|
|
|
30935
31575
|
this.opts.log?.info(`skipping stale task dispatch ${ackSourceId ?? taskId} \u2014 assigned to ${task.assignee_id}, creator ${task.creator_id}`);
|
|
30936
31576
|
return true;
|
|
30937
31577
|
}
|
|
30938
|
-
return this.handleTaskAssignment(task, ackSourceId);
|
|
31578
|
+
return this.handleTaskAssignment(task, ackSourceId, opts.dispatchEventId);
|
|
30939
31579
|
}
|
|
30940
31580
|
async handleTaskComment(commentId, taskId, actorId, deliveryReason) {
|
|
30941
31581
|
if (this.shuttingDown)
|
|
@@ -31343,74 +31983,46 @@ var ParallAgentGateway = class {
|
|
|
31343
31983
|
}
|
|
31344
31984
|
processed++;
|
|
31345
31985
|
try {
|
|
31346
|
-
|
|
31986
|
+
const ackItem = () => {
|
|
31987
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
31988
|
+
});
|
|
31989
|
+
};
|
|
31347
31990
|
if (item.event_type === "task_assign" && item.task_id) {
|
|
31348
31991
|
try {
|
|
31349
|
-
|
|
31992
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
|
|
31993
|
+
dispatchEventId
|
|
31994
|
+
}), ackItem);
|
|
31350
31995
|
} catch (err) {
|
|
31351
31996
|
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
31352
31997
|
continue;
|
|
31353
31998
|
}
|
|
31354
31999
|
} else if (item.event_type === "task_update" && item.task_id) {
|
|
31355
32000
|
try {
|
|
31356
|
-
|
|
32001
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
|
|
32002
|
+
allowCreator: true,
|
|
32003
|
+
dispatchEventId
|
|
32004
|
+
}), ackItem);
|
|
31357
32005
|
} catch (err) {
|
|
31358
32006
|
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
31359
32007
|
continue;
|
|
31360
32008
|
}
|
|
31361
32009
|
} else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
|
|
31362
|
-
|
|
32010
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleTaskComment(item.source_id, item.task_id ?? "", item.actor_id, item.delivery_reason), ackItem);
|
|
31363
32011
|
} else if (item.event_type === "wiki_comment" && item.source_id) {
|
|
31364
|
-
|
|
32012
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleWikiComment(item.source_id, item.actor_id, item.delivery_reason), ackItem);
|
|
31365
32013
|
} else if (item.event_type === "schedule.fire" && item.source_id) {
|
|
31366
|
-
|
|
32014
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleScheduleFire(item.source_id, item.actor_id), ackItem);
|
|
31367
32015
|
} else if (item.event_type === "external_trigger" && item.source_id) {
|
|
31368
|
-
|
|
32016
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleExternalTriggerRun(item.source_id), ackItem);
|
|
31369
32017
|
} else if (item.event_type === "channel_message" && item.source_id) {
|
|
31370
|
-
|
|
32018
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleChannelMessage(item.source_id), ackItem);
|
|
31371
32019
|
} else if (item.event_type === "approval_decided" && item.source_id) {
|
|
31372
|
-
|
|
32020
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null), ackItem);
|
|
31373
32021
|
} 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(() => {
|
|
32022
|
+
await this.consumeMessageWorkItem({
|
|
32023
|
+
id: item.id,
|
|
32024
|
+
source_id: item.source_id,
|
|
32025
|
+
chat_id: item.chat_id
|
|
31414
32026
|
});
|
|
31415
32027
|
}
|
|
31416
32028
|
} catch (err) {
|
|
@@ -31476,6 +32088,14 @@ ${fullSummary}` : fullSummary;
|
|
|
31476
32088
|
this.abortFork(targetId, "ws reconnect");
|
|
31477
32089
|
}
|
|
31478
32090
|
}
|
|
32091
|
+
if (this.laneLedger && this.inFlightDispatches === 0 && this.laneLedger.activeCount > 0) {
|
|
32092
|
+
log?.info(`releasing ${this.laneLedger.activeCount} stale lane(s) on reconnect`);
|
|
32093
|
+
await this.laneLedger.releaseAll();
|
|
32094
|
+
}
|
|
32095
|
+
if (this.laneLedger && this.ledgerDisabled) {
|
|
32096
|
+
log?.info("re-probing dispatch ledger after reconnect (was disabled)");
|
|
32097
|
+
this.ledgerDisabled = false;
|
|
32098
|
+
}
|
|
31479
32099
|
const intervalSec = data.heartbeat_interval > 0 ? data.heartbeat_interval : 30;
|
|
31480
32100
|
try {
|
|
31481
32101
|
const count = await fetchAllChats(client, config.org_id, this.chatInfoMap);
|
|
@@ -31546,6 +32166,10 @@ ${fullSummary}` : fullSummary;
|
|
|
31546
32166
|
}
|
|
31547
32167
|
if (this.heartbeatTimer)
|
|
31548
32168
|
clearInterval(this.heartbeatTimer);
|
|
32169
|
+
if (this.laneLedger && this.laneLedger.activeCount > 0) {
|
|
32170
|
+
this.opts.log?.info(`releasing ${this.laneLedger.activeCount} lane(s) on shutdown`);
|
|
32171
|
+
await this.laneLedger.releaseAll();
|
|
32172
|
+
}
|
|
31549
32173
|
await this.opts.onBeforeDisconnect?.();
|
|
31550
32174
|
this.opts.ws.disconnect();
|
|
31551
32175
|
this.opts.log?.info(`disconnected`);
|
|
@@ -31556,8 +32180,8 @@ ${fullSummary}` : fullSummary;
|
|
|
31556
32180
|
import { execSync } from "node:child_process";
|
|
31557
32181
|
import { constants } from "node:fs";
|
|
31558
32182
|
import * as fsSync from "node:fs";
|
|
31559
|
-
import * as
|
|
31560
|
-
import * as
|
|
32183
|
+
import * as fs3 from "node:fs/promises";
|
|
32184
|
+
import * as path3 from "node:path";
|
|
31561
32185
|
var DEFAULT_MAX_TOTAL_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
31562
32186
|
var DEFAULT_ATTACHMENT_CACHE_MAX_BYTES = 512 * 1024 * 1024;
|
|
31563
32187
|
var DEFAULT_ATTACHMENT_DOWNLOAD_TIMEOUT_MS = 3e4;
|
|
@@ -31587,11 +32211,11 @@ async function prepareLocalImageAttachments(event, context2, opts) {
|
|
|
31587
32211
|
};
|
|
31588
32212
|
}
|
|
31589
32213
|
const rootDir = await ensureAttachmentRootDir(opts.workspaceDir);
|
|
31590
|
-
const messageDir =
|
|
32214
|
+
const messageDir = path3.join(rootDir, sanitizePathSegment(event.messageId));
|
|
31591
32215
|
await ensurePathIsNotSymlink(messageDir);
|
|
31592
|
-
await
|
|
32216
|
+
await fs3.mkdir(messageDir, { recursive: true });
|
|
31593
32217
|
await ensurePathIsNotSymlink(messageDir);
|
|
31594
|
-
const activeMessageDir =
|
|
32218
|
+
const activeMessageDir = path3.resolve(messageDir);
|
|
31595
32219
|
activeAttachmentDirs.add(activeMessageDir);
|
|
31596
32220
|
const maintenanceCooldownMs = opts.maintenanceCooldownMs ?? DEFAULT_MAINTENANCE_COOLDOWN_MS;
|
|
31597
32221
|
const maintenancePromise = scheduleAttachmentMaintenance(rootDir, {
|
|
@@ -31608,7 +32232,7 @@ async function prepareLocalImageAttachments(event, context2, opts) {
|
|
|
31608
32232
|
const notes = [];
|
|
31609
32233
|
let downloadedBytes = 0;
|
|
31610
32234
|
for (const att of imageAttachments) {
|
|
31611
|
-
const localPath =
|
|
32235
|
+
const localPath = path3.join(messageDir, localFileName(att.id, att.fileName, att.mimeType));
|
|
31612
32236
|
const downloadTimeoutMs = opts.downloadTimeoutMs ?? DEFAULT_ATTACHMENT_DOWNLOAD_TIMEOUT_MS;
|
|
31613
32237
|
const fetchFresh = async () => {
|
|
31614
32238
|
const fileInfo = await withTimeout(context2.client.getFileUrl(att.id), downloadTimeoutMs, `file URL lookup timed out after ${downloadTimeoutMs}ms`);
|
|
@@ -31665,7 +32289,7 @@ async function appendPreparedLocalAttachmentRefs(body, event, context2, opts) {
|
|
|
31665
32289
|
return { body: appendLocalAttachmentRefs(body, attachments), attachments };
|
|
31666
32290
|
}
|
|
31667
32291
|
function pinLocalAttachmentPaths(images) {
|
|
31668
|
-
const dirs = new Set(images.map((image) =>
|
|
32292
|
+
const dirs = new Set(images.map((image) => path3.resolve(path3.dirname(image.localPath))));
|
|
31669
32293
|
for (const dir of dirs) {
|
|
31670
32294
|
activeAttachmentDirs.add(dir);
|
|
31671
32295
|
}
|
|
@@ -31680,7 +32304,7 @@ function pinLocalAttachmentPaths(images) {
|
|
|
31680
32304
|
};
|
|
31681
32305
|
}
|
|
31682
32306
|
function attachmentRootDir(workspaceDir) {
|
|
31683
|
-
return
|
|
32307
|
+
return path3.join(path3.resolve(workspaceDir), ".parall", "attachments");
|
|
31684
32308
|
}
|
|
31685
32309
|
function ensureLocalAttachmentGitExclude(workingDirectory) {
|
|
31686
32310
|
try {
|
|
@@ -31689,8 +32313,8 @@ function ensureLocalAttachmentGitExclude(workingDirectory) {
|
|
|
31689
32313
|
encoding: "utf8",
|
|
31690
32314
|
stdio: ["ignore", "pipe", "ignore"]
|
|
31691
32315
|
}).trim();
|
|
31692
|
-
const excludePath =
|
|
31693
|
-
fsSync.mkdirSync(
|
|
32316
|
+
const excludePath = path3.isAbsolute(rel) ? rel : path3.join(workingDirectory, rel);
|
|
32317
|
+
fsSync.mkdirSync(path3.dirname(excludePath), { recursive: true });
|
|
31694
32318
|
const existing = fsSync.existsSync(excludePath) ? fsSync.readFileSync(excludePath, "utf8") : "";
|
|
31695
32319
|
if (existing.split(/\r?\n/).some((line) => line.trim() === ".parall/"))
|
|
31696
32320
|
return;
|
|
@@ -31726,18 +32350,18 @@ function scheduleAttachmentMaintenance(rootDir, opts) {
|
|
|
31726
32350
|
return run;
|
|
31727
32351
|
}
|
|
31728
32352
|
async function ensureAttachmentRootDir(workspaceDir) {
|
|
31729
|
-
const workspaceRoot =
|
|
31730
|
-
const parallDir =
|
|
32353
|
+
const workspaceRoot = path3.resolve(workspaceDir);
|
|
32354
|
+
const parallDir = path3.join(workspaceRoot, ".parall");
|
|
31731
32355
|
const rootDir = attachmentRootDir(workspaceRoot);
|
|
31732
|
-
await
|
|
32356
|
+
await fs3.mkdir(workspaceRoot, { recursive: true });
|
|
31733
32357
|
await ensurePathIsNotSymlink(parallDir);
|
|
31734
|
-
await
|
|
32358
|
+
await fs3.mkdir(parallDir, { recursive: true, mode: 448 });
|
|
31735
32359
|
await ensurePathIsNotSymlink(parallDir);
|
|
31736
32360
|
await ensurePathIsNotSymlink(rootDir);
|
|
31737
|
-
await
|
|
32361
|
+
await fs3.mkdir(rootDir, { recursive: true, mode: 448 });
|
|
31738
32362
|
await ensurePathIsNotSymlink(rootDir);
|
|
31739
|
-
const realWorkspace = await
|
|
31740
|
-
const realRoot = await
|
|
32363
|
+
const realWorkspace = await fs3.realpath(workspaceRoot);
|
|
32364
|
+
const realRoot = await fs3.realpath(rootDir);
|
|
31741
32365
|
if (!isPathInside(realRoot, realWorkspace)) {
|
|
31742
32366
|
throw new Error(`attachment root escapes workspace: ${rootDir}`);
|
|
31743
32367
|
}
|
|
@@ -31745,7 +32369,7 @@ async function ensureAttachmentRootDir(workspaceDir) {
|
|
|
31745
32369
|
}
|
|
31746
32370
|
async function ensurePathIsNotSymlink(filePath) {
|
|
31747
32371
|
try {
|
|
31748
|
-
const stat = await
|
|
32372
|
+
const stat = await fs3.lstat(filePath);
|
|
31749
32373
|
if (stat.isSymbolicLink()) {
|
|
31750
32374
|
throw new Error(`refusing to use symlinked attachment path ${filePath}`);
|
|
31751
32375
|
}
|
|
@@ -31756,8 +32380,8 @@ async function ensurePathIsNotSymlink(filePath) {
|
|
|
31756
32380
|
}
|
|
31757
32381
|
}
|
|
31758
32382
|
function isPathInside(childPath, parentPath) {
|
|
31759
|
-
const rel =
|
|
31760
|
-
return rel === "" || !!rel && !rel.startsWith("..") && !
|
|
32383
|
+
const rel = path3.relative(parentPath, childPath);
|
|
32384
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !path3.isAbsolute(rel);
|
|
31761
32385
|
}
|
|
31762
32386
|
async function existingUsableFile(filePath, expectedSize, rootDir) {
|
|
31763
32387
|
try {
|
|
@@ -31768,30 +32392,30 @@ async function existingUsableFile(filePath, expectedSize, rootDir) {
|
|
|
31768
32392
|
}
|
|
31769
32393
|
}
|
|
31770
32394
|
async function localFileStatInsideRoot(filePath, rootDir) {
|
|
31771
|
-
const stat = await
|
|
32395
|
+
const stat = await fs3.lstat(filePath);
|
|
31772
32396
|
if (stat.isSymbolicLink()) {
|
|
31773
32397
|
throw new Error(`refusing to use symlinked attachment file ${filePath}`);
|
|
31774
32398
|
}
|
|
31775
32399
|
if (!stat.isFile()) {
|
|
31776
32400
|
throw new Error(`attachment path is not a file ${filePath}`);
|
|
31777
32401
|
}
|
|
31778
|
-
const realRoot = await
|
|
31779
|
-
const realFile = await
|
|
32402
|
+
const realRoot = await fs3.realpath(rootDir);
|
|
32403
|
+
const realFile = await fs3.realpath(filePath);
|
|
31780
32404
|
if (!isPathInside(realFile, realRoot)) {
|
|
31781
32405
|
throw new Error(`attachment file escapes workspace: ${filePath}`);
|
|
31782
32406
|
}
|
|
31783
32407
|
return stat;
|
|
31784
32408
|
}
|
|
31785
32409
|
async function localDirectoryStatInsideRoot(dirPath, rootDir) {
|
|
31786
|
-
const stat = await
|
|
32410
|
+
const stat = await fs3.lstat(dirPath);
|
|
31787
32411
|
if (stat.isSymbolicLink()) {
|
|
31788
32412
|
throw new Error(`refusing to use symlinked attachment directory ${dirPath}`);
|
|
31789
32413
|
}
|
|
31790
32414
|
if (!stat.isDirectory()) {
|
|
31791
32415
|
throw new Error(`attachment path is not a directory ${dirPath}`);
|
|
31792
32416
|
}
|
|
31793
|
-
const realRoot = await
|
|
31794
|
-
const realDir = await
|
|
32417
|
+
const realRoot = await fs3.realpath(rootDir);
|
|
32418
|
+
const realDir = await fs3.realpath(dirPath);
|
|
31795
32419
|
if (!isPathInside(realDir, realRoot)) {
|
|
31796
32420
|
throw new Error(`attachment directory escapes workspace: ${dirPath}`);
|
|
31797
32421
|
}
|
|
@@ -31799,7 +32423,7 @@ async function localDirectoryStatInsideRoot(dirPath, rootDir) {
|
|
|
31799
32423
|
}
|
|
31800
32424
|
async function openLocalFileInsideRoot(filePath, rootDir) {
|
|
31801
32425
|
const checkedStat = await localFileStatInsideRoot(filePath, rootDir);
|
|
31802
|
-
const file = await
|
|
32426
|
+
const file = await fs3.open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
31803
32427
|
let keepOpen = false;
|
|
31804
32428
|
try {
|
|
31805
32429
|
const openedStat = await file.stat();
|
|
@@ -31815,8 +32439,8 @@ async function openLocalFileInsideRoot(filePath, rootDir) {
|
|
|
31815
32439
|
}
|
|
31816
32440
|
}
|
|
31817
32441
|
async function openLocalTempFileInsideRoot(filePath, rootDir) {
|
|
31818
|
-
await localDirectoryStatInsideRoot(
|
|
31819
|
-
const file = await
|
|
32442
|
+
await localDirectoryStatInsideRoot(path3.dirname(filePath), rootDir);
|
|
32443
|
+
const file = await fs3.open(filePath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
|
|
31820
32444
|
let keepOpen = false;
|
|
31821
32445
|
try {
|
|
31822
32446
|
const checkedStat = await localFileStatInsideRoot(filePath, rootDir);
|
|
@@ -31841,7 +32465,7 @@ async function assertLocalFileIdentity(filePath, rootDir, expected) {
|
|
|
31841
32465
|
async function removeLocalFileIfInside(filePath, rootDir) {
|
|
31842
32466
|
try {
|
|
31843
32467
|
await localFileStatInsideRoot(filePath, rootDir);
|
|
31844
|
-
await
|
|
32468
|
+
await fs3.rm(filePath, { force: true });
|
|
31845
32469
|
} catch {
|
|
31846
32470
|
}
|
|
31847
32471
|
}
|
|
@@ -31854,7 +32478,7 @@ function sameFile(a, b) {
|
|
|
31854
32478
|
async function cleanupOldAttachmentFiles(rootDir, ttlMs, log, preserveDirs) {
|
|
31855
32479
|
let entries;
|
|
31856
32480
|
try {
|
|
31857
|
-
entries = await
|
|
32481
|
+
entries = await fs3.readdir(rootDir, { withFileTypes: true });
|
|
31858
32482
|
} catch {
|
|
31859
32483
|
return;
|
|
31860
32484
|
}
|
|
@@ -31862,15 +32486,15 @@ async function cleanupOldAttachmentFiles(rootDir, ttlMs, log, preserveDirs) {
|
|
|
31862
32486
|
await Promise.all(entries.map(async (entry) => {
|
|
31863
32487
|
if (!entry.isDirectory())
|
|
31864
32488
|
return;
|
|
31865
|
-
const fullPath =
|
|
32489
|
+
const fullPath = path3.join(rootDir, entry.name);
|
|
31866
32490
|
try {
|
|
31867
|
-
if (preserveDirs?.has(
|
|
32491
|
+
if (preserveDirs?.has(path3.resolve(fullPath)))
|
|
31868
32492
|
return;
|
|
31869
|
-
const stat = await
|
|
32493
|
+
const stat = await fs3.lstat(fullPath);
|
|
31870
32494
|
if (!stat.isDirectory())
|
|
31871
32495
|
return;
|
|
31872
32496
|
if (stat.mtimeMs < cutoff) {
|
|
31873
|
-
await
|
|
32497
|
+
await fs3.rm(fullPath, { recursive: true, force: true });
|
|
31874
32498
|
}
|
|
31875
32499
|
} catch (err) {
|
|
31876
32500
|
log?.warn?.(`agent-core: failed to clean attachment temp dir ${fullPath}: ${String(err)}`);
|
|
@@ -31882,7 +32506,7 @@ async function pruneAttachmentCache(rootDir, maxBytes, log, preserveDirs) {
|
|
|
31882
32506
|
return;
|
|
31883
32507
|
let entries;
|
|
31884
32508
|
try {
|
|
31885
|
-
entries = await
|
|
32509
|
+
entries = await fs3.readdir(rootDir, { withFileTypes: true });
|
|
31886
32510
|
} catch {
|
|
31887
32511
|
return;
|
|
31888
32512
|
}
|
|
@@ -31891,9 +32515,9 @@ async function pruneAttachmentCache(rootDir, maxBytes, log, preserveDirs) {
|
|
|
31891
32515
|
for (const entry of entries) {
|
|
31892
32516
|
if (!entry.isDirectory())
|
|
31893
32517
|
continue;
|
|
31894
|
-
const fullPath =
|
|
32518
|
+
const fullPath = path3.join(rootDir, entry.name);
|
|
31895
32519
|
try {
|
|
31896
|
-
const stat = await
|
|
32520
|
+
const stat = await fs3.lstat(fullPath);
|
|
31897
32521
|
if (!stat.isDirectory())
|
|
31898
32522
|
continue;
|
|
31899
32523
|
const size = await directorySize(fullPath);
|
|
@@ -31909,10 +32533,10 @@ async function pruneAttachmentCache(rootDir, maxBytes, log, preserveDirs) {
|
|
|
31909
32533
|
for (const dir of dirs) {
|
|
31910
32534
|
if (total <= maxBytes)
|
|
31911
32535
|
break;
|
|
31912
|
-
if (preserveDirs?.has(
|
|
32536
|
+
if (preserveDirs?.has(path3.resolve(dir.path)))
|
|
31913
32537
|
continue;
|
|
31914
32538
|
try {
|
|
31915
|
-
await
|
|
32539
|
+
await fs3.rm(dir.path, { recursive: true, force: true });
|
|
31916
32540
|
total -= dir.size;
|
|
31917
32541
|
} catch (err) {
|
|
31918
32542
|
log?.warn?.(`agent-core: failed to prune attachment cache dir ${dir.path}: ${String(err)}`);
|
|
@@ -31921,12 +32545,12 @@ async function pruneAttachmentCache(rootDir, maxBytes, log, preserveDirs) {
|
|
|
31921
32545
|
}
|
|
31922
32546
|
async function directorySize(dirPath) {
|
|
31923
32547
|
let total = 0;
|
|
31924
|
-
const entries = await
|
|
32548
|
+
const entries = await fs3.readdir(dirPath, { withFileTypes: true });
|
|
31925
32549
|
for (const entry of entries) {
|
|
31926
|
-
const fullPath =
|
|
32550
|
+
const fullPath = path3.join(dirPath, entry.name);
|
|
31927
32551
|
let stat;
|
|
31928
32552
|
try {
|
|
31929
|
-
stat = await
|
|
32553
|
+
stat = await fs3.lstat(fullPath);
|
|
31930
32554
|
} catch {
|
|
31931
32555
|
continue;
|
|
31932
32556
|
}
|
|
@@ -31941,10 +32565,10 @@ async function directorySize(dirPath) {
|
|
|
31941
32565
|
return total;
|
|
31942
32566
|
}
|
|
31943
32567
|
function activeDirsForRoot(rootDir) {
|
|
31944
|
-
const root =
|
|
32568
|
+
const root = path3.resolve(rootDir);
|
|
31945
32569
|
const dirs = /* @__PURE__ */ new Set();
|
|
31946
32570
|
for (const dir of activeAttachmentDirs) {
|
|
31947
|
-
if (dir === root || dir.startsWith(`${root}${
|
|
32571
|
+
if (dir === root || dir.startsWith(`${root}${path3.sep}`)) {
|
|
31948
32572
|
dirs.add(dir);
|
|
31949
32573
|
}
|
|
31950
32574
|
}
|
|
@@ -32041,9 +32665,9 @@ async function writeResponseToFileWithLimit(res, filePath, maxBytes, rootDir) {
|
|
|
32041
32665
|
}
|
|
32042
32666
|
writtenStat = await file.stat();
|
|
32043
32667
|
await closeFile();
|
|
32044
|
-
await localDirectoryStatInsideRoot(
|
|
32668
|
+
await localDirectoryStatInsideRoot(path3.dirname(filePath), rootDir);
|
|
32045
32669
|
await assertLocalFileIdentity(tmpPath, rootDir, writtenStat);
|
|
32046
|
-
await
|
|
32670
|
+
await fs3.rename(tmpPath, filePath);
|
|
32047
32671
|
completed = true;
|
|
32048
32672
|
return written;
|
|
32049
32673
|
} finally {
|
|
@@ -32059,9 +32683,9 @@ async function writeResponseToFileWithLimit(res, filePath, maxBytes, rootDir) {
|
|
|
32059
32683
|
}
|
|
32060
32684
|
}
|
|
32061
32685
|
function localFileName(attachmentId, fileName, mimeType) {
|
|
32062
|
-
const safeName = sanitizePathSegment(
|
|
32063
|
-
const ext =
|
|
32064
|
-
const stem =
|
|
32686
|
+
const safeName = sanitizePathSegment(path3.basename(fileName || attachmentId));
|
|
32687
|
+
const ext = path3.extname(safeName) || extensionForMime(mimeType);
|
|
32688
|
+
const stem = path3.basename(safeName, path3.extname(safeName)) || attachmentId;
|
|
32065
32689
|
return `${sanitizePathSegment(attachmentId)}-${stem}${ext}`;
|
|
32066
32690
|
}
|
|
32067
32691
|
function extensionForMime(mimeType) {
|
|
@@ -32127,7 +32751,7 @@ function parseContentLength(value) {
|
|
|
32127
32751
|
// dist/gateway.js
|
|
32128
32752
|
import * as crypto2 from "node:crypto";
|
|
32129
32753
|
import * as os2 from "node:os";
|
|
32130
|
-
import * as
|
|
32754
|
+
import * as path7 from "node:path";
|
|
32131
32755
|
|
|
32132
32756
|
// dist/runtime.js
|
|
32133
32757
|
var runtime = null;
|
|
@@ -32185,15 +32809,15 @@ function buildOrchestratorSessionKey(accountId) {
|
|
|
32185
32809
|
}
|
|
32186
32810
|
|
|
32187
32811
|
// dist/config-manager.js
|
|
32188
|
-
import * as
|
|
32189
|
-
import * as
|
|
32812
|
+
import * as fs4 from "node:fs";
|
|
32813
|
+
import * as path4 from "node:path";
|
|
32190
32814
|
var CACHE_FILENAME = "parall-platform-config.json";
|
|
32191
32815
|
function cachePath(stateDir) {
|
|
32192
|
-
return
|
|
32816
|
+
return path4.join(stateDir, CACHE_FILENAME);
|
|
32193
32817
|
}
|
|
32194
32818
|
function loadCachedConfig(stateDir) {
|
|
32195
32819
|
try {
|
|
32196
|
-
const raw =
|
|
32820
|
+
const raw = fs4.readFileSync(cachePath(stateDir), "utf-8");
|
|
32197
32821
|
return JSON.parse(raw);
|
|
32198
32822
|
} catch {
|
|
32199
32823
|
return null;
|
|
@@ -32207,14 +32831,14 @@ function saveCachedConfig(stateDir, config) {
|
|
|
32207
32831
|
};
|
|
32208
32832
|
const filePath = cachePath(stateDir);
|
|
32209
32833
|
const tmpPath = `${filePath}.tmp`;
|
|
32210
|
-
|
|
32211
|
-
|
|
32212
|
-
|
|
32834
|
+
fs4.mkdirSync(path4.dirname(filePath), { recursive: true });
|
|
32835
|
+
fs4.writeFileSync(tmpPath, JSON.stringify(cached, null, 2), "utf-8");
|
|
32836
|
+
fs4.renameSync(tmpPath, filePath);
|
|
32213
32837
|
}
|
|
32214
32838
|
function applyToOpenClawConfig(configPath, platformConfig, credentials) {
|
|
32215
32839
|
let existing = {};
|
|
32216
32840
|
try {
|
|
32217
|
-
const raw =
|
|
32841
|
+
const raw = fs4.readFileSync(configPath, "utf-8");
|
|
32218
32842
|
existing = JSON.parse(raw);
|
|
32219
32843
|
} catch {
|
|
32220
32844
|
}
|
|
@@ -32286,9 +32910,9 @@ function applyToOpenClawConfig(configPath, platformConfig, credentials) {
|
|
|
32286
32910
|
agents.defaults = cleanedExisting;
|
|
32287
32911
|
existing.agents = agents;
|
|
32288
32912
|
const tmpPath = `${configPath}.tmp`;
|
|
32289
|
-
|
|
32290
|
-
|
|
32291
|
-
|
|
32913
|
+
fs4.mkdirSync(path4.dirname(configPath), { recursive: true });
|
|
32914
|
+
fs4.writeFileSync(tmpPath, JSON.stringify(existing, null, 2), "utf-8");
|
|
32915
|
+
fs4.renameSync(tmpPath, configPath);
|
|
32292
32916
|
}
|
|
32293
32917
|
async function fetchAndApplyPlatformConfig(opts) {
|
|
32294
32918
|
const { client, stateDir, configPath, credentials, log } = opts;
|
|
@@ -32327,7 +32951,7 @@ async function fetchAndApplyPlatformConfig(opts) {
|
|
|
32327
32951
|
|
|
32328
32952
|
// dist/wiki-helper.js
|
|
32329
32953
|
import { spawn, spawnSync } from "node:child_process";
|
|
32330
|
-
import
|
|
32954
|
+
import path5 from "node:path";
|
|
32331
32955
|
var DEFAULT_SYNC_TIMEOUT_MS = 9e4;
|
|
32332
32956
|
var DEFAULT_WATCH_INTERVAL_SEC = 30;
|
|
32333
32957
|
function isCommandMissing(error) {
|
|
@@ -32342,7 +32966,7 @@ function resolveParallCli() {
|
|
|
32342
32966
|
return _cli;
|
|
32343
32967
|
}
|
|
32344
32968
|
function resolveMountRoot(stateDir) {
|
|
32345
|
-
return process.env.PRLL_WIKI_MOUNT_ROOT?.trim() ||
|
|
32969
|
+
return process.env.PRLL_WIKI_MOUNT_ROOT?.trim() || path5.join(stateDir, "workspace");
|
|
32346
32970
|
}
|
|
32347
32971
|
function resolveWatchIntervalSec() {
|
|
32348
32972
|
const raw = process.env.PRLL_WIKI_REFRESH_INTERVAL_SEC?.trim();
|
|
@@ -32458,7 +33082,7 @@ async function startWikiHelper(params) {
|
|
|
32458
33082
|
// dist/oc-session.js
|
|
32459
33083
|
import { randomUUID } from "node:crypto";
|
|
32460
33084
|
import { existsSync as existsSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
32461
|
-
import { join as
|
|
33085
|
+
import { join as join4, resolve as resolve2 } from "node:path";
|
|
32462
33086
|
var CURRENT_SESSION_VERSION = 3;
|
|
32463
33087
|
function generateId(existing) {
|
|
32464
33088
|
for (let i = 0; i < 100; i++) {
|
|
@@ -32604,7 +33228,7 @@ var SessionManager = class _SessionManager {
|
|
|
32604
33228
|
this.leafId = null;
|
|
32605
33229
|
this.flushed = false;
|
|
32606
33230
|
const ts = timestamp.replace(/[:.]/g, "-");
|
|
32607
|
-
this.sessionFile =
|
|
33231
|
+
this.sessionFile = join4(this.sessionDir, `${ts}_${this.sessionId}.jsonl`);
|
|
32608
33232
|
}
|
|
32609
33233
|
buildIndex() {
|
|
32610
33234
|
this.byId.clear();
|
|
@@ -32651,14 +33275,14 @@ var SessionManager = class _SessionManager {
|
|
|
32651
33275
|
}
|
|
32652
33276
|
// -- Branching -------------------------------------------------------------
|
|
32653
33277
|
getBranch(fromId) {
|
|
32654
|
-
const
|
|
33278
|
+
const path8 = [];
|
|
32655
33279
|
const startId = fromId ?? this.leafId;
|
|
32656
33280
|
let current = startId ? this.byId.get(startId) : void 0;
|
|
32657
33281
|
while (current) {
|
|
32658
|
-
|
|
33282
|
+
path8.unshift(current);
|
|
32659
33283
|
current = current.parentId ? this.byId.get(current.parentId) : void 0;
|
|
32660
33284
|
}
|
|
32661
|
-
return
|
|
33285
|
+
return path8;
|
|
32662
33286
|
}
|
|
32663
33287
|
createBranchedSession(leafId) {
|
|
32664
33288
|
const branch = this.getBranch(leafId);
|
|
@@ -32668,7 +33292,7 @@ var SessionManager = class _SessionManager {
|
|
|
32668
33292
|
const newId = randomUUID();
|
|
32669
33293
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
32670
33294
|
const ts = timestamp.replace(/[:.]/g, "-");
|
|
32671
|
-
const newFile =
|
|
33295
|
+
const newFile = join4(this.sessionDir, `${ts}_${newId}.jsonl`);
|
|
32672
33296
|
const header = {
|
|
32673
33297
|
type: "session",
|
|
32674
33298
|
version: CURRENT_SESSION_VERSION,
|
|
@@ -32714,50 +33338,50 @@ var SessionManager = class _SessionManager {
|
|
|
32714
33338
|
return newFile;
|
|
32715
33339
|
}
|
|
32716
33340
|
// -- Factory ---------------------------------------------------------------
|
|
32717
|
-
static open(
|
|
32718
|
-
const entries = loadEntries(
|
|
33341
|
+
static open(path8) {
|
|
33342
|
+
const entries = loadEntries(path8);
|
|
32719
33343
|
const header = entries.find((e) => e.type === "session");
|
|
32720
33344
|
const cwd = header?.cwd ?? process.cwd();
|
|
32721
|
-
const dir = resolve2(
|
|
32722
|
-
return new _SessionManager(cwd, dir,
|
|
33345
|
+
const dir = resolve2(path8, "..");
|
|
33346
|
+
return new _SessionManager(cwd, dir, path8);
|
|
32723
33347
|
}
|
|
32724
33348
|
};
|
|
32725
33349
|
|
|
32726
33350
|
// dist/fork.js
|
|
32727
|
-
import * as
|
|
32728
|
-
import * as
|
|
33351
|
+
import * as fs5 from "node:fs";
|
|
33352
|
+
import * as path6 from "node:path";
|
|
32729
33353
|
import * as crypto from "node:crypto";
|
|
32730
33354
|
function readStoreEntry(sessionsDir, sessionKey) {
|
|
32731
|
-
const storeFile =
|
|
33355
|
+
const storeFile = path6.join(sessionsDir, "sessions.json");
|
|
32732
33356
|
try {
|
|
32733
|
-
const store = JSON.parse(
|
|
33357
|
+
const store = JSON.parse(fs5.readFileSync(storeFile, "utf-8"));
|
|
32734
33358
|
return store[sessionKey] ?? store[sessionKey.toLowerCase()] ?? null;
|
|
32735
33359
|
} catch {
|
|
32736
33360
|
return null;
|
|
32737
33361
|
}
|
|
32738
33362
|
}
|
|
32739
33363
|
function writeStoreEntry(sessionsDir, sessionKey, entry) {
|
|
32740
|
-
const storeFile =
|
|
33364
|
+
const storeFile = path6.join(sessionsDir, "sessions.json");
|
|
32741
33365
|
try {
|
|
32742
33366
|
let store = {};
|
|
32743
33367
|
try {
|
|
32744
|
-
store = JSON.parse(
|
|
33368
|
+
store = JSON.parse(fs5.readFileSync(storeFile, "utf-8"));
|
|
32745
33369
|
} catch {
|
|
32746
33370
|
}
|
|
32747
33371
|
store[sessionKey.toLowerCase()] = entry;
|
|
32748
|
-
|
|
33372
|
+
fs5.writeFileSync(storeFile, JSON.stringify(store, null, 2), { encoding: "utf-8" });
|
|
32749
33373
|
return true;
|
|
32750
33374
|
} catch {
|
|
32751
33375
|
return false;
|
|
32752
33376
|
}
|
|
32753
33377
|
}
|
|
32754
33378
|
function deleteStoreEntry(sessionsDir, sessionKey) {
|
|
32755
|
-
const storeFile =
|
|
33379
|
+
const storeFile = path6.join(sessionsDir, "sessions.json");
|
|
32756
33380
|
try {
|
|
32757
|
-
const store = JSON.parse(
|
|
33381
|
+
const store = JSON.parse(fs5.readFileSync(storeFile, "utf-8"));
|
|
32758
33382
|
delete store[sessionKey];
|
|
32759
33383
|
delete store[sessionKey.toLowerCase()];
|
|
32760
|
-
|
|
33384
|
+
fs5.writeFileSync(storeFile, JSON.stringify(store, null, 2), { encoding: "utf-8" });
|
|
32761
33385
|
} catch {
|
|
32762
33386
|
}
|
|
32763
33387
|
}
|
|
@@ -32769,17 +33393,17 @@ function resolveTranscriptFile(sessionsDir, sessionKey) {
|
|
|
32769
33393
|
if (!entry?.sessionId)
|
|
32770
33394
|
return null;
|
|
32771
33395
|
if (entry.sessionFile) {
|
|
32772
|
-
const resolved =
|
|
32773
|
-
if (
|
|
33396
|
+
const resolved = path6.isAbsolute(entry.sessionFile) ? entry.sessionFile : path6.join(sessionsDir, entry.sessionFile);
|
|
33397
|
+
if (fs5.existsSync(resolved))
|
|
32774
33398
|
return resolved;
|
|
32775
33399
|
}
|
|
32776
|
-
const conventional =
|
|
32777
|
-
if (
|
|
33400
|
+
const conventional = path6.join(sessionsDir, `${entry.sessionId}.jsonl`);
|
|
33401
|
+
if (fs5.existsSync(conventional))
|
|
32778
33402
|
return conventional;
|
|
32779
33403
|
try {
|
|
32780
|
-
const files =
|
|
33404
|
+
const files = fs5.readdirSync(sessionsDir);
|
|
32781
33405
|
const match = files.find((file) => file.includes(entry.sessionId) && file.endsWith(".jsonl"));
|
|
32782
|
-
return match ?
|
|
33406
|
+
return match ? path6.join(sessionsDir, match) : null;
|
|
32783
33407
|
} catch {
|
|
32784
33408
|
return null;
|
|
32785
33409
|
}
|
|
@@ -32790,7 +33414,7 @@ function resolveSessionId(sessionsDir, sessionKey) {
|
|
|
32790
33414
|
}
|
|
32791
33415
|
function forkOrchestratorSession(opts) {
|
|
32792
33416
|
const { orchestratorSessionKey, accountId, transcriptFile, sessionsDir } = opts;
|
|
32793
|
-
if (!
|
|
33417
|
+
if (!fs5.existsSync(transcriptFile))
|
|
32794
33418
|
return null;
|
|
32795
33419
|
try {
|
|
32796
33420
|
const manager = SessionManager.open(transcriptFile);
|
|
@@ -32810,7 +33434,7 @@ function forkOrchestratorSession(opts) {
|
|
|
32810
33434
|
sessionId = crypto.randomUUID();
|
|
32811
33435
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
32812
33436
|
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
32813
|
-
sessionFile =
|
|
33437
|
+
sessionFile = path6.join(manager.getSessionDir(), `${fileTimestamp}_${sessionId}.jsonl`);
|
|
32814
33438
|
const header = {
|
|
32815
33439
|
type: "session",
|
|
32816
33440
|
version: CURRENT_SESSION_VERSION,
|
|
@@ -32819,7 +33443,7 @@ function forkOrchestratorSession(opts) {
|
|
|
32819
33443
|
cwd: manager.getCwd(),
|
|
32820
33444
|
parentSession: transcriptFile
|
|
32821
33445
|
};
|
|
32822
|
-
|
|
33446
|
+
fs5.writeFileSync(sessionFile, `${JSON.stringify(header)}
|
|
32823
33447
|
`, {
|
|
32824
33448
|
encoding: "utf-8",
|
|
32825
33449
|
mode: 384,
|
|
@@ -32829,7 +33453,7 @@ function forkOrchestratorSession(opts) {
|
|
|
32829
33453
|
const forkSessionKey = `${orchestratorSessionKey}:fork:${sessionId}`;
|
|
32830
33454
|
const wrote = writeStoreEntry(sessionsDir, forkSessionKey, {
|
|
32831
33455
|
sessionId,
|
|
32832
|
-
sessionFile:
|
|
33456
|
+
sessionFile: path6.relative(sessionsDir, sessionFile),
|
|
32833
33457
|
updatedAt: Date.now(),
|
|
32834
33458
|
spawnedBy: orchestratorSessionKey,
|
|
32835
33459
|
parentSessionKey: orchestratorSessionKey,
|
|
@@ -32844,8 +33468,8 @@ function forkOrchestratorSession(opts) {
|
|
|
32844
33468
|
}
|
|
32845
33469
|
function cleanupForkSession(opts) {
|
|
32846
33470
|
try {
|
|
32847
|
-
if (
|
|
32848
|
-
|
|
33471
|
+
if (fs5.existsSync(opts.sessionFile)) {
|
|
33472
|
+
fs5.unlinkSync(opts.sessionFile);
|
|
32849
33473
|
}
|
|
32850
33474
|
} catch {
|
|
32851
33475
|
}
|
|
@@ -33155,8 +33779,8 @@ var parallGateway = {
|
|
|
33155
33779
|
const telemetry = await initAgentTelemetry("parall-openclaw-agent", "openclaw");
|
|
33156
33780
|
const otelLog = createOtelLogger("agent", "openclaw-agent");
|
|
33157
33781
|
try {
|
|
33158
|
-
const stateDir = process.env.OPENCLAW_STATE_DIR ||
|
|
33159
|
-
const openclawConfigPath =
|
|
33782
|
+
const stateDir = process.env.OPENCLAW_STATE_DIR || path7.join(process.env.HOME || "/data", ".openclaw");
|
|
33783
|
+
const openclawConfigPath = path7.join(stateDir, "openclaw.json");
|
|
33160
33784
|
const configManagerOpts = {
|
|
33161
33785
|
client,
|
|
33162
33786
|
stateDir,
|
|
@@ -33192,7 +33816,7 @@ var parallGateway = {
|
|
|
33192
33816
|
wsUrl
|
|
33193
33817
|
});
|
|
33194
33818
|
const orchestratorKey = buildOrchestratorSessionKey(ctx.accountId);
|
|
33195
|
-
const sessionsDir =
|
|
33819
|
+
const sessionsDir = path7.join(stateDir, "agents", "main", "sessions");
|
|
33196
33820
|
const workspaceDir = process.cwd();
|
|
33197
33821
|
ensureLocalAttachmentGitExclude(workspaceDir);
|
|
33198
33822
|
const dispatchAdapter = createOpenClawDispatchAdapter({
|