@parall/daemon 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/bundle/manifest.json +11 -11
- package/bundle/parall-browser-pod.js +352 -27
- package/bundle/parall-claude-agent.js +1219 -269
- package/bundle/parall-codex-agent.js +1225 -276
- package/bundle/parall-daemon.js +33552 -31712
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/clip-runtime/browser-daemon-env.d.ts +29 -0
- package/dist/clip-runtime/browser-daemon-env.d.ts.map +1 -0
- package/dist/clip-runtime/browser-daemon-env.js +70 -0
- package/dist/clip-runtime/browser-profile-manager.d.ts +18 -5
- package/dist/clip-runtime/browser-profile-manager.d.ts.map +1 -1
- package/dist/clip-runtime/browser-profile-manager.js +149 -24
- package/dist/clip-runtime/browser-profile-pool.d.ts +250 -0
- package/dist/clip-runtime/browser-profile-pool.d.ts.map +1 -0
- package/dist/clip-runtime/browser-profile-pool.js +581 -0
- package/dist/clip-runtime/index.d.ts +2 -2
- package/dist/clip-runtime/index.d.ts.map +1 -1
- package/dist/clip-runtime/index.js +2 -1
- package/dist/clip-runtime/process-manager.d.ts +5 -3
- package/dist/clip-runtime/process-manager.d.ts.map +1 -1
- package/dist/clip-runtime/subprocess.d.ts +14 -0
- package/dist/clip-runtime/subprocess.d.ts.map +1 -1
- package/dist/clip-runtime/subprocess.js +49 -0
- package/dist/config.d.ts +14 -15
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -26
- package/dist/daemon-main.d.ts +8 -0
- package/dist/daemon-main.d.ts.map +1 -0
- package/dist/daemon-main.js +165 -0
- package/dist/daemon-paths.d.ts +14 -0
- package/dist/daemon-paths.d.ts.map +1 -0
- package/dist/daemon-paths.js +26 -0
- package/dist/daemon-update-mode.d.ts +8 -0
- package/dist/daemon-update-mode.d.ts.map +1 -0
- package/dist/daemon-update-mode.js +18 -0
- package/dist/index.js +41 -167
- package/dist/runtime-bin-resolver.d.ts +4 -0
- package/dist/runtime-bin-resolver.d.ts.map +1 -1
- package/dist/runtime-bin-resolver.js +40 -7
- package/dist/runtime-detector.d.ts +30 -0
- package/dist/runtime-detector.d.ts.map +1 -0
- package/dist/runtime-detector.js +100 -0
- package/dist/supervisor.d.ts +53 -2
- package/dist/supervisor.d.ts.map +1 -1
- package/dist/supervisor.js +604 -67
- package/dist/update-health-gate.d.ts +66 -0
- package/dist/update-health-gate.d.ts.map +1 -0
- package/dist/update-health-gate.js +93 -0
- package/dist/updater-manifest.d.ts +2 -1
- package/dist/updater-manifest.d.ts.map +1 -1
- package/dist/updater-manifest.js +38 -7
- package/dist/updater.d.ts +13 -2
- package/dist/updater.d.ts.map +1 -1
- package/dist/updater.js +126 -17
- package/package.json +6 -6
|
@@ -17718,9 +17718,9 @@ var require_getMachineId_linux = __commonJS({
|
|
|
17718
17718
|
var api_1 = (init_esm(), __toCommonJS(esm_exports));
|
|
17719
17719
|
async function getMachineId() {
|
|
17720
17720
|
const paths = ["/etc/machine-id", "/var/lib/dbus/machine-id"];
|
|
17721
|
-
for (const
|
|
17721
|
+
for (const path10 of paths) {
|
|
17722
17722
|
try {
|
|
17723
|
-
const result = await fs_1.promises.readFile(
|
|
17723
|
+
const result = await fs_1.promises.readFile(path10, { encoding: "utf8" });
|
|
17724
17724
|
return result.trim();
|
|
17725
17725
|
} catch (e) {
|
|
17726
17726
|
api_1.diag.debug(`error reading machine id: ${e}`);
|
|
@@ -21123,7 +21123,7 @@ function appendRootPathToUrlIfNeeded(url) {
|
|
|
21123
21123
|
return void 0;
|
|
21124
21124
|
}
|
|
21125
21125
|
}
|
|
21126
|
-
function appendResourcePathToUrl(url,
|
|
21126
|
+
function appendResourcePathToUrl(url, path10) {
|
|
21127
21127
|
try {
|
|
21128
21128
|
new URL(url);
|
|
21129
21129
|
} catch (_a) {
|
|
@@ -21133,11 +21133,11 @@ function appendResourcePathToUrl(url, path9) {
|
|
|
21133
21133
|
if (!url.endsWith("/")) {
|
|
21134
21134
|
url = url + "/";
|
|
21135
21135
|
}
|
|
21136
|
-
url +=
|
|
21136
|
+
url += path10;
|
|
21137
21137
|
try {
|
|
21138
21138
|
new URL(url);
|
|
21139
21139
|
} catch (_b) {
|
|
21140
|
-
diag2.warn("Configuration: Provided URL appended with '" +
|
|
21140
|
+
diag2.warn("Configuration: Provided URL appended with '" + path10 + "' is not a valid URL, using 'undefined' instead of '" + url + "'");
|
|
21141
21141
|
return void 0;
|
|
21142
21142
|
}
|
|
21143
21143
|
return url;
|
|
@@ -26474,6 +26474,19 @@ function parseProviderConfig(env) {
|
|
|
26474
26474
|
}
|
|
26475
26475
|
}
|
|
26476
26476
|
|
|
26477
|
+
// ts/agent-core/dist/lane-key.js
|
|
26478
|
+
import * as path from "node:path";
|
|
26479
|
+
function laneKeyForTarget(targetUri, threadRootId) {
|
|
26480
|
+
return Buffer.from(`${targetUri}
|
|
26481
|
+
${threadRootId ?? ""}`, "utf8").toString("base64url");
|
|
26482
|
+
}
|
|
26483
|
+
function dispatchLaneContextDir(stateDir) {
|
|
26484
|
+
return path.join(stateDir, "dispatch-lane-context");
|
|
26485
|
+
}
|
|
26486
|
+
function laneContextFilePath(contextDir, targetUri, threadRootId) {
|
|
26487
|
+
return path.join(contextDir, `${laneKeyForTarget(targetUri, threadRootId)}.json`);
|
|
26488
|
+
}
|
|
26489
|
+
|
|
26477
26490
|
// ts/agent-core/dist/session-state.js
|
|
26478
26491
|
function normalizeSessionKey(sessionKey) {
|
|
26479
26492
|
return sessionKey.toLowerCase();
|
|
@@ -26647,6 +26660,17 @@ function buildEventBody(event) {
|
|
|
26647
26660
|
if (event.attachedUri)
|
|
26648
26661
|
lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
|
|
26649
26662
|
lines.push("", event.body);
|
|
26663
|
+
} else if (event.type === "channel_message") {
|
|
26664
|
+
lines.push(`[Event: channel.message]`);
|
|
26665
|
+
const providerLabel = sanitizeMeta(event.channelProvider ?? "external IM");
|
|
26666
|
+
const convLabel = event.channelExternalConversationId ? `${sanitizeMeta(event.channelExternalConversationId)} (${sanitizeMeta(event.channelConversationType ?? "conversation")})` : sanitizeMeta(event.channelConversationType ?? "conversation");
|
|
26667
|
+
lines.push(`[Channel: ${providerLabel} | conversation: ${convLabel}]`);
|
|
26668
|
+
lines.push(`[From: ${sanitizeMeta(event.senderName)} (external user, not a Parall member)]`);
|
|
26669
|
+
if (event.channelExternalMessageId) {
|
|
26670
|
+
lines.push(`[External message ID: ${sanitizeMeta(event.channelExternalMessageId)}]`);
|
|
26671
|
+
}
|
|
26672
|
+
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.]`);
|
|
26673
|
+
lines.push("", event.body);
|
|
26650
26674
|
} else if (event.type === "external_trigger") {
|
|
26651
26675
|
lines.push(`[Event: external.trigger]`);
|
|
26652
26676
|
lines.push(`[Trigger: prll://${event.targetId}]`);
|
|
@@ -26688,7 +26712,7 @@ function buildSendMessageHint(event) {
|
|
|
26688
26712
|
}
|
|
26689
26713
|
if (event.targetId.startsWith("cht_")) {
|
|
26690
26714
|
return `
|
|
26691
|
-
<system-reminder>To reply, run
|
|
26715
|
+
<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>`;
|
|
26692
26716
|
}
|
|
26693
26717
|
if (event.targetId.startsWith("tsk_")) {
|
|
26694
26718
|
return `
|
|
@@ -26697,6 +26721,14 @@ function buildSendMessageHint(event) {
|
|
|
26697
26721
|
if (event.targetId.startsWith("sch_")) {
|
|
26698
26722
|
return `
|
|
26699
26723
|
<system-reminder>To communicate, use the CLI: \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
|
|
26724
|
+
}
|
|
26725
|
+
if (event.type === "channel_message") {
|
|
26726
|
+
const clipLabel = event.channelProvider ? `the \`${event.channelProvider}\` clip's` : "your channel provider clip's";
|
|
26727
|
+
const apiLabel = event.channelProvider ?? "external platform";
|
|
26728
|
+
const target = event.channelExternalConversationId ? `{"chat_id": "${event.channelExternalConversationId}", "text": "..."}` : `{"chat_id": "<conversation id>", "text": "..."}`;
|
|
26729
|
+
const threadAlt = event.channelExternalMessageId ? ` To reply in-thread to this specific message, use {"message_id": "${event.channelExternalMessageId}", "text": "..."} instead.` : "";
|
|
26730
|
+
return `
|
|
26731
|
+
<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>`;
|
|
26700
26732
|
}
|
|
26701
26733
|
if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
|
|
26702
26734
|
return `
|
|
@@ -26904,6 +26936,8 @@ Or upload first and reuse across chats:
|
|
|
26904
26936
|
parall messages send prll://cht_aaa --attachment att_yyy --text "Report"
|
|
26905
26937
|
parall messages send prll://cht_bbb --attachment att_yyy --text "FYI"
|
|
26906
26938
|
|
|
26939
|
+
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 \`$(...)\`.
|
|
26940
|
+
|
|
26907
26941
|
### When to reference
|
|
26908
26942
|
|
|
26909
26943
|
- **Origin** \u2014 always link the message or task that triggered your work
|
|
@@ -26954,11 +26988,21 @@ To say something in a chat, you **must** invoke the Parall CLI via your shell/ex
|
|
|
26954
26988
|
|
|
26955
26989
|
All outbound interactions go through the \`parall\` CLI. Credentials are pre-injected as environment variables \u2014 no setup needed. If \`parall\` is not on PATH, use \`npx --yes @parall/cli@latest\` instead.
|
|
26956
26990
|
|
|
26957
|
-
- \`parall messages send prll://cht_xxx --text
|
|
26958
|
-
- \`parall dm prll://usr_xxx --text
|
|
26991
|
+
- \`parall messages send prll://cht_xxx --text-file -\` \u2014 reply into the triggering chat (pipe the body via a quoted heredoc; see Shell-safety below)
|
|
26992
|
+
- \`parall dm prll://usr_xxx --text-file - [--no-reply]\` \u2014 direct message another user
|
|
26959
26993
|
- \`parall tasks update prll://tsk_xxx --status in_progress\` \u2014 task state
|
|
26960
26994
|
- \`parall no-reply [--reason "..."]\` \u2014 explicitly declare this turn silent (audit signal; not required for silence, just clarifies intent)
|
|
26961
26995
|
|
|
26996
|
+
**Shell-safety \u2014 never wrap real message content in double quotes.** Your command runs in a shell, which expands \`$\`, backticks, and \`$(...)\` inside \`"..."\` before the CLI sees them: \`--text "That costs $1,000"\` sends \`That costs ,000\`, and \`--text "$(cmd)"\` executes \`cmd\`. Pass message bodies via \`--text-file <path>\` (write the file first \u2014 no shell touches it) or a quoted heredoc that disables expansion:
|
|
26997
|
+
|
|
26998
|
+
\`\`\`bash
|
|
26999
|
+
parall messages send prll://cht_xxx --text-file - <<'EOF'
|
|
27000
|
+
That costs $1,000, and $(whoami) stays literal. I'm on it.
|
|
27001
|
+
EOF
|
|
27002
|
+
\`\`\`
|
|
27003
|
+
|
|
27004
|
+
Keep \`--text "..."\` for short literals with no \`$\`, backtick, or apostrophe.
|
|
27005
|
+
|
|
26962
27006
|
The bridge injects Parall context via environment variables. The static credentials \`PRLL_API_URL\`, \`PRLL_API_KEY\`, and \`PRLL_ORG_ID\` are always set. \`PRLL_CONTEXT_FILE\` points to a per-session JSON file that the gateway updates each dispatch with \`session_id\`, \`chat_id\`, \`trigger_message_id\`, \`no_reply\`, and \`step_id\` (updated per tool call). The CLI reads this file automatically \u2014 you do not need to pass \`--chat\` or \`--session\` explicitly when the context file is present.
|
|
26963
27007
|
|
|
26964
27008
|
## Attachments
|
|
@@ -27003,7 +27047,7 @@ Messages may arrive with a \`[Thread: prll://msg_xxx]\` line in the event block,
|
|
|
27003
27047
|
|
|
27004
27048
|
### CLI usage
|
|
27005
27049
|
- The \`msg_xxx\` from the \`[Thread: prll://msg_xxx]\` line is the thread root ID \u2014 pass it as \`--thread-root-id\`
|
|
27006
|
-
- Reply in a thread: \`parall messages send <chatId> --text
|
|
27050
|
+
- Reply in a thread: \`parall messages send <chatId> --text-file /tmp/reply.md --thread-root-id <msgId>\`
|
|
27007
27051
|
- Read thread replies: \`parall messages list <chatId> --thread-root-id <msgId>\`
|
|
27008
27052
|
|
|
27009
27053
|
### Thread etiquette
|
|
@@ -27071,8 +27115,8 @@ function childLogger(logger, sub) {
|
|
|
27071
27115
|
|
|
27072
27116
|
// ts/agent-core/dist/gateway-base.js
|
|
27073
27117
|
import * as os from "node:os";
|
|
27074
|
-
import * as
|
|
27075
|
-
import * as
|
|
27118
|
+
import * as fs2 from "node:fs";
|
|
27119
|
+
import * as path2 from "node:path";
|
|
27076
27120
|
|
|
27077
27121
|
// ts/sdk/dist/types.js
|
|
27078
27122
|
var MENTION_ALL_USER_ID = "all";
|
|
@@ -27104,6 +27148,7 @@ var ENDPOINTS = {
|
|
|
27104
27148
|
// Org-scoped
|
|
27105
27149
|
ORG: (orgId) => `${API_BASE}/orgs/${orgId}`,
|
|
27106
27150
|
ORG_MEMBERS: (orgId) => `${API_BASE}/orgs/${orgId}/members`,
|
|
27151
|
+
ORG_MEMBERS_FORMER: (orgId) => `${API_BASE}/orgs/${orgId}/members/former`,
|
|
27107
27152
|
TEAMS: (orgId) => `${API_BASE}/orgs/${orgId}/teams`,
|
|
27108
27153
|
ORG_MEMBERS_ONLINE: (orgId) => `${API_BASE}/orgs/${orgId}/members/online`,
|
|
27109
27154
|
ORG_MEMBER: (orgId, userId) => `${API_BASE}/orgs/${orgId}/members/${userId}`,
|
|
@@ -27253,6 +27298,19 @@ var ENDPOINTS = {
|
|
|
27253
27298
|
EXTERNAL_TRIGGER: (orgId, triggerId) => `${API_BASE}/orgs/${orgId}/external-triggers/${triggerId}`,
|
|
27254
27299
|
EXTERNAL_TRIGGER_RUNS: (orgId) => `${API_BASE}/orgs/${orgId}/external-trigger-runs`,
|
|
27255
27300
|
EXTERNAL_TRIGGER_RUN: (orgId, runId) => `${API_BASE}/orgs/${orgId}/external-trigger-runs/${runId}`,
|
|
27301
|
+
// External IM channel (org-scoped, platform-mediated Feishu/Slack)
|
|
27302
|
+
CHANNEL_CONNECTIONS: (orgId) => `${API_BASE}/orgs/${orgId}/channel-connections`,
|
|
27303
|
+
CHANNEL_CONNECTION: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}`,
|
|
27304
|
+
CHANNEL_CONNECTION_CREDENTIALS: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/credentials`,
|
|
27305
|
+
CHANNEL_CONNECTION_INGRESS_TOKEN_REGENERATE: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/ingress-token/regenerate`,
|
|
27306
|
+
CHANNEL_CONNECTION_CONVERSATIONS: (orgId, connectionId) => `${API_BASE}/orgs/${orgId}/channel-connections/${connectionId}/conversations`,
|
|
27307
|
+
CHANNEL_CONVERSATION: (orgId, conversationId) => `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}`,
|
|
27308
|
+
CHANNEL_CONVERSATION_MESSAGES: (orgId, conversationId) => `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}/messages`,
|
|
27309
|
+
CHANNEL_CONVERSATION_SESSION: (orgId, conversationId) => `${API_BASE}/orgs/${orgId}/channel-conversations/${conversationId}/session`,
|
|
27310
|
+
CHANNEL_MESSAGE: (orgId, messageId) => `${API_BASE}/orgs/${orgId}/channel-messages/${messageId}`,
|
|
27311
|
+
CHANNEL_PROVISIONING: (orgId) => `${API_BASE}/orgs/${orgId}/channel-provisioning`,
|
|
27312
|
+
CHANNEL_PROVISIONING_SESSION: (orgId, sessionId) => `${API_BASE}/orgs/${orgId}/channel-provisioning/${sessionId}`,
|
|
27313
|
+
CHANNEL_PROVISIONING_CANCEL: (orgId, sessionId) => `${API_BASE}/orgs/${orgId}/channel-provisioning/${sessionId}/cancel`,
|
|
27256
27314
|
// Invitations (org-scoped, admin)
|
|
27257
27315
|
ORG_INVITATIONS: (orgId) => `${API_BASE}/orgs/${orgId}/invitations`,
|
|
27258
27316
|
ORG_INVITATION: (orgId, invId) => `${API_BASE}/orgs/${orgId}/invitations/${invId}`,
|
|
@@ -27270,9 +27328,16 @@ var ENDPOINTS = {
|
|
|
27270
27328
|
INVITE_LINK_JOIN: `${API_BASE}/invite-link/join`,
|
|
27271
27329
|
// Wikis (org-scoped, served by wiki-service)
|
|
27272
27330
|
WIKIS: (orgId) => `${WIKI_BASE}/orgs/${orgId}/wikis`,
|
|
27331
|
+
// Recycle bin — soft-deleted wikis (owner only). Must precede WIKI in the
|
|
27332
|
+
// backend router so `deleted` isn't captured as a {wikiId}; the SDK builder
|
|
27333
|
+
// is just a string.
|
|
27334
|
+
WIKIS_DELETED: (orgId) => `${WIKI_BASE}/orgs/${orgId}/wikis/deleted`,
|
|
27335
|
+
// Detail URL — also reused for DELETE (soft-delete) and `${WIKI}/restore`.
|
|
27273
27336
|
WIKI: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}`,
|
|
27337
|
+
WIKI_RESTORE: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/restore`,
|
|
27274
27338
|
WIKI_TREE: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/tree`,
|
|
27275
27339
|
WIKI_BLOB: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/blob`,
|
|
27340
|
+
WIKI_COPY: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/copy`,
|
|
27276
27341
|
WIKI_NODE_SECTIONS: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/node-sections`,
|
|
27277
27342
|
WIKI_SEARCH: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/search`,
|
|
27278
27343
|
WIKI_PAGE_INDEX: (orgId, wikiId) => `${WIKI_BASE}/orgs/${orgId}/wikis/${wikiId}/page-index`,
|
|
@@ -27326,6 +27391,11 @@ var ENDPOINTS = {
|
|
|
27326
27391
|
DISPATCH_ACK_BY_ID: (orgId, id) => `${API_BASE}/orgs/${orgId}/dispatch/${id}/ack`,
|
|
27327
27392
|
DISPATCH_EXPIRE: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/expire`,
|
|
27328
27393
|
DISPATCH_BY_MESSAGES: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/by-messages`,
|
|
27394
|
+
DISPATCH_CLAIM: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/claim`,
|
|
27395
|
+
DISPATCH_STEER: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/steer`,
|
|
27396
|
+
DISPATCH_COMPLETE: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/complete`,
|
|
27397
|
+
DISPATCH_RELEASE: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/release`,
|
|
27398
|
+
DISPATCH_HEARTBEAT: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/heartbeat`,
|
|
27329
27399
|
// Unread
|
|
27330
27400
|
UNREAD: `${API_BASE}/me/unread`,
|
|
27331
27401
|
ORG_UNREAD: (orgId) => `${API_BASE}/orgs/${orgId}/unread`,
|
|
@@ -27355,6 +27425,14 @@ var ENDPOINTS = {
|
|
|
27355
27425
|
BILLING_AUTO_RELOAD: (orgId) => `${API_BASE}/orgs/${orgId}/billing/auto-reload`,
|
|
27356
27426
|
BILLING_SETUP_INTENT: (orgId) => `${API_BASE}/orgs/${orgId}/billing/setup-intent`,
|
|
27357
27427
|
COMPUTE_PRICING: () => `${API_BASE}/billing/compute-pricing`,
|
|
27428
|
+
// Runtime capability table (public, no auth) — SSOT for the create/settings
|
|
27429
|
+
// interlock: per-runtime compute modes, native model family, cross-family
|
|
27430
|
+
// availability, and recommended model.
|
|
27431
|
+
RUNTIMES: () => `${API_BASE}/runtimes`,
|
|
27432
|
+
// Model catalog (public, no auth) — served from the server's DB-backed
|
|
27433
|
+
// snapshot. Optional include_model keeps an agent's pinned hidden legacy
|
|
27434
|
+
// model representable in settings pickers.
|
|
27435
|
+
MODELS: (includeModel) => includeModel ? `${API_BASE}/models?include_model=${encodeURIComponent(includeModel)}` : `${API_BASE}/models`,
|
|
27358
27436
|
// ADMIN_GRANTS intentionally NOT exported here — it sits under the
|
|
27359
27437
|
// unauthenticated `/internal/admin/*` surface and must not bleed into the
|
|
27360
27438
|
// public SDK. Admin dashboard hits the URL directly from its own client.
|
|
@@ -27367,6 +27445,7 @@ var ENDPOINTS = {
|
|
|
27367
27445
|
AGENT_CLIP: (orgId, agentId, clipId) => `${CLIP_BASE}/orgs/${orgId}/agents/${agentId}/clips/${clipId}`,
|
|
27368
27446
|
CLIP_INVOKE: (orgId) => `${CLIP_BASE}/orgs/${orgId}/invoke`,
|
|
27369
27447
|
CLIP_ONLINE: (orgId) => `${CLIP_BASE}/orgs/${orgId}/online`,
|
|
27448
|
+
BROWSER_RUNTIME_STATUS: (orgId) => `${CLIP_BASE}/orgs/${orgId}/browser-runtime/status`,
|
|
27370
27449
|
BROWSER_PROFILES: (orgId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles`,
|
|
27371
27450
|
BROWSER_PROFILE: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}`,
|
|
27372
27451
|
BROWSER_PROFILE_OPEN: (orgId, profileId) => `${CLIP_BASE}/orgs/${orgId}/browser-profiles/${profileId}/open`,
|
|
@@ -27420,10 +27499,13 @@ var WS_EVENTS = {
|
|
|
27420
27499
|
INVITATION_REVOKED: "invitation.revoked",
|
|
27421
27500
|
ORG_JOIN_REQUEST_NEW: "org.join_request.new",
|
|
27422
27501
|
ORG_INVITE_LINK_JOINED: "org.invite_link.joined",
|
|
27502
|
+
ORG_MEMBER_REMOVED: "org.member.removed",
|
|
27423
27503
|
AGENT_CONFIG_UPDATE: "agent_config.update",
|
|
27424
27504
|
PRESENCE_UPDATE: "presence.update",
|
|
27425
27505
|
WIKI_CHANGESET_CREATED: "wiki.changeset.created",
|
|
27426
27506
|
WIKI_CHANGESET_UPDATED: "wiki.changeset.updated",
|
|
27507
|
+
WIKI_DELETED: "wiki.deleted",
|
|
27508
|
+
WIKI_RESTORED: "wiki.restored",
|
|
27427
27509
|
COMMENT_CREATED: "comment.created",
|
|
27428
27510
|
COMMENT_UPDATED: "comment.updated",
|
|
27429
27511
|
COMMENT_DELETED: "comment.deleted",
|
|
@@ -27434,6 +27516,7 @@ var WS_EVENTS = {
|
|
|
27434
27516
|
READ_POSITION_UPDATED: "read_position.updated",
|
|
27435
27517
|
DISPATCH_NEW: "dispatch.new",
|
|
27436
27518
|
DISPATCH_RECEIVED: "dispatch.received",
|
|
27519
|
+
DISPATCH_RESOLVED: "dispatch.resolved",
|
|
27437
27520
|
SCHEDULE_CREATED: "schedule.created",
|
|
27438
27521
|
SCHEDULE_UPDATED: "schedule.updated",
|
|
27439
27522
|
SCHEDULE_DELETED: "schedule.deleted",
|
|
@@ -27451,6 +27534,7 @@ var WS_EVENTS = {
|
|
|
27451
27534
|
MACHINE_FILESYSTEM_BROWSE: "machine.filesystem.browse",
|
|
27452
27535
|
MACHINE_UPDATE: "machine.update",
|
|
27453
27536
|
MACHINE_CONFIG_UPDATED: "machine.config.updated",
|
|
27537
|
+
MACHINE_AGENT_CONFIG_UPDATED: "machine.agent_config.updated",
|
|
27454
27538
|
MACHINE_CLIP_SYNC: "machine.clip.sync",
|
|
27455
27539
|
MACHINE_BROWSER_PROFILE_LIFECYCLE: "machine.browser_profile.lifecycle",
|
|
27456
27540
|
MACHINE_BROWSER_PROFILE_VIEWER: "machine.browser_profile.viewer",
|
|
@@ -27528,8 +27612,8 @@ var ParallClient = class _ParallClient {
|
|
|
27528
27612
|
* is authoritative, so wiki vs api routing can't drift from how a caller
|
|
27529
27613
|
* happens to invoke the client.
|
|
27530
27614
|
*/
|
|
27531
|
-
baseUrlFor(
|
|
27532
|
-
return
|
|
27615
|
+
baseUrlFor(path10) {
|
|
27616
|
+
return path10.startsWith(WIKI_BASE) ? this.wikiBaseUrl : this.baseUrl;
|
|
27533
27617
|
}
|
|
27534
27618
|
setToken(token) {
|
|
27535
27619
|
this.token = token;
|
|
@@ -27556,10 +27640,10 @@ var ParallClient = class _ParallClient {
|
|
|
27556
27640
|
* REFRESH_THRESHOLD_S, refresh it **before** sending the request.
|
|
27557
27641
|
* No-op when the token is still fresh, missing, or un-parseable.
|
|
27558
27642
|
*/
|
|
27559
|
-
async ensureFreshToken(
|
|
27643
|
+
async ensureFreshToken(path10) {
|
|
27560
27644
|
if (!this.token || !this.getRefreshToken)
|
|
27561
27645
|
return;
|
|
27562
|
-
const pathSuffix =
|
|
27646
|
+
const pathSuffix = path10.replace(/^\/api\/v1/, "");
|
|
27563
27647
|
if (_ParallClient.AUTH_PATHS.has(pathSuffix))
|
|
27564
27648
|
return;
|
|
27565
27649
|
const exp = _ParallClient.decodeJwtExp(this.token);
|
|
@@ -27591,11 +27675,11 @@ var ParallClient = class _ParallClient {
|
|
|
27591
27675
|
this.refreshPromise = null;
|
|
27592
27676
|
}
|
|
27593
27677
|
}
|
|
27594
|
-
async request(method,
|
|
27678
|
+
async request(method, path10, body, query, retried = false, opts) {
|
|
27595
27679
|
if (!retried) {
|
|
27596
|
-
await this.ensureFreshToken(
|
|
27680
|
+
await this.ensureFreshToken(path10);
|
|
27597
27681
|
}
|
|
27598
|
-
let url = `${this.baseUrlFor(
|
|
27682
|
+
let url = `${this.baseUrlFor(path10)}${path10}`;
|
|
27599
27683
|
if (query) {
|
|
27600
27684
|
const params = new URLSearchParams();
|
|
27601
27685
|
for (const [key, value] of Object.entries(query)) {
|
|
@@ -27625,12 +27709,12 @@ var ParallClient = class _ParallClient {
|
|
|
27625
27709
|
throw _ParallClient.normalizeFetchError(err);
|
|
27626
27710
|
}
|
|
27627
27711
|
if (res.status === 401) {
|
|
27628
|
-
const pathSuffix =
|
|
27712
|
+
const pathSuffix = path10.replace(/^\/api\/v1/, "");
|
|
27629
27713
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
27630
27714
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
27631
27715
|
const refreshed = await this.tryRefresh();
|
|
27632
27716
|
if (refreshed) {
|
|
27633
|
-
return this.request(method,
|
|
27717
|
+
return this.request(method, path10, body, query, true, opts);
|
|
27634
27718
|
}
|
|
27635
27719
|
}
|
|
27636
27720
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -27641,6 +27725,7 @@ var ParallClient = class _ParallClient {
|
|
|
27641
27725
|
const rawErrorBody = await res.json().catch(() => ({}));
|
|
27642
27726
|
throw buildApiError(res, rawErrorBody);
|
|
27643
27727
|
}
|
|
27728
|
+
opts?.onStatus?.(res.status);
|
|
27644
27729
|
if (res.status === 204)
|
|
27645
27730
|
return void 0;
|
|
27646
27731
|
if (res.status === 202) {
|
|
@@ -27659,15 +27744,15 @@ var ParallClient = class _ParallClient {
|
|
|
27659
27744
|
* hit the 100 MiB cap, so a longer 5-minute timeout is used so a
|
|
27660
27745
|
* 50 MiB blob on a slow connection doesn't get chopped at 15 s.
|
|
27661
27746
|
*/
|
|
27662
|
-
async multipartRequest(method,
|
|
27747
|
+
async multipartRequest(method, path10, body, retried = false) {
|
|
27663
27748
|
if (!retried) {
|
|
27664
|
-
await this.ensureFreshToken(
|
|
27749
|
+
await this.ensureFreshToken(path10);
|
|
27665
27750
|
}
|
|
27666
27751
|
const { "Content-Type": _drop, ...headers } = this.buildHeaders();
|
|
27667
27752
|
void _drop;
|
|
27668
27753
|
let res;
|
|
27669
27754
|
try {
|
|
27670
|
-
res = await fetch(`${this.baseUrlFor(
|
|
27755
|
+
res = await fetch(`${this.baseUrlFor(path10)}${path10}`, {
|
|
27671
27756
|
method,
|
|
27672
27757
|
headers,
|
|
27673
27758
|
body,
|
|
@@ -27677,12 +27762,12 @@ var ParallClient = class _ParallClient {
|
|
|
27677
27762
|
throw _ParallClient.normalizeFetchError(err);
|
|
27678
27763
|
}
|
|
27679
27764
|
if (res.status === 401) {
|
|
27680
|
-
const pathSuffix =
|
|
27765
|
+
const pathSuffix = path10.replace(/^\/api\/v1/, "");
|
|
27681
27766
|
const isAuthPath = _ParallClient.AUTH_PATHS.has(pathSuffix);
|
|
27682
27767
|
if (!retried && !isAuthPath && this.getRefreshToken) {
|
|
27683
27768
|
const refreshed = await this.tryRefresh();
|
|
27684
27769
|
if (refreshed) {
|
|
27685
|
-
return this.multipartRequest(method,
|
|
27770
|
+
return this.multipartRequest(method, path10, body, true);
|
|
27686
27771
|
}
|
|
27687
27772
|
}
|
|
27688
27773
|
if (this.onTokenExpired && !isAuthPath) {
|
|
@@ -27790,6 +27875,13 @@ var ParallClient = class _ParallClient {
|
|
|
27790
27875
|
const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS(orgId));
|
|
27791
27876
|
return res.data;
|
|
27792
27877
|
}
|
|
27878
|
+
/** User IDs of soft-removed (former) org members — for marking their avatar /
|
|
27879
|
+
* name as "left" where dormant relations still surface them (DMs, task
|
|
27880
|
+
* assignees, message history). */
|
|
27881
|
+
async getFormerMemberIds(orgId) {
|
|
27882
|
+
const res = await this.request("GET", ENDPOINTS.ORG_MEMBERS_FORMER(orgId));
|
|
27883
|
+
return res.user_ids ?? [];
|
|
27884
|
+
}
|
|
27793
27885
|
async getTeams(orgId) {
|
|
27794
27886
|
const res = await this.request("GET", ENDPOINTS.TEAMS(orgId));
|
|
27795
27887
|
return res.data;
|
|
@@ -27967,6 +28059,21 @@ var ParallClient = class _ParallClient {
|
|
|
27967
28059
|
async sendMessage(orgId, chatId, req) {
|
|
27968
28060
|
return this.request("POST", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), req);
|
|
27969
28061
|
}
|
|
28062
|
+
/**
|
|
28063
|
+
* sendMessage variant that also reports whether the server answered with an
|
|
28064
|
+
* idempotent replay (HTTP 200 — the message already existed for this
|
|
28065
|
+
* idempotency/effect key) instead of a fresh create (201). Used by the CLI
|
|
28066
|
+
* to surface "already sent by a previous run (deduplicated)".
|
|
28067
|
+
*/
|
|
28068
|
+
async sendMessageDetailed(orgId, chatId, req) {
|
|
28069
|
+
let status = 0;
|
|
28070
|
+
const message = await this.request("POST", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), req, void 0, false, {
|
|
28071
|
+
onStatus: (s) => {
|
|
28072
|
+
status = s;
|
|
28073
|
+
}
|
|
28074
|
+
});
|
|
28075
|
+
return { message, deduplicated: status === 200 };
|
|
28076
|
+
}
|
|
27970
28077
|
async getMessages(orgId, chatId, params) {
|
|
27971
28078
|
return this.request("GET", ENDPOINTS.CHAT_MESSAGES(orgId, chatId), void 0, params);
|
|
27972
28079
|
}
|
|
@@ -28278,20 +28385,25 @@ var ParallClient = class _ParallClient {
|
|
|
28278
28385
|
return res.data;
|
|
28279
28386
|
}
|
|
28280
28387
|
/** `POST /machines/me/browser-profiles/{profileId}/status` — report runtime status. */
|
|
28281
|
-
async reportBrowserProfileStatus(profileId, status, errorMsg) {
|
|
28388
|
+
async reportBrowserProfileStatus(profileId, status, errorMsg, generation) {
|
|
28282
28389
|
await this.request("POST", ENDPOINTS.MACHINES_ME_BROWSER_PROFILE_STATUS(profileId), {
|
|
28283
28390
|
status,
|
|
28284
|
-
...errorMsg ? { error_msg: errorMsg } : {}
|
|
28391
|
+
...errorMsg ? { error_msg: errorMsg } : {},
|
|
28392
|
+
...generation !== void 0 ? { generation } : {}
|
|
28285
28393
|
});
|
|
28286
28394
|
}
|
|
28287
28395
|
/**
|
|
28288
28396
|
* `POST /machines/me/health` — bump the Machine's `updated_at` to now and
|
|
28289
|
-
* report daemon state. The daemon
|
|
28290
|
-
*
|
|
28397
|
+
* report daemon state. The daemon calls this on startup and again whenever
|
|
28398
|
+
* periodic runtime re-detection produces a CHANGED result (there is no
|
|
28399
|
+
* fixed-cadence keepalive loop today — steady state posts nothing). All
|
|
28291
28400
|
* fields are optional and only persisted when changed:
|
|
28292
28401
|
* - `daemonVersion` — the running bundle/launcher version.
|
|
28293
28402
|
* - `selfUpdateCapable` — whether the daemon can act on a machine.update
|
|
28294
28403
|
* signal (true under a service manager, false for bare `npx` foreground).
|
|
28404
|
+
* - `detectedRuntimes` — runtime CLIs found on the host. Omitted = no
|
|
28405
|
+
* report this beat (server keeps the stored value); [] = detection ran
|
|
28406
|
+
* and found nothing (server clears to empty).
|
|
28295
28407
|
*/
|
|
28296
28408
|
async postMachineHeartbeat(opts) {
|
|
28297
28409
|
const body = {};
|
|
@@ -28299,6 +28411,8 @@ var ParallClient = class _ParallClient {
|
|
|
28299
28411
|
body.daemon_version = opts.daemonVersion;
|
|
28300
28412
|
if (opts?.selfUpdateCapable !== void 0)
|
|
28301
28413
|
body.self_update_capable = opts.selfUpdateCapable;
|
|
28414
|
+
if (opts?.detectedRuntimes !== void 0)
|
|
28415
|
+
body.detected_runtimes = opts.detectedRuntimes;
|
|
28302
28416
|
return this.request("POST", ENDPOINTS.MACHINES_ME_HEALTH, Object.keys(body).length > 0 ? body : void 0);
|
|
28303
28417
|
}
|
|
28304
28418
|
async reportAgentWorkspaceState(agentId, state) {
|
|
@@ -28347,8 +28461,8 @@ var ParallClient = class _ParallClient {
|
|
|
28347
28461
|
async requestMachineUpdate(orgId, machineId, mandatory = false) {
|
|
28348
28462
|
await this.request("POST", ENDPOINTS.MACHINE_REQUEST_UPDATE(orgId, machineId), { mandatory });
|
|
28349
28463
|
}
|
|
28350
|
-
async browseMachineFilesystem(orgId, machineId,
|
|
28351
|
-
return this.request("POST", ENDPOINTS.MACHINE_BROWSE(orgId, machineId), { path:
|
|
28464
|
+
async browseMachineFilesystem(orgId, machineId, path10) {
|
|
28465
|
+
return this.request("POST", ENDPOINTS.MACHINE_BROWSE(orgId, machineId), { path: path10 }, void 0, false, { timeoutMs: 15e3 });
|
|
28352
28466
|
}
|
|
28353
28467
|
/** Create a new machine key. Returns the raw key string (shown once) + metadata. */
|
|
28354
28468
|
async createMachineKey(orgId, machineId, name) {
|
|
@@ -28421,6 +28535,30 @@ var ParallClient = class _ParallClient {
|
|
|
28421
28535
|
const params = maxAgeHours !== void 0 ? { max_age_hours: String(maxAgeHours) } : void 0;
|
|
28422
28536
|
return this.request("POST", ENDPOINTS.DISPATCH_EXPIRE(orgId), void 0, params);
|
|
28423
28537
|
}
|
|
28538
|
+
/**
|
|
28539
|
+
* Claim a dispatch lane (explicit consume endpoint). Occupies the
|
|
28540
|
+
* (agent, target, thread) lane and folds claimable WorkItems into it;
|
|
28541
|
+
* `claimed: false` means a healthy incumbent holds the lane.
|
|
28542
|
+
*/
|
|
28543
|
+
async claimDispatch(orgId, req) {
|
|
28544
|
+
return this.request("POST", ENDPOINTS.DISPATCH_CLAIM(orgId), req);
|
|
28545
|
+
}
|
|
28546
|
+
/** Fold a pending same-target WorkItem into a live lane (409 STALE_LANE when dethroned). */
|
|
28547
|
+
async steerDispatch(orgId, req) {
|
|
28548
|
+
return this.request("POST", ENDPOINTS.DISPATCH_STEER(orgId), req);
|
|
28549
|
+
}
|
|
28550
|
+
/** End a turn: no_action sweep of the lane's members + lane release + re-drive check. */
|
|
28551
|
+
async completeDispatch(orgId, req) {
|
|
28552
|
+
return this.request("POST", ENDPOINTS.DISPATCH_COMPLETE(orgId), req);
|
|
28553
|
+
}
|
|
28554
|
+
/** Release a lane on graceful shutdown — members return to pending immediately. */
|
|
28555
|
+
async releaseDispatchLane(orgId, lane) {
|
|
28556
|
+
return this.request("POST", ENDPOINTS.DISPATCH_RELEASE(orgId), { lane });
|
|
28557
|
+
}
|
|
28558
|
+
/** Renew a live lane's lease (long-turn keepalive). 409 STALE_LANE when dethroned. */
|
|
28559
|
+
async heartbeatDispatchLane(orgId, req) {
|
|
28560
|
+
return this.request("POST", ENDPOINTS.DISPATCH_HEARTBEAT(orgId), req);
|
|
28561
|
+
}
|
|
28424
28562
|
async getDispatchByMessages(orgId, chatId, messageIds) {
|
|
28425
28563
|
const params = { chat_id: chatId, message_ids: messageIds.join(",") };
|
|
28426
28564
|
return this.request("GET", ENDPOINTS.DISPATCH_BY_MESSAGES(orgId), void 0, params);
|
|
@@ -28617,6 +28755,59 @@ var ParallClient = class _ParallClient {
|
|
|
28617
28755
|
async deleteExternalConnection(orgId, connectionId) {
|
|
28618
28756
|
return this.request("DELETE", ENDPOINTS.EXTERNAL_CONNECTION(orgId, connectionId));
|
|
28619
28757
|
}
|
|
28758
|
+
// ---- External IM channel (org-scoped) ----
|
|
28759
|
+
async createChannelConnection(orgId, input) {
|
|
28760
|
+
return this.request("POST", ENDPOINTS.CHANNEL_CONNECTIONS(orgId), input);
|
|
28761
|
+
}
|
|
28762
|
+
async listChannelConnections(orgId) {
|
|
28763
|
+
return this.request("GET", ENDPOINTS.CHANNEL_CONNECTIONS(orgId));
|
|
28764
|
+
}
|
|
28765
|
+
async getChannelConnection(orgId, connectionId) {
|
|
28766
|
+
return this.request("GET", ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId));
|
|
28767
|
+
}
|
|
28768
|
+
async updateChannelConnection(orgId, connectionId, patch) {
|
|
28769
|
+
return this.request("PATCH", ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId), patch);
|
|
28770
|
+
}
|
|
28771
|
+
async archiveChannelConnection(orgId, connectionId) {
|
|
28772
|
+
return this.request("DELETE", ENDPOINTS.CHANNEL_CONNECTION(orgId, connectionId));
|
|
28773
|
+
}
|
|
28774
|
+
async deliverChannelCredentials(orgId, connectionId, credentials) {
|
|
28775
|
+
return this.request("POST", ENDPOINTS.CHANNEL_CONNECTION_CREDENTIALS(orgId, connectionId), credentials);
|
|
28776
|
+
}
|
|
28777
|
+
async regenerateChannelIngressToken(orgId, connectionId) {
|
|
28778
|
+
return this.request("POST", ENDPOINTS.CHANNEL_CONNECTION_INGRESS_TOKEN_REGENERATE(orgId, connectionId));
|
|
28779
|
+
}
|
|
28780
|
+
/** Start a Feishu one-click provisioning session (device flow QR). */
|
|
28781
|
+
async initiateChannelProvisioning(orgId, input) {
|
|
28782
|
+
return this.request("POST", ENDPOINTS.CHANNEL_PROVISIONING(orgId), input);
|
|
28783
|
+
}
|
|
28784
|
+
/**
|
|
28785
|
+
* Lazy status poll — server-side this may forward one provider poll, so
|
|
28786
|
+
* call it at the session's `poll_interval_seconds` cadence, not faster.
|
|
28787
|
+
*/
|
|
28788
|
+
async getChannelProvisioningSession(orgId, sessionId) {
|
|
28789
|
+
return this.request("GET", ENDPOINTS.CHANNEL_PROVISIONING_SESSION(orgId, sessionId));
|
|
28790
|
+
}
|
|
28791
|
+
async cancelChannelProvisioning(orgId, sessionId) {
|
|
28792
|
+
return this.request("POST", ENDPOINTS.CHANNEL_PROVISIONING_CANCEL(orgId, sessionId));
|
|
28793
|
+
}
|
|
28794
|
+
async listChannelConversations(orgId, connectionId) {
|
|
28795
|
+
return this.request("GET", ENDPOINTS.CHANNEL_CONNECTION_CONVERSATIONS(orgId, connectionId));
|
|
28796
|
+
}
|
|
28797
|
+
async getChannelConversation(orgId, conversationId) {
|
|
28798
|
+
return this.request("GET", ENDPOINTS.CHANNEL_CONVERSATION(orgId, conversationId));
|
|
28799
|
+
}
|
|
28800
|
+
async listChannelConversationMessages(orgId, conversationId, limit) {
|
|
28801
|
+
return this.request("GET", ENDPOINTS.CHANNEL_CONVERSATION_MESSAGES(orgId, conversationId), void 0, { limit });
|
|
28802
|
+
}
|
|
28803
|
+
async setChannelConversationSession(orgId, conversationId, agentSessionId) {
|
|
28804
|
+
return this.request("PATCH", ENDPOINTS.CHANNEL_CONVERSATION_SESSION(orgId, conversationId), {
|
|
28805
|
+
agent_session_id: agentSessionId
|
|
28806
|
+
});
|
|
28807
|
+
}
|
|
28808
|
+
async getChannelMessage(orgId, messageId) {
|
|
28809
|
+
return this.request("GET", ENDPOINTS.CHANNEL_MESSAGE(orgId, messageId));
|
|
28810
|
+
}
|
|
28620
28811
|
async getExternalTriggerSchema(orgId, connectionId) {
|
|
28621
28812
|
return this.request("GET", ENDPOINTS.EXTERNAL_TRIGGER_SCHEMA(orgId, connectionId));
|
|
28622
28813
|
}
|
|
@@ -28658,6 +28849,23 @@ var ParallClient = class _ParallClient {
|
|
|
28658
28849
|
async getWiki(orgId, wikiId) {
|
|
28659
28850
|
return this.request("GET", ENDPOINTS.WIKI(orgId, wikiId));
|
|
28660
28851
|
}
|
|
28852
|
+
/** Soft-delete an entire wiki (org owner only; the default wiki is
|
|
28853
|
+
* protected → 403 WIKI_PROTECTED). Returns the deleted wiki. Recoverable
|
|
28854
|
+
* via restoreWiki within the retention window. */
|
|
28855
|
+
async deleteWiki(orgId, wikiId) {
|
|
28856
|
+
return this.request("DELETE", ENDPOINTS.WIKI(orgId, wikiId));
|
|
28857
|
+
}
|
|
28858
|
+
/** Restore a soft-deleted wiki (org owner only). 409 WIKI_PURGE_STARTED once
|
|
28859
|
+
* purge has been claimed (no longer restorable), or 404 once the row is gone.
|
|
28860
|
+
* Returns the restored wiki. */
|
|
28861
|
+
async restoreWiki(orgId, wikiId) {
|
|
28862
|
+
return this.request("POST", ENDPOINTS.WIKI_RESTORE(orgId, wikiId));
|
|
28863
|
+
}
|
|
28864
|
+
/** Recycle bin — list soft-deleted wikis for the org (owner only). */
|
|
28865
|
+
async getDeletedWikis(orgId) {
|
|
28866
|
+
const res = await this.request("GET", ENDPOINTS.WIKIS_DELETED(orgId));
|
|
28867
|
+
return res.data;
|
|
28868
|
+
}
|
|
28661
28869
|
async getWikiTree(orgId, wikiId, params) {
|
|
28662
28870
|
return this.request("GET", ENDPOINTS.WIKI_TREE(orgId, wikiId), void 0, params);
|
|
28663
28871
|
}
|
|
@@ -28691,6 +28899,15 @@ var ParallClient = class _ParallClient {
|
|
|
28691
28899
|
blob.signed_url = this.absoluteMediaUrl(blob.signed_url);
|
|
28692
28900
|
return blob;
|
|
28693
28901
|
}
|
|
28902
|
+
/**
|
|
28903
|
+
* Copy one wiki file to another path as an independent snapshot (no sync).
|
|
28904
|
+
* Used to "share a private file out": copy a file from the caller's personal
|
|
28905
|
+
* namespace (`users/{userId}/…`) into the public wiki, leaving the original
|
|
28906
|
+
* untouched. 409 `FILE_EXISTS` if `dest_path` already exists.
|
|
28907
|
+
*/
|
|
28908
|
+
async copyWikiFile(orgId, wikiId, req) {
|
|
28909
|
+
return this.request("POST", ENDPOINTS.WIKI_COPY(orgId, wikiId), req);
|
|
28910
|
+
}
|
|
28694
28911
|
async getWikiNodeSections(orgId, wikiId, params) {
|
|
28695
28912
|
return this.request("GET", ENDPOINTS.WIKI_NODE_SECTIONS(orgId, wikiId), void 0, params);
|
|
28696
28913
|
}
|
|
@@ -28806,8 +29023,8 @@ var ParallClient = class _ParallClient {
|
|
|
28806
29023
|
async deleteWikiRestriction(orgId, wikiId, restrictionId) {
|
|
28807
29024
|
await this.request("DELETE", ENDPOINTS.WIKI_RESTRICTION(orgId, wikiId, restrictionId));
|
|
28808
29025
|
}
|
|
28809
|
-
async getWikiAccessStatus(orgId, wikiId,
|
|
28810
|
-
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0,
|
|
29026
|
+
async getWikiAccessStatus(orgId, wikiId, path10) {
|
|
29027
|
+
return this.request("GET", ENDPOINTS.WIKI_ACCESS_STATUS(orgId, wikiId), void 0, path10 ? { path: path10 } : void 0);
|
|
28811
29028
|
}
|
|
28812
29029
|
async createWikiAccessRequest(orgId, wikiId, data) {
|
|
28813
29030
|
await this.request("POST", ENDPOINTS.WIKI_ACCESS_REQUESTS(orgId, wikiId), data);
|
|
@@ -28816,14 +29033,14 @@ var ParallClient = class _ParallClient {
|
|
|
28816
29033
|
async getWikiCommits(orgId, wikiId, params) {
|
|
28817
29034
|
return this.request("GET", ENDPOINTS.WIKI_COMMITS(orgId, wikiId), void 0, params);
|
|
28818
29035
|
}
|
|
28819
|
-
async getWikiFileCommits(orgId, wikiId,
|
|
29036
|
+
async getWikiFileCommits(orgId, wikiId, path10, params) {
|
|
28820
29037
|
return this.request("GET", ENDPOINTS.WIKI_FILE_COMMITS(orgId, wikiId), void 0, {
|
|
28821
|
-
path:
|
|
29038
|
+
path: path10,
|
|
28822
29039
|
...params
|
|
28823
29040
|
});
|
|
28824
29041
|
}
|
|
28825
|
-
async getWikiBlame(orgId, wikiId,
|
|
28826
|
-
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path:
|
|
29042
|
+
async getWikiBlame(orgId, wikiId, path10, ref) {
|
|
29043
|
+
return this.request("GET", ENDPOINTS.WIKI_BLAME(orgId, wikiId), void 0, { path: path10, ref });
|
|
28827
29044
|
}
|
|
28828
29045
|
// ---- Wiki Operations (audit log) ----
|
|
28829
29046
|
async getWikiOperations(orgId, wikiId, params) {
|
|
@@ -28964,6 +29181,19 @@ var ParallClient = class _ParallClient {
|
|
|
28964
29181
|
const resp = await this.request("GET", ENDPOINTS.COMPUTE_PRICING());
|
|
28965
29182
|
return resp.data;
|
|
28966
29183
|
}
|
|
29184
|
+
/** Runtime capability table (public) — SSOT for the create/settings interlock. */
|
|
29185
|
+
async getRuntimes() {
|
|
29186
|
+
const resp = await this.request("GET", ENDPOINTS.RUNTIMES());
|
|
29187
|
+
return resp.data;
|
|
29188
|
+
}
|
|
29189
|
+
/**
|
|
29190
|
+
* Platform model catalog (public) — DB-backed, replaces the compile-time
|
|
29191
|
+
* PLATFORM_MODELS constant. Pass includeModel to keep an agent's pinned
|
|
29192
|
+
* hidden legacy model representable (settings picker).
|
|
29193
|
+
*/
|
|
29194
|
+
async getModels(includeModel) {
|
|
29195
|
+
return this.request("GET", ENDPOINTS.MODELS(includeModel));
|
|
29196
|
+
}
|
|
28967
29197
|
// ---- Clips ----
|
|
28968
29198
|
async listClips(orgId) {
|
|
28969
29199
|
const resp = await this.request("GET", ENDPOINTS.CLIPS(orgId));
|
|
@@ -29008,6 +29238,11 @@ var ParallClient = class _ParallClient {
|
|
|
29008
29238
|
const resp = await this.request("GET", ENDPOINTS.CLIP_ONLINE(orgId));
|
|
29009
29239
|
return resp.data;
|
|
29010
29240
|
}
|
|
29241
|
+
/** Deployment-wide hosted browser runtime availability — drives whether the
|
|
29242
|
+
* create UI offers the platform-hosted placement. Fail-closed server-side. */
|
|
29243
|
+
async getBrowserRuntimeStatus(orgId) {
|
|
29244
|
+
return this.request("GET", ENDPOINTS.BROWSER_RUNTIME_STATUS(orgId));
|
|
29245
|
+
}
|
|
29011
29246
|
/** Org-wide browser-profile discovery list. Returns the sanitized
|
|
29012
29247
|
* {@link BrowserProfileListItem} shape (not the full domain model), each row
|
|
29013
29248
|
* carrying a per-viewer `can_open` control hint. */
|
|
@@ -29437,6 +29672,404 @@ var ParallWs = class {
|
|
|
29437
29672
|
}
|
|
29438
29673
|
};
|
|
29439
29674
|
|
|
29675
|
+
// ts/agent-core/dist/lane-ledger.js
|
|
29676
|
+
import * as fs from "node:fs";
|
|
29677
|
+
var LedgerUnsupportedError = class extends Error {
|
|
29678
|
+
};
|
|
29679
|
+
function isStaleLane(err) {
|
|
29680
|
+
return err instanceof ApiError && err.status === 409 && err.code === "STALE_LANE";
|
|
29681
|
+
}
|
|
29682
|
+
function isEndpointMissing(err) {
|
|
29683
|
+
return err instanceof ApiError && err.status === 404 && !err.code;
|
|
29684
|
+
}
|
|
29685
|
+
var LaneLedger = class {
|
|
29686
|
+
opts;
|
|
29687
|
+
lanes = /* @__PURE__ */ new Map();
|
|
29688
|
+
constructor(opts) {
|
|
29689
|
+
this.opts = opts;
|
|
29690
|
+
}
|
|
29691
|
+
get contextDir() {
|
|
29692
|
+
return this.opts.contextDir;
|
|
29693
|
+
}
|
|
29694
|
+
/** Only chat message events ride the lane ledger; typed events stay on the legacy ack path. */
|
|
29695
|
+
handles(event) {
|
|
29696
|
+
return event.type === "message" && event.targetId.startsWith("cht_");
|
|
29697
|
+
}
|
|
29698
|
+
laneKeyFor(event) {
|
|
29699
|
+
if (event.type !== "message" && event.dispatchEventId) {
|
|
29700
|
+
return laneKeyForTarget(`dsp:${event.dispatchEventId}`);
|
|
29701
|
+
}
|
|
29702
|
+
return laneKeyForTarget(`prll://${event.targetId}`, event.threadRootId);
|
|
29703
|
+
}
|
|
29704
|
+
getForEvent(event) {
|
|
29705
|
+
return this.lanes.get(this.laneKeyFor(event));
|
|
29706
|
+
}
|
|
29707
|
+
laneContextPath(lane) {
|
|
29708
|
+
return laneContextFilePath(this.opts.contextDir, lane.targetUri, lane.threadRootId);
|
|
29709
|
+
}
|
|
29710
|
+
/**
|
|
29711
|
+
* Claim (or reuse) the lane for a group of same-lane message events and
|
|
29712
|
+
* fold every group member into it. Returns 'foreign' when a healthy
|
|
29713
|
+
* incumbent (another pod) holds the resource — the caller must not
|
|
29714
|
+
* dispatch; the events stay pending server-side and re-drive after the
|
|
29715
|
+
* incumbent completes.
|
|
29716
|
+
*/
|
|
29717
|
+
async ensureLane(events) {
|
|
29718
|
+
const trigger = events[events.length - 1];
|
|
29719
|
+
const laneKey = this.laneKeyFor(trigger);
|
|
29720
|
+
let lane = this.lanes.get(laneKey);
|
|
29721
|
+
if (!lane) {
|
|
29722
|
+
const targetUri = `prll://${trigger.targetId}`;
|
|
29723
|
+
let res;
|
|
29724
|
+
try {
|
|
29725
|
+
res = await this.opts.client.claimDispatch(this.opts.orgId, {
|
|
29726
|
+
target_uri: targetUri,
|
|
29727
|
+
thread_root_id: trigger.threadRootId,
|
|
29728
|
+
limit: 100
|
|
29729
|
+
});
|
|
29730
|
+
} catch (err) {
|
|
29731
|
+
if (isEndpointMissing(err))
|
|
29732
|
+
throw new LedgerUnsupportedError("claim endpoint unavailable");
|
|
29733
|
+
throw err;
|
|
29734
|
+
}
|
|
29735
|
+
if (!res.claimed || !res.lane) {
|
|
29736
|
+
this.opts.log?.info(`lane for ${targetUri} held by a healthy incumbent \u2014 leaving events pending for re-drive`);
|
|
29737
|
+
return null;
|
|
29738
|
+
}
|
|
29739
|
+
const leaseUntilMs = Date.parse(res.lease_until ?? "");
|
|
29740
|
+
lane = {
|
|
29741
|
+
laneKey,
|
|
29742
|
+
lane: res.lane,
|
|
29743
|
+
targetUri,
|
|
29744
|
+
threadRootId: trigger.threadRootId,
|
|
29745
|
+
folded: /* @__PURE__ */ new Map(),
|
|
29746
|
+
...Number.isNaN(leaseUntilMs) ? {} : { leaseUntilMs, leaseTtlMs: Math.max(leaseUntilMs - Date.now(), 6e4) }
|
|
29747
|
+
};
|
|
29748
|
+
for (const ev of res.events ?? []) {
|
|
29749
|
+
lane.folded.set(ev.source_id, ev.id);
|
|
29750
|
+
}
|
|
29751
|
+
this.lanes.set(laneKey, lane);
|
|
29752
|
+
}
|
|
29753
|
+
for (const ev of events) {
|
|
29754
|
+
if (lane.folded.has(ev.messageId))
|
|
29755
|
+
continue;
|
|
29756
|
+
try {
|
|
29757
|
+
const res = await this.opts.client.steerDispatch(this.opts.orgId, {
|
|
29758
|
+
lane: lane.lane,
|
|
29759
|
+
target_uri: lane.targetUri,
|
|
29760
|
+
thread_root_id: lane.threadRootId,
|
|
29761
|
+
...ev.dispatchEventId ? { dispatch_event_id: ev.dispatchEventId } : { source_type: "message", source_id: ev.messageId }
|
|
29762
|
+
});
|
|
29763
|
+
lane.folded.set(ev.messageId, res.dispatch_event_id);
|
|
29764
|
+
} catch (err) {
|
|
29765
|
+
if (isStaleLane(err)) {
|
|
29766
|
+
this.lanes.delete(laneKey);
|
|
29767
|
+
return null;
|
|
29768
|
+
}
|
|
29769
|
+
this.opts.log?.warn(`steer fold failed for ${ev.messageId} \u2014 failing closed, releasing lane: ${String(err)}`);
|
|
29770
|
+
await this.release(laneKey);
|
|
29771
|
+
return null;
|
|
29772
|
+
}
|
|
29773
|
+
}
|
|
29774
|
+
return lane;
|
|
29775
|
+
}
|
|
29776
|
+
/**
|
|
29777
|
+
* Fold a live mid-turn message into its active lane BEFORE injecting it
|
|
29778
|
+
* into the running turn. Injection without a successful fold is forbidden —
|
|
29779
|
+
* an un-folded injected message would be re-driven after complete and the
|
|
29780
|
+
* model would handle it twice.
|
|
29781
|
+
*/
|
|
29782
|
+
async steerLive(event) {
|
|
29783
|
+
const laneKey = this.laneKeyFor(event);
|
|
29784
|
+
const lane = this.lanes.get(laneKey);
|
|
29785
|
+
if (!lane)
|
|
29786
|
+
return false;
|
|
29787
|
+
if (lane.folded.has(event.messageId))
|
|
29788
|
+
return true;
|
|
29789
|
+
try {
|
|
29790
|
+
const res = await this.opts.client.steerDispatch(this.opts.orgId, {
|
|
29791
|
+
lane: lane.lane,
|
|
29792
|
+
target_uri: lane.targetUri,
|
|
29793
|
+
thread_root_id: lane.threadRootId,
|
|
29794
|
+
...event.dispatchEventId ? { dispatch_event_id: event.dispatchEventId } : { source_type: "message", source_id: event.messageId }
|
|
29795
|
+
});
|
|
29796
|
+
lane.folded.set(event.messageId, res.dispatch_event_id);
|
|
29797
|
+
return true;
|
|
29798
|
+
} catch (err) {
|
|
29799
|
+
if (isStaleLane(err)) {
|
|
29800
|
+
this.lanes.delete(laneKey);
|
|
29801
|
+
} else {
|
|
29802
|
+
this.opts.log?.warn(`live steer failed for ${event.messageId}: ${String(err)}`);
|
|
29803
|
+
}
|
|
29804
|
+
return false;
|
|
29805
|
+
}
|
|
29806
|
+
}
|
|
29807
|
+
/**
|
|
29808
|
+
* Complete the lane when no local work remains for it: the server sweeps
|
|
29809
|
+
* still-leased members as no_action, releases the occupancy row, and
|
|
29810
|
+
* re-drives any same-target pending work. A STALE_LANE answer means a
|
|
29811
|
+
* takeover already owns the resource — local state is dropped either way.
|
|
29812
|
+
*/
|
|
29813
|
+
async completeIfIdle(laneKey, hasMoreLocal) {
|
|
29814
|
+
const lane = this.lanes.get(laneKey);
|
|
29815
|
+
if (!lane || hasMoreLocal)
|
|
29816
|
+
return;
|
|
29817
|
+
this.lanes.delete(laneKey);
|
|
29818
|
+
this.removeLaneContext(lane);
|
|
29819
|
+
try {
|
|
29820
|
+
const res = await this.opts.client.completeDispatch(this.opts.orgId, {
|
|
29821
|
+
lane: lane.lane,
|
|
29822
|
+
target_uri: lane.targetUri,
|
|
29823
|
+
thread_root_id: lane.threadRootId
|
|
29824
|
+
});
|
|
29825
|
+
if (res.swept_no_action > 0 || res.redriven) {
|
|
29826
|
+
this.opts.log?.info(`lane complete for ${lane.targetUri}: swept ${res.swept_no_action} no_action, redriven=${res.redriven}`);
|
|
29827
|
+
}
|
|
29828
|
+
} catch (err) {
|
|
29829
|
+
if (isStaleLane(err)) {
|
|
29830
|
+
this.opts.log?.info(`lane complete skipped for ${lane.targetUri} \u2014 taken over`);
|
|
29831
|
+
return;
|
|
29832
|
+
}
|
|
29833
|
+
this.opts.log?.warn(`lane complete failed for ${lane.targetUri}: ${String(err)}`);
|
|
29834
|
+
}
|
|
29835
|
+
}
|
|
29836
|
+
/**
|
|
29837
|
+
* Long-turn keepalive: renew the lane's lease on runtime activity, throttled
|
|
29838
|
+
* so a chatty turn doesn't spam the server. Without this, a legitimately
|
|
29839
|
+
* long turn (> lane TTL) would be dethroned mid-flight and every subsequent
|
|
29840
|
+
* write misfired with STALE_LANE — the design doc's "long turns renew via
|
|
29841
|
+
* step writes". Fire-and-forget: a failed renewal is surfaced by the next
|
|
29842
|
+
* write's incumbency check anyway.
|
|
29843
|
+
*/
|
|
29844
|
+
maybeRenew(lane) {
|
|
29845
|
+
const now = Date.now();
|
|
29846
|
+
const ttl = lane.leaseTtlMs ?? 10 * 6e4;
|
|
29847
|
+
const until = lane.leaseUntilMs ?? now;
|
|
29848
|
+
if (until - now > ttl / 2)
|
|
29849
|
+
return;
|
|
29850
|
+
lane.leaseUntilMs = now + ttl;
|
|
29851
|
+
void this.opts.client.heartbeatDispatchLane(this.opts.orgId, {
|
|
29852
|
+
lane: lane.lane,
|
|
29853
|
+
target_uri: lane.targetUri,
|
|
29854
|
+
thread_root_id: lane.threadRootId
|
|
29855
|
+
}).then((res) => {
|
|
29856
|
+
const until2 = Date.parse(res?.lease_until ?? "");
|
|
29857
|
+
if (!Number.isNaN(until2))
|
|
29858
|
+
lane.leaseUntilMs = until2;
|
|
29859
|
+
}).catch((err) => {
|
|
29860
|
+
if (isStaleLane(err)) {
|
|
29861
|
+
this.lanes.delete(lane.laneKey);
|
|
29862
|
+
this.opts.log?.warn(`lane ${lane.targetUri} was taken over during the turn`);
|
|
29863
|
+
return;
|
|
29864
|
+
}
|
|
29865
|
+
this.opts.log?.warn(`lane heartbeat failed for ${lane.targetUri}: ${String(err)}`);
|
|
29866
|
+
});
|
|
29867
|
+
}
|
|
29868
|
+
/**
|
|
29869
|
+
* Release a lane's unresolved members back to pending (dispatch error /
|
|
29870
|
+
* shutdown) so the next pod re-claims immediately instead of waiting out
|
|
29871
|
+
* the lease.
|
|
29872
|
+
*/
|
|
29873
|
+
async release(laneKey) {
|
|
29874
|
+
const lane = this.lanes.get(laneKey);
|
|
29875
|
+
if (!lane)
|
|
29876
|
+
return;
|
|
29877
|
+
this.lanes.delete(laneKey);
|
|
29878
|
+
this.removeLaneContext(lane);
|
|
29879
|
+
try {
|
|
29880
|
+
await this.opts.client.releaseDispatchLane(this.opts.orgId, lane.lane);
|
|
29881
|
+
} catch (err) {
|
|
29882
|
+
this.opts.log?.warn(`lane release failed for ${lane.targetUri}: ${String(err)}`);
|
|
29883
|
+
}
|
|
29884
|
+
}
|
|
29885
|
+
async releaseAll() {
|
|
29886
|
+
const keys = [...this.lanes.keys()];
|
|
29887
|
+
for (const key of keys) {
|
|
29888
|
+
await this.release(key);
|
|
29889
|
+
}
|
|
29890
|
+
}
|
|
29891
|
+
/** True when any lane is currently active (used by shutdown logging). */
|
|
29892
|
+
get activeCount() {
|
|
29893
|
+
return this.lanes.size;
|
|
29894
|
+
}
|
|
29895
|
+
/**
|
|
29896
|
+
* Claim the single-member lane of one typed WorkItem (resource = dsp:<id>),
|
|
29897
|
+
* by WorkItem id or by source identity (the live task.assigned event has no
|
|
29898
|
+
* WorkItem id). Returns null when a healthy incumbent (another pod) holds
|
|
29899
|
+
* it or the WorkItem is already resolved — the caller must skip processing.
|
|
29900
|
+
*/
|
|
29901
|
+
async claimTyped(ref) {
|
|
29902
|
+
let res;
|
|
29903
|
+
try {
|
|
29904
|
+
res = await this.opts.client.claimDispatch(this.opts.orgId, {
|
|
29905
|
+
dispatch_event_id: ref.dispatchEventId,
|
|
29906
|
+
source_type: ref.dispatchEventId ? void 0 : ref.sourceType,
|
|
29907
|
+
source_id: ref.dispatchEventId ? void 0 : ref.sourceId
|
|
29908
|
+
});
|
|
29909
|
+
} catch (err) {
|
|
29910
|
+
if (isEndpointMissing(err))
|
|
29911
|
+
throw new LedgerUnsupportedError("claim endpoint unavailable");
|
|
29912
|
+
throw err;
|
|
29913
|
+
}
|
|
29914
|
+
if (!res.claimed || !res.lane || !res.events?.length)
|
|
29915
|
+
return null;
|
|
29916
|
+
const workItem = res.events[0];
|
|
29917
|
+
const targetUri = `dsp:${workItem.id}`;
|
|
29918
|
+
const leaseUntilMs = Date.parse(res.lease_until ?? "");
|
|
29919
|
+
const lane = {
|
|
29920
|
+
laneKey: laneKeyForTarget(targetUri),
|
|
29921
|
+
lane: res.lane,
|
|
29922
|
+
targetUri,
|
|
29923
|
+
folded: /* @__PURE__ */ new Map([[workItem.source_id, workItem.id]]),
|
|
29924
|
+
typedDispatchEventId: workItem.id,
|
|
29925
|
+
...Number.isNaN(leaseUntilMs) ? {} : { leaseUntilMs, leaseTtlMs: Math.max(leaseUntilMs - Date.now(), 6e4) }
|
|
29926
|
+
};
|
|
29927
|
+
this.lanes.set(lane.laneKey, lane);
|
|
29928
|
+
return lane;
|
|
29929
|
+
}
|
|
29930
|
+
/**
|
|
29931
|
+
* Remove the per-lane context file (and its CLI sidecar) when the lane
|
|
29932
|
+
* ends. A leftover file would make a later cross-context send to the same
|
|
29933
|
+
* target bind a dead lane token and misfire with STALE_LANE instead of
|
|
29934
|
+
* taking the plain non-ledger path.
|
|
29935
|
+
*/
|
|
29936
|
+
removeLaneContext(lane) {
|
|
29937
|
+
const contextPath = this.laneContextPath(lane);
|
|
29938
|
+
for (const p of [contextPath, contextPath.replace(/\.json$/, ".reply-state.json")]) {
|
|
29939
|
+
try {
|
|
29940
|
+
fs.rmSync(p, { force: true });
|
|
29941
|
+
} catch {
|
|
29942
|
+
}
|
|
29943
|
+
}
|
|
29944
|
+
}
|
|
29945
|
+
};
|
|
29946
|
+
|
|
29947
|
+
// ts/agent-core/dist/gateway-lane-flow.js
|
|
29948
|
+
async function dispatchLaneGroup(host, opts) {
|
|
29949
|
+
const ledger = host.laneLedger;
|
|
29950
|
+
const event = opts.events[opts.events.length - 1];
|
|
29951
|
+
let lane;
|
|
29952
|
+
try {
|
|
29953
|
+
lane = await ledger.ensureLane(opts.events);
|
|
29954
|
+
} catch (err) {
|
|
29955
|
+
if (!(err instanceof LedgerUnsupportedError))
|
|
29956
|
+
throw err;
|
|
29957
|
+
host.disableLedger("claim endpoint missing");
|
|
29958
|
+
await host.emitDispatchReceived(event);
|
|
29959
|
+
const dispatched2 = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText);
|
|
29960
|
+
if (!dispatched2)
|
|
29961
|
+
return "shutdown";
|
|
29962
|
+
for (const ev of opts.events) {
|
|
29963
|
+
host.opts.client.ackDispatch(host.opts.config.org_id, {
|
|
29964
|
+
source_type: ev.ackSourceType ?? "message",
|
|
29965
|
+
source_id: ev.ackSourceId ?? ev.messageId
|
|
29966
|
+
}).catch(() => {
|
|
29967
|
+
});
|
|
29968
|
+
}
|
|
29969
|
+
return "dispatched";
|
|
29970
|
+
}
|
|
29971
|
+
if (!lane) {
|
|
29972
|
+
for (const ev of opts.events) {
|
|
29973
|
+
host.dispatchedMessages.delete(ev.messageId);
|
|
29974
|
+
}
|
|
29975
|
+
return "foreign";
|
|
29976
|
+
}
|
|
29977
|
+
let dispatched = false;
|
|
29978
|
+
try {
|
|
29979
|
+
dispatched = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText);
|
|
29980
|
+
} catch (err) {
|
|
29981
|
+
await ledger.release(lane.laneKey).catch(() => {
|
|
29982
|
+
});
|
|
29983
|
+
throw err;
|
|
29984
|
+
}
|
|
29985
|
+
if (!dispatched) {
|
|
29986
|
+
return "shutdown";
|
|
29987
|
+
}
|
|
29988
|
+
const pendingInjections = host.opts.dispatchAdapter.hasPendingInjections?.(opts.sessionKey) ?? false;
|
|
29989
|
+
await ledger.completeIfIdle(lane.laneKey, pendingInjections || opts.hasMoreLocal());
|
|
29990
|
+
return "dispatched";
|
|
29991
|
+
}
|
|
29992
|
+
async function consumeTypedDispatch(host, ref, run, ack) {
|
|
29993
|
+
if (!host.laneLedger || host.ledgerDisabled) {
|
|
29994
|
+
if (await run(ref.dispatchEventId))
|
|
29995
|
+
ack(ref.dispatchEventId);
|
|
29996
|
+
return;
|
|
29997
|
+
}
|
|
29998
|
+
let lane;
|
|
29999
|
+
try {
|
|
30000
|
+
lane = await host.laneLedger.claimTyped(ref);
|
|
30001
|
+
} catch (err) {
|
|
30002
|
+
if (err instanceof LedgerUnsupportedError) {
|
|
30003
|
+
host.disableLedger("claim endpoint missing");
|
|
30004
|
+
if (await run(ref.dispatchEventId))
|
|
30005
|
+
ack(ref.dispatchEventId);
|
|
30006
|
+
return;
|
|
30007
|
+
}
|
|
30008
|
+
throw err;
|
|
30009
|
+
}
|
|
30010
|
+
if (!lane) {
|
|
30011
|
+
host.opts.log?.info(`typed dispatch ${ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`} not claimable (held elsewhere or already resolved) \u2014 skipping`);
|
|
30012
|
+
return;
|
|
30013
|
+
}
|
|
30014
|
+
try {
|
|
30015
|
+
if (await run(lane.typedDispatchEventId))
|
|
30016
|
+
ack(lane.typedDispatchEventId);
|
|
30017
|
+
} finally {
|
|
30018
|
+
await host.laneLedger.completeIfIdle(lane.laneKey, false).catch(() => {
|
|
30019
|
+
});
|
|
30020
|
+
}
|
|
30021
|
+
}
|
|
30022
|
+
async function consumeMessageWorkItem(host, item) {
|
|
30023
|
+
if (host.shuttingDown)
|
|
30024
|
+
return;
|
|
30025
|
+
if (!host.tryClaimMessage(item.source_id))
|
|
30026
|
+
return;
|
|
30027
|
+
const ackItem = () => {
|
|
30028
|
+
host.opts.client.ackDispatchByID(host.opts.config.org_id, item.id).catch(() => {
|
|
30029
|
+
});
|
|
30030
|
+
};
|
|
30031
|
+
let msg = null;
|
|
30032
|
+
try {
|
|
30033
|
+
msg = await host.opts.client.getMessage(item.source_id);
|
|
30034
|
+
} catch (err) {
|
|
30035
|
+
const status = err?.status;
|
|
30036
|
+
if (status !== 404) {
|
|
30037
|
+
host.opts.log?.warn(`message fetch failed for ${item.source_id}, leaving pending: ${String(err)}`);
|
|
30038
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30039
|
+
return;
|
|
30040
|
+
}
|
|
30041
|
+
}
|
|
30042
|
+
if (!msg || msg.sender_id === host.opts.agentUserId) {
|
|
30043
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30044
|
+
ackItem();
|
|
30045
|
+
return;
|
|
30046
|
+
}
|
|
30047
|
+
const decision = await host.buildMessageDispatchDecision(item.chat_id, msg);
|
|
30048
|
+
if (decision.action === "retry") {
|
|
30049
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30050
|
+
return;
|
|
30051
|
+
}
|
|
30052
|
+
if (decision.action === "skip") {
|
|
30053
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30054
|
+
ackItem();
|
|
30055
|
+
return;
|
|
30056
|
+
}
|
|
30057
|
+
decision.event.dispatchEventId = item.id;
|
|
30058
|
+
const laneResolved = host.usesLaneLedger(decision.event);
|
|
30059
|
+
try {
|
|
30060
|
+
const dispatched = await host.handleInboundEvent(decision.event);
|
|
30061
|
+
if (dispatched) {
|
|
30062
|
+
if (!laneResolved)
|
|
30063
|
+
ackItem();
|
|
30064
|
+
} else {
|
|
30065
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30066
|
+
}
|
|
30067
|
+
} catch (err) {
|
|
30068
|
+
host.dispatchedMessages.delete(item.source_id);
|
|
30069
|
+
throw err;
|
|
30070
|
+
}
|
|
30071
|
+
}
|
|
30072
|
+
|
|
29440
30073
|
// ts/agent-core/dist/telemetry.js
|
|
29441
30074
|
init_esm();
|
|
29442
30075
|
var import_api_logs = __toESM(require_src(), 1);
|
|
@@ -29667,6 +30300,9 @@ function resolveStepTarget(event) {
|
|
|
29667
30300
|
if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
|
|
29668
30301
|
return { target_type: "external_trigger", target_id: event.targetId };
|
|
29669
30302
|
}
|
|
30303
|
+
if (event.type === "channel_message" || event.targetId.startsWith("chv_")) {
|
|
30304
|
+
return { target_type: "channel_conversation", target_id: event.targetId };
|
|
30305
|
+
}
|
|
29670
30306
|
if (event.type === "wiki_comment") {
|
|
29671
30307
|
return { target_type: "wiki", target_id: event.targetId || void 0 };
|
|
29672
30308
|
}
|
|
@@ -29710,6 +30346,9 @@ var ParallAgentGateway = class {
|
|
|
29710
30346
|
opts;
|
|
29711
30347
|
chatInfoMap = /* @__PURE__ */ new Map();
|
|
29712
30348
|
dispatchedTasks = /* @__PURE__ */ new Set();
|
|
30349
|
+
// connection id → provider alias, for channel_message prompt labeling
|
|
30350
|
+
// (stable mapping; avoids one connection fetch per inbound message).
|
|
30351
|
+
channelConnectionProviders = /* @__PURE__ */ new Map();
|
|
29713
30352
|
dispatchedMessages = /* @__PURE__ */ new Set();
|
|
29714
30353
|
forkStates = /* @__PURE__ */ new Map();
|
|
29715
30354
|
dispatchState = {
|
|
@@ -29732,6 +30371,14 @@ var ParallAgentGateway = class {
|
|
|
29732
30371
|
inFlightDispatches = 0;
|
|
29733
30372
|
drainResolvers = [];
|
|
29734
30373
|
pendingRestartNotification = null;
|
|
30374
|
+
laneLedger;
|
|
30375
|
+
// Sticky fallback: flipped when the server predates the ledger (claim
|
|
30376
|
+
// endpoint 404) so every subsequent dispatch uses the legacy flow.
|
|
30377
|
+
ledgerDisabled = false;
|
|
30378
|
+
// Group key of the group currently being dispatched on main — lane-aware
|
|
30379
|
+
// (targetId + thread), unlike mainCurrentTargetId which stays chat-level
|
|
30380
|
+
// for fork routing decisions.
|
|
30381
|
+
mainCurrentGroupKey;
|
|
29735
30382
|
DISPATCHED_MESSAGES_CAP = 5e3;
|
|
29736
30383
|
// SHUTDOWN_DEADLINE_MS is read by waitForDrain via the configured value
|
|
29737
30384
|
// below — kept as instance state so per-runtime configs can override it
|
|
@@ -29741,6 +30388,14 @@ var ParallAgentGateway = class {
|
|
|
29741
30388
|
DISPATCH_DEADLINE_MS;
|
|
29742
30389
|
constructor(opts) {
|
|
29743
30390
|
this.opts = opts;
|
|
30391
|
+
if (opts.dispatchContextDir) {
|
|
30392
|
+
this.laneLedger = new LaneLedger({
|
|
30393
|
+
client: opts.client,
|
|
30394
|
+
orgId: opts.config.org_id,
|
|
30395
|
+
contextDir: opts.dispatchContextDir,
|
|
30396
|
+
log: opts.log
|
|
30397
|
+
});
|
|
30398
|
+
}
|
|
29744
30399
|
this.SHUTDOWN_DEADLINE_MS = opts.shutdownDeadlineMs ?? 6e4;
|
|
29745
30400
|
this.FORK_DEADLINE_MS = opts.forkDeadlineMs ?? 2 * 60 * 6e4;
|
|
29746
30401
|
this.DISPATCH_DEADLINE_MS = opts.dispatchDeadlineMs ?? 20 * 6e4;
|
|
@@ -29810,14 +30465,18 @@ var ParallAgentGateway = class {
|
|
|
29810
30465
|
if (data.status !== "todo" && data.status !== "in_progress")
|
|
29811
30466
|
return;
|
|
29812
30467
|
try {
|
|
29813
|
-
|
|
29814
|
-
|
|
30468
|
+
await this.consumeTypedDispatch(data.dispatch_event_id ? { dispatchEventId: data.dispatch_event_id } : { sourceType: "task_activity", sourceId: data.id }, (dispatchEventId) => this.handleTaskAssignment(data, data.id, dispatchEventId), (dispatchEventId) => {
|
|
30469
|
+
if (dispatchEventId) {
|
|
30470
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, dispatchEventId).catch(() => {
|
|
30471
|
+
});
|
|
30472
|
+
return;
|
|
30473
|
+
}
|
|
29815
30474
|
this.opts.client.ackDispatch(this.opts.config.org_id, {
|
|
29816
30475
|
source_type: "task_activity",
|
|
29817
30476
|
source_id: data.id
|
|
29818
30477
|
}).catch(() => {
|
|
29819
30478
|
});
|
|
29820
|
-
}
|
|
30479
|
+
});
|
|
29821
30480
|
} catch (err) {
|
|
29822
30481
|
this.opts.log?.error(`task dispatch failed for ${data.id}: ${String(err)}`);
|
|
29823
30482
|
}
|
|
@@ -29827,11 +30486,10 @@ var ParallAgentGateway = class {
|
|
|
29827
30486
|
if (!data.source_id || !data.task_id)
|
|
29828
30487
|
return;
|
|
29829
30488
|
try {
|
|
29830
|
-
|
|
29831
|
-
if (dispatched) {
|
|
30489
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleTaskComment(data.source_id, data.task_id ?? "", data.actor_id, data.delivery_reason), () => {
|
|
29832
30490
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29833
30491
|
});
|
|
29834
|
-
}
|
|
30492
|
+
});
|
|
29835
30493
|
} catch (err) {
|
|
29836
30494
|
this.opts.log?.error(`task comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29837
30495
|
}
|
|
@@ -29839,11 +30497,10 @@ var ParallAgentGateway = class {
|
|
|
29839
30497
|
if (!data.source_id)
|
|
29840
30498
|
return;
|
|
29841
30499
|
try {
|
|
29842
|
-
|
|
29843
|
-
if (dispatched) {
|
|
30500
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.handleWikiComment(data.source_id, data.actor_id, data.delivery_reason), () => {
|
|
29844
30501
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29845
30502
|
});
|
|
29846
|
-
}
|
|
30503
|
+
});
|
|
29847
30504
|
} catch (err) {
|
|
29848
30505
|
this.opts.log?.error(`wiki comment dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29849
30506
|
}
|
|
@@ -29851,11 +30508,13 @@ var ParallAgentGateway = class {
|
|
|
29851
30508
|
if (!data.task_id)
|
|
29852
30509
|
return;
|
|
29853
30510
|
try {
|
|
29854
|
-
|
|
29855
|
-
|
|
30511
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, (dispatchEventId) => this.handleTaskDispatch(data.task_id ?? "", data.source_id ?? data.task_id ?? "", {
|
|
30512
|
+
allowCreator: true,
|
|
30513
|
+
dispatchEventId
|
|
30514
|
+
}), () => {
|
|
29856
30515
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29857
30516
|
});
|
|
29858
|
-
}
|
|
30517
|
+
});
|
|
29859
30518
|
} catch (err) {
|
|
29860
30519
|
this.opts.log?.error(`task update dispatch failed for ${data.task_id}: ${String(err)}`);
|
|
29861
30520
|
}
|
|
@@ -29863,11 +30522,10 @@ var ParallAgentGateway = class {
|
|
|
29863
30522
|
if (!data.source_id)
|
|
29864
30523
|
return;
|
|
29865
30524
|
try {
|
|
29866
|
-
|
|
29867
|
-
if (dispatched) {
|
|
30525
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleScheduleFire(data.source_id, data.actor_id), () => {
|
|
29868
30526
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29869
30527
|
});
|
|
29870
|
-
}
|
|
30528
|
+
});
|
|
29871
30529
|
} catch (err) {
|
|
29872
30530
|
this.opts.log?.error(`schedule fire dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29873
30531
|
}
|
|
@@ -29875,26 +30533,41 @@ var ParallAgentGateway = class {
|
|
|
29875
30533
|
if (!data.source_id)
|
|
29876
30534
|
return;
|
|
29877
30535
|
try {
|
|
29878
|
-
|
|
29879
|
-
if (dispatched) {
|
|
30536
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleExternalTriggerRun(data.source_id), () => {
|
|
29880
30537
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29881
30538
|
});
|
|
29882
|
-
}
|
|
30539
|
+
});
|
|
29883
30540
|
} catch (err) {
|
|
29884
30541
|
this.opts.log?.error(`external trigger dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29885
30542
|
}
|
|
30543
|
+
} else if (data.event_type === "channel_message") {
|
|
30544
|
+
if (!data.source_id)
|
|
30545
|
+
return;
|
|
30546
|
+
try {
|
|
30547
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleChannelMessage(data.source_id), () => {
|
|
30548
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
30549
|
+
});
|
|
30550
|
+
});
|
|
30551
|
+
} catch (err) {
|
|
30552
|
+
this.opts.log?.error(`channel message dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
30553
|
+
}
|
|
29886
30554
|
} else if (data.event_type === "approval_decided") {
|
|
29887
30555
|
if (!data.source_id)
|
|
29888
30556
|
return;
|
|
29889
30557
|
try {
|
|
29890
|
-
|
|
29891
|
-
if (dispatched) {
|
|
30558
|
+
await this.consumeTypedDispatch({ dispatchEventId: data.id }, () => this.fetchAndHandleApprovalDecided(data.source_id, data.actor_id, data.chat_id ?? null), () => {
|
|
29892
30559
|
this.opts.client.ackDispatchByID(this.opts.config.org_id, data.id).catch(() => {
|
|
29893
30560
|
});
|
|
29894
|
-
}
|
|
30561
|
+
});
|
|
29895
30562
|
} catch (err) {
|
|
29896
30563
|
this.opts.log?.error(`approval decided dispatch failed for ${data.source_id}: ${String(err)}`);
|
|
29897
30564
|
}
|
|
30565
|
+
} else if (data.event_type === "message" && this.laneLedger && data.source_id && data.chat_id) {
|
|
30566
|
+
try {
|
|
30567
|
+
await this.handleMessageRedrive(data);
|
|
30568
|
+
} catch (err) {
|
|
30569
|
+
this.opts.log?.error(`message re-drive failed for ${data.source_id}: ${String(err)}`);
|
|
30570
|
+
}
|
|
29898
30571
|
} else if (data.event_type !== "message" && data.event_type !== "task_assign") {
|
|
29899
30572
|
this.opts.log?.info(`dispatch.new with unhandled event_type=${String(data.event_type)} (id=${data.id}) \u2014 no-op`);
|
|
29900
30573
|
}
|
|
@@ -29930,6 +30603,39 @@ var ParallAgentGateway = class {
|
|
|
29930
30603
|
source_id: sourceId
|
|
29931
30604
|
});
|
|
29932
30605
|
}
|
|
30606
|
+
/** True when this event's lifecycle is owned by the dispatch lane ledger. */
|
|
30607
|
+
usesLaneLedger(event) {
|
|
30608
|
+
return this.laneLedger != null && !this.ledgerDisabled && this.laneLedger.handles(event);
|
|
30609
|
+
}
|
|
30610
|
+
disableLedger(reason) {
|
|
30611
|
+
if (this.ledgerDisabled)
|
|
30612
|
+
return;
|
|
30613
|
+
this.ledgerDisabled = true;
|
|
30614
|
+
this.opts.log?.warn(`dispatch ledger unavailable (${reason}) \u2014 falling back to legacy received/ack flow`);
|
|
30615
|
+
}
|
|
30616
|
+
/**
|
|
30617
|
+
* Buffer grouping key. Lane-ledger message events group by full lane
|
|
30618
|
+
* identity (chat + thread) so a channel lane and a thread lane in the same
|
|
30619
|
+
* chat dispatch as separate turns with separate claims; everything else
|
|
30620
|
+
* keeps the historical chat-level grouping.
|
|
30621
|
+
*/
|
|
30622
|
+
dispatchGroupKey(event) {
|
|
30623
|
+
if (this.usesLaneLedger(event)) {
|
|
30624
|
+
return this.laneLedger.laneKeyFor(event);
|
|
30625
|
+
}
|
|
30626
|
+
return event.targetId;
|
|
30627
|
+
}
|
|
30628
|
+
// Lane-flow protocols live in gateway-lane-flow.ts; these thin delegates
|
|
30629
|
+
// keep call sites and tests on the class surface.
|
|
30630
|
+
laneFlowHost() {
|
|
30631
|
+
return this;
|
|
30632
|
+
}
|
|
30633
|
+
dispatchLaneGroup(opts) {
|
|
30634
|
+
return dispatchLaneGroup(this.laneFlowHost(), opts);
|
|
30635
|
+
}
|
|
30636
|
+
consumeTypedDispatch(ref, run, ack) {
|
|
30637
|
+
return consumeTypedDispatch(this.laneFlowHost(), ref, run, ack);
|
|
30638
|
+
}
|
|
29933
30639
|
buildDispatchContext(event, sessionKey) {
|
|
29934
30640
|
const binding = this.sessionBindings.get(sessionKey);
|
|
29935
30641
|
return {
|
|
@@ -29946,6 +30652,7 @@ var ParallAgentGateway = class {
|
|
|
29946
30652
|
noReply: event.noReply ?? false,
|
|
29947
30653
|
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey),
|
|
29948
30654
|
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey),
|
|
30655
|
+
contextDirPath: this.opts.dispatchContextDir,
|
|
29949
30656
|
client: this.opts.client,
|
|
29950
30657
|
log: this.opts.log
|
|
29951
30658
|
};
|
|
@@ -29961,12 +30668,17 @@ var ParallAgentGateway = class {
|
|
|
29961
30668
|
target_type: target.target_type,
|
|
29962
30669
|
target_id: target.target_id,
|
|
29963
30670
|
content: {
|
|
29964
|
-
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",
|
|
30671
|
+
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",
|
|
29965
30672
|
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" ? {
|
|
29966
30673
|
trigger_id: event.targetId,
|
|
29967
30674
|
run_id: event.messageId,
|
|
29968
30675
|
connection_id: event.externalConnectionId,
|
|
29969
30676
|
ingress_event_id: event.externalIngressEventId
|
|
30677
|
+
} : event.type === "channel_message" ? {
|
|
30678
|
+
conversation_id: event.targetId,
|
|
30679
|
+
channel_message_id: event.messageId,
|
|
30680
|
+
provider: event.channelProvider,
|
|
30681
|
+
external_conversation_id: event.channelExternalConversationId
|
|
29970
30682
|
} : event.type === "approval" ? { approval_id: event.messageId } : { message_id: event.messageId },
|
|
29971
30683
|
sender_id: event.senderId,
|
|
29972
30684
|
sender_name: event.senderName,
|
|
@@ -29980,7 +30692,7 @@ var ParallAgentGateway = class {
|
|
|
29980
30692
|
this.opts.log?.warn(`failed to create input step: ${String(err)}`);
|
|
29981
30693
|
}
|
|
29982
30694
|
}
|
|
29983
|
-
async createRuntimeStep(sessionId, event, runtimeEvent, stepIdFilePath, contextFilePath) {
|
|
30695
|
+
async createRuntimeStep(sessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2) {
|
|
29984
30696
|
const target = resolveStepTarget(event);
|
|
29985
30697
|
try {
|
|
29986
30698
|
switch (runtimeEvent.type) {
|
|
@@ -30026,6 +30738,9 @@ var ParallAgentGateway = class {
|
|
|
30026
30738
|
} else if (stepIdFilePath) {
|
|
30027
30739
|
this.writeStepIdFile(stepIdFilePath, step.id);
|
|
30028
30740
|
}
|
|
30741
|
+
if (laneContextFilePath2) {
|
|
30742
|
+
this.updateContextFileStepId(laneContextFilePath2, step.id);
|
|
30743
|
+
}
|
|
30029
30744
|
break;
|
|
30030
30745
|
}
|
|
30031
30746
|
case "tool_result":
|
|
@@ -30048,6 +30763,9 @@ var ParallAgentGateway = class {
|
|
|
30048
30763
|
} else if (stepIdFilePath) {
|
|
30049
30764
|
this.clearStepIdFile(stepIdFilePath);
|
|
30050
30765
|
}
|
|
30766
|
+
if (laneContextFilePath2) {
|
|
30767
|
+
this.updateContextFileStepId(laneContextFilePath2, null);
|
|
30768
|
+
}
|
|
30051
30769
|
break;
|
|
30052
30770
|
case "error":
|
|
30053
30771
|
await this.opts.client.createAgentStep(this.opts.config.org_id, this.opts.agentUserId, sessionId, {
|
|
@@ -30067,28 +30785,28 @@ var ParallAgentGateway = class {
|
|
|
30067
30785
|
}
|
|
30068
30786
|
writeContextFile(filePath, ctx) {
|
|
30069
30787
|
try {
|
|
30070
|
-
|
|
30071
|
-
|
|
30788
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
30789
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30072
30790
|
} catch (err) {
|
|
30073
30791
|
this.opts.log?.warn(`failed to write context file ${filePath}: ${String(err)}`);
|
|
30074
30792
|
}
|
|
30075
30793
|
}
|
|
30076
30794
|
updateContextFileStepId(filePath, stepId) {
|
|
30077
30795
|
try {
|
|
30078
|
-
const raw =
|
|
30796
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
30079
30797
|
const ctx = JSON.parse(raw);
|
|
30080
30798
|
ctx.step_id = stepId;
|
|
30081
|
-
|
|
30799
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30082
30800
|
} catch (err) {
|
|
30083
30801
|
this.opts.log?.warn(`failed to update context file step_id ${filePath}: ${String(err)}`);
|
|
30084
30802
|
}
|
|
30085
30803
|
}
|
|
30086
30804
|
updateContextFileSessionId(filePath, sessionId) {
|
|
30087
30805
|
try {
|
|
30088
|
-
const raw =
|
|
30806
|
+
const raw = fs2.readFileSync(filePath, "utf8");
|
|
30089
30807
|
const ctx = JSON.parse(raw);
|
|
30090
30808
|
ctx.session_id = sessionId;
|
|
30091
|
-
|
|
30809
|
+
fs2.writeFileSync(filePath, JSON.stringify(ctx), "utf8");
|
|
30092
30810
|
} catch (err) {
|
|
30093
30811
|
this.opts.log?.warn(`failed to update context file session_id ${filePath}: ${String(err)}`);
|
|
30094
30812
|
}
|
|
@@ -30096,8 +30814,8 @@ var ParallAgentGateway = class {
|
|
|
30096
30814
|
/** @deprecated Use writeContextFile / updateContextFileStepId. */
|
|
30097
30815
|
writeStepIdFile(filePath, stepId) {
|
|
30098
30816
|
try {
|
|
30099
|
-
|
|
30100
|
-
|
|
30817
|
+
fs2.mkdirSync(path2.dirname(filePath), { recursive: true });
|
|
30818
|
+
fs2.writeFileSync(filePath, stepId, "utf8");
|
|
30101
30819
|
} catch (err) {
|
|
30102
30820
|
this.opts.log?.warn(`failed to write step id file ${filePath}: ${String(err)}`);
|
|
30103
30821
|
}
|
|
@@ -30105,7 +30823,7 @@ var ParallAgentGateway = class {
|
|
|
30105
30823
|
/** @deprecated Use writeContextFile / updateContextFileStepId. */
|
|
30106
30824
|
clearStepIdFile(filePath) {
|
|
30107
30825
|
try {
|
|
30108
|
-
|
|
30826
|
+
fs2.writeFileSync(filePath, "", "utf8");
|
|
30109
30827
|
} catch {
|
|
30110
30828
|
}
|
|
30111
30829
|
}
|
|
@@ -30114,7 +30832,7 @@ var ParallAgentGateway = class {
|
|
|
30114
30832
|
await this.createInputStep(sessionId, event);
|
|
30115
30833
|
}
|
|
30116
30834
|
}
|
|
30117
|
-
async bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath) {
|
|
30835
|
+
async bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2) {
|
|
30118
30836
|
const runtimeLaneKey = runtimeEvent.runtimeLaneKey || sessionKey;
|
|
30119
30837
|
const existing = this.sessionBindings.get(sessionKey);
|
|
30120
30838
|
if (existing && existing.runtimeLaneKey === runtimeLaneKey && existing.runtimeSessionId === runtimeEvent.runtimeSessionId) {
|
|
@@ -30158,6 +30876,9 @@ var ParallAgentGateway = class {
|
|
|
30158
30876
|
if (contextFilePath) {
|
|
30159
30877
|
this.updateContextFileSessionId(contextFilePath, session.id);
|
|
30160
30878
|
}
|
|
30879
|
+
if (laneContextFilePath2) {
|
|
30880
|
+
this.updateContextFileSessionId(laneContextFilePath2, session.id);
|
|
30881
|
+
}
|
|
30161
30882
|
await this.opts.onSessionBinding?.(binding);
|
|
30162
30883
|
return binding;
|
|
30163
30884
|
}
|
|
@@ -30184,14 +30905,27 @@ var ParallAgentGateway = class {
|
|
|
30184
30905
|
const dispatchContext = this.buildDispatchContext(event, sessionKey);
|
|
30185
30906
|
const contextFilePath = dispatchContext.contextFilePath;
|
|
30186
30907
|
const stepIdFilePath = dispatchContext.stepIdFilePath;
|
|
30908
|
+
const activeLane = this.ledgerDisabled ? void 0 : this.laneLedger?.getForEvent(event);
|
|
30909
|
+
const laneContextFilePath2 = activeLane ? this.laneLedger?.laneContextPath(activeLane) : void 0;
|
|
30910
|
+
const contextBody = {
|
|
30911
|
+
session_id: dispatchContext.sessionId ?? null,
|
|
30912
|
+
chat_id: dispatchContext.chatId ?? null,
|
|
30913
|
+
trigger_message_id: dispatchContext.triggerMessageId ?? null,
|
|
30914
|
+
no_reply: dispatchContext.noReply,
|
|
30915
|
+
step_id: null,
|
|
30916
|
+
dispatch_event_id: activeLane?.typedDispatchEventId ?? activeLane?.folded.get(event.messageId) ?? null,
|
|
30917
|
+
lane: activeLane?.lane ?? null,
|
|
30918
|
+
target_uri: activeLane?.targetUri ?? null,
|
|
30919
|
+
thread_root_id: activeLane?.threadRootId ?? null,
|
|
30920
|
+
// Typed binding hint for the CLI: which task this dispatch is about
|
|
30921
|
+
// (parall task update attaches the typed effect only on a match).
|
|
30922
|
+
task_id: event.type === "task" ? event.targetId : null
|
|
30923
|
+
};
|
|
30187
30924
|
if (contextFilePath) {
|
|
30188
|
-
this.writeContextFile(contextFilePath,
|
|
30189
|
-
|
|
30190
|
-
|
|
30191
|
-
|
|
30192
|
-
no_reply: dispatchContext.noReply,
|
|
30193
|
-
step_id: null
|
|
30194
|
-
});
|
|
30925
|
+
this.writeContextFile(contextFilePath, contextBody);
|
|
30926
|
+
}
|
|
30927
|
+
if (laneContextFilePath2) {
|
|
30928
|
+
this.writeContextFile(laneContextFilePath2, contextBody);
|
|
30195
30929
|
}
|
|
30196
30930
|
this.inFlightDispatches++;
|
|
30197
30931
|
const deadlineTimer = this.DISPATCH_DEADLINE_MS > 0 ? setTimeout(() => {
|
|
@@ -30217,7 +30951,15 @@ var ParallAgentGateway = class {
|
|
|
30217
30951
|
context: dispatchContext
|
|
30218
30952
|
})) {
|
|
30219
30953
|
if (runtimeEvent.type === "runtime_session") {
|
|
30220
|
-
|
|
30954
|
+
const priorAgentSessionId = binding?.agentSessionId;
|
|
30955
|
+
binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2);
|
|
30956
|
+
if (event.targetType === "channel_conversation" && binding.agentSessionId !== priorAgentSessionId) {
|
|
30957
|
+
try {
|
|
30958
|
+
await this.opts.client.setChannelConversationSession(this.opts.config.org_id, event.targetId, binding.agentSessionId);
|
|
30959
|
+
} catch (err) {
|
|
30960
|
+
this.opts.log?.warn(`failed to record session mapping for channel conversation ${event.targetId}: ${String(err)}`);
|
|
30961
|
+
}
|
|
30962
|
+
}
|
|
30221
30963
|
if (!inputStepsCreated) {
|
|
30222
30964
|
if (earlierEvents.length > 0) {
|
|
30223
30965
|
await this.createInputStepsForEarlierEvents(binding.agentSessionId, earlierEvents);
|
|
@@ -30242,6 +30984,9 @@ var ParallAgentGateway = class {
|
|
|
30242
30984
|
await this.createInputStep(binding.agentSessionId, event);
|
|
30243
30985
|
inputStepsCreated = true;
|
|
30244
30986
|
}
|
|
30987
|
+
if (activeLane && !this.ledgerDisabled) {
|
|
30988
|
+
this.laneLedger?.maybeRenew(activeLane);
|
|
30989
|
+
}
|
|
30245
30990
|
if (captureText && runtimeEvent.type === "text" && runtimeEvent.text) {
|
|
30246
30991
|
captureText.push(runtimeEvent.text);
|
|
30247
30992
|
}
|
|
@@ -30258,7 +31003,7 @@ var ParallAgentGateway = class {
|
|
|
30258
31003
|
} else if (runtimeEvent.type === "tool_result" && pendingSendCallIds.delete(runtimeEvent.callId)) {
|
|
30259
31004
|
recordMessageSend(sessionKey, !runtimeEvent.error);
|
|
30260
31005
|
}
|
|
30261
|
-
await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath);
|
|
31006
|
+
await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
30262
31007
|
}
|
|
30263
31008
|
if (!binding) {
|
|
30264
31009
|
binding = this.sessionBindings.get(sessionKey);
|
|
@@ -30280,7 +31025,7 @@ var ParallAgentGateway = class {
|
|
|
30280
31025
|
await this.createRuntimeStep(binding.agentSessionId, event, {
|
|
30281
31026
|
type: "error",
|
|
30282
31027
|
message: `Dispatch failed: ${String(err)}`
|
|
30283
|
-
}, stepIdFilePath, contextFilePath);
|
|
31028
|
+
}, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
30284
31029
|
} catch (stepErr) {
|
|
30285
31030
|
if (this.isSessionNotLiveError(stepErr))
|
|
30286
31031
|
staleDetected = true;
|
|
@@ -30323,6 +31068,9 @@ var ParallAgentGateway = class {
|
|
|
30323
31068
|
} else if (stepIdFilePath) {
|
|
30324
31069
|
this.clearStepIdFile(stepIdFilePath);
|
|
30325
31070
|
}
|
|
31071
|
+
if (laneContextFilePath2) {
|
|
31072
|
+
this.updateContextFileStepId(laneContextFilePath2, null);
|
|
31073
|
+
}
|
|
30326
31074
|
this.inFlightDispatches--;
|
|
30327
31075
|
if (this.inFlightDispatches === 0 && this.drainResolvers.length > 0) {
|
|
30328
31076
|
const resolvers = this.drainResolvers.splice(0);
|
|
@@ -30380,13 +31128,39 @@ var ParallAgentGateway = class {
|
|
|
30380
31128
|
item.resolve(false);
|
|
30381
31129
|
break;
|
|
30382
31130
|
}
|
|
30383
|
-
|
|
31131
|
+
let items;
|
|
31132
|
+
const head = fork.queue[0];
|
|
31133
|
+
if (head && this.usesLaneLedger(head.event)) {
|
|
31134
|
+
const headKey = this.dispatchGroupKey(head.event);
|
|
31135
|
+
const splitAt = fork.queue.findIndex((it) => this.dispatchGroupKey(it.event) !== headKey);
|
|
31136
|
+
items = splitAt === -1 ? fork.queue.splice(0) : fork.queue.splice(0, splitAt);
|
|
31137
|
+
} else {
|
|
31138
|
+
items = fork.queue.splice(0);
|
|
31139
|
+
}
|
|
30384
31140
|
const events = items.map((item) => item.event);
|
|
30385
31141
|
const last = events[events.length - 1];
|
|
30386
31142
|
const earlier = events.slice(0, -1);
|
|
30387
31143
|
try {
|
|
30388
31144
|
const batchText = [];
|
|
30389
|
-
|
|
31145
|
+
let dispatched;
|
|
31146
|
+
if (this.usesLaneLedger(last)) {
|
|
31147
|
+
const outcome = await this.dispatchLaneGroup({
|
|
31148
|
+
events,
|
|
31149
|
+
sessionKey: fork.fork.sessionKey,
|
|
31150
|
+
body: buildForkScopePrefix(last) + buildEventBody(last),
|
|
31151
|
+
earlier,
|
|
31152
|
+
captureText: batchText,
|
|
31153
|
+
hasMoreLocal: () => fork.queue.length > 0
|
|
31154
|
+
});
|
|
31155
|
+
if (outcome === "foreign") {
|
|
31156
|
+
for (const item of items)
|
|
31157
|
+
item.resolve(false);
|
|
31158
|
+
break;
|
|
31159
|
+
}
|
|
31160
|
+
dispatched = outcome === "dispatched";
|
|
31161
|
+
} else {
|
|
31162
|
+
dispatched = await this.runDispatch(last, fork.fork.sessionKey, buildForkScopePrefix(last) + buildEventBody(last), earlier, batchText);
|
|
31163
|
+
}
|
|
30390
31164
|
if (!dispatched) {
|
|
30391
31165
|
for (const item of items) {
|
|
30392
31166
|
item.resolve(false);
|
|
@@ -30489,9 +31263,9 @@ var ParallAgentGateway = class {
|
|
|
30489
31263
|
this.opts.log?.info(`drainMainBuffer halted (shutting down) \u2014 ${this.dispatchState.mainBuffer.length} buffered, ${this.dispatchState.pendingForkResults.length} pending fork results left for catch-up`);
|
|
30490
31264
|
break;
|
|
30491
31265
|
}
|
|
30492
|
-
const
|
|
31266
|
+
const groupKey = this.dispatchGroupKey(this.dispatchState.mainBuffer[0]);
|
|
30493
31267
|
const events = [];
|
|
30494
|
-
while (this.dispatchState.mainBuffer[0]
|
|
31268
|
+
while (this.dispatchState.mainBuffer[0] && this.dispatchGroupKey(this.dispatchState.mainBuffer[0]) === groupKey) {
|
|
30495
31269
|
events.push(this.dispatchState.mainBuffer.shift());
|
|
30496
31270
|
}
|
|
30497
31271
|
const event = events[events.length - 1];
|
|
@@ -30500,7 +31274,36 @@ var ParallAgentGateway = class {
|
|
|
30500
31274
|
const pendingFork = hasPendingInjections ? [] : this.dispatchState.pendingForkResults.splice(0);
|
|
30501
31275
|
const forkPrefix = buildForkResultPrefix(pendingFork);
|
|
30502
31276
|
this.dispatchState.mainCurrentTargetId = event.targetId;
|
|
31277
|
+
this.mainCurrentGroupKey = groupKey;
|
|
30503
31278
|
this.dispatchState.mainPreDispatchBranchPoint = this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
|
|
31279
|
+
if (this.usesLaneLedger(event)) {
|
|
31280
|
+
let outcome;
|
|
31281
|
+
try {
|
|
31282
|
+
outcome = await this.dispatchLaneGroup({
|
|
31283
|
+
events,
|
|
31284
|
+
sessionKey: this.opts.runtimeKey,
|
|
31285
|
+
body: forkPrefix + buildEventBody(event),
|
|
31286
|
+
earlier,
|
|
31287
|
+
hasMoreLocal: () => this.dispatchState.mainBuffer.some((e) => this.dispatchGroupKey(e) === groupKey)
|
|
31288
|
+
});
|
|
31289
|
+
} catch (err) {
|
|
31290
|
+
this.opts.log?.error(`lane dispatch failed for ${event.messageId}: ${String(err)}`);
|
|
31291
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31292
|
+
for (const ev of events)
|
|
31293
|
+
this.dispatchedMessages.delete(ev.messageId);
|
|
31294
|
+
continue;
|
|
31295
|
+
}
|
|
31296
|
+
if (outcome === "shutdown") {
|
|
31297
|
+
this.dispatchState.mainBuffer.unshift(...events);
|
|
31298
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31299
|
+
break;
|
|
31300
|
+
}
|
|
31301
|
+
if (outcome === "foreign") {
|
|
31302
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31303
|
+
continue;
|
|
31304
|
+
}
|
|
31305
|
+
continue;
|
|
31306
|
+
}
|
|
30504
31307
|
try {
|
|
30505
31308
|
await this.emitDispatchReceived(event);
|
|
30506
31309
|
} catch (err) {
|
|
@@ -30529,6 +31332,7 @@ var ParallAgentGateway = class {
|
|
|
30529
31332
|
this.draining = false;
|
|
30530
31333
|
this.dispatchState.mainDispatching = false;
|
|
30531
31334
|
this.dispatchState.mainCurrentTargetId = void 0;
|
|
31335
|
+
this.mainCurrentGroupKey = void 0;
|
|
30532
31336
|
this.dispatchState.mainPreDispatchBranchPoint = void 0;
|
|
30533
31337
|
if (!this.shuttingDown && this.dispatchState.mainBuffer.length > 0) {
|
|
30534
31338
|
setTimeout(() => {
|
|
@@ -30548,13 +31352,38 @@ var ParallAgentGateway = class {
|
|
|
30548
31352
|
const forkPrefix = buildForkResultPrefix(pendingFork);
|
|
30549
31353
|
this.dispatchState.mainDispatching = true;
|
|
30550
31354
|
this.dispatchState.mainCurrentTargetId = event.targetId;
|
|
31355
|
+
this.mainCurrentGroupKey = this.dispatchGroupKey(event);
|
|
30551
31356
|
this.dispatchState.mainPreDispatchBranchPoint = this.opts.dispatchAdapter.getBranchPoint?.(this.opts.runtimeKey);
|
|
31357
|
+
if (this.usesLaneLedger(event)) {
|
|
31358
|
+
let outcome = "shutdown";
|
|
31359
|
+
try {
|
|
31360
|
+
try {
|
|
31361
|
+
outcome = await this.dispatchLaneGroup({
|
|
31362
|
+
events: [event],
|
|
31363
|
+
sessionKey: this.opts.runtimeKey,
|
|
31364
|
+
body: forkPrefix + buildEventBody(event),
|
|
31365
|
+
earlier: [],
|
|
31366
|
+
hasMoreLocal: () => this.dispatchState.mainBuffer.some((e) => this.dispatchGroupKey(e) === this.dispatchGroupKey(event))
|
|
31367
|
+
});
|
|
31368
|
+
} catch (err) {
|
|
31369
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31370
|
+
throw err;
|
|
31371
|
+
}
|
|
31372
|
+
if (outcome !== "dispatched") {
|
|
31373
|
+
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
31374
|
+
}
|
|
31375
|
+
} finally {
|
|
31376
|
+
await this.drainMainBuffer();
|
|
31377
|
+
}
|
|
31378
|
+
return outcome === "dispatched";
|
|
31379
|
+
}
|
|
30552
31380
|
try {
|
|
30553
31381
|
await this.emitDispatchReceived(event);
|
|
30554
31382
|
} catch (err) {
|
|
30555
31383
|
this.opts.log?.warn?.(`mark-received failed, leaving unacked for retry: ${String(err)}`);
|
|
30556
31384
|
this.dispatchState.mainDispatching = false;
|
|
30557
31385
|
this.dispatchState.mainCurrentTargetId = void 0;
|
|
31386
|
+
this.mainCurrentGroupKey = void 0;
|
|
30558
31387
|
this.dispatchState.mainPreDispatchBranchPoint = void 0;
|
|
30559
31388
|
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
30560
31389
|
return false;
|
|
@@ -30575,7 +31404,11 @@ var ParallAgentGateway = class {
|
|
|
30575
31404
|
return false;
|
|
30576
31405
|
}
|
|
30577
31406
|
this.dispatchState.mainBuffer.push(event);
|
|
30578
|
-
if (this.
|
|
31407
|
+
if (this.usesLaneLedger(event)) {
|
|
31408
|
+
if (this.mainCurrentGroupKey === this.dispatchGroupKey(event) && await this.laneLedger?.steerLive(event) && await this.opts.dispatchAdapter.enqueueDuringDispatch?.(this.opts.runtimeKey, buildEventBody(event))) {
|
|
31409
|
+
this.opts.log?.info(`steer folded+injected for ${event.messageId} (will drain for bookkeeping)`);
|
|
31410
|
+
}
|
|
31411
|
+
} else if (this.dispatchState.mainCurrentTargetId === event.targetId && await this.opts.dispatchAdapter.enqueueDuringDispatch?.(this.opts.runtimeKey, buildEventBody(event))) {
|
|
30579
31412
|
this.opts.log?.info(`steer injected for ${event.messageId} (will drain for bookkeeping)`);
|
|
30580
31413
|
}
|
|
30581
31414
|
if (!this.dispatchState.mainDispatching && !this.draining && this.dispatchState.mainBuffer.length > 0) {
|
|
@@ -30746,8 +31579,10 @@ var ParallAgentGateway = class {
|
|
|
30746
31579
|
try {
|
|
30747
31580
|
const dispatched = await this.handleInboundEvent(event);
|
|
30748
31581
|
if (dispatched) {
|
|
30749
|
-
|
|
30750
|
-
|
|
31582
|
+
if (!this.usesLaneLedger(event)) {
|
|
31583
|
+
this.opts.client.ackDispatch(this.opts.config.org_id, { source_type: "message", source_id: data.id }).catch(() => {
|
|
31584
|
+
});
|
|
31585
|
+
}
|
|
30751
31586
|
} else {
|
|
30752
31587
|
this.dispatchedMessages.delete(data.id);
|
|
30753
31588
|
}
|
|
@@ -30756,7 +31591,21 @@ var ParallAgentGateway = class {
|
|
|
30756
31591
|
this.dispatchedMessages.delete(data.id);
|
|
30757
31592
|
}
|
|
30758
31593
|
}
|
|
30759
|
-
|
|
31594
|
+
// Ledger re-drive consumption: dispatch.new message hints re-enter the
|
|
31595
|
+
// shared WorkItem consumption path (same protocol as catch-up).
|
|
31596
|
+
async handleMessageRedrive(item) {
|
|
31597
|
+
if (!item.chat_id || !item.source_id)
|
|
31598
|
+
return;
|
|
31599
|
+
await this.consumeMessageWorkItem({
|
|
31600
|
+
id: item.id,
|
|
31601
|
+
source_id: item.source_id,
|
|
31602
|
+
chat_id: item.chat_id
|
|
31603
|
+
});
|
|
31604
|
+
}
|
|
31605
|
+
consumeMessageWorkItem(item) {
|
|
31606
|
+
return consumeMessageWorkItem(this.laneFlowHost(), item);
|
|
31607
|
+
}
|
|
31608
|
+
async handleTaskAssignment(task, ackSourceId, dispatchEventId) {
|
|
30760
31609
|
if (this.shuttingDown)
|
|
30761
31610
|
return false;
|
|
30762
31611
|
const dedupeKey = `${task.id}:${task.updated_at}`;
|
|
@@ -30785,7 +31634,8 @@ var ParallAgentGateway = class {
|
|
|
30785
31634
|
body: parts.join("\n"),
|
|
30786
31635
|
sentAt: task.updated_at ?? task.created_at,
|
|
30787
31636
|
ackSourceType: "task_activity",
|
|
30788
|
-
ackSourceId
|
|
31637
|
+
ackSourceId,
|
|
31638
|
+
dispatchEventId
|
|
30789
31639
|
};
|
|
30790
31640
|
const dispatched = await this.handleInboundEvent(event);
|
|
30791
31641
|
if (!dispatched) {
|
|
@@ -30809,7 +31659,7 @@ var ParallAgentGateway = class {
|
|
|
30809
31659
|
this.opts.log?.info(`skipping stale task dispatch ${ackSourceId ?? taskId} \u2014 assigned to ${task.assignee_id}, creator ${task.creator_id}`);
|
|
30810
31660
|
return true;
|
|
30811
31661
|
}
|
|
30812
|
-
return this.handleTaskAssignment(task, ackSourceId);
|
|
31662
|
+
return this.handleTaskAssignment(task, ackSourceId, opts.dispatchEventId);
|
|
30813
31663
|
}
|
|
30814
31664
|
async handleTaskComment(commentId, taskId, actorId, deliveryReason) {
|
|
30815
31665
|
if (this.shuttingDown)
|
|
@@ -31020,6 +31870,75 @@ var ParallAgentGateway = class {
|
|
|
31020
31870
|
return true;
|
|
31021
31871
|
return this.handleExternalTriggerRun(run);
|
|
31022
31872
|
}
|
|
31873
|
+
// fetchAndHandleChannelMessage resolves a channel_message dispatch to its
|
|
31874
|
+
// durable ChannelMessage + conversation and hands it to the inbound
|
|
31875
|
+
// pipeline. targetId = the ChannelConversation id, so per-conversation
|
|
31876
|
+
// multi-turn continuity rides the same per-target session mechanics as
|
|
31877
|
+
// chats. Design: docs/engineering-design/external-im-channel-design.md.
|
|
31878
|
+
async fetchAndHandleChannelMessage(messageId) {
|
|
31879
|
+
if (this.shuttingDown)
|
|
31880
|
+
return false;
|
|
31881
|
+
const claimKey = `channel_message:${messageId}`;
|
|
31882
|
+
if (!this.tryClaimMessage(claimKey))
|
|
31883
|
+
return false;
|
|
31884
|
+
let msg = null;
|
|
31885
|
+
let conv = null;
|
|
31886
|
+
try {
|
|
31887
|
+
msg = await this.opts.client.getChannelMessage(this.opts.config.org_id, messageId);
|
|
31888
|
+
conv = await this.opts.client.getChannelConversation(this.opts.config.org_id, msg.conversation_id);
|
|
31889
|
+
} catch (err) {
|
|
31890
|
+
const status = err?.status;
|
|
31891
|
+
if (status === 404) {
|
|
31892
|
+
this.opts.log?.warn(`channel message ${messageId} not accessible (404), acking stale dispatch`);
|
|
31893
|
+
return true;
|
|
31894
|
+
}
|
|
31895
|
+
this.dispatchedMessages.delete(claimKey);
|
|
31896
|
+
this.opts.log?.warn(`channel message fetch failed for ${messageId}, leaving pending: ${String(err)}`);
|
|
31897
|
+
return false;
|
|
31898
|
+
}
|
|
31899
|
+
if (!msg || !conv) {
|
|
31900
|
+
return true;
|
|
31901
|
+
}
|
|
31902
|
+
this.opts.log?.info(`channel message: ${msg.id} (conversation ${conv.id})`);
|
|
31903
|
+
let provider = this.channelConnectionProviders.get(conv.connection_id);
|
|
31904
|
+
if (!provider) {
|
|
31905
|
+
try {
|
|
31906
|
+
const connection = await this.opts.client.getChannelConnection(this.opts.config.org_id, conv.connection_id);
|
|
31907
|
+
provider = connection.provider;
|
|
31908
|
+
this.channelConnectionProviders.set(conv.connection_id, provider);
|
|
31909
|
+
} catch {
|
|
31910
|
+
provider = void 0;
|
|
31911
|
+
}
|
|
31912
|
+
}
|
|
31913
|
+
const event = {
|
|
31914
|
+
type: "channel_message",
|
|
31915
|
+
targetId: conv.id,
|
|
31916
|
+
targetName: conv.external_user_name || conv.external_conversation_id,
|
|
31917
|
+
targetType: "channel_conversation",
|
|
31918
|
+
senderId: msg.external_user_id || "external",
|
|
31919
|
+
senderName: msg.external_user_name || msg.external_user_id || "external user",
|
|
31920
|
+
messageId: msg.id,
|
|
31921
|
+
body: msg.text,
|
|
31922
|
+
sentAt: msg.received_at,
|
|
31923
|
+
channelProvider: provider,
|
|
31924
|
+
channelConversationType: conv.conversation_type || void 0,
|
|
31925
|
+
channelExternalConversationId: conv.external_conversation_id,
|
|
31926
|
+
channelExternalMessageId: msg.external_message_id,
|
|
31927
|
+
ackSourceType: "channel_message",
|
|
31928
|
+
ackSourceId: msg.id
|
|
31929
|
+
};
|
|
31930
|
+
let dispatched;
|
|
31931
|
+
try {
|
|
31932
|
+
dispatched = await this.handleInboundEvent(event);
|
|
31933
|
+
} catch (err) {
|
|
31934
|
+
this.dispatchedMessages.delete(claimKey);
|
|
31935
|
+
throw err;
|
|
31936
|
+
}
|
|
31937
|
+
if (!dispatched) {
|
|
31938
|
+
this.dispatchedMessages.delete(claimKey);
|
|
31939
|
+
}
|
|
31940
|
+
return dispatched;
|
|
31941
|
+
}
|
|
31023
31942
|
async handleExternalTriggerRun(run) {
|
|
31024
31943
|
if (this.shuttingDown)
|
|
31025
31944
|
return false;
|
|
@@ -31148,72 +32067,46 @@ var ParallAgentGateway = class {
|
|
|
31148
32067
|
}
|
|
31149
32068
|
processed++;
|
|
31150
32069
|
try {
|
|
31151
|
-
|
|
32070
|
+
const ackItem = () => {
|
|
32071
|
+
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
32072
|
+
});
|
|
32073
|
+
};
|
|
31152
32074
|
if (item.event_type === "task_assign" && item.task_id) {
|
|
31153
32075
|
try {
|
|
31154
|
-
|
|
32076
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
|
|
32077
|
+
dispatchEventId
|
|
32078
|
+
}), ackItem);
|
|
31155
32079
|
} catch (err) {
|
|
31156
32080
|
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
31157
32081
|
continue;
|
|
31158
32082
|
}
|
|
31159
32083
|
} else if (item.event_type === "task_update" && item.task_id) {
|
|
31160
32084
|
try {
|
|
31161
|
-
|
|
32085
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, (dispatchEventId) => this.handleTaskDispatch(item.task_id ?? "", item.source_id ?? item.task_id ?? "", {
|
|
32086
|
+
allowCreator: true,
|
|
32087
|
+
dispatchEventId
|
|
32088
|
+
}), ackItem);
|
|
31162
32089
|
} catch (err) {
|
|
31163
32090
|
this.opts.log?.warn(`catch-up task fetch failed for ${item.task_id}, leaving pending: ${String(err)}`);
|
|
31164
32091
|
continue;
|
|
31165
32092
|
}
|
|
31166
32093
|
} else if (item.event_type === "task_comment" && item.source_id && item.task_id) {
|
|
31167
|
-
|
|
32094
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleTaskComment(item.source_id, item.task_id ?? "", item.actor_id, item.delivery_reason), ackItem);
|
|
31168
32095
|
} else if (item.event_type === "wiki_comment" && item.source_id) {
|
|
31169
|
-
|
|
32096
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.handleWikiComment(item.source_id, item.actor_id, item.delivery_reason), ackItem);
|
|
31170
32097
|
} else if (item.event_type === "schedule.fire" && item.source_id) {
|
|
31171
|
-
|
|
32098
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleScheduleFire(item.source_id, item.actor_id), ackItem);
|
|
31172
32099
|
} else if (item.event_type === "external_trigger" && item.source_id) {
|
|
31173
|
-
|
|
32100
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleExternalTriggerRun(item.source_id), ackItem);
|
|
32101
|
+
} else if (item.event_type === "channel_message" && item.source_id) {
|
|
32102
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleChannelMessage(item.source_id), ackItem);
|
|
31174
32103
|
} else if (item.event_type === "approval_decided" && item.source_id) {
|
|
31175
|
-
|
|
32104
|
+
await this.consumeTypedDispatch({ dispatchEventId: item.id }, () => this.fetchAndHandleApprovalDecided(item.source_id, item.actor_id, item.chat_id ?? null), ackItem);
|
|
31176
32105
|
} else if (item.event_type === "message" && item.source_id && item.chat_id) {
|
|
31177
|
-
|
|
31178
|
-
|
|
31179
|
-
|
|
31180
|
-
|
|
31181
|
-
try {
|
|
31182
|
-
msg = await this.opts.client.getMessage(item.source_id);
|
|
31183
|
-
} catch (err) {
|
|
31184
|
-
const status = err?.status;
|
|
31185
|
-
if (status === 404) {
|
|
31186
|
-
msg = null;
|
|
31187
|
-
} else {
|
|
31188
|
-
msgFetchFailed = true;
|
|
31189
|
-
this.opts.log?.warn(`catch-up message fetch failed for ${item.source_id}, leaving pending: ${String(err)}`);
|
|
31190
|
-
}
|
|
31191
|
-
}
|
|
31192
|
-
if (msgFetchFailed) {
|
|
31193
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31194
|
-
continue;
|
|
31195
|
-
}
|
|
31196
|
-
if (!msg || msg.sender_id === this.opts.agentUserId) {
|
|
31197
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31198
|
-
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
31199
|
-
});
|
|
31200
|
-
continue;
|
|
31201
|
-
}
|
|
31202
|
-
const decision = await this.buildMessageDispatchDecision(item.chat_id, msg);
|
|
31203
|
-
if (decision.action === "retry") {
|
|
31204
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31205
|
-
continue;
|
|
31206
|
-
}
|
|
31207
|
-
if (decision.action === "skip") {
|
|
31208
|
-
this.dispatchedMessages.delete(item.source_id);
|
|
31209
|
-
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
31210
|
-
});
|
|
31211
|
-
continue;
|
|
31212
|
-
}
|
|
31213
|
-
dispatched = await this.handleInboundEvent(decision.event);
|
|
31214
|
-
}
|
|
31215
|
-
if (dispatched) {
|
|
31216
|
-
this.opts.client.ackDispatchByID(this.opts.config.org_id, item.id).catch(() => {
|
|
32106
|
+
await this.consumeMessageWorkItem({
|
|
32107
|
+
id: item.id,
|
|
32108
|
+
source_id: item.source_id,
|
|
32109
|
+
chat_id: item.chat_id
|
|
31217
32110
|
});
|
|
31218
32111
|
}
|
|
31219
32112
|
} catch (err) {
|
|
@@ -31279,6 +32172,14 @@ ${fullSummary}` : fullSummary;
|
|
|
31279
32172
|
this.abortFork(targetId, "ws reconnect");
|
|
31280
32173
|
}
|
|
31281
32174
|
}
|
|
32175
|
+
if (this.laneLedger && this.inFlightDispatches === 0 && this.laneLedger.activeCount > 0) {
|
|
32176
|
+
log2?.info(`releasing ${this.laneLedger.activeCount} stale lane(s) on reconnect`);
|
|
32177
|
+
await this.laneLedger.releaseAll();
|
|
32178
|
+
}
|
|
32179
|
+
if (this.laneLedger && this.ledgerDisabled) {
|
|
32180
|
+
log2?.info("re-probing dispatch ledger after reconnect (was disabled)");
|
|
32181
|
+
this.ledgerDisabled = false;
|
|
32182
|
+
}
|
|
31282
32183
|
const intervalSec = data.heartbeat_interval > 0 ? data.heartbeat_interval : 30;
|
|
31283
32184
|
try {
|
|
31284
32185
|
const count = await fetchAllChats(client, config.org_id, this.chatInfoMap);
|
|
@@ -31349,6 +32250,10 @@ ${fullSummary}` : fullSummary;
|
|
|
31349
32250
|
}
|
|
31350
32251
|
if (this.heartbeatTimer)
|
|
31351
32252
|
clearInterval(this.heartbeatTimer);
|
|
32253
|
+
if (this.laneLedger && this.laneLedger.activeCount > 0) {
|
|
32254
|
+
this.opts.log?.info(`releasing ${this.laneLedger.activeCount} lane(s) on shutdown`);
|
|
32255
|
+
await this.laneLedger.releaseAll();
|
|
32256
|
+
}
|
|
31352
32257
|
await this.opts.onBeforeDisconnect?.();
|
|
31353
32258
|
this.opts.ws.disconnect();
|
|
31354
32259
|
this.opts.log?.info(`disconnected`);
|
|
@@ -31356,8 +32261,8 @@ ${fullSummary}` : fullSummary;
|
|
|
31356
32261
|
};
|
|
31357
32262
|
|
|
31358
32263
|
// ts/agent-core/dist/platform-config.js
|
|
31359
|
-
import * as
|
|
31360
|
-
import * as
|
|
32264
|
+
import * as fs3 from "node:fs";
|
|
32265
|
+
import * as path3 from "node:path";
|
|
31361
32266
|
function isPlatformManagedProfile(profile) {
|
|
31362
32267
|
return profile?.machine_id != null || profile?.model_management === "platform";
|
|
31363
32268
|
}
|
|
@@ -31368,14 +32273,17 @@ function resolveRuntimeModel(isOperatorOverride, platformModel, configModel) {
|
|
|
31368
32273
|
const operatorOverride = isOperatorOverride ? platformModel : null;
|
|
31369
32274
|
return operatorOverride ?? configModel ?? platformModel ?? void 0;
|
|
31370
32275
|
}
|
|
32276
|
+
function deriveModelIsPin(defaults, profile) {
|
|
32277
|
+
return defaults.modelIsPin !== void 0 ? defaults.modelIsPin : isPlatformModelOverride(profile);
|
|
32278
|
+
}
|
|
31371
32279
|
var CACHE_FILENAME = "parall-platform-config.json";
|
|
31372
32280
|
var SUPPORTED_SCHEMA_VERSION = 1;
|
|
31373
32281
|
function cachePath(stateDir) {
|
|
31374
|
-
return
|
|
32282
|
+
return path3.join(stateDir, CACHE_FILENAME);
|
|
31375
32283
|
}
|
|
31376
32284
|
function loadCache(stateDir) {
|
|
31377
32285
|
try {
|
|
31378
|
-
const raw =
|
|
32286
|
+
const raw = fs3.readFileSync(cachePath(stateDir), "utf-8");
|
|
31379
32287
|
return JSON.parse(raw);
|
|
31380
32288
|
} catch {
|
|
31381
32289
|
return null;
|
|
@@ -31385,33 +32293,38 @@ function saveCache(stateDir, response) {
|
|
|
31385
32293
|
const cached = {
|
|
31386
32294
|
version: response.version,
|
|
31387
32295
|
config: response.config,
|
|
32296
|
+
modelIsPin: response.model_is_pin,
|
|
31388
32297
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
31389
32298
|
};
|
|
31390
32299
|
const filePath = cachePath(stateDir);
|
|
31391
32300
|
const tmpPath = `${filePath}.tmp`;
|
|
31392
|
-
|
|
31393
|
-
|
|
31394
|
-
|
|
32301
|
+
fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
32302
|
+
fs3.writeFileSync(tmpPath, JSON.stringify(cached, null, 2), "utf-8");
|
|
32303
|
+
fs3.renameSync(tmpPath, filePath);
|
|
31395
32304
|
}
|
|
31396
32305
|
function runtimeModelName(canonicalModel, runtimeType, config) {
|
|
31397
32306
|
const runtime = runtimeType?.trim();
|
|
31398
|
-
if (!runtime || runtime === "openclaw")
|
|
32307
|
+
if (!runtime || runtime === "openclaw" || runtime === "hermes")
|
|
31399
32308
|
return canonicalModel;
|
|
31400
32309
|
const models = config.models ?? {};
|
|
31401
32310
|
const providers = models.providers ?? {};
|
|
31402
32311
|
const parall = providers.parall ?? {};
|
|
31403
32312
|
const catalog = Array.isArray(parall.models) ? parall.models : [];
|
|
31404
32313
|
const match = catalog.find((model) => model.id === canonicalModel);
|
|
31405
|
-
|
|
32314
|
+
if (!match)
|
|
32315
|
+
return null;
|
|
32316
|
+
const runtimeNames = match.runtime_names ?? {};
|
|
31406
32317
|
const runtimeName = runtimeNames[runtime];
|
|
31407
|
-
|
|
32318
|
+
if (typeof runtimeName === "string" && runtimeName)
|
|
32319
|
+
return runtimeName;
|
|
32320
|
+
return canonicalModel;
|
|
31408
32321
|
}
|
|
31409
32322
|
function extractDefaults(config, runtimeType) {
|
|
31410
32323
|
const agents = config.agents ?? {};
|
|
31411
32324
|
const defaults = agents.defaults ?? {};
|
|
31412
32325
|
let model = null;
|
|
31413
32326
|
if (typeof defaults.model === "string" && defaults.model) {
|
|
31414
|
-
const canonicalModel = defaults.model.replace(/^parall\//, "");
|
|
32327
|
+
const canonicalModel = defaults.model.replace(/^parall-anthropic\//, "").replace(/^parall\//, "");
|
|
31415
32328
|
model = runtimeModelName(canonicalModel, runtimeType, config);
|
|
31416
32329
|
}
|
|
31417
32330
|
let thinkingEffort = null;
|
|
@@ -31423,13 +32336,20 @@ function extractDefaults(config, runtimeType) {
|
|
|
31423
32336
|
function createPlatformConfigManager(opts) {
|
|
31424
32337
|
const { client, stateDir, runtimeType, log: log2 } = opts;
|
|
31425
32338
|
let cachedVersion;
|
|
31426
|
-
let currentDefaults = {
|
|
32339
|
+
let currentDefaults = {
|
|
32340
|
+
model: null,
|
|
32341
|
+
thinkingEffort: null,
|
|
32342
|
+
modelIsPin: void 0
|
|
32343
|
+
};
|
|
31427
32344
|
let currentRawConfig = null;
|
|
31428
32345
|
const cached = loadCache(stateDir);
|
|
31429
32346
|
if (cached) {
|
|
31430
|
-
cachedVersion = cached.version;
|
|
32347
|
+
cachedVersion = cached.modelIsPin === void 0 ? void 0 : cached.version;
|
|
31431
32348
|
currentRawConfig = cached.config;
|
|
31432
|
-
currentDefaults =
|
|
32349
|
+
currentDefaults = {
|
|
32350
|
+
...extractDefaults(cached.config, runtimeType),
|
|
32351
|
+
modelIsPin: cached.modelIsPin
|
|
32352
|
+
};
|
|
31433
32353
|
}
|
|
31434
32354
|
return {
|
|
31435
32355
|
async fetch() {
|
|
@@ -31455,7 +32375,10 @@ function createPlatformConfigManager(opts) {
|
|
|
31455
32375
|
saveCache(stateDir, fresh);
|
|
31456
32376
|
cachedVersion = fresh.version;
|
|
31457
32377
|
currentRawConfig = fresh.config;
|
|
31458
|
-
currentDefaults =
|
|
32378
|
+
currentDefaults = {
|
|
32379
|
+
...extractDefaults(fresh.config, runtimeType),
|
|
32380
|
+
modelIsPin: fresh.model_is_pin
|
|
32381
|
+
};
|
|
31459
32382
|
return currentDefaults;
|
|
31460
32383
|
},
|
|
31461
32384
|
current() {
|
|
@@ -31468,8 +32391,8 @@ function createPlatformConfigManager(opts) {
|
|
|
31468
32391
|
}
|
|
31469
32392
|
|
|
31470
32393
|
// ts/agent-core/dist/skills/index.js
|
|
31471
|
-
import * as
|
|
31472
|
-
import * as
|
|
32394
|
+
import * as fs4 from "node:fs";
|
|
32395
|
+
import * as path4 from "node:path";
|
|
31473
32396
|
|
|
31474
32397
|
// ts/agent-core/dist/skills/parall-platform.js
|
|
31475
32398
|
var PARALL_PLATFORM_SKILL = `# Parall Platform
|
|
@@ -31572,16 +32495,35 @@ original discussion and its approval/rejection IS the precedent.
|
|
|
31572
32495
|
|
|
31573
32496
|
Each \`[Event: message.new]\` includes \`[Chat: ... (prll://cht_xxx)]\` \u2014 use that chat URI to reply.
|
|
31574
32497
|
|
|
32498
|
+
> **How you pass the message body matters \u2014 your command runs through a shell.**
|
|
32499
|
+
> Inside double quotes the shell expands \`$\`, backticks, and \`$(...)\` *before*
|
|
32500
|
+
> the CLI sees them: \`--text "That costs $1,000"\` sends \`That costs ,000\`, and
|
|
32501
|
+
> \`--text "$(cmd)"\` runs \`cmd\`. Single quotes instead break on apostrophes
|
|
32502
|
+
> (\`I'm\`, \`don't\`). So do **not** wrap real message content in quotes \u2014 pass it
|
|
32503
|
+
> through \`--text-file\` (a written file, or a quoted heredoc \`<<'EOF'\` that
|
|
32504
|
+
> disables all expansion). Reserve \`--text "..."\` for short literals with no
|
|
32505
|
+
> \`$\`, backtick, or apostrophe.
|
|
32506
|
+
|
|
31575
32507
|
\`\`\`bash
|
|
31576
|
-
#
|
|
31577
|
-
|
|
32508
|
+
# One-off reply \u2192 quoted heredoc into stdin. The quoted delimiter <<'EOF'
|
|
32509
|
+
# disables ALL shell expansion, so $, backticks and apostrophes pass verbatim.
|
|
32510
|
+
parall messages send prll://cht_xxx --text-file - <<'PARALL_EOF'
|
|
32511
|
+
Sure \u2014 that's $1,000, and $(whoami) stays literal. I'm on it.
|
|
32512
|
+
PARALL_EOF
|
|
31578
32513
|
|
|
31579
|
-
#
|
|
31580
|
-
|
|
32514
|
+
# Longer / multi-line reply \u2192 write it with your file tool (no shell touches
|
|
32515
|
+
# the body), then point --text-file at the file.
|
|
32516
|
+
parall messages send prll://cht_xxx --text-file /tmp/reply.md
|
|
32517
|
+
|
|
32518
|
+
# Short literal with no $, backtick, or apostrophe \u2192 --text is fine.
|
|
32519
|
+
parall messages send prll://cht_xxx --text "On it"
|
|
32520
|
+
|
|
32521
|
+
# Direct message by user URI or display name (same --text-file / heredoc rules)
|
|
32522
|
+
parall dm prll://usr_xxx --text-file /tmp/reply.md
|
|
31581
32523
|
parall dm "Alice" --text "Hello"
|
|
31582
32524
|
|
|
31583
32525
|
# Thread reply
|
|
31584
|
-
parall messages send prll://cht_xxx --text
|
|
32526
|
+
parall messages send prll://cht_xxx --text-file /tmp/reply.md --thread-root-id 01JWC...
|
|
31585
32527
|
|
|
31586
32528
|
# FYI message (no response expected \u2014 the recipient sees \`[Hint: no_reply]\`)
|
|
31587
32529
|
parall messages send prll://cht_xxx --text "FYI: done" --no-reply
|
|
@@ -31613,7 +32555,9 @@ parall messages send prll://cht_xxx --attachment att_xxx --text "See attached"
|
|
|
31613
32555
|
parall dm "Alice" --file /tmp/report.pdf --text "Report attached"
|
|
31614
32556
|
\`\`\`
|
|
31615
32557
|
|
|
31616
|
-
\`--file\` and \`--attachment\` are mutually exclusive. \`--text\`
|
|
32558
|
+
\`--file\` and \`--attachment\` are mutually exclusive. A caption (\`--text\` for
|
|
32559
|
+
short literals, or \`--text-file\` for anything with \`$\`, backticks, or quotes)
|
|
32560
|
+
can be combined with either.
|
|
31617
32561
|
|
|
31618
32562
|
## Approvals
|
|
31619
32563
|
|
|
@@ -32221,13 +33165,13 @@ var SKILLS = [
|
|
|
32221
33165
|
}
|
|
32222
33166
|
];
|
|
32223
33167
|
function writeSkillFiles(targetDir) {
|
|
32224
|
-
|
|
33168
|
+
fs4.mkdirSync(targetDir, { recursive: true });
|
|
32225
33169
|
for (const skill of SKILLS) {
|
|
32226
|
-
|
|
33170
|
+
fs4.writeFileSync(path4.join(targetDir, `${skill.name}.md`), skill.content, "utf8");
|
|
32227
33171
|
}
|
|
32228
33172
|
}
|
|
32229
33173
|
function buildSkillReferences(workspaceDir) {
|
|
32230
|
-
const dir =
|
|
33174
|
+
const dir = path4.join(workspaceDir, ".parall", "skills");
|
|
32231
33175
|
const lines = SKILLS.map((s) => `- ${s.description.split(":")[0]}: \`${dir}/${s.name}.md\``);
|
|
32232
33176
|
return `## Platform Skills (read on demand)
|
|
32233
33177
|
|
|
@@ -32237,7 +33181,7 @@ ${lines.join("\n")}
|
|
|
32237
33181
|
|
|
32238
33182
|
// ts/claude-agent/dist/config.js
|
|
32239
33183
|
import * as os2 from "node:os";
|
|
32240
|
-
import * as
|
|
33184
|
+
import * as path5 from "node:path";
|
|
32241
33185
|
function requireEnv(env, name) {
|
|
32242
33186
|
const value = env[name]?.trim();
|
|
32243
33187
|
if (!value) {
|
|
@@ -32251,15 +33195,15 @@ function parseList(value) {
|
|
|
32251
33195
|
return value.split(/[,\n]/).map((item) => item.trim()).filter(Boolean);
|
|
32252
33196
|
}
|
|
32253
33197
|
function resolvePath(value) {
|
|
32254
|
-
return
|
|
33198
|
+
return path5.isAbsolute(value) ? value : path5.resolve(process.cwd(), value);
|
|
32255
33199
|
}
|
|
32256
33200
|
function resolveClaudeAgentConfig(env = process.env) {
|
|
32257
33201
|
const apiUrl = requireEnv(env, "PRLL_API_URL");
|
|
32258
33202
|
const apiKey = requireEnv(env, "PRLL_API_KEY");
|
|
32259
33203
|
const orgId = requireEnv(env, "PRLL_ORG_ID");
|
|
32260
33204
|
const claudeHome = resolvePath(env.PRLL_CLAUDE_HOME?.trim() || env.HOME || os2.homedir());
|
|
32261
|
-
const stateDir = resolvePath(env.PRLL_STATE_DIR?.trim() ||
|
|
32262
|
-
const workspaceDir = resolvePath(env.PRLL_WORKSPACE_DIR?.trim() ||
|
|
33205
|
+
const stateDir = resolvePath(env.PRLL_STATE_DIR?.trim() || path5.join(claudeHome, ".parall-agent"));
|
|
33206
|
+
const workspaceDir = resolvePath(env.PRLL_WORKSPACE_DIR?.trim() || path5.join(stateDir, "workspace"));
|
|
32263
33207
|
const additionalDirs = parseList(env.PRLL_CLAUDE_ADD_DIRS).map(resolvePath);
|
|
32264
33208
|
return {
|
|
32265
33209
|
apiUrl,
|
|
@@ -32294,20 +33238,23 @@ function buildClaudeRuntimeKey(agentUserId) {
|
|
|
32294
33238
|
}
|
|
32295
33239
|
function sessionStateFilePathForRuntime(stateDir, runtimeKey) {
|
|
32296
33240
|
const fileName = Buffer.from(runtimeKey).toString("base64url");
|
|
32297
|
-
return
|
|
33241
|
+
return path5.join(stateDir, "sessions", `${fileName}.json`);
|
|
32298
33242
|
}
|
|
32299
33243
|
function contextFilePathForSession(stateDir, sessionKey) {
|
|
32300
33244
|
const fileName = Buffer.from(sessionKey).toString("base64url");
|
|
32301
|
-
return
|
|
33245
|
+
return path5.join(stateDir, "dispatch-context", `${fileName}.json`);
|
|
33246
|
+
}
|
|
33247
|
+
function dispatchContextDirPath(stateDir) {
|
|
33248
|
+
return dispatchLaneContextDir(stateDir);
|
|
32302
33249
|
}
|
|
32303
33250
|
function stepIdFilePathForSession(stateDir, sessionKey) {
|
|
32304
33251
|
const fileName = Buffer.from(sessionKey).toString("base64url");
|
|
32305
|
-
return
|
|
33252
|
+
return path5.join(stateDir, "step-ids", `${fileName}.txt`);
|
|
32306
33253
|
}
|
|
32307
33254
|
|
|
32308
33255
|
// ts/claude-agent/dist/dispatch.js
|
|
32309
|
-
import * as
|
|
32310
|
-
import * as
|
|
33256
|
+
import * as fs6 from "node:fs";
|
|
33257
|
+
import * as path7 from "node:path";
|
|
32311
33258
|
import { randomUUID } from "node:crypto";
|
|
32312
33259
|
import { execSync as execSync2, spawn } from "node:child_process";
|
|
32313
33260
|
|
|
@@ -32315,8 +33262,8 @@ import { execSync as execSync2, spawn } from "node:child_process";
|
|
|
32315
33262
|
import { execSync } from "node:child_process";
|
|
32316
33263
|
import { constants } from "node:fs";
|
|
32317
33264
|
import * as fsSync from "node:fs";
|
|
32318
|
-
import * as
|
|
32319
|
-
import * as
|
|
33265
|
+
import * as fs5 from "node:fs/promises";
|
|
33266
|
+
import * as path6 from "node:path";
|
|
32320
33267
|
var DEFAULT_MAX_TOTAL_IMAGE_BYTES = 20 * 1024 * 1024;
|
|
32321
33268
|
var DEFAULT_ATTACHMENT_CACHE_MAX_BYTES = 512 * 1024 * 1024;
|
|
32322
33269
|
var DEFAULT_ATTACHMENT_DOWNLOAD_TIMEOUT_MS = 3e4;
|
|
@@ -32346,11 +33293,11 @@ async function prepareLocalImageAttachments(event, context2, opts) {
|
|
|
32346
33293
|
};
|
|
32347
33294
|
}
|
|
32348
33295
|
const rootDir = await ensureAttachmentRootDir(opts.workspaceDir);
|
|
32349
|
-
const messageDir =
|
|
33296
|
+
const messageDir = path6.join(rootDir, sanitizePathSegment(event.messageId));
|
|
32350
33297
|
await ensurePathIsNotSymlink(messageDir);
|
|
32351
|
-
await
|
|
33298
|
+
await fs5.mkdir(messageDir, { recursive: true });
|
|
32352
33299
|
await ensurePathIsNotSymlink(messageDir);
|
|
32353
|
-
const activeMessageDir =
|
|
33300
|
+
const activeMessageDir = path6.resolve(messageDir);
|
|
32354
33301
|
activeAttachmentDirs.add(activeMessageDir);
|
|
32355
33302
|
const maintenanceCooldownMs = opts.maintenanceCooldownMs ?? DEFAULT_MAINTENANCE_COOLDOWN_MS;
|
|
32356
33303
|
const maintenancePromise = scheduleAttachmentMaintenance(rootDir, {
|
|
@@ -32367,7 +33314,7 @@ async function prepareLocalImageAttachments(event, context2, opts) {
|
|
|
32367
33314
|
const notes = [];
|
|
32368
33315
|
let downloadedBytes = 0;
|
|
32369
33316
|
for (const att of imageAttachments) {
|
|
32370
|
-
const localPath =
|
|
33317
|
+
const localPath = path6.join(messageDir, localFileName(att.id, att.fileName, att.mimeType));
|
|
32371
33318
|
const downloadTimeoutMs = opts.downloadTimeoutMs ?? DEFAULT_ATTACHMENT_DOWNLOAD_TIMEOUT_MS;
|
|
32372
33319
|
const fetchFresh = async () => {
|
|
32373
33320
|
const fileInfo = await withTimeout(context2.client.getFileUrl(att.id), downloadTimeoutMs, `file URL lookup timed out after ${downloadTimeoutMs}ms`);
|
|
@@ -32424,7 +33371,7 @@ async function appendPreparedLocalAttachmentRefs(body, event, context2, opts) {
|
|
|
32424
33371
|
return { body: appendLocalAttachmentRefs(body, attachments), attachments };
|
|
32425
33372
|
}
|
|
32426
33373
|
function pinLocalAttachmentPaths(images) {
|
|
32427
|
-
const dirs = new Set(images.map((image) =>
|
|
33374
|
+
const dirs = new Set(images.map((image) => path6.resolve(path6.dirname(image.localPath))));
|
|
32428
33375
|
for (const dir of dirs) {
|
|
32429
33376
|
activeAttachmentDirs.add(dir);
|
|
32430
33377
|
}
|
|
@@ -32439,7 +33386,7 @@ function pinLocalAttachmentPaths(images) {
|
|
|
32439
33386
|
};
|
|
32440
33387
|
}
|
|
32441
33388
|
function attachmentRootDir(workspaceDir) {
|
|
32442
|
-
return
|
|
33389
|
+
return path6.join(path6.resolve(workspaceDir), ".parall", "attachments");
|
|
32443
33390
|
}
|
|
32444
33391
|
function ensureLocalAttachmentGitExclude(workingDirectory) {
|
|
32445
33392
|
try {
|
|
@@ -32448,8 +33395,8 @@ function ensureLocalAttachmentGitExclude(workingDirectory) {
|
|
|
32448
33395
|
encoding: "utf8",
|
|
32449
33396
|
stdio: ["ignore", "pipe", "ignore"]
|
|
32450
33397
|
}).trim();
|
|
32451
|
-
const excludePath =
|
|
32452
|
-
fsSync.mkdirSync(
|
|
33398
|
+
const excludePath = path6.isAbsolute(rel) ? rel : path6.join(workingDirectory, rel);
|
|
33399
|
+
fsSync.mkdirSync(path6.dirname(excludePath), { recursive: true });
|
|
32453
33400
|
const existing = fsSync.existsSync(excludePath) ? fsSync.readFileSync(excludePath, "utf8") : "";
|
|
32454
33401
|
if (existing.split(/\r?\n/).some((line) => line.trim() === ".parall/"))
|
|
32455
33402
|
return;
|
|
@@ -32485,18 +33432,18 @@ function scheduleAttachmentMaintenance(rootDir, opts) {
|
|
|
32485
33432
|
return run;
|
|
32486
33433
|
}
|
|
32487
33434
|
async function ensureAttachmentRootDir(workspaceDir) {
|
|
32488
|
-
const workspaceRoot =
|
|
32489
|
-
const parallDir =
|
|
33435
|
+
const workspaceRoot = path6.resolve(workspaceDir);
|
|
33436
|
+
const parallDir = path6.join(workspaceRoot, ".parall");
|
|
32490
33437
|
const rootDir = attachmentRootDir(workspaceRoot);
|
|
32491
|
-
await
|
|
33438
|
+
await fs5.mkdir(workspaceRoot, { recursive: true });
|
|
32492
33439
|
await ensurePathIsNotSymlink(parallDir);
|
|
32493
|
-
await
|
|
33440
|
+
await fs5.mkdir(parallDir, { recursive: true, mode: 448 });
|
|
32494
33441
|
await ensurePathIsNotSymlink(parallDir);
|
|
32495
33442
|
await ensurePathIsNotSymlink(rootDir);
|
|
32496
|
-
await
|
|
33443
|
+
await fs5.mkdir(rootDir, { recursive: true, mode: 448 });
|
|
32497
33444
|
await ensurePathIsNotSymlink(rootDir);
|
|
32498
|
-
const realWorkspace = await
|
|
32499
|
-
const realRoot = await
|
|
33445
|
+
const realWorkspace = await fs5.realpath(workspaceRoot);
|
|
33446
|
+
const realRoot = await fs5.realpath(rootDir);
|
|
32500
33447
|
if (!isPathInside(realRoot, realWorkspace)) {
|
|
32501
33448
|
throw new Error(`attachment root escapes workspace: ${rootDir}`);
|
|
32502
33449
|
}
|
|
@@ -32504,7 +33451,7 @@ async function ensureAttachmentRootDir(workspaceDir) {
|
|
|
32504
33451
|
}
|
|
32505
33452
|
async function ensurePathIsNotSymlink(filePath) {
|
|
32506
33453
|
try {
|
|
32507
|
-
const stat = await
|
|
33454
|
+
const stat = await fs5.lstat(filePath);
|
|
32508
33455
|
if (stat.isSymbolicLink()) {
|
|
32509
33456
|
throw new Error(`refusing to use symlinked attachment path ${filePath}`);
|
|
32510
33457
|
}
|
|
@@ -32515,8 +33462,8 @@ async function ensurePathIsNotSymlink(filePath) {
|
|
|
32515
33462
|
}
|
|
32516
33463
|
}
|
|
32517
33464
|
function isPathInside(childPath, parentPath) {
|
|
32518
|
-
const rel =
|
|
32519
|
-
return rel === "" || !!rel && !rel.startsWith("..") && !
|
|
33465
|
+
const rel = path6.relative(parentPath, childPath);
|
|
33466
|
+
return rel === "" || !!rel && !rel.startsWith("..") && !path6.isAbsolute(rel);
|
|
32520
33467
|
}
|
|
32521
33468
|
async function existingUsableFile(filePath, expectedSize, rootDir) {
|
|
32522
33469
|
try {
|
|
@@ -32527,30 +33474,30 @@ async function existingUsableFile(filePath, expectedSize, rootDir) {
|
|
|
32527
33474
|
}
|
|
32528
33475
|
}
|
|
32529
33476
|
async function localFileStatInsideRoot(filePath, rootDir) {
|
|
32530
|
-
const stat = await
|
|
33477
|
+
const stat = await fs5.lstat(filePath);
|
|
32531
33478
|
if (stat.isSymbolicLink()) {
|
|
32532
33479
|
throw new Error(`refusing to use symlinked attachment file ${filePath}`);
|
|
32533
33480
|
}
|
|
32534
33481
|
if (!stat.isFile()) {
|
|
32535
33482
|
throw new Error(`attachment path is not a file ${filePath}`);
|
|
32536
33483
|
}
|
|
32537
|
-
const realRoot = await
|
|
32538
|
-
const realFile = await
|
|
33484
|
+
const realRoot = await fs5.realpath(rootDir);
|
|
33485
|
+
const realFile = await fs5.realpath(filePath);
|
|
32539
33486
|
if (!isPathInside(realFile, realRoot)) {
|
|
32540
33487
|
throw new Error(`attachment file escapes workspace: ${filePath}`);
|
|
32541
33488
|
}
|
|
32542
33489
|
return stat;
|
|
32543
33490
|
}
|
|
32544
33491
|
async function localDirectoryStatInsideRoot(dirPath, rootDir) {
|
|
32545
|
-
const stat = await
|
|
33492
|
+
const stat = await fs5.lstat(dirPath);
|
|
32546
33493
|
if (stat.isSymbolicLink()) {
|
|
32547
33494
|
throw new Error(`refusing to use symlinked attachment directory ${dirPath}`);
|
|
32548
33495
|
}
|
|
32549
33496
|
if (!stat.isDirectory()) {
|
|
32550
33497
|
throw new Error(`attachment path is not a directory ${dirPath}`);
|
|
32551
33498
|
}
|
|
32552
|
-
const realRoot = await
|
|
32553
|
-
const realDir = await
|
|
33499
|
+
const realRoot = await fs5.realpath(rootDir);
|
|
33500
|
+
const realDir = await fs5.realpath(dirPath);
|
|
32554
33501
|
if (!isPathInside(realDir, realRoot)) {
|
|
32555
33502
|
throw new Error(`attachment directory escapes workspace: ${dirPath}`);
|
|
32556
33503
|
}
|
|
@@ -32558,7 +33505,7 @@ async function localDirectoryStatInsideRoot(dirPath, rootDir) {
|
|
|
32558
33505
|
}
|
|
32559
33506
|
async function openLocalFileInsideRoot(filePath, rootDir) {
|
|
32560
33507
|
const checkedStat = await localFileStatInsideRoot(filePath, rootDir);
|
|
32561
|
-
const file = await
|
|
33508
|
+
const file = await fs5.open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
32562
33509
|
let keepOpen = false;
|
|
32563
33510
|
try {
|
|
32564
33511
|
const openedStat = await file.stat();
|
|
@@ -32574,8 +33521,8 @@ async function openLocalFileInsideRoot(filePath, rootDir) {
|
|
|
32574
33521
|
}
|
|
32575
33522
|
}
|
|
32576
33523
|
async function openLocalTempFileInsideRoot(filePath, rootDir) {
|
|
32577
|
-
await localDirectoryStatInsideRoot(
|
|
32578
|
-
const file = await
|
|
33524
|
+
await localDirectoryStatInsideRoot(path6.dirname(filePath), rootDir);
|
|
33525
|
+
const file = await fs5.open(filePath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
|
|
32579
33526
|
let keepOpen = false;
|
|
32580
33527
|
try {
|
|
32581
33528
|
const checkedStat = await localFileStatInsideRoot(filePath, rootDir);
|
|
@@ -32600,7 +33547,7 @@ async function assertLocalFileIdentity(filePath, rootDir, expected) {
|
|
|
32600
33547
|
async function removeLocalFileIfInside(filePath, rootDir) {
|
|
32601
33548
|
try {
|
|
32602
33549
|
await localFileStatInsideRoot(filePath, rootDir);
|
|
32603
|
-
await
|
|
33550
|
+
await fs5.rm(filePath, { force: true });
|
|
32604
33551
|
} catch {
|
|
32605
33552
|
}
|
|
32606
33553
|
}
|
|
@@ -32613,7 +33560,7 @@ function sameFile(a, b) {
|
|
|
32613
33560
|
async function cleanupOldAttachmentFiles(rootDir, ttlMs, log2, preserveDirs) {
|
|
32614
33561
|
let entries;
|
|
32615
33562
|
try {
|
|
32616
|
-
entries = await
|
|
33563
|
+
entries = await fs5.readdir(rootDir, { withFileTypes: true });
|
|
32617
33564
|
} catch {
|
|
32618
33565
|
return;
|
|
32619
33566
|
}
|
|
@@ -32621,15 +33568,15 @@ async function cleanupOldAttachmentFiles(rootDir, ttlMs, log2, preserveDirs) {
|
|
|
32621
33568
|
await Promise.all(entries.map(async (entry) => {
|
|
32622
33569
|
if (!entry.isDirectory())
|
|
32623
33570
|
return;
|
|
32624
|
-
const fullPath =
|
|
33571
|
+
const fullPath = path6.join(rootDir, entry.name);
|
|
32625
33572
|
try {
|
|
32626
|
-
if (preserveDirs?.has(
|
|
33573
|
+
if (preserveDirs?.has(path6.resolve(fullPath)))
|
|
32627
33574
|
return;
|
|
32628
|
-
const stat = await
|
|
33575
|
+
const stat = await fs5.lstat(fullPath);
|
|
32629
33576
|
if (!stat.isDirectory())
|
|
32630
33577
|
return;
|
|
32631
33578
|
if (stat.mtimeMs < cutoff) {
|
|
32632
|
-
await
|
|
33579
|
+
await fs5.rm(fullPath, { recursive: true, force: true });
|
|
32633
33580
|
}
|
|
32634
33581
|
} catch (err) {
|
|
32635
33582
|
log2?.warn?.(`agent-core: failed to clean attachment temp dir ${fullPath}: ${String(err)}`);
|
|
@@ -32641,7 +33588,7 @@ async function pruneAttachmentCache(rootDir, maxBytes, log2, preserveDirs) {
|
|
|
32641
33588
|
return;
|
|
32642
33589
|
let entries;
|
|
32643
33590
|
try {
|
|
32644
|
-
entries = await
|
|
33591
|
+
entries = await fs5.readdir(rootDir, { withFileTypes: true });
|
|
32645
33592
|
} catch {
|
|
32646
33593
|
return;
|
|
32647
33594
|
}
|
|
@@ -32650,9 +33597,9 @@ async function pruneAttachmentCache(rootDir, maxBytes, log2, preserveDirs) {
|
|
|
32650
33597
|
for (const entry of entries) {
|
|
32651
33598
|
if (!entry.isDirectory())
|
|
32652
33599
|
continue;
|
|
32653
|
-
const fullPath =
|
|
33600
|
+
const fullPath = path6.join(rootDir, entry.name);
|
|
32654
33601
|
try {
|
|
32655
|
-
const stat = await
|
|
33602
|
+
const stat = await fs5.lstat(fullPath);
|
|
32656
33603
|
if (!stat.isDirectory())
|
|
32657
33604
|
continue;
|
|
32658
33605
|
const size = await directorySize(fullPath);
|
|
@@ -32668,10 +33615,10 @@ async function pruneAttachmentCache(rootDir, maxBytes, log2, preserveDirs) {
|
|
|
32668
33615
|
for (const dir of dirs) {
|
|
32669
33616
|
if (total <= maxBytes)
|
|
32670
33617
|
break;
|
|
32671
|
-
if (preserveDirs?.has(
|
|
33618
|
+
if (preserveDirs?.has(path6.resolve(dir.path)))
|
|
32672
33619
|
continue;
|
|
32673
33620
|
try {
|
|
32674
|
-
await
|
|
33621
|
+
await fs5.rm(dir.path, { recursive: true, force: true });
|
|
32675
33622
|
total -= dir.size;
|
|
32676
33623
|
} catch (err) {
|
|
32677
33624
|
log2?.warn?.(`agent-core: failed to prune attachment cache dir ${dir.path}: ${String(err)}`);
|
|
@@ -32680,12 +33627,12 @@ async function pruneAttachmentCache(rootDir, maxBytes, log2, preserveDirs) {
|
|
|
32680
33627
|
}
|
|
32681
33628
|
async function directorySize(dirPath) {
|
|
32682
33629
|
let total = 0;
|
|
32683
|
-
const entries = await
|
|
33630
|
+
const entries = await fs5.readdir(dirPath, { withFileTypes: true });
|
|
32684
33631
|
for (const entry of entries) {
|
|
32685
|
-
const fullPath =
|
|
33632
|
+
const fullPath = path6.join(dirPath, entry.name);
|
|
32686
33633
|
let stat;
|
|
32687
33634
|
try {
|
|
32688
|
-
stat = await
|
|
33635
|
+
stat = await fs5.lstat(fullPath);
|
|
32689
33636
|
} catch {
|
|
32690
33637
|
continue;
|
|
32691
33638
|
}
|
|
@@ -32700,10 +33647,10 @@ async function directorySize(dirPath) {
|
|
|
32700
33647
|
return total;
|
|
32701
33648
|
}
|
|
32702
33649
|
function activeDirsForRoot(rootDir) {
|
|
32703
|
-
const root =
|
|
33650
|
+
const root = path6.resolve(rootDir);
|
|
32704
33651
|
const dirs = /* @__PURE__ */ new Set();
|
|
32705
33652
|
for (const dir of activeAttachmentDirs) {
|
|
32706
|
-
if (dir === root || dir.startsWith(`${root}${
|
|
33653
|
+
if (dir === root || dir.startsWith(`${root}${path6.sep}`)) {
|
|
32707
33654
|
dirs.add(dir);
|
|
32708
33655
|
}
|
|
32709
33656
|
}
|
|
@@ -32800,9 +33747,9 @@ async function writeResponseToFileWithLimit(res, filePath, maxBytes, rootDir) {
|
|
|
32800
33747
|
}
|
|
32801
33748
|
writtenStat = await file.stat();
|
|
32802
33749
|
await closeFile();
|
|
32803
|
-
await localDirectoryStatInsideRoot(
|
|
33750
|
+
await localDirectoryStatInsideRoot(path6.dirname(filePath), rootDir);
|
|
32804
33751
|
await assertLocalFileIdentity(tmpPath, rootDir, writtenStat);
|
|
32805
|
-
await
|
|
33752
|
+
await fs5.rename(tmpPath, filePath);
|
|
32806
33753
|
completed = true;
|
|
32807
33754
|
return written;
|
|
32808
33755
|
} finally {
|
|
@@ -32818,9 +33765,9 @@ async function writeResponseToFileWithLimit(res, filePath, maxBytes, rootDir) {
|
|
|
32818
33765
|
}
|
|
32819
33766
|
}
|
|
32820
33767
|
function localFileName(attachmentId, fileName, mimeType) {
|
|
32821
|
-
const safeName = sanitizePathSegment(
|
|
32822
|
-
const ext =
|
|
32823
|
-
const stem =
|
|
33768
|
+
const safeName = sanitizePathSegment(path6.basename(fileName || attachmentId));
|
|
33769
|
+
const ext = path6.extname(safeName) || extensionForMime(mimeType);
|
|
33770
|
+
const stem = path6.basename(safeName, path6.extname(safeName)) || attachmentId;
|
|
32824
33771
|
return `${sanitizePathSegment(attachmentId)}-${stem}${ext}`;
|
|
32825
33772
|
}
|
|
32826
33773
|
function extensionForMime(mimeType) {
|
|
@@ -33100,7 +34047,11 @@ function buildSpawnEnv(parentEnv, claudeHome, context2, opts) {
|
|
|
33100
34047
|
PRLL_TRIGGER_MESSAGE_ID: context2.triggerMessageId ?? "",
|
|
33101
34048
|
PRLL_NO_REPLY: context2.noReply ? "1" : "",
|
|
33102
34049
|
PRLL_CONTEXT_FILE: context2.contextFilePath ?? "",
|
|
33103
|
-
PRLL_STEP_ID_FILE: context2.stepIdFilePath ?? ""
|
|
34050
|
+
PRLL_STEP_ID_FILE: context2.stepIdFilePath ?? "",
|
|
34051
|
+
// Per-lane dispatch context directory (stable for the bridge's lifetime,
|
|
34052
|
+
// so pinning it at spawn is safe even for long-lived subprocesses). The
|
|
34053
|
+
// CLI keys into it by send target to derive dispatch effect keys.
|
|
34054
|
+
PRLL_CONTEXT_DIR: context2.contextDirPath ?? ""
|
|
33104
34055
|
};
|
|
33105
34056
|
if (!result.PRLL_WIKI_MOUNT_ROOT?.trim() && opts.wikiMountRoot) {
|
|
33106
34057
|
result.PRLL_WIKI_MOUNT_ROOT = opts.wikiMountRoot;
|
|
@@ -33206,8 +34157,8 @@ var ClaudeCodeAdapter = class {
|
|
|
33206
34157
|
if (!sessionId)
|
|
33207
34158
|
return void 0;
|
|
33208
34159
|
const projectSlug = this.opts.workspaceDir.replace(/[/.]/g, "-");
|
|
33209
|
-
const filePath =
|
|
33210
|
-
return
|
|
34160
|
+
const filePath = path7.join(this.opts.claudeHome, ".claude", "projects", projectSlug, `${sessionId}.jsonl`);
|
|
34161
|
+
return fs6.existsSync(filePath) ? filePath : void 0;
|
|
33211
34162
|
}
|
|
33212
34163
|
forkSession({ sessionKey }) {
|
|
33213
34164
|
return this.opts.sessionManager.createForkSession(sessionKey);
|
|
@@ -33493,7 +34444,7 @@ var ClaudeCodeAdapter = class {
|
|
|
33493
34444
|
if (this.opts.disallowedTools.length > 0) {
|
|
33494
34445
|
args.push("--disallowedTools", this.opts.disallowedTools.join(","));
|
|
33495
34446
|
}
|
|
33496
|
-
args.push("--append-system-prompt-file",
|
|
34447
|
+
args.push("--append-system-prompt-file", path7.join(this.opts.workspaceDir, ".parall", "system-prompt.md"));
|
|
33497
34448
|
if (this.opts.appendSystemPrompt) {
|
|
33498
34449
|
args.push("--append-system-prompt", this.opts.appendSystemPrompt);
|
|
33499
34450
|
}
|
|
@@ -33518,14 +34469,15 @@ var ClaudeCodeAdapter = class {
|
|
|
33518
34469
|
noReply: false,
|
|
33519
34470
|
contextFilePath: this.opts.contextFilePathForSession?.(sessionKey) ?? "",
|
|
33520
34471
|
stepIdFilePath: this.opts.stepIdFilePathForSession?.(sessionKey) ?? "",
|
|
34472
|
+
contextDirPath: this.opts.contextDirPath ?? "",
|
|
33521
34473
|
client: void 0
|
|
33522
34474
|
};
|
|
33523
34475
|
}
|
|
33524
34476
|
};
|
|
33525
34477
|
|
|
33526
34478
|
// ts/claude-agent/dist/session-manager.js
|
|
33527
|
-
import * as
|
|
33528
|
-
import * as
|
|
34479
|
+
import * as fs7 from "node:fs";
|
|
34480
|
+
import * as path8 from "node:path";
|
|
33529
34481
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
33530
34482
|
var ClaudeSessionManager = class _ClaudeSessionManager {
|
|
33531
34483
|
mainSessionKey;
|
|
@@ -33674,13 +34626,13 @@ var ClaudeSessionManager = class _ClaudeSessionManager {
|
|
|
33674
34626
|
clearMainSession() {
|
|
33675
34627
|
this.sessionIds.delete(this.mainSessionKey);
|
|
33676
34628
|
try {
|
|
33677
|
-
|
|
34629
|
+
fs7.unlinkSync(this.stateFilePath);
|
|
33678
34630
|
} catch {
|
|
33679
34631
|
}
|
|
33680
34632
|
}
|
|
33681
34633
|
restore() {
|
|
33682
34634
|
try {
|
|
33683
|
-
const raw =
|
|
34635
|
+
const raw = fs7.readFileSync(this.stateFilePath, "utf8");
|
|
33684
34636
|
const parsed = JSON.parse(raw);
|
|
33685
34637
|
if (parsed.runtimeKey === this.mainSessionKey && typeof parsed.sessionId === "string" && parsed.sessionId.trim()) {
|
|
33686
34638
|
this.sessionIds.set(this.mainSessionKey, parsed.sessionId.trim());
|
|
@@ -33690,8 +34642,8 @@ var ClaudeSessionManager = class _ClaudeSessionManager {
|
|
|
33690
34642
|
}
|
|
33691
34643
|
persist(sessionId) {
|
|
33692
34644
|
try {
|
|
33693
|
-
|
|
33694
|
-
|
|
34645
|
+
fs7.mkdirSync(path8.dirname(this.stateFilePath), { recursive: true });
|
|
34646
|
+
fs7.writeFileSync(this.stateFilePath, JSON.stringify({
|
|
33695
34647
|
runtimeKey: this.mainSessionKey,
|
|
33696
34648
|
sessionId
|
|
33697
34649
|
}, null, 2));
|
|
@@ -33702,8 +34654,8 @@ var ClaudeSessionManager = class _ClaudeSessionManager {
|
|
|
33702
34654
|
};
|
|
33703
34655
|
|
|
33704
34656
|
// ts/claude-agent/dist/workspace.js
|
|
33705
|
-
import * as
|
|
33706
|
-
import * as
|
|
34657
|
+
import * as fs8 from "node:fs";
|
|
34658
|
+
import * as path9 from "node:path";
|
|
33707
34659
|
function ensureClaudeWorkspace(workspaceDir, log2, agentIdentity) {
|
|
33708
34660
|
const systemPrompt = [
|
|
33709
34661
|
buildIdentity(agentIdentity),
|
|
@@ -33712,12 +34664,12 @@ function ensureClaudeWorkspace(workspaceDir, log2, agentIdentity) {
|
|
|
33712
34664
|
PRLL_REFERENCE_GUIDE,
|
|
33713
34665
|
buildSkillReferences(workspaceDir)
|
|
33714
34666
|
].join("\n\n");
|
|
33715
|
-
|
|
33716
|
-
|
|
33717
|
-
const parallDir =
|
|
33718
|
-
|
|
33719
|
-
|
|
33720
|
-
writeSkillFiles(
|
|
34667
|
+
fs8.mkdirSync(workspaceDir, { recursive: true });
|
|
34668
|
+
fs8.mkdirSync(path9.join(workspaceDir, ".claude"), { recursive: true });
|
|
34669
|
+
const parallDir = path9.join(workspaceDir, ".parall");
|
|
34670
|
+
fs8.mkdirSync(parallDir, { recursive: true });
|
|
34671
|
+
fs8.writeFileSync(path9.join(parallDir, "system-prompt.md"), systemPrompt, "utf8");
|
|
34672
|
+
writeSkillFiles(path9.join(parallDir, "skills"));
|
|
33721
34673
|
ensureLocalAttachmentGitExclude(workspaceDir);
|
|
33722
34674
|
}
|
|
33723
34675
|
|
|
@@ -33792,14 +34744,13 @@ async function main() {
|
|
|
33792
34744
|
log: agentLog
|
|
33793
34745
|
});
|
|
33794
34746
|
const platformDefaults = await configMgr.fetch();
|
|
33795
|
-
|
|
33796
|
-
let platformModelOverride = isPlatformModelOverride(me.agent_profile);
|
|
34747
|
+
const platformModelOverride = deriveModelIsPin(platformDefaults, me.agent_profile);
|
|
33797
34748
|
const resolvedModel = resolveRuntimeModel(platformModelOverride, platformDefaults.model, config.model);
|
|
33798
|
-
const resolvedEffort =
|
|
34749
|
+
const resolvedEffort = platformDefaults.thinkingEffort ?? (process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || void 0);
|
|
33799
34750
|
if (platformDefaults.model)
|
|
33800
|
-
agentLog.info(`platform config: model=${platformDefaults.model} \u2192 resolved=${resolvedModel ?? "cli-default"} (${platformModelOverride ? "
|
|
34751
|
+
agentLog.info(`platform config: model=${platformDefaults.model} \u2192 resolved=${resolvedModel ?? "cli-default"} (${platformModelOverride ? "pin/override" : "floor"})`);
|
|
33801
34752
|
if (platformDefaults.thinkingEffort)
|
|
33802
|
-
agentLog.info(`platform config: thinking_effort=${platformDefaults.thinkingEffort}
|
|
34753
|
+
agentLog.info(`platform config: thinking_effort=${platformDefaults.thinkingEffort}`);
|
|
33803
34754
|
const adapter = new ClaudeCodeAdapter({
|
|
33804
34755
|
claudeBin: config.claudeBin,
|
|
33805
34756
|
claudeHome: config.claudeHome,
|
|
@@ -33819,11 +34770,29 @@ async function main() {
|
|
|
33819
34770
|
apiKey: config.apiKey,
|
|
33820
34771
|
orgId: config.orgId,
|
|
33821
34772
|
contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
|
|
33822
|
-
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey)
|
|
34773
|
+
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
34774
|
+
contextDirPath: dispatchContextDirPath(config.stateDir)
|
|
33823
34775
|
});
|
|
33824
34776
|
if (resolvedEffort) {
|
|
33825
34777
|
adapter.updateConfig({ effort: resolvedEffort });
|
|
33826
34778
|
}
|
|
34779
|
+
const refreshPlatformConfig = async () => {
|
|
34780
|
+
const updated = await configMgr.fetch();
|
|
34781
|
+
let refreshedProfile;
|
|
34782
|
+
if (updated.modelIsPin === void 0) {
|
|
34783
|
+
try {
|
|
34784
|
+
refreshedProfile = (await getAgentMeWithLegacyFallback(client, config.orgId)).agent_profile;
|
|
34785
|
+
} catch (err) {
|
|
34786
|
+
agentLog.warn(`platform config refresh: legacy /agents/me fallback failed: ${String(err)}`);
|
|
34787
|
+
}
|
|
34788
|
+
}
|
|
34789
|
+
const isPin = deriveModelIsPin(updated, refreshedProfile);
|
|
34790
|
+
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
34791
|
+
adapter.updateConfig({
|
|
34792
|
+
model: resolveRuntimeModel(isPin, updated.model, config.model) ?? null,
|
|
34793
|
+
effort: updated.thinkingEffort ?? localEffort
|
|
34794
|
+
});
|
|
34795
|
+
};
|
|
33827
34796
|
const gateway = new ParallAgentGateway({
|
|
33828
34797
|
accountId: agentUserId,
|
|
33829
34798
|
client,
|
|
@@ -33850,28 +34819,9 @@ async function main() {
|
|
|
33850
34819
|
dispatchDeadlineMs: parseDispatchDeadlineMs(process.env.PRLL_DISPATCH_DEADLINE_MS),
|
|
33851
34820
|
contextFilePathForSession: (sessionKey) => contextFilePathForSession(config.stateDir, sessionKey),
|
|
33852
34821
|
stepIdFilePathForSession: (sessionKey) => stepIdFilePathForSession(config.stateDir, sessionKey),
|
|
33853
|
-
|
|
33854
|
-
|
|
33855
|
-
|
|
33856
|
-
platformModelOverride = isPlatformModelOverride(refreshed.agent_profile);
|
|
33857
|
-
const updated = await configMgr.fetch();
|
|
33858
|
-
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
33859
|
-
adapter.updateConfig({
|
|
33860
|
-
model: resolveRuntimeModel(platformModelOverride, updated.model, config.model) ?? null,
|
|
33861
|
-
effort: platformManaged ? updated.thinkingEffort ?? localEffort : localEffort
|
|
33862
|
-
});
|
|
33863
|
-
},
|
|
33864
|
-
onSessionReady: async () => {
|
|
33865
|
-
const refreshed = await getAgentMeWithLegacyFallback(client, config.orgId);
|
|
33866
|
-
platformManaged = isPlatformManagedProfile(refreshed.agent_profile);
|
|
33867
|
-
platformModelOverride = isPlatformModelOverride(refreshed.agent_profile);
|
|
33868
|
-
const updated = await configMgr.fetch();
|
|
33869
|
-
const localEffort = process.env.CLAUDE_CODE_EFFORT_LEVEL?.trim() || null;
|
|
33870
|
-
adapter.updateConfig({
|
|
33871
|
-
model: resolveRuntimeModel(platformModelOverride, updated.model, config.model) ?? null,
|
|
33872
|
-
effort: platformManaged ? updated.thinkingEffort ?? localEffort : localEffort
|
|
33873
|
-
});
|
|
33874
|
-
},
|
|
34822
|
+
dispatchContextDir: dispatchContextDirPath(config.stateDir),
|
|
34823
|
+
onConfigUpdate: refreshPlatformConfig,
|
|
34824
|
+
onSessionReady: refreshPlatformConfig,
|
|
33875
34825
|
// Long-lived `claude` subprocesses outlive a single dispatch, so the
|
|
33876
34826
|
// gateway's shuttingDown flag alone does not tear them down. Piggyback on
|
|
33877
34827
|
// onBeforeDisconnect to close stdin and SIGTERM any survivors after
|