@parall/parall 1.63.0 → 1.65.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.
@@ -52248,6 +52248,67 @@ function describeRuntimeTurnTrigger(trigger) {
52248
52248
  }
52249
52249
  }
52250
52250
 
52251
+ // ../agent-core/dist/redact.js
52252
+ function redactSecrets(s, knownValues = []) {
52253
+ return maskKnownValues(s, knownValues).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|basic)\s+)[A-Za-z0-9._~+/=-]+/gi, "$1***").replace(/(\b(?:[\w-]*[_-])?(?:api[_-]?key|key|token|secret|password|passwd|authorization)["']?\s*[=:]\s*)(?:"(?:\\.|[^"\\\r\n])*"|'(?:\\.|[^'\\\r\n])*'|[^\s&;,}\]"']+)/gi, "$1***").replace(/[A-Za-z0-9_-]{32,}/g, "***");
52254
+ }
52255
+ function maskKnownValues(s, knownValues) {
52256
+ let out = s;
52257
+ for (const v of knownValues) {
52258
+ if (typeof v === "string" && v.length >= 6)
52259
+ out = out.split(v).join("***");
52260
+ }
52261
+ return out;
52262
+ }
52263
+ function redactTurnOutcome(event, knownValues) {
52264
+ const redacted = { ...event };
52265
+ if (redacted.detail)
52266
+ redacted.detail = redactSecrets(redacted.detail, knownValues);
52267
+ if (redacted.raw) {
52268
+ redacted.raw = Object.fromEntries(Object.entries(redacted.raw).map(([k, v]) => [
52269
+ k,
52270
+ typeof v === "string" ? redactSecrets(v, knownValues) : v
52271
+ ]));
52272
+ }
52273
+ return redacted;
52274
+ }
52275
+ function describeTurnOutcomeFailure(outcome) {
52276
+ const retryNote = outcome.retryAt ? `, retry at ${outcome.retryAt}` : "";
52277
+ return {
52278
+ warn: `${outcome.outcome}${retryNote}${outcome.detail ? ` \u2014 ${outcome.detail}` : ""}`,
52279
+ stepMessage: `LLM turn ${outcome.outcome}${retryNote}${outcome.detail ? `: ${outcome.detail}` : ""}`
52280
+ };
52281
+ }
52282
+ function redactLogger(log, knownValues) {
52283
+ if (!log)
52284
+ return void 0;
52285
+ return {
52286
+ info: (message) => log.info(redactSecrets(message, knownValues)),
52287
+ warn: (message) => log.warn(redactSecrets(message, knownValues)),
52288
+ error: (message) => log.error(redactSecrets(message, knownValues)),
52289
+ ...log.child ? { child: (name) => redactLogger(log.child(name), knownValues) } : {}
52290
+ };
52291
+ }
52292
+
52293
+ // ../agent-core/dist/runtime-observation.js
52294
+ async function persistRuntimeObservation(persister, sessionId, target, event, log, knownSecrets = []) {
52295
+ const native = event.content.native && Object.fromEntries(Object.entries(event.content.native).map(([key, value]) => [
52296
+ key,
52297
+ typeof value !== "string" ? value : key.endsWith("_id") ? maskKnownValues(value, knownSecrets) : redactSecrets(value, knownSecrets)
52298
+ ]));
52299
+ try {
52300
+ await persister.persist(sessionId, "observation", {
52301
+ step_type: "observation",
52302
+ ...target,
52303
+ idempotency_key: `observation:${event.observationId}`,
52304
+ content: { ...event.content, ...native ? { native } : {} },
52305
+ projection: false
52306
+ });
52307
+ } catch (err) {
52308
+ log?.warn(`runtime observation could not be saved for ${sessionId}: ${String(err)}`);
52309
+ }
52310
+ }
52311
+
52251
52312
  // ../agent-core/dist/fork-prefix.js
52252
52313
  function sanitizeMeta(value) {
52253
52314
  return value.replace(/[\r\n]+/g, " ").replace(/[[\]|]/g, " ").trim();
@@ -52286,6 +52347,143 @@ function buildForkResultPrefix(results) {
52286
52347
  return blocks.join("\n\n") + "\n\n---\n\n";
52287
52348
  }
52288
52349
 
52350
+ // ../agent-core/dist/gateway-runtime-step.js
52351
+ import { randomUUID } from "node:crypto";
52352
+ async function createRuntimeStep(host, sessionId, target, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2) {
52353
+ switch (runtimeEvent.type) {
52354
+ case "observation":
52355
+ await persistRuntimeObservation(host.stepPersister, sessionId, target, runtimeEvent, host.opts?.log, host.opts?.config ? [host.opts.config.api_key] : []);
52356
+ break;
52357
+ case "thinking":
52358
+ await host.stepPersister.persist(sessionId, "thinking", {
52359
+ step_type: "thinking",
52360
+ target_type: target.target_type,
52361
+ target_id: target.target_id,
52362
+ idempotency_key: randomUUID(),
52363
+ content: { text: runtimeEvent.text },
52364
+ group_key: runtimeEvent.groupKey
52365
+ });
52366
+ break;
52367
+ case "text":
52368
+ await host.stepPersister.persist(sessionId, "text", {
52369
+ step_type: "text",
52370
+ target_type: target.target_type,
52371
+ target_id: target.target_id,
52372
+ idempotency_key: randomUUID(),
52373
+ content: {
52374
+ text: runtimeEvent.text,
52375
+ suppressed: runtimeEvent.project !== true
52376
+ },
52377
+ projection: runtimeEvent.project === true,
52378
+ group_key: runtimeEvent.groupKey
52379
+ });
52380
+ break;
52381
+ case "tool_call": {
52382
+ const step = await host.stepPersister.persist(sessionId, "tool_call", {
52383
+ step_type: "tool_call",
52384
+ target_type: target.target_type,
52385
+ target_id: target.target_id,
52386
+ // call_id is session-unique for bridge runtimes (server-enforced),
52387
+ // so the bare form anchors the tool step pair across retries —
52388
+ // unlike parel's turn-scoped `tc:{turnId}:{callId}` (see
52389
+ // protocol-vectors/agent-steps.json).
52390
+ idempotency_key: `tc:${runtimeEvent.callId}`,
52391
+ content: {
52392
+ call_id: runtimeEvent.callId,
52393
+ tool_name: runtimeEvent.toolName,
52394
+ tool_input: runtimeEvent.input,
52395
+ status: "running",
52396
+ started_at: runtimeEvent.startedAt ?? (/* @__PURE__ */ new Date()).toISOString()
52397
+ },
52398
+ group_key: runtimeEvent.groupKey,
52399
+ runtime_key: runtimeEvent.callId
52400
+ });
52401
+ if (step) {
52402
+ if (contextFilePath) {
52403
+ host.updateContextFileStepId(contextFilePath, step.id);
52404
+ } else if (stepIdFilePath) {
52405
+ host.writeStepIdFile(stepIdFilePath, step.id);
52406
+ }
52407
+ if (laneContextFilePath2) {
52408
+ host.updateContextFileStepId(laneContextFilePath2, step.id);
52409
+ }
52410
+ }
52411
+ break;
52412
+ }
52413
+ case "tool_result":
52414
+ await host.stepPersister.persist(sessionId, "tool_result", {
52415
+ step_type: "tool_result",
52416
+ target_type: target.target_type,
52417
+ target_id: target.target_id,
52418
+ idempotency_key: `tr:${runtimeEvent.callId}`,
52419
+ content: {
52420
+ call_id: runtimeEvent.callId,
52421
+ tool_name: runtimeEvent.toolName,
52422
+ status: runtimeEvent.error ? "error" : "success",
52423
+ output: runtimeEvent.output,
52424
+ duration_ms: runtimeEvent.durationMs ?? 0,
52425
+ collapsible: true
52426
+ },
52427
+ group_key: runtimeEvent.groupKey
52428
+ });
52429
+ if (contextFilePath) {
52430
+ host.updateContextFileStepId(contextFilePath, null);
52431
+ } else if (stepIdFilePath) {
52432
+ host.clearStepIdFile(stepIdFilePath);
52433
+ }
52434
+ if (laneContextFilePath2) {
52435
+ host.updateContextFileStepId(laneContextFilePath2, null);
52436
+ }
52437
+ break;
52438
+ case "error":
52439
+ await host.stepPersister.persist(sessionId, "error", {
52440
+ step_type: "text",
52441
+ target_type: target.target_type,
52442
+ target_id: target.target_id,
52443
+ idempotency_key: randomUUID(),
52444
+ content: buildErrorStepContent(runtimeEvent.message),
52445
+ projection: false,
52446
+ group_key: runtimeEvent.groupKey
52447
+ });
52448
+ break;
52449
+ }
52450
+ }
52451
+
52452
+ // ../agent-core/dist/dispatch-recovery.js
52453
+ var DispatchRecovery = class {
52454
+ scan;
52455
+ stopped;
52456
+ onError;
52457
+ running = false;
52458
+ requested = false;
52459
+ constructor(scan, stopped, onError) {
52460
+ this.scan = scan;
52461
+ this.stopped = stopped;
52462
+ this.onError = onError;
52463
+ }
52464
+ request = () => {
52465
+ if (this.stopped())
52466
+ return;
52467
+ this.requested = true;
52468
+ if (this.running)
52469
+ return;
52470
+ this.running = true;
52471
+ void this.run();
52472
+ };
52473
+ async run() {
52474
+ try {
52475
+ while (this.requested && !this.stopped()) {
52476
+ this.requested = false;
52477
+ await this.scan();
52478
+ }
52479
+ } catch (err) {
52480
+ this.onError(err);
52481
+ } finally {
52482
+ this.running = false;
52483
+ }
52484
+ }
52485
+ };
52486
+
52289
52487
  // ../agent-core/dist/gateway-base.js
52290
52488
  import * as os from "node:os";
52291
52489
 
@@ -52310,7 +52508,7 @@ function splitChangeSource(sourceId) {
52310
52508
  // ../agent-core/dist/gateway-base.js
52311
52509
  import * as fs4 from "node:fs";
52312
52510
  import * as path5 from "node:path";
52313
- import { randomUUID } from "node:crypto";
52511
+ import { randomUUID as randomUUID2 } from "node:crypto";
52314
52512
 
52315
52513
  // ../sdk/dist/browser-viewer.js
52316
52514
  var BROWSER_VIEWER_READINESS_TIMEOUT_MS = 6e4;
@@ -52323,8 +52521,23 @@ function browserViewerRequestOptions(command, opts) {
52323
52521
  };
52324
52522
  }
52325
52523
 
52524
+ // ../sdk/dist/slack-endpoints.js
52525
+ function slackEndpoints(apiBase) {
52526
+ return {
52527
+ SLACK_CHANNELS: (orgId) => `${apiBase}/orgs/${orgId}/agents/me/slack/channels`,
52528
+ SLACK_USERS: (orgId) => `${apiBase}/orgs/${orgId}/agents/me/slack/users`,
52529
+ SLACK_REPLIES: (orgId) => `${apiBase}/orgs/${orgId}/agents/me/slack/replies`,
52530
+ SLACK_HISTORY: (orgId) => `${apiBase}/orgs/${orgId}/agents/me/slack/history`,
52531
+ SLACK_MEMBERS: (orgId) => `${apiBase}/orgs/${orgId}/agents/me/slack/members`,
52532
+ SLACK_STATUS: (orgId) => `${apiBase}/orgs/${orgId}/agents/me/slack/status`,
52533
+ SLACK_FILE: (orgId) => `${apiBase}/orgs/${orgId}/agents/me/slack/file`,
52534
+ SLACK_FILES: (orgId) => `${apiBase}/orgs/${orgId}/agents/me/slack/files`
52535
+ };
52536
+ }
52537
+
52326
52538
  // ../sdk/dist/clip-connection-endpoints.js
52327
52539
  var clipConnectionEndpoints = {
52540
+ ORG_CLIP_MCP_OAUTH_ATTEMPT: (orgId, clipId, attemptId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-oauth-attempts/${attemptId}`,
52328
52541
  CLIP_CONNECTIONS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/connections`,
52329
52542
  CLIP_CONNECTION_ACCESS: (orgId, connId) => `/api/v1/orgs/${orgId}/clip-connections/${connId}/access`,
52330
52543
  CLIP_CONNECTION: (orgId, connId) => `/api/v1/orgs/${orgId}/clip-connections/${connId}`
@@ -52625,6 +52838,7 @@ var ENDPOINTS = {
52625
52838
  TRIGGER: (orgId, triggerId) => `${API_BASE2}/orgs/${orgId}/external-triggers/${triggerId}`,
52626
52839
  /** @deprecated Use TRIGGER. Same compatibility path. */
52627
52840
  EXTERNAL_TRIGGER: (orgId, triggerId) => `${API_BASE2}/orgs/${orgId}/external-triggers/${triggerId}`,
52841
+ PLATFORM_TRIGGER_SCHEMA: (orgId) => `${API_BASE2}/orgs/${orgId}/platform-trigger-schema`,
52628
52842
  TRIGGER_RUNS: (orgId) => `${API_BASE2}/orgs/${orgId}/external-trigger-runs`,
52629
52843
  /** @deprecated Use TRIGGER_RUNS. Same compatibility path. */
52630
52844
  EXTERNAL_TRIGGER_RUNS: (orgId) => `${API_BASE2}/orgs/${orgId}/external-trigger-runs`,
@@ -52649,13 +52863,7 @@ var ENDPOINTS = {
52649
52863
  // Tier-B platform verb (agent-only): send one message as the bound bot.
52650
52864
  CHANNEL_SEND: (orgId) => `${API_BASE2}/orgs/${orgId}/agents/me/channel-send`,
52651
52865
  // Tier-B read verbs (agent-only): workspace visibility as the bot sees it.
52652
- SLACK_CHANNELS: (orgId) => `${API_BASE2}/orgs/${orgId}/agents/me/slack/channels`,
52653
- SLACK_USERS: (orgId) => `${API_BASE2}/orgs/${orgId}/agents/me/slack/users`,
52654
- SLACK_HISTORY: (orgId) => `${API_BASE2}/orgs/${orgId}/agents/me/slack/history`,
52655
- SLACK_MEMBERS: (orgId) => `${API_BASE2}/orgs/${orgId}/agents/me/slack/members`,
52656
- SLACK_STATUS: (orgId) => `${API_BASE2}/orgs/${orgId}/agents/me/slack/status`,
52657
- SLACK_FILE: (orgId) => `${API_BASE2}/orgs/${orgId}/agents/me/slack/file`,
52658
- SLACK_FILES: (orgId) => `${API_BASE2}/orgs/${orgId}/agents/me/slack/files`,
52866
+ ...slackEndpoints(API_BASE2),
52659
52867
  // WeChat tier-B read verbs (agent-only; internal research preview).
52660
52868
  ...wechatEndpoints(API_BASE2),
52661
52869
  // Invitations (org-scoped, admin)
@@ -52802,6 +53010,8 @@ var ENDPOINTS = {
52802
53010
  BILLING_CHECKOUT: (orgId) => `${API_BASE2}/orgs/${orgId}/billing/checkout`,
52803
53011
  BILLING_AUTO_RELOAD: (orgId) => `${API_BASE2}/orgs/${orgId}/billing/auto-reload`,
52804
53012
  BILLING_SETUP_INTENT: (orgId) => `${API_BASE2}/orgs/${orgId}/billing/setup-intent`,
53013
+ BILLING_USAGE_DAILY: (orgId) => `${API_BASE2}/orgs/${orgId}/billing/usage-daily`,
53014
+ BILLING_USAGE_HEATMAP: (orgId) => `${API_BASE2}/orgs/${orgId}/billing/usage-heatmap`,
52805
53015
  COMPUTE_PRICING: () => `${API_BASE2}/billing/compute-pricing`,
52806
53016
  // Runtime capability table (public, no auth) — SSOT for the create/settings
52807
53017
  // interlock: per-runtime compute modes, native model family, cross-family
@@ -52836,16 +53046,6 @@ var ENDPOINTS = {
52836
53046
  BROWSER_PROFILE_VIEWER_COMMAND: (orgId, profileId) => `${API_BASE2}/orgs/${orgId}/browser-profiles/${profileId}/viewer/command`,
52837
53047
  // Edge device endpoints
52838
53048
  ...edgeEndpoints,
52839
- ORG_BROWSER_ALIASES: (orgId) => `/api/v1/orgs/${orgId}/browser-aliases`,
52840
- ORG_BROWSER_ALIAS: (orgId, aliasId) => `/api/v1/orgs/${orgId}/browser-aliases/${aliasId}`,
52841
- ORG_BROWSER_ALIAS_READINESS_CHECKS: (orgId, aliasId) => `/api/v1/orgs/${orgId}/browser-aliases/${aliasId}/readiness-checks`,
52842
- ORG_BROWSER_ALIAS_READINESS_CHECK: (orgId, aliasId, checkId) => `/api/v1/orgs/${orgId}/browser-aliases/${aliasId}/readiness-checks/${checkId}`,
52843
- ORG_API_KEY_BROWSER_ALIAS_GRANTS: (orgId, keyId) => `/api/v1/orgs/${orgId}/api-keys/${keyId}/browser-alias-grants`,
52844
- ORG_API_KEY_BROWSER_ALIAS_GRANT: (orgId, keyId, aliasId) => `/api/v1/orgs/${orgId}/api-keys/${keyId}/browser-alias-grants/${aliasId}`,
52845
- ORG_MANAGED_ALIASES: (orgId) => `/api/v1/orgs/${orgId}/aliases`,
52846
- ORG_MANAGED_ALIAS: (orgId, aliasName) => `/api/v1/orgs/${orgId}/aliases/${encodeURIComponent(aliasName)}`,
52847
- ORG_BROWSER_ALIAS_EXECUTIONS: (orgId) => `/api/v1/orgs/${orgId}/executions`,
52848
- ORG_BROWSER_ALIAS_EXECUTION: (orgId, requestId) => `/api/v1/orgs/${orgId}/executions/${encodeURIComponent(requestId)}`,
52849
53049
  ORG_BROWSER_USE_OPERATIONS: (orgId) => `/api/v1/orgs/${orgId}/browser-use/operations`,
52850
53050
  ORG_BROWSER_USE_OPERATION: (orgId, operationId) => `/api/v1/orgs/${orgId}/browser-use/operations/${encodeURIComponent(operationId)}`,
52851
53051
  // Per-profile egress proxy (hosted Cloud Profiles; manager-only, human-only).
@@ -53345,14 +53545,16 @@ var SlackFilesClient = class extends AttachmentClient {
53345
53545
  }
53346
53546
  /**
53347
53547
  * Tier-B file upload verb (agent-only): share a file into a Slack
53348
- * conversation this connection has seen inbound, with the same
53349
- * reply-anchor contract as the text send.
53548
+ * conversation or thread with the same addressing as text sends.
53549
+ * Native threadTs requires a server supporting Slack thread sends.
53350
53550
  */
53351
53551
  async sendSlackFile(orgId, input) {
53352
53552
  const fd = new FormData();
53353
53553
  fd.append("conversation_id", input.conversationId);
53354
53554
  if (input.replyTo)
53355
53555
  fd.append("reply_to", input.replyTo);
53556
+ if (input.threadTs)
53557
+ fd.append("thread_ts", input.threadTs);
53356
53558
  if (input.text)
53357
53559
  fd.append("text", input.text);
53358
53560
  fd.append("file", input.content, input.fileName);
@@ -53360,8 +53562,48 @@ var SlackFilesClient = class extends AttachmentClient {
53360
53562
  }
53361
53563
  };
53362
53564
 
53565
+ // ../sdk/dist/slack-read-client.js
53566
+ var SlackReadClient = class extends SlackFilesClient {
53567
+ slackReadQuery(base, query, extra) {
53568
+ const params = new URLSearchParams();
53569
+ if (query?.cursor)
53570
+ params.set("cursor", query.cursor);
53571
+ if (query?.limit)
53572
+ params.set("limit", String(query.limit));
53573
+ for (const [k, v] of Object.entries(extra ?? {}))
53574
+ params.set(k, v);
53575
+ const qs = params.toString();
53576
+ return qs ? `${base}?${qs}` : base;
53577
+ }
53578
+ /**
53579
+ * Tier-B read verbs (agent-only): workspace visibility as the bot sees
53580
+ * it. Same live gate as the send verb; authorization beyond it is the
53581
+ * bot's own Slack permissions.
53582
+ */
53583
+ async listSlackChannels(orgId, query) {
53584
+ return this.request("GET", this.slackReadQuery(ENDPOINTS.SLACK_CHANNELS(orgId), query));
53585
+ }
53586
+ async listSlackUsers(orgId, query) {
53587
+ return this.request("GET", this.slackReadQuery(ENDPOINTS.SLACK_USERS(orgId), query));
53588
+ }
53589
+ async slackHistory(orgId, conversationId, query) {
53590
+ return this.request("GET", this.slackReadQuery(ENDPOINTS.SLACK_HISTORY(orgId), query, { conversation: conversationId }));
53591
+ }
53592
+ /** Read a Slack thread (parent and replies), one page in Slack order. */
53593
+ async slackReplies(orgId, channel, ts, query) {
53594
+ return this.request("GET", this.slackReadQuery(ENDPOINTS.SLACK_REPLIES(orgId), query, { channel, ts }));
53595
+ }
53596
+ async slackMembers(orgId, conversationId, query) {
53597
+ return this.request("GET", this.slackReadQuery(ENDPOINTS.SLACK_MEMBERS(orgId), query, { conversation: conversationId }));
53598
+ }
53599
+ /** Set/clear the Agents-pane "typing…" indicator (best-effort cosmetic). */
53600
+ async setSlackStatus(orgId, input) {
53601
+ await this.request("POST", ENDPOINTS.SLACK_STATUS(orgId), input);
53602
+ }
53603
+ };
53604
+
53363
53605
  // ../sdk/dist/channel-conversation-client.js
53364
- var ChannelConversationClient = class extends SlackFilesClient {
53606
+ var ChannelConversationClient = class extends SlackReadClient {
53365
53607
  /**
53366
53608
  * Org-admin write of what the agent receives from a group root
53367
53609
  * (`PATCH …/channel-conversations/{id}/attention`); the agent's own path
@@ -53392,10 +53634,62 @@ var ClipConnectionClient = class extends ChannelConversationClient {
53392
53634
  async putClipConnectionAccess(orgId, connId, input) {
53393
53635
  return this.request("PUT", ENDPOINTS.CLIP_CONNECTION_ACCESS(orgId, connId), input);
53394
53636
  }
53637
+ /** Start OAuth using the reviewed platform app or provider registration ladder.
53638
+ * Allow discovery (30s), persistence (10s), and network overhead to finish. */
53639
+ async createClipMCPOAuthConnection(orgId, clipId, serverUrl, alias, oauthClient) {
53640
+ return this.request("POST", ENDPOINTS.ORG_CLIP_MCP_CONFIGS(orgId, clipId), {
53641
+ server_url: serverUrl,
53642
+ auth_type: "oauth",
53643
+ ...alias ? { alias } : {},
53644
+ ...oauthClient ? { oauth_client: oauthClient } : {}
53645
+ }, void 0, false, { timeoutMs: 5e4 });
53646
+ }
53647
+ /** Cancel exactly one pending attempt; requires its initiator or an org admin. */
53648
+ async cancelClipMCPOAuthAttempt(orgId, clipId, attemptId) {
53649
+ return this.request("DELETE", ENDPOINTS.ORG_CLIP_MCP_OAUTH_ATTEMPT(orgId, clipId, attemptId));
53650
+ }
53395
53651
  };
53396
53652
 
53397
53653
  // ../sdk/dist/browser-profile-client.js
53398
53654
  var BrowserProfileClient = class extends ClipConnectionClient {
53655
+ /** Metadata-only compatibility discovery. Management scope errors propagate;
53656
+ * callers with a known Profile can execute without this discovery chain. */
53657
+ async listBrowserUseProfiles(orgId, profileId) {
53658
+ const me = await this.request("GET", ENDPOINTS.USERS_ME);
53659
+ const devices = await this.request("GET", ENDPOINTS.ORG_EDGE_DEVICES(orgId));
53660
+ const data = [];
53661
+ for (const device of devices.data) {
53662
+ const personal = device.managed_by === "member";
53663
+ if (personal && device.owner_user_id !== me.id)
53664
+ continue;
53665
+ const profiles = await this.request("GET", ENDPOINTS.ORG_EDGE_PROFILES(orgId, device.id));
53666
+ for (const profile of profiles) {
53667
+ if (profileId && profile.id !== profileId)
53668
+ continue;
53669
+ const access = personal ? null : await this.getBrowserProfileAccess(orgId, device.id, profile.name);
53670
+ if (!personal && !access?.org_public && !(access?.grants ?? []).some((g) => g.user_id === me.id && g.grants.length > 0))
53671
+ continue;
53672
+ data.push({
53673
+ profile_id: profile.id,
53674
+ name: profile.name,
53675
+ device_name: device.name,
53676
+ placement: device.placement ?? "byoc",
53677
+ lifecycle_state: profile.lifecycle_state,
53678
+ can_access: true,
53679
+ availability: device.placement === "hosted" ? device.hosted_state ?? "unknown" : device.status
53680
+ });
53681
+ }
53682
+ }
53683
+ return { data };
53684
+ }
53685
+ /** Submit once: even refresh-enabled clients must not replay browser actions. */
53686
+ async createBrowserUseOperation(orgId, request3) {
53687
+ return this.request("POST", ENDPOINTS.ORG_BROWSER_USE_OPERATIONS(orgId), request3, void 0, true);
53688
+ }
53689
+ /** Recover proof/data without creating a new browser operation. */
53690
+ async getBrowserUseOperation(orgId, operationId, opts) {
53691
+ return this.request("GET", ENDPOINTS.ORG_BROWSER_USE_OPERATION(orgId, operationId), void 0, void 0, false, opts);
53692
+ }
53399
53693
  async getBrowserProfileAccess(orgId, edgeId, profileName) {
53400
53694
  return this.request("GET", ENDPOINTS.ORG_EDGE_PROFILE_ACCESS(orgId, edgeId, profileName));
53401
53695
  }
@@ -53839,8 +54133,8 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
53839
54133
  return this.request("POST", ENDPOINTS.AUTH_OAUTH_EXCHANGE, { code });
53840
54134
  }
53841
54135
  // ---- WebSocket ----
53842
- async getWsTicket() {
53843
- return this.request("POST", ENDPOINTS.WS_TICKET);
54136
+ async getWsTicket(req) {
54137
+ return this.request("POST", ENDPOINTS.WS_TICKET, req);
53844
54138
  }
53845
54139
  // ---- Users ----
53846
54140
  async getMe() {
@@ -54827,8 +55121,8 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
54827
55121
  * no_action sweep otherwise. Idempotent.
54828
55122
  *
54829
55123
  * @deprecated Legacy ok-only alias — use {@link completeDispatch} with the
54830
- * `sources` form, which also carries `turn_outcome`. The endpoint retires
54831
- * at S3b (dispatch-convergence-design.md §3).
55124
+ * `sources` form, which also carries `turn_outcome`. Retirement conditions:
55125
+ * agent-dispatch-idempotency-design.md#compatibility.
54832
55126
  */
54833
55127
  async completeDispatchSources(orgId, req) {
54834
55128
  return this.request("POST", ENDPOINTS.DISPATCH_COMPLETE_SOURCES(orgId), req);
@@ -54983,38 +55277,6 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
54983
55277
  async sendChannelMessage(orgId, input) {
54984
55278
  return this.request("POST", ENDPOINTS.CHANNEL_SEND(orgId), input);
54985
55279
  }
54986
- slackReadQuery(base, query, extra) {
54987
- const params = new URLSearchParams();
54988
- if (query?.cursor)
54989
- params.set("cursor", query.cursor);
54990
- if (query?.limit)
54991
- params.set("limit", String(query.limit));
54992
- for (const [k, v] of Object.entries(extra ?? {}))
54993
- params.set(k, v);
54994
- const qs = params.toString();
54995
- return qs ? `${base}?${qs}` : base;
54996
- }
54997
- /**
54998
- * Tier-B read verbs (agent-only): workspace visibility as the bot sees
54999
- * it. Same live gate as the send verb; authorization beyond it is the
55000
- * bot's own Slack permissions.
55001
- */
55002
- async listSlackChannels(orgId, query) {
55003
- return this.request("GET", this.slackReadQuery(ENDPOINTS.SLACK_CHANNELS(orgId), query));
55004
- }
55005
- async listSlackUsers(orgId, query) {
55006
- return this.request("GET", this.slackReadQuery(ENDPOINTS.SLACK_USERS(orgId), query));
55007
- }
55008
- async slackHistory(orgId, conversationId, query) {
55009
- return this.request("GET", this.slackReadQuery(ENDPOINTS.SLACK_HISTORY(orgId), query, { conversation: conversationId }));
55010
- }
55011
- async slackMembers(orgId, conversationId, query) {
55012
- return this.request("GET", this.slackReadQuery(ENDPOINTS.SLACK_MEMBERS(orgId), query, { conversation: conversationId }));
55013
- }
55014
- /** Set/clear the Agents-pane "typing…" indicator (best-effort cosmetic). */
55015
- async setSlackStatus(orgId, input) {
55016
- await this.request("POST", ENDPOINTS.SLACK_STATUS(orgId), input);
55017
- }
55018
55280
  /**
55019
55281
  * Authorized raw GET (binary responses) with the same auth, 401
55020
55282
  * refresh-and-retry-once, and error-envelope handling as `request` — the
@@ -55071,6 +55333,9 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
55071
55333
  async getTriggerSchema(orgId, connectionId) {
55072
55334
  return this.request("GET", ENDPOINTS.TRIGGER_SCHEMA(orgId, connectionId));
55073
55335
  }
55336
+ async getPlatformTriggerSchema(orgId, eventTypes) {
55337
+ return this.request("GET", ENDPOINTS.PLATFORM_TRIGGER_SCHEMA(orgId), void 0, eventTypes && eventTypes.length > 0 ? { event_types: eventTypes.join(",") } : void 0);
55338
+ }
55074
55339
  async listTriggerIngressEvents(orgId, filters) {
55075
55340
  return this.request("GET", ENDPOINTS.TRIGGER_INGRESS_EVENTS(orgId), void 0, filters);
55076
55341
  }
@@ -55172,8 +55437,8 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
55172
55437
  return url;
55173
55438
  return `${this.baseUrlFor(url)}${url}`;
55174
55439
  }
55175
- async getWikiBlob(orgId, wikiId, params) {
55176
- const blob = await this.request("GET", ENDPOINTS.WIKI_BLOB(orgId, wikiId), void 0, params);
55440
+ async getWikiBlob(orgId, wikiId, params, opts) {
55441
+ const blob = await this.request("GET", ENDPOINTS.WIKI_BLOB(orgId, wikiId), void 0, params, false, opts);
55177
55442
  if (blob.signed_url)
55178
55443
  blob.signed_url = this.absoluteMediaUrl(blob.signed_url);
55179
55444
  return blob;
@@ -55546,6 +55811,21 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
55546
55811
  async getBilling(orgId) {
55547
55812
  return this.request("GET", ENDPOINTS.BILLING(orgId));
55548
55813
  }
55814
+ async getUsageDaily(orgId, opts) {
55815
+ const params = new URLSearchParams();
55816
+ params.set("start_date", opts.start_date);
55817
+ params.set("end_date", opts.end_date);
55818
+ return this.request("GET", `${ENDPOINTS.BILLING_USAGE_DAILY(orgId)}?${params}`);
55819
+ }
55820
+ async getUsageHeatmap(orgId, opts) {
55821
+ const params = new URLSearchParams();
55822
+ params.set("start_time", opts.start_time);
55823
+ if (opts.limit != null)
55824
+ params.set("limit", String(opts.limit));
55825
+ if (opts.offset != null)
55826
+ params.set("offset", String(opts.offset));
55827
+ return this.request("GET", `${ENDPOINTS.BILLING_USAGE_HEATMAP(orgId)}?${params}`);
55828
+ }
55549
55829
  async listBillingTransactions(orgId, opts) {
55550
55830
  const params = new URLSearchParams();
55551
55831
  if (opts?.cursor)
@@ -55715,12 +55995,14 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
55715
55995
  }
55716
55996
  // ---- Clip registry (v3, api-server org registry — `crg_` entries) ----
55717
55997
  /**
55718
- * List registry clips visible to the org: its own plus public+approved
55719
- * cross-org entries. This is the surface `installRegistryClip` and clip
55720
- * connections operate on.
55998
+ * List one page of registry clips visible to the org: its own plus
55999
+ * public+approved cross-org entries, used by installs and connections.
55721
56000
  */
55722
- async listOrgRegistryClips(orgId) {
55723
- const resp = await this.request("GET", `${ENDPOINTS.ORG_CLIP_REGISTRY(orgId)}?limit=100`);
56001
+ async listOrgRegistryClips(orgId, opts) {
56002
+ const params = new URLSearchParams({ limit: String(opts?.limit ?? 100) });
56003
+ if (opts?.offset !== void 0)
56004
+ params.set("offset", String(opts.offset));
56005
+ const resp = await this.request("GET", `${ENDPOINTS.ORG_CLIP_REGISTRY(orgId)}?${params}`);
55724
56006
  return resp ?? [];
55725
56007
  }
55726
56008
  /**
@@ -55801,7 +56083,6 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
55801
56083
  /**
55802
56084
  * Read a clip's per-org exec access (by user id — humans and agents alike).
55803
56085
  * Org-member readable, so a denied caller can learn why exec answered
55804
- * `CONNECTION_NOT_ALLOWED` (retired: `CLIP_NOT_ALLOWED` / `CLIP_CONNECTION_NOT_ALLOWED`) (agents:
55805
56086
  * `CLIP_AGENT_NOT_ALLOWED` / `CLIP_AGENT_CONNECTION_NOT_ALLOWED`). Writing
55806
56087
  * it (`putClipAgentExecAccess`) is an org owner/admin's act.
55807
56088
  */
@@ -55882,69 +56163,6 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
55882
56163
  async getEdgeProfileOperation(orgId, edgeId, operationId) {
55883
56164
  return this.request("GET", ENDPOINTS.ORG_EDGE_PROFILE_OPERATION(orgId, edgeId, operationId));
55884
56165
  }
55885
- /** Create and atomically materialize one Local BYOC Browser Alias. A replay
55886
- * with the same idempotency key and body returns the original stable ccn_. */
55887
- async createBrowserAlias(orgId, request3) {
55888
- return this.request("POST", ENDPOINTS.ORG_BROWSER_ALIASES(orgId), request3);
55889
- }
55890
- async listBrowserAliases(orgId) {
55891
- return this.request("GET", ENDPOINTS.ORG_BROWSER_ALIASES(orgId));
55892
- }
55893
- async getBrowserAlias(orgId, aliasId) {
55894
- return this.request("GET", ENDPOINTS.ORG_BROWSER_ALIAS(orgId, aliasId));
55895
- }
55896
- async renameBrowserAlias(orgId, aliasId, request3) {
55897
- return this.request("PATCH", ENDPOINTS.ORG_BROWSER_ALIAS(orgId, aliasId), request3);
55898
- }
55899
- async deleteBrowserAlias(orgId, aliasId, request3) {
55900
- return this.request("DELETE", ENDPOINTS.ORG_BROWSER_ALIAS(orgId, aliasId), request3);
55901
- }
55902
- /** Start one durable readiness check. The key is carried as the standard
55903
- * Idempotency-Key header; the request body is intentionally empty. */
55904
- async createBrowserAliasReadinessCheck(orgId, aliasId, request3) {
55905
- return this.request("POST", ENDPOINTS.ORG_BROWSER_ALIAS_READINESS_CHECKS(orgId, aliasId), void 0, void 0, false, { headers: { "Idempotency-Key": request3.idempotency_key } });
55906
- }
55907
- /** Sole durable recovery surface for one readiness request. */
55908
- async getBrowserAliasReadinessCheck(orgId, aliasId, checkId) {
55909
- return this.request("GET", ENDPOINTS.ORG_BROWSER_ALIAS_READINESS_CHECK(orgId, aliasId, checkId));
55910
- }
55911
- /** Organization Owner view. Hashes, reviewed snapshots and deltas are
55912
- * intentionally available only on this human-JWT management surface. */
55913
- async listBrowserAliasGrants(orgId, keyId) {
55914
- return this.request("GET", ENDPOINTS.ORG_API_KEY_BROWSER_ALIAS_GRANTS(orgId, keyId));
55915
- }
55916
- async getBrowserAliasGrant(orgId, keyId, aliasId) {
55917
- return this.request("GET", ENDPOINTS.ORG_API_KEY_BROWSER_ALIAS_GRANT(orgId, keyId, aliasId));
55918
- }
55919
- async replaceBrowserAliasGrant(orgId, keyId, aliasId, request3) {
55920
- return this.request("PUT", ENDPOINTS.ORG_API_KEY_BROWSER_ALIAS_GRANT(orgId, keyId, aliasId), request3);
55921
- }
55922
- /** Managed-key discovery. The response is already grant-filtered and never
55923
- * carries contract hashes, review snapshots, Edge IDs or Profile IDs. */
55924
- async listManagedBrowserAliases(orgId) {
55925
- return this.request("GET", ENDPOINTS.ORG_MANAGED_ALIASES(orgId));
55926
- }
55927
- async getManagedBrowserAlias(orgId, aliasName) {
55928
- return this.request("GET", ENDPOINTS.ORG_MANAGED_ALIAS(orgId, aliasName));
55929
- }
55930
- /** Accept and dispatch one @alias command. request_id is generated by the
55931
- * caller before local validation and is also the sole durable recovery key. */
55932
- async createBrowserAliasExecution(orgId, request3) {
55933
- return this.request("POST", ENDPOINTS.ORG_BROWSER_ALIAS_EXECUTIONS(orgId), request3);
55934
- }
55935
- async getBrowserAliasExecution(orgId, requestId) {
55936
- return this.request("GET", ENDPOINTS.ORG_BROWSER_ALIAS_EXECUTION(orgId, requestId));
55937
- }
55938
- /** Accept one idempotent Browser Use operation for an explicit Profile.
55939
- * JWT, personal, agent and managed keys use profile access. The Server resolves Profile→Edge. */
55940
- async createBrowserUseOperation(orgId, request3) {
55941
- return this.request("POST", ENDPOINTS.ORG_BROWSER_USE_OPERATIONS(orgId), request3);
55942
- }
55943
- /** Recover the durable receipt. Successful data is present only inside the
55944
- * Server's short encrypted result TTL; terminal proof remains afterwards. */
55945
- async getBrowserUseOperation(orgId, operationId) {
55946
- return this.request("GET", ENDPOINTS.ORG_BROWSER_USE_OPERATION(orgId, operationId));
55947
- }
55948
56166
  /**
55949
56167
  * Read a hosted Cloud Profile's egress-proxy status (manager-only: hosted
55950
56168
  * human maintainer or org admin). Sanitized — the password never comes back.
@@ -56054,7 +56272,6 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
56054
56272
  * - `EDGE_HOSTED_DISABLED_FOR_ORG` 403, `EDGE_REPAIR` 503 (operator-held),
56055
56273
  * `EDGE_RUNTIME_UNAVAILABLE` 503 (deployment has no hosted runtime).
56056
56274
  * - Access errors (before side effects): human `CONNECTION_NOT_ALLOWED`.
56057
- * `CLIP_NOT_ALLOWED` / `CLIP_CONNECTION_NOT_ALLOWED` are retired.
56058
56275
  * Agent principals receive the historical
56059
56276
  * `CLIP_AGENT_NOT_ALLOWED` / `CLIP_AGENT_CONNECTION_NOT_ALLOWED` for one
56060
56277
  * release. An org owner/admin can widen access in the clip's settings.
@@ -56138,17 +56355,6 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
56138
56355
  timeoutMs: 4e4
56139
56356
  });
56140
56357
  }
56141
- /** Start a new Official OAuth MCP connection with the platform-owned app.
56142
- * Omitting oauth_client is load-bearing: the server resolves the exact
56143
- * reviewed-version binding and never falls back to DCR/CIMD cross-org. */
56144
- async createClipMCPOAuthConnection(orgId, clipId, serverUrl, alias, oauthClient) {
56145
- return this.request("POST", ENDPOINTS.ORG_CLIP_MCP_CONFIGS(orgId, clipId), {
56146
- server_url: serverUrl,
56147
- auth_type: "oauth",
56148
- ...alias ? { alias } : {},
56149
- ...oauthClient ? { oauth_client: oauthClient } : {}
56150
- });
56151
- }
56152
56358
  /** Re-authorize an existing MCP credential slot. */
56153
56359
  async initiateClipMCPOAuthById(orgId, clipId, configId, serverUrl, expectedVersion, oauthClient) {
56154
56360
  const req = {
@@ -56337,6 +56543,11 @@ function describeWsStateDetail(detail) {
56337
56543
  function isRetryableNetworkError(err) {
56338
56544
  return err instanceof Error && err.name === "ApiError" && "status" in err && err.status === 0;
56339
56545
  }
56546
+ function authRejectionFrom(err) {
56547
+ if (!(err instanceof ApiError) || err.status !== 401)
56548
+ return null;
56549
+ return { status: err.status, code: err.code, message: err.message };
56550
+ }
56340
56551
  function isBrowserRuntime() {
56341
56552
  return typeof window !== "undefined";
56342
56553
  }
@@ -56345,9 +56556,12 @@ var ParallWs = class {
56345
56556
  options;
56346
56557
  listeners = /* @__PURE__ */ new Map();
56347
56558
  stateListeners = /* @__PURE__ */ new Set();
56559
+ authInvalidListeners = /* @__PURE__ */ new Set();
56348
56560
  heartbeatTimer = null;
56349
56561
  reconnectTimer = null;
56350
56562
  reconnectAttempts = 0;
56563
+ /** Ticket attempts in the current auth-invalid episode (reset on success). */
56564
+ authAttempts = 0;
56351
56565
  lastSeq = 0;
56352
56566
  _state = "disconnected";
56353
56567
  intentionalClose = false;
@@ -56360,6 +56574,7 @@ var ParallWs = class {
56360
56574
  reconnect: true,
56361
56575
  reconnectInterval: 1e3,
56362
56576
  maxReconnectInterval: 3e4,
56577
+ maxAuthAttempts: 3,
56363
56578
  ...options
56364
56579
  };
56365
56580
  this.lastSeq = options.lastSeq ?? 0;
@@ -56369,12 +56584,25 @@ var ParallWs = class {
56369
56584
  }
56370
56585
  async connect() {
56371
56586
  this.intentionalClose = false;
56587
+ if (this._state === "auth-invalid")
56588
+ this.authAttempts = 0;
56372
56589
  this.setupBrowserListeners();
56373
56590
  this.setState("connecting");
56374
56591
  let ticket;
56375
56592
  try {
56376
56593
  ticket = await this.options.getTicket();
56377
56594
  } catch (err) {
56595
+ const authRejection = authRejectionFrom(err);
56596
+ if (authRejection) {
56597
+ this.authAttempts += 1;
56598
+ if (this.authAttempts < this.options.maxAuthAttempts && this.options.reconnect) {
56599
+ console.warn(`WS ticket auth rejected (attempt ${this.authAttempts}/${this.options.maxAuthAttempts}):`, err);
56600
+ this.scheduleReconnect({ cause: "ticket_failed" });
56601
+ } else {
56602
+ this.enterAuthInvalid(authRejection);
56603
+ }
56604
+ return;
56605
+ }
56378
56606
  if (isBrowserRuntime() && isRetryableNetworkError(err)) {
56379
56607
  console.warn("Failed to get WS ticket:", err);
56380
56608
  } else {
@@ -56421,6 +56649,7 @@ var ParallWs = class {
56421
56649
  this.ws.onopen = () => {
56422
56650
  clearTimeout(connectTimeout);
56423
56651
  this.reconnectAttempts = 0;
56652
+ this.authAttempts = 0;
56424
56653
  this.lastReceivedAt = Date.now();
56425
56654
  this.setState("connected");
56426
56655
  };
@@ -56508,6 +56737,20 @@ var ParallWs = class {
56508
56737
  this.stateListeners.delete(handler);
56509
56738
  };
56510
56739
  }
56740
+ /**
56741
+ * Subscribe to the auth-invalid transition. Fired exactly once when the
56742
+ * client enters the terminal 'auth-invalid' state (a second episode after
56743
+ * a recovered reconnect fires again — one event per transition edge).
56744
+ * Consumers: runtime bridges exit for supervisor re-mint (see
56745
+ * @parall/agent-core gateway-base), the daemon logs recovery guidance for
56746
+ * its machine key, UIs surface a re-authenticate action.
56747
+ */
56748
+ onAuthInvalid(handler) {
56749
+ this.authInvalidListeners.add(handler);
56750
+ return () => {
56751
+ this.authInvalidListeners.delete(handler);
56752
+ };
56753
+ }
56511
56754
  // ---- Internal ----
56512
56755
  send(frame) {
56513
56756
  if (this.ws?.readyState === WebSocket.OPEN) {
@@ -56553,6 +56796,8 @@ var ParallWs = class {
56553
56796
  }
56554
56797
  }
56555
56798
  scheduleReconnect(detail) {
56799
+ if (this._state === "auth-invalid")
56800
+ return;
56556
56801
  this.setState("reconnecting", { ...detail, attempt: this.reconnectAttempts + 1 });
56557
56802
  this.clearReconnect();
56558
56803
  const base = Math.min(this.options.reconnectInterval * Math.pow(2, this.reconnectAttempts), this.options.maxReconnectInterval);
@@ -56568,6 +56813,20 @@ var ParallWs = class {
56568
56813
  this.reconnectTimer = null;
56569
56814
  }
56570
56815
  }
56816
+ /**
56817
+ * Enter the terminal auth-invalid state and notify consumers once. Loud on
56818
+ * purpose: before this branch existed, a revoked key produced an infinite
56819
+ * silent connecting→reconnecting loop with no indication that the
56820
+ * credential — not the network — was the problem.
56821
+ */
56822
+ enterAuthInvalid(event) {
56823
+ this.clearReconnect();
56824
+ this.setState("auth-invalid");
56825
+ console.error(`WS auth-invalid: server rejected this credential (status ${event.status}${event.code ? `, code ${event.code}` : ""}) \u2014 ${event.message}. The key is invalid, expired, or revoked; retrying it cannot succeed, so reconnects are stopped. Recover by refreshing credentials (re-login, or issue a new API key), then connect again.`);
56826
+ for (const listener of this.authInvalidListeners) {
56827
+ listener(event);
56828
+ }
56829
+ }
56571
56830
  /** Force-close a dead/stale connection and trigger reconnect. */
56572
56831
  forceReconnect(cause) {
56573
56832
  this.stopHeartbeat();
@@ -56672,6 +56931,16 @@ var ParallWs = class {
56672
56931
  }
56673
56932
  };
56674
56933
 
56934
+ // ../sdk/dist/browser-use-contract.generated.js
56935
+ var browserUseContract = { "actions": { "read": { "grant": "browser.read", "target": "optional_tab", "params": { "required": [], "properties": { "url": { "type": "string", "min_length": 1, "max_length": 8192, "schemes": ["http", "https", "about:blank"] } }, "rules": ["exactly one of params.url or target is required", "params.url creates a hidden Profile-bound tab and closes it before terminal success or failure"], "additional_properties": false }, "success": { "base": null, "fields": { "profile_id": "ebp_identifier", "url": "string", "title": "string", "article": "nullable_article" } }, "errors": ["TAB_NOT_FOUND", "TAB_GONE", "PAGE_LOAD_FAILED"] }, "tabs": { "grant": "browser.read", "target": "none", "params": { "required": [], "properties": {}, "additional_properties": false }, "success": { "base": null, "fields": { "tabs": "tab_summary_array" } }, "errors": ["TAB_GONE"] }, "wait": { "grant": "browser.read", "target": "tab", "params": { "required": [], "properties": { "selector": { "type": "string", "min_length": 1, "max_length": 4096 }, "state": { "enum": ["attached", "detached", "visible", "hidden", "load"], "default": "attached" }, "timeout_ms": { "type": "integer", "minimum": 1, "maximum": 3e4, "default": 1e4 } }, "rules": ["selector is required unless state=load", "state=load forbids selector"], "additional_properties": false }, "success": { "base": "tab_state", "fields": { "matched": { "const": true } } }, "errors": ["TAB_NOT_FOUND", "TAB_GONE", "PAGE_LOAD_FAILED"] }, "snapshot": { "grant": "browser.read", "target": "tab", "params": { "required": [], "properties": { "interactive": { "type": "boolean", "default": false }, "compact": { "type": "boolean", "default": false }, "depth": { "type": "integer", "minimum": 0, "maximum": 64 } }, "additional_properties": false }, "success": { "base": "tab_state", "fields": { "snapshot": "string", "snapshot_id": "identifier" } }, "errors": ["TAB_NOT_FOUND", "TAB_GONE", "PAGE_LOAD_FAILED"] }, "get": { "grant": "browser.read", "target": "tab_or_ref", "params": { "required": ["prop"], "properties": { "selector": { "type": "string", "min_length": 1, "max_length": 4096 }, "prop": { "enum": ["text", "html", "value", "attr", "title", "url"] }, "name": { "type": "string", "min_length": 1, "max_length": 256 } }, "rules": ["title/url forbid locator and name", "text/html/value require exactly one locator", "attr requires exactly one locator and name"], "additional_properties": false }, "success": { "base": "tab_state", "fields": { "value": "nullable_string" } }, "errors": ["TAB_NOT_FOUND", "TAB_GONE", "STALE_REF", "PAGE_LOAD_FAILED"] }, "screenshot": { "grant": "browser.read", "target": "tab", "params": { "required": [], "properties": {}, "additional_properties": false }, "success": { "base": "tab_state", "fields": { "format": { "const": "png" }, "base64": "canonical_base64" } }, "errors": ["TAB_NOT_FOUND", "TAB_GONE", "PAGE_LOAD_FAILED"] }, "open": { "grant": "browser.interact", "target": "none", "params": { "required": ["url"], "properties": { "url": { "type": "string", "min_length": 1, "max_length": 8192, "schemes": ["http", "https", "about:blank"] }, "hidden": { "type": "boolean", "default": false }, "viewport": { "type": "viewport" } }, "additional_properties": false }, "success": { "base": "tab_state", "fields": {} }, "errors": ["PAGE_LOAD_FAILED"] }, "navigate": { "grant": "browser.interact", "target": "tab", "params": { "required": ["url"], "properties": { "url": { "type": "string", "min_length": 1, "max_length": 8192, "schemes": ["http", "https", "about:blank"] } }, "additional_properties": false }, "success": { "base": "tab_state", "fields": {} }, "errors": ["TAB_NOT_FOUND", "TAB_GONE", "PAGE_LOAD_FAILED"] }, "viewport": { "grant": "browser.interact", "target": "tab", "params": { "required": ["width", "height"], "properties": { "width": { "type": "integer", "minimum": 320, "maximum": 3840 }, "height": { "type": "integer", "minimum": 240, "maximum": 2160 } }, "additional_properties": false }, "success": { "base": "tab_state", "fields": { "viewport": "viewport" } }, "errors": ["TAB_NOT_FOUND", "TAB_GONE"] }, "click": { "grant": "browser.interact", "target": "tab_or_ref", "params": { "required": [], "properties": { "selector": "selector" }, "rules": ["exactly one of params.selector or target.ref"], "additional_properties": false }, "success": { "base": "tab_state", "fields": { "clicked": { "const": true } } }, "errors": ["TAB_NOT_FOUND", "TAB_GONE", "STALE_REF", "PAGE_LOAD_FAILED"] }, "fill": { "grant": "browser.interact", "target": "tab_or_ref", "params": { "required": ["text"], "properties": { "selector": "selector", "text": { "type": "string", "allow_empty": true } }, "rules": ["exactly one of params.selector or target.ref"], "additional_properties": false }, "success": { "base": "tab_state", "fields": { "filled": { "const": true }, "characters": "non_negative_integer" } }, "errors": ["TAB_NOT_FOUND", "TAB_GONE", "STALE_REF", "PAGE_LOAD_FAILED"] }, "type": { "grant": "browser.interact", "target": "tab_or_ref", "params": { "required": ["text"], "properties": { "selector": "selector", "text": { "type": "string", "min_length": 1 } }, "rules": ["exactly one of params.selector or target.ref"], "additional_properties": false }, "success": { "base": "tab_state", "fields": { "typed": { "const": true }, "characters": "non_negative_integer" } }, "errors": ["TAB_NOT_FOUND", "TAB_GONE", "STALE_REF", "PAGE_LOAD_FAILED"] }, "press": { "grant": "browser.interact", "target": "tab", "params": { "required": ["key"], "properties": { "key": "single_unicode_character_or_named_key" }, "additional_properties": false }, "success": { "base": "tab_state", "fields": { "pressed": { "const": true } } }, "errors": ["TAB_NOT_FOUND", "TAB_GONE", "PAGE_LOAD_FAILED"] }, "scroll": { "grant": "browser.interact", "target": "tab", "params": { "required": [], "properties": { "direction": { "enum": ["up", "down", "left", "right"], "default": "down" }, "pixels": { "type": "integer", "minimum": 1, "maximum": 1e5, "default": 500 } }, "additional_properties": false }, "success": { "base": "tab_state", "fields": { "scrolled": { "const": true } } }, "errors": ["TAB_NOT_FOUND", "TAB_GONE", "PAGE_LOAD_FAILED"] }, "close": { "grant": "browser.interact", "target": "tab", "params": { "required": [], "properties": {}, "additional_properties": false }, "success": { "base": "tab_state", "fields": { "closed": { "const": true } } }, "errors": ["TAB_NOT_FOUND", "TAB_GONE"] }, "recover": { "grant": "browser.interact", "target": "tab", "params": { "required": [], "properties": {}, "additional_properties": false }, "success": { "base": "tab_state", "fields": { "recovered_from_tab_id": "identifier" } }, "errors": ["TAB_NOT_FOUND", "TAB_GONE", "PAGE_LOAD_FAILED"] }, "eval": { "grant": "browser.advanced", "target": "tab", "params": { "required": ["expression"], "properties": { "expression": { "type": "string", "min_length": 1 }, "timeout_ms": { "type": "integer", "minimum": 1, "maximum": 15e3, "default": 15e3 } }, "additional_properties": false }, "success": { "base": "tab_state", "fields": { "value": "json_value_max_256_kib" } }, "errors": ["TAB_NOT_FOUND", "TAB_GONE", "PAGE_LOAD_FAILED"] } }, "successShapes": { "tab_state": { "required": ["tab_id", "profile_id", "browser_generation", "document_generation", "url", "title"], "fields": { "tab_id": "identifier", "profile_id": "ebp_identifier", "browser_generation": "positive_integer", "document_generation": "positive_integer", "url": "string", "title": "string" }, "additional_properties": false }, "tab_summary": { "base": "tab_state", "required_additions": ["loading"], "fields": { "loading": "boolean" }, "additional_properties": false }, "article": { "required": ["title", "byline", "excerpt", "text", "published_time", "site_name", "length"], "fields": { "title": "nullable_string", "byline": "nullable_string", "excerpt": "nullable_string", "text": "string", "published_time": "nullable_string", "site_name": "nullable_string", "length": "non_negative_integer" }, "additional_properties": false } } };
56936
+
56937
+ // ../sdk/dist/browser-use-validation.js
56938
+ var BROWSER_USE_ACTION_SCHEMAS = browserUseContract.actions;
56939
+ var BROWSER_USE_ACTIONS = Object.freeze(Object.keys(BROWSER_USE_ACTION_SCHEMAS));
56940
+
56941
+ // ../sdk/dist/browser-use-receipt.js
56942
+ var KEYS = "contract_version operation_id request_id profile_id profile_version profile_config_revision action required_grant phase dispatched receipt_id receipt_digest occurred_at tab_id browser_generation duration_ms result_digest result_available result_expires_at data error deadline accepted_at started_at terminal_at updated_at".split(" ");
56943
+
56675
56944
  // ../agent-core/dist/fork-state.js
56676
56945
  var FORK_CONTINUATION_RETRY_CAP = 256;
56677
56946
  function forkContinuationRetryKey(event) {
@@ -56722,46 +56991,60 @@ function createActiveForkState(fork, targetId, continuationPrefix) {
56722
56991
  };
56723
56992
  }
56724
56993
 
56725
- // ../agent-core/dist/redact.js
56726
- function redactSecrets(s, knownValues = []) {
56727
- return maskKnownValues(s, knownValues).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|basic)\s+)[A-Za-z0-9._~+/=-]+/gi, "$1***").replace(/(\b(?:[\w-]*[_-])?(?:api[_-]?key|key|token|secret|password|passwd|authorization)["']?\s*[=:]\s*)(?:"(?:\\.|[^"\\\r\n])*"|'(?:\\.|[^'\\\r\n])*'|[^\s&;,}\]"']+)/gi, "$1***").replace(/[A-Za-z0-9_-]{32,}/g, "***");
56994
+ // ../agent-core/dist/gateway-steer.js
56995
+ var pendingSteers = /* @__PURE__ */ new WeakMap();
56996
+ function hasMainSteers(state) {
56997
+ return (pendingSteers.get(state)?.size ?? 0) > 0;
56728
56998
  }
56729
- function maskKnownValues(s, knownValues) {
56730
- let out = s;
56731
- for (const v of knownValues) {
56732
- if (typeof v === "string" && v.length >= 6)
56733
- out = out.split(v).join("***");
56734
- }
56735
- return out;
56999
+ async function waitForMainSteers(state) {
57000
+ await Promise.allSettled(pendingSteers.get(state) ?? []);
56736
57001
  }
56737
- function redactTurnOutcome(event, knownValues) {
56738
- const redacted = { ...event };
56739
- if (redacted.detail)
56740
- redacted.detail = redactSecrets(redacted.detail, knownValues);
56741
- if (redacted.raw) {
56742
- redacted.raw = Object.fromEntries(Object.entries(redacted.raw).map(([k, v]) => [
56743
- k,
56744
- typeof v === "string" ? redactSecrets(v, knownValues) : v
56745
- ]));
57002
+ async function steerLaneMessage(host, event) {
57003
+ let pending = pendingSteers.get(host.dispatchState);
57004
+ if (!pending) {
57005
+ pending = /* @__PURE__ */ new Set();
57006
+ pendingSteers.set(host.dispatchState, pending);
57007
+ }
57008
+ const work = Promise.resolve().then(() => steerLaneMessageInner(host, event));
57009
+ pending.add(work);
57010
+ try {
57011
+ await work;
57012
+ } finally {
57013
+ pending.delete(work);
57014
+ if (pending.size === 0)
57015
+ pendingSteers.delete(host.dispatchState);
57016
+ host.notifyDrainWaiters?.();
56746
57017
  }
56747
- return redacted;
56748
57018
  }
56749
- function describeTurnOutcomeFailure(outcome) {
56750
- const retryNote = outcome.retryAt ? `, retry at ${outcome.retryAt}` : "";
56751
- return {
56752
- warn: `${outcome.outcome}${retryNote}${outcome.detail ? ` \u2014 ${outcome.detail}` : ""}`,
56753
- stepMessage: `LLM turn ${outcome.outcome}${retryNote}${outcome.detail ? `: ${outcome.detail}` : ""}`
56754
- };
57019
+ function hasUninjectedSameLaneEventAhead(host, event) {
57020
+ const eventIndex = host.dispatchState.mainBuffer.lastIndexOf(event);
57021
+ if (eventIndex <= 0)
57022
+ return false;
57023
+ const groupKey = host.dispatchGroupKey(event);
57024
+ return host.dispatchState.mainBuffer.slice(0, eventIndex).some((buffered) => host.dispatchGroupKey(buffered) === groupKey && !host.injectedMainBufferEvents.has(buffered));
56755
57025
  }
56756
- function redactLogger(log, knownValues) {
56757
- if (!log)
56758
- return void 0;
56759
- return {
56760
- info: (message) => log.info(redactSecrets(message, knownValues)),
56761
- warn: (message) => log.warn(redactSecrets(message, knownValues)),
56762
- error: (message) => log.error(redactSecrets(message, knownValues)),
56763
- ...log.child ? { child: (name) => redactLogger(log.child(name), knownValues) } : {}
56764
- };
57026
+ async function steerLaneMessageInner(host, event) {
57027
+ const { laneLedger: ledger, opts } = host;
57028
+ const adapter = opts.dispatchAdapter;
57029
+ if (!ledger || !adapter.enqueueDuringDispatch)
57030
+ return;
57031
+ if (hasUninjectedSameLaneEventAhead(host, event))
57032
+ return;
57033
+ const folded = await ledger.steerLive(event);
57034
+ if (!folded)
57035
+ return;
57036
+ if (!folded.frame)
57037
+ return;
57038
+ event.frame = folded.frame;
57039
+ const injected = await adapter.enqueueDuringDispatch(opts.runtimeKey, eventBody(event), folded.inputLifecycle);
57040
+ if (injected) {
57041
+ host.injectedMainBufferEvents.add(event);
57042
+ ledger.markSeen(event, folded.covered);
57043
+ opts.log?.info(`steer frame handed to adapter for ${event.messageId} (will drain for bookkeeping)`);
57044
+ } else if (folded.frame) {
57045
+ ledger.deferFrame(event, folded.frame, folded.covered);
57046
+ opts.log?.info(`steer folded but not injected for ${event.messageId} \u2014 frame deferred to the drain`);
57047
+ }
56765
57048
  }
56766
57049
 
56767
57050
  // ../agent-core/dist/lane-ledger.js
@@ -56794,21 +57077,218 @@ function laneTargetUri(event) {
56794
57077
  if (event.type === "message") {
56795
57078
  return event.targetId.startsWith(CHAT_LANE_PREFIX) ? `${PRLL_SCHEME}${event.targetId}` : void 0;
56796
57079
  }
56797
- if (event.type === "channel_message") {
56798
- return event.targetUri?.startsWith(CHANNEL_LANE_PREFIX) ? event.targetUri : void 0;
57080
+ if (event.type === "channel_message") {
57081
+ return event.targetUri?.startsWith(CHANNEL_LANE_PREFIX) ? event.targetUri : void 0;
57082
+ }
57083
+ return void 0;
57084
+ }
57085
+ function isTypedEvent(event) {
57086
+ return event.type !== "message" && laneTargetUri(event) === void 0;
57087
+ }
57088
+
57089
+ // ../agent-core/dist/lane-outcome.js
57090
+ function errorWireClass(cls) {
57091
+ if (!cls || cls === "ok" || cls === "usage_limit")
57092
+ return void 0;
57093
+ return cls;
57094
+ }
57095
+
57096
+ // ../agent-core/dist/runtime-instance.js
57097
+ function isTurnToken(token) {
57098
+ return token.startsWith("turn_");
57099
+ }
57100
+
57101
+ // ../agent-core/dist/lane-input-receipts.js
57102
+ var LaneInputReceipts = class {
57103
+ opts;
57104
+ confirmations;
57105
+ constructor(opts, confirmations) {
57106
+ this.opts = opts;
57107
+ this.confirmations = confirmations;
57108
+ }
57109
+ lifecycle(lane, messageIds) {
57110
+ const explicit = lane.coverageMode === "explicit";
57111
+ const unique = [...new Set(messageIds)];
57112
+ const dispatchEventIds = unique.map((messageId) => lane.folded.get(messageId)).filter((id) => Boolean(id));
57113
+ if (explicit && dispatchEventIds.length !== unique.length) {
57114
+ throw new Error(`explicit lane ${lane.lane} is missing a folded WorkItem mapping`);
57115
+ }
57116
+ if (!explicit && dispatchEventIds.length === 0)
57117
+ return void 0;
57118
+ return {
57119
+ deliveryKey: dispatchEventIds.join(","),
57120
+ dispatchEventIds,
57121
+ update: explicit ? (state, failure) => this.updateInputState(lane, dispatchEventIds, state, failure) : async () => void 0
57122
+ };
57123
+ }
57124
+ async updateInputState(lane, dispatchEventIds, state, failure) {
57125
+ lane.observedInputs ??= /* @__PURE__ */ new Map();
57126
+ for (const id of dispatchEventIds)
57127
+ lane.observedInputs.set(id, state);
57128
+ let confirmed;
57129
+ const ids = [...dispatchEventIds];
57130
+ const failed = new Set(state === "failed" ? ids : []);
57131
+ const sourceIds = [...lane.folded].filter(([, id]) => failed.has(id)).map(([id]) => id);
57132
+ const request3 = {
57133
+ lane: lane.lane,
57134
+ target_uri: lane.targetUri,
57135
+ thread_root_id: lane.threadRootId,
57136
+ dispatch_event_ids: ids,
57137
+ state,
57138
+ session_id: lane.sessionAmbiguous ? void 0 : lane.sessionId,
57139
+ ...state === "failed" && failure ? {
57140
+ outcome_class: errorWireClass(failure.outcomeClass),
57141
+ detail: failure.detail ? redactSecrets(failure.detail, this.opts.knownSecrets).slice(0, 500) : void 0
57142
+ } : {}
57143
+ };
57144
+ await this.confirmations.submit(lane.lane, {
57145
+ label: `input ${state}`,
57146
+ send: async () => {
57147
+ const restore = sourceIds.length ? this.opts.releaseLocalClaims?.(sourceIds) : void 0;
57148
+ let result;
57149
+ try {
57150
+ result = await this.opts.client.updateDispatchInputState(this.opts.orgId, request3);
57151
+ } catch (err) {
57152
+ restore?.();
57153
+ throw err;
57154
+ }
57155
+ if (result.recognized !== ids.length && !isTurnToken(lane.lane)) {
57156
+ restore?.();
57157
+ throw new ApiError(409, "input acknowledgement scope changed", "INPUT_SCOPE_CHANGED");
57158
+ }
57159
+ if (state === "failed") {
57160
+ for (const sourceId of sourceIds) {
57161
+ lane.folded.delete(sourceId);
57162
+ lane.seen.delete(sourceId);
57163
+ }
57164
+ }
57165
+ confirmed = { retry: state === "failed" && result.released > 0 };
57166
+ }
57167
+ });
57168
+ return confirmed ?? { retry: false, pending: true };
57169
+ }
57170
+ async syncAttribution(lane) {
57171
+ if (lane.settlementMode !== "receipt" || lane.settling || lane.sessionAmbiguous || !lane.sessionId || !lane.observedInputs?.size)
57172
+ return;
57173
+ const sessionId = lane.sessionId;
57174
+ const groups = /* @__PURE__ */ new Map();
57175
+ for (const [id, state] of lane.observedInputs) {
57176
+ const ids = groups.get(state) ?? [];
57177
+ ids.push(id);
57178
+ groups.set(state, ids);
57179
+ }
57180
+ for (const [state, ids] of groups) {
57181
+ await this.confirmations.submit(lane.lane, {
57182
+ label: "input session",
57183
+ send: async () => {
57184
+ await this.opts.client.updateDispatchInputState(this.opts.orgId, {
57185
+ lane: lane.lane,
57186
+ target_uri: lane.targetUri,
57187
+ thread_root_id: lane.threadRootId,
57188
+ dispatch_event_ids: ids,
57189
+ state,
57190
+ session_id: sessionId
57191
+ });
57192
+ }
57193
+ });
57194
+ }
57195
+ }
57196
+ };
57197
+
57198
+ // ../agent-core/dist/lane-confirmations.js
57199
+ var LaneConfirmations = class {
57200
+ opts;
57201
+ pending = /* @__PURE__ */ new Map();
57202
+ disposed = false;
57203
+ constructor(opts) {
57204
+ this.opts = opts;
57205
+ }
57206
+ /** Try once inline; a retained write returns without blocking native execution. */
57207
+ async submit(token, write) {
57208
+ if (this.disposed)
57209
+ return;
57210
+ let pending = this.pending.get(token);
57211
+ if (pending) {
57212
+ if (pending.blocked)
57213
+ return;
57214
+ pending.writes.push(write);
57215
+ return;
57216
+ }
57217
+ pending = { writes: [write], failures: 0, blocked: false };
57218
+ this.pending.set(token, pending);
57219
+ await this.run(token, pending);
57220
+ }
57221
+ async run(token, pending) {
57222
+ if (pending.running || pending.blocked || this.pending.get(token) !== pending)
57223
+ return;
57224
+ const recovered = pending.failures > 0;
57225
+ pending.running = Promise.resolve().then(async () => {
57226
+ while (pending.writes.length && this.pending.get(token) === pending) {
57227
+ const write = pending.writes[0];
57228
+ try {
57229
+ await write.send();
57230
+ pending.writes.shift();
57231
+ } catch (err) {
57232
+ if (this.pending.get(token) !== pending)
57233
+ return;
57234
+ if (err instanceof ApiError && err.status === 409 && err.code === "STALE_LANE") {
57235
+ this.pending.delete(token);
57236
+ this.opts.onStale(token);
57237
+ this.opts.onRecovered?.();
57238
+ return;
57239
+ }
57240
+ const transient = !(err instanceof ApiError) || err.status === 0 || err.status >= 500 || err.status === 408 || err.status === 429;
57241
+ if (!transient) {
57242
+ pending.blocked = true;
57243
+ this.opts.log?.error(`lane ${write.label} rejected for ${token}; confirmation retained: ${String(err)}`);
57244
+ return;
57245
+ }
57246
+ pending.failures += 1;
57247
+ this.opts.log?.warn(`lane ${write.label} unconfirmed for ${token}; retry ${pending.failures}: ${String(err)}`);
57248
+ const delays = this.opts.delaysMs?.length ? this.opts.delaysMs : [2e3, 1e4, 3e4, 6e4, 12e4];
57249
+ const delay = delays[Math.min(pending.failures - 1, delays.length - 1)];
57250
+ pending.timer = setTimeout(() => {
57251
+ pending.timer = void 0;
57252
+ void this.run(token, pending);
57253
+ }, delay);
57254
+ pending.timer.unref?.();
57255
+ return;
57256
+ }
57257
+ }
57258
+ if (this.pending.get(token) === pending)
57259
+ this.pending.delete(token);
57260
+ if (recovered)
57261
+ this.opts.onRecovered?.();
57262
+ });
57263
+ try {
57264
+ await pending.running;
57265
+ } finally {
57266
+ pending.running = void 0;
57267
+ }
57268
+ }
57269
+ /** Reconnect/shutdown may accelerate retries; permanent rejections stay visible. */
57270
+ async retryNow() {
57271
+ await Promise.all([...this.pending].map(async ([token, pending]) => {
57272
+ if (pending.timer)
57273
+ clearTimeout(pending.timer);
57274
+ pending.timer = void 0;
57275
+ await this.run(token, pending);
57276
+ }));
56799
57277
  }
56800
- return void 0;
56801
- }
56802
- function isTypedEvent(event) {
56803
- return event.type !== "message" && laneTargetUri(event) === void 0;
56804
- }
57278
+ forget(token) {
57279
+ const pending = this.pending.get(token);
57280
+ if (pending?.timer)
57281
+ clearTimeout(pending.timer);
57282
+ this.pending.delete(token);
57283
+ }
57284
+ dispose() {
57285
+ this.disposed = true;
57286
+ for (const token of this.pending.keys())
57287
+ this.forget(token);
57288
+ }
57289
+ };
56805
57290
 
56806
57291
  // ../agent-core/dist/lane-ledger.js
56807
- function errorWireClass(cls) {
56808
- if (!cls || cls === "ok" || cls === "usage_limit")
56809
- return void 0;
56810
- return cls;
56811
- }
56812
57292
  var LedgerUnsupportedError = class extends Error {
56813
57293
  };
56814
57294
  function bindLaneSession(lane, agentSessionId) {
@@ -56834,8 +57314,24 @@ function isEndpointMissing2(err) {
56834
57314
  var LaneLedger = class {
56835
57315
  opts;
56836
57316
  lanes = /* @__PURE__ */ new Map();
57317
+ confirmations;
57318
+ inputs;
57319
+ claimResponses = /* @__PURE__ */ new Map();
57320
+ claims = /* @__PURE__ */ new Map();
56837
57321
  constructor(opts) {
56838
57322
  this.opts = opts;
57323
+ this.confirmations = new LaneConfirmations({
57324
+ delaysMs: opts.terminalRetryDelaysMs,
57325
+ log: opts.log,
57326
+ onRecovered: opts.onRecovered,
57327
+ onStale: (token) => {
57328
+ for (const lane of this.lanes.values()) {
57329
+ if (lane.lane === token)
57330
+ this.forgetLane(lane);
57331
+ }
57332
+ }
57333
+ });
57334
+ this.inputs = new LaneInputReceipts(opts, this.confirmations);
56839
57335
  }
56840
57336
  get contextDir() {
56841
57337
  return this.opts.contextDir;
@@ -56934,7 +57430,16 @@ ${frame}` : frame;
56934
57430
  * incumbent completes.
56935
57431
  */
56936
57432
  async ensureLane(events) {
56937
- return this.ensureLaneAttempt(events, false);
57433
+ const key = this.laneKeyFor(events[events.length - 1]);
57434
+ const prior = this.claims.get(key);
57435
+ const claim = (prior ? prior.catch(() => null) : Promise.resolve()).then(() => this.ensureLaneAttempt(events, false));
57436
+ this.claims.set(key, claim);
57437
+ try {
57438
+ return await claim;
57439
+ } finally {
57440
+ if (this.claims.get(key) === claim)
57441
+ this.claims.delete(key);
57442
+ }
56938
57443
  }
56939
57444
  /**
56940
57445
  * One ensureLane pass. `reclaimed` marks the arbitration retry: STALE_LANE
@@ -56952,22 +57457,22 @@ ${frame}` : frame;
56952
57457
  const trigger = events[events.length - 1];
56953
57458
  const laneKey = this.laneKeyFor(trigger);
56954
57459
  let lane = this.lanes.get(laneKey);
57460
+ if (lane?.settling)
57461
+ return null;
56955
57462
  const reused = lane != null;
56956
57463
  if (!lane) {
56957
57464
  const targetUri = laneTargetUri(trigger) ?? `prll://${trigger.targetId}`;
56958
- let res;
56959
- try {
56960
- res = await this.opts.client.claimDispatch(this.opts.orgId, {
56961
- target_uri: targetUri,
56962
- thread_root_id: trigger.threadRootId,
56963
- limit: 100,
56964
- coverage_mode: this.opts.coverageMode ?? "implicit"
56965
- });
56966
- } catch (err) {
56967
- if (isEndpointMissing2(err))
56968
- throw new LedgerUnsupportedError("claim endpoint unavailable");
56969
- throw err;
56970
- }
57465
+ const res = await this.claimRecoverably(laneKey, {
57466
+ target_uri: targetUri,
57467
+ thread_root_id: trigger.threadRootId,
57468
+ limit: 100,
57469
+ coverage_mode: this.opts.coverageMode ?? "implicit",
57470
+ settlement_mode: this.opts.instanceId ? this.opts.settlementMode : void 0,
57471
+ resume_received: this.opts.instanceId ? true : void 0,
57472
+ instance_id: this.opts.instanceId
57473
+ });
57474
+ if (!res)
57475
+ return null;
56971
57476
  if (!res.claimed || !res.lane) {
56972
57477
  if (res.reason === "empty") {
56973
57478
  this.opts.log?.warn(`claim for ${targetUri} came back empty \u2014 nothing foldable; leaving to the reconciler`);
@@ -56990,6 +57495,7 @@ ${frame}` : frame;
56990
57495
  targetUri,
56991
57496
  threadRootId: trigger.threadRootId,
56992
57497
  coverageMode: actualCoverage,
57498
+ settlementMode: res.settlement_mode ?? "legacy",
56993
57499
  folded: /* @__PURE__ */ new Map(),
56994
57500
  framed: [],
56995
57501
  seen: /* @__PURE__ */ new Set(),
@@ -57024,9 +57530,8 @@ ${frame}` : frame;
57024
57530
  }
57025
57531
  return null;
57026
57532
  }
57027
- this.opts.log?.warn(`steer fold failed for ${ev.messageId} \u2014 failing closed, releasing lane: ${String(err)}`);
57028
- await this.release(laneKey, "steer_failed");
57029
- return null;
57533
+ this.opts.log?.warn(`steer fold failed for ${ev.messageId}; preserving prior members: ${String(err)}`);
57534
+ continue;
57030
57535
  }
57031
57536
  }
57032
57537
  return lane;
@@ -57040,7 +57545,7 @@ ${frame}` : frame;
57040
57545
  async steerLive(event) {
57041
57546
  const laneKey = this.laneKeyFor(event);
57042
57547
  const lane = this.lanes.get(laneKey);
57043
- if (!lane)
57548
+ if (!lane || lane.settling)
57044
57549
  return null;
57045
57550
  if (lane.seen.has(event.messageId))
57046
57551
  return null;
@@ -57086,54 +57591,7 @@ ${frame}` : frame;
57086
57591
  * prompt or injection actually delivers (frame coverage ∪ buffered group).
57087
57592
  */
57088
57593
  inputLifecycleFor(lane, messageIds) {
57089
- const explicit = lane.coverageMode === "explicit";
57090
- const unique = [...new Set(messageIds)];
57091
- const dispatchEventIds = unique.map((messageId) => lane.folded.get(messageId)).filter((id) => Boolean(id));
57092
- if (explicit && dispatchEventIds.length !== unique.length) {
57093
- throw new Error(`explicit lane ${lane.lane} is missing a folded WorkItem mapping`);
57094
- }
57095
- if (!explicit && dispatchEventIds.length === 0)
57096
- return void 0;
57097
- return {
57098
- deliveryKey: dispatchEventIds.join(","),
57099
- dispatchEventIds,
57100
- update: explicit ? (state, failure) => this.updateInputState(lane, dispatchEventIds, state, failure) : async () => void 0
57101
- };
57102
- }
57103
- async updateInputState(lane, dispatchEventIds, state, failure) {
57104
- const failed = new Set(state === "failed" ? dispatchEventIds : []);
57105
- const failedSourceIds = [...lane.folded].filter(([, dispatchEventId]) => failed.has(dispatchEventId)).map(([sourceId]) => sourceId);
57106
- const restoreLocalClaims = failedSourceIds.length > 0 ? this.opts.releaseLocalClaims?.(failedSourceIds) : void 0;
57107
- try {
57108
- const result = await this.opts.client.updateDispatchInputState(this.opts.orgId, {
57109
- lane: lane.lane,
57110
- target_uri: lane.targetUri,
57111
- thread_root_id: lane.threadRootId,
57112
- dispatch_event_ids: dispatchEventIds,
57113
- state,
57114
- ...state === "failed" && failure ? {
57115
- outcome_class: errorWireClass(failure.outcomeClass),
57116
- detail: failure.detail ? redactSecrets(failure.detail, this.opts.knownSecrets).slice(0, 500) : void 0
57117
- } : {}
57118
- });
57119
- if (result.recognized !== dispatchEventIds.length) {
57120
- throw new Error(`input lifecycle ${state} recognized ${result.recognized}/${dispatchEventIds.length} WorkItems`);
57121
- }
57122
- if (state === "failed" && result.released === 0) {
57123
- restoreLocalClaims?.();
57124
- return { retry: false };
57125
- }
57126
- } catch (err) {
57127
- restoreLocalClaims?.();
57128
- throw err;
57129
- }
57130
- if (state === "failed") {
57131
- for (const [sourceId, dispatchEventId] of lane.folded) {
57132
- if (failed.has(dispatchEventId))
57133
- lane.folded.delete(sourceId);
57134
- }
57135
- }
57136
- return { retry: state === "failed" };
57594
+ return this.inputs.lifecycle(lane, messageIds);
57137
57595
  }
57138
57596
  /** Carry the runtime error into the lane's forced completion. */
57139
57597
  markTurnError(laneKey, info = {}) {
@@ -57162,62 +57620,62 @@ ${frame}` : frame;
57162
57620
  const lane = this.lanes.get(laneKey);
57163
57621
  if (!lane || hasMoreLocal)
57164
57622
  return;
57165
- this.lanes.delete(laneKey);
57166
- this.removeLaneContext(lane);
57623
+ if (lane.settling)
57624
+ return;
57625
+ lane.settling = true;
57167
57626
  const deferred = !lane.turnError && !this.deferredUnsupported ? lane.turnDeferred : void 0;
57168
57627
  const outcome = lane.turnError ? "error" : lane.turnDeferred ? this.deferredUnsupported ? "error" : "deferred" : "ok";
57169
- try {
57170
- const res = await this.opts.client.completeDispatch(this.opts.orgId, {
57171
- lane: lane.lane,
57172
- target_uri: lane.targetUri,
57173
- thread_root_id: lane.threadRootId,
57174
- // An error turn releases its members for retry; a deferred turn
57175
- // re-delivers them at retry_at without burning redrive budget
57176
- // (ignored by older servers, which 400 on the unknown enum — see the
57177
- // fallback below).
57178
- turn_outcome: outcome,
57179
- ...deferred ? {
57180
- outcome_class: deferred.outcomeClass,
57181
- ...deferred.retryAt ? { retry_at: deferred.retryAt } : {}
57182
- } : {},
57183
- // An error complete carries the bridge's classification and redacted
57184
- // evidence so the server can persist WHY on the released row
57185
- // (agent-health-alerting-design.md §4.5). usage_limit never travels
57186
- // with error: it is the deferred contract, and a server that rejected
57187
- // deferred (deferredUnsupported) also predates the class field.
57188
- ...outcome === "error" && lane.turnError ? {
57189
- ...errorWireClass(lane.turnError.outcomeClass) ? { outcome_class: errorWireClass(lane.turnError.outcomeClass) } : {},
57190
- ...lane.turnError.detail ? { detail: lane.turnError.detail.slice(0, 500) } : {}
57191
- } : {},
57192
- session_id: lane.sessionAmbiguous ? void 0 : lane.sessionId
57193
- });
57194
- if (res.swept_no_action > 0 || res.redriven) {
57195
- this.opts.log?.info(`lane complete for ${lane.targetUri}: swept ${res.swept_no_action} no_action, redriven=${res.redriven}`);
57196
- }
57197
- } catch (err) {
57198
- if (isStaleLane(err)) {
57199
- this.opts.log?.info(`lane complete skipped for ${lane.targetUri} \u2014 taken over`);
57200
- return;
57201
- }
57202
- if (outcome === "deferred" && err instanceof ApiError && err.status === 400) {
57203
- this.deferredUnsupported = true;
57204
- this.opts.log?.warn(`server rejected turn_outcome=deferred for ${lane.targetUri} \u2014 falling back to error completes`);
57628
+ const request3 = {
57629
+ lane: lane.lane,
57630
+ target_uri: lane.targetUri,
57631
+ thread_root_id: lane.threadRootId,
57632
+ // An error turn releases its members for retry; a deferred turn
57633
+ // re-delivers them at retry_at without burning redrive budget
57634
+ // (ignored by older servers, which 400 on the unknown enum — see the
57635
+ // fallback below).
57636
+ turn_outcome: outcome,
57637
+ ...deferred ? {
57638
+ outcome_class: deferred.outcomeClass,
57639
+ ...deferred.retryAt ? { retry_at: deferred.retryAt } : {}
57640
+ } : {},
57641
+ // An error complete carries the bridge's classification and redacted
57642
+ // evidence so the server can persist WHY on the released row
57643
+ // (agent-health-alerting-design.md §4.5). usage_limit never travels
57644
+ // with error: it is the deferred contract, and a server that rejected
57645
+ // deferred (deferredUnsupported) also predates the class field.
57646
+ ...outcome === "error" && lane.turnError ? {
57647
+ ...errorWireClass(lane.turnError.outcomeClass) ? { outcome_class: errorWireClass(lane.turnError.outcomeClass) } : {},
57648
+ ...lane.turnError.detail ? { detail: lane.turnError.detail.slice(0, 500) } : {}
57649
+ } : {},
57650
+ session_id: lane.sessionAmbiguous ? void 0 : lane.sessionId
57651
+ };
57652
+ let fallback = false;
57653
+ await this.confirmations.submit(lane.lane, {
57654
+ label: "complete",
57655
+ send: async () => {
57205
57656
  try {
57206
- await this.opts.client.completeDispatch(this.opts.orgId, {
57207
- lane: lane.lane,
57208
- target_uri: lane.targetUri,
57209
- thread_root_id: lane.threadRootId,
57657
+ await this.opts.client.completeDispatch(this.opts.orgId, fallback ? {
57658
+ lane: request3.lane,
57659
+ target_uri: request3.target_uri,
57660
+ thread_root_id: request3.thread_root_id,
57210
57661
  turn_outcome: "error"
57211
- });
57212
- } catch (fallbackErr) {
57213
- if (isStaleLane(fallbackErr))
57214
- return;
57215
- this.opts.log?.warn(`lane complete (deferred fallback) failed for ${lane.targetUri}: ${String(fallbackErr)}`);
57662
+ } : request3);
57663
+ } catch (err) {
57664
+ if (!fallback && outcome === "deferred" && err instanceof ApiError && err.status === 400) {
57665
+ this.deferredUnsupported = true;
57666
+ fallback = true;
57667
+ await this.opts.client.completeDispatch(this.opts.orgId, {
57668
+ lane: request3.lane,
57669
+ target_uri: request3.target_uri,
57670
+ thread_root_id: request3.thread_root_id,
57671
+ turn_outcome: "error"
57672
+ });
57673
+ } else
57674
+ throw err;
57216
57675
  }
57217
- return;
57676
+ this.forgetLane(lane);
57218
57677
  }
57219
- this.opts.log?.warn(`lane complete failed for ${lane.targetUri}: ${String(err)}`);
57220
- }
57678
+ });
57221
57679
  }
57222
57680
  /**
57223
57681
  * Renew one lane by its key — the external runtime-activity hook for
@@ -57239,6 +57697,8 @@ ${frame}` : frame;
57239
57697
  * write's incumbency check anyway.
57240
57698
  */
57241
57699
  maybeRenew(lane) {
57700
+ if (isTurnToken(lane.lane))
57701
+ return;
57242
57702
  const now = Date.now();
57243
57703
  const ttl = lane.leaseTtlMs ?? 10 * 6e4;
57244
57704
  const until = lane.leaseUntilMs ?? now;
@@ -57269,26 +57729,40 @@ ${frame}` : frame;
57269
57729
  */
57270
57730
  async release(laneKey, reason = "unspecified", info) {
57271
57731
  const lane = this.lanes.get(laneKey);
57272
- if (!lane)
57732
+ if (!lane || lane.settling)
57273
57733
  return;
57274
- this.lanes.delete(laneKey);
57275
- this.removeLaneContext(lane);
57276
- try {
57277
- const wireClass = errorWireClass(info?.outcomeClass);
57278
- const outcome = info && (wireClass || info.detail) ? {
57279
- ...wireClass ? { outcome_class: wireClass } : {},
57280
- ...info.detail ? { detail: info.detail } : {}
57281
- } : void 0;
57282
- await this.opts.client.releaseDispatchLane(this.opts.orgId, lane.lane, reason, outcome);
57283
- } catch (err) {
57284
- this.opts.log?.warn(`lane release (${reason}) failed for ${lane.targetUri}: ${String(err)}`);
57285
- }
57734
+ lane.settling = true;
57735
+ const wireClass = errorWireClass(info?.outcomeClass);
57736
+ const outcome = info && (wireClass || info.detail) ? {
57737
+ ...wireClass ? { outcome_class: wireClass } : {},
57738
+ ...info.detail ? { detail: redactSecrets(info.detail, this.opts.knownSecrets).slice(0, 500) } : {}
57739
+ } : void 0;
57740
+ await this.confirmations.submit(lane.lane, {
57741
+ label: "release",
57742
+ send: async () => {
57743
+ await this.opts.client.releaseDispatchLane(this.opts.orgId, lane.lane, reason, outcome);
57744
+ this.forgetLane(lane);
57745
+ }
57746
+ });
57286
57747
  }
57287
57748
  async releaseAll(reason = "shutdown") {
57749
+ await this.confirmations.retryNow();
57288
57750
  const keys = [...this.lanes.keys()];
57289
57751
  for (const key of keys) {
57290
57752
  await this.release(key, reason);
57291
57753
  }
57754
+ for (const [key, claim] of this.claimResponses) {
57755
+ if (!claim.response?.lane)
57756
+ continue;
57757
+ const token = claim.response.lane;
57758
+ await this.confirmations.submit(token, {
57759
+ label: "unconsumed claim release",
57760
+ send: async () => {
57761
+ await this.opts.client.releaseDispatchLane(this.opts.orgId, token, reason);
57762
+ this.claimResponses.delete(key);
57763
+ }
57764
+ });
57765
+ }
57292
57766
  }
57293
57767
  /** True when any lane is currently active (used by shutdown logging). */
57294
57768
  get activeCount() {
@@ -57303,18 +57777,57 @@ ${frame}` : frame;
57303
57777
  * — the caller must skip processing.
57304
57778
  */
57305
57779
  async claimTyped(ref) {
57306
- let res;
57780
+ const key = `typed:${ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`}`;
57781
+ if (this.claims.has(key))
57782
+ return null;
57783
+ if (ref.dispatchEventId && this.lanes.has(laneKeyForTarget(`dsp:${ref.dispatchEventId}`)))
57784
+ return null;
57785
+ const claim = this.claimTypedAttempt(ref);
57786
+ this.claims.set(key, claim);
57307
57787
  try {
57308
- res = await this.opts.client.claimDispatch(this.opts.orgId, {
57309
- dispatch_event_id: ref.dispatchEventId,
57310
- source_type: ref.dispatchEventId ? void 0 : ref.sourceType,
57311
- source_id: ref.dispatchEventId ? void 0 : ref.sourceId
57788
+ return await claim;
57789
+ } finally {
57790
+ this.claims.delete(key);
57791
+ }
57792
+ }
57793
+ async claimRecoverably(key, request3) {
57794
+ let claim = this.claimResponses.get(key);
57795
+ if (!claim) {
57796
+ claim = { request: request3 };
57797
+ this.claimResponses.set(key, claim);
57798
+ const captured = claim;
57799
+ await this.confirmations.submit(`claim:${key}`, {
57800
+ label: "claim",
57801
+ send: async () => {
57802
+ try {
57803
+ captured.response = await this.opts.client.claimDispatch(this.opts.orgId, request3);
57804
+ } catch (err) {
57805
+ if (isEndpointMissing2(err))
57806
+ captured.unsupported = true;
57807
+ else
57808
+ throw err;
57809
+ }
57810
+ }
57312
57811
  });
57313
- } catch (err) {
57314
- if (isEndpointMissing2(err))
57315
- throw new LedgerUnsupportedError("claim endpoint unavailable");
57316
- throw err;
57317
57812
  }
57813
+ if (claim.unsupported) {
57814
+ this.claimResponses.delete(key);
57815
+ throw new LedgerUnsupportedError("claim endpoint unavailable");
57816
+ }
57817
+ if (!claim.response)
57818
+ return null;
57819
+ this.claimResponses.delete(key);
57820
+ return claim.response;
57821
+ }
57822
+ async claimTypedAttempt(ref) {
57823
+ const res = await this.claimRecoverably(`typed:${ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`}`, {
57824
+ dispatch_event_id: ref.dispatchEventId,
57825
+ source_type: ref.dispatchEventId ? void 0 : ref.sourceType,
57826
+ source_id: ref.dispatchEventId ? void 0 : ref.sourceId,
57827
+ instance_id: this.opts.instanceId
57828
+ });
57829
+ if (!res)
57830
+ return null;
57318
57831
  if (!res.claimed || !res.lane || !res.events?.length) {
57319
57832
  if (res.reason === "stale") {
57320
57833
  this.opts.log?.info(`typed dispatch ${ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`} resolved by the server as stale (${res.stale_reason ?? "unspecified"})`);
@@ -57326,6 +57839,8 @@ ${frame}` : frame;
57326
57839
  }
57327
57840
  const workItem = res.events[0];
57328
57841
  const targetUri = `dsp:${workItem.id}`;
57842
+ if (this.lanes.has(laneKeyForTarget(targetUri)))
57843
+ return null;
57329
57844
  const leaseUntilMs = Date.parse(res.lease_until ?? "");
57330
57845
  const lane = {
57331
57846
  laneKey: laneKeyForTarget(targetUri),
@@ -57354,7 +57869,34 @@ ${frame}` : frame;
57354
57869
  const lane = this.lanes.get(laneKey);
57355
57870
  if (!lane)
57356
57871
  return;
57357
- this.lanes.delete(laneKey);
57872
+ this.confirmations.forget(lane.lane);
57873
+ this.forgetLane(lane);
57874
+ }
57875
+ syncInputAttribution(lane) {
57876
+ return this.inputs.syncAttribution(lane);
57877
+ }
57878
+ async confirmTyped(lane, send) {
57879
+ if (lane.settling)
57880
+ return;
57881
+ lane.settling = true;
57882
+ await this.confirmations.submit(lane.lane, {
57883
+ label: "typed complete",
57884
+ send: async () => {
57885
+ await send();
57886
+ this.forgetLane(lane);
57887
+ }
57888
+ });
57889
+ }
57890
+ retryConfirmations() {
57891
+ return this.confirmations.retryNow();
57892
+ }
57893
+ dispose() {
57894
+ this.confirmations.dispose();
57895
+ }
57896
+ forgetLane(lane) {
57897
+ if (this.lanes.get(lane.laneKey) !== lane)
57898
+ return;
57899
+ this.lanes.delete(lane.laneKey);
57358
57900
  this.removeLaneContext(lane);
57359
57901
  }
57360
57902
  /**
@@ -58248,36 +58790,6 @@ function applyWake(event, wake) {
58248
58790
  if (wake.attachments?.length)
58249
58791
  event.attachments = wake.attachments;
58250
58792
  }
58251
- function hasUninjectedSameLaneEventAhead(host, event) {
58252
- const eventIndex = host.dispatchState.mainBuffer.lastIndexOf(event);
58253
- if (eventIndex <= 0)
58254
- return false;
58255
- const groupKey = host.dispatchGroupKey(event);
58256
- return host.dispatchState.mainBuffer.slice(0, eventIndex).some((buffered) => host.dispatchGroupKey(buffered) === groupKey && !host.injectedMainBufferEvents.has(buffered));
58257
- }
58258
- async function steerLaneMessage(host, event) {
58259
- const { laneLedger: ledger, opts } = host;
58260
- const adapter = opts.dispatchAdapter;
58261
- if (!ledger || !adapter.enqueueDuringDispatch)
58262
- return;
58263
- if (hasUninjectedSameLaneEventAhead(host, event))
58264
- return;
58265
- const folded = await ledger.steerLive(event);
58266
- if (!folded)
58267
- return;
58268
- if (!folded.frame)
58269
- return;
58270
- event.frame = folded.frame;
58271
- const injected = await adapter.enqueueDuringDispatch(opts.runtimeKey, eventBody(event), folded.inputLifecycle);
58272
- if (injected) {
58273
- host.injectedMainBufferEvents.add(event);
58274
- ledger.markSeen(event, folded.covered);
58275
- opts.log?.info(`steer folded+injected for ${event.messageId} (will drain for bookkeeping)`);
58276
- } else if (folded.frame) {
58277
- ledger.deferFrame(event, folded.frame, folded.covered);
58278
- opts.log?.info(`steer folded but not injected for ${event.messageId} \u2014 frame deferred to the drain`);
58279
- }
58280
- }
58281
58793
  async function dispatchLaneGroup(host, opts) {
58282
58794
  const event = opts.events[opts.events.length - 1];
58283
58795
  return runInDispatchTrace({
@@ -58297,7 +58809,7 @@ async function dispatchLaneGroup(host, opts) {
58297
58809
  }
58298
58810
  async function dispatchLaneGroupInner(host, opts) {
58299
58811
  const ledger = host.laneLedger;
58300
- const event = opts.events[opts.events.length - 1];
58812
+ let event = opts.events[opts.events.length - 1];
58301
58813
  let lane;
58302
58814
  try {
58303
58815
  lane = await runInPhaseSpan("claim", () => ledger.ensureLane(opts.events));
@@ -58315,18 +58827,51 @@ async function dispatchLaneGroupInner(host, opts) {
58315
58827
  }
58316
58828
  return "foreign";
58317
58829
  }
58830
+ const foldedEvents = opts.events.filter((ev) => lane.folded.has(ev.messageId));
58831
+ for (const ev of opts.events) {
58832
+ if (!lane.folded.has(ev.messageId))
58833
+ host.dispatchedMessages.delete(ev.messageId);
58834
+ }
58835
+ if (foldedEvents.length === 0) {
58836
+ await ledger.completeIfIdle(lane.laneKey, unsettledInjections(host, opts.sessionKey) || opts.hasMoreLocal());
58837
+ return "foreign";
58838
+ }
58839
+ opts = { ...opts, events: foldedEvents, earlier: foldedEvents.slice(0, -1) };
58840
+ event = foldedEvents[foldedEvents.length - 1];
58318
58841
  const pending = ledger.takeFrame(lane);
58319
58842
  const frame = pending.frame;
58320
58843
  if (!frame && opts.events.every((ev) => lane.seen.has(ev.messageId))) {
58321
58844
  host.opts.log?.info(`lane group for ${event.messageId} already rendered by the server frame \u2014 no turn`);
58322
58845
  const acknowledge = host.opts.dispatchAdapter.acknowledgeDiscardedInjection?.bind(host.opts.dispatchAdapter);
58846
+ const withdrawn = [];
58323
58847
  if (acknowledge) {
58324
- for (const ev of opts.events) {
58325
- const deliveryKey = lane.folded.get(ev.messageId);
58326
- if (deliveryKey)
58327
- acknowledge(opts.sessionKey, deliveryKey);
58328
- }
58848
+ try {
58849
+ for (const ev of opts.events) {
58850
+ const deliveryKey = lane.folded.get(ev.messageId);
58851
+ if (!deliveryKey)
58852
+ continue;
58853
+ if (await acknowledge(opts.sessionKey, deliveryKey) === "cancelled")
58854
+ withdrawn.push(ev);
58855
+ }
58856
+ } catch (err) {
58857
+ host.opts.log?.warn(`discarded input acknowledgement failed for ${event.messageId}: ${String(err)}`);
58858
+ }
58859
+ }
58860
+ if (withdrawn.length > 0 && !opts.withdrawnRetry) {
58861
+ for (const ev of withdrawn)
58862
+ host.dispatchedMessages.delete(ev.messageId);
58863
+ await ledger.completeIfIdle(lane.laneKey, unsettledInjections(host, opts.sessionKey));
58864
+ if (ledger.getForEvent(event))
58865
+ return "no_turn";
58866
+ return dispatchLaneGroupInner(host, {
58867
+ ...opts,
58868
+ events: withdrawn,
58869
+ earlier: withdrawn.slice(0, -1),
58870
+ withdrawnRetry: true
58871
+ });
58329
58872
  }
58873
+ for (const ev of opts.events)
58874
+ host.dispatchedMessages.delete(ev.messageId);
58330
58875
  await runInPhaseSpan("settle", () => ledger.completeIfIdle(lane.laneKey, unsettledInjections(host, opts.sessionKey) || opts.hasMoreLocal()));
58331
58876
  return "no_turn";
58332
58877
  }
@@ -58350,6 +58895,9 @@ async function dispatchLaneGroupInner(host, opts) {
58350
58895
  ]);
58351
58896
  dispatched = await host.runDispatch(event, opts.sessionKey, opts.bodyPrefix + eventBody(event), opts.earlier, opts.captureText, inputLifecycle);
58352
58897
  } catch (err) {
58898
+ while (opts.sessionKey === host.opts.runtimeKey && hasMainSteers(host.dispatchState)) {
58899
+ await waitForMainSteers(host.dispatchState);
58900
+ }
58353
58901
  host.noteSessionLane(opts.sessionKey, null);
58354
58902
  await runInPhaseSpan("settle", () => ledger.release(lane.laneKey, "runtime_error", releaseErrorInfo(err, [host.opts.config.api_key])).catch(() => {
58355
58903
  }));
@@ -58361,6 +58909,9 @@ async function dispatchLaneGroupInner(host, opts) {
58361
58909
  if (!dispatched) {
58362
58910
  return "shutdown";
58363
58911
  }
58912
+ while (opts.sessionKey === host.opts.runtimeKey && hasMainSteers(host.dispatchState)) {
58913
+ await waitForMainSteers(host.dispatchState);
58914
+ }
58364
58915
  const settled = host.consumeTurnOutcome(opts.sessionKey);
58365
58916
  if (settled) {
58366
58917
  if (settled.kind === "deferred") {
@@ -58512,17 +59063,24 @@ async function consumeTypedDispatch(host, ref, run, hooks) {
58512
59063
  viaLegacyAck = true;
58513
59064
  resolved = settleAck(await runInPhaseSpan("settle", () => hooks.legacyAck(claimed.typedDispatchEventId)));
58514
59065
  } else {
58515
- const outcome = await runInPhaseSpan("settle", () => resolveDispatchByID(host, claimed.typedDispatchEventId, claimed.lane));
58516
- if (outcome === "unsupported") {
58517
- host.typedByIdCompleteUnsupported = true;
58518
- host.opts.log?.warn("server predates the by-id dispatch complete \u2014 falling back to the legacy typed ack");
58519
- viaLegacyAck = true;
58520
- resolved = settleAck(await runInPhaseSpan("settle", () => hooks.legacyAck(claimed.typedDispatchEventId)));
58521
- } else if (outcome === "stale") {
58522
- resolved = true;
58523
- } else {
58524
- resolved = outcome === "ok";
58525
- }
59066
+ await runInPhaseSpan("settle", () => ledger.confirmTyped(claimed, async () => {
59067
+ try {
59068
+ await host.opts.client.completeDispatch(host.opts.config.org_id, {
59069
+ dispatch_event_id: claimed.typedDispatchEventId,
59070
+ lane: claimed.lane,
59071
+ turn_outcome: "ok"
59072
+ });
59073
+ } catch (err) {
59074
+ if (!isByIDCompleteUnsupported(err))
59075
+ throw err;
59076
+ host.typedByIdCompleteUnsupported = true;
59077
+ if (!settleAck(await hooks.legacyAck(claimed.typedDispatchEventId))) {
59078
+ throw new Error("legacy typed acknowledgement unconfirmed");
59079
+ }
59080
+ await host.opts.client.releaseDispatchLane(host.opts.config.org_id, claimed.lane);
59081
+ }
59082
+ }));
59083
+ resolved = true;
58526
59084
  }
58527
59085
  }
58528
59086
  } catch (err) {
@@ -58532,7 +59090,8 @@ async function consumeTypedDispatch(host, ref, run, hooks) {
58532
59090
  settle(resolved);
58533
59091
  span.setAttribute("dispatch.lane_result", resolved ? "dispatched" : "released");
58534
59092
  if (resolved && !viaLegacyAck) {
58535
- ledger.dropLocal(claimed.laneKey);
59093
+ if (!claimed.settling)
59094
+ ledger.dropLocal(claimed.laneKey);
58536
59095
  } else {
58537
59096
  await runInPhaseSpan("settle", () => ledger.completeIfIdle(claimed.laneKey, false).catch(() => {
58538
59097
  }));
@@ -58696,6 +59255,19 @@ async function handleCompactSignal(host, data) {
58696
59255
  // ../agent-core/dist/gateway-runtime-turns.js
58697
59256
  var UNTARGETED_STEP = { target_type: "" };
58698
59257
  function handleRuntimeActivity(host, event) {
59258
+ if (event.kind === "observation") {
59259
+ const binding = host.sessionBindings.get(event.sessionKey);
59260
+ if (!binding || binding.runtimeSessionId !== event.event.runtimeSessionId) {
59261
+ host.opts.log?.warn(`runtime observation has no matching session binding on ${event.sessionKey}`);
59262
+ return;
59263
+ }
59264
+ host.inFlightRuntimeTurns += 1;
59265
+ void persistRuntimeObservation(host.stepPersister, binding.agentSessionId, UNTARGETED_STEP, event.event, host.opts.log, [host.opts.config.api_key]).finally(() => {
59266
+ host.inFlightRuntimeTurns -= 1;
59267
+ host.notifyDrainWaiters();
59268
+ });
59269
+ return;
59270
+ }
58699
59271
  const sessionKey = event.kind === "turn" ? event.turn.sessionKey : event.sessionKey;
58700
59272
  const label = event.kind === "turn" ? `runtime-initiated turn ${event.turn.groupKey} on ${sessionKey}` : `runtime child session close for ${sessionKey}`;
58701
59273
  const prior = host.runtimeActivityChains.get(sessionKey) ?? Promise.resolve();
@@ -58738,7 +59310,8 @@ async function runRuntimeTurn(host, turn) {
58738
59310
  };
58739
59311
  try {
58740
59312
  for await (const runtimeEvent of turn.events) {
58741
- deadline.touch();
59313
+ if (runtimeEvent.type !== "observation")
59314
+ deadline.touch();
58742
59315
  if (runtimeEvent.type === "runtime_session") {
58743
59316
  try {
58744
59317
  binding = await host.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath);
@@ -58748,7 +59321,7 @@ async function runRuntimeTurn(host, turn) {
58748
59321
  }
58749
59322
  continue;
58750
59323
  }
58751
- if (!binding) {
59324
+ if (!binding || runtimeEvent.type === "observation" && binding.runtimeSessionId !== runtimeEvent.runtimeSessionId) {
58752
59325
  droppedWithoutBinding += 1;
58753
59326
  continue;
58754
59327
  }
@@ -58763,7 +59336,8 @@ async function runRuntimeTurn(host, turn) {
58763
59336
  await host.createRuntimeStep(binding.agentSessionId, UNTARGETED_STEP, { type: "error", message: failure.stepMessage, groupKey }, void 0, contextFilePath);
58764
59337
  continue;
58765
59338
  }
58766
- await ensureBegun();
59339
+ if (runtimeEvent.type !== "observation")
59340
+ await ensureBegun();
58767
59341
  stepCount += 1;
58768
59342
  await host.createRuntimeStep(binding.agentSessionId, UNTARGETED_STEP, runtimeEvent, void 0, contextFilePath);
58769
59343
  }
@@ -58972,6 +59546,19 @@ var DispatchInactivityDeadline = class {
58972
59546
  this.onExpire();
58973
59547
  }, delayMs);
58974
59548
  }
59549
+ /**
59550
+ * Re-arm after an expiry that was only reported, not acted on (the
59551
+ * no-abort default: a long silent turn is logged at intervals, never
59552
+ * killed — attached-turn-ownership).
59553
+ */
59554
+ rearm() {
59555
+ if (this.disposed || this.timeoutMs <= 0)
59556
+ return;
59557
+ this.expired = false;
59558
+ this.lastActivityAt = Date.now();
59559
+ if (!this.timer)
59560
+ this.arm(this.timeoutMs);
59561
+ }
58975
59562
  dispose() {
58976
59563
  if (this.disposed)
58977
59564
  return;
@@ -59881,6 +60468,7 @@ function recordToolCall(sessionKey) {
59881
60468
  }
59882
60469
 
59883
60470
  // ../agent-core/dist/gateway-base.js
60471
+ var AUTH_INVALID_EXIT_CODE = 86;
59884
60472
  var TYPED_EVENT_KINDS = {
59885
60473
  task_assign: { type: "task", ackSourceType: "task_activity" },
59886
60474
  task_update: { type: "task", ackSourceType: "task_activity" },
@@ -59917,11 +60505,11 @@ function parseDispatchDeadlineMs(raw) {
59917
60505
  }
59918
60506
  function inputStepIdempotencyKey(event) {
59919
60507
  if (event.type === "message" || event.type === "channel_message") {
59920
- return event.messageId ? `input:${event.messageId}` : randomUUID();
60508
+ return event.messageId ? `input:${event.messageId}` : randomUUID2();
59921
60509
  }
59922
60510
  if (event.dispatchEventId)
59923
60511
  return `input:${event.dispatchEventId}`;
59924
- return randomUUID();
60512
+ return randomUUID2();
59925
60513
  }
59926
60514
  function resolveStepTarget(event) {
59927
60515
  if (event.input?.step_target) {
@@ -60050,14 +60638,9 @@ var ParallAgentGateway = class {
60050
60638
  pendingRestartNotification = null;
60051
60639
  laneLedger;
60052
60640
  stepPersister;
60053
- // Single entry point for session active/idle writes — serialized
60054
- // desired-state reconciler (see session-lifecycle.ts). The gateway only
60055
- // declares turn boundaries; ordering, retries and stale-finish rejection
60056
- // live in the coordinator.
60641
+ // Session lifecycle owns ordering, retries and stale-finish rejection.
60057
60642
  sessionLifecycle;
60058
- // Normal fork teardown use case: seal → drain → close → release. The
60059
- // gateway only triggers it; ordering and ownership live in the finalizer
60060
- // (see fork-session-finalizer.ts).
60643
+ // ForkSessionFinalizer owns seal → drain → close → release.
60061
60644
  forkFinalizer;
60062
60645
  // Sticky fallback: flipped when the server predates the ledger (claim
60063
60646
  // endpoint 404) so every subsequent dispatch uses the legacy flow.
@@ -60073,10 +60656,12 @@ var ParallAgentGateway = class {
60073
60656
  SHUTDOWN_DEADLINE_MS;
60074
60657
  FORK_DEADLINE_MS;
60075
60658
  DISPATCH_DEADLINE_MS;
60659
+ INACTIVITY_WARN_MS;
60076
60660
  constructor(opts) {
60077
60661
  this.opts = opts;
60078
60662
  opts = this.opts = { ...opts, log: redactLogger(opts.log, [opts.config.api_key]) };
60079
60663
  if (opts.dispatchContextDir) {
60664
+ const recovery = new DispatchRecovery(() => this.catchUpFromDispatch(), () => this.shuttingDown, (err) => opts.log?.warn(`dispatch recovery failed: ${String(err)}`));
60080
60665
  this.laneLedger = new LaneLedger({
60081
60666
  client: opts.client,
60082
60667
  orgId: opts.config.org_id,
@@ -60084,12 +60669,17 @@ var ParallAgentGateway = class {
60084
60669
  log: opts.log,
60085
60670
  knownSecrets: [opts.config.api_key],
60086
60671
  coverageMode: opts.dispatchAdapter.inputLifecycleMode ?? "implicit",
60672
+ settlementMode: opts.dispatchAdapter.inputSettlementMode,
60673
+ instanceId: opts.instanceId,
60674
+ terminalRetryDelaysMs: opts.laneTerminalRetryDelaysMs,
60675
+ onRecovered: recovery.request,
60087
60676
  releaseLocalClaims: (sourceIds) => releaseLocalMessageClaims(this.dispatchedMessages, sourceIds)
60088
60677
  });
60089
60678
  }
60090
60679
  this.SHUTDOWN_DEADLINE_MS = opts.shutdownDeadlineMs ?? 6e4;
60091
- this.FORK_DEADLINE_MS = opts.forkDeadlineMs ?? 2 * 60 * 6e4;
60092
- this.DISPATCH_DEADLINE_MS = opts.dispatchDeadlineMs ?? 20 * 6e4;
60680
+ this.FORK_DEADLINE_MS = opts.forkDeadlineMs ?? 0;
60681
+ this.DISPATCH_DEADLINE_MS = opts.dispatchDeadlineMs ?? 0;
60682
+ this.INACTIVITY_WARN_MS = opts.inactivityWarnMs ?? 30 * 6e4;
60093
60683
  this.unsubscribeRuntimeActivity = opts.dispatchAdapter.subscribeRuntimeActivity?.((event) => this.handleRuntimeActivity(event));
60094
60684
  this.stepPersister = new StepPersister({
60095
60685
  client: opts.client,
@@ -60167,12 +60757,34 @@ var ParallAgentGateway = class {
60167
60757
  }
60168
60758
  });
60169
60759
  this.opts.log?.info(`connecting to ${this.opts.connectionLabel ?? "Parall WS"}...`);
60760
+ let settled = false;
60761
+ let onSettled = null;
60762
+ const finish = async (opts) => {
60763
+ if (settled)
60764
+ return;
60765
+ settled = true;
60766
+ await this.shutdown(opts);
60767
+ onSettled?.();
60768
+ };
60769
+ ws.onAuthInvalid((event) => {
60770
+ const exitCode = this.opts.authInvalidExitCode === void 0 ? AUTH_INVALID_EXIT_CODE : this.opts.authInvalidExitCode;
60771
+ log?.error(`AUTH-INVALID agent=${this.opts.agentUserId} status=${event.status}${event.code ? ` code=${event.code}` : ""} \u2014 the server rejected this runtime's credential (${event.message}); the key is invalid, expired, or revoked.`);
60772
+ if (exitCode === null) {
60773
+ log?.error("AUTH-INVALID: reconnects are stopped and this account gateway has shut down. Refresh this account\u2019s credential and start it again; other accounts in this process are unaffected.");
60774
+ } else {
60775
+ log?.error(`AUTH-INVALID: reconnects are stopped. Exiting with code ${exitCode} so the supervisor can re-mint the launch credential and restart this agent; if no supervisor manages this process, replace its PRLL_API_KEY and start it again.`);
60776
+ process.exitCode = exitCode;
60777
+ }
60778
+ void finish({ skipDrain: true });
60779
+ });
60170
60780
  await ws.connect();
60171
60781
  return new Promise((resolve3) => {
60172
- abortSignal.addEventListener("abort", async () => {
60173
- await this.shutdown();
60782
+ if (settled) {
60174
60783
  resolve3();
60175
- });
60784
+ return;
60785
+ }
60786
+ onSettled = resolve3;
60787
+ abortSignal.addEventListener("abort", () => void finish());
60176
60788
  });
60177
60789
  }
60178
60790
  tryClaimMessage(id) {
@@ -60404,100 +61016,7 @@ var ParallAgentGateway = class {
60404
61016
  });
60405
61017
  }
60406
61018
  async createRuntimeStep(sessionId, target, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2) {
60407
- switch (runtimeEvent.type) {
60408
- case "thinking":
60409
- await this.stepPersister.persist(sessionId, "thinking", {
60410
- step_type: "thinking",
60411
- target_type: target.target_type,
60412
- target_id: target.target_id,
60413
- idempotency_key: randomUUID(),
60414
- content: { text: runtimeEvent.text },
60415
- group_key: runtimeEvent.groupKey
60416
- });
60417
- break;
60418
- case "text":
60419
- await this.stepPersister.persist(sessionId, "text", {
60420
- step_type: "text",
60421
- target_type: target.target_type,
60422
- target_id: target.target_id,
60423
- idempotency_key: randomUUID(),
60424
- content: {
60425
- text: runtimeEvent.text,
60426
- suppressed: runtimeEvent.project !== true
60427
- },
60428
- projection: runtimeEvent.project === true,
60429
- group_key: runtimeEvent.groupKey
60430
- });
60431
- break;
60432
- case "tool_call": {
60433
- const step = await this.stepPersister.persist(sessionId, "tool_call", {
60434
- step_type: "tool_call",
60435
- target_type: target.target_type,
60436
- target_id: target.target_id,
60437
- // call_id is session-unique for bridge runtimes (server-enforced),
60438
- // so the bare form anchors the tool step pair across retries —
60439
- // unlike parel's turn-scoped `tc:{turnId}:{callId}` (see
60440
- // protocol-vectors/agent-steps.json).
60441
- idempotency_key: `tc:${runtimeEvent.callId}`,
60442
- content: {
60443
- call_id: runtimeEvent.callId,
60444
- tool_name: runtimeEvent.toolName,
60445
- tool_input: runtimeEvent.input,
60446
- status: "running",
60447
- started_at: runtimeEvent.startedAt ?? (/* @__PURE__ */ new Date()).toISOString()
60448
- },
60449
- group_key: runtimeEvent.groupKey,
60450
- runtime_key: runtimeEvent.callId
60451
- });
60452
- if (step) {
60453
- if (contextFilePath) {
60454
- this.updateContextFileStepId(contextFilePath, step.id);
60455
- } else if (stepIdFilePath) {
60456
- this.writeStepIdFile(stepIdFilePath, step.id);
60457
- }
60458
- if (laneContextFilePath2) {
60459
- this.updateContextFileStepId(laneContextFilePath2, step.id);
60460
- }
60461
- }
60462
- break;
60463
- }
60464
- case "tool_result":
60465
- await this.stepPersister.persist(sessionId, "tool_result", {
60466
- step_type: "tool_result",
60467
- target_type: target.target_type,
60468
- target_id: target.target_id,
60469
- idempotency_key: `tr:${runtimeEvent.callId}`,
60470
- content: {
60471
- call_id: runtimeEvent.callId,
60472
- tool_name: runtimeEvent.toolName,
60473
- status: runtimeEvent.error ? "error" : "success",
60474
- output: runtimeEvent.output,
60475
- duration_ms: runtimeEvent.durationMs ?? 0,
60476
- collapsible: true
60477
- },
60478
- group_key: runtimeEvent.groupKey
60479
- });
60480
- if (contextFilePath) {
60481
- this.updateContextFileStepId(contextFilePath, null);
60482
- } else if (stepIdFilePath) {
60483
- this.clearStepIdFile(stepIdFilePath);
60484
- }
60485
- if (laneContextFilePath2) {
60486
- this.updateContextFileStepId(laneContextFilePath2, null);
60487
- }
60488
- break;
60489
- case "error":
60490
- await this.stepPersister.persist(sessionId, "error", {
60491
- step_type: "text",
60492
- target_type: target.target_type,
60493
- target_id: target.target_id,
60494
- idempotency_key: randomUUID(),
60495
- content: buildErrorStepContent(runtimeEvent.message),
60496
- projection: false,
60497
- group_key: runtimeEvent.groupKey
60498
- });
60499
- break;
60500
- }
61019
+ return createRuntimeStep(this, sessionId, target, runtimeEvent, stepIdFilePath, contextFilePath, laneContextFilePath2);
60501
61020
  }
60502
61021
  writeContextFile(filePath, ctx) {
60503
61022
  try {
@@ -60590,6 +61109,7 @@ var ParallAgentGateway = class {
60590
61109
  step_id: null,
60591
61110
  dispatch_event_id: activeLane?.typedDispatchEventId ?? activeLane?.folded.get(event.messageId) ?? null,
60592
61111
  lane: activeLane?.lane ?? null,
61112
+ settlement_mode: activeLane?.settlementMode ?? "legacy",
60593
61113
  target_uri: activeLane?.targetUri ?? null,
60594
61114
  thread_root_id: activeLane?.threadRootId ?? null,
60595
61115
  // Typed binding hint for the CLI: which task this dispatch is about
@@ -60609,13 +61129,19 @@ var ParallAgentGateway = class {
60609
61129
  threadRootId: contextBody.thread_root_id
60610
61130
  });
60611
61131
  this.inFlightDispatches++;
60612
- const dispatchDeadline = this.dispatchInactivityDeadlines.start(sessionKey, this.DISPATCH_DEADLINE_MS, () => {
60613
- this.opts.log?.warn(`dispatch inactivity deadline exceeded (${this.DISPATCH_DEADLINE_MS}ms) for ${event.messageId} on ${sessionKey}; aborting`);
60614
- try {
60615
- this.opts.dispatchAdapter.abortDispatch?.(sessionKey);
60616
- } catch (err) {
60617
- this.opts.log?.warn(`abortDispatch threw for ${sessionKey}: ${String(err)}`);
61132
+ const abortAfterMs = this.DISPATCH_DEADLINE_MS;
61133
+ const dispatchDeadline = this.dispatchInactivityDeadlines.start(sessionKey, abortAfterMs > 0 ? abortAfterMs : this.INACTIVITY_WARN_MS, () => {
61134
+ if (abortAfterMs > 0) {
61135
+ this.opts.log?.warn(`dispatch inactivity deadline exceeded (${abortAfterMs}ms) for ${event.messageId} on ${sessionKey}; aborting`);
61136
+ try {
61137
+ this.opts.dispatchAdapter.abortDispatch?.(sessionKey);
61138
+ } catch (err) {
61139
+ this.opts.log?.warn(`abortDispatch threw for ${sessionKey}: ${String(err)}`);
61140
+ }
61141
+ return;
60618
61142
  }
61143
+ this.opts.log?.warn(`no runtime output for ${Math.round(this.INACTIVITY_WARN_MS / 6e4)} min on ${sessionKey} (${event.messageId}); the turn stays open \u2014 only the runtime finishing or a process restart ends it`);
61144
+ dispatchDeadline.rearm();
60619
61145
  });
60620
61146
  let binding = this.sessionBindings.get(sessionKey);
60621
61147
  let inputStepsCreated = false;
@@ -60640,12 +61166,14 @@ var ParallAgentGateway = class {
60640
61166
  inputLifecycle,
60641
61167
  noteActivity: dispatchDeadline.touch
60642
61168
  })) {
60643
- dispatchDeadline.touch();
61169
+ if (runtimeEvent.type !== "observation")
61170
+ dispatchDeadline.touch();
60644
61171
  if (runtimeEvent.type === "runtime_session") {
60645
61172
  const priorAgentSessionId = binding?.agentSessionId;
60646
61173
  binding = await runInPhaseSpan("session", () => this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2));
60647
61174
  if (activeLane) {
60648
61175
  bindLaneSession(activeLane, binding.agentSessionId);
61176
+ await this.laneLedger?.syncInputAttribution(activeLane);
60649
61177
  }
60650
61178
  if (event.targetType === "channel_conversation" && binding.agentSessionId !== priorAgentSessionId) {
60651
61179
  try {
@@ -60664,6 +61192,14 @@ var ParallAgentGateway = class {
60664
61192
  }
60665
61193
  continue;
60666
61194
  }
61195
+ if (runtimeEvent.type === "observation") {
61196
+ if (binding?.runtimeSessionId === runtimeEvent.runtimeSessionId) {
61197
+ await this.createRuntimeStep(binding.agentSessionId, stepTarget, runtimeEvent);
61198
+ } else {
61199
+ this.opts.log?.warn("runtime observation has no matching active session binding");
61200
+ }
61201
+ continue;
61202
+ }
60667
61203
  if (runtimeEvent.type === "turn_outcome") {
60668
61204
  const outcomeEvent = redactTurnOutcome(runtimeEvent, [this.opts.config.api_key]);
60669
61205
  turnOutcomeEvent = outcomeEvent;
@@ -61036,6 +61572,8 @@ var ParallAgentGateway = class {
61036
61572
  while (this.idleCompact.inFlight)
61037
61573
  await this.idleCompact.inFlight;
61038
61574
  while (this.dispatchState.mainBuffer.length > 0) {
61575
+ while (hasMainSteers(this.dispatchState))
61576
+ await waitForMainSteers(this.dispatchState);
61039
61577
  if (this.shuttingDown) {
61040
61578
  this.opts.log?.info(`drainMainBuffer halted (shutting down) \u2014 ${this.dispatchState.mainBuffer.length} buffered, ${this.dispatchState.pendingForkResults.length} pending fork results left for catch-up`);
61041
61579
  break;
@@ -61285,9 +61823,11 @@ var ParallAgentGateway = class {
61285
61823
  const activeFork = createActiveForkState(fork, event.targetId, continuationPrefix);
61286
61824
  this.forkStates.set(event.targetId, activeFork);
61287
61825
  this.dispatchState.activeForks.set(event.targetId, fork.sessionKey);
61288
- activeFork.deadlineTimer = setTimeout(() => {
61289
- this.abortFork(event.targetId, `deadline exceeded (${this.FORK_DEADLINE_MS}ms)`);
61290
- }, this.FORK_DEADLINE_MS);
61826
+ if (this.FORK_DEADLINE_MS > 0) {
61827
+ activeFork.deadlineTimer = setTimeout(() => {
61828
+ this.abortFork(event.targetId, `deadline exceeded (${this.FORK_DEADLINE_MS}ms)`);
61829
+ }, this.FORK_DEADLINE_MS);
61830
+ }
61291
61831
  const firstEventPromise = new Promise((resolve3) => {
61292
61832
  activeFork.queue.push({ event, resolve: resolve3 });
61293
61833
  });
@@ -61486,12 +62026,16 @@ ${fullSummary}` : fullSummary;
61486
62026
  this.sessionId = data.session_id ?? "";
61487
62027
  if (this.forkStates.size > 0) {
61488
62028
  const targetIds = [...this.forkStates.keys()];
61489
- log?.info(`aborting ${targetIds.length} active fork(s) on reconnect`);
61490
- for (const targetId of targetIds) {
61491
- this.abortFork(targetId, "ws reconnect");
62029
+ if (this.opts.instanceId) {
62030
+ log?.info(`${targetIds.length} active fork(s) continue across the reconnect`);
62031
+ } else {
62032
+ log?.info(`aborting ${targetIds.length} active fork(s) on reconnect`);
62033
+ for (const targetId of targetIds) {
62034
+ this.abortFork(targetId, "ws reconnect");
62035
+ }
61492
62036
  }
61493
62037
  }
61494
- if (this.laneLedger && this.inFlightDispatches === 0 && this.laneLedger.activeCount > 0) {
62038
+ if (this.laneLedger && !this.opts.instanceId && this.inFlightDispatches === 0 && this.laneLedger.activeCount > 0) {
61495
62039
  log?.info(`releasing ${this.laneLedger.activeCount} stale lane(s) on reconnect`);
61496
62040
  await this.laneLedger.releaseAll();
61497
62041
  }
@@ -61538,6 +62082,7 @@ ${fullSummary}` : fullSummary;
61538
62082
  uptime: os.uptime()
61539
62083
  });
61540
62084
  }, intervalSec * 1e3);
62085
+ await this.laneLedger?.retryConfirmations();
61541
62086
  this.catchUpFromDispatch().catch((err) => {
61542
62087
  log?.warn(`dispatch catch-up failed: ${String(err)}`);
61543
62088
  });
@@ -61604,13 +62149,9 @@ ${fullSummary}` : fullSummary;
61604
62149
  }
61605
62150
  }
61606
62151
  }
61607
- /**
61608
- * Nothing in flight: no dispatch, no runtime-initiated turn, and the
61609
- * runtime itself reports idle (isBusy — a turn it is executing that has
61610
- * not surfaced yet, or a follow-up hold after background work finished).
61611
- */
62152
+ /** Drain includes in-flight native work and unfinished steer registration. */
61612
62153
  isDrained() {
61613
- return this.inFlightDispatches === 0 && this.inFlightRuntimeTurns === 0 && !this.adapterBusy();
62154
+ return this.inFlightDispatches === 0 && this.inFlightRuntimeTurns === 0 && !hasMainSteers(this.dispatchState) && !this.adapterBusy();
61614
62155
  }
61615
62156
  adapterBusy() {
61616
62157
  try {
@@ -61623,10 +62164,12 @@ ${fullSummary}` : fullSummary;
61623
62164
  notifyDrainWaiters() {
61624
62165
  this.drainGate.notify();
61625
62166
  }
61626
- async shutdown() {
62167
+ async shutdown(opts = { skipDrain: false }) {
61627
62168
  this.shuttingDown = true;
61628
62169
  this.idleCompact.abort?.();
61629
- if (!this.isDrained()) {
62170
+ if (opts.skipDrain && !this.isDrained()) {
62171
+ this.opts.log?.warn(`skipping drain (credential rejected): ${this.inFlightDispatches} dispatch(es), ${this.inFlightRuntimeTurns} runtime-initiated turn(s) abandoned to server redelivery`);
62172
+ } else if (!this.isDrained()) {
61630
62173
  this.opts.log?.info(`draining ${this.inFlightDispatches} in-flight dispatch(es), ${this.inFlightRuntimeTurns} runtime-initiated turn(s), runtime busy=${this.adapterBusy()}, deadline ${this.SHUTDOWN_DEADLINE_MS}ms`);
61631
62174
  await this.drainGate.wait(this.SHUTDOWN_DEADLINE_MS);
61632
62175
  if (!this.isDrained()) {
@@ -61637,7 +62180,7 @@ ${fullSummary}` : fullSummary;
61637
62180
  }
61638
62181
  if (this.heartbeatTimer)
61639
62182
  clearInterval(this.heartbeatTimer);
61640
- if (this.laneLedger && this.laneLedger.activeCount > 0) {
62183
+ if (this.laneLedger) {
61641
62184
  this.opts.log?.info(`releasing ${this.laneLedger.activeCount} lane(s) on shutdown`);
61642
62185
  await this.laneLedger.releaseAll();
61643
62186
  }
@@ -61657,6 +62200,8 @@ ${fullSummary}` : fullSummary;
61657
62200
  this.opts.log?.warn(`${lifecycleRemaining} session lifecycle write(s) unreconciled at shutdown`);
61658
62201
  }
61659
62202
  this.sessionLifecycle.dispose();
62203
+ await this.laneLedger?.retryConfirmations();
62204
+ this.laneLedger?.dispose();
61660
62205
  this.opts.ws.disconnect();
61661
62206
  this.unsubscribeRuntimeActivity?.();
61662
62207
  this.opts.log?.info(`disconnected`);
@@ -62649,17 +63194,17 @@ async function startWikiHelper(params) {
62649
63194
  }
62650
63195
 
62651
63196
  // dist/oc-session.js
62652
- import { randomUUID as randomUUID2 } from "node:crypto";
63197
+ import { randomUUID as randomUUID3 } from "node:crypto";
62653
63198
  import { existsSync as existsSync4, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
62654
63199
  import { join as join7, resolve as resolve2 } from "node:path";
62655
63200
  var CURRENT_SESSION_VERSION = 3;
62656
63201
  function generateId(existing) {
62657
63202
  for (let i = 0; i < 100; i++) {
62658
- const id = randomUUID2().slice(0, 8);
63203
+ const id = randomUUID3().slice(0, 8);
62659
63204
  if (!existing.has(id))
62660
63205
  return id;
62661
63206
  }
62662
- return randomUUID2();
63207
+ return randomUUID3();
62663
63208
  }
62664
63209
  function loadEntries(filePath) {
62665
63210
  if (!existsSync4(filePath))
@@ -62774,14 +63319,14 @@ var SessionManager = class _SessionManager {
62774
63319
  if (header && header.version > CURRENT_SESSION_VERSION) {
62775
63320
  throw new Error(`Session file ${this.sessionFile} uses version ${header.version}, but oc-session.ts only supports up to ${CURRENT_SESSION_VERSION}. Sync oc-session.ts with upstream pi-coding-agent (see AGENTS.md step 4).`);
62776
63321
  }
62777
- this.sessionId = header?.id ?? randomUUID2();
63322
+ this.sessionId = header?.id ?? randomUUID3();
62778
63323
  if (migrate(this.fileEntries))
62779
63324
  this.rewrite();
62780
63325
  this.buildIndex();
62781
63326
  this.flushed = true;
62782
63327
  }
62783
63328
  initEmpty() {
62784
- this.sessionId = randomUUID2();
63329
+ this.sessionId = randomUUID3();
62785
63330
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
62786
63331
  this.fileEntries = [
62787
63332
  {
@@ -62858,7 +63403,7 @@ var SessionManager = class _SessionManager {
62858
63403
  if (branch.length === 0)
62859
63404
  throw new Error(`Entry ${leafId} not found`);
62860
63405
  const pathWithoutLabels = branch.filter((e) => e.type !== "label");
62861
- const newId = randomUUID2();
63406
+ const newId = randomUUID3();
62862
63407
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
62863
63408
  const ts = timestamp.replace(/[:.]/g, "-");
62864
63409
  const newFile = join7(this.sessionDir, `${ts}_${newId}.jsonl`);
@@ -63429,13 +63974,18 @@ var parallGateway = {
63429
63974
  runtimeKey: orchestratorKey,
63430
63975
  runtimeRef: { hostname: os2.hostname(), pid: process.pid },
63431
63976
  dispatchAdapter,
63977
+ // One OpenClaw host embeds many account gateways in one process: a
63978
+ // rejected credential stops THIS account's gateway only — it must
63979
+ // not set the host-wide process exit code or claim the process is
63980
+ // exiting.
63981
+ authInvalidExitCode: null,
63432
63982
  // Opts openclaw into the dispatch ledger (claim/fold/complete +
63433
63983
  // idempotent reply effects) — the same shared-gateway machinery
63434
63984
  // claude/codex ride; openclaw stays buffer-only (no mid-turn steer),
63435
63985
  // which the ledger does not require. Replies already flow through
63436
63986
  // @parall/cli, which reads PRLL_CONTEXT_DIR (injected in hooks.ts)
63437
63987
  // to bind dispatch_lane + reply effect keys.
63438
- // Design: docs/engineering-design/dispatch-convergence-design.md §7 (S2).
63988
+ // Design: docs/engineering-design/agent-dispatch-idempotency-design.md#effects.
63439
63989
  dispatchContextDir: dispatchLaneContextDir(stateDir),
63440
63990
  // Per-session context file (PRLL_CONTEXT_FILE contract) — the CLI's
63441
63991
  // TYPED dispatch binding (parall tasks update → task_update effect)