@parall/parall 1.51.0 → 1.52.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +37 -1
- package/dist/index.bundle.mjs +648 -246
- package/package.json +3 -3
- package/skills/parall-clips/SKILL.md +45 -5
- package/src/gateway.ts +37 -1
package/dist/index.bundle.mjs
CHANGED
|
@@ -51594,6 +51594,7 @@ var ENDPOINTS = {
|
|
|
51594
51594
|
DISPATCH_BY_MESSAGES: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/by-messages`,
|
|
51595
51595
|
DISPATCH_CLAIM: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/claim`,
|
|
51596
51596
|
DISPATCH_STEER: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/steer`,
|
|
51597
|
+
DISPATCH_INPUT_STATE: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/input-state`,
|
|
51597
51598
|
DISPATCH_COMPLETE: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/complete`,
|
|
51598
51599
|
DISPATCH_COMPLETE_SOURCES: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/complete-sources`,
|
|
51599
51600
|
DISPATCH_RELEASE: (orgId) => `${API_BASE}/orgs/${orgId}/dispatch/release`,
|
|
@@ -51691,7 +51692,11 @@ var ENDPOINTS = {
|
|
|
51691
51692
|
// Pinix Hub catalog proxy above is a different, id-less surface)
|
|
51692
51693
|
ORG_CLIP_REGISTRY: (orgId) => `/api/v1/orgs/${orgId}/clip-registry`,
|
|
51693
51694
|
ORG_CLIP_INSTALL: (orgId) => `/api/v1/orgs/${orgId}/clips/install`,
|
|
51694
|
-
ORG_CLIPS_INSTALLED: (orgId) => `/api/v1/orgs/${orgId}/clips/installed
|
|
51695
|
+
ORG_CLIPS_INSTALLED: (orgId) => `/api/v1/orgs/${orgId}/clips/installed`,
|
|
51696
|
+
// MCP clip server config (cap:clip-mcp; publisher-org only — cross-org gets
|
|
51697
|
+
// 403 MCP_CROSS_ORG_DISABLED on the whole family, reads included)
|
|
51698
|
+
ORG_CLIP_MCP_CONFIG: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config`,
|
|
51699
|
+
ORG_CLIP_MCP_TOOLS_REFRESH: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-config/tools/refresh`
|
|
51695
51700
|
};
|
|
51696
51701
|
var WS_EVENTS = {
|
|
51697
51702
|
// Client -> Server
|
|
@@ -52879,6 +52884,10 @@ var ParallClient = class _ParallClient {
|
|
|
52879
52884
|
async steerDispatch(orgId, req) {
|
|
52880
52885
|
return this.request("POST", ENDPOINTS.DISPATCH_STEER(orgId), req);
|
|
52881
52886
|
}
|
|
52887
|
+
/** Advance exact runtime-input lifecycle for members of an explicit lane. */
|
|
52888
|
+
async updateDispatchInputState(orgId, req) {
|
|
52889
|
+
return this.request("POST", ENDPOINTS.DISPATCH_INPUT_STATE(orgId), req);
|
|
52890
|
+
}
|
|
52882
52891
|
async completeDispatch(orgId, req) {
|
|
52883
52892
|
return this.request("POST", ENDPOINTS.DISPATCH_COMPLETE(orgId), req);
|
|
52884
52893
|
}
|
|
@@ -53844,12 +53853,17 @@ var ParallClient = class _ParallClient {
|
|
|
53844
53853
|
* command RAN and failed — `error`/`error_code` describe why). Everything
|
|
53845
53854
|
* else throws a typed {@link ApiError}; match on `err.code`:
|
|
53846
53855
|
*
|
|
53847
|
-
* Safe to retry (
|
|
53848
|
-
*
|
|
53849
|
-
*
|
|
53850
|
-
* - `
|
|
53851
|
-
*
|
|
53852
|
-
* - `
|
|
53856
|
+
* Safe to retry (provably not executed). Exactly three carry `Retry-After`
|
|
53857
|
+
* pacing ({@link ApiError.retryAfterSeconds}) — `EDGE_ACTIVATING`,
|
|
53858
|
+
* `EDGE_BUSY`, `EDGE_CONCURRENCY_LIMIT`:
|
|
53859
|
+
* - `EDGE_ACTIVATING` 503 — cold cloud profile is starting. Answered before
|
|
53860
|
+
* any dispatch. Bounded backoff, same `correlation_id` across the loop.
|
|
53861
|
+
* - `EDGE_BUSY` 409 — the device is executing another request; the pod
|
|
53862
|
+
* refused this one before starting any script.
|
|
53863
|
+
* - `EDGE_CONCURRENCY_LIMIT` 429 — org at its concurrent-session limit,
|
|
53864
|
+
* answered before any dispatch.
|
|
53865
|
+
* - `EDGE_UNAVAILABLE` 503 — session torn down / replaced mid-dispatch. No
|
|
53866
|
+
* `Retry-After` (no slot to wait for — retry re-resolves routing).
|
|
53853
53867
|
*
|
|
53854
53868
|
* NOT retryable:
|
|
53855
53869
|
* - `OUTCOME_UNKNOWN` 504 — dispatched, but no result arrived. The command
|
|
@@ -53888,6 +53902,60 @@ var ParallClient = class _ParallClient {
|
|
|
53888
53902
|
async deleteClipConnection(orgId, connId) {
|
|
53889
53903
|
return this.request("DELETE", ENDPOINTS.CLIP_CONNECTION(orgId, connId));
|
|
53890
53904
|
}
|
|
53905
|
+
// ---- MCP clip server config (cap:clip-mcp; publisher-org only) ----
|
|
53906
|
+
/**
|
|
53907
|
+
* Read the redacted MCP config. 404 NOT_FOUND when the clip has none yet;
|
|
53908
|
+
* 403 MCP_CROSS_ORG_DISABLED for a cross-org installed clip (the whole
|
|
53909
|
+
* mcp-config family is publisher-org property, reads included). `version`
|
|
53910
|
+
* is the CAS token the mutations echo via If-Match.
|
|
53911
|
+
*/
|
|
53912
|
+
async getClipMCPConfig(orgId, clipId) {
|
|
53913
|
+
return this.request("GET", ENDPOINTS.ORG_CLIP_MCP_CONFIG(orgId, clipId));
|
|
53914
|
+
}
|
|
53915
|
+
/**
|
|
53916
|
+
* Upsert the MCP config (human org-admin JWT only). Pass `expectedVersion`
|
|
53917
|
+
* from the last GET; omit it ONLY on first create (no config exists yet).
|
|
53918
|
+
* Save probes the remote server first — a probe failure persists nothing and
|
|
53919
|
+
* surfaces as MCP_URL_FORBIDDEN / MCP_AUTH_FAILED / MCP_SERVER_UNREACHABLE /
|
|
53920
|
+
* MCP_PROTOCOL_ERROR. Other typed conflicts: MCP_CONFIG_STALE (reload, then
|
|
53921
|
+
* retry with the current version), MCP_CONFIG_BUSY (another change in
|
|
53922
|
+
* flight — retry), MCP_CONNECTION_CONFLICT (a device-targeted default
|
|
53923
|
+
* connection must be unbound first), SECRETBOX_UNCONFIGURED (503 — the
|
|
53924
|
+
* server cannot store credentials safely).
|
|
53925
|
+
*/
|
|
53926
|
+
async putClipMCPConfig(orgId, clipId, req, expectedVersion) {
|
|
53927
|
+
return this.request("PUT", ENDPOINTS.ORG_CLIP_MCP_CONFIG(orgId, clipId), req, void 0, false, {
|
|
53928
|
+
headers: expectedVersion ? { "If-Match": `"${expectedVersion}"` } : void 0,
|
|
53929
|
+
// The server probes the remote MCP server inside the request on a fixed
|
|
53930
|
+
// 30s budget; give the HTTP layer headroom past it so a slow-but-valid
|
|
53931
|
+
// save isn't chopped locally into a fake transport error.
|
|
53932
|
+
timeoutMs: 4e4
|
|
53933
|
+
});
|
|
53934
|
+
}
|
|
53935
|
+
/**
|
|
53936
|
+
* Delete the MCP config and every connection routed through it (one
|
|
53937
|
+
* transaction — no orphaned target-less connections). `expectedVersion` is
|
|
53938
|
+
* mandatory: deleting always mutates an existing config.
|
|
53939
|
+
*/
|
|
53940
|
+
async deleteClipMCPConfig(orgId, clipId, expectedVersion) {
|
|
53941
|
+
return this.request("DELETE", ENDPOINTS.ORG_CLIP_MCP_CONFIG(orgId, clipId), void 0, void 0, false, { headers: { "If-Match": `"${expectedVersion}"` } });
|
|
53942
|
+
}
|
|
53943
|
+
/**
|
|
53944
|
+
* Re-run tools/list and replace the cached snapshot (org-admin only).
|
|
53945
|
+
* Deliberately does NOT advance the CAS version, so an in-flight edit in
|
|
53946
|
+
* another tab stays valid; on probe failure the old snapshot survives.
|
|
53947
|
+
*/
|
|
53948
|
+
async refreshClipMCPTools(orgId, clipId) {
|
|
53949
|
+
return this.request(
|
|
53950
|
+
"POST",
|
|
53951
|
+
ENDPOINTS.ORG_CLIP_MCP_TOOLS_REFRESH(orgId, clipId),
|
|
53952
|
+
void 0,
|
|
53953
|
+
void 0,
|
|
53954
|
+
false,
|
|
53955
|
+
// Same fixed 30s server-side probe budget as PUT (see putClipMCPConfig).
|
|
53956
|
+
{ timeoutMs: 4e4 }
|
|
53957
|
+
);
|
|
53958
|
+
}
|
|
53891
53959
|
};
|
|
53892
53960
|
function normalizeWikiChangeset(changeset) {
|
|
53893
53961
|
return {
|
|
@@ -54293,10 +54361,221 @@ var ParallWs = class {
|
|
|
54293
54361
|
}
|
|
54294
54362
|
};
|
|
54295
54363
|
|
|
54364
|
+
// ../agent-core/dist/event-format.js
|
|
54365
|
+
function sanitizeMeta(value) {
|
|
54366
|
+
return value.replace(/[\r\n]+/g, " ").replace(/[[\]|]/g, " ").trim();
|
|
54367
|
+
}
|
|
54368
|
+
function buildEventBody(event) {
|
|
54369
|
+
const lines = [];
|
|
54370
|
+
if (event.type === "message") {
|
|
54371
|
+
lines.push(`[Event: message.new]`);
|
|
54372
|
+
const chatLabel = event.targetName ? `"${event.targetName}" (prll://${event.targetId})` : `prll://${event.targetId}`;
|
|
54373
|
+
lines.push(`[Chat: ${chatLabel} | type: ${event.targetType ?? "unknown"}]`);
|
|
54374
|
+
lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
|
|
54375
|
+
lines.push(`[Message ID: prll://${event.messageId}]`);
|
|
54376
|
+
if (event.threadRootId) {
|
|
54377
|
+
const threadMeta = [
|
|
54378
|
+
`prll://${event.threadRootId}`,
|
|
54379
|
+
event.threadReplyCount != null ? `${event.threadReplyCount} replies` : null,
|
|
54380
|
+
event.threadUnreadCount != null && event.threadUnreadCount > 0 ? `${event.threadUnreadCount} unread` : null,
|
|
54381
|
+
event.threadUnreadCount != null && event.threadUnreadCount > 0 && event.threadUnreadSince ? `since: prll://${event.threadUnreadSince}` : null
|
|
54382
|
+
].filter(Boolean).join(" | ");
|
|
54383
|
+
lines.push(`[Thread: ${threadMeta}]`);
|
|
54384
|
+
}
|
|
54385
|
+
if (event.unreadCount != null && event.unreadCount > 1) {
|
|
54386
|
+
const countStr = event.unreadCount >= 1e3 ? "999+" : String(event.unreadCount);
|
|
54387
|
+
const sinceStr = event.unreadSince ? ` | since: prll://${event.unreadSince}` : "";
|
|
54388
|
+
let line = `[Unread: ${countStr} messages${sinceStr}]`;
|
|
54389
|
+
if (event.unreadCount > 50)
|
|
54390
|
+
line += ` \u2014 fetch recent context with --limit, not all`;
|
|
54391
|
+
lines.push(line);
|
|
54392
|
+
}
|
|
54393
|
+
if (event.noReply)
|
|
54394
|
+
lines.push(`[Hint: no_reply]`);
|
|
54395
|
+
if (event.attachments?.length) {
|
|
54396
|
+
for (const att of event.attachments) {
|
|
54397
|
+
const sizeStr = att.fileSize >= 1048576 ? `${(att.fileSize / 1048576).toFixed(1)}MB` : `${Math.round(att.fileSize / 1024)}KB`;
|
|
54398
|
+
lines.push(`[Attachment: prll://${att.id} | ${sanitizeMeta(att.mimeType)} | ${sizeStr} | ${sanitizeMeta(att.fileName)}]`);
|
|
54399
|
+
}
|
|
54400
|
+
}
|
|
54401
|
+
lines.push("", event.body);
|
|
54402
|
+
} else if (event.type === "task_comment") {
|
|
54403
|
+
lines.push(`[Event: task.comment.created]`);
|
|
54404
|
+
const taskLabel = event.targetName ? `${event.targetName} (prll://${event.targetId})` : `prll://${event.targetId}`;
|
|
54405
|
+
lines.push(`[Task: ${taskLabel}]`);
|
|
54406
|
+
if (event.deliveryReason)
|
|
54407
|
+
lines.push(`[Delivery: ${sanitizeMeta(event.deliveryReason)}]`);
|
|
54408
|
+
lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
|
|
54409
|
+
lines.push(`[Comment ID: prll://${event.messageId}]`);
|
|
54410
|
+
lines.push("", event.body);
|
|
54411
|
+
} else if (event.type === "wiki_comment") {
|
|
54412
|
+
lines.push(`[Event: wiki.comment.created]`);
|
|
54413
|
+
const target = event.replyTargetUri ?? `prll://${event.targetId}`;
|
|
54414
|
+
if (event.targetType === "changeset") {
|
|
54415
|
+
lines.push(`[Wiki Changeset: ${target}]`);
|
|
54416
|
+
} else {
|
|
54417
|
+
lines.push(`[Wiki: ${event.targetName ? `${sanitizeMeta(event.targetName)} (${target})` : target}]`);
|
|
54418
|
+
}
|
|
54419
|
+
if (event.deliveryReason)
|
|
54420
|
+
lines.push(`[Delivery: ${sanitizeMeta(event.deliveryReason)}]`);
|
|
54421
|
+
lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
|
|
54422
|
+
lines.push(`[Comment ID: prll://${event.messageId}]`);
|
|
54423
|
+
lines.push("", event.body);
|
|
54424
|
+
} else if (event.type === "approval") {
|
|
54425
|
+
lines.push(`[Event: approval.decided]`);
|
|
54426
|
+
lines.push(`[Approval: prll://${event.messageId}]`);
|
|
54427
|
+
lines.push(`[Chat: prll://${event.targetId}]`);
|
|
54428
|
+
lines.push(`[Decided by: ${event.senderName} (prll://${event.senderId})]`);
|
|
54429
|
+
lines.push("", event.body);
|
|
54430
|
+
} else if (event.type === "schedule") {
|
|
54431
|
+
lines.push(`[Event: schedule.fired]`);
|
|
54432
|
+
lines.push(`[Schedule: prll://${event.targetId}]`);
|
|
54433
|
+
lines.push(`[Run: prll://${event.messageId}]`);
|
|
54434
|
+
if (event.scheduledFireAt)
|
|
54435
|
+
lines.push(`[Scheduled at: ${sanitizeMeta(event.scheduledFireAt)}]`);
|
|
54436
|
+
if (event.attachedUri)
|
|
54437
|
+
lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
|
|
54438
|
+
lines.push("", event.body);
|
|
54439
|
+
} else if (event.type === "channel_message") {
|
|
54440
|
+
lines.push(`[Event: channel.message]`);
|
|
54441
|
+
const providerLabel = sanitizeMeta(event.channelProvider ?? "external IM");
|
|
54442
|
+
const convLabel = event.channelExternalConversationId ? `${sanitizeMeta(event.channelExternalConversationId)} (${sanitizeMeta(event.channelConversationType ?? "conversation")})` : sanitizeMeta(event.channelConversationType ?? "conversation");
|
|
54443
|
+
lines.push(`[Channel: ${providerLabel} | conversation: ${convLabel}]`);
|
|
54444
|
+
lines.push(`[From: ${sanitizeMeta(event.senderName)} (external user, not a Parall member)]`);
|
|
54445
|
+
if (event.channelExternalMessageId) {
|
|
54446
|
+
lines.push(`[External message ID: ${sanitizeMeta(event.channelExternalMessageId)}]`);
|
|
54447
|
+
}
|
|
54448
|
+
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.]`);
|
|
54449
|
+
lines.push("", event.body);
|
|
54450
|
+
} else if (event.type === "external_trigger") {
|
|
54451
|
+
lines.push(`[Event: external.trigger]`);
|
|
54452
|
+
lines.push(`[Trigger: prll://${event.targetId}]`);
|
|
54453
|
+
lines.push(`[Run: prll://${event.messageId}]`);
|
|
54454
|
+
if (event.externalConnectionId) {
|
|
54455
|
+
const label = event.externalConnectionDisplayName ? `${sanitizeMeta(event.externalConnectionDisplayName)} (prll://${event.externalConnectionId})` : `prll://${event.externalConnectionId}`;
|
|
54456
|
+
lines.push(`[Connection: ${label}]`);
|
|
54457
|
+
}
|
|
54458
|
+
if (event.externalIngressEventId)
|
|
54459
|
+
lines.push(`[Ingress: prll://${event.externalIngressEventId}]`);
|
|
54460
|
+
if (event.attachedUri)
|
|
54461
|
+
lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
|
|
54462
|
+
if (event.externalConnectionSourceType) {
|
|
54463
|
+
lines.push(`[Source: ${sanitizeMeta(event.externalConnectionSourceType)}]`);
|
|
54464
|
+
}
|
|
54465
|
+
if (event.externalIngressEventType) {
|
|
54466
|
+
lines.push(`[External event: ${sanitizeMeta(event.externalIngressEventType)}]`);
|
|
54467
|
+
}
|
|
54468
|
+
lines.push("", event.body);
|
|
54469
|
+
} else {
|
|
54470
|
+
lines.push(`[Event: task.assigned]`);
|
|
54471
|
+
const taskLabel = event.targetName ? `${event.targetName} (prll://${event.targetId})` : `prll://${event.targetId}`;
|
|
54472
|
+
lines.push(`[Task: ${taskLabel}]`);
|
|
54473
|
+
lines.push(`[Assigned by: ${event.senderName} (prll://${event.senderId})]`);
|
|
54474
|
+
lines.push("", event.body);
|
|
54475
|
+
}
|
|
54476
|
+
return lines.join("\n") + buildSendMessageHint(event);
|
|
54477
|
+
}
|
|
54478
|
+
function buildEventBodyForForkResult(event) {
|
|
54479
|
+
return buildEventBody(event).replace(/\n<system-reminder>[\s\S]*<\/system-reminder>$/, "");
|
|
54480
|
+
}
|
|
54481
|
+
function buildSendMessageHint(event) {
|
|
54482
|
+
if (event.noReply)
|
|
54483
|
+
return "";
|
|
54484
|
+
if (event.type === "wiki_comment" && event.replyTargetUri) {
|
|
54485
|
+
const where = event.targetType === "changeset" ? "this changeset comment" : "this wiki page";
|
|
54486
|
+
return `
|
|
54487
|
+
<system-reminder>To reply on ${where}, run: \`parall comments add --target "${event.replyTargetUri}" --body "..."\` (read the thread first with \`parall comments list --target "${event.replyTargetUri}"\`). To message someone instead, use \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
|
|
54488
|
+
}
|
|
54489
|
+
if (event.targetId.startsWith("cht_")) {
|
|
54490
|
+
if (event.threadRootId) {
|
|
54491
|
+
return `
|
|
54492
|
+
<system-reminder>To reply in this thread, run \`parall messages send prll://${event.targetId} --thread-root-id ${sanitizeMeta(event.threadRootId)} --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>`;
|
|
54493
|
+
}
|
|
54494
|
+
return `
|
|
54495
|
+
<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>`;
|
|
54496
|
+
}
|
|
54497
|
+
if (event.targetId.startsWith("tsk_")) {
|
|
54498
|
+
return `
|
|
54499
|
+
<system-reminder>To respond, use the CLI: \`parall tasks update\` / \`parall tasks comments add\`. To message someone, use \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
|
|
54500
|
+
}
|
|
54501
|
+
if (event.targetId.startsWith("sch_")) {
|
|
54502
|
+
return `
|
|
54503
|
+
<system-reminder>To communicate, use the CLI: \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
|
|
54504
|
+
}
|
|
54505
|
+
if (event.type === "channel_message") {
|
|
54506
|
+
if (event.channelCliCapable) {
|
|
54507
|
+
if (event.channelProvider === "slack") {
|
|
54508
|
+
const channelArg = event.channelExternalConversationId ? ` --channel "${event.channelExternalConversationId}"` : " --channel <conversation id from this event>";
|
|
54509
|
+
const replyTo = event.channelExternalMessageId ? ` --reply-to "${event.channelExternalMessageId}"` : "";
|
|
54510
|
+
return `
|
|
54511
|
+
<system-reminder>To reply, use the platform verb: \`parall slack send${channelArg}${replyTo} --text <your reply>\`. In channels --reply-to is REQUIRED (the reply lands in that message's thread); in DMs it is optional (DMs are linear). \`parall slack send\` is the ONLY outbound path \u2014 your plain text output is NOT delivered to the external conversation.</system-reminder>`;
|
|
54512
|
+
}
|
|
54513
|
+
if (!event.channelProvider) {
|
|
54514
|
+
return `
|
|
54515
|
+
<system-reminder>To reply, use the channel capability granted in your system prompt \u2014 for Feishu conversations that is \`lark-cli im\`, for Slack it is \`parall slack send\` (pass the message id from this event as --reply-to). That capability is the ONLY outbound path \u2014 your plain text output is NOT delivered to the external conversation.</system-reminder>`;
|
|
54516
|
+
}
|
|
54517
|
+
const convRef = event.channelExternalConversationId ? `chat_id "${event.channelExternalConversationId}"` : "the conversation id named in this event";
|
|
54518
|
+
const threadAlt = event.channelExternalMessageId ? ` To reply threaded to this specific message, reference message_id "${event.channelExternalMessageId}".` : "";
|
|
54519
|
+
return `
|
|
54520
|
+
<system-reminder>To reply, use the official Feishu CLI on your PATH: send a message to ${convRef} with \`lark-cli im\` (see \`lark-cli im --help\` for send syntax; auth is provisioned automatically).${threadAlt} lark-cli is the ONLY outbound path \u2014 your plain text output is NOT delivered to the external conversation.</system-reminder>`;
|
|
54521
|
+
}
|
|
54522
|
+
const platform = event.channelProvider ?? "the external platform";
|
|
54523
|
+
return `
|
|
54524
|
+
<system-reminder>This message arrived from ${platform}, but outbound replies are currently disabled for this org (no channel capability granted). Do NOT attempt to reply on the external platform. If action is needed, surface it inside Parall (\`parall messages send\` / \`parall dm\`). Your plain text output is not delivered anywhere.</system-reminder>`;
|
|
54525
|
+
}
|
|
54526
|
+
if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
|
|
54527
|
+
return `
|
|
54528
|
+
<system-reminder>This external trigger is incoming-only. Your plain text output is not sent back to the external provider. To communicate in Parall, use \`parall messages send\` / \`parall dm\`; provider-specific outbound actions require a separate capability.</system-reminder>`;
|
|
54529
|
+
}
|
|
54530
|
+
return "";
|
|
54531
|
+
}
|
|
54532
|
+
function buildForkScopePrefix(event) {
|
|
54533
|
+
const targetLabel = event.targetName ? `"${event.targetName}" (prll://${event.targetId})` : `prll://${event.targetId}`;
|
|
54534
|
+
return `[Fork scope: You are a parallel worker forked from the main session to handle ${targetLabel}. The conversation history above belongs to the main session's in-progress dispatch \u2014 do NOT continue it or complete it, or produce external output (comments, messages) for work visible there. The main session owns its own output.
|
|
54535
|
+
|
|
54536
|
+
Only act on the event below within your target scope. If your event reveals findings about the main session's work, report them in your summary \u2014 do not post them externally.
|
|
54537
|
+
|
|
54538
|
+
End with a brief "Fork summary" of actions taken and decisions made.]
|
|
54539
|
+
|
|
54540
|
+
`;
|
|
54541
|
+
}
|
|
54542
|
+
function buildForkResultPrefix(results) {
|
|
54543
|
+
if (!results.length)
|
|
54544
|
+
return "";
|
|
54545
|
+
const blocks = results.map((result) => {
|
|
54546
|
+
const lines = [];
|
|
54547
|
+
for (const body of result.eventBodies) {
|
|
54548
|
+
lines.push(body);
|
|
54549
|
+
}
|
|
54550
|
+
lines.push(`[This event was handled by a parallel fork session. Do NOT re-handle, re-reply, or duplicate work for it.]`);
|
|
54551
|
+
lines.push(`[Fork summary: ${result.agentSummary ? sanitizeMeta(result.agentSummary) : "No fork summary available \u2014 the fork completed without producing a text summary. Check the target chat/task for any actions the fork already took before acting."}]`);
|
|
54552
|
+
if (result.actions.length)
|
|
54553
|
+
lines.push(`[Fork actions: ${result.actions.join("; ")}]`);
|
|
54554
|
+
if (result.historyPath)
|
|
54555
|
+
lines.push(`[Fork history: ${result.historyPath}]`);
|
|
54556
|
+
return lines.join("\n");
|
|
54557
|
+
});
|
|
54558
|
+
return blocks.join("\n\n") + "\n\n---\n\n";
|
|
54559
|
+
}
|
|
54560
|
+
|
|
54296
54561
|
// ../agent-core/dist/lane-ledger.js
|
|
54297
54562
|
import * as fs from "node:fs";
|
|
54298
54563
|
var LedgerUnsupportedError = class extends Error {
|
|
54299
54564
|
};
|
|
54565
|
+
function bindLaneSession(lane, agentSessionId) {
|
|
54566
|
+
if (lane.sessionId && lane.sessionId !== agentSessionId) {
|
|
54567
|
+
lane.sessionAmbiguous = true;
|
|
54568
|
+
} else if (!lane.sessionAmbiguous) {
|
|
54569
|
+
lane.sessionId = agentSessionId;
|
|
54570
|
+
}
|
|
54571
|
+
}
|
|
54572
|
+
function releaseLocalMessageClaims(claims, sourceIds) {
|
|
54573
|
+
const released = sourceIds.filter((sourceId) => claims.delete(sourceId));
|
|
54574
|
+
return () => {
|
|
54575
|
+
for (const sourceId of released)
|
|
54576
|
+
claims.add(sourceId);
|
|
54577
|
+
};
|
|
54578
|
+
}
|
|
54300
54579
|
function isStaleLane(err) {
|
|
54301
54580
|
return err instanceof ApiError && err.status === 409 && err.code === "STALE_LANE";
|
|
54302
54581
|
}
|
|
@@ -54346,7 +54625,8 @@ var LaneLedger = class {
|
|
|
54346
54625
|
res = await this.opts.client.claimDispatch(this.opts.orgId, {
|
|
54347
54626
|
target_uri: targetUri,
|
|
54348
54627
|
thread_root_id: trigger.threadRootId,
|
|
54349
|
-
limit: 100
|
|
54628
|
+
limit: 100,
|
|
54629
|
+
coverage_mode: this.opts.coverageMode ?? "implicit"
|
|
54350
54630
|
});
|
|
54351
54631
|
} catch (err) {
|
|
54352
54632
|
if (isEndpointMissing(err))
|
|
@@ -54361,12 +54641,20 @@ var LaneLedger = class {
|
|
|
54361
54641
|
}
|
|
54362
54642
|
return null;
|
|
54363
54643
|
}
|
|
54644
|
+
const requestedCoverage = this.opts.coverageMode ?? "implicit";
|
|
54645
|
+
const actualCoverage = res.coverage_mode ?? "implicit";
|
|
54646
|
+
if (requestedCoverage === "explicit" && actualCoverage !== "explicit") {
|
|
54647
|
+
await this.opts.client.releaseDispatchLane(this.opts.orgId, res.lane).catch(() => {
|
|
54648
|
+
});
|
|
54649
|
+
throw new Error(`server did not negotiate explicit input coverage for ${targetUri}; lane released`);
|
|
54650
|
+
}
|
|
54364
54651
|
const leaseUntilMs = Date.parse(res.lease_until ?? "");
|
|
54365
54652
|
lane = {
|
|
54366
54653
|
laneKey,
|
|
54367
54654
|
lane: res.lane,
|
|
54368
54655
|
targetUri,
|
|
54369
54656
|
threadRootId: trigger.threadRootId,
|
|
54657
|
+
coverageMode: actualCoverage,
|
|
54370
54658
|
folded: /* @__PURE__ */ new Map(),
|
|
54371
54659
|
...Number.isNaN(leaseUntilMs) ? {} : { leaseUntilMs, leaseTtlMs: Math.max(leaseUntilMs - Date.now(), 6e4) }
|
|
54372
54660
|
};
|
|
@@ -54408,9 +54696,10 @@ var LaneLedger = class {
|
|
|
54408
54696
|
const laneKey = this.laneKeyFor(event);
|
|
54409
54697
|
const lane = this.lanes.get(laneKey);
|
|
54410
54698
|
if (!lane)
|
|
54411
|
-
return
|
|
54412
|
-
if (lane.folded.has(event.messageId))
|
|
54413
|
-
return
|
|
54699
|
+
return null;
|
|
54700
|
+
if (lane.folded.has(event.messageId)) {
|
|
54701
|
+
return { inputLifecycle: this.inputLifecycle(lane, [event]) };
|
|
54702
|
+
}
|
|
54414
54703
|
try {
|
|
54415
54704
|
const res = await this.opts.client.steerDispatch(this.opts.orgId, {
|
|
54416
54705
|
lane: lane.lane,
|
|
@@ -54419,16 +54708,64 @@ var LaneLedger = class {
|
|
|
54419
54708
|
...event.dispatchEventId ? { dispatch_event_id: event.dispatchEventId } : { source_type: "message", source_id: event.messageId }
|
|
54420
54709
|
});
|
|
54421
54710
|
lane.folded.set(event.messageId, res.dispatch_event_id);
|
|
54422
|
-
return
|
|
54711
|
+
return { inputLifecycle: this.inputLifecycle(lane, [event]) };
|
|
54423
54712
|
} catch (err) {
|
|
54424
54713
|
if (isStaleLane(err)) {
|
|
54425
54714
|
this.lanes.delete(laneKey);
|
|
54426
54715
|
} else {
|
|
54427
54716
|
this.opts.log?.warn(`live steer failed for ${event.messageId}: ${String(err)}`);
|
|
54428
54717
|
}
|
|
54429
|
-
return
|
|
54718
|
+
return null;
|
|
54430
54719
|
}
|
|
54431
54720
|
}
|
|
54721
|
+
/**
|
|
54722
|
+
* Build the exact runtime-input lifecycle for one prompt/injection. A
|
|
54723
|
+
* batched prompt covers every WorkItem represented in that single frame.
|
|
54724
|
+
*/
|
|
54725
|
+
inputLifecycle(lane, events) {
|
|
54726
|
+
if (lane.coverageMode !== "explicit")
|
|
54727
|
+
return void 0;
|
|
54728
|
+
const dispatchEventIds = events.map((event) => lane.folded.get(event.messageId)).filter((id) => Boolean(id));
|
|
54729
|
+
if (dispatchEventIds.length !== events.length) {
|
|
54730
|
+
throw new Error(`explicit lane ${lane.lane} is missing a folded WorkItem mapping`);
|
|
54731
|
+
}
|
|
54732
|
+
return {
|
|
54733
|
+
deliveryKey: dispatchEventIds.join(","),
|
|
54734
|
+
dispatchEventIds,
|
|
54735
|
+
update: (state) => this.updateInputState(lane, dispatchEventIds, state)
|
|
54736
|
+
};
|
|
54737
|
+
}
|
|
54738
|
+
async updateInputState(lane, dispatchEventIds, state) {
|
|
54739
|
+
const failed = new Set(state === "failed" ? dispatchEventIds : []);
|
|
54740
|
+
const failedSourceIds = [...lane.folded].filter(([, dispatchEventId]) => failed.has(dispatchEventId)).map(([sourceId]) => sourceId);
|
|
54741
|
+
const restoreLocalClaims = failedSourceIds.length > 0 ? this.opts.releaseLocalClaims?.(failedSourceIds) : void 0;
|
|
54742
|
+
try {
|
|
54743
|
+
const result = await this.opts.client.updateDispatchInputState(this.opts.orgId, {
|
|
54744
|
+
lane: lane.lane,
|
|
54745
|
+
target_uri: lane.targetUri,
|
|
54746
|
+
thread_root_id: lane.threadRootId,
|
|
54747
|
+
dispatch_event_ids: dispatchEventIds,
|
|
54748
|
+
state
|
|
54749
|
+
});
|
|
54750
|
+
if (result.recognized !== dispatchEventIds.length) {
|
|
54751
|
+
throw new Error(`input lifecycle ${state} recognized ${result.recognized}/${dispatchEventIds.length} WorkItems`);
|
|
54752
|
+
}
|
|
54753
|
+
if (state === "failed" && result.released === 0) {
|
|
54754
|
+
restoreLocalClaims?.();
|
|
54755
|
+
return { retry: false };
|
|
54756
|
+
}
|
|
54757
|
+
} catch (err) {
|
|
54758
|
+
restoreLocalClaims?.();
|
|
54759
|
+
throw err;
|
|
54760
|
+
}
|
|
54761
|
+
if (state === "failed") {
|
|
54762
|
+
for (const [sourceId, dispatchEventId] of lane.folded) {
|
|
54763
|
+
if (failed.has(dispatchEventId))
|
|
54764
|
+
lane.folded.delete(sourceId);
|
|
54765
|
+
}
|
|
54766
|
+
}
|
|
54767
|
+
return { retry: state === "failed" };
|
|
54768
|
+
}
|
|
54432
54769
|
/**
|
|
54433
54770
|
* Complete the lane when no local work remains for it: the server sweeps
|
|
54434
54771
|
* still-leased members as no_action, releases the occupancy row, and
|
|
@@ -54447,20 +54784,46 @@ var LaneLedger = class {
|
|
|
54447
54784
|
if (lane)
|
|
54448
54785
|
lane.turnError = true;
|
|
54449
54786
|
}
|
|
54787
|
+
/**
|
|
54788
|
+
* Record that the turn on this lane ended on a self-healing usage limit.
|
|
54789
|
+
* Like markTurnError this is transport state for the lane's final
|
|
54790
|
+
* complete; an error bit set on the same lane outranks it.
|
|
54791
|
+
*/
|
|
54792
|
+
markTurnDeferred(laneKey, info) {
|
|
54793
|
+
const lane = this.lanes.get(laneKey);
|
|
54794
|
+
if (lane)
|
|
54795
|
+
lane.turnDeferred = info;
|
|
54796
|
+
}
|
|
54797
|
+
/**
|
|
54798
|
+
* Sticky: the server rejected turn_outcome=deferred (predates it). Deferred
|
|
54799
|
+
* completes fall back to the error form for the rest of the process — the
|
|
54800
|
+
* members still release for retry, just on the redrive budget instead of
|
|
54801
|
+
* the reset-time schedule.
|
|
54802
|
+
*/
|
|
54803
|
+
deferredUnsupported = false;
|
|
54450
54804
|
async completeIfIdle(laneKey, hasMoreLocal) {
|
|
54451
54805
|
const lane = this.lanes.get(laneKey);
|
|
54452
54806
|
if (!lane || hasMoreLocal)
|
|
54453
54807
|
return;
|
|
54454
54808
|
this.lanes.delete(laneKey);
|
|
54455
54809
|
this.removeLaneContext(lane);
|
|
54810
|
+
const deferred = !lane.turnError && !this.deferredUnsupported ? lane.turnDeferred : void 0;
|
|
54811
|
+
const outcome = lane.turnError ? "error" : lane.turnDeferred ? this.deferredUnsupported ? "error" : "deferred" : "ok";
|
|
54456
54812
|
try {
|
|
54457
54813
|
const res = await this.opts.client.completeDispatch(this.opts.orgId, {
|
|
54458
54814
|
lane: lane.lane,
|
|
54459
54815
|
target_uri: lane.targetUri,
|
|
54460
54816
|
thread_root_id: lane.threadRootId,
|
|
54461
|
-
// An error turn releases its members for retry
|
|
54462
|
-
// them
|
|
54463
|
-
|
|
54817
|
+
// An error turn releases its members for retry; a deferred turn
|
|
54818
|
+
// re-delivers them at retry_at without burning redrive budget
|
|
54819
|
+
// (ignored by older servers, which 400 on the unknown enum — see the
|
|
54820
|
+
// fallback below).
|
|
54821
|
+
turn_outcome: outcome,
|
|
54822
|
+
...deferred ? {
|
|
54823
|
+
outcome_class: deferred.outcomeClass,
|
|
54824
|
+
...deferred.retryAt ? { retry_at: deferred.retryAt } : {}
|
|
54825
|
+
} : {},
|
|
54826
|
+
session_id: lane.sessionAmbiguous ? void 0 : lane.sessionId
|
|
54464
54827
|
});
|
|
54465
54828
|
if (res.swept_no_action > 0 || res.redriven) {
|
|
54466
54829
|
this.opts.log?.info(`lane complete for ${lane.targetUri}: swept ${res.swept_no_action} no_action, redriven=${res.redriven}`);
|
|
@@ -54470,6 +54833,23 @@ var LaneLedger = class {
|
|
|
54470
54833
|
this.opts.log?.info(`lane complete skipped for ${lane.targetUri} \u2014 taken over`);
|
|
54471
54834
|
return;
|
|
54472
54835
|
}
|
|
54836
|
+
if (outcome === "deferred" && err instanceof ApiError && err.status === 400) {
|
|
54837
|
+
this.deferredUnsupported = true;
|
|
54838
|
+
this.opts.log?.warn(`server rejected turn_outcome=deferred for ${lane.targetUri} \u2014 falling back to error completes`);
|
|
54839
|
+
try {
|
|
54840
|
+
await this.opts.client.completeDispatch(this.opts.orgId, {
|
|
54841
|
+
lane: lane.lane,
|
|
54842
|
+
target_uri: lane.targetUri,
|
|
54843
|
+
thread_root_id: lane.threadRootId,
|
|
54844
|
+
turn_outcome: "error"
|
|
54845
|
+
});
|
|
54846
|
+
} catch (fallbackErr) {
|
|
54847
|
+
if (isStaleLane(fallbackErr))
|
|
54848
|
+
return;
|
|
54849
|
+
this.opts.log?.warn(`lane complete (deferred fallback) failed for ${lane.targetUri}: ${String(fallbackErr)}`);
|
|
54850
|
+
}
|
|
54851
|
+
return;
|
|
54852
|
+
}
|
|
54473
54853
|
this.opts.log?.warn(`lane complete failed for ${lane.targetUri}: ${String(err)}`);
|
|
54474
54854
|
}
|
|
54475
54855
|
}
|
|
@@ -54571,6 +54951,7 @@ var LaneLedger = class {
|
|
|
54571
54951
|
laneKey: laneKeyForTarget(targetUri),
|
|
54572
54952
|
lane: res.lane,
|
|
54573
54953
|
targetUri,
|
|
54954
|
+
coverageMode: "implicit",
|
|
54574
54955
|
folded: /* @__PURE__ */ new Map([[workItem.source_id, workItem.id]]),
|
|
54575
54956
|
typedDispatchEventId: workItem.id,
|
|
54576
54957
|
...Number.isNaN(leaseUntilMs) ? {} : { leaseUntilMs, leaseTtlMs: Math.max(leaseUntilMs - Date.now(), 6e4) }
|
|
@@ -54610,6 +54991,16 @@ var LaneLedger = class {
|
|
|
54610
54991
|
};
|
|
54611
54992
|
|
|
54612
54993
|
// ../agent-core/dist/gateway-lane-flow.js
|
|
54994
|
+
async function steerLaneMessage(host, event) {
|
|
54995
|
+
const { laneLedger: ledger, opts } = host;
|
|
54996
|
+
const adapter = opts.dispatchAdapter;
|
|
54997
|
+
if (!ledger || !adapter.enqueueDuringDispatch)
|
|
54998
|
+
return;
|
|
54999
|
+
const folded = await ledger.steerLive(event);
|
|
55000
|
+
if (folded && await adapter.enqueueDuringDispatch(opts.runtimeKey, buildEventBody(event), folded.inputLifecycle)) {
|
|
55001
|
+
opts.log?.info(`steer folded+injected for ${event.messageId} (will drain for bookkeeping)`);
|
|
55002
|
+
}
|
|
55003
|
+
}
|
|
54613
55004
|
async function dispatchLaneGroup(host, opts) {
|
|
54614
55005
|
const ledger = host.laneLedger;
|
|
54615
55006
|
const event = opts.events[opts.events.length - 1];
|
|
@@ -54642,7 +55033,8 @@ async function dispatchLaneGroup(host, opts) {
|
|
|
54642
55033
|
host.noteSessionLane(opts.sessionKey, lane.laneKey);
|
|
54643
55034
|
let dispatched = false;
|
|
54644
55035
|
try {
|
|
54645
|
-
|
|
55036
|
+
const inputLifecycle = ledger.inputLifecycle(lane, [...opts.earlier, event]);
|
|
55037
|
+
dispatched = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText, inputLifecycle);
|
|
54646
55038
|
} catch (err) {
|
|
54647
55039
|
host.noteSessionLane(opts.sessionKey, null);
|
|
54648
55040
|
await ledger.release(lane.laneKey).catch(() => {
|
|
@@ -54655,8 +55047,16 @@ async function dispatchLaneGroup(host, opts) {
|
|
|
54655
55047
|
if (!dispatched) {
|
|
54656
55048
|
return "shutdown";
|
|
54657
55049
|
}
|
|
54658
|
-
|
|
54659
|
-
|
|
55050
|
+
const settled = host.consumeTurnOutcome(opts.sessionKey);
|
|
55051
|
+
if (settled) {
|
|
55052
|
+
if (settled.kind === "deferred") {
|
|
55053
|
+
ledger.markTurnDeferred(lane.laneKey, {
|
|
55054
|
+
outcomeClass: settled.outcomeClass,
|
|
55055
|
+
...settled.retryAt ? { retryAt: settled.retryAt } : {}
|
|
55056
|
+
});
|
|
55057
|
+
} else {
|
|
55058
|
+
ledger.markTurnError(lane.laneKey);
|
|
55059
|
+
}
|
|
54660
55060
|
for (const msgId of lane.folded.keys()) {
|
|
54661
55061
|
host.dispatchedMessages.delete(msgId);
|
|
54662
55062
|
}
|
|
@@ -54665,7 +55065,7 @@ async function dispatchLaneGroup(host, opts) {
|
|
|
54665
55065
|
} catch {
|
|
54666
55066
|
}
|
|
54667
55067
|
await ledger.completeIfIdle(lane.laneKey, false);
|
|
54668
|
-
return "failed";
|
|
55068
|
+
return settled.kind === "deferred" ? "deferred" : "failed";
|
|
54669
55069
|
}
|
|
54670
55070
|
const pendingInjections = host.opts.dispatchAdapter.hasPendingInjections?.(opts.sessionKey) ?? false;
|
|
54671
55071
|
await ledger.completeIfIdle(lane.laneKey, pendingInjections || opts.hasMoreLocal());
|
|
@@ -54866,6 +55266,8 @@ async function consumeMessageWorkItem(host, item) {
|
|
|
54866
55266
|
return;
|
|
54867
55267
|
if (!host.tryClaimMessage(item.source_id))
|
|
54868
55268
|
return;
|
|
55269
|
+
if (host.dispatchState.mainBuffer.some((event) => event.messageId === item.source_id))
|
|
55270
|
+
return;
|
|
54869
55271
|
const ackItem = () => {
|
|
54870
55272
|
if (host.laneLedger && !host.ledgerDisabled && !host.typedByIdCompleteUnsupported) {
|
|
54871
55273
|
void resolveDispatchByID(host, item.id).then((outcome) => {
|
|
@@ -55036,203 +55438,6 @@ function routeTrigger(event, state, strategy = defaultRoutingStrategy) {
|
|
|
55036
55438
|
return strategy(event, state);
|
|
55037
55439
|
}
|
|
55038
55440
|
|
|
55039
|
-
// ../agent-core/dist/event-format.js
|
|
55040
|
-
function sanitizeMeta(value) {
|
|
55041
|
-
return value.replace(/[\r\n]+/g, " ").replace(/[[\]|]/g, " ").trim();
|
|
55042
|
-
}
|
|
55043
|
-
function buildEventBody(event) {
|
|
55044
|
-
const lines = [];
|
|
55045
|
-
if (event.type === "message") {
|
|
55046
|
-
lines.push(`[Event: message.new]`);
|
|
55047
|
-
const chatLabel = event.targetName ? `"${event.targetName}" (prll://${event.targetId})` : `prll://${event.targetId}`;
|
|
55048
|
-
lines.push(`[Chat: ${chatLabel} | type: ${event.targetType ?? "unknown"}]`);
|
|
55049
|
-
lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
|
|
55050
|
-
lines.push(`[Message ID: prll://${event.messageId}]`);
|
|
55051
|
-
if (event.threadRootId) {
|
|
55052
|
-
const threadMeta = [
|
|
55053
|
-
`prll://${event.threadRootId}`,
|
|
55054
|
-
event.threadReplyCount != null ? `${event.threadReplyCount} replies` : null,
|
|
55055
|
-
event.threadUnreadCount != null && event.threadUnreadCount > 0 ? `${event.threadUnreadCount} unread` : null,
|
|
55056
|
-
event.threadUnreadCount != null && event.threadUnreadCount > 0 && event.threadUnreadSince ? `since: prll://${event.threadUnreadSince}` : null
|
|
55057
|
-
].filter(Boolean).join(" | ");
|
|
55058
|
-
lines.push(`[Thread: ${threadMeta}]`);
|
|
55059
|
-
}
|
|
55060
|
-
if (event.unreadCount != null && event.unreadCount > 1) {
|
|
55061
|
-
const countStr = event.unreadCount >= 1e3 ? "999+" : String(event.unreadCount);
|
|
55062
|
-
const sinceStr = event.unreadSince ? ` | since: prll://${event.unreadSince}` : "";
|
|
55063
|
-
let line = `[Unread: ${countStr} messages${sinceStr}]`;
|
|
55064
|
-
if (event.unreadCount > 50)
|
|
55065
|
-
line += ` \u2014 fetch recent context with --limit, not all`;
|
|
55066
|
-
lines.push(line);
|
|
55067
|
-
}
|
|
55068
|
-
if (event.noReply)
|
|
55069
|
-
lines.push(`[Hint: no_reply]`);
|
|
55070
|
-
if (event.attachments?.length) {
|
|
55071
|
-
for (const att of event.attachments) {
|
|
55072
|
-
const sizeStr = att.fileSize >= 1048576 ? `${(att.fileSize / 1048576).toFixed(1)}MB` : `${Math.round(att.fileSize / 1024)}KB`;
|
|
55073
|
-
lines.push(`[Attachment: prll://${att.id} | ${sanitizeMeta(att.mimeType)} | ${sizeStr} | ${sanitizeMeta(att.fileName)}]`);
|
|
55074
|
-
}
|
|
55075
|
-
}
|
|
55076
|
-
lines.push("", event.body);
|
|
55077
|
-
} else if (event.type === "task_comment") {
|
|
55078
|
-
lines.push(`[Event: task.comment.created]`);
|
|
55079
|
-
const taskLabel = event.targetName ? `${event.targetName} (prll://${event.targetId})` : `prll://${event.targetId}`;
|
|
55080
|
-
lines.push(`[Task: ${taskLabel}]`);
|
|
55081
|
-
if (event.deliveryReason)
|
|
55082
|
-
lines.push(`[Delivery: ${sanitizeMeta(event.deliveryReason)}]`);
|
|
55083
|
-
lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
|
|
55084
|
-
lines.push(`[Comment ID: prll://${event.messageId}]`);
|
|
55085
|
-
lines.push("", event.body);
|
|
55086
|
-
} else if (event.type === "wiki_comment") {
|
|
55087
|
-
lines.push(`[Event: wiki.comment.created]`);
|
|
55088
|
-
const target = event.replyTargetUri ?? `prll://${event.targetId}`;
|
|
55089
|
-
if (event.targetType === "changeset") {
|
|
55090
|
-
lines.push(`[Wiki Changeset: ${target}]`);
|
|
55091
|
-
} else {
|
|
55092
|
-
lines.push(`[Wiki: ${event.targetName ? `${sanitizeMeta(event.targetName)} (${target})` : target}]`);
|
|
55093
|
-
}
|
|
55094
|
-
if (event.deliveryReason)
|
|
55095
|
-
lines.push(`[Delivery: ${sanitizeMeta(event.deliveryReason)}]`);
|
|
55096
|
-
lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
|
|
55097
|
-
lines.push(`[Comment ID: prll://${event.messageId}]`);
|
|
55098
|
-
lines.push("", event.body);
|
|
55099
|
-
} else if (event.type === "approval") {
|
|
55100
|
-
lines.push(`[Event: approval.decided]`);
|
|
55101
|
-
lines.push(`[Approval: prll://${event.messageId}]`);
|
|
55102
|
-
lines.push(`[Chat: prll://${event.targetId}]`);
|
|
55103
|
-
lines.push(`[Decided by: ${event.senderName} (prll://${event.senderId})]`);
|
|
55104
|
-
lines.push("", event.body);
|
|
55105
|
-
} else if (event.type === "schedule") {
|
|
55106
|
-
lines.push(`[Event: schedule.fired]`);
|
|
55107
|
-
lines.push(`[Schedule: prll://${event.targetId}]`);
|
|
55108
|
-
lines.push(`[Run: prll://${event.messageId}]`);
|
|
55109
|
-
if (event.scheduledFireAt)
|
|
55110
|
-
lines.push(`[Scheduled at: ${sanitizeMeta(event.scheduledFireAt)}]`);
|
|
55111
|
-
if (event.attachedUri)
|
|
55112
|
-
lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
|
|
55113
|
-
lines.push("", event.body);
|
|
55114
|
-
} else if (event.type === "channel_message") {
|
|
55115
|
-
lines.push(`[Event: channel.message]`);
|
|
55116
|
-
const providerLabel = sanitizeMeta(event.channelProvider ?? "external IM");
|
|
55117
|
-
const convLabel = event.channelExternalConversationId ? `${sanitizeMeta(event.channelExternalConversationId)} (${sanitizeMeta(event.channelConversationType ?? "conversation")})` : sanitizeMeta(event.channelConversationType ?? "conversation");
|
|
55118
|
-
lines.push(`[Channel: ${providerLabel} | conversation: ${convLabel}]`);
|
|
55119
|
-
lines.push(`[From: ${sanitizeMeta(event.senderName)} (external user, not a Parall member)]`);
|
|
55120
|
-
if (event.channelExternalMessageId) {
|
|
55121
|
-
lines.push(`[External message ID: ${sanitizeMeta(event.channelExternalMessageId)}]`);
|
|
55122
|
-
}
|
|
55123
|
-
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.]`);
|
|
55124
|
-
lines.push("", event.body);
|
|
55125
|
-
} else if (event.type === "external_trigger") {
|
|
55126
|
-
lines.push(`[Event: external.trigger]`);
|
|
55127
|
-
lines.push(`[Trigger: prll://${event.targetId}]`);
|
|
55128
|
-
lines.push(`[Run: prll://${event.messageId}]`);
|
|
55129
|
-
if (event.externalConnectionId) {
|
|
55130
|
-
const label = event.externalConnectionDisplayName ? `${sanitizeMeta(event.externalConnectionDisplayName)} (prll://${event.externalConnectionId})` : `prll://${event.externalConnectionId}`;
|
|
55131
|
-
lines.push(`[Connection: ${label}]`);
|
|
55132
|
-
}
|
|
55133
|
-
if (event.externalIngressEventId)
|
|
55134
|
-
lines.push(`[Ingress: prll://${event.externalIngressEventId}]`);
|
|
55135
|
-
if (event.attachedUri)
|
|
55136
|
-
lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
|
|
55137
|
-
if (event.externalConnectionSourceType) {
|
|
55138
|
-
lines.push(`[Source: ${sanitizeMeta(event.externalConnectionSourceType)}]`);
|
|
55139
|
-
}
|
|
55140
|
-
if (event.externalIngressEventType) {
|
|
55141
|
-
lines.push(`[External event: ${sanitizeMeta(event.externalIngressEventType)}]`);
|
|
55142
|
-
}
|
|
55143
|
-
lines.push("", event.body);
|
|
55144
|
-
} else {
|
|
55145
|
-
lines.push(`[Event: task.assigned]`);
|
|
55146
|
-
const taskLabel = event.targetName ? `${event.targetName} (prll://${event.targetId})` : `prll://${event.targetId}`;
|
|
55147
|
-
lines.push(`[Task: ${taskLabel}]`);
|
|
55148
|
-
lines.push(`[Assigned by: ${event.senderName} (prll://${event.senderId})]`);
|
|
55149
|
-
lines.push("", event.body);
|
|
55150
|
-
}
|
|
55151
|
-
return lines.join("\n") + buildSendMessageHint(event);
|
|
55152
|
-
}
|
|
55153
|
-
function buildEventBodyForForkResult(event) {
|
|
55154
|
-
return buildEventBody(event).replace(/\n<system-reminder>[\s\S]*<\/system-reminder>$/, "");
|
|
55155
|
-
}
|
|
55156
|
-
function buildSendMessageHint(event) {
|
|
55157
|
-
if (event.noReply)
|
|
55158
|
-
return "";
|
|
55159
|
-
if (event.type === "wiki_comment" && event.replyTargetUri) {
|
|
55160
|
-
const where = event.targetType === "changeset" ? "this changeset comment" : "this wiki page";
|
|
55161
|
-
return `
|
|
55162
|
-
<system-reminder>To reply on ${where}, run: \`parall comments add --target "${event.replyTargetUri}" --body "..."\` (read the thread first with \`parall comments list --target "${event.replyTargetUri}"\`). To message someone instead, use \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
|
|
55163
|
-
}
|
|
55164
|
-
if (event.targetId.startsWith("cht_")) {
|
|
55165
|
-
if (event.threadRootId) {
|
|
55166
|
-
return `
|
|
55167
|
-
<system-reminder>To reply in this thread, run \`parall messages send prll://${event.targetId} --thread-root-id ${sanitizeMeta(event.threadRootId)} --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>`;
|
|
55168
|
-
}
|
|
55169
|
-
return `
|
|
55170
|
-
<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>`;
|
|
55171
|
-
}
|
|
55172
|
-
if (event.targetId.startsWith("tsk_")) {
|
|
55173
|
-
return `
|
|
55174
|
-
<system-reminder>To respond, use the CLI: \`parall tasks update\` / \`parall tasks comments add\`. To message someone, use \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
|
|
55175
|
-
}
|
|
55176
|
-
if (event.targetId.startsWith("sch_")) {
|
|
55177
|
-
return `
|
|
55178
|
-
<system-reminder>To communicate, use the CLI: \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
|
|
55179
|
-
}
|
|
55180
|
-
if (event.type === "channel_message") {
|
|
55181
|
-
if (event.channelCliCapable) {
|
|
55182
|
-
if (event.channelProvider === "slack") {
|
|
55183
|
-
const channelArg = event.channelExternalConversationId ? ` --channel "${event.channelExternalConversationId}"` : " --channel <conversation id from this event>";
|
|
55184
|
-
const replyTo = event.channelExternalMessageId ? ` --reply-to "${event.channelExternalMessageId}"` : "";
|
|
55185
|
-
return `
|
|
55186
|
-
<system-reminder>To reply, use the platform verb: \`parall slack send${channelArg}${replyTo} --text <your reply>\`. In channels --reply-to is REQUIRED (the reply lands in that message's thread); in DMs it is optional (DMs are linear). \`parall slack send\` is the ONLY outbound path \u2014 your plain text output is NOT delivered to the external conversation.</system-reminder>`;
|
|
55187
|
-
}
|
|
55188
|
-
if (!event.channelProvider) {
|
|
55189
|
-
return `
|
|
55190
|
-
<system-reminder>To reply, use the channel capability granted in your system prompt \u2014 for Feishu conversations that is \`lark-cli im\`, for Slack it is \`parall slack send\` (pass the message id from this event as --reply-to). That capability is the ONLY outbound path \u2014 your plain text output is NOT delivered to the external conversation.</system-reminder>`;
|
|
55191
|
-
}
|
|
55192
|
-
const convRef = event.channelExternalConversationId ? `chat_id "${event.channelExternalConversationId}"` : "the conversation id named in this event";
|
|
55193
|
-
const threadAlt = event.channelExternalMessageId ? ` To reply threaded to this specific message, reference message_id "${event.channelExternalMessageId}".` : "";
|
|
55194
|
-
return `
|
|
55195
|
-
<system-reminder>To reply, use the official Feishu CLI on your PATH: send a message to ${convRef} with \`lark-cli im\` (see \`lark-cli im --help\` for send syntax; auth is provisioned automatically).${threadAlt} lark-cli is the ONLY outbound path \u2014 your plain text output is NOT delivered to the external conversation.</system-reminder>`;
|
|
55196
|
-
}
|
|
55197
|
-
const platform = event.channelProvider ?? "the external platform";
|
|
55198
|
-
return `
|
|
55199
|
-
<system-reminder>This message arrived from ${platform}, but outbound replies are currently disabled for this org (no channel capability granted). Do NOT attempt to reply on the external platform. If action is needed, surface it inside Parall (\`parall messages send\` / \`parall dm\`). Your plain text output is not delivered anywhere.</system-reminder>`;
|
|
55200
|
-
}
|
|
55201
|
-
if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
|
|
55202
|
-
return `
|
|
55203
|
-
<system-reminder>This external trigger is incoming-only. Your plain text output is not sent back to the external provider. To communicate in Parall, use \`parall messages send\` / \`parall dm\`; provider-specific outbound actions require a separate capability.</system-reminder>`;
|
|
55204
|
-
}
|
|
55205
|
-
return "";
|
|
55206
|
-
}
|
|
55207
|
-
function buildForkScopePrefix(event) {
|
|
55208
|
-
const targetLabel = event.targetName ? `"${event.targetName}" (prll://${event.targetId})` : `prll://${event.targetId}`;
|
|
55209
|
-
return `[Fork scope: You are a parallel worker forked from the main session to handle ${targetLabel}. The conversation history above belongs to the main session's in-progress dispatch \u2014 do NOT continue it or complete it, or produce external output (comments, messages) for work visible there. The main session owns its own output.
|
|
55210
|
-
|
|
55211
|
-
Only act on the event below within your target scope. If your event reveals findings about the main session's work, report them in your summary \u2014 do not post them externally.
|
|
55212
|
-
|
|
55213
|
-
End with a brief "Fork summary" of actions taken and decisions made.]
|
|
55214
|
-
|
|
55215
|
-
`;
|
|
55216
|
-
}
|
|
55217
|
-
function buildForkResultPrefix(results) {
|
|
55218
|
-
if (!results.length)
|
|
55219
|
-
return "";
|
|
55220
|
-
const blocks = results.map((result) => {
|
|
55221
|
-
const lines = [];
|
|
55222
|
-
for (const body of result.eventBodies) {
|
|
55223
|
-
lines.push(body);
|
|
55224
|
-
}
|
|
55225
|
-
lines.push(`[This event was handled by a parallel fork session. Do NOT re-handle, re-reply, or duplicate work for it.]`);
|
|
55226
|
-
lines.push(`[Fork summary: ${result.agentSummary ? sanitizeMeta(result.agentSummary) : "No fork summary available \u2014 the fork completed without producing a text summary. Check the target chat/task for any actions the fork already took before acting."}]`);
|
|
55227
|
-
if (result.actions.length)
|
|
55228
|
-
lines.push(`[Fork actions: ${result.actions.join("; ")}]`);
|
|
55229
|
-
if (result.historyPath)
|
|
55230
|
-
lines.push(`[Fork history: ${result.historyPath}]`);
|
|
55231
|
-
return lines.join("\n");
|
|
55232
|
-
});
|
|
55233
|
-
return blocks.join("\n\n") + "\n\n---\n\n";
|
|
55234
|
-
}
|
|
55235
|
-
|
|
55236
55441
|
// ../agent-core/dist/prompt-fragments.js
|
|
55237
55442
|
function identityFromMe(me) {
|
|
55238
55443
|
return {
|
|
@@ -55424,6 +55629,22 @@ All three forms work \u2014 pick whichever fits:
|
|
|
55424
55629
|
Bare URIs and empty-context refs are preferred in most cases \u2014 the platform
|
|
55425
55630
|
resolves and renders the entity title automatically.
|
|
55426
55631
|
|
|
55632
|
+
### Mentioning people and agents
|
|
55633
|
+
|
|
55634
|
+
A real member mention is a \`prll://usr_...\` reference. Plain \`@Display Name\` is
|
|
55635
|
+
only text: it does not notify a human or trigger an agent.
|
|
55636
|
+
|
|
55637
|
+
When another member must be notified or an agent explicitly triggered, include
|
|
55638
|
+
their user reference in the message body. Prefer the empty-context form because
|
|
55639
|
+
the platform resolves the member's current display name:
|
|
55640
|
+
|
|
55641
|
+
[](prll://usr_xxx)
|
|
55642
|
+
|
|
55643
|
+
Use \`[Display Name](prll://usr_xxx)\` when the surrounding sentence needs an
|
|
55644
|
+
explicit label. Find the user ID in the incoming message or with
|
|
55645
|
+
\`parall members list\`. Never substitute plain \`@Display Name\` when notification
|
|
55646
|
+
or agent dispatch matters.
|
|
55647
|
+
|
|
55427
55648
|
### URI format
|
|
55428
55649
|
|
|
55429
55650
|
\`prll://\` follows standard URI structure: \`scheme://authority/path?query#fragment\`.
|
|
@@ -55619,6 +55840,28 @@ function buildErrorStepContent(message) {
|
|
|
55619
55840
|
return { text: message, suppressed: false, status: "error" };
|
|
55620
55841
|
}
|
|
55621
55842
|
|
|
55843
|
+
// ../agent-core/dist/redact.js
|
|
55844
|
+
function redactSecrets(s, knownValues = []) {
|
|
55845
|
+
let out = s;
|
|
55846
|
+
for (const v of knownValues) {
|
|
55847
|
+
if (typeof v === "string" && v.length >= 6)
|
|
55848
|
+
out = out.split(v).join("***");
|
|
55849
|
+
}
|
|
55850
|
+
return out.replace(/\b(agk|mck|cpk)_[A-Za-z0-9_-]+/g, "$1_***").replace(/\b(sk|pk|rk)-[A-Za-z0-9_-]{8,}/g, "$1-***").replace(/\bAKIA[0-9A-Z]{16}\b/g, "AKIA***").replace(/\b(bearer\s+)[A-Za-z0-9._~+/=-]{8,}/gi, "$1***").replace(/[A-Za-z0-9_-]{32,}/g, "***");
|
|
55851
|
+
}
|
|
55852
|
+
function redactTurnOutcome(event, knownValues) {
|
|
55853
|
+
const redacted = { ...event };
|
|
55854
|
+
if (redacted.detail)
|
|
55855
|
+
redacted.detail = redactSecrets(redacted.detail, knownValues);
|
|
55856
|
+
if (redacted.raw) {
|
|
55857
|
+
redacted.raw = Object.fromEntries(Object.entries(redacted.raw).map(([k, v]) => [
|
|
55858
|
+
k,
|
|
55859
|
+
typeof v === "string" ? redactSecrets(v, knownValues) : v
|
|
55860
|
+
]));
|
|
55861
|
+
}
|
|
55862
|
+
return redacted;
|
|
55863
|
+
}
|
|
55864
|
+
|
|
55622
55865
|
// ../agent-core/dist/gateway-base.js
|
|
55623
55866
|
import * as os from "node:os";
|
|
55624
55867
|
import * as fs3 from "node:fs";
|
|
@@ -56480,6 +56723,8 @@ var tracer = null;
|
|
|
56480
56723
|
var dispatchCounter = null;
|
|
56481
56724
|
var dispatchDuration = null;
|
|
56482
56725
|
var missingReplyCounter = null;
|
|
56726
|
+
var turnTokensCounter = null;
|
|
56727
|
+
var turnCostCounter = null;
|
|
56483
56728
|
var otelLogger = null;
|
|
56484
56729
|
function resolveTargetType(targetId) {
|
|
56485
56730
|
if (targetId.startsWith("cht_"))
|
|
@@ -56554,6 +56799,12 @@ async function initAgentTelemetry(serviceName, runtimeType) {
|
|
|
56554
56799
|
missingReplyCounter = meter.createCounter("parall.dispatch.missing_reply", {
|
|
56555
56800
|
description: "Dispatches where agent produced text but sent no reply message"
|
|
56556
56801
|
});
|
|
56802
|
+
turnTokensCounter = meter.createCounter("parall.turn.tokens", {
|
|
56803
|
+
description: "LLM tokens consumed per turn, by kind (input/output/cache_read/cache_creation)"
|
|
56804
|
+
});
|
|
56805
|
+
turnCostCounter = meter.createCounter("parall.turn.cost_usd", {
|
|
56806
|
+
description: "LLM cost per turn in USD (when the runtime reports it)"
|
|
56807
|
+
});
|
|
56557
56808
|
initialized = true;
|
|
56558
56809
|
shutdownFn = async () => {
|
|
56559
56810
|
await tracerProvider.forceFlush();
|
|
@@ -56587,7 +56838,7 @@ function startDispatchSpan(event, runtimeType, sessionKey) {
|
|
|
56587
56838
|
}
|
|
56588
56839
|
});
|
|
56589
56840
|
}
|
|
56590
|
-
function endDispatchSpan(span, metricsSnapshot, error) {
|
|
56841
|
+
function endDispatchSpan(span, metricsSnapshot, error, turnOutcome) {
|
|
56591
56842
|
if (!span)
|
|
56592
56843
|
return;
|
|
56593
56844
|
if (metricsSnapshot) {
|
|
@@ -56601,27 +56852,77 @@ function endDispatchSpan(span, metricsSnapshot, error) {
|
|
|
56601
56852
|
"dispatch.duration_ms": Date.now() - metricsSnapshot.started_at
|
|
56602
56853
|
});
|
|
56603
56854
|
}
|
|
56855
|
+
if (turnOutcome) {
|
|
56856
|
+
span.setAttribute("dispatch.outcome", turnOutcome.outcome);
|
|
56857
|
+
if (turnOutcome.detail)
|
|
56858
|
+
span.setAttribute("dispatch.outcome_detail", turnOutcome.detail);
|
|
56859
|
+
if (turnOutcome.retryAt)
|
|
56860
|
+
span.setAttribute("dispatch.retry_at", turnOutcome.retryAt);
|
|
56861
|
+
if (turnOutcome.model)
|
|
56862
|
+
span.setAttribute("dispatch.model", turnOutcome.model);
|
|
56863
|
+
if (turnOutcome.raw && Object.keys(turnOutcome.raw).length > 0) {
|
|
56864
|
+
try {
|
|
56865
|
+
span.setAttribute("dispatch.outcome_raw", JSON.stringify(turnOutcome.raw));
|
|
56866
|
+
} catch {
|
|
56867
|
+
}
|
|
56868
|
+
}
|
|
56869
|
+
const u = turnOutcome.usage;
|
|
56870
|
+
if (u) {
|
|
56871
|
+
if (u.inputTokens !== void 0)
|
|
56872
|
+
span.setAttribute("dispatch.tokens_input", u.inputTokens);
|
|
56873
|
+
if (u.outputTokens !== void 0)
|
|
56874
|
+
span.setAttribute("dispatch.tokens_output", u.outputTokens);
|
|
56875
|
+
if (u.cacheReadTokens !== void 0)
|
|
56876
|
+
span.setAttribute("dispatch.tokens_cache_read", u.cacheReadTokens);
|
|
56877
|
+
if (u.cacheCreationTokens !== void 0)
|
|
56878
|
+
span.setAttribute("dispatch.tokens_cache_creation", u.cacheCreationTokens);
|
|
56879
|
+
if (u.costUsd !== void 0)
|
|
56880
|
+
span.setAttribute("dispatch.cost_usd", u.costUsd);
|
|
56881
|
+
if (u.durationApiMs !== void 0)
|
|
56882
|
+
span.setAttribute("dispatch.duration_api_ms", u.durationApiMs);
|
|
56883
|
+
}
|
|
56884
|
+
}
|
|
56604
56885
|
if (error) {
|
|
56605
|
-
|
|
56606
|
-
span.
|
|
56886
|
+
const safe = redactSecrets(String(error));
|
|
56887
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: safe });
|
|
56888
|
+
span.recordException(error instanceof Error ? new Error(safe) : new Error(safe));
|
|
56607
56889
|
}
|
|
56608
56890
|
span.end();
|
|
56609
56891
|
}
|
|
56610
|
-
function recordDispatchMetric(event, runtimeType, durationMs) {
|
|
56892
|
+
function recordDispatchMetric(event, runtimeType, durationMs, outcome = "ok") {
|
|
56611
56893
|
if (!initialized)
|
|
56612
56894
|
return;
|
|
56613
56895
|
const attrs = {
|
|
56614
56896
|
target_type: resolveTargetType(event.targetId),
|
|
56615
56897
|
event_type: event.type,
|
|
56616
|
-
runtime_type: runtimeType
|
|
56898
|
+
runtime_type: runtimeType,
|
|
56899
|
+
outcome
|
|
56617
56900
|
};
|
|
56618
56901
|
dispatchCounter?.add(1, attrs);
|
|
56619
56902
|
dispatchDuration?.record(durationMs, attrs);
|
|
56620
56903
|
}
|
|
56621
|
-
function recordMissingReply(runtimeType) {
|
|
56904
|
+
function recordMissingReply(runtimeType, outcome = "ok") {
|
|
56622
56905
|
if (!initialized)
|
|
56623
56906
|
return;
|
|
56624
|
-
missingReplyCounter?.add(1, { runtime_type: runtimeType });
|
|
56907
|
+
missingReplyCounter?.add(1, { runtime_type: runtimeType, outcome });
|
|
56908
|
+
}
|
|
56909
|
+
function recordTurnUsage(usage, runtimeType) {
|
|
56910
|
+
if (!initialized || !usage)
|
|
56911
|
+
return;
|
|
56912
|
+
const kinds = [
|
|
56913
|
+
["input", usage.inputTokens],
|
|
56914
|
+
["output", usage.outputTokens],
|
|
56915
|
+
["cache_read", usage.cacheReadTokens],
|
|
56916
|
+
["cache_creation", usage.cacheCreationTokens]
|
|
56917
|
+
];
|
|
56918
|
+
for (const [kind, value] of kinds) {
|
|
56919
|
+
if (value !== void 0 && value > 0) {
|
|
56920
|
+
turnTokensCounter?.add(value, { kind, runtime_type: runtimeType });
|
|
56921
|
+
}
|
|
56922
|
+
}
|
|
56923
|
+
if (usage.costUsd !== void 0 && usage.costUsd > 0) {
|
|
56924
|
+
turnCostCounter?.add(usage.costUsd, { runtime_type: runtimeType });
|
|
56925
|
+
}
|
|
56625
56926
|
}
|
|
56626
56927
|
var sessionKeyStorage = new AsyncLocalStorage();
|
|
56627
56928
|
function runWithSessionKey(sessionKey, fn) {
|
|
@@ -56821,7 +57122,9 @@ var ParallAgentGateway = class {
|
|
|
56821
57122
|
client: opts.client,
|
|
56822
57123
|
orgId: opts.config.org_id,
|
|
56823
57124
|
contextDir: opts.dispatchContextDir,
|
|
56824
|
-
log: opts.log
|
|
57125
|
+
log: opts.log,
|
|
57126
|
+
coverageMode: opts.dispatchAdapter.inputLifecycleMode ?? "implicit",
|
|
57127
|
+
releaseLocalClaims: (sourceIds) => releaseLocalMessageClaims(this.dispatchedMessages, sourceIds)
|
|
56825
57128
|
});
|
|
56826
57129
|
}
|
|
56827
57130
|
this.SHUTDOWN_DEADLINE_MS = opts.shutdownDeadlineMs ?? 6e4;
|
|
@@ -57095,17 +57398,49 @@ var ParallAgentGateway = class {
|
|
|
57095
57398
|
if (laneKey)
|
|
57096
57399
|
this.laneLedger?.renewByKey(laneKey);
|
|
57097
57400
|
}
|
|
57098
|
-
/** Sessions whose in-flight turn surfaced a runtime error event. */
|
|
57099
|
-
turnErrorSessions = /* @__PURE__ */ new Set();
|
|
57100
57401
|
/**
|
|
57101
|
-
*
|
|
57402
|
+
* Settled LLM-layer outcome of each session's last turn — an error to
|
|
57403
|
+
* release lane members on the redrive budget, or a deferred usage-limit
|
|
57404
|
+
* wait (agent-turn-outcome-design.md). Absent = clean turn.
|
|
57405
|
+
*/
|
|
57406
|
+
turnOutcomes = /* @__PURE__ */ new Map();
|
|
57407
|
+
/**
|
|
57408
|
+
* Consume (read-and-clear) the settled outcome for sessionKey's last turn.
|
|
57102
57409
|
* Feeds complete's turn_outcome so an error turn's lane members are
|
|
57103
|
-
* released for retry
|
|
57104
|
-
*
|
|
57105
|
-
*
|
|
57410
|
+
* released for retry (and a deferred turn's members re-deliver at retryAt)
|
|
57411
|
+
* instead of being no_action-swept (design §3). Consuming (rather than
|
|
57412
|
+
* peeking) keeps one-shot fork session keys from accumulating forever.
|
|
57413
|
+
*/
|
|
57414
|
+
consumeTurnOutcome(sessionKey) {
|
|
57415
|
+
const outcome = this.turnOutcomes.get(sessionKey);
|
|
57416
|
+
this.turnOutcomes.delete(sessionKey);
|
|
57417
|
+
return outcome;
|
|
57418
|
+
}
|
|
57419
|
+
/**
|
|
57420
|
+
* Boolean view of consumeTurnOutcome for the typed/fork call sites, which
|
|
57421
|
+
* have no deferred semantics: ANY non-clean outcome (error or deferred)
|
|
57422
|
+
* counts as an errored turn there — release-for-retry beats a false
|
|
57423
|
+
* "handled".
|
|
57106
57424
|
*/
|
|
57107
57425
|
consumeTurnError(sessionKey) {
|
|
57108
|
-
return this.
|
|
57426
|
+
return this.consumeTurnOutcome(sessionKey) !== void 0;
|
|
57427
|
+
}
|
|
57428
|
+
/**
|
|
57429
|
+
* Fold turn signals into the session's settled outcome. Two sources, one
|
|
57430
|
+
* precedence rule: the bridge's explicit turn_outcome classification always
|
|
57431
|
+
* wins (it may refine the SAME failure a generic `error` event already
|
|
57432
|
+
* reported — e.g. Claude's result frame yields both), while a bare `error`
|
|
57433
|
+
* event only fills the slot when no classification exists. Wire order makes
|
|
57434
|
+
* this safe: every bridge emits its error events before the turn-boundary
|
|
57435
|
+
* turn_outcome.
|
|
57436
|
+
*/
|
|
57437
|
+
recordTurnErrorSignal(sessionKey) {
|
|
57438
|
+
if (!this.turnOutcomes.has(sessionKey)) {
|
|
57439
|
+
this.turnOutcomes.set(sessionKey, { kind: "error" });
|
|
57440
|
+
}
|
|
57441
|
+
}
|
|
57442
|
+
recordTurnClassification(sessionKey, next) {
|
|
57443
|
+
this.turnOutcomes.set(sessionKey, next);
|
|
57109
57444
|
}
|
|
57110
57445
|
async emitDispatchReceived(event) {
|
|
57111
57446
|
const sourceType = event.ackSourceType ?? (event.type === "task" ? "task_activity" : "message");
|
|
@@ -57275,7 +57610,14 @@ var ParallAgentGateway = class {
|
|
|
57275
57610
|
channel_message_id: event.messageId,
|
|
57276
57611
|
provider: event.channelProvider,
|
|
57277
57612
|
external_conversation_id: event.channelExternalConversationId
|
|
57278
|
-
} : event.type === "approval" ? { approval_id: event.messageId } : {
|
|
57613
|
+
} : event.type === "approval" ? { approval_id: event.messageId } : {
|
|
57614
|
+
message_id: event.messageId,
|
|
57615
|
+
// Thread context for the session→chat backlink:
|
|
57616
|
+
// a thread reply can only be navigated to with
|
|
57617
|
+
// its thread root (the chat surface rejects
|
|
57618
|
+
// bare thread-message targets).
|
|
57619
|
+
...event.threadRootId ? { thread_root_id: event.threadRootId } : {}
|
|
57620
|
+
},
|
|
57279
57621
|
sender_id: event.senderId,
|
|
57280
57622
|
sender_name: event.senderName,
|
|
57281
57623
|
summary: event.body.substring(0, 200),
|
|
@@ -57482,7 +57824,7 @@ var ParallAgentGateway = class {
|
|
|
57482
57824
|
// are shutting down. Callers MUST treat `false` as "not dispatched" and
|
|
57483
57825
|
// skip the server-side ack so the event stays in the dispatch queue for
|
|
57484
57826
|
// catch-up on the replacement pod — otherwise we silently drop work.
|
|
57485
|
-
async runDispatch(event, sessionKey, bodyForAgent, earlierEvents = [], captureText) {
|
|
57827
|
+
async runDispatch(event, sessionKey, bodyForAgent, earlierEvents = [], captureText, inputLifecycle) {
|
|
57486
57828
|
if (this.shuttingDown) {
|
|
57487
57829
|
this.opts.log?.info(`skipping dispatch for ${event.messageId} (shutting down) \u2014 leaving unacked for catch-up on replacement pod`);
|
|
57488
57830
|
return false;
|
|
@@ -57492,7 +57834,7 @@ var ParallAgentGateway = class {
|
|
|
57492
57834
|
this.pendingRestartNotification = null;
|
|
57493
57835
|
}
|
|
57494
57836
|
resetDispatchMetrics(sessionKey);
|
|
57495
|
-
this.
|
|
57837
|
+
this.turnOutcomes.delete(sessionKey);
|
|
57496
57838
|
return runWithSessionKey(sessionKey, async () => {
|
|
57497
57839
|
let dispatchSpan = null;
|
|
57498
57840
|
setSessionChatId(sessionKey, event.targetId);
|
|
@@ -57537,6 +57879,8 @@ var ParallAgentGateway = class {
|
|
|
57537
57879
|
let inputStepsCreated = false;
|
|
57538
57880
|
let turnHandle;
|
|
57539
57881
|
let dispatchError;
|
|
57882
|
+
let turnOutcomeEvent;
|
|
57883
|
+
let sawErrorEvent = false;
|
|
57540
57884
|
const pendingSendCallIds = /* @__PURE__ */ new Set();
|
|
57541
57885
|
const ensureTurnBegun = async () => {
|
|
57542
57886
|
if (turnHandle || !binding)
|
|
@@ -57550,11 +57894,15 @@ var ParallAgentGateway = class {
|
|
|
57550
57894
|
earlierEvents,
|
|
57551
57895
|
bodyForAgent,
|
|
57552
57896
|
sessionKey,
|
|
57553
|
-
context: dispatchContext
|
|
57897
|
+
context: dispatchContext,
|
|
57898
|
+
inputLifecycle
|
|
57554
57899
|
})) {
|
|
57555
57900
|
if (runtimeEvent.type === "runtime_session") {
|
|
57556
57901
|
const priorAgentSessionId = binding?.agentSessionId;
|
|
57557
57902
|
binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2);
|
|
57903
|
+
if (activeLane) {
|
|
57904
|
+
bindLaneSession(activeLane, binding.agentSessionId);
|
|
57905
|
+
}
|
|
57558
57906
|
if (event.targetType === "channel_conversation" && binding.agentSessionId !== priorAgentSessionId) {
|
|
57559
57907
|
try {
|
|
57560
57908
|
await this.opts.client.setChannelConversationSession(this.opts.config.org_id, event.targetId, binding.agentSessionId);
|
|
@@ -57572,6 +57920,36 @@ var ParallAgentGateway = class {
|
|
|
57572
57920
|
}
|
|
57573
57921
|
continue;
|
|
57574
57922
|
}
|
|
57923
|
+
if (runtimeEvent.type === "turn_outcome") {
|
|
57924
|
+
const outcomeEvent = redactTurnOutcome(runtimeEvent, [this.opts.config.api_key]);
|
|
57925
|
+
turnOutcomeEvent = outcomeEvent;
|
|
57926
|
+
if (outcomeEvent.outcome === "ok") {
|
|
57927
|
+
this.turnOutcomes.delete(sessionKey);
|
|
57928
|
+
continue;
|
|
57929
|
+
}
|
|
57930
|
+
this.recordTurnClassification(sessionKey, outcomeEvent.outcome === "usage_limit" ? {
|
|
57931
|
+
kind: "deferred",
|
|
57932
|
+
outcomeClass: outcomeEvent.outcome,
|
|
57933
|
+
...outcomeEvent.retryAt ? { retryAt: outcomeEvent.retryAt } : {}
|
|
57934
|
+
} : { kind: "error", outcomeClass: outcomeEvent.outcome });
|
|
57935
|
+
const retryNote = outcomeEvent.retryAt ? `, retry at ${outcomeEvent.retryAt}` : "";
|
|
57936
|
+
this.opts.log?.warn(`turn outcome: ${outcomeEvent.outcome}${retryNote}${outcomeEvent.detail ? ` \u2014 ${outcomeEvent.detail}` : ""}`);
|
|
57937
|
+
if (binding) {
|
|
57938
|
+
await ensureTurnBegun();
|
|
57939
|
+
if (!inputStepsCreated) {
|
|
57940
|
+
if (earlierEvents.length > 0) {
|
|
57941
|
+
await this.createInputStepsForEarlierEvents(binding.agentSessionId, earlierEvents);
|
|
57942
|
+
}
|
|
57943
|
+
await this.createInputStep(binding.agentSessionId, event);
|
|
57944
|
+
inputStepsCreated = true;
|
|
57945
|
+
}
|
|
57946
|
+
await this.createRuntimeStep(binding.agentSessionId, event, {
|
|
57947
|
+
type: "error",
|
|
57948
|
+
message: `LLM turn ${outcomeEvent.outcome}${retryNote}${outcomeEvent.detail ? `: ${outcomeEvent.detail}` : ""}`
|
|
57949
|
+
}, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
57950
|
+
}
|
|
57951
|
+
continue;
|
|
57952
|
+
}
|
|
57575
57953
|
if (!binding) {
|
|
57576
57954
|
const detail = runtimeEvent.type === "error" ? `: ${runtimeEvent.message}` : "";
|
|
57577
57955
|
throw new Error(`runtime emitted ${runtimeEvent.type} before runtime_session${detail}`);
|
|
@@ -57604,7 +57982,8 @@ var ParallAgentGateway = class {
|
|
|
57604
57982
|
recordMessageSend(sessionKey, !runtimeEvent.error);
|
|
57605
57983
|
}
|
|
57606
57984
|
if (runtimeEvent.type === "error") {
|
|
57607
|
-
|
|
57985
|
+
sawErrorEvent = true;
|
|
57986
|
+
this.recordTurnErrorSignal(sessionKey);
|
|
57608
57987
|
}
|
|
57609
57988
|
await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
57610
57989
|
}
|
|
@@ -57658,10 +58037,12 @@ var ParallAgentGateway = class {
|
|
|
57658
58037
|
clearTimeout(deadlineTimer);
|
|
57659
58038
|
const metricsSnapshot = getDispatchMetrics(sessionKey);
|
|
57660
58039
|
const durationMs = metricsSnapshot ? Date.now() - metricsSnapshot.started_at : 0;
|
|
57661
|
-
|
|
57662
|
-
|
|
58040
|
+
const effectiveOutcome = turnOutcomeEvent?.outcome ?? (dispatchError || sawErrorEvent ? "runtime_crash" : "ok");
|
|
58041
|
+
endDispatchSpan(dispatchSpan, metricsSnapshot, dispatchError, turnOutcomeEvent);
|
|
58042
|
+
recordDispatchMetric(event, this.opts.runtimeType, durationMs, effectiveOutcome);
|
|
58043
|
+
recordTurnUsage(turnOutcomeEvent?.usage, this.opts.runtimeType);
|
|
57663
58044
|
if (metricsSnapshot && !dispatchError && event.type === "message" && event.targetId?.startsWith("cht_") && !event.noReply && metricsSnapshot.deliver_text_chunks > 0 && metricsSnapshot.message_send_successes === 0 && !metricsSnapshot.no_reply_called) {
|
|
57664
|
-
recordMissingReply(this.opts.runtimeType);
|
|
58045
|
+
recordMissingReply(this.opts.runtimeType, effectiveOutcome);
|
|
57665
58046
|
}
|
|
57666
58047
|
clearDispatchMetrics(sessionKey);
|
|
57667
58048
|
if (turnHandle) {
|
|
@@ -57769,7 +58150,7 @@ var ParallAgentGateway = class {
|
|
|
57769
58150
|
item.resolve(false);
|
|
57770
58151
|
break;
|
|
57771
58152
|
}
|
|
57772
|
-
if (outcome === "failed") {
|
|
58153
|
+
if (outcome === "failed" || outcome === "deferred") {
|
|
57773
58154
|
for (const item of items)
|
|
57774
58155
|
item.resolve(false);
|
|
57775
58156
|
continue;
|
|
@@ -57813,7 +58194,7 @@ var ParallAgentGateway = class {
|
|
|
57813
58194
|
remaining.resolve(false);
|
|
57814
58195
|
}
|
|
57815
58196
|
} finally {
|
|
57816
|
-
this.
|
|
58197
|
+
this.turnOutcomes.delete(fork.fork.sessionKey);
|
|
57817
58198
|
if (fork.deadlineTimer) {
|
|
57818
58199
|
clearTimeout(fork.deadlineTimer);
|
|
57819
58200
|
fork.deadlineTimer = null;
|
|
@@ -57928,7 +58309,7 @@ var ParallAgentGateway = class {
|
|
|
57928
58309
|
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
57929
58310
|
continue;
|
|
57930
58311
|
}
|
|
57931
|
-
if (outcome === "failed") {
|
|
58312
|
+
if (outcome === "failed" || outcome === "deferred") {
|
|
57932
58313
|
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
57933
58314
|
continue;
|
|
57934
58315
|
}
|
|
@@ -58075,8 +58456,8 @@ var ParallAgentGateway = class {
|
|
|
58075
58456
|
this.dispatchState.mainBuffer.push(event);
|
|
58076
58457
|
const typedAheadInBuffer = this.dispatchState.mainBuffer.some((e) => e.type !== "message");
|
|
58077
58458
|
if (this.usesLaneLedger(event)) {
|
|
58078
|
-
if (!typedAheadInBuffer && this.mainCurrentGroupKey === this.dispatchGroupKey(event) && this.opts.dispatchAdapter.enqueueDuringDispatch != null
|
|
58079
|
-
this.
|
|
58459
|
+
if (!typedAheadInBuffer && this.mainCurrentGroupKey === this.dispatchGroupKey(event) && this.opts.dispatchAdapter.enqueueDuringDispatch != null) {
|
|
58460
|
+
await steerLaneMessage(this.laneFlowHost(), event);
|
|
58080
58461
|
}
|
|
58081
58462
|
} else if (
|
|
58082
58463
|
// Message events only. A typed event (task_comment/schedule/…)
|
|
@@ -60406,6 +60787,24 @@ ${event.body}` : event.body
|
|
|
60406
60787
|
};
|
|
60407
60788
|
});
|
|
60408
60789
|
}
|
|
60790
|
+
var OC_AUTH_TEXT = /unauthorized|401|403|invalid api key|authentication/i;
|
|
60791
|
+
var OC_CONTEXT_TEXT = /context (window|length)|prompt is too long|request too large|413/i;
|
|
60792
|
+
var OC_API_TEXT = /overloaded|429|rate limit|5\d\d|bad gateway|service unavailable|timeout/i;
|
|
60793
|
+
function classifyOpenClawFailure(err) {
|
|
60794
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
60795
|
+
let outcome = "runtime_crash";
|
|
60796
|
+
if (OC_AUTH_TEXT.test(message))
|
|
60797
|
+
outcome = "auth";
|
|
60798
|
+
else if (OC_CONTEXT_TEXT.test(message))
|
|
60799
|
+
outcome = "context_overflow";
|
|
60800
|
+
else if (OC_API_TEXT.test(message))
|
|
60801
|
+
outcome = "api_error";
|
|
60802
|
+
return {
|
|
60803
|
+
type: "turn_outcome",
|
|
60804
|
+
outcome,
|
|
60805
|
+
...message ? { detail: message.slice(0, 500) } : {}
|
|
60806
|
+
};
|
|
60807
|
+
}
|
|
60409
60808
|
function createRuntimeEventStream() {
|
|
60410
60809
|
const queue = [];
|
|
60411
60810
|
const waiters = [];
|
|
@@ -60560,7 +60959,10 @@ function createOpenClawDispatchAdapter(opts) {
|
|
|
60560
60959
|
}
|
|
60561
60960
|
}
|
|
60562
60961
|
});
|
|
60563
|
-
run.then(() => stream.end()).catch((err) =>
|
|
60962
|
+
run.then(() => stream.end()).catch((err) => {
|
|
60963
|
+
stream.push(classifyOpenClawFailure(err));
|
|
60964
|
+
stream.fail(err);
|
|
60965
|
+
});
|
|
60564
60966
|
let sessionId;
|
|
60565
60967
|
try {
|
|
60566
60968
|
sessionId = await Promise.race([
|