@parall/parall 1.36.1 → 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/hooks.js +2 -2
- package/dist/index.bundle.mjs +1128 -236
- package/package.json +3 -3
- package/skills/parall-platform/SKILL.md +27 -6
- package/src/hooks.ts +2 -2
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();
|
|
@@ -26662,6 +26672,17 @@ function buildEventBody(event) {
|
|
|
26662
26672
|
if (event.attachedUri)
|
|
26663
26673
|
lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
|
|
26664
26674
|
lines.push("", event.body);
|
|
26675
|
+
} else if (event.type === "channel_message") {
|
|
26676
|
+
lines.push(`[Event: channel.message]`);
|
|
26677
|
+
const providerLabel = sanitizeMeta(event.channelProvider ?? "external IM");
|
|
26678
|
+
const convLabel = event.channelExternalConversationId ? `${sanitizeMeta(event.channelExternalConversationId)} (${sanitizeMeta(event.channelConversationType ?? "conversation")})` : sanitizeMeta(event.channelConversationType ?? "conversation");
|
|
26679
|
+
lines.push(`[Channel: ${providerLabel} | conversation: ${convLabel}]`);
|
|
26680
|
+
lines.push(`[From: ${sanitizeMeta(event.senderName)} (external user, not a Parall member)]`);
|
|
26681
|
+
if (event.channelExternalMessageId) {
|
|
26682
|
+
lines.push(`[External message ID: ${sanitizeMeta(event.channelExternalMessageId)}]`);
|
|
26683
|
+
}
|
|
26684
|
+
lines.push(`[Audience: this conversation lives on ${providerLabel}, OUTSIDE Parall. Readers cannot open prll:// links, Parall cards, or internal attachments \u2014 never include them in replies. Write plain conversational text.]`);
|
|
26685
|
+
lines.push("", event.body);
|
|
26665
26686
|
} else if (event.type === "external_trigger") {
|
|
26666
26687
|
lines.push(`[Event: external.trigger]`);
|
|
26667
26688
|
lines.push(`[Trigger: prll://${event.targetId}]`);
|
|
@@ -26703,7 +26724,7 @@ function buildSendMessageHint(event) {
|
|
|
26703
26724
|
}
|
|
26704
26725
|
if (event.targetId.startsWith("cht_")) {
|
|
26705
26726
|
return `
|
|
26706
|
-
<system-reminder>To reply, run
|
|
26727
|
+
<system-reminder>To reply, run \`parall messages send prll://${event.targetId} --text-file - <<'EOF'\` \u2026 \`EOF\` \u2014 the quoted heredoc keeps \`$\`, backticks and apostrophes literal (plain \`--text "$1,000"\` sends \`,000\`). Your plain text output is not delivered to the chat.</system-reminder>`;
|
|
26707
26728
|
}
|
|
26708
26729
|
if (event.targetId.startsWith("tsk_")) {
|
|
26709
26730
|
return `
|
|
@@ -26712,6 +26733,14 @@ function buildSendMessageHint(event) {
|
|
|
26712
26733
|
if (event.targetId.startsWith("sch_")) {
|
|
26713
26734
|
return `
|
|
26714
26735
|
<system-reminder>To communicate, use the CLI: \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
|
|
26736
|
+
}
|
|
26737
|
+
if (event.type === "channel_message") {
|
|
26738
|
+
const clipLabel = event.channelProvider ? `the \`${event.channelProvider}\` clip's` : "your channel provider clip's";
|
|
26739
|
+
const apiLabel = event.channelProvider ?? "external platform";
|
|
26740
|
+
const target = event.channelExternalConversationId ? `{"chat_id": "${event.channelExternalConversationId}", "text": "..."}` : `{"chat_id": "<conversation id>", "text": "..."}`;
|
|
26741
|
+
const threadAlt = event.channelExternalMessageId ? ` To reply in-thread to this specific message, use {"message_id": "${event.channelExternalMessageId}", "text": "..."} instead.` : "";
|
|
26742
|
+
return `
|
|
26743
|
+
<system-reminder>To reply, invoke ${clipLabel} \`send_message\` command with ${target} \u2014 your plain text output is NOT delivered to the external conversation.${threadAlt} The same clip's \`call\` command reaches the wider ${apiLabel} API when needed.</system-reminder>`;
|
|
26715
26744
|
}
|
|
26716
26745
|
if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
|
|
26717
26746
|
return `
|
|
@@ -26919,6 +26948,8 @@ Or upload first and reuse across chats:
|
|
|
26919
26948
|
parall messages send prll://cht_aaa --attachment att_yyy --text "Report"
|
|
26920
26949
|
parall messages send prll://cht_bbb --attachment att_yyy --text "FYI"
|
|
26921
26950
|
|
|
26951
|
+
The \`--text\` captions above are safe short literals. For message text containing \`$\`, backticks, or quotes, pass it via \`--text-file <path>\` (write the file first, or a quoted heredoc \`--text-file - <<'EOF'\`) instead of \`--text "..."\` \u2014 inside double quotes the shell turns \`$1,000\` into \`,000\` and executes \`$(...)\`.
|
|
26952
|
+
|
|
26922
26953
|
### When to reference
|
|
26923
26954
|
|
|
26924
26955
|
- **Origin** \u2014 always link the message or task that triggered your work
|
|
@@ -27000,8 +27031,8 @@ function isParallNoReplyCommand(command) {
|
|
|
27000
27031
|
|
|
27001
27032
|
// ../agent-core/dist/gateway-base.js
|
|
27002
27033
|
import * as os from "node:os";
|
|
27003
|
-
import * as
|
|
27004
|
-
import * as
|
|
27034
|
+
import * as fs2 from "node:fs";
|
|
27035
|
+
import * as path2 from "node:path";
|
|
27005
27036
|
|
|
27006
27037
|
// ../sdk/dist/types.js
|
|
27007
27038
|
var MENTION_ALL_USER_ID = "all";
|
|
@@ -27033,6 +27064,7 @@ var ENDPOINTS = {
|
|
|
27033
27064
|
// Org-scoped
|
|
27034
27065
|
ORG: (orgId) => `${API_BASE}/orgs/${orgId}`,
|
|
27035
27066
|
ORG_MEMBERS: (orgId) => `${API_BASE}/orgs/${orgId}/members`,
|
|
27067
|
+
ORG_MEMBERS_FORMER: (orgId) => `${API_BASE}/orgs/${orgId}/members/former`,
|
|
27036
27068
|
TEAMS: (orgId) => `${API_BASE}/orgs/${orgId}/teams`,
|
|
27037
27069
|
ORG_MEMBERS_ONLINE: (orgId) => `${API_BASE}/orgs/${orgId}/members/online`,
|
|
27038
27070
|
ORG_MEMBER: (orgId, userId) => `${API_BASE}/orgs/${orgId}/members/${userId}`,
|
|
@@ -27182,6 +27214,19 @@ var ENDPOINTS = {
|
|
|
27182
27214
|
EXTERNAL_TRIGGER: (orgId, triggerId) => `${API_BASE}/orgs/${orgId}/external-triggers/${triggerId}`,
|
|
27183
27215
|
EXTERNAL_TRIGGER_RUNS: (orgId) => `${API_BASE}/orgs/${orgId}/external-trigger-runs`,
|
|
27184
27216
|
EXTERNAL_TRIGGER_RUN: (orgId, runId) => `${API_BASE}/orgs/${orgId}/external-trigger-runs/${runId}`,
|
|
27217
|
+
// External IM channel (org-scoped, platform-mediated Feishu/Slack)
|
|
27218
|
+
CHANNEL_CONNECTIONS: (orgId) => `${API_BASE}/orgs/${orgId}/channel-connections`,
|
|
27219
|
+
CHANNEL_CONNECTION: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}`,
|
|
27220
|
+
CHANNEL_CONNECTION_CREDENTIALS: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/credentials`,
|
|
27221
|
+
CHANNEL_CONNECTION_INGRESS_TOKEN_REGENERATE: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/ingress-token/regenerate`,
|
|
27222
|
+
CHANNEL_CONNECTION_CONVERSATIONS: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/conversations`,
|
|
27223
|
+
CHANNEL_CONVERSATION: (orgId, conversationId) => `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}`,
|
|
27224
|
+
CHANNEL_CONVERSATION_MESSAGES: (orgId, conversationId) => `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}/messages`,
|
|
27225
|
+
CHANNEL_CONVERSATION_SESSION: (orgId, conversationId) => `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}/session`,
|
|
27226
|
+
CHANNEL_MESSAGE: (orgId, messageId) => `${API_BASE}/orgs/${orgId}/channel-messages/${messageId}`,
|
|
27227
|
+
CHANNEL_PROVISIONING: (orgId) => `${API_BASE}/orgs/${orgId}/channel-provisioning`,
|
|
27228
|
+
CHANNEL_PROVISIONING_SESSION: (orgId, sessionId) => `${API_BASE}/orgs/${orgId}/channel-provisioning/${sessionId}`,
|
|
27229
|
+
CHANNEL_PROVISIONING_CANCEL: (orgId, sessionId) => `${API_BASE}/orgs/${orgId}/channel-provisioning/${sessionId}/cancel`,
|
|
27185
27230
|
// Invitations (org-scoped, admin)
|
|
27186
27231
|
ORG_INVITATIONS: (orgId) => `${API_BASE}/orgs/${orgId}/invitations`,
|
|
27187
27232
|
ORG_INVITATION: (orgId, invId) => `${API_BASE}/orgs/${orgId}/invitations/${invId}`,
|
|
@@ -27199,9 +27244,16 @@ var ENDPOINTS = {
|
|
|
27199
27244
|
INVITE_LINK_JOIN: `${API_BASE}/invite-link/join`,
|
|
27200
27245
|
// Wikis (org-scoped, served by wiki-service)
|
|
27201
27246
|
WIKIS: (orgId) => `${WIKI_BASE}/orgs/${orgId}/wikis`,
|
|
27247
|
+
// Recycle bin — soft-deleted wikis (owner only). Must precede WIKI in the
|
|
27248
|
+
// backend router so `deleted` isn't captured as a {wikiId}; the SDK builder
|
|
27249
|
+
// is just a string.
|
|
27250
|
+
WIKIS_DELETED: (orgId) => `${WIKI_BASE}/orgs/${orgId}/wikis/deleted`,
|
|
27251
|
+
// Detail URL — also reused for DELETE (soft-delete) and `${WIKI}/restore`.
|
|
27202
27252
|
WIKI: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}`,
|
|
27253
|
+
WIKI_RESTORE: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/restore`,
|
|
27203
27254
|
WIKI_TREE: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/tree`,
|
|
27204
27255
|
WIKI_BLOB: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/blob`,
|
|
27256
|
+
WIKI_COPY: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/copy`,
|
|
27205
27257
|
WIKI_NODE_SECTIONS: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/node-sections`,
|
|
27206
27258
|
WIKI_SEARCH: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/search`,
|
|
27207
27259
|
WIKI_PAGE_INDEX: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/page-index`,
|
|
@@ -27255,6 +27307,11 @@ var ENDPOINTS = {
|
|
|
27255
27307
|
DISPATCH_ACK_BY_ID: (orgId, id) => `${API_BASE}/orgs/${orgId}/dispatch/${id}/ack`,
|
|
27256
27308
|
DISPATCH_EXPIRE: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/expire`,
|
|
27257
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`,
|
|
27258
27315
|
// Unread
|
|
27259
27316
|
UNREAD: `${API_BASE}/me/unread`,
|
|
27260
27317
|
ORG_UNREAD: (orgId) => `${API_BASE}/orgs/${orgId}/unread`,
|
|
@@ -27284,6 +27341,14 @@ var ENDPOINTS = {
|
|
|
27284
27341
|
BILLING_AUTO_RELOAD: (orgId) => `${API_BASE}/orgs/${orgId}/billing/auto-reload`,
|
|
27285
27342
|
BILLING_SETUP_INTENT: (orgId) => `${API_BASE}/orgs/${orgId}/billing/setup-intent`,
|
|
27286
27343
|
COMPUTE_PRICING: () => `${API_BASE}/billing/compute-pricing`,
|
|
27344
|
+
// Runtime capability table (public, no auth) — SSOT for the create/settings
|
|
27345
|
+
// interlock: per-runtime compute modes, native model family, cross-family
|
|
27346
|
+
// availability, and recommended model.
|
|
27347
|
+
RUNTIMES: () => `${API_BASE}/runtimes`,
|
|
27348
|
+
// Model catalog (public, no auth) — served from the server's DB-backed
|
|
27349
|
+
// snapshot. Optional include_model keeps an agent's pinned hidden legacy
|
|
27350
|
+
// model representable in settings pickers.
|
|
27351
|
+
MODELS: (includeModel) => includeModel ? `${API_BASE}/models?include_model=${encodeURIComponent(includeModel)}` : `${API_BASE}/models`,
|
|
27287
27352
|
// ADMIN_GRANTS intentionally NOT exported here — it sits under the
|
|
27288
27353
|
// unauthenticated `/internal/admin/*` surface and must not bleed into the
|
|
27289
27354
|
// public SDK. Admin dashboard hits the URL directly from its own client.
|
|
@@ -27296,6 +27361,7 @@ var ENDPOINTS = {
|
|
|
27296
27361
|
AGENT_CLIP: (orgId, agentId, clipId) => `${CLIP_BASE}/orgs/${orgId}/agents/${agentId}/clips/${clipId}`,
|
|
27297
27362
|
CLIP_INVOKE: (orgId) => `${CLIP_BASE}/orgs/${orgId}/invoke`,
|
|
27298
27363
|
CLIP_ONLINE: (orgId) => `${CLIP_BASE}/orgs/${orgId}/online`,
|
|
27364
|
+
BROWSER_RUNTIME_STATUS: (orgId) => `${CLIP_BASE}/orgs/${orgId}/browser-runtime/status`,
|
|
27299
27365
|
BROWSER_PROFILES: (orgId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles`,
|
|
27300
27366
|
BROWSER_PROFILE: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}`,
|
|
27301
27367
|
BROWSER_PROFILE_OPEN: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}/open`,
|
|
@@ -27349,10 +27415,13 @@ var WS_EVENTS = {
|
|
|
27349
27415
|
INVITATION_REVOKED: "invitation.revoked",
|
|
27350
27416
|
ORG_JOIN_REQUEST_NEW: "org.join_request.new",
|
|
27351
27417
|
ORG_INVITE_LINK_JOINED: "org.invite_link.joined",
|
|
27418
|
+
ORG_MEMBER_REMOVED: "org.member.removed",
|
|
27352
27419
|
AGENT_CONFIG_UPDATE: "agent_config.update",
|
|
27353
27420
|
PRESENCE_UPDATE: "presence.update",
|
|
27354
27421
|
WIKI_CHANGESET_CREATED: "wiki.changeset.created",
|
|
27355
27422
|
WIKI_CHANGESET_UPDATED: "wiki.changeset.updated",
|
|
27423
|
+
WIKI_DELETED: "wiki.deleted",
|
|
27424
|
+
WIKI_RESTORED: "wiki.restored",
|
|
27356
27425
|
COMMENT_CREATED: "comment.created",
|
|
27357
27426
|
COMMENT_UPDATED: "comment.updated",
|
|
27358
27427
|
COMMENT_DELETED: "comment.deleted",
|
|
@@ -27363,6 +27432,7 @@ var WS_EVENTS = {
|
|
|
27363
27432
|
READ_POSITION_UPDATED: "read_position.updated",
|
|
27364
27433
|
DISPATCH_NEW: "dispatch.new",
|
|
27365
27434
|
DISPATCH_RECEIVED: "dispatch.received",
|
|
27435
|
+
DISPATCH_RESOLVED: "dispatch.resolved",
|
|
27366
27436
|
SCHEDULE_CREATED: "schedule.created",
|
|
27367
27437
|
SCHEDULE_UPDATED: "schedule.updated",
|
|
27368
27438
|
SCHEDULE_DELETED: "schedule.deleted",
|
|
@@ -27380,6 +27450,7 @@ var WS_EVENTS = {
|
|
|
27380
27450
|
MACHINE_FILESYSTEM_BROWSE: "machine.filesystem.browse",
|
|
27381
27451
|
MACHINE_UPDATE: "machine.update",
|
|
27382
27452
|
MACHINE_CONFIG_UPDATED: "machine.config.updated",
|
|
27453
|
+
MACHINE_AGENT_CONFIG_UPDATED: "machine.agent_config.updated",
|
|
27383
27454
|
MACHINE_CLIP_SYNC: "machine.clip.sync",
|
|
27384
27455
|
MACHINE_BROWSER_PROFILE_LIFECYCLE: "machine.browser_profile.lifecycle",
|
|
27385
27456
|
MACHINE_BROWSER_PROFILE_VIEWER: "machine.browser_profile.viewer",
|
|
@@ -27457,8 +27528,8 @@ var ParallClient = class _ParallClient {
|
|
|
27457
27528
|
* is authoritative, so wiki vs api routing can't drift from how a caller
|
|
27458
27529
|
* happens to invoke the client.
|
|
27459
27530
|
*/
|
|
27460
|
-
baseUrlFor(
|
|
27461
|
-
return
|
|
27531
|
+
baseUrlFor(path8) {
|
|
27532
|
+
return path8.startsWith(WIKI_BASE) ? this.wikiBaseUrl : this.baseUrl;
|
|
27462
27533
|
}
|
|
27463
27534
|
setToken(token) {
|
|
27464
27535
|
this.token = token;
|
|
@@ -27485,10 +27556,10 @@ var ParallClient = class _ParallClient {
|
|
|
27485
27556
|
* REFRESH_THRESHOLD_S, refresh it **before** sending the request.
|
|
27486
27557
|
* No-op when the token is still fresh, missing, or un-parseable.
|
|
27487
27558
|
*/
|
|
27488
|
-
async ensureFreshToken(
|
|
27559
|
+
async ensureFreshToken(path8) {
|
|
27489
27560
|
if (!this.token || !this.getRefreshToken)
|
|
27490
27561
|
return;
|
|
27491
|
-
const pathSuffix =
|
|
27562
|
+
const pathSuffix = path8.replace(/^\/api\/v1/, "");
|
|
27492
27563
|
if (_ParallClient.AUTH_PATHS.has(pathSuffix))
|
|
27493
27564
|
return;
|
|
27494
27565
|
const exp = _ParallClient.decodeJwtExp(this.token);
|
|
@@ -27520,11 +27591,11 @@ var ParallClient = class _ParallClient {
|
|
|
27520
27591
|
this.refreshPromise = null;
|
|
27521
27592
|
}
|
|
27522
27593
|
}
|
|
27523
|
-
async request(method,
|
|
27594
|
+
async request(method, path8, body, query, retried = false, opts) {
|
|
27524
27595
|
if (!retried) {
|
|
27525
|
-
await this.ensureFreshToken(
|
|
27596
|
+
await this.ensureFreshToken(path8);
|
|
27526
27597
|
}
|
|
27527
|
-
let url = `${this.baseUrlFor(
|
|
27598
|
+
let url = `${this.baseUrlFor(path8)}${path8}`;
|
|
27528
27599
|
if (query) {
|
|
27529
27600
|
const params = new URLSearchParams();
|
|
27530
27601
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -27554,12 +27625,12 @@ var ParallClient = class _ParallClient {
|
|
|
27554
27625
|
throw _ParallClient.normalizeFetchError(err);
|
|
27555
27626
|
}
|
|
27556
27627
|
if (res.status === 401) {
|
|
27557
|
-
const pathSuffix =
|
|
27628
|
+
const pathSuffix = path8.replace(/^\/api\/v1/, "");
|
|
27558
27629
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
27559
27630
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
27560
27631
|
const refreshed = await this.tryRefresh();
|
|
27561
27632
|
if (refreshed) {
|
|
27562
|
-
return this.request(method,
|
|
27633
|
+
return this.request(method, path8, body, query, true, opts);
|
|
27563
27634
|
}
|
|
27564
27635
|
}
|
|
27565
27636
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -27570,6 +27641,7 @@ var ParallClient = class _ParallClient {
|
|
|
27570
27641
|
const rawErrorBody = await res.json().catch(() => ({}));
|
|
27571
27642
|
throw buildApiError(res, rawErrorBody);
|
|
27572
27643
|
}
|
|
27644
|
+
opts?.onStatus?.(res.status);
|
|
27573
27645
|
if (res.status === 204)
|
|
27574
27646
|
return void 0;
|
|
27575
27647
|
if (res.status === 202) {
|
|
@@ -27588,15 +27660,15 @@ var ParallClient = class _ParallClient {
|
|
|
27588
27660
|
* hit the 100 MiB cap, so a longer 5-minute timeout is used so a
|
|
27589
27661
|
* 50 MiB blob on a slow connection doesn't get chopped at 15 s.
|
|
27590
27662
|
*/
|
|
27591
|
-
async multipartRequest(method,
|
|
27663
|
+
async multipartRequest(method, path8, body, retried = false) {
|
|
27592
27664
|
if (!retried) {
|
|
27593
|
-
await this.ensureFreshToken(
|
|
27665
|
+
await this.ensureFreshToken(path8);
|
|
27594
27666
|
}
|
|
27595
27667
|
const { "Content-Type": _drop, ...headers } = this.buildHeaders();
|
|
27596
27668
|
void _drop;
|
|
27597
27669
|
let res;
|
|
27598
27670
|
try {
|
|
27599
|
-
res = await fetch(`${this.baseUrlFor(
|
|
27671
|
+
res = await fetch(`${this.baseUrlFor(path8)}${path8}`, {
|
|
27600
27672
|
method,
|
|
27601
27673
|
headers,
|
|
27602
27674
|
body,
|
|
@@ -27606,12 +27678,12 @@ var ParallClient = class _ParallClient {
|
|
|
27606
27678
|
throw _ParallClient.normalizeFetchError(err);
|
|
27607
27679
|
}
|
|
27608
27680
|
if (res.status === 401) {
|
|
27609
|
-
const pathSuffix =
|
|
27681
|
+
const pathSuffix = path8.replace(/^\/api\/v1/, "");
|
|
27610
27682
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
27611
27683
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
27612
27684
|
const refreshed = await this.tryRefresh();
|
|
27613
27685
|
if (refreshed) {
|
|
27614
|
-
return this.multipartRequest(method,
|
|
27686
|
+
return this.multipartRequest(method, path8, body, true);
|
|
27615
27687
|
}
|
|
27616
27688
|
}
|
|
27617
27689
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -27719,6 +27791,13 @@ var ParallClient = class _ParallClient {
|
|
|
27719
27791
|
const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS(orgId));
|
|
27720
27792
|
return res.data;
|
|
27721
27793
|
}
|
|
27794
|
+
/** User IDs of soft-removed (former) org members — for marking their avatar /
|
|
27795
|
+
* name as "left" where dormant relations still surface them (DMs, task
|
|
27796
|
+
* assignees, message history). */
|
|
27797
|
+
async getFormerMemberIds(orgId) {
|
|
27798
|
+
const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS_FORMER(orgId));
|
|
27799
|
+
return res.user_ids ?? [];
|
|
27800
|
+
}
|
|
27722
27801
|
async getTeams(orgId) {
|
|
27723
27802
|
const res = await this.request("GET", ENDPOINTS.TEAMS(orgId));
|
|
27724
27803
|
return res.data;
|
|
@@ -27896,6 +27975,21 @@ var ParallClient = class _ParallClient {
|
|
|
27896
27975
|
async sendMessage(orgId, chatId, req) {
|
|
27897
27976
|
return this.request("POST", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), req);
|
|
27898
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
|
+
}
|
|
27899
27993
|
async getMessages(orgId, chatId, params) {
|
|
27900
27994
|
return this.request("GET", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), void 0, params);
|
|
27901
27995
|
}
|
|
@@ -28207,20 +28301,25 @@ var ParallClient = class _ParallClient {
|
|
|
28207
28301
|
return res.data;
|
|
28208
28302
|
}
|
|
28209
28303
|
/** `POST /machines/me/browser-profiles/{profileId}/status` — report runtime status. */
|
|
28210
|
-
async reportBrowserProfileStatus(profileId, status, errorMsg) {
|
|
28304
|
+
async reportBrowserProfileStatus(profileId, status, errorMsg, generation) {
|
|
28211
28305
|
await this.request("POST", ENDPOINTS.MACHINES_ME_BROWSER_PROFILE_STATUS(profileId), {
|
|
28212
28306
|
status,
|
|
28213
|
-
...errorMsg ? { error_msg: errorMsg } : {}
|
|
28307
|
+
...errorMsg ? { error_msg: errorMsg } : {},
|
|
28308
|
+
...generation !== void 0 ? { generation } : {}
|
|
28214
28309
|
});
|
|
28215
28310
|
}
|
|
28216
28311
|
/**
|
|
28217
28312
|
* `POST /machines/me/health` — bump the Machine's `updated_at` to now and
|
|
28218
|
-
* report daemon state. The daemon
|
|
28219
|
-
*
|
|
28313
|
+
* report daemon state. The daemon calls this on startup and again whenever
|
|
28314
|
+
* periodic runtime re-detection produces a CHANGED result (there is no
|
|
28315
|
+
* fixed-cadence keepalive loop today — steady state posts nothing). All
|
|
28220
28316
|
* fields are optional and only persisted when changed:
|
|
28221
28317
|
* - `daemonVersion` — the running bundle/launcher version.
|
|
28222
28318
|
* - `selfUpdateCapable` — whether the daemon can act on a machine.update
|
|
28223
28319
|
* signal (true under a service manager, false for bare `npx` foreground).
|
|
28320
|
+
* - `detectedRuntimes` — runtime CLIs found on the host. Omitted = no
|
|
28321
|
+
* report this beat (server keeps the stored value); [] = detection ran
|
|
28322
|
+
* and found nothing (server clears to empty).
|
|
28224
28323
|
*/
|
|
28225
28324
|
async postMachineHeartbeat(opts) {
|
|
28226
28325
|
const body = {};
|
|
@@ -28228,6 +28327,8 @@ var ParallClient = class _ParallClient {
|
|
|
28228
28327
|
body.daemon_version = opts.daemonVersion;
|
|
28229
28328
|
if (opts?.selfUpdateCapable !== void 0)
|
|
28230
28329
|
body.self_update_capable = opts.selfUpdateCapable;
|
|
28330
|
+
if (opts?.detectedRuntimes !== void 0)
|
|
28331
|
+
body.detected_runtimes = opts.detectedRuntimes;
|
|
28231
28332
|
return this.request("POST", ENDPOINTS.MACHINES_ME_HEALTH, Object.keys(body).length > 0 ? body : void 0);
|
|
28232
28333
|
}
|
|
28233
28334
|
async reportAgentWorkspaceState(agentId, state) {
|
|
@@ -28276,8 +28377,8 @@ var ParallClient = class _ParallClient {
|
|
|
28276
28377
|
async requestMachineUpdate(orgId, machineId, mandatory = false) {
|
|
28277
28378
|
await this.request("POST", ENDPOINTS.MACHINE_REQUEST_UPDATE(orgId, machineId), { mandatory });
|
|
28278
28379
|
}
|
|
28279
|
-
async browseMachineFilesystem(orgId, machineId,
|
|
28280
|
-
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 });
|
|
28281
28382
|
}
|
|
28282
28383
|
/** Create a new machine key. Returns the raw key string (shown once) + metadata. */
|
|
28283
28384
|
async createMachineKey(orgId, machineId, name) {
|
|
@@ -28350,6 +28451,30 @@ var ParallClient = class _ParallClient {
|
|
|
28350
28451
|
const params = maxAgeHours !== void 0 ? { max_age_hours: String(maxAgeHours) } : void 0;
|
|
28351
28452
|
return this.request("POST", ENDPOINTS.DISPATCH_EXPIRE(orgId), void 0, params);
|
|
28352
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
|
+
}
|
|
28353
28478
|
async getDispatchByMessages(orgId, chatId, messageIds) {
|
|
28354
28479
|
const params = { chat_id: chatId, message_ids: messageIds.join(",") };
|
|
28355
28480
|
return this.request("GET", ENDPOINTS.DISPATCH_BY_MESSAGES(orgId), void 0, params);
|
|
@@ -28546,6 +28671,59 @@ var ParallClient = class _ParallClient {
|
|
|
28546
28671
|
async deleteExternalConnection(orgId, connectionId) {
|
|
28547
28672
|
return this.request("DELETE", ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId));
|
|
28548
28673
|
}
|
|
28674
|
+
// ---- External IM channel (org-scoped) ----
|
|
28675
|
+
async createChannelConnection(orgId, input) {
|
|
28676
|
+
return this.request("POST", ENDPOINTS.CHANNEL_CONNECTIONS(orgId), input);
|
|
28677
|
+
}
|
|
28678
|
+
async listChannelConnections(orgId) {
|
|
28679
|
+
return this.request("GET", ENDPOINTS.CHANNEL_CONNECTIONS(orgId));
|
|
28680
|
+
}
|
|
28681
|
+
async getChannelConnection(orgId, connectionId) {
|
|
28682
|
+
return this.request("GET", ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId));
|
|
28683
|
+
}
|
|
28684
|
+
async updateChannelConnection(orgId, connectionId, patch) {
|
|
28685
|
+
return this.request("PATCH", ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId), patch);
|
|
28686
|
+
}
|
|
28687
|
+
async archiveChannelConnection(orgId, connectionId) {
|
|
28688
|
+
return this.request("DELETE", ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId));
|
|
28689
|
+
}
|
|
28690
|
+
async deliverChannelCredentials(orgId, connectionId, credentials) {
|
|
28691
|
+
return this.request("POST", ENDPOINTS.CHANNEL_CONNECTION_CREDENTIALS(orgId, connectionId), credentials);
|
|
28692
|
+
}
|
|
28693
|
+
async regenerateChannelIngressToken(orgId, connectionId) {
|
|
28694
|
+
return this.request("POST", ENDPOINTS.CHANNEL_CONNECTION_INGRESS_TOKEN_REGENERATE(orgId, connectionId));
|
|
28695
|
+
}
|
|
28696
|
+
/** Start a Feishu one-click provisioning session (device flow QR). */
|
|
28697
|
+
async initiateChannelProvisioning(orgId, input) {
|
|
28698
|
+
return this.request("POST", ENDPOINTS.CHANNEL_PROVISIONING(orgId), input);
|
|
28699
|
+
}
|
|
28700
|
+
/**
|
|
28701
|
+
* Lazy status poll — server-side this may forward one provider poll, so
|
|
28702
|
+
* call it at the session's `poll_interval_seconds` cadence, not faster.
|
|
28703
|
+
*/
|
|
28704
|
+
async getChannelProvisioningSession(orgId, sessionId) {
|
|
28705
|
+
return this.request("GET", ENDPOINTS.CHANNEL_PROVISIONING_SESSION(orgId, sessionId));
|
|
28706
|
+
}
|
|
28707
|
+
async cancelChannelProvisioning(orgId, sessionId) {
|
|
28708
|
+
return this.request("POST", ENDPOINTS.CHANNEL_PROVISIONING_CANCEL(orgId, sessionId));
|
|
28709
|
+
}
|
|
28710
|
+
async listChannelConversations(orgId, connectionId) {
|
|
28711
|
+
return this.request("GET", ENDPOINTS.CHANNEL_CONNECTION_CONVERSATIONS(orgId, connectionId));
|
|
28712
|
+
}
|
|
28713
|
+
async getChannelConversation(orgId, conversationId) {
|
|
28714
|
+
return this.request("GET", ENDPOINTS.CHANNEL_CONVERSATION(orgId, conversationId));
|
|
28715
|
+
}
|
|
28716
|
+
async listChannelConversationMessages(orgId, conversationId, limit) {
|
|
28717
|
+
return this.request("GET", ENDPOINTS.CHANNEL_CONVERSATION_MESSAGES(orgId, conversationId), void 0, { limit });
|
|
28718
|
+
}
|
|
28719
|
+
async setChannelConversationSession(orgId, conversationId, agentSessionId) {
|
|
28720
|
+
return this.request("PATCH", ENDPOINTS.CHANNEL_CONVERSATION_SESSION(orgId, conversationId), {
|
|
28721
|
+
agent_session_id: agentSessionId
|
|
28722
|
+
});
|
|
28723
|
+
}
|
|
28724
|
+
async getChannelMessage(orgId, messageId) {
|
|
28725
|
+
return this.request("GET", ENDPOINTS.CHANNEL_MESSAGE(orgId, messageId));
|
|
28726
|
+
}
|
|
28549
28727
|
async getExternalTriggerSchema(orgId, connectionId) {
|
|
28550
28728
|
return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGER_SCHEMA(orgId, connectionId));
|
|
28551
28729
|
}
|
|
@@ -28587,6 +28765,23 @@ var ParallClient = class _ParallClient {
|
|
|
28587
28765
|
async getWiki(orgId, wikiId) {
|
|
28588
28766
|
return this.request("GET", ENDPOINTS.WIKI(orgId, wikiId));
|
|
28589
28767
|
}
|
|
28768
|
+
/** Soft-delete an entire wiki (org owner only; the default wiki is
|
|
28769
|
+
* protected → 403 WIKI_PROTECTED). Returns the deleted wiki. Recoverable
|
|
28770
|
+
* via restoreWiki within the retention window. */
|
|
28771
|
+
async deleteWiki(orgId, wikiId) {
|
|
28772
|
+
return this.request("DELETE", ENDPOINTS.WIKI(orgId, wikiId));
|
|
28773
|
+
}
|
|
28774
|
+
/** Restore a soft-deleted wiki (org owner only). 409 WIKI_PURGE_STARTED once
|
|
28775
|
+
* purge has been claimed (no longer restorable), or 404 once the row is gone.
|
|
28776
|
+
* Returns the restored wiki. */
|
|
28777
|
+
async restoreWiki(orgId, wikiId) {
|
|
28778
|
+
return this.request("POST", ENDPOINTS.WIKI_RESTORE(orgId, wikiId));
|
|
28779
|
+
}
|
|
28780
|
+
/** Recycle bin — list soft-deleted wikis for the org (owner only). */
|
|
28781
|
+
async getDeletedWikis(orgId) {
|
|
28782
|
+
const res = await this.request("GET", ENDPOINTS.WIKIS_DELETED(orgId));
|
|
28783
|
+
return res.data;
|
|
28784
|
+
}
|
|
28590
28785
|
async getWikiTree(orgId, wikiId, params) {
|
|
28591
28786
|
return this.request("GET", ENDPOINTS.WIKI_TREE(orgId, wikiId), void 0, params);
|
|
28592
28787
|
}
|
|
@@ -28620,6 +28815,15 @@ var ParallClient = class _ParallClient {
|
|
|
28620
28815
|
blob.signed_url = this.absoluteMediaUrl(blob.signed_url);
|
|
28621
28816
|
return blob;
|
|
28622
28817
|
}
|
|
28818
|
+
/**
|
|
28819
|
+
* Copy one wiki file to another path as an independent snapshot (no sync).
|
|
28820
|
+
* Used to "share a private file out": copy a file from the caller's personal
|
|
28821
|
+
* namespace (`users/{userId}/…`) into the public wiki, leaving the original
|
|
28822
|
+
* untouched. 409 `FILE_EXISTS` if `dest_path` already exists.
|
|
28823
|
+
*/
|
|
28824
|
+
async copyWikiFile(orgId, wikiId, req) {
|
|
28825
|
+
return this.request("POST", ENDPOINTS.WIKI_COPY(orgId, wikiId), req);
|
|
28826
|
+
}
|
|
28623
28827
|
async getWikiNodeSections(orgId, wikiId, params) {
|
|
28624
28828
|
return this.request("GET", ENDPOINTS.WIKI_NODE_SECTIONS(orgId, wikiId), void 0, params);
|
|
28625
28829
|
}
|
|
@@ -28735,8 +28939,8 @@ var ParallClient = class _ParallClient {
|
|
|
28735
28939
|
async deleteWikiRestriction(orgId, wikiId, restrictionId) {
|
|
28736
28940
|
await this.request("DELETE", ENDPOINTS.WIKI_RESTRICTION(orgId, wikiId, restrictionId));
|
|
28737
28941
|
}
|
|
28738
|
-
async getWikiAccessStatus(orgId, wikiId,
|
|
28739
|
-
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);
|
|
28740
28944
|
}
|
|
28741
28945
|
async createWikiAccessRequest(orgId, wikiId, data) {
|
|
28742
28946
|
await this.request("POST", ENDPOINTS.WIKI_ACCESS_REQUESTS(orgId, wikiId), data);
|
|
@@ -28745,14 +28949,14 @@ var ParallClient = class _ParallClient {
|
|
|
28745
28949
|
async getWikiCommits(orgId, wikiId, params) {
|
|
28746
28950
|
return this.request("GET", ENDPOINTS.WIKI_COMMITS(orgId, wikiId), void 0, params);
|
|
28747
28951
|
}
|
|
28748
|
-
async getWikiFileCommits(orgId, wikiId,
|
|
28952
|
+
async getWikiFileCommits(orgId, wikiId, path8, params) {
|
|
28749
28953
|
return this.request("GET", ENDPOINTS.WIKI_FILE_COMMITS(orgId, wikiId), void 0, {
|
|
28750
|
-
path:
|
|
28954
|
+
path: path8,
|
|
28751
28955
|
...params
|
|
28752
28956
|
});
|
|
28753
28957
|
}
|
|
28754
|
-
async getWikiBlame(orgId, wikiId,
|
|
28755
|
-
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 });
|
|
28756
28960
|
}
|
|
28757
28961
|
// ---- Wiki Operations (audit log) ----
|
|
28758
28962
|
async getWikiOperations(orgId, wikiId, params) {
|
|
@@ -28893,6 +29097,19 @@ var ParallClient = class _ParallClient {
|
|
|
28893
29097
|
const resp = await this.request("GET", ENDPOINTS.COMPUTE_PRICING());
|
|
28894
29098
|
return resp.data;
|
|
28895
29099
|
}
|
|
29100
|
+
/** Runtime capability table (public) — SSOT for the create/settings interlock. */
|
|
29101
|
+
async getRuntimes() {
|
|
29102
|
+
const resp = await this.request("GET", ENDPOINTS.RUNTIMES());
|
|
29103
|
+
return resp.data;
|
|
29104
|
+
}
|
|
29105
|
+
/**
|
|
29106
|
+
* Platform model catalog (public) — DB-backed, replaces the compile-time
|
|
29107
|
+
* PLATFORM_MODELS constant. Pass includeModel to keep an agent's pinned
|
|
29108
|
+
* hidden legacy model representable (settings picker).
|
|
29109
|
+
*/
|
|
29110
|
+
async getModels(includeModel) {
|
|
29111
|
+
return this.request("GET", ENDPOINTS.MODELS(includeModel));
|
|
29112
|
+
}
|
|
28896
29113
|
// ---- Clips ----
|
|
28897
29114
|
async listClips(orgId) {
|
|
28898
29115
|
const resp = await this.request("GET", ENDPOINTS.CLIPS(orgId));
|
|
@@ -28937,6 +29154,11 @@ var ParallClient = class _ParallClient {
|
|
|
28937
29154
|
const resp = await this.request("GET", ENDPOINTS.CLIP_ONLINE(orgId));
|
|
28938
29155
|
return resp.data;
|
|
28939
29156
|
}
|
|
29157
|
+
/** Deployment-wide hosted browser runtime availability — drives whether the
|
|
29158
|
+
* create UI offers the platform-hosted placement. Fail-closed server-side. */
|
|
29159
|
+
async getBrowserRuntimeStatus(orgId) {
|
|
29160
|
+
return this.request("GET", ENDPOINTS.BROWSER_RUNTIME_STATUS(orgId));
|
|
29161
|
+
}
|
|
28940
29162
|
/** Org-wide browser-profile discovery list. Returns the sanitized
|
|
28941
29163
|
* {@link BrowserProfileListItem} shape (not the full domain model), each row
|
|
28942
29164
|
* carrying a per-viewer `can_open` control hint. */
|
|
@@ -29366,6 +29588,404 @@ var ParallWs = class {
|
|
|
29366
29588
|
}
|
|
29367
29589
|
};
|
|
29368
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
|
+
|
|
29369
29989
|
// ../agent-core/dist/telemetry.js
|
|
29370
29990
|
init_esm();
|
|
29371
29991
|
var import_api_logs = __toESM(require_src(), 1);
|
|
@@ -29596,6 +30216,9 @@ function resolveStepTarget(event) {
|
|
|
29596
30216
|
if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
|
|
29597
30217
|
return { target_type: "external_trigger", target_id: event.targetId };
|
|
29598
30218
|
}
|
|
30219
|
+
if (event.type === "channel_message" || event.targetId.startsWith("chv_")) {
|
|
30220
|
+
return { target_type: "channel_conversation", target_id: event.targetId };
|
|
30221
|
+
}
|
|
29599
30222
|
if (event.type === "wiki_comment") {
|
|
29600
30223
|
return { target_type: "wiki", target_id: event.targetId || void 0 };
|
|
29601
30224
|
}
|
|
@@ -29639,6 +30262,9 @@ var ParallAgentGateway = class {
|
|
|
29639
30262
|
opts;
|
|
29640
30263
|
chatInfoMap = /* @__PURE__ */ new Map();
|
|
29641
30264
|
dispatchedTasks = /* @__PURE__ */ new Set();
|
|
30265
|
+
// connection id → provider alias, for channel_message prompt labeling
|
|
30266
|
+
// (stable mapping; avoids one connection fetch per inbound message).
|
|
30267
|
+
channelConnectionProviders = /* @__PURE__ */ new Map();
|
|
29642
30268
|
dispatchedMessages = /* @__PURE__ */ new Set();
|
|
29643
30269
|
forkStates = /* @__PURE__ */ new Map();
|
|
29644
30270
|
dispatchState = {
|
|
@@ -29661,6 +30287,14 @@ var ParallAgentGateway = class {
|
|
|
29661
30287
|
inFlightDispatches = 0;
|
|
29662
30288
|
drainResolvers = [];
|
|
29663
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;
|
|
29664
30298
|
DISPATCHED_MESSAGES_CAP = 5e3;
|
|
29665
30299
|
// SHUTDOWN_DEADLINE_MS is read by waitForDrain via the configured value
|
|
29666
30300
|
// below — kept as instance state so per-runtime configs can override it
|
|
@@ -29670,6 +30304,14 @@ var ParallAgentGateway = class {
|
|
|
29670
30304
|
DISPATCH_DEADLINE_MS;
|
|
29671
30305
|
constructor(opts) {
|
|
29672
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
|
+
}
|
|
29673
30315
|
this.SHUTDOWN_DEADLINE_MS = opts.shutdownDeadlineMs ?? 6e4;
|
|
29674
30316
|
this.FORK_DEADLINE_MS = opts.forkDeadlineMs ?? 2 * 60 * 6e4;
|
|
29675
30317
|
this.DISPATCH_DEADLINE_MS = opts.dispatchDeadlineMs ?? 20 * 6e4;
|
|
@@ -29739,14 +30381,18 @@ var ParallAgentGateway = class {
|
|
|
29739
30381
|
if (data.status !== "todo" && data.status !== "in_progress")
|
|
29740
30382
|
return;
|
|
29741
30383
|
try {
|
|
29742
|
-
|
|
29743
|
-
|
|
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
|
+
}
|
|
29744
30390
|
this.opts.client.ackDispatch(this.opts.config.org_id, {
|
|
29745
30391
|
source_type: "task_activity",
|
|
29746
30392
|
source_id: data.id
|
|
29747
30393
|
}).catch(() => {
|
|
29748
30394
|
});
|
|
29749
|
-
}
|
|
30395
|
+
});
|
|
29750
30396
|
} catch (err) {
|
|
29751
30397
|
this.opts.log?.error(`task dispatch failed for ${data.id}: ${String(err)}`);
|
|
29752
30398
|
}
|
|
@@ -29756,11 +30402,10 @@ var ParallAgentGateway = class {
|
|
|
29756
30402
|
if (!data.source_id || !data.task_id)
|
|
29757
30403
|
return;
|
|
29758
30404
|
try {
|
|
29759
|
-
|
|
29760
|
-
if (dispatched) {
|
|
30405
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleTaskComment(data.source_id, data.task_id ?? "", data.actor_id, data.delivery_reason), () => {
|
|
29761
30406
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29762
30407
|
});
|
|
29763
|
-
}
|
|
30408
|
+
});
|
|
29764
30409
|
} catch (err) {
|
|
29765
30410
|
this.opts.log?.error(`task comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29766
30411
|
}
|
|
@@ -29768,11 +30413,10 @@ var ParallAgentGateway = class {
|
|
|
29768
30413
|
if (!data.source_id)
|
|
29769
30414
|
return;
|
|
29770
30415
|
try {
|
|
29771
|
-
|
|
29772
|
-
if (dispatched) {
|
|
30416
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleWikiComment(data.source_id, data.actor_id, data.delivery_reason), () => {
|
|
29773
30417
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29774
30418
|
});
|
|
29775
|
-
}
|
|
30419
|
+
});
|
|
29776
30420
|
} catch (err) {
|
|
29777
30421
|
this.opts.log?.error(`wiki comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29778
30422
|
}
|
|
@@ -29780,11 +30424,13 @@ var ParallAgentGateway = class {
|
|
|
29780
30424
|
if (!data.task_id)
|
|
29781
30425
|
return;
|
|
29782
30426
|
try {
|
|
29783
|
-
|
|
29784
|
-
|
|
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
|
+
}), () => {
|
|
29785
30431
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29786
30432
|
});
|
|
29787
|
-
}
|
|
30433
|
+
});
|
|
29788
30434
|
} catch (err) {
|
|
29789
30435
|
this.opts.log?.error(`task update dispatch failed for ${data.task_id}: ${String(err)}`);
|
|
29790
30436
|
}
|
|
@@ -29792,11 +30438,10 @@ var ParallAgentGateway = class {
|
|
|
29792
30438
|
if (!data.source_id)
|
|
29793
30439
|
return;
|
|
29794
30440
|
try {
|
|
29795
|
-
|
|
29796
|
-
if (dispatched) {
|
|
30441
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleScheduleFire(data.source_id, data.actor_id), () => {
|
|
29797
30442
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29798
30443
|
});
|
|
29799
|
-
}
|
|
30444
|
+
});
|
|
29800
30445
|
} catch (err) {
|
|
29801
30446
|
this.opts.log?.error(`schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29802
30447
|
}
|
|
@@ -29804,26 +30449,41 @@ var ParallAgentGateway = class {
|
|
|
29804
30449
|
if (!data.source_id)
|
|
29805
30450
|
return;
|
|
29806
30451
|
try {
|
|
29807
|
-
|
|
29808
|
-
if (dispatched) {
|
|
30452
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleExternalTriggerRun(data.source_id), () => {
|
|
29809
30453
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29810
30454
|
});
|
|
29811
|
-
}
|
|
30455
|
+
});
|
|
29812
30456
|
} catch (err) {
|
|
29813
30457
|
this.opts.log?.error(`external trigger dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29814
30458
|
}
|
|
30459
|
+
} else if (data.event_type === "channel_message") {
|
|
30460
|
+
if (!data.source_id)
|
|
30461
|
+
return;
|
|
30462
|
+
try {
|
|
30463
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleChannelMessage(data.source_id), () => {
|
|
30464
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30465
|
+
});
|
|
30466
|
+
});
|
|
30467
|
+
} catch (err) {
|
|
30468
|
+
this.opts.log?.error(`channel message dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30469
|
+
}
|
|
29815
30470
|
} else if (data.event_type === "approval_decided") {
|
|
29816
30471
|
if (!data.source_id)
|
|
29817
30472
|
return;
|
|
29818
30473
|
try {
|
|
29819
|
-
|
|
29820
|
-
if (dispatched) {
|
|
30474
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleApprovalDecided(data.source_id, data.actor_id, data.chat_id ?? null), () => {
|
|
29821
30475
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29822
30476
|
});
|
|
29823
|
-
}
|
|
30477
|
+
});
|
|
29824
30478
|
} catch (err) {
|
|
29825
30479
|
this.opts.log?.error(`approval decided dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29826
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
|
+
}
|
|
29827
30487
|
} else if (data.event_type !== "message" && data.event_type !== "task_assign") {
|
|
29828
30488
|
this.opts.log?.info(`dispatch.new with unhandled event_type=${String(data.event_type)} (id=${data.id}) \u2014 no-op`);
|
|
29829
30489
|
}
|
|
@@ -29859,6 +30519,39 @@ var ParallAgentGateway = class {
|
|
|
29859
30519
|
source_id: sourceId
|
|
29860
30520
|
});
|
|
29861
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
|
+
}
|
|
29862
30555
|
buildDispatchContext(event, sessionKey) {
|
|
29863
30556
|
const binding = this.sessionBindings.get(sessionKey);
|
|
29864
30557
|
return {
|
|
@@ -29875,6 +30568,7 @@ var ParallAgentGateway = class {
|
|
|
29875
30568
|
noReply: event.noReply ?? false,
|
|
29876
30569
|
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey),
|
|
29877
30570
|
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey),
|
|
30571
|
+
contextDirPath: this.opts.dispatchContextDir,
|
|
29878
30572
|
client: this.opts.client,
|
|
29879
30573
|
log: this.opts.log
|
|
29880
30574
|
};
|
|
@@ -29890,12 +30584,17 @@ var ParallAgentGateway = class {
|
|
|
29890
30584
|
target_type: target.target_type,
|
|
29891
30585
|
target_id: target.target_id,
|
|
29892
30586
|
content: {
|
|
29893
|
-
trigger_type: event.type === "task" ? "task_assign" : event.type === "task_comment" ? "task_comment" : event.type === "wiki_comment" ? "wiki_comment" : event.type === "schedule" ? "schedule_fire" : event.type === "external_trigger" ? "external_trigger" : event.type === "approval" ? "approval_decided" : "mention",
|
|
30587
|
+
trigger_type: event.type === "task" ? "task_assign" : event.type === "task_comment" ? "task_comment" : event.type === "wiki_comment" ? "wiki_comment" : event.type === "schedule" ? "schedule_fire" : event.type === "external_trigger" ? "external_trigger" : event.type === "channel_message" ? "channel_message" : event.type === "approval" ? "approval_decided" : "mention",
|
|
29894
30588
|
trigger_ref: event.type === "task" ? { task_id: event.targetId } : event.type === "task_comment" ? { comment_id: event.messageId, task_id: event.targetId } : event.type === "wiki_comment" ? { comment_id: event.messageId, target_uri: event.replyTargetUri } : event.type === "schedule" ? { schedule_id: event.targetId, run_id: event.messageId } : event.type === "external_trigger" ? {
|
|
29895
30589
|
trigger_id: event.targetId,
|
|
29896
30590
|
run_id: event.messageId,
|
|
29897
30591
|
connection_id: event.externalConnectionId,
|
|
29898
30592
|
ingress_event_id: event.externalIngressEventId
|
|
30593
|
+
} : event.type === "channel_message" ? {
|
|
30594
|
+
conversation_id: event.targetId,
|
|
30595
|
+
channel_message_id: event.messageId,
|
|
30596
|
+
provider: event.channelProvider,
|
|
30597
|
+
external_conversation_id: event.channelExternalConversationId
|
|
29899
30598
|
} : event.type === "approval" ? { approval_id: event.messageId } : { message_id: event.messageId },
|
|
29900
30599
|
sender_id: event.senderId,
|
|
29901
30600
|
sender_name: event.senderName,
|
|
@@ -29909,7 +30608,7 @@ var ParallAgentGateway = class {
|
|
|
29909
30608
|
this.opts.log?.warn(`failed to create input step: ${String(err)}`);
|
|
29910
30609
|
}
|
|
29911
30610
|
}
|
|
29912
|
-
async createRuntimeStep(sessionId, event, runtimeEvent, stepIdFilePath, contextFilePath) {
|
|
30611
|
+
async createRuntimeStep(sessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2) {
|
|
29913
30612
|
const target = resolveStepTarget(event);
|
|
29914
30613
|
try {
|
|
29915
30614
|
switch (runtimeEvent.type) {
|
|
@@ -29955,6 +30654,9 @@ var ParallAgentGateway = class {
|
|
|
29955
30654
|
} else if (stepIdFilePath) {
|
|
29956
30655
|
this.writeStepIdFile(stepIdFilePath, step.id);
|
|
29957
30656
|
}
|
|
30657
|
+
if (laneContextFilePath2) {
|
|
30658
|
+
this.updateContextFileStepId(laneContextFilePath2, step.id);
|
|
30659
|
+
}
|
|
29958
30660
|
break;
|
|
29959
30661
|
}
|
|
29960
30662
|
case "tool_result":
|
|
@@ -29977,6 +30679,9 @@ var ParallAgentGateway = class {
|
|
|
29977
30679
|
} else if (stepIdFilePath) {
|
|
29978
30680
|
this.clearStepIdFile(stepIdFilePath);
|
|
29979
30681
|
}
|
|
30682
|
+
if (laneContextFilePath2) {
|
|
30683
|
+
this.updateContextFileStepId(laneContextFilePath2, null);
|
|
30684
|
+
}
|
|
29980
30685
|
break;
|
|
29981
30686
|
case "error":
|
|
29982
30687
|
await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
|
|
@@ -29996,28 +30701,28 @@ var ParallAgentGateway = class {
|
|
|
29996
30701
|
}
|
|
29997
30702
|
writeContextFile(filePath, ctx) {
|
|
29998
30703
|
try {
|
|
29999
|
-
|
|
30000
|
-
|
|
30704
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
30705
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30001
30706
|
} catch (err) {
|
|
30002
30707
|
this.opts.log?.warn(`failed to write context file ${filePath}: ${String(err)}`);
|
|
30003
30708
|
}
|
|
30004
30709
|
}
|
|
30005
30710
|
updateContextFileStepId(filePath, stepId) {
|
|
30006
30711
|
try {
|
|
30007
|
-
const raw =
|
|
30712
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
30008
30713
|
const ctx = JSON.parse(raw);
|
|
30009
30714
|
ctx.step_id = stepId;
|
|
30010
|
-
|
|
30715
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30011
30716
|
} catch (err) {
|
|
30012
30717
|
this.opts.log?.warn(`failed to update context file step_id ${filePath}: ${String(err)}`);
|
|
30013
30718
|
}
|
|
30014
30719
|
}
|
|
30015
30720
|
updateContextFileSessionId(filePath, sessionId) {
|
|
30016
30721
|
try {
|
|
30017
|
-
const raw =
|
|
30722
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
30018
30723
|
const ctx = JSON.parse(raw);
|
|
30019
30724
|
ctx.session_id = sessionId;
|
|
30020
|
-
|
|
30725
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30021
30726
|
} catch (err) {
|
|
30022
30727
|
this.opts.log?.warn(`failed to update context file session_id ${filePath}: ${String(err)}`);
|
|
30023
30728
|
}
|
|
@@ -30025,8 +30730,8 @@ var ParallAgentGateway = class {
|
|
|
30025
30730
|
/** @deprecated Use writeContextFile / updateContextFileStepId. */
|
|
30026
30731
|
writeStepIdFile(filePath, stepId) {
|
|
30027
30732
|
try {
|
|
30028
|
-
|
|
30029
|
-
|
|
30733
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
30734
|
+
fs2.writeFileSync(filePath, stepId, "utf8");
|
|
30030
30735
|
} catch (err) {
|
|
30031
30736
|
this.opts.log?.warn(`failed to write step id file ${filePath}: ${String(err)}`);
|
|
30032
30737
|
}
|
|
@@ -30034,7 +30739,7 @@ var ParallAgentGateway = class {
|
|
|
30034
30739
|
/** @deprecated Use writeContextFile / updateContextFileStepId. */
|
|
30035
30740
|
clearStepIdFile(filePath) {
|
|
30036
30741
|
try {
|
|
30037
|
-
|
|
30742
|
+
fs2.writeFileSync(filePath, "", "utf8");
|
|
30038
30743
|
} catch {
|
|
30039
30744
|
}
|
|
30040
30745
|
}
|
|
@@ -30043,7 +30748,7 @@ var ParallAgentGateway = class {
|
|
|
30043
30748
|
await this.createInputStep(sessionId, event);
|
|
30044
30749
|
}
|
|
30045
30750
|
}
|
|
30046
|
-
async bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath) {
|
|
30751
|
+
async bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2) {
|
|
30047
30752
|
const runtimeLaneKey = runtimeEvent.runtimeLaneKey || sessionKey;
|
|
30048
30753
|
const existing = this.sessionBindings.get(sessionKey);
|
|
30049
30754
|
if (existing && existing.runtimeLaneKey === runtimeLaneKey && existing.runtimeSessionId === runtimeEvent.runtimeSessionId) {
|
|
@@ -30087,6 +30792,9 @@ var ParallAgentGateway = class {
|
|
|
30087
30792
|
if (contextFilePath) {
|
|
30088
30793
|
this.updateContextFileSessionId(contextFilePath, session.id);
|
|
30089
30794
|
}
|
|
30795
|
+
if (laneContextFilePath2) {
|
|
30796
|
+
this.updateContextFileSessionId(laneContextFilePath2, session.id);
|
|
30797
|
+
}
|
|
30090
30798
|
await this.opts.onSessionBinding?.(binding);
|
|
30091
30799
|
return binding;
|
|
30092
30800
|
}
|
|
@@ -30113,14 +30821,27 @@ var ParallAgentGateway = class {
|
|
|
30113
30821
|
const dispatchContext = this.buildDispatchContext(event, sessionKey);
|
|
30114
30822
|
const contextFilePath = dispatchContext.contextFilePath;
|
|
30115
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
|
+
};
|
|
30116
30840
|
if (contextFilePath) {
|
|
30117
|
-
this.writeContextFile(contextFilePath,
|
|
30118
|
-
|
|
30119
|
-
|
|
30120
|
-
|
|
30121
|
-
no_reply: dispatchContext.noReply,
|
|
30122
|
-
step_id: null
|
|
30123
|
-
});
|
|
30841
|
+
this.writeContextFile(contextFilePath, contextBody);
|
|
30842
|
+
}
|
|
30843
|
+
if (laneContextFilePath2) {
|
|
30844
|
+
this.writeContextFile(laneContextFilePath2, contextBody);
|
|
30124
30845
|
}
|
|
30125
30846
|
this.inFlightDispatches++;
|
|
30126
30847
|
const deadlineTimer = this.DISPATCH_DEADLINE_MS > 0 ? setTimeout(() => {
|
|
@@ -30146,7 +30867,15 @@ var ParallAgentGateway = class {
|
|
|
30146
30867
|
context: dispatchContext
|
|
30147
30868
|
})) {
|
|
30148
30869
|
if (runtimeEvent.type === "runtime_session") {
|
|
30149
|
-
|
|
30870
|
+
const priorAgentSessionId = binding?.agentSessionId;
|
|
30871
|
+
binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2);
|
|
30872
|
+
if (event.targetType === "channel_conversation" && binding.agentSessionId !== priorAgentSessionId) {
|
|
30873
|
+
try {
|
|
30874
|
+
await this.opts.client.setChannelConversationSession(this.opts.config.org_id, event.targetId, binding.agentSessionId);
|
|
30875
|
+
} catch (err) {
|
|
30876
|
+
this.opts.log?.warn(`failed to record session mapping for channel conversation ${event.targetId}: ${String(err)}`);
|
|
30877
|
+
}
|
|
30878
|
+
}
|
|
30150
30879
|
if (!inputStepsCreated) {
|
|
30151
30880
|
if (earlierEvents.length > 0) {
|
|
30152
30881
|
await this.createInputStepsForEarlierEvents(binding.agentSessionId, earlierEvents);
|
|
@@ -30171,6 +30900,9 @@ var ParallAgentGateway = class {
|
|
|
30171
30900
|
await this.createInputStep(binding.agentSessionId, event);
|
|
30172
30901
|
inputStepsCreated = true;
|
|
30173
30902
|
}
|
|
30903
|
+
if (activeLane && !this.ledgerDisabled) {
|
|
30904
|
+
this.laneLedger?.maybeRenew(activeLane);
|
|
30905
|
+
}
|
|
30174
30906
|
if (captureText && runtimeEvent.type === "text" && runtimeEvent.text) {
|
|
30175
30907
|
captureText.push(runtimeEvent.text);
|
|
30176
30908
|
}
|
|
@@ -30187,7 +30919,7 @@ var ParallAgentGateway = class {
|
|
|
30187
30919
|
} else if (runtimeEvent.type === "tool_result" && pendingSendCallIds.delete(runtimeEvent.callId)) {
|
|
30188
30920
|
recordMessageSend(sessionKey, !runtimeEvent.error);
|
|
30189
30921
|
}
|
|
30190
|
-
await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath);
|
|
30922
|
+
await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
30191
30923
|
}
|
|
30192
30924
|
if (!binding) {
|
|
30193
30925
|
binding = this.sessionBindings.get(sessionKey);
|
|
@@ -30209,7 +30941,7 @@ var ParallAgentGateway = class {
|
|
|
30209
30941
|
await this.createRuntimeStep(binding.agentSessionId, event, {
|
|
30210
30942
|
type: "error",
|
|
30211
30943
|
message: `Dispatch failed: ${String(err)}`
|
|
30212
|
-
}, stepIdFilePath, contextFilePath);
|
|
30944
|
+
}, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
30213
30945
|
} catch (stepErr) {
|
|
30214
30946
|
if (this.isSessionNotLiveError(stepErr))
|
|
30215
30947
|
staleDetected = true;
|
|
@@ -30252,6 +30984,9 @@ var ParallAgentGateway = class {
|
|
|
30252
30984
|
} else if (stepIdFilePath) {
|
|
30253
30985
|
this.clearStepIdFile(stepIdFilePath);
|
|
30254
30986
|
}
|
|
30987
|
+
if (laneContextFilePath2) {
|
|
30988
|
+
this.updateContextFileStepId(laneContextFilePath2, null);
|
|
30989
|
+
}
|
|
30255
30990
|
this.inFlightDispatches--;
|
|
30256
30991
|
if (this.inFlightDispatches === 0 && this.drainResolvers.length > 0) {
|
|
30257
30992
|
const resolvers = this.drainResolvers.splice(0);
|
|
@@ -30309,13 +31044,39 @@ var ParallAgentGateway = class {
|
|
|
30309
31044
|
item.resolve(false);
|
|
30310
31045
|
break;
|
|
30311
31046
|
}
|
|
30312
|
-
|
|
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
|
+
}
|
|
30313
31056
|
const events = items.map((item) => item.event);
|
|
30314
31057
|
const last = events[events.length - 1];
|
|
30315
31058
|
const earlier = events.slice(0, -1);
|
|
30316
31059
|
try {
|
|
30317
31060
|
const batchText = [];
|
|
30318
|
-
|
|
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
|
+
}
|
|
30319
31080
|
if (!dispatched) {
|
|
30320
31081
|
for (const item of items) {
|
|
30321
31082
|
item.resolve(false);
|
|
@@ -30418,9 +31179,9 @@ var ParallAgentGateway = class {
|
|
|
30418
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`);
|
|
30419
31180
|
break;
|
|
30420
31181
|
}
|
|
30421
|
-
const
|
|
31182
|
+
const groupKey = this.dispatchGroupKey(this.dispatchState.mainBuffer[0]);
|
|
30422
31183
|
const events = [];
|
|
30423
|
-
while (this.dispatchState.mainBuffer[0]
|
|
31184
|
+
while (this.dispatchState.mainBuffer[0] && this.dispatchGroupKey(this.dispatchState.mainBuffer[0]) === groupKey) {
|
|
30424
31185
|
events.push(this.dispatchState.mainBuffer.shift());
|
|
30425
31186
|
}
|
|
30426
31187
|
const event = events[events.length - 1];
|
|
@@ -30429,7 +31190,36 @@ var ParallAgentGateway = class {
|
|
|
30429
31190
|
const pendingFork = hasPendingInjections ? [] : this.dispatchState.pendingForkResults.splice(0);
|
|
30430
31191
|
const forkPrefix = buildForkResultPrefix(pendingFork);
|
|
30431
31192
|
this.dispatchState.mainCurrentTargetId = event.targetId;
|
|
31193
|
+
this.mainCurrentGroupKey = groupKey;
|
|
30432
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
|
+
}
|
|
30433
31223
|
try {
|
|
30434
31224
|
await this.emitDispatchReceived(event);
|
|
30435
31225
|
} catch (err) {
|
|
@@ -30458,6 +31248,7 @@ var ParallAgentGateway = class {
|
|
|
30458
31248
|
this.draining = false;
|
|
30459
31249
|
this.dispatchState.mainDispatching = false;
|
|
30460
31250
|
this.dispatchState.mainCurrentTargetId = void 0;
|
|
31251
|
+
this.mainCurrentGroupKey = void 0;
|
|
30461
31252
|
this.dispatchState.mainPreDispatchBranchPoint = void 0;
|
|
30462
31253
|
if (!this.shuttingDown && this.dispatchState.mainBuffer.length > 0) {
|
|
30463
31254
|
setTimeout(() => {
|
|
@@ -30477,13 +31268,38 @@ var ParallAgentGateway = class {
|
|
|
30477
31268
|
const forkPrefix = buildForkResultPrefix(pendingFork);
|
|
30478
31269
|
this.dispatchState.mainDispatching = true;
|
|
30479
31270
|
this.dispatchState.mainCurrentTargetId = event.targetId;
|
|
31271
|
+
this.mainCurrentGroupKey = this.dispatchGroupKey(event);
|
|
30480
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
|
+
}
|
|
30481
31296
|
try {
|
|
30482
31297
|
await this.emitDispatchReceived(event);
|
|
30483
31298
|
} catch (err) {
|
|
30484
31299
|
this.opts.log?.warn?.(`mark-received failed, leaving unacked for retry: ${String(err)}`);
|
|
30485
31300
|
this.dispatchState.mainDispatching = false;
|
|
30486
31301
|
this.dispatchState.mainCurrentTargetId = void 0;
|
|
31302
|
+
this.mainCurrentGroupKey = void 0;
|
|
30487
31303
|
this.dispatchState.mainPreDispatchBranchPoint = void 0;
|
|
30488
31304
|
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
30489
31305
|
return false;
|
|
@@ -30504,7 +31320,11 @@ var ParallAgentGateway = class {
|
|
|
30504
31320
|
return false;
|
|
30505
31321
|
}
|
|
30506
31322
|
this.dispatchState.mainBuffer.push(event);
|
|
30507
|
-
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))) {
|
|
30508
31328
|
this.opts.log?.info(`steer injected for ${event.messageId} (will drain for bookkeeping)`);
|
|
30509
31329
|
}
|
|
30510
31330
|
if (!this.dispatchState.mainDispatching && !this.draining && this.dispatchState.mainBuffer.length > 0) {
|
|
@@ -30675,8 +31495,10 @@ var ParallAgentGateway = class {
|
|
|
30675
31495
|
try {
|
|
30676
31496
|
const dispatched = await this.handleInboundEvent(event);
|
|
30677
31497
|
if (dispatched) {
|
|
30678
|
-
|
|
30679
|
-
|
|
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
|
+
}
|
|
30680
31502
|
} else {
|
|
30681
31503
|
this.dispatchedMessages.delete(data.id);
|
|
30682
31504
|
}
|
|
@@ -30685,7 +31507,21 @@ var ParallAgentGateway = class {
|
|
|
30685
31507
|
this.dispatchedMessages.delete(data.id);
|
|
30686
31508
|
}
|
|
30687
31509
|
}
|
|
30688
|
-
|
|
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) {
|
|
30689
31525
|
if (this.shuttingDown)
|
|
30690
31526
|
return false;
|
|
30691
31527
|
const dedupeKey = `${task.id}:${task.updated_at}`;
|
|
@@ -30714,7 +31550,8 @@ var ParallAgentGateway = class {
|
|
|
30714
31550
|
body: parts.join("\n"),
|
|
30715
31551
|
sentAt: task.updated_at ?? task.created_at,
|
|
30716
31552
|
ackSourceType: "task_activity",
|
|
30717
|
-
ackSourceId
|
|
31553
|
+
ackSourceId,
|
|
31554
|
+
dispatchEventId
|
|
30718
31555
|
};
|
|
30719
31556
|
const dispatched = await this.handleInboundEvent(event);
|
|
30720
31557
|
if (!dispatched) {
|
|
@@ -30738,7 +31575,7 @@ var ParallAgentGateway = class {
|
|
|
30738
31575
|
this.opts.log?.info(`skipping stale task dispatch ${ackSourceId ?? taskId} \u2014 assigned to ${task.assignee_id}, creator ${task.creator_id}`);
|
|
30739
31576
|
return true;
|
|
30740
31577
|
}
|
|
30741
|
-
return this.handleTaskAssignment(task, ackSourceId);
|
|
31578
|
+
return this.handleTaskAssignment(task, ackSourceId, opts.dispatchEventId);
|
|
30742
31579
|
}
|
|
30743
31580
|
async handleTaskComment(commentId, taskId, actorId, deliveryReason) {
|
|
30744
31581
|
if (this.shuttingDown)
|
|
@@ -30949,6 +31786,75 @@ var ParallAgentGateway = class {
|
|
|
30949
31786
|
return true;
|
|
30950
31787
|
return this.handleExternalTriggerRun(run);
|
|
30951
31788
|
}
|
|
31789
|
+
// fetchAndHandleChannelMessage resolves a channel_message dispatch to its
|
|
31790
|
+
// durable ChannelMessage + conversation and hands it to the inbound
|
|
31791
|
+
// pipeline. targetId = the ChannelConversation id, so per-conversation
|
|
31792
|
+
// multi-turn continuity rides the same per-target session mechanics as
|
|
31793
|
+
// chats. Design: docs/engineering-design/external-im-channel-design.md.
|
|
31794
|
+
async fetchAndHandleChannelMessage(messageId) {
|
|
31795
|
+
if (this.shuttingDown)
|
|
31796
|
+
return false;
|
|
31797
|
+
const claimKey = `channel_message:${messageId}`;
|
|
31798
|
+
if (!this.tryClaimMessage(claimKey))
|
|
31799
|
+
return false;
|
|
31800
|
+
let msg = null;
|
|
31801
|
+
let conv = null;
|
|
31802
|
+
try {
|
|
31803
|
+
msg = await this.opts.client.getChannelMessage(this.opts.config.org_id, messageId);
|
|
31804
|
+
conv = await this.opts.client.getChannelConversation(this.opts.config.org_id, msg.conversation_id);
|
|
31805
|
+
} catch (err) {
|
|
31806
|
+
const status = err?.status;
|
|
31807
|
+
if (status === 404) {
|
|
31808
|
+
this.opts.log?.warn(`channel message ${messageId} not accessible (404), acking stale dispatch`);
|
|
31809
|
+
return true;
|
|
31810
|
+
}
|
|
31811
|
+
this.dispatchedMessages.delete(claimKey);
|
|
31812
|
+
this.opts.log?.warn(`channel message fetch failed for ${messageId}, leaving pending: ${String(err)}`);
|
|
31813
|
+
return false;
|
|
31814
|
+
}
|
|
31815
|
+
if (!msg || !conv) {
|
|
31816
|
+
return true;
|
|
31817
|
+
}
|
|
31818
|
+
this.opts.log?.info(`channel message: ${msg.id} (conversation ${conv.id})`);
|
|
31819
|
+
let provider = this.channelConnectionProviders.get(conv.connection_id);
|
|
31820
|
+
if (!provider) {
|
|
31821
|
+
try {
|
|
31822
|
+
const connection = await this.opts.client.getChannelConnection(this.opts.config.org_id, conv.connection_id);
|
|
31823
|
+
provider = connection.provider;
|
|
31824
|
+
this.channelConnectionProviders.set(conv.connection_id, provider);
|
|
31825
|
+
} catch {
|
|
31826
|
+
provider = void 0;
|
|
31827
|
+
}
|
|
31828
|
+
}
|
|
31829
|
+
const event = {
|
|
31830
|
+
type: "channel_message",
|
|
31831
|
+
targetId: conv.id,
|
|
31832
|
+
targetName: conv.external_user_name || conv.external_conversation_id,
|
|
31833
|
+
targetType: "channel_conversation",
|
|
31834
|
+
senderId: msg.external_user_id || "external",
|
|
31835
|
+
senderName: msg.external_user_name || msg.external_user_id || "external user",
|
|
31836
|
+
messageId: msg.id,
|
|
31837
|
+
body: msg.text,
|
|
31838
|
+
sentAt: msg.received_at,
|
|
31839
|
+
channelProvider: provider,
|
|
31840
|
+
channelConversationType: conv.conversation_type || void 0,
|
|
31841
|
+
channelExternalConversationId: conv.external_conversation_id,
|
|
31842
|
+
channelExternalMessageId: msg.external_message_id,
|
|
31843
|
+
ackSourceType: "channel_message",
|
|
31844
|
+
ackSourceId: msg.id
|
|
31845
|
+
};
|
|
31846
|
+
let dispatched;
|
|
31847
|
+
try {
|
|
31848
|
+
dispatched = await this.handleInboundEvent(event);
|
|
31849
|
+
} catch (err) {
|
|
31850
|
+
this.dispatchedMessages.delete(claimKey);
|
|
31851
|
+
throw err;
|
|
31852
|
+
}
|
|
31853
|
+
if (!dispatched) {
|
|
31854
|
+
this.dispatchedMessages.delete(claimKey);
|
|
31855
|
+
}
|
|
31856
|
+
return dispatched;
|
|
31857
|
+
}
|
|
30952
31858
|
async handleExternalTriggerRun(run) {
|
|
30953
31859
|
if (this.shuttingDown)
|
|
30954
31860
|
return false;
|
|
@@ -31077,72 +31983,46 @@ var ParallAgentGateway = class {
|
|
|
31077
31983
|
}
|
|
31078
31984
|
processed++;
|
|
31079
31985
|
try {
|
|
31080
|
-
|
|
31986
|
+
const ackItem = () => {
|
|
31987
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
31988
|
+
});
|
|
31989
|
+
};
|
|
31081
31990
|
if (item.event_type === "task_assign" && item.task_id) {
|
|
31082
31991
|
try {
|
|
31083
|
-
|
|
31992
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
|
|
31993
|
+
dispatchEventId
|
|
31994
|
+
}), ackItem);
|
|
31084
31995
|
} catch (err) {
|
|
31085
31996
|
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
31086
31997
|
continue;
|
|
31087
31998
|
}
|
|
31088
31999
|
} else if (item.event_type === "task_update" && item.task_id) {
|
|
31089
32000
|
try {
|
|
31090
|
-
|
|
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);
|
|
31091
32005
|
} catch (err) {
|
|
31092
32006
|
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
31093
32007
|
continue;
|
|
31094
32008
|
}
|
|
31095
32009
|
} else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
|
|
31096
|
-
|
|
32010
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleTaskComment(item.source_id, item.task_id ?? "", item.actor_id, item.delivery_reason), ackItem);
|
|
31097
32011
|
} else if (item.event_type === "wiki_comment" && item.source_id) {
|
|
31098
|
-
|
|
32012
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleWikiComment(item.source_id, item.actor_id, item.delivery_reason), ackItem);
|
|
31099
32013
|
} else if (item.event_type === "schedule.fire" && item.source_id) {
|
|
31100
|
-
|
|
32014
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleScheduleFire(item.source_id, item.actor_id), ackItem);
|
|
31101
32015
|
} else if (item.event_type === "external_trigger" && item.source_id) {
|
|
31102
|
-
|
|
32016
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleExternalTriggerRun(item.source_id), ackItem);
|
|
32017
|
+
} else if (item.event_type === "channel_message" && item.source_id) {
|
|
32018
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleChannelMessage(item.source_id), ackItem);
|
|
31103
32019
|
} else if (item.event_type === "approval_decided" && item.source_id) {
|
|
31104
|
-
|
|
32020
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null), ackItem);
|
|
31105
32021
|
} else if (item.event_type === "message" && item.source_id && item.chat_id) {
|
|
31106
|
-
|
|
31107
|
-
|
|
31108
|
-
|
|
31109
|
-
|
|
31110
|
-
try {
|
|
31111
|
-
msg = await this.opts.client.getMessage(item.source_id);
|
|
31112
|
-
} catch (err) {
|
|
31113
|
-
const status = err?.status;
|
|
31114
|
-
if (status === 404) {
|
|
31115
|
-
msg = null;
|
|
31116
|
-
} else {
|
|
31117
|
-
msgFetchFailed = true;
|
|
31118
|
-
this.opts.log?.warn(`catch-up message fetch failed for ${item.source_id}, leaving pending: ${String(err)}`);
|
|
31119
|
-
}
|
|
31120
|
-
}
|
|
31121
|
-
if (msgFetchFailed) {
|
|
31122
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31123
|
-
continue;
|
|
31124
|
-
}
|
|
31125
|
-
if (!msg || msg.sender_id === this.opts.agentUserId) {
|
|
31126
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31127
|
-
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
31128
|
-
});
|
|
31129
|
-
continue;
|
|
31130
|
-
}
|
|
31131
|
-
const decision = await this.buildMessageDispatchDecision(item.chat_id, msg);
|
|
31132
|
-
if (decision.action === "retry") {
|
|
31133
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31134
|
-
continue;
|
|
31135
|
-
}
|
|
31136
|
-
if (decision.action === "skip") {
|
|
31137
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31138
|
-
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
31139
|
-
});
|
|
31140
|
-
continue;
|
|
31141
|
-
}
|
|
31142
|
-
dispatched = await this.handleInboundEvent(decision.event);
|
|
31143
|
-
}
|
|
31144
|
-
if (dispatched) {
|
|
31145
|
-
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
|
|
31146
32026
|
});
|
|
31147
32027
|
}
|
|
31148
32028
|
} catch (err) {
|
|
@@ -31208,6 +32088,14 @@ ${fullSummary}` : fullSummary;
|
|
|
31208
32088
|
this.abortFork(targetId, "ws reconnect");
|
|
31209
32089
|
}
|
|
31210
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
|
+
}
|
|
31211
32099
|
const intervalSec = data.heartbeat_interval > 0 ? data.heartbeat_interval : 30;
|
|
31212
32100
|
try {
|
|
31213
32101
|
const count = await fetchAllChats(client, config.org_id, this.chatInfoMap);
|
|
@@ -31278,6 +32166,10 @@ ${fullSummary}` : fullSummary;
|
|
|
31278
32166
|
}
|
|
31279
32167
|
if (this.heartbeatTimer)
|
|
31280
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
|
+
}
|
|
31281
32173
|
await this.opts.onBeforeDisconnect?.();
|
|
31282
32174
|
this.opts.ws.disconnect();
|
|
31283
32175
|
this.opts.log?.info(`disconnected`);
|
|
@@ -31288,8 +32180,8 @@ ${fullSummary}` : fullSummary;
|
|
|
31288
32180
|
import { execSync } from "node:child_process";
|
|
31289
32181
|
import { constants } from "node:fs";
|
|
31290
32182
|
import * as fsSync from "node:fs";
|
|
31291
|
-
import * as
|
|
31292
|
-
import * as
|
|
32183
|
+
import * as fs3 from "node:fs/promises";
|
|
32184
|
+
import * as path3 from "node:path";
|
|
31293
32185
|
var DEFAULT_MAX_TOTAL_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
31294
32186
|
var DEFAULT_ATTACHMENT_CACHE_MAX_BYTES = 512 * 1024 * 1024;
|
|
31295
32187
|
var DEFAULT_ATTACHMENT_DOWNLOAD_TIMEOUT_MS = 3e4;
|
|
@@ -31319,11 +32211,11 @@ async function prepareLocalImageAttachments(event, context2, opts) {
|
|
|
31319
32211
|
};
|
|
31320
32212
|
}
|
|
31321
32213
|
const rootDir = await ensureAttachmentRootDir(opts.workspaceDir);
|
|
31322
|
-
const messageDir =
|
|
32214
|
+
const messageDir = path3.join(rootDir, sanitizePathSegment(event.messageId));
|
|
31323
32215
|
await ensurePathIsNotSymlink(messageDir);
|
|
31324
|
-
await
|
|
32216
|
+
await fs3.mkdir(messageDir, { recursive: true });
|
|
31325
32217
|
await ensurePathIsNotSymlink(messageDir);
|
|
31326
|
-
const activeMessageDir =
|
|
32218
|
+
const activeMessageDir = path3.resolve(messageDir);
|
|
31327
32219
|
activeAttachmentDirs.add(activeMessageDir);
|
|
31328
32220
|
const maintenanceCooldownMs = opts.maintenanceCooldownMs ?? DEFAULT_MAINTENANCE_COOLDOWN_MS;
|
|
31329
32221
|
const maintenancePromise = scheduleAttachmentMaintenance(rootDir, {
|
|
@@ -31340,7 +32232,7 @@ async function prepareLocalImageAttachments(event, context2, opts) {
|
|
|
31340
32232
|
const notes = [];
|
|
31341
32233
|
let downloadedBytes = 0;
|
|
31342
32234
|
for (const att of imageAttachments) {
|
|
31343
|
-
const localPath =
|
|
32235
|
+
const localPath = path3.join(messageDir, localFileName(att.id, att.fileName, att.mimeType));
|
|
31344
32236
|
const downloadTimeoutMs = opts.downloadTimeoutMs ?? DEFAULT_ATTACHMENT_DOWNLOAD_TIMEOUT_MS;
|
|
31345
32237
|
const fetchFresh = async () => {
|
|
31346
32238
|
const fileInfo = await withTimeout(context2.client.getFileUrl(att.id), downloadTimeoutMs, `file URL lookup timed out after ${downloadTimeoutMs}ms`);
|
|
@@ -31397,7 +32289,7 @@ async function appendPreparedLocalAttachmentRefs(body, event, context2, opts) {
|
|
|
31397
32289
|
return { body: appendLocalAttachmentRefs(body, attachments), attachments };
|
|
31398
32290
|
}
|
|
31399
32291
|
function pinLocalAttachmentPaths(images) {
|
|
31400
|
-
const dirs = new Set(images.map((image) =>
|
|
32292
|
+
const dirs = new Set(images.map((image) => path3.resolve(path3.dirname(image.localPath))));
|
|
31401
32293
|
for (const dir of dirs) {
|
|
31402
32294
|
activeAttachmentDirs.add(dir);
|
|
31403
32295
|
}
|
|
@@ -31412,7 +32304,7 @@ function pinLocalAttachmentPaths(images) {
|
|
|
31412
32304
|
};
|
|
31413
32305
|
}
|
|
31414
32306
|
function attachmentRootDir(workspaceDir) {
|
|
31415
|
-
return
|
|
32307
|
+
return path3.join(path3.resolve(workspaceDir), ".parall", "attachments");
|
|
31416
32308
|
}
|
|
31417
32309
|
function ensureLocalAttachmentGitExclude(workingDirectory) {
|
|
31418
32310
|
try {
|
|
@@ -31421,8 +32313,8 @@ function ensureLocalAttachmentGitExclude(workingDirectory) {
|
|
|
31421
32313
|
encoding: "utf8",
|
|
31422
32314
|
stdio: ["ignore", "pipe", "ignore"]
|
|
31423
32315
|
}).trim();
|
|
31424
|
-
const excludePath =
|
|
31425
|
-
fsSync.mkdirSync(
|
|
32316
|
+
const excludePath = path3.isAbsolute(rel) ? rel : path3.join(workingDirectory, rel);
|
|
32317
|
+
fsSync.mkdirSync(path3.dirname(excludePath), { recursive: true });
|
|
31426
32318
|
const existing = fsSync.existsSync(excludePath) ? fsSync.readFileSync(excludePath, "utf8") : "";
|
|
31427
32319
|
if (existing.split(/\r?\n/).some((line) => line.trim() === ".parall/"))
|
|
31428
32320
|
return;
|
|
@@ -31458,18 +32350,18 @@ function scheduleAttachmentMaintenance(rootDir, opts) {
|
|
|
31458
32350
|
return run;
|
|
31459
32351
|
}
|
|
31460
32352
|
async function ensureAttachmentRootDir(workspaceDir) {
|
|
31461
|
-
const workspaceRoot =
|
|
31462
|
-
const parallDir =
|
|
32353
|
+
const workspaceRoot = path3.resolve(workspaceDir);
|
|
32354
|
+
const parallDir = path3.join(workspaceRoot, ".parall");
|
|
31463
32355
|
const rootDir = attachmentRootDir(workspaceRoot);
|
|
31464
|
-
await
|
|
32356
|
+
await fs3.mkdir(workspaceRoot, { recursive: true });
|
|
31465
32357
|
await ensurePathIsNotSymlink(parallDir);
|
|
31466
|
-
await
|
|
32358
|
+
await fs3.mkdir(parallDir, { recursive: true, mode: 448 });
|
|
31467
32359
|
await ensurePathIsNotSymlink(parallDir);
|
|
31468
32360
|
await ensurePathIsNotSymlink(rootDir);
|
|
31469
|
-
await
|
|
32361
|
+
await fs3.mkdir(rootDir, { recursive: true, mode: 448 });
|
|
31470
32362
|
await ensurePathIsNotSymlink(rootDir);
|
|
31471
|
-
const realWorkspace = await
|
|
31472
|
-
const realRoot = await
|
|
32363
|
+
const realWorkspace = await fs3.realpath(workspaceRoot);
|
|
32364
|
+
const realRoot = await fs3.realpath(rootDir);
|
|
31473
32365
|
if (!isPathInside(realRoot, realWorkspace)) {
|
|
31474
32366
|
throw new Error(`attachment root escapes workspace: ${rootDir}`);
|
|
31475
32367
|
}
|
|
@@ -31477,7 +32369,7 @@ async function ensureAttachmentRootDir(workspaceDir) {
|
|
|
31477
32369
|
}
|
|
31478
32370
|
async function ensurePathIsNotSymlink(filePath) {
|
|
31479
32371
|
try {
|
|
31480
|
-
const stat = await
|
|
32372
|
+
const stat = await fs3.lstat(filePath);
|
|
31481
32373
|
if (stat.isSymbolicLink()) {
|
|
31482
32374
|
throw new Error(`refusing to use symlinked attachment path ${filePath}`);
|
|
31483
32375
|
}
|
|
@@ -31488,8 +32380,8 @@ async function ensurePathIsNotSymlink(filePath) {
|
|
|
31488
32380
|
}
|
|
31489
32381
|
}
|
|
31490
32382
|
function isPathInside(childPath, parentPath) {
|
|
31491
|
-
const rel =
|
|
31492
|
-
return rel === "" || !!rel && !rel.startsWith("..") && !
|
|
32383
|
+
const rel = path3.relative(parentPath, childPath);
|
|
32384
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !path3.isAbsolute(rel);
|
|
31493
32385
|
}
|
|
31494
32386
|
async function existingUsableFile(filePath, expectedSize, rootDir) {
|
|
31495
32387
|
try {
|
|
@@ -31500,30 +32392,30 @@ async function existingUsableFile(filePath, expectedSize, rootDir) {
|
|
|
31500
32392
|
}
|
|
31501
32393
|
}
|
|
31502
32394
|
async function localFileStatInsideRoot(filePath, rootDir) {
|
|
31503
|
-
const stat = await
|
|
32395
|
+
const stat = await fs3.lstat(filePath);
|
|
31504
32396
|
if (stat.isSymbolicLink()) {
|
|
31505
32397
|
throw new Error(`refusing to use symlinked attachment file ${filePath}`);
|
|
31506
32398
|
}
|
|
31507
32399
|
if (!stat.isFile()) {
|
|
31508
32400
|
throw new Error(`attachment path is not a file ${filePath}`);
|
|
31509
32401
|
}
|
|
31510
|
-
const realRoot = await
|
|
31511
|
-
const realFile = await
|
|
32402
|
+
const realRoot = await fs3.realpath(rootDir);
|
|
32403
|
+
const realFile = await fs3.realpath(filePath);
|
|
31512
32404
|
if (!isPathInside(realFile, realRoot)) {
|
|
31513
32405
|
throw new Error(`attachment file escapes workspace: ${filePath}`);
|
|
31514
32406
|
}
|
|
31515
32407
|
return stat;
|
|
31516
32408
|
}
|
|
31517
32409
|
async function localDirectoryStatInsideRoot(dirPath, rootDir) {
|
|
31518
|
-
const stat = await
|
|
32410
|
+
const stat = await fs3.lstat(dirPath);
|
|
31519
32411
|
if (stat.isSymbolicLink()) {
|
|
31520
32412
|
throw new Error(`refusing to use symlinked attachment directory ${dirPath}`);
|
|
31521
32413
|
}
|
|
31522
32414
|
if (!stat.isDirectory()) {
|
|
31523
32415
|
throw new Error(`attachment path is not a directory ${dirPath}`);
|
|
31524
32416
|
}
|
|
31525
|
-
const realRoot = await
|
|
31526
|
-
const realDir = await
|
|
32417
|
+
const realRoot = await fs3.realpath(rootDir);
|
|
32418
|
+
const realDir = await fs3.realpath(dirPath);
|
|
31527
32419
|
if (!isPathInside(realDir, realRoot)) {
|
|
31528
32420
|
throw new Error(`attachment directory escapes workspace: ${dirPath}`);
|
|
31529
32421
|
}
|
|
@@ -31531,7 +32423,7 @@ async function localDirectoryStatInsideRoot(dirPath, rootDir) {
|
|
|
31531
32423
|
}
|
|
31532
32424
|
async function openLocalFileInsideRoot(filePath, rootDir) {
|
|
31533
32425
|
const checkedStat = await localFileStatInsideRoot(filePath, rootDir);
|
|
31534
|
-
const file = await
|
|
32426
|
+
const file = await fs3.open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
31535
32427
|
let keepOpen = false;
|
|
31536
32428
|
try {
|
|
31537
32429
|
const openedStat = await file.stat();
|
|
@@ -31547,8 +32439,8 @@ async function openLocalFileInsideRoot(filePath, rootDir) {
|
|
|
31547
32439
|
}
|
|
31548
32440
|
}
|
|
31549
32441
|
async function openLocalTempFileInsideRoot(filePath, rootDir) {
|
|
31550
|
-
await localDirectoryStatInsideRoot(
|
|
31551
|
-
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);
|
|
31552
32444
|
let keepOpen = false;
|
|
31553
32445
|
try {
|
|
31554
32446
|
const checkedStat = await localFileStatInsideRoot(filePath, rootDir);
|
|
@@ -31573,7 +32465,7 @@ async function assertLocalFileIdentity(filePath, rootDir, expected) {
|
|
|
31573
32465
|
async function removeLocalFileIfInside(filePath, rootDir) {
|
|
31574
32466
|
try {
|
|
31575
32467
|
await localFileStatInsideRoot(filePath, rootDir);
|
|
31576
|
-
await
|
|
32468
|
+
await fs3.rm(filePath, { force: true });
|
|
31577
32469
|
} catch {
|
|
31578
32470
|
}
|
|
31579
32471
|
}
|
|
@@ -31586,7 +32478,7 @@ function sameFile(a, b) {
|
|
|
31586
32478
|
async function cleanupOldAttachmentFiles(rootDir, ttlMs, log, preserveDirs) {
|
|
31587
32479
|
let entries;
|
|
31588
32480
|
try {
|
|
31589
|
-
entries = await
|
|
32481
|
+
entries = await fs3.readdir(rootDir, { withFileTypes: true });
|
|
31590
32482
|
} catch {
|
|
31591
32483
|
return;
|
|
31592
32484
|
}
|
|
@@ -31594,15 +32486,15 @@ async function cleanupOldAttachmentFiles(rootDir, ttlMs, log, preserveDirs) {
|
|
|
31594
32486
|
await Promise.all(entries.map(async (entry) => {
|
|
31595
32487
|
if (!entry.isDirectory())
|
|
31596
32488
|
return;
|
|
31597
|
-
const fullPath =
|
|
32489
|
+
const fullPath = path3.join(rootDir, entry.name);
|
|
31598
32490
|
try {
|
|
31599
|
-
if (preserveDirs?.has(
|
|
32491
|
+
if (preserveDirs?.has(path3.resolve(fullPath)))
|
|
31600
32492
|
return;
|
|
31601
|
-
const stat = await
|
|
32493
|
+
const stat = await fs3.lstat(fullPath);
|
|
31602
32494
|
if (!stat.isDirectory())
|
|
31603
32495
|
return;
|
|
31604
32496
|
if (stat.mtimeMs < cutoff) {
|
|
31605
|
-
await
|
|
32497
|
+
await fs3.rm(fullPath, { recursive: true, force: true });
|
|
31606
32498
|
}
|
|
31607
32499
|
} catch (err) {
|
|
31608
32500
|
log?.warn?.(`agent-core: failed to clean attachment temp dir ${fullPath}: ${String(err)}`);
|
|
@@ -31614,7 +32506,7 @@ async function pruneAttachmentCache(rootDir, maxBytes, log, preserveDirs) {
|
|
|
31614
32506
|
return;
|
|
31615
32507
|
let entries;
|
|
31616
32508
|
try {
|
|
31617
|
-
entries = await
|
|
32509
|
+
entries = await fs3.readdir(rootDir, { withFileTypes: true });
|
|
31618
32510
|
} catch {
|
|
31619
32511
|
return;
|
|
31620
32512
|
}
|
|
@@ -31623,9 +32515,9 @@ async function pruneAttachmentCache(rootDir, maxBytes, log, preserveDirs) {
|
|
|
31623
32515
|
for (const entry of entries) {
|
|
31624
32516
|
if (!entry.isDirectory())
|
|
31625
32517
|
continue;
|
|
31626
|
-
const fullPath =
|
|
32518
|
+
const fullPath = path3.join(rootDir, entry.name);
|
|
31627
32519
|
try {
|
|
31628
|
-
const stat = await
|
|
32520
|
+
const stat = await fs3.lstat(fullPath);
|
|
31629
32521
|
if (!stat.isDirectory())
|
|
31630
32522
|
continue;
|
|
31631
32523
|
const size = await directorySize(fullPath);
|
|
@@ -31641,10 +32533,10 @@ async function pruneAttachmentCache(rootDir, maxBytes, log, preserveDirs) {
|
|
|
31641
32533
|
for (const dir of dirs) {
|
|
31642
32534
|
if (total <= maxBytes)
|
|
31643
32535
|
break;
|
|
31644
|
-
if (preserveDirs?.has(
|
|
32536
|
+
if (preserveDirs?.has(path3.resolve(dir.path)))
|
|
31645
32537
|
continue;
|
|
31646
32538
|
try {
|
|
31647
|
-
await
|
|
32539
|
+
await fs3.rm(dir.path, { recursive: true, force: true });
|
|
31648
32540
|
total -= dir.size;
|
|
31649
32541
|
} catch (err) {
|
|
31650
32542
|
log?.warn?.(`agent-core: failed to prune attachment cache dir ${dir.path}: ${String(err)}`);
|
|
@@ -31653,12 +32545,12 @@ async function pruneAttachmentCache(rootDir, maxBytes, log, preserveDirs) {
|
|
|
31653
32545
|
}
|
|
31654
32546
|
async function directorySize(dirPath) {
|
|
31655
32547
|
let total = 0;
|
|
31656
|
-
const entries = await
|
|
32548
|
+
const entries = await fs3.readdir(dirPath, { withFileTypes: true });
|
|
31657
32549
|
for (const entry of entries) {
|
|
31658
|
-
const fullPath =
|
|
32550
|
+
const fullPath = path3.join(dirPath, entry.name);
|
|
31659
32551
|
let stat;
|
|
31660
32552
|
try {
|
|
31661
|
-
stat = await
|
|
32553
|
+
stat = await fs3.lstat(fullPath);
|
|
31662
32554
|
} catch {
|
|
31663
32555
|
continue;
|
|
31664
32556
|
}
|
|
@@ -31673,10 +32565,10 @@ async function directorySize(dirPath) {
|
|
|
31673
32565
|
return total;
|
|
31674
32566
|
}
|
|
31675
32567
|
function activeDirsForRoot(rootDir) {
|
|
31676
|
-
const root =
|
|
32568
|
+
const root = path3.resolve(rootDir);
|
|
31677
32569
|
const dirs = /* @__PURE__ */ new Set();
|
|
31678
32570
|
for (const dir of activeAttachmentDirs) {
|
|
31679
|
-
if (dir === root || dir.startsWith(`${root}${
|
|
32571
|
+
if (dir === root || dir.startsWith(`${root}${path3.sep}`)) {
|
|
31680
32572
|
dirs.add(dir);
|
|
31681
32573
|
}
|
|
31682
32574
|
}
|
|
@@ -31773,9 +32665,9 @@ async function writeResponseToFileWithLimit(res, filePath, maxBytes, rootDir) {
|
|
|
31773
32665
|
}
|
|
31774
32666
|
writtenStat = await file.stat();
|
|
31775
32667
|
await closeFile();
|
|
31776
|
-
await localDirectoryStatInsideRoot(
|
|
32668
|
+
await localDirectoryStatInsideRoot(path3.dirname(filePath), rootDir);
|
|
31777
32669
|
await assertLocalFileIdentity(tmpPath, rootDir, writtenStat);
|
|
31778
|
-
await
|
|
32670
|
+
await fs3.rename(tmpPath, filePath);
|
|
31779
32671
|
completed = true;
|
|
31780
32672
|
return written;
|
|
31781
32673
|
} finally {
|
|
@@ -31791,9 +32683,9 @@ async function writeResponseToFileWithLimit(res, filePath, maxBytes, rootDir) {
|
|
|
31791
32683
|
}
|
|
31792
32684
|
}
|
|
31793
32685
|
function localFileName(attachmentId, fileName, mimeType) {
|
|
31794
|
-
const safeName = sanitizePathSegment(
|
|
31795
|
-
const ext =
|
|
31796
|
-
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;
|
|
31797
32689
|
return `${sanitizePathSegment(attachmentId)}-${stem}${ext}`;
|
|
31798
32690
|
}
|
|
31799
32691
|
function extensionForMime(mimeType) {
|
|
@@ -31859,7 +32751,7 @@ function parseContentLength(value) {
|
|
|
31859
32751
|
// dist/gateway.js
|
|
31860
32752
|
import * as crypto2 from "node:crypto";
|
|
31861
32753
|
import * as os2 from "node:os";
|
|
31862
|
-
import * as
|
|
32754
|
+
import * as path7 from "node:path";
|
|
31863
32755
|
|
|
31864
32756
|
// dist/runtime.js
|
|
31865
32757
|
var runtime = null;
|
|
@@ -31917,15 +32809,15 @@ function buildOrchestratorSessionKey(accountId) {
|
|
|
31917
32809
|
}
|
|
31918
32810
|
|
|
31919
32811
|
// dist/config-manager.js
|
|
31920
|
-
import * as
|
|
31921
|
-
import * as
|
|
32812
|
+
import * as fs4 from "node:fs";
|
|
32813
|
+
import * as path4 from "node:path";
|
|
31922
32814
|
var CACHE_FILENAME = "parall-platform-config.json";
|
|
31923
32815
|
function cachePath(stateDir) {
|
|
31924
|
-
return
|
|
32816
|
+
return path4.join(stateDir, CACHE_FILENAME);
|
|
31925
32817
|
}
|
|
31926
32818
|
function loadCachedConfig(stateDir) {
|
|
31927
32819
|
try {
|
|
31928
|
-
const raw =
|
|
32820
|
+
const raw = fs4.readFileSync(cachePath(stateDir), "utf-8");
|
|
31929
32821
|
return JSON.parse(raw);
|
|
31930
32822
|
} catch {
|
|
31931
32823
|
return null;
|
|
@@ -31939,14 +32831,14 @@ function saveCachedConfig(stateDir, config) {
|
|
|
31939
32831
|
};
|
|
31940
32832
|
const filePath = cachePath(stateDir);
|
|
31941
32833
|
const tmpPath = `${filePath}.tmp`;
|
|
31942
|
-
|
|
31943
|
-
|
|
31944
|
-
|
|
32834
|
+
fs4.mkdirSync(path4.dirname(filePath), { recursive: true });
|
|
32835
|
+
fs4.writeFileSync(tmpPath, JSON.stringify(cached, null, 2), "utf-8");
|
|
32836
|
+
fs4.renameSync(tmpPath, filePath);
|
|
31945
32837
|
}
|
|
31946
32838
|
function applyToOpenClawConfig(configPath, platformConfig, credentials) {
|
|
31947
32839
|
let existing = {};
|
|
31948
32840
|
try {
|
|
31949
|
-
const raw =
|
|
32841
|
+
const raw = fs4.readFileSync(configPath, "utf-8");
|
|
31950
32842
|
existing = JSON.parse(raw);
|
|
31951
32843
|
} catch {
|
|
31952
32844
|
}
|
|
@@ -32018,9 +32910,9 @@ function applyToOpenClawConfig(configPath, platformConfig, credentials) {
|
|
|
32018
32910
|
agents.defaults = cleanedExisting;
|
|
32019
32911
|
existing.agents = agents;
|
|
32020
32912
|
const tmpPath = `${configPath}.tmp`;
|
|
32021
|
-
|
|
32022
|
-
|
|
32023
|
-
|
|
32913
|
+
fs4.mkdirSync(path4.dirname(configPath), { recursive: true });
|
|
32914
|
+
fs4.writeFileSync(tmpPath, JSON.stringify(existing, null, 2), "utf-8");
|
|
32915
|
+
fs4.renameSync(tmpPath, configPath);
|
|
32024
32916
|
}
|
|
32025
32917
|
async function fetchAndApplyPlatformConfig(opts) {
|
|
32026
32918
|
const { client, stateDir, configPath, credentials, log } = opts;
|
|
@@ -32059,7 +32951,7 @@ async function fetchAndApplyPlatformConfig(opts) {
|
|
|
32059
32951
|
|
|
32060
32952
|
// dist/wiki-helper.js
|
|
32061
32953
|
import { spawn, spawnSync } from "node:child_process";
|
|
32062
|
-
import
|
|
32954
|
+
import path5 from "node:path";
|
|
32063
32955
|
var DEFAULT_SYNC_TIMEOUT_MS = 9e4;
|
|
32064
32956
|
var DEFAULT_WATCH_INTERVAL_SEC = 30;
|
|
32065
32957
|
function isCommandMissing(error) {
|
|
@@ -32074,7 +32966,7 @@ function resolveParallCli() {
|
|
|
32074
32966
|
return _cli;
|
|
32075
32967
|
}
|
|
32076
32968
|
function resolveMountRoot(stateDir) {
|
|
32077
|
-
return process.env.PRLL_WIKI_MOUNT_ROOT?.trim() ||
|
|
32969
|
+
return process.env.PRLL_WIKI_MOUNT_ROOT?.trim() || path5.join(stateDir, "workspace");
|
|
32078
32970
|
}
|
|
32079
32971
|
function resolveWatchIntervalSec() {
|
|
32080
32972
|
const raw = process.env.PRLL_WIKI_REFRESH_INTERVAL_SEC?.trim();
|
|
@@ -32190,7 +33082,7 @@ async function startWikiHelper(params) {
|
|
|
32190
33082
|
// dist/oc-session.js
|
|
32191
33083
|
import { randomUUID } from "node:crypto";
|
|
32192
33084
|
import { existsSync as existsSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
32193
|
-
import { join as
|
|
33085
|
+
import { join as join4, resolve as resolve2 } from "node:path";
|
|
32194
33086
|
var CURRENT_SESSION_VERSION = 3;
|
|
32195
33087
|
function generateId(existing) {
|
|
32196
33088
|
for (let i = 0; i < 100; i++) {
|
|
@@ -32336,7 +33228,7 @@ var SessionManager = class _SessionManager {
|
|
|
32336
33228
|
this.leafId = null;
|
|
32337
33229
|
this.flushed = false;
|
|
32338
33230
|
const ts = timestamp.replace(/[:.]/g, "-");
|
|
32339
|
-
this.sessionFile =
|
|
33231
|
+
this.sessionFile = join4(this.sessionDir, `${ts}_${this.sessionId}.jsonl`);
|
|
32340
33232
|
}
|
|
32341
33233
|
buildIndex() {
|
|
32342
33234
|
this.byId.clear();
|
|
@@ -32383,14 +33275,14 @@ var SessionManager = class _SessionManager {
|
|
|
32383
33275
|
}
|
|
32384
33276
|
// -- Branching -------------------------------------------------------------
|
|
32385
33277
|
getBranch(fromId) {
|
|
32386
|
-
const
|
|
33278
|
+
const path8 = [];
|
|
32387
33279
|
const startId = fromId ?? this.leafId;
|
|
32388
33280
|
let current = startId ? this.byId.get(startId) : void 0;
|
|
32389
33281
|
while (current) {
|
|
32390
|
-
|
|
33282
|
+
path8.unshift(current);
|
|
32391
33283
|
current = current.parentId ? this.byId.get(current.parentId) : void 0;
|
|
32392
33284
|
}
|
|
32393
|
-
return
|
|
33285
|
+
return path8;
|
|
32394
33286
|
}
|
|
32395
33287
|
createBranchedSession(leafId) {
|
|
32396
33288
|
const branch = this.getBranch(leafId);
|
|
@@ -32400,7 +33292,7 @@ var SessionManager = class _SessionManager {
|
|
|
32400
33292
|
const newId = randomUUID();
|
|
32401
33293
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
32402
33294
|
const ts = timestamp.replace(/[:.]/g, "-");
|
|
32403
|
-
const newFile =
|
|
33295
|
+
const newFile = join4(this.sessionDir, `${ts}_${newId}.jsonl`);
|
|
32404
33296
|
const header = {
|
|
32405
33297
|
type: "session",
|
|
32406
33298
|
version: CURRENT_SESSION_VERSION,
|
|
@@ -32446,50 +33338,50 @@ var SessionManager = class _SessionManager {
|
|
|
32446
33338
|
return newFile;
|
|
32447
33339
|
}
|
|
32448
33340
|
// -- Factory ---------------------------------------------------------------
|
|
32449
|
-
static open(
|
|
32450
|
-
const entries = loadEntries(
|
|
33341
|
+
static open(path8) {
|
|
33342
|
+
const entries = loadEntries(path8);
|
|
32451
33343
|
const header = entries.find((e) => e.type === "session");
|
|
32452
33344
|
const cwd = header?.cwd ?? process.cwd();
|
|
32453
|
-
const dir = resolve2(
|
|
32454
|
-
return new _SessionManager(cwd, dir,
|
|
33345
|
+
const dir = resolve2(path8, "..");
|
|
33346
|
+
return new _SessionManager(cwd, dir, path8);
|
|
32455
33347
|
}
|
|
32456
33348
|
};
|
|
32457
33349
|
|
|
32458
33350
|
// dist/fork.js
|
|
32459
|
-
import * as
|
|
32460
|
-
import * as
|
|
33351
|
+
import * as fs5 from "node:fs";
|
|
33352
|
+
import * as path6 from "node:path";
|
|
32461
33353
|
import * as crypto from "node:crypto";
|
|
32462
33354
|
function readStoreEntry(sessionsDir, sessionKey) {
|
|
32463
|
-
const storeFile =
|
|
33355
|
+
const storeFile = path6.join(sessionsDir, "sessions.json");
|
|
32464
33356
|
try {
|
|
32465
|
-
const store = JSON.parse(
|
|
33357
|
+
const store = JSON.parse(fs5.readFileSync(storeFile, "utf-8"));
|
|
32466
33358
|
return store[sessionKey] ?? store[sessionKey.toLowerCase()] ?? null;
|
|
32467
33359
|
} catch {
|
|
32468
33360
|
return null;
|
|
32469
33361
|
}
|
|
32470
33362
|
}
|
|
32471
33363
|
function writeStoreEntry(sessionsDir, sessionKey, entry) {
|
|
32472
|
-
const storeFile =
|
|
33364
|
+
const storeFile = path6.join(sessionsDir, "sessions.json");
|
|
32473
33365
|
try {
|
|
32474
33366
|
let store = {};
|
|
32475
33367
|
try {
|
|
32476
|
-
store = JSON.parse(
|
|
33368
|
+
store = JSON.parse(fs5.readFileSync(storeFile, "utf-8"));
|
|
32477
33369
|
} catch {
|
|
32478
33370
|
}
|
|
32479
33371
|
store[sessionKey.toLowerCase()] = entry;
|
|
32480
|
-
|
|
33372
|
+
fs5.writeFileSync(storeFile, JSON.stringify(store, null, 2), { encoding: "utf-8" });
|
|
32481
33373
|
return true;
|
|
32482
33374
|
} catch {
|
|
32483
33375
|
return false;
|
|
32484
33376
|
}
|
|
32485
33377
|
}
|
|
32486
33378
|
function deleteStoreEntry(sessionsDir, sessionKey) {
|
|
32487
|
-
const storeFile =
|
|
33379
|
+
const storeFile = path6.join(sessionsDir, "sessions.json");
|
|
32488
33380
|
try {
|
|
32489
|
-
const store = JSON.parse(
|
|
33381
|
+
const store = JSON.parse(fs5.readFileSync(storeFile, "utf-8"));
|
|
32490
33382
|
delete store[sessionKey];
|
|
32491
33383
|
delete store[sessionKey.toLowerCase()];
|
|
32492
|
-
|
|
33384
|
+
fs5.writeFileSync(storeFile, JSON.stringify(store, null, 2), { encoding: "utf-8" });
|
|
32493
33385
|
} catch {
|
|
32494
33386
|
}
|
|
32495
33387
|
}
|
|
@@ -32501,17 +33393,17 @@ function resolveTranscriptFile(sessionsDir, sessionKey) {
|
|
|
32501
33393
|
if (!entry?.sessionId)
|
|
32502
33394
|
return null;
|
|
32503
33395
|
if (entry.sessionFile) {
|
|
32504
|
-
const resolved =
|
|
32505
|
-
if (
|
|
33396
|
+
const resolved = path6.isAbsolute(entry.sessionFile) ? entry.sessionFile : path6.join(sessionsDir, entry.sessionFile);
|
|
33397
|
+
if (fs5.existsSync(resolved))
|
|
32506
33398
|
return resolved;
|
|
32507
33399
|
}
|
|
32508
|
-
const conventional =
|
|
32509
|
-
if (
|
|
33400
|
+
const conventional = path6.join(sessionsDir, `${entry.sessionId}.jsonl`);
|
|
33401
|
+
if (fs5.existsSync(conventional))
|
|
32510
33402
|
return conventional;
|
|
32511
33403
|
try {
|
|
32512
|
-
const files =
|
|
33404
|
+
const files = fs5.readdirSync(sessionsDir);
|
|
32513
33405
|
const match = files.find((file) => file.includes(entry.sessionId) && file.endsWith(".jsonl"));
|
|
32514
|
-
return match ?
|
|
33406
|
+
return match ? path6.join(sessionsDir, match) : null;
|
|
32515
33407
|
} catch {
|
|
32516
33408
|
return null;
|
|
32517
33409
|
}
|
|
@@ -32522,7 +33414,7 @@ function resolveSessionId(sessionsDir, sessionKey) {
|
|
|
32522
33414
|
}
|
|
32523
33415
|
function forkOrchestratorSession(opts) {
|
|
32524
33416
|
const { orchestratorSessionKey, accountId, transcriptFile, sessionsDir } = opts;
|
|
32525
|
-
if (!
|
|
33417
|
+
if (!fs5.existsSync(transcriptFile))
|
|
32526
33418
|
return null;
|
|
32527
33419
|
try {
|
|
32528
33420
|
const manager = SessionManager.open(transcriptFile);
|
|
@@ -32542,7 +33434,7 @@ function forkOrchestratorSession(opts) {
|
|
|
32542
33434
|
sessionId = crypto.randomUUID();
|
|
32543
33435
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
32544
33436
|
const fileTimestamp = timestamp.replace(/[:.]/g, "-");
|
|
32545
|
-
sessionFile =
|
|
33437
|
+
sessionFile = path6.join(manager.getSessionDir(), `${fileTimestamp}_${sessionId}.jsonl`);
|
|
32546
33438
|
const header = {
|
|
32547
33439
|
type: "session",
|
|
32548
33440
|
version: CURRENT_SESSION_VERSION,
|
|
@@ -32551,7 +33443,7 @@ function forkOrchestratorSession(opts) {
|
|
|
32551
33443
|
cwd: manager.getCwd(),
|
|
32552
33444
|
parentSession: transcriptFile
|
|
32553
33445
|
};
|
|
32554
|
-
|
|
33446
|
+
fs5.writeFileSync(sessionFile, `${JSON.stringify(header)}
|
|
32555
33447
|
`, {
|
|
32556
33448
|
encoding: "utf-8",
|
|
32557
33449
|
mode: 384,
|
|
@@ -32561,7 +33453,7 @@ function forkOrchestratorSession(opts) {
|
|
|
32561
33453
|
const forkSessionKey = `${orchestratorSessionKey}:fork:${sessionId}`;
|
|
32562
33454
|
const wrote = writeStoreEntry(sessionsDir, forkSessionKey, {
|
|
32563
33455
|
sessionId,
|
|
32564
|
-
sessionFile:
|
|
33456
|
+
sessionFile: path6.relative(sessionsDir, sessionFile),
|
|
32565
33457
|
updatedAt: Date.now(),
|
|
32566
33458
|
spawnedBy: orchestratorSessionKey,
|
|
32567
33459
|
parentSessionKey: orchestratorSessionKey,
|
|
@@ -32576,8 +33468,8 @@ function forkOrchestratorSession(opts) {
|
|
|
32576
33468
|
}
|
|
32577
33469
|
function cleanupForkSession(opts) {
|
|
32578
33470
|
try {
|
|
32579
|
-
if (
|
|
32580
|
-
|
|
33471
|
+
if (fs5.existsSync(opts.sessionFile)) {
|
|
33472
|
+
fs5.unlinkSync(opts.sessionFile);
|
|
32581
33473
|
}
|
|
32582
33474
|
} catch {
|
|
32583
33475
|
}
|
|
@@ -32887,8 +33779,8 @@ var parallGateway = {
|
|
|
32887
33779
|
const telemetry = await initAgentTelemetry("parall-openclaw-agent", "openclaw");
|
|
32888
33780
|
const otelLog = createOtelLogger("agent", "openclaw-agent");
|
|
32889
33781
|
try {
|
|
32890
|
-
const stateDir = process.env.OPENCLAW_STATE_DIR ||
|
|
32891
|
-
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");
|
|
32892
33784
|
const configManagerOpts = {
|
|
32893
33785
|
client,
|
|
32894
33786
|
stateDir,
|
|
@@ -32924,7 +33816,7 @@ var parallGateway = {
|
|
|
32924
33816
|
wsUrl
|
|
32925
33817
|
});
|
|
32926
33818
|
const orchestratorKey = buildOrchestratorSessionKey(ctx.accountId);
|
|
32927
|
-
const sessionsDir =
|
|
33819
|
+
const sessionsDir = path7.join(stateDir, "agents", "main", "sessions");
|
|
32928
33820
|
const workspaceDir = process.cwd();
|
|
32929
33821
|
ensureLocalAttachmentGitExclude(workspaceDir);
|
|
32930
33822
|
const dispatchAdapter = createOpenClawDispatchAdapter({
|
|
@@ -33117,9 +34009,9 @@ To respond, you **must** use the Parall CLI via the exec (Bash) tool. If \`paral
|
|
|
33117
34009
|
|
|
33118
34010
|
### Event types and where to reply
|
|
33119
34011
|
|
|
33120
|
-
- **\`[Event: message.new]\`** \u2014 includes \`[Chat: ... (prll://cht_xxx)]\`. Reply into that chat:
|
|
34012
|
+
- **\`[Event: message.new]\`** \u2014 includes \`[Chat: ... (prll://cht_xxx)]\`. Reply into that chat. Write the reply with your file tool first (shell-safe), then send it \u2014 don't wrap message content in \`--text "..."\`, since the shell expands \`$1,000\`\u2192\`,000\` and runs \`$(...)\` inside double quotes:
|
|
33121
34013
|
|
|
33122
|
-
parall messages send prll://cht_xxx --text
|
|
34014
|
+
parall messages send prll://cht_xxx --text-file /tmp/reply.md
|
|
33123
34015
|
|
|
33124
34016
|
- **\`[Event: task.assigned]\` / \`[Event: task.comment.created]\`** \u2014 includes \`[Task: ... (prll://tsk_xxx)]\`. Act on the task; use task CLI subcommands (\`tasks update\`, \`tasks comment\`). See the \`parall-tasks\` skill.
|
|
33125
34017
|
|