@parall/parall 1.50.1 → 1.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/gateway.d.ts.map +1 -1
- package/dist/gateway.js +37 -1
- package/dist/index.bundle.mjs +658 -249
- 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`,
|
|
@@ -51628,6 +51629,9 @@ var ENDPOINTS = {
|
|
|
51628
51629
|
TEMPLATE: (orgId, templateId) => `${API_BASE}/orgs/${orgId}/templates/${templateId}`,
|
|
51629
51630
|
TEMPLATE_DEPLOYMENTS: (orgId) => `${API_BASE}/orgs/${orgId}/template-deployments`,
|
|
51630
51631
|
TEMPLATE_DEPLOYMENT: (orgId, deploymentId) => `${API_BASE}/orgs/${orgId}/template-deployments/${deploymentId}`,
|
|
51632
|
+
CLIP_DEPENDENCIES_CHECK: (orgId) => `${API_BASE}/orgs/${orgId}/clip-dependencies/check`,
|
|
51633
|
+
// Onboarding wizard progress (org-scoped, self)
|
|
51634
|
+
ONBOARDING_PROGRESS: (orgId) => `${API_BASE}/orgs/${orgId}/onboarding`,
|
|
51631
51635
|
// Billing & Credits (org-scoped)
|
|
51632
51636
|
BILLING: (orgId) => `${API_BASE}/orgs/${orgId}/billing`,
|
|
51633
51637
|
BILLING_TRANSACTIONS: (orgId) => `${API_BASE}/orgs/${orgId}/billing/transactions`,
|
|
@@ -51688,7 +51692,11 @@ var ENDPOINTS = {
|
|
|
51688
51692
|
// Pinix Hub catalog proxy above is a different, id-less surface)
|
|
51689
51693
|
ORG_CLIP_REGISTRY: (orgId) => `/api/v1/orgs/${orgId}/clip-registry`,
|
|
51690
51694
|
ORG_CLIP_INSTALL: (orgId) => `/api/v1/orgs/${orgId}/clips/install`,
|
|
51691
|
-
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`
|
|
51692
51700
|
};
|
|
51693
51701
|
var WS_EVENTS = {
|
|
51694
51702
|
// Client -> Server
|
|
@@ -52182,8 +52190,10 @@ var ParallClient = class _ParallClient {
|
|
|
52182
52190
|
q.limit = String(params.limit);
|
|
52183
52191
|
return this.request("GET", ENDPOINTS.ORG_MEMBER_CHATS(orgId, memberId), void 0, q);
|
|
52184
52192
|
}
|
|
52185
|
-
// Pending tasks (todo + in_progress) assigned to a member.
|
|
52186
|
-
// member profile Activity
|
|
52193
|
+
// Pending tasks (todo + in_progress) assigned to a member. On-demand listing
|
|
52194
|
+
// (member profile Activity; agents via `parall tasks`) — startup catch-up
|
|
52195
|
+
// runs on dispatch redrive, not this endpoint. Self-assigned tasks appear
|
|
52196
|
+
// here without any dispatch WorkItem (self-assign is deliberately silent).
|
|
52187
52197
|
async getMemberTasks(orgId, memberId, params) {
|
|
52188
52198
|
const q = {};
|
|
52189
52199
|
if (params?.cursor)
|
|
@@ -52525,7 +52535,12 @@ var ParallClient = class _ParallClient {
|
|
|
52525
52535
|
async getAgentRuntimeRelease(orgId, agentId, tag) {
|
|
52526
52536
|
return this.request("GET", ENDPOINTS.AGENT_RUNTIME_RELEASE(orgId, agentId, tag));
|
|
52527
52537
|
}
|
|
52528
|
-
/**
|
|
52538
|
+
/**
|
|
52539
|
+
* Fetch all pending tasks (todo/in_progress) assigned to an agent. Pages
|
|
52540
|
+
* automatically. On-demand listing only — not a dispatch source: startup
|
|
52541
|
+
* catch-up runs on dispatch redrive, and self-assigned tasks listed here
|
|
52542
|
+
* deliberately have no WorkItem behind them.
|
|
52543
|
+
*/
|
|
52529
52544
|
async getAgentTasks(orgId, agentId) {
|
|
52530
52545
|
const all = [];
|
|
52531
52546
|
let cursor;
|
|
@@ -52869,6 +52884,10 @@ var ParallClient = class _ParallClient {
|
|
|
52869
52884
|
async steerDispatch(orgId, req) {
|
|
52870
52885
|
return this.request("POST", ENDPOINTS.DISPATCH_STEER(orgId), req);
|
|
52871
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
|
+
}
|
|
52872
52891
|
async completeDispatch(orgId, req) {
|
|
52873
52892
|
return this.request("POST", ENDPOINTS.DISPATCH_COMPLETE(orgId), req);
|
|
52874
52893
|
}
|
|
@@ -53527,6 +53546,19 @@ var ParallClient = class _ParallClient {
|
|
|
53527
53546
|
async getTemplateDeployment(orgId, deploymentId) {
|
|
53528
53547
|
return this.request("GET", ENDPOINTS.TEMPLATE_DEPLOYMENT(orgId, deploymentId));
|
|
53529
53548
|
}
|
|
53549
|
+
/** Pre-deploy dependency connection state (wizard S3). Live answer, no
|
|
53550
|
+
* cache — poll it the way getTemplateDeployment is polled. */
|
|
53551
|
+
async checkClipDependencies(orgId, refs) {
|
|
53552
|
+
const response = await this.request("POST", ENDPOINTS.CLIP_DEPENDENCIES_CHECK(orgId), { refs });
|
|
53553
|
+
return response.results ?? [];
|
|
53554
|
+
}
|
|
53555
|
+
// ---- Onboarding wizard progress (org-scoped, self) ----
|
|
53556
|
+
async getOnboardingProgress(orgId) {
|
|
53557
|
+
return this.request("GET", ENDPOINTS.ONBOARDING_PROGRESS(orgId));
|
|
53558
|
+
}
|
|
53559
|
+
async updateOnboardingProgress(orgId, request3) {
|
|
53560
|
+
return this.request("PATCH", ENDPOINTS.ONBOARDING_PROGRESS(orgId), request3);
|
|
53561
|
+
}
|
|
53530
53562
|
// ---- Billing & Credits (org-scoped) ----
|
|
53531
53563
|
async getBilling(orgId) {
|
|
53532
53564
|
return this.request("GET", ENDPOINTS.BILLING(orgId));
|
|
@@ -53821,12 +53853,17 @@ var ParallClient = class _ParallClient {
|
|
|
53821
53853
|
* command RAN and failed — `error`/`error_code` describe why). Everything
|
|
53822
53854
|
* else throws a typed {@link ApiError}; match on `err.code`:
|
|
53823
53855
|
*
|
|
53824
|
-
* Safe to retry (
|
|
53825
|
-
*
|
|
53826
|
-
*
|
|
53827
|
-
* - `
|
|
53828
|
-
*
|
|
53829
|
-
* - `
|
|
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).
|
|
53830
53867
|
*
|
|
53831
53868
|
* NOT retryable:
|
|
53832
53869
|
* - `OUTCOME_UNKNOWN` 504 — dispatched, but no result arrived. The command
|
|
@@ -53865,6 +53902,60 @@ var ParallClient = class _ParallClient {
|
|
|
53865
53902
|
async deleteClipConnection(orgId, connId) {
|
|
53866
53903
|
return this.request("DELETE", ENDPOINTS.CLIP_CONNECTION(orgId, connId));
|
|
53867
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
|
+
}
|
|
53868
53959
|
};
|
|
53869
53960
|
function normalizeWikiChangeset(changeset) {
|
|
53870
53961
|
return {
|
|
@@ -54270,10 +54361,221 @@ var ParallWs = class {
|
|
|
54270
54361
|
}
|
|
54271
54362
|
};
|
|
54272
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
|
+
|
|
54273
54561
|
// ../agent-core/dist/lane-ledger.js
|
|
54274
54562
|
import * as fs from "node:fs";
|
|
54275
54563
|
var LedgerUnsupportedError = class extends Error {
|
|
54276
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
|
+
}
|
|
54277
54579
|
function isStaleLane(err) {
|
|
54278
54580
|
return err instanceof ApiError && err.status === 409 && err.code === "STALE_LANE";
|
|
54279
54581
|
}
|
|
@@ -54323,7 +54625,8 @@ var LaneLedger = class {
|
|
|
54323
54625
|
res = await this.opts.client.claimDispatch(this.opts.orgId, {
|
|
54324
54626
|
target_uri: targetUri,
|
|
54325
54627
|
thread_root_id: trigger.threadRootId,
|
|
54326
|
-
limit: 100
|
|
54628
|
+
limit: 100,
|
|
54629
|
+
coverage_mode: this.opts.coverageMode ?? "implicit"
|
|
54327
54630
|
});
|
|
54328
54631
|
} catch (err) {
|
|
54329
54632
|
if (isEndpointMissing(err))
|
|
@@ -54338,12 +54641,20 @@ var LaneLedger = class {
|
|
|
54338
54641
|
}
|
|
54339
54642
|
return null;
|
|
54340
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
|
+
}
|
|
54341
54651
|
const leaseUntilMs = Date.parse(res.lease_until ?? "");
|
|
54342
54652
|
lane = {
|
|
54343
54653
|
laneKey,
|
|
54344
54654
|
lane: res.lane,
|
|
54345
54655
|
targetUri,
|
|
54346
54656
|
threadRootId: trigger.threadRootId,
|
|
54657
|
+
coverageMode: actualCoverage,
|
|
54347
54658
|
folded: /* @__PURE__ */ new Map(),
|
|
54348
54659
|
...Number.isNaN(leaseUntilMs) ? {} : { leaseUntilMs, leaseTtlMs: Math.max(leaseUntilMs - Date.now(), 6e4) }
|
|
54349
54660
|
};
|
|
@@ -54385,9 +54696,10 @@ var LaneLedger = class {
|
|
|
54385
54696
|
const laneKey = this.laneKeyFor(event);
|
|
54386
54697
|
const lane = this.lanes.get(laneKey);
|
|
54387
54698
|
if (!lane)
|
|
54388
|
-
return
|
|
54389
|
-
if (lane.folded.has(event.messageId))
|
|
54390
|
-
return
|
|
54699
|
+
return null;
|
|
54700
|
+
if (lane.folded.has(event.messageId)) {
|
|
54701
|
+
return { inputLifecycle: this.inputLifecycle(lane, [event]) };
|
|
54702
|
+
}
|
|
54391
54703
|
try {
|
|
54392
54704
|
const res = await this.opts.client.steerDispatch(this.opts.orgId, {
|
|
54393
54705
|
lane: lane.lane,
|
|
@@ -54396,16 +54708,64 @@ var LaneLedger = class {
|
|
|
54396
54708
|
...event.dispatchEventId ? { dispatch_event_id: event.dispatchEventId } : { source_type: "message", source_id: event.messageId }
|
|
54397
54709
|
});
|
|
54398
54710
|
lane.folded.set(event.messageId, res.dispatch_event_id);
|
|
54399
|
-
return
|
|
54711
|
+
return { inputLifecycle: this.inputLifecycle(lane, [event]) };
|
|
54400
54712
|
} catch (err) {
|
|
54401
54713
|
if (isStaleLane(err)) {
|
|
54402
54714
|
this.lanes.delete(laneKey);
|
|
54403
54715
|
} else {
|
|
54404
54716
|
this.opts.log?.warn(`live steer failed for ${event.messageId}: ${String(err)}`);
|
|
54405
54717
|
}
|
|
54406
|
-
return
|
|
54718
|
+
return null;
|
|
54407
54719
|
}
|
|
54408
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
|
+
}
|
|
54409
54769
|
/**
|
|
54410
54770
|
* Complete the lane when no local work remains for it: the server sweeps
|
|
54411
54771
|
* still-leased members as no_action, releases the occupancy row, and
|
|
@@ -54424,20 +54784,46 @@ var LaneLedger = class {
|
|
|
54424
54784
|
if (lane)
|
|
54425
54785
|
lane.turnError = true;
|
|
54426
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;
|
|
54427
54804
|
async completeIfIdle(laneKey, hasMoreLocal) {
|
|
54428
54805
|
const lane = this.lanes.get(laneKey);
|
|
54429
54806
|
if (!lane || hasMoreLocal)
|
|
54430
54807
|
return;
|
|
54431
54808
|
this.lanes.delete(laneKey);
|
|
54432
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";
|
|
54433
54812
|
try {
|
|
54434
54813
|
const res = await this.opts.client.completeDispatch(this.opts.orgId, {
|
|
54435
54814
|
lane: lane.lane,
|
|
54436
54815
|
target_uri: lane.targetUri,
|
|
54437
54816
|
thread_root_id: lane.threadRootId,
|
|
54438
|
-
// An error turn releases its members for retry
|
|
54439
|
-
// them
|
|
54440
|
-
|
|
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
|
|
54441
54827
|
});
|
|
54442
54828
|
if (res.swept_no_action > 0 || res.redriven) {
|
|
54443
54829
|
this.opts.log?.info(`lane complete for ${lane.targetUri}: swept ${res.swept_no_action} no_action, redriven=${res.redriven}`);
|
|
@@ -54447,6 +54833,23 @@ var LaneLedger = class {
|
|
|
54447
54833
|
this.opts.log?.info(`lane complete skipped for ${lane.targetUri} \u2014 taken over`);
|
|
54448
54834
|
return;
|
|
54449
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
|
+
}
|
|
54450
54853
|
this.opts.log?.warn(`lane complete failed for ${lane.targetUri}: ${String(err)}`);
|
|
54451
54854
|
}
|
|
54452
54855
|
}
|
|
@@ -54548,6 +54951,7 @@ var LaneLedger = class {
|
|
|
54548
54951
|
laneKey: laneKeyForTarget(targetUri),
|
|
54549
54952
|
lane: res.lane,
|
|
54550
54953
|
targetUri,
|
|
54954
|
+
coverageMode: "implicit",
|
|
54551
54955
|
folded: /* @__PURE__ */ new Map([[workItem.source_id, workItem.id]]),
|
|
54552
54956
|
typedDispatchEventId: workItem.id,
|
|
54553
54957
|
...Number.isNaN(leaseUntilMs) ? {} : { leaseUntilMs, leaseTtlMs: Math.max(leaseUntilMs - Date.now(), 6e4) }
|
|
@@ -54587,6 +54991,16 @@ var LaneLedger = class {
|
|
|
54587
54991
|
};
|
|
54588
54992
|
|
|
54589
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
|
+
}
|
|
54590
55004
|
async function dispatchLaneGroup(host, opts) {
|
|
54591
55005
|
const ledger = host.laneLedger;
|
|
54592
55006
|
const event = opts.events[opts.events.length - 1];
|
|
@@ -54619,7 +55033,8 @@ async function dispatchLaneGroup(host, opts) {
|
|
|
54619
55033
|
host.noteSessionLane(opts.sessionKey, lane.laneKey);
|
|
54620
55034
|
let dispatched = false;
|
|
54621
55035
|
try {
|
|
54622
|
-
|
|
55036
|
+
const inputLifecycle = ledger.inputLifecycle(lane, [...opts.earlier, event]);
|
|
55037
|
+
dispatched = await host.runDispatch(event, opts.sessionKey, opts.body, opts.earlier, opts.captureText, inputLifecycle);
|
|
54623
55038
|
} catch (err) {
|
|
54624
55039
|
host.noteSessionLane(opts.sessionKey, null);
|
|
54625
55040
|
await ledger.release(lane.laneKey).catch(() => {
|
|
@@ -54632,8 +55047,16 @@ async function dispatchLaneGroup(host, opts) {
|
|
|
54632
55047
|
if (!dispatched) {
|
|
54633
55048
|
return "shutdown";
|
|
54634
55049
|
}
|
|
54635
|
-
|
|
54636
|
-
|
|
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
|
+
}
|
|
54637
55060
|
for (const msgId of lane.folded.keys()) {
|
|
54638
55061
|
host.dispatchedMessages.delete(msgId);
|
|
54639
55062
|
}
|
|
@@ -54642,7 +55065,7 @@ async function dispatchLaneGroup(host, opts) {
|
|
|
54642
55065
|
} catch {
|
|
54643
55066
|
}
|
|
54644
55067
|
await ledger.completeIfIdle(lane.laneKey, false);
|
|
54645
|
-
return "failed";
|
|
55068
|
+
return settled.kind === "deferred" ? "deferred" : "failed";
|
|
54646
55069
|
}
|
|
54647
55070
|
const pendingInjections = host.opts.dispatchAdapter.hasPendingInjections?.(opts.sessionKey) ?? false;
|
|
54648
55071
|
await ledger.completeIfIdle(lane.laneKey, pendingInjections || opts.hasMoreLocal());
|
|
@@ -54843,6 +55266,8 @@ async function consumeMessageWorkItem(host, item) {
|
|
|
54843
55266
|
return;
|
|
54844
55267
|
if (!host.tryClaimMessage(item.source_id))
|
|
54845
55268
|
return;
|
|
55269
|
+
if (host.dispatchState.mainBuffer.some((event) => event.messageId === item.source_id))
|
|
55270
|
+
return;
|
|
54846
55271
|
const ackItem = () => {
|
|
54847
55272
|
if (host.laneLedger && !host.ledgerDisabled && !host.typedByIdCompleteUnsupported) {
|
|
54848
55273
|
void resolveDispatchByID(host, item.id).then((outcome) => {
|
|
@@ -55013,203 +55438,6 @@ function routeTrigger(event, state, strategy = defaultRoutingStrategy) {
|
|
|
55013
55438
|
return strategy(event, state);
|
|
55014
55439
|
}
|
|
55015
55440
|
|
|
55016
|
-
// ../agent-core/dist/event-format.js
|
|
55017
|
-
function sanitizeMeta(value) {
|
|
55018
|
-
return value.replace(/[\r\n]+/g, " ").replace(/[[\]|]/g, " ").trim();
|
|
55019
|
-
}
|
|
55020
|
-
function buildEventBody(event) {
|
|
55021
|
-
const lines = [];
|
|
55022
|
-
if (event.type === "message") {
|
|
55023
|
-
lines.push(`[Event: message.new]`);
|
|
55024
|
-
const chatLabel = event.targetName ? `"${event.targetName}" (prll://${event.targetId})` : `prll://${event.targetId}`;
|
|
55025
|
-
lines.push(`[Chat: ${chatLabel} | type: ${event.targetType ?? "unknown"}]`);
|
|
55026
|
-
lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
|
|
55027
|
-
lines.push(`[Message ID: prll://${event.messageId}]`);
|
|
55028
|
-
if (event.threadRootId) {
|
|
55029
|
-
const threadMeta = [
|
|
55030
|
-
`prll://${event.threadRootId}`,
|
|
55031
|
-
event.threadReplyCount != null ? `${event.threadReplyCount} replies` : null,
|
|
55032
|
-
event.threadUnreadCount != null && event.threadUnreadCount > 0 ? `${event.threadUnreadCount} unread` : null,
|
|
55033
|
-
event.threadUnreadCount != null && event.threadUnreadCount > 0 && event.threadUnreadSince ? `since: prll://${event.threadUnreadSince}` : null
|
|
55034
|
-
].filter(Boolean).join(" | ");
|
|
55035
|
-
lines.push(`[Thread: ${threadMeta}]`);
|
|
55036
|
-
}
|
|
55037
|
-
if (event.unreadCount != null && event.unreadCount > 1) {
|
|
55038
|
-
const countStr = event.unreadCount >= 1e3 ? "999+" : String(event.unreadCount);
|
|
55039
|
-
const sinceStr = event.unreadSince ? ` | since: prll://${event.unreadSince}` : "";
|
|
55040
|
-
let line = `[Unread: ${countStr} messages${sinceStr}]`;
|
|
55041
|
-
if (event.unreadCount > 50)
|
|
55042
|
-
line += ` \u2014 fetch recent context with --limit, not all`;
|
|
55043
|
-
lines.push(line);
|
|
55044
|
-
}
|
|
55045
|
-
if (event.noReply)
|
|
55046
|
-
lines.push(`[Hint: no_reply]`);
|
|
55047
|
-
if (event.attachments?.length) {
|
|
55048
|
-
for (const att of event.attachments) {
|
|
55049
|
-
const sizeStr = att.fileSize >= 1048576 ? `${(att.fileSize / 1048576).toFixed(1)}MB` : `${Math.round(att.fileSize / 1024)}KB`;
|
|
55050
|
-
lines.push(`[Attachment: prll://${att.id} | ${sanitizeMeta(att.mimeType)} | ${sizeStr} | ${sanitizeMeta(att.fileName)}]`);
|
|
55051
|
-
}
|
|
55052
|
-
}
|
|
55053
|
-
lines.push("", event.body);
|
|
55054
|
-
} else if (event.type === "task_comment") {
|
|
55055
|
-
lines.push(`[Event: task.comment.created]`);
|
|
55056
|
-
const taskLabel = event.targetName ? `${event.targetName} (prll://${event.targetId})` : `prll://${event.targetId}`;
|
|
55057
|
-
lines.push(`[Task: ${taskLabel}]`);
|
|
55058
|
-
if (event.deliveryReason)
|
|
55059
|
-
lines.push(`[Delivery: ${sanitizeMeta(event.deliveryReason)}]`);
|
|
55060
|
-
lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
|
|
55061
|
-
lines.push(`[Comment ID: prll://${event.messageId}]`);
|
|
55062
|
-
lines.push("", event.body);
|
|
55063
|
-
} else if (event.type === "wiki_comment") {
|
|
55064
|
-
lines.push(`[Event: wiki.comment.created]`);
|
|
55065
|
-
const target = event.replyTargetUri ?? `prll://${event.targetId}`;
|
|
55066
|
-
if (event.targetType === "changeset") {
|
|
55067
|
-
lines.push(`[Wiki Changeset: ${target}]`);
|
|
55068
|
-
} else {
|
|
55069
|
-
lines.push(`[Wiki: ${event.targetName ? `${sanitizeMeta(event.targetName)} (${target})` : target}]`);
|
|
55070
|
-
}
|
|
55071
|
-
if (event.deliveryReason)
|
|
55072
|
-
lines.push(`[Delivery: ${sanitizeMeta(event.deliveryReason)}]`);
|
|
55073
|
-
lines.push(`[From: ${event.senderName} (prll://${event.senderId})]`);
|
|
55074
|
-
lines.push(`[Comment ID: prll://${event.messageId}]`);
|
|
55075
|
-
lines.push("", event.body);
|
|
55076
|
-
} else if (event.type === "approval") {
|
|
55077
|
-
lines.push(`[Event: approval.decided]`);
|
|
55078
|
-
lines.push(`[Approval: prll://${event.messageId}]`);
|
|
55079
|
-
lines.push(`[Chat: prll://${event.targetId}]`);
|
|
55080
|
-
lines.push(`[Decided by: ${event.senderName} (prll://${event.senderId})]`);
|
|
55081
|
-
lines.push("", event.body);
|
|
55082
|
-
} else if (event.type === "schedule") {
|
|
55083
|
-
lines.push(`[Event: schedule.fired]`);
|
|
55084
|
-
lines.push(`[Schedule: prll://${event.targetId}]`);
|
|
55085
|
-
lines.push(`[Run: prll://${event.messageId}]`);
|
|
55086
|
-
if (event.scheduledFireAt)
|
|
55087
|
-
lines.push(`[Scheduled at: ${sanitizeMeta(event.scheduledFireAt)}]`);
|
|
55088
|
-
if (event.attachedUri)
|
|
55089
|
-
lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
|
|
55090
|
-
lines.push("", event.body);
|
|
55091
|
-
} else if (event.type === "channel_message") {
|
|
55092
|
-
lines.push(`[Event: channel.message]`);
|
|
55093
|
-
const providerLabel = sanitizeMeta(event.channelProvider ?? "external IM");
|
|
55094
|
-
const convLabel = event.channelExternalConversationId ? `${sanitizeMeta(event.channelExternalConversationId)} (${sanitizeMeta(event.channelConversationType ?? "conversation")})` : sanitizeMeta(event.channelConversationType ?? "conversation");
|
|
55095
|
-
lines.push(`[Channel: ${providerLabel} | conversation: ${convLabel}]`);
|
|
55096
|
-
lines.push(`[From: ${sanitizeMeta(event.senderName)} (external user, not a Parall member)]`);
|
|
55097
|
-
if (event.channelExternalMessageId) {
|
|
55098
|
-
lines.push(`[External message ID: ${sanitizeMeta(event.channelExternalMessageId)}]`);
|
|
55099
|
-
}
|
|
55100
|
-
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.]`);
|
|
55101
|
-
lines.push("", event.body);
|
|
55102
|
-
} else if (event.type === "external_trigger") {
|
|
55103
|
-
lines.push(`[Event: external.trigger]`);
|
|
55104
|
-
lines.push(`[Trigger: prll://${event.targetId}]`);
|
|
55105
|
-
lines.push(`[Run: prll://${event.messageId}]`);
|
|
55106
|
-
if (event.externalConnectionId) {
|
|
55107
|
-
const label = event.externalConnectionDisplayName ? `${sanitizeMeta(event.externalConnectionDisplayName)} (prll://${event.externalConnectionId})` : `prll://${event.externalConnectionId}`;
|
|
55108
|
-
lines.push(`[Connection: ${label}]`);
|
|
55109
|
-
}
|
|
55110
|
-
if (event.externalIngressEventId)
|
|
55111
|
-
lines.push(`[Ingress: prll://${event.externalIngressEventId}]`);
|
|
55112
|
-
if (event.attachedUri)
|
|
55113
|
-
lines.push(`[Attached: ${sanitizeMeta(event.attachedUri)}]`);
|
|
55114
|
-
if (event.externalConnectionSourceType) {
|
|
55115
|
-
lines.push(`[Source: ${sanitizeMeta(event.externalConnectionSourceType)}]`);
|
|
55116
|
-
}
|
|
55117
|
-
if (event.externalIngressEventType) {
|
|
55118
|
-
lines.push(`[External event: ${sanitizeMeta(event.externalIngressEventType)}]`);
|
|
55119
|
-
}
|
|
55120
|
-
lines.push("", event.body);
|
|
55121
|
-
} else {
|
|
55122
|
-
lines.push(`[Event: task.assigned]`);
|
|
55123
|
-
const taskLabel = event.targetName ? `${event.targetName} (prll://${event.targetId})` : `prll://${event.targetId}`;
|
|
55124
|
-
lines.push(`[Task: ${taskLabel}]`);
|
|
55125
|
-
lines.push(`[Assigned by: ${event.senderName} (prll://${event.senderId})]`);
|
|
55126
|
-
lines.push("", event.body);
|
|
55127
|
-
}
|
|
55128
|
-
return lines.join("\n") + buildSendMessageHint(event);
|
|
55129
|
-
}
|
|
55130
|
-
function buildEventBodyForForkResult(event) {
|
|
55131
|
-
return buildEventBody(event).replace(/\n<system-reminder>[\s\S]*<\/system-reminder>$/, "");
|
|
55132
|
-
}
|
|
55133
|
-
function buildSendMessageHint(event) {
|
|
55134
|
-
if (event.noReply)
|
|
55135
|
-
return "";
|
|
55136
|
-
if (event.type === "wiki_comment" && event.replyTargetUri) {
|
|
55137
|
-
const where = event.targetType === "changeset" ? "this changeset comment" : "this wiki page";
|
|
55138
|
-
return `
|
|
55139
|
-
<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>`;
|
|
55140
|
-
}
|
|
55141
|
-
if (event.targetId.startsWith("cht_")) {
|
|
55142
|
-
if (event.threadRootId) {
|
|
55143
|
-
return `
|
|
55144
|
-
<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>`;
|
|
55145
|
-
}
|
|
55146
|
-
return `
|
|
55147
|
-
<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>`;
|
|
55148
|
-
}
|
|
55149
|
-
if (event.targetId.startsWith("tsk_")) {
|
|
55150
|
-
return `
|
|
55151
|
-
<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>`;
|
|
55152
|
-
}
|
|
55153
|
-
if (event.targetId.startsWith("sch_")) {
|
|
55154
|
-
return `
|
|
55155
|
-
<system-reminder>To communicate, use the CLI: \`parall messages send\` / \`parall dm\`. Your plain text output is not delivered.</system-reminder>`;
|
|
55156
|
-
}
|
|
55157
|
-
if (event.type === "channel_message") {
|
|
55158
|
-
if (event.channelCliCapable) {
|
|
55159
|
-
if (event.channelProvider === "slack") {
|
|
55160
|
-
const channelArg = event.channelExternalConversationId ? ` --channel "${event.channelExternalConversationId}"` : " --channel <conversation id from this event>";
|
|
55161
|
-
const replyTo = event.channelExternalMessageId ? ` --reply-to "${event.channelExternalMessageId}"` : "";
|
|
55162
|
-
return `
|
|
55163
|
-
<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>`;
|
|
55164
|
-
}
|
|
55165
|
-
if (!event.channelProvider) {
|
|
55166
|
-
return `
|
|
55167
|
-
<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>`;
|
|
55168
|
-
}
|
|
55169
|
-
const convRef = event.channelExternalConversationId ? `chat_id "${event.channelExternalConversationId}"` : "the conversation id named in this event";
|
|
55170
|
-
const threadAlt = event.channelExternalMessageId ? ` To reply threaded to this specific message, reference message_id "${event.channelExternalMessageId}".` : "";
|
|
55171
|
-
return `
|
|
55172
|
-
<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>`;
|
|
55173
|
-
}
|
|
55174
|
-
const platform = event.channelProvider ?? "the external platform";
|
|
55175
|
-
return `
|
|
55176
|
-
<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>`;
|
|
55177
|
-
}
|
|
55178
|
-
if (event.type === "external_trigger" || event.targetId.startsWith("xtr_")) {
|
|
55179
|
-
return `
|
|
55180
|
-
<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>`;
|
|
55181
|
-
}
|
|
55182
|
-
return "";
|
|
55183
|
-
}
|
|
55184
|
-
function buildForkScopePrefix(event) {
|
|
55185
|
-
const targetLabel = event.targetName ? `"${event.targetName}" (prll://${event.targetId})` : `prll://${event.targetId}`;
|
|
55186
|
-
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.
|
|
55187
|
-
|
|
55188
|
-
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.
|
|
55189
|
-
|
|
55190
|
-
End with a brief "Fork summary" of actions taken and decisions made.]
|
|
55191
|
-
|
|
55192
|
-
`;
|
|
55193
|
-
}
|
|
55194
|
-
function buildForkResultPrefix(results) {
|
|
55195
|
-
if (!results.length)
|
|
55196
|
-
return "";
|
|
55197
|
-
const blocks = results.map((result) => {
|
|
55198
|
-
const lines = [];
|
|
55199
|
-
for (const body of result.eventBodies) {
|
|
55200
|
-
lines.push(body);
|
|
55201
|
-
}
|
|
55202
|
-
lines.push(`[This event was handled by a parallel fork session. Do NOT re-handle, re-reply, or duplicate work for it.]`);
|
|
55203
|
-
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."}]`);
|
|
55204
|
-
if (result.actions.length)
|
|
55205
|
-
lines.push(`[Fork actions: ${result.actions.join("; ")}]`);
|
|
55206
|
-
if (result.historyPath)
|
|
55207
|
-
lines.push(`[Fork history: ${result.historyPath}]`);
|
|
55208
|
-
return lines.join("\n");
|
|
55209
|
-
});
|
|
55210
|
-
return blocks.join("\n\n") + "\n\n---\n\n";
|
|
55211
|
-
}
|
|
55212
|
-
|
|
55213
55441
|
// ../agent-core/dist/prompt-fragments.js
|
|
55214
55442
|
function identityFromMe(me) {
|
|
55215
55443
|
return {
|
|
@@ -55596,6 +55824,28 @@ function buildErrorStepContent(message) {
|
|
|
55596
55824
|
return { text: message, suppressed: false, status: "error" };
|
|
55597
55825
|
}
|
|
55598
55826
|
|
|
55827
|
+
// ../agent-core/dist/redact.js
|
|
55828
|
+
function redactSecrets(s, knownValues = []) {
|
|
55829
|
+
let out = s;
|
|
55830
|
+
for (const v of knownValues) {
|
|
55831
|
+
if (typeof v === "string" && v.length >= 6)
|
|
55832
|
+
out = out.split(v).join("***");
|
|
55833
|
+
}
|
|
55834
|
+
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, "***");
|
|
55835
|
+
}
|
|
55836
|
+
function redactTurnOutcome(event, knownValues) {
|
|
55837
|
+
const redacted = { ...event };
|
|
55838
|
+
if (redacted.detail)
|
|
55839
|
+
redacted.detail = redactSecrets(redacted.detail, knownValues);
|
|
55840
|
+
if (redacted.raw) {
|
|
55841
|
+
redacted.raw = Object.fromEntries(Object.entries(redacted.raw).map(([k, v]) => [
|
|
55842
|
+
k,
|
|
55843
|
+
typeof v === "string" ? redactSecrets(v, knownValues) : v
|
|
55844
|
+
]));
|
|
55845
|
+
}
|
|
55846
|
+
return redacted;
|
|
55847
|
+
}
|
|
55848
|
+
|
|
55599
55849
|
// ../agent-core/dist/gateway-base.js
|
|
55600
55850
|
import * as os from "node:os";
|
|
55601
55851
|
import * as fs3 from "node:fs";
|
|
@@ -56457,6 +56707,8 @@ var tracer = null;
|
|
|
56457
56707
|
var dispatchCounter = null;
|
|
56458
56708
|
var dispatchDuration = null;
|
|
56459
56709
|
var missingReplyCounter = null;
|
|
56710
|
+
var turnTokensCounter = null;
|
|
56711
|
+
var turnCostCounter = null;
|
|
56460
56712
|
var otelLogger = null;
|
|
56461
56713
|
function resolveTargetType(targetId) {
|
|
56462
56714
|
if (targetId.startsWith("cht_"))
|
|
@@ -56531,6 +56783,12 @@ async function initAgentTelemetry(serviceName, runtimeType) {
|
|
|
56531
56783
|
missingReplyCounter = meter.createCounter("parall.dispatch.missing_reply", {
|
|
56532
56784
|
description: "Dispatches where agent produced text but sent no reply message"
|
|
56533
56785
|
});
|
|
56786
|
+
turnTokensCounter = meter.createCounter("parall.turn.tokens", {
|
|
56787
|
+
description: "LLM tokens consumed per turn, by kind (input/output/cache_read/cache_creation)"
|
|
56788
|
+
});
|
|
56789
|
+
turnCostCounter = meter.createCounter("parall.turn.cost_usd", {
|
|
56790
|
+
description: "LLM cost per turn in USD (when the runtime reports it)"
|
|
56791
|
+
});
|
|
56534
56792
|
initialized = true;
|
|
56535
56793
|
shutdownFn = async () => {
|
|
56536
56794
|
await tracerProvider.forceFlush();
|
|
@@ -56564,7 +56822,7 @@ function startDispatchSpan(event, runtimeType, sessionKey) {
|
|
|
56564
56822
|
}
|
|
56565
56823
|
});
|
|
56566
56824
|
}
|
|
56567
|
-
function endDispatchSpan(span, metricsSnapshot, error) {
|
|
56825
|
+
function endDispatchSpan(span, metricsSnapshot, error, turnOutcome) {
|
|
56568
56826
|
if (!span)
|
|
56569
56827
|
return;
|
|
56570
56828
|
if (metricsSnapshot) {
|
|
@@ -56578,27 +56836,77 @@ function endDispatchSpan(span, metricsSnapshot, error) {
|
|
|
56578
56836
|
"dispatch.duration_ms": Date.now() - metricsSnapshot.started_at
|
|
56579
56837
|
});
|
|
56580
56838
|
}
|
|
56839
|
+
if (turnOutcome) {
|
|
56840
|
+
span.setAttribute("dispatch.outcome", turnOutcome.outcome);
|
|
56841
|
+
if (turnOutcome.detail)
|
|
56842
|
+
span.setAttribute("dispatch.outcome_detail", turnOutcome.detail);
|
|
56843
|
+
if (turnOutcome.retryAt)
|
|
56844
|
+
span.setAttribute("dispatch.retry_at", turnOutcome.retryAt);
|
|
56845
|
+
if (turnOutcome.model)
|
|
56846
|
+
span.setAttribute("dispatch.model", turnOutcome.model);
|
|
56847
|
+
if (turnOutcome.raw && Object.keys(turnOutcome.raw).length > 0) {
|
|
56848
|
+
try {
|
|
56849
|
+
span.setAttribute("dispatch.outcome_raw", JSON.stringify(turnOutcome.raw));
|
|
56850
|
+
} catch {
|
|
56851
|
+
}
|
|
56852
|
+
}
|
|
56853
|
+
const u = turnOutcome.usage;
|
|
56854
|
+
if (u) {
|
|
56855
|
+
if (u.inputTokens !== void 0)
|
|
56856
|
+
span.setAttribute("dispatch.tokens_input", u.inputTokens);
|
|
56857
|
+
if (u.outputTokens !== void 0)
|
|
56858
|
+
span.setAttribute("dispatch.tokens_output", u.outputTokens);
|
|
56859
|
+
if (u.cacheReadTokens !== void 0)
|
|
56860
|
+
span.setAttribute("dispatch.tokens_cache_read", u.cacheReadTokens);
|
|
56861
|
+
if (u.cacheCreationTokens !== void 0)
|
|
56862
|
+
span.setAttribute("dispatch.tokens_cache_creation", u.cacheCreationTokens);
|
|
56863
|
+
if (u.costUsd !== void 0)
|
|
56864
|
+
span.setAttribute("dispatch.cost_usd", u.costUsd);
|
|
56865
|
+
if (u.durationApiMs !== void 0)
|
|
56866
|
+
span.setAttribute("dispatch.duration_api_ms", u.durationApiMs);
|
|
56867
|
+
}
|
|
56868
|
+
}
|
|
56581
56869
|
if (error) {
|
|
56582
|
-
|
|
56583
|
-
span.
|
|
56870
|
+
const safe = redactSecrets(String(error));
|
|
56871
|
+
span.setStatus({ code: SpanStatusCode.ERROR, message: safe });
|
|
56872
|
+
span.recordException(error instanceof Error ? new Error(safe) : new Error(safe));
|
|
56584
56873
|
}
|
|
56585
56874
|
span.end();
|
|
56586
56875
|
}
|
|
56587
|
-
function recordDispatchMetric(event, runtimeType, durationMs) {
|
|
56876
|
+
function recordDispatchMetric(event, runtimeType, durationMs, outcome = "ok") {
|
|
56588
56877
|
if (!initialized)
|
|
56589
56878
|
return;
|
|
56590
56879
|
const attrs = {
|
|
56591
56880
|
target_type: resolveTargetType(event.targetId),
|
|
56592
56881
|
event_type: event.type,
|
|
56593
|
-
runtime_type: runtimeType
|
|
56882
|
+
runtime_type: runtimeType,
|
|
56883
|
+
outcome
|
|
56594
56884
|
};
|
|
56595
56885
|
dispatchCounter?.add(1, attrs);
|
|
56596
56886
|
dispatchDuration?.record(durationMs, attrs);
|
|
56597
56887
|
}
|
|
56598
|
-
function recordMissingReply(runtimeType) {
|
|
56888
|
+
function recordMissingReply(runtimeType, outcome = "ok") {
|
|
56599
56889
|
if (!initialized)
|
|
56600
56890
|
return;
|
|
56601
|
-
missingReplyCounter?.add(1, { runtime_type: runtimeType });
|
|
56891
|
+
missingReplyCounter?.add(1, { runtime_type: runtimeType, outcome });
|
|
56892
|
+
}
|
|
56893
|
+
function recordTurnUsage(usage, runtimeType) {
|
|
56894
|
+
if (!initialized || !usage)
|
|
56895
|
+
return;
|
|
56896
|
+
const kinds = [
|
|
56897
|
+
["input", usage.inputTokens],
|
|
56898
|
+
["output", usage.outputTokens],
|
|
56899
|
+
["cache_read", usage.cacheReadTokens],
|
|
56900
|
+
["cache_creation", usage.cacheCreationTokens]
|
|
56901
|
+
];
|
|
56902
|
+
for (const [kind, value] of kinds) {
|
|
56903
|
+
if (value !== void 0 && value > 0) {
|
|
56904
|
+
turnTokensCounter?.add(value, { kind, runtime_type: runtimeType });
|
|
56905
|
+
}
|
|
56906
|
+
}
|
|
56907
|
+
if (usage.costUsd !== void 0 && usage.costUsd > 0) {
|
|
56908
|
+
turnCostCounter?.add(usage.costUsd, { runtime_type: runtimeType });
|
|
56909
|
+
}
|
|
56602
56910
|
}
|
|
56603
56911
|
var sessionKeyStorage = new AsyncLocalStorage();
|
|
56604
56912
|
function runWithSessionKey(sessionKey, fn) {
|
|
@@ -56798,7 +57106,9 @@ var ParallAgentGateway = class {
|
|
|
56798
57106
|
client: opts.client,
|
|
56799
57107
|
orgId: opts.config.org_id,
|
|
56800
57108
|
contextDir: opts.dispatchContextDir,
|
|
56801
|
-
log: opts.log
|
|
57109
|
+
log: opts.log,
|
|
57110
|
+
coverageMode: opts.dispatchAdapter.inputLifecycleMode ?? "implicit",
|
|
57111
|
+
releaseLocalClaims: (sourceIds) => releaseLocalMessageClaims(this.dispatchedMessages, sourceIds)
|
|
56802
57112
|
});
|
|
56803
57113
|
}
|
|
56804
57114
|
this.SHUTDOWN_DEADLINE_MS = opts.shutdownDeadlineMs ?? 6e4;
|
|
@@ -57072,17 +57382,49 @@ var ParallAgentGateway = class {
|
|
|
57072
57382
|
if (laneKey)
|
|
57073
57383
|
this.laneLedger?.renewByKey(laneKey);
|
|
57074
57384
|
}
|
|
57075
|
-
/** Sessions whose in-flight turn surfaced a runtime error event. */
|
|
57076
|
-
turnErrorSessions = /* @__PURE__ */ new Set();
|
|
57077
57385
|
/**
|
|
57078
|
-
*
|
|
57386
|
+
* Settled LLM-layer outcome of each session's last turn — an error to
|
|
57387
|
+
* release lane members on the redrive budget, or a deferred usage-limit
|
|
57388
|
+
* wait (agent-turn-outcome-design.md). Absent = clean turn.
|
|
57389
|
+
*/
|
|
57390
|
+
turnOutcomes = /* @__PURE__ */ new Map();
|
|
57391
|
+
/**
|
|
57392
|
+
* Consume (read-and-clear) the settled outcome for sessionKey's last turn.
|
|
57079
57393
|
* Feeds complete's turn_outcome so an error turn's lane members are
|
|
57080
|
-
* released for retry
|
|
57081
|
-
*
|
|
57082
|
-
*
|
|
57394
|
+
* released for retry (and a deferred turn's members re-deliver at retryAt)
|
|
57395
|
+
* instead of being no_action-swept (design §3). Consuming (rather than
|
|
57396
|
+
* peeking) keeps one-shot fork session keys from accumulating forever.
|
|
57397
|
+
*/
|
|
57398
|
+
consumeTurnOutcome(sessionKey) {
|
|
57399
|
+
const outcome = this.turnOutcomes.get(sessionKey);
|
|
57400
|
+
this.turnOutcomes.delete(sessionKey);
|
|
57401
|
+
return outcome;
|
|
57402
|
+
}
|
|
57403
|
+
/**
|
|
57404
|
+
* Boolean view of consumeTurnOutcome for the typed/fork call sites, which
|
|
57405
|
+
* have no deferred semantics: ANY non-clean outcome (error or deferred)
|
|
57406
|
+
* counts as an errored turn there — release-for-retry beats a false
|
|
57407
|
+
* "handled".
|
|
57083
57408
|
*/
|
|
57084
57409
|
consumeTurnError(sessionKey) {
|
|
57085
|
-
return this.
|
|
57410
|
+
return this.consumeTurnOutcome(sessionKey) !== void 0;
|
|
57411
|
+
}
|
|
57412
|
+
/**
|
|
57413
|
+
* Fold turn signals into the session's settled outcome. Two sources, one
|
|
57414
|
+
* precedence rule: the bridge's explicit turn_outcome classification always
|
|
57415
|
+
* wins (it may refine the SAME failure a generic `error` event already
|
|
57416
|
+
* reported — e.g. Claude's result frame yields both), while a bare `error`
|
|
57417
|
+
* event only fills the slot when no classification exists. Wire order makes
|
|
57418
|
+
* this safe: every bridge emits its error events before the turn-boundary
|
|
57419
|
+
* turn_outcome.
|
|
57420
|
+
*/
|
|
57421
|
+
recordTurnErrorSignal(sessionKey) {
|
|
57422
|
+
if (!this.turnOutcomes.has(sessionKey)) {
|
|
57423
|
+
this.turnOutcomes.set(sessionKey, { kind: "error" });
|
|
57424
|
+
}
|
|
57425
|
+
}
|
|
57426
|
+
recordTurnClassification(sessionKey, next) {
|
|
57427
|
+
this.turnOutcomes.set(sessionKey, next);
|
|
57086
57428
|
}
|
|
57087
57429
|
async emitDispatchReceived(event) {
|
|
57088
57430
|
const sourceType = event.ackSourceType ?? (event.type === "task" ? "task_activity" : "message");
|
|
@@ -57252,7 +57594,14 @@ var ParallAgentGateway = class {
|
|
|
57252
57594
|
channel_message_id: event.messageId,
|
|
57253
57595
|
provider: event.channelProvider,
|
|
57254
57596
|
external_conversation_id: event.channelExternalConversationId
|
|
57255
|
-
} : event.type === "approval" ? { approval_id: event.messageId } : {
|
|
57597
|
+
} : event.type === "approval" ? { approval_id: event.messageId } : {
|
|
57598
|
+
message_id: event.messageId,
|
|
57599
|
+
// Thread context for the session→chat backlink:
|
|
57600
|
+
// a thread reply can only be navigated to with
|
|
57601
|
+
// its thread root (the chat surface rejects
|
|
57602
|
+
// bare thread-message targets).
|
|
57603
|
+
...event.threadRootId ? { thread_root_id: event.threadRootId } : {}
|
|
57604
|
+
},
|
|
57256
57605
|
sender_id: event.senderId,
|
|
57257
57606
|
sender_name: event.senderName,
|
|
57258
57607
|
summary: event.body.substring(0, 200),
|
|
@@ -57459,7 +57808,7 @@ var ParallAgentGateway = class {
|
|
|
57459
57808
|
// are shutting down. Callers MUST treat `false` as "not dispatched" and
|
|
57460
57809
|
// skip the server-side ack so the event stays in the dispatch queue for
|
|
57461
57810
|
// catch-up on the replacement pod — otherwise we silently drop work.
|
|
57462
|
-
async runDispatch(event, sessionKey, bodyForAgent, earlierEvents = [], captureText) {
|
|
57811
|
+
async runDispatch(event, sessionKey, bodyForAgent, earlierEvents = [], captureText, inputLifecycle) {
|
|
57463
57812
|
if (this.shuttingDown) {
|
|
57464
57813
|
this.opts.log?.info(`skipping dispatch for ${event.messageId} (shutting down) \u2014 leaving unacked for catch-up on replacement pod`);
|
|
57465
57814
|
return false;
|
|
@@ -57469,7 +57818,7 @@ var ParallAgentGateway = class {
|
|
|
57469
57818
|
this.pendingRestartNotification = null;
|
|
57470
57819
|
}
|
|
57471
57820
|
resetDispatchMetrics(sessionKey);
|
|
57472
|
-
this.
|
|
57821
|
+
this.turnOutcomes.delete(sessionKey);
|
|
57473
57822
|
return runWithSessionKey(sessionKey, async () => {
|
|
57474
57823
|
let dispatchSpan = null;
|
|
57475
57824
|
setSessionChatId(sessionKey, event.targetId);
|
|
@@ -57514,6 +57863,8 @@ var ParallAgentGateway = class {
|
|
|
57514
57863
|
let inputStepsCreated = false;
|
|
57515
57864
|
let turnHandle;
|
|
57516
57865
|
let dispatchError;
|
|
57866
|
+
let turnOutcomeEvent;
|
|
57867
|
+
let sawErrorEvent = false;
|
|
57517
57868
|
const pendingSendCallIds = /* @__PURE__ */ new Set();
|
|
57518
57869
|
const ensureTurnBegun = async () => {
|
|
57519
57870
|
if (turnHandle || !binding)
|
|
@@ -57527,11 +57878,15 @@ var ParallAgentGateway = class {
|
|
|
57527
57878
|
earlierEvents,
|
|
57528
57879
|
bodyForAgent,
|
|
57529
57880
|
sessionKey,
|
|
57530
|
-
context: dispatchContext
|
|
57881
|
+
context: dispatchContext,
|
|
57882
|
+
inputLifecycle
|
|
57531
57883
|
})) {
|
|
57532
57884
|
if (runtimeEvent.type === "runtime_session") {
|
|
57533
57885
|
const priorAgentSessionId = binding?.agentSessionId;
|
|
57534
57886
|
binding = await this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2);
|
|
57887
|
+
if (activeLane) {
|
|
57888
|
+
bindLaneSession(activeLane, binding.agentSessionId);
|
|
57889
|
+
}
|
|
57535
57890
|
if (event.targetType === "channel_conversation" && binding.agentSessionId !== priorAgentSessionId) {
|
|
57536
57891
|
try {
|
|
57537
57892
|
await this.opts.client.setChannelConversationSession(this.opts.config.org_id, event.targetId, binding.agentSessionId);
|
|
@@ -57549,6 +57904,36 @@ var ParallAgentGateway = class {
|
|
|
57549
57904
|
}
|
|
57550
57905
|
continue;
|
|
57551
57906
|
}
|
|
57907
|
+
if (runtimeEvent.type === "turn_outcome") {
|
|
57908
|
+
const outcomeEvent = redactTurnOutcome(runtimeEvent, [this.opts.config.api_key]);
|
|
57909
|
+
turnOutcomeEvent = outcomeEvent;
|
|
57910
|
+
if (outcomeEvent.outcome === "ok") {
|
|
57911
|
+
this.turnOutcomes.delete(sessionKey);
|
|
57912
|
+
continue;
|
|
57913
|
+
}
|
|
57914
|
+
this.recordTurnClassification(sessionKey, outcomeEvent.outcome === "usage_limit" ? {
|
|
57915
|
+
kind: "deferred",
|
|
57916
|
+
outcomeClass: outcomeEvent.outcome,
|
|
57917
|
+
...outcomeEvent.retryAt ? { retryAt: outcomeEvent.retryAt } : {}
|
|
57918
|
+
} : { kind: "error", outcomeClass: outcomeEvent.outcome });
|
|
57919
|
+
const retryNote = outcomeEvent.retryAt ? `, retry at ${outcomeEvent.retryAt}` : "";
|
|
57920
|
+
this.opts.log?.warn(`turn outcome: ${outcomeEvent.outcome}${retryNote}${outcomeEvent.detail ? ` \u2014 ${outcomeEvent.detail}` : ""}`);
|
|
57921
|
+
if (binding) {
|
|
57922
|
+
await ensureTurnBegun();
|
|
57923
|
+
if (!inputStepsCreated) {
|
|
57924
|
+
if (earlierEvents.length > 0) {
|
|
57925
|
+
await this.createInputStepsForEarlierEvents(binding.agentSessionId, earlierEvents);
|
|
57926
|
+
}
|
|
57927
|
+
await this.createInputStep(binding.agentSessionId, event);
|
|
57928
|
+
inputStepsCreated = true;
|
|
57929
|
+
}
|
|
57930
|
+
await this.createRuntimeStep(binding.agentSessionId, event, {
|
|
57931
|
+
type: "error",
|
|
57932
|
+
message: `LLM turn ${outcomeEvent.outcome}${retryNote}${outcomeEvent.detail ? `: ${outcomeEvent.detail}` : ""}`
|
|
57933
|
+
}, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
57934
|
+
}
|
|
57935
|
+
continue;
|
|
57936
|
+
}
|
|
57552
57937
|
if (!binding) {
|
|
57553
57938
|
const detail = runtimeEvent.type === "error" ? `: ${runtimeEvent.message}` : "";
|
|
57554
57939
|
throw new Error(`runtime emitted ${runtimeEvent.type} before runtime_session${detail}`);
|
|
@@ -57581,7 +57966,8 @@ var ParallAgentGateway = class {
|
|
|
57581
57966
|
recordMessageSend(sessionKey, !runtimeEvent.error);
|
|
57582
57967
|
}
|
|
57583
57968
|
if (runtimeEvent.type === "error") {
|
|
57584
|
-
|
|
57969
|
+
sawErrorEvent = true;
|
|
57970
|
+
this.recordTurnErrorSignal(sessionKey);
|
|
57585
57971
|
}
|
|
57586
57972
|
await this.createRuntimeStep(binding.agentSessionId, event, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2);
|
|
57587
57973
|
}
|
|
@@ -57635,10 +58021,12 @@ var ParallAgentGateway = class {
|
|
|
57635
58021
|
clearTimeout(deadlineTimer);
|
|
57636
58022
|
const metricsSnapshot = getDispatchMetrics(sessionKey);
|
|
57637
58023
|
const durationMs = metricsSnapshot ? Date.now() - metricsSnapshot.started_at : 0;
|
|
57638
|
-
|
|
57639
|
-
|
|
58024
|
+
const effectiveOutcome = turnOutcomeEvent?.outcome ?? (dispatchError || sawErrorEvent ? "runtime_crash" : "ok");
|
|
58025
|
+
endDispatchSpan(dispatchSpan, metricsSnapshot, dispatchError, turnOutcomeEvent);
|
|
58026
|
+
recordDispatchMetric(event, this.opts.runtimeType, durationMs, effectiveOutcome);
|
|
58027
|
+
recordTurnUsage(turnOutcomeEvent?.usage, this.opts.runtimeType);
|
|
57640
58028
|
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) {
|
|
57641
|
-
recordMissingReply(this.opts.runtimeType);
|
|
58029
|
+
recordMissingReply(this.opts.runtimeType, effectiveOutcome);
|
|
57642
58030
|
}
|
|
57643
58031
|
clearDispatchMetrics(sessionKey);
|
|
57644
58032
|
if (turnHandle) {
|
|
@@ -57746,7 +58134,7 @@ var ParallAgentGateway = class {
|
|
|
57746
58134
|
item.resolve(false);
|
|
57747
58135
|
break;
|
|
57748
58136
|
}
|
|
57749
|
-
if (outcome === "failed") {
|
|
58137
|
+
if (outcome === "failed" || outcome === "deferred") {
|
|
57750
58138
|
for (const item of items)
|
|
57751
58139
|
item.resolve(false);
|
|
57752
58140
|
continue;
|
|
@@ -57790,7 +58178,7 @@ var ParallAgentGateway = class {
|
|
|
57790
58178
|
remaining.resolve(false);
|
|
57791
58179
|
}
|
|
57792
58180
|
} finally {
|
|
57793
|
-
this.
|
|
58181
|
+
this.turnOutcomes.delete(fork.fork.sessionKey);
|
|
57794
58182
|
if (fork.deadlineTimer) {
|
|
57795
58183
|
clearTimeout(fork.deadlineTimer);
|
|
57796
58184
|
fork.deadlineTimer = null;
|
|
@@ -57905,7 +58293,7 @@ var ParallAgentGateway = class {
|
|
|
57905
58293
|
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
57906
58294
|
continue;
|
|
57907
58295
|
}
|
|
57908
|
-
if (outcome === "failed") {
|
|
58296
|
+
if (outcome === "failed" || outcome === "deferred") {
|
|
57909
58297
|
this.dispatchState.pendingForkResults.unshift(...pendingFork);
|
|
57910
58298
|
continue;
|
|
57911
58299
|
}
|
|
@@ -58052,8 +58440,8 @@ var ParallAgentGateway = class {
|
|
|
58052
58440
|
this.dispatchState.mainBuffer.push(event);
|
|
58053
58441
|
const typedAheadInBuffer = this.dispatchState.mainBuffer.some((e) => e.type !== "message");
|
|
58054
58442
|
if (this.usesLaneLedger(event)) {
|
|
58055
|
-
if (!typedAheadInBuffer && this.mainCurrentGroupKey === this.dispatchGroupKey(event) && this.opts.dispatchAdapter.enqueueDuringDispatch != null
|
|
58056
|
-
this.
|
|
58443
|
+
if (!typedAheadInBuffer && this.mainCurrentGroupKey === this.dispatchGroupKey(event) && this.opts.dispatchAdapter.enqueueDuringDispatch != null) {
|
|
58444
|
+
await steerLaneMessage(this.laneFlowHost(), event);
|
|
58057
58445
|
}
|
|
58058
58446
|
} else if (
|
|
58059
58447
|
// Message events only. A typed event (task_comment/schedule/…)
|
|
@@ -60383,6 +60771,24 @@ ${event.body}` : event.body
|
|
|
60383
60771
|
};
|
|
60384
60772
|
});
|
|
60385
60773
|
}
|
|
60774
|
+
var OC_AUTH_TEXT = /unauthorized|401|403|invalid api key|authentication/i;
|
|
60775
|
+
var OC_CONTEXT_TEXT = /context (window|length)|prompt is too long|request too large|413/i;
|
|
60776
|
+
var OC_API_TEXT = /overloaded|429|rate limit|5\d\d|bad gateway|service unavailable|timeout/i;
|
|
60777
|
+
function classifyOpenClawFailure(err) {
|
|
60778
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
60779
|
+
let outcome = "runtime_crash";
|
|
60780
|
+
if (OC_AUTH_TEXT.test(message))
|
|
60781
|
+
outcome = "auth";
|
|
60782
|
+
else if (OC_CONTEXT_TEXT.test(message))
|
|
60783
|
+
outcome = "context_overflow";
|
|
60784
|
+
else if (OC_API_TEXT.test(message))
|
|
60785
|
+
outcome = "api_error";
|
|
60786
|
+
return {
|
|
60787
|
+
type: "turn_outcome",
|
|
60788
|
+
outcome,
|
|
60789
|
+
...message ? { detail: message.slice(0, 500) } : {}
|
|
60790
|
+
};
|
|
60791
|
+
}
|
|
60386
60792
|
function createRuntimeEventStream() {
|
|
60387
60793
|
const queue = [];
|
|
60388
60794
|
const waiters = [];
|
|
@@ -60537,7 +60943,10 @@ function createOpenClawDispatchAdapter(opts) {
|
|
|
60537
60943
|
}
|
|
60538
60944
|
}
|
|
60539
60945
|
});
|
|
60540
|
-
run.then(() => stream.end()).catch((err) =>
|
|
60946
|
+
run.then(() => stream.end()).catch((err) => {
|
|
60947
|
+
stream.push(classifyOpenClawFailure(err));
|
|
60948
|
+
stream.fail(err);
|
|
60949
|
+
});
|
|
60541
60950
|
let sessionId;
|
|
60542
60951
|
try {
|
|
60543
60952
|
sessionId = await Promise.race([
|