@parall/parall 1.63.0 → 1.64.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.
@@ -52286,6 +52286,41 @@ function buildForkResultPrefix(results) {
52286
52286
  return blocks.join("\n\n") + "\n\n---\n\n";
52287
52287
  }
52288
52288
 
52289
+ // ../agent-core/dist/dispatch-recovery.js
52290
+ var DispatchRecovery = class {
52291
+ scan;
52292
+ stopped;
52293
+ onError;
52294
+ running = false;
52295
+ requested = false;
52296
+ constructor(scan, stopped, onError) {
52297
+ this.scan = scan;
52298
+ this.stopped = stopped;
52299
+ this.onError = onError;
52300
+ }
52301
+ request = () => {
52302
+ if (this.stopped())
52303
+ return;
52304
+ this.requested = true;
52305
+ if (this.running)
52306
+ return;
52307
+ this.running = true;
52308
+ void this.run();
52309
+ };
52310
+ async run() {
52311
+ try {
52312
+ while (this.requested && !this.stopped()) {
52313
+ this.requested = false;
52314
+ await this.scan();
52315
+ }
52316
+ } catch (err) {
52317
+ this.onError(err);
52318
+ } finally {
52319
+ this.running = false;
52320
+ }
52321
+ }
52322
+ };
52323
+
52289
52324
  // ../agent-core/dist/gateway-base.js
52290
52325
  import * as os from "node:os";
52291
52326
 
@@ -52325,6 +52360,7 @@ function browserViewerRequestOptions(command, opts) {
52325
52360
 
52326
52361
  // ../sdk/dist/clip-connection-endpoints.js
52327
52362
  var clipConnectionEndpoints = {
52363
+ ORG_CLIP_MCP_OAUTH_ATTEMPT: (orgId, clipId, attemptId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/mcp-oauth-attempts/${attemptId}`,
52328
52364
  CLIP_CONNECTIONS: (orgId, clipId) => `/api/v1/orgs/${orgId}/clip-registry/${clipId}/connections`,
52329
52365
  CLIP_CONNECTION_ACCESS: (orgId, connId) => `/api/v1/orgs/${orgId}/clip-connections/${connId}/access`,
52330
52366
  CLIP_CONNECTION: (orgId, connId) => `/api/v1/orgs/${orgId}/clip-connections/${connId}`
@@ -52625,6 +52661,7 @@ var ENDPOINTS = {
52625
52661
  TRIGGER: (orgId, triggerId) => `${API_BASE2}/orgs/${orgId}/external-triggers/${triggerId}`,
52626
52662
  /** @deprecated Use TRIGGER. Same compatibility path. */
52627
52663
  EXTERNAL_TRIGGER: (orgId, triggerId) => `${API_BASE2}/orgs/${orgId}/external-triggers/${triggerId}`,
52664
+ PLATFORM_TRIGGER_SCHEMA: (orgId) => `${API_BASE2}/orgs/${orgId}/platform-trigger-schema`,
52628
52665
  TRIGGER_RUNS: (orgId) => `${API_BASE2}/orgs/${orgId}/external-trigger-runs`,
52629
52666
  /** @deprecated Use TRIGGER_RUNS. Same compatibility path. */
52630
52667
  EXTERNAL_TRIGGER_RUNS: (orgId) => `${API_BASE2}/orgs/${orgId}/external-trigger-runs`,
@@ -52802,6 +52839,8 @@ var ENDPOINTS = {
52802
52839
  BILLING_CHECKOUT: (orgId) => `${API_BASE2}/orgs/${orgId}/billing/checkout`,
52803
52840
  BILLING_AUTO_RELOAD: (orgId) => `${API_BASE2}/orgs/${orgId}/billing/auto-reload`,
52804
52841
  BILLING_SETUP_INTENT: (orgId) => `${API_BASE2}/orgs/${orgId}/billing/setup-intent`,
52842
+ BILLING_USAGE_DAILY: (orgId) => `${API_BASE2}/orgs/${orgId}/billing/usage-daily`,
52843
+ BILLING_USAGE_HEATMAP: (orgId) => `${API_BASE2}/orgs/${orgId}/billing/usage-heatmap`,
52805
52844
  COMPUTE_PRICING: () => `${API_BASE2}/billing/compute-pricing`,
52806
52845
  // Runtime capability table (public, no auth) — SSOT for the create/settings
52807
52846
  // interlock: per-runtime compute modes, native model family, cross-family
@@ -52836,16 +52875,6 @@ var ENDPOINTS = {
52836
52875
  BROWSER_PROFILE_VIEWER_COMMAND: (orgId, profileId) => `${API_BASE2}/orgs/${orgId}/browser-profiles/${profileId}/viewer/command`,
52837
52876
  // Edge device endpoints
52838
52877
  ...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
52878
  ORG_BROWSER_USE_OPERATIONS: (orgId) => `/api/v1/orgs/${orgId}/browser-use/operations`,
52850
52879
  ORG_BROWSER_USE_OPERATION: (orgId, operationId) => `/api/v1/orgs/${orgId}/browser-use/operations/${encodeURIComponent(operationId)}`,
52851
52880
  // Per-profile egress proxy (hosted Cloud Profiles; manager-only, human-only).
@@ -53392,10 +53421,62 @@ var ClipConnectionClient = class extends ChannelConversationClient {
53392
53421
  async putClipConnectionAccess(orgId, connId, input) {
53393
53422
  return this.request("PUT", ENDPOINTS.CLIP_CONNECTION_ACCESS(orgId, connId), input);
53394
53423
  }
53424
+ /** Start OAuth using the reviewed platform app or provider registration ladder.
53425
+ * Allow discovery (30s), persistence (10s), and network overhead to finish. */
53426
+ async createClipMCPOAuthConnection(orgId, clipId, serverUrl, alias, oauthClient) {
53427
+ return this.request("POST", ENDPOINTS.ORG_CLIP_MCP_CONFIGS(orgId, clipId), {
53428
+ server_url: serverUrl,
53429
+ auth_type: "oauth",
53430
+ ...alias ? { alias } : {},
53431
+ ...oauthClient ? { oauth_client: oauthClient } : {}
53432
+ }, void 0, false, { timeoutMs: 5e4 });
53433
+ }
53434
+ /** Cancel exactly one pending attempt; requires its initiator or an org admin. */
53435
+ async cancelClipMCPOAuthAttempt(orgId, clipId, attemptId) {
53436
+ return this.request("DELETE", ENDPOINTS.ORG_CLIP_MCP_OAUTH_ATTEMPT(orgId, clipId, attemptId));
53437
+ }
53395
53438
  };
53396
53439
 
53397
53440
  // ../sdk/dist/browser-profile-client.js
53398
53441
  var BrowserProfileClient = class extends ClipConnectionClient {
53442
+ /** Metadata-only compatibility discovery. Management scope errors propagate;
53443
+ * callers with a known Profile can execute without this discovery chain. */
53444
+ async listBrowserUseProfiles(orgId, profileId) {
53445
+ const me = await this.request("GET", ENDPOINTS.USERS_ME);
53446
+ const devices = await this.request("GET", ENDPOINTS.ORG_EDGE_DEVICES(orgId));
53447
+ const data = [];
53448
+ for (const device of devices.data) {
53449
+ const personal = device.managed_by === "member";
53450
+ if (personal && device.owner_user_id !== me.id)
53451
+ continue;
53452
+ const profiles = await this.request("GET", ENDPOINTS.ORG_EDGE_PROFILES(orgId, device.id));
53453
+ for (const profile of profiles) {
53454
+ if (profileId && profile.id !== profileId)
53455
+ continue;
53456
+ const access = personal ? null : await this.getBrowserProfileAccess(orgId, device.id, profile.name);
53457
+ if (!personal && !access?.org_public && !(access?.grants ?? []).some((g) => g.user_id === me.id && g.grants.length > 0))
53458
+ continue;
53459
+ data.push({
53460
+ profile_id: profile.id,
53461
+ name: profile.name,
53462
+ device_name: device.name,
53463
+ placement: device.placement ?? "byoc",
53464
+ lifecycle_state: profile.lifecycle_state,
53465
+ can_access: true,
53466
+ availability: device.placement === "hosted" ? device.hosted_state ?? "unknown" : device.status
53467
+ });
53468
+ }
53469
+ }
53470
+ return { data };
53471
+ }
53472
+ /** Submit once: even refresh-enabled clients must not replay browser actions. */
53473
+ async createBrowserUseOperation(orgId, request3) {
53474
+ return this.request("POST", ENDPOINTS.ORG_BROWSER_USE_OPERATIONS(orgId), request3, void 0, true);
53475
+ }
53476
+ /** Recover proof/data without creating a new browser operation. */
53477
+ async getBrowserUseOperation(orgId, operationId, opts) {
53478
+ return this.request("GET", ENDPOINTS.ORG_BROWSER_USE_OPERATION(orgId, operationId), void 0, void 0, false, opts);
53479
+ }
53399
53480
  async getBrowserProfileAccess(orgId, edgeId, profileName) {
53400
53481
  return this.request("GET", ENDPOINTS.ORG_EDGE_PROFILE_ACCESS(orgId, edgeId, profileName));
53401
53482
  }
@@ -53839,8 +53920,8 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
53839
53920
  return this.request("POST", ENDPOINTS.AUTH_OAUTH_EXCHANGE, { code });
53840
53921
  }
53841
53922
  // ---- WebSocket ----
53842
- async getWsTicket() {
53843
- return this.request("POST", ENDPOINTS.WS_TICKET);
53923
+ async getWsTicket(req) {
53924
+ return this.request("POST", ENDPOINTS.WS_TICKET, req);
53844
53925
  }
53845
53926
  // ---- Users ----
53846
53927
  async getMe() {
@@ -54827,8 +54908,8 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
54827
54908
  * no_action sweep otherwise. Idempotent.
54828
54909
  *
54829
54910
  * @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).
54911
+ * `sources` form, which also carries `turn_outcome`. Retirement conditions:
54912
+ * agent-dispatch-idempotency-design.md#compatibility.
54832
54913
  */
54833
54914
  async completeDispatchSources(orgId, req) {
54834
54915
  return this.request("POST", ENDPOINTS.DISPATCH_COMPLETE_SOURCES(orgId), req);
@@ -55071,6 +55152,9 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
55071
55152
  async getTriggerSchema(orgId, connectionId) {
55072
55153
  return this.request("GET", ENDPOINTS.TRIGGER_SCHEMA(orgId, connectionId));
55073
55154
  }
55155
+ async getPlatformTriggerSchema(orgId, eventTypes) {
55156
+ return this.request("GET", ENDPOINTS.PLATFORM_TRIGGER_SCHEMA(orgId), void 0, eventTypes && eventTypes.length > 0 ? { event_types: eventTypes.join(",") } : void 0);
55157
+ }
55074
55158
  async listTriggerIngressEvents(orgId, filters) {
55075
55159
  return this.request("GET", ENDPOINTS.TRIGGER_INGRESS_EVENTS(orgId), void 0, filters);
55076
55160
  }
@@ -55172,8 +55256,8 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
55172
55256
  return url;
55173
55257
  return `${this.baseUrlFor(url)}${url}`;
55174
55258
  }
55175
- async getWikiBlob(orgId, wikiId, params) {
55176
- const blob = await this.request("GET", ENDPOINTS.WIKI_BLOB(orgId, wikiId), void 0, params);
55259
+ async getWikiBlob(orgId, wikiId, params, opts) {
55260
+ const blob = await this.request("GET", ENDPOINTS.WIKI_BLOB(orgId, wikiId), void 0, params, false, opts);
55177
55261
  if (blob.signed_url)
55178
55262
  blob.signed_url = this.absoluteMediaUrl(blob.signed_url);
55179
55263
  return blob;
@@ -55546,6 +55630,21 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
55546
55630
  async getBilling(orgId) {
55547
55631
  return this.request("GET", ENDPOINTS.BILLING(orgId));
55548
55632
  }
55633
+ async getUsageDaily(orgId, opts) {
55634
+ const params = new URLSearchParams();
55635
+ params.set("start_date", opts.start_date);
55636
+ params.set("end_date", opts.end_date);
55637
+ return this.request("GET", `${ENDPOINTS.BILLING_USAGE_DAILY(orgId)}?${params}`);
55638
+ }
55639
+ async getUsageHeatmap(orgId, opts) {
55640
+ const params = new URLSearchParams();
55641
+ params.set("start_time", opts.start_time);
55642
+ if (opts.limit != null)
55643
+ params.set("limit", String(opts.limit));
55644
+ if (opts.offset != null)
55645
+ params.set("offset", String(opts.offset));
55646
+ return this.request("GET", `${ENDPOINTS.BILLING_USAGE_HEATMAP(orgId)}?${params}`);
55647
+ }
55549
55648
  async listBillingTransactions(orgId, opts) {
55550
55649
  const params = new URLSearchParams();
55551
55650
  if (opts?.cursor)
@@ -55715,12 +55814,14 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
55715
55814
  }
55716
55815
  // ---- Clip registry (v3, api-server org registry — `crg_` entries) ----
55717
55816
  /**
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.
55817
+ * List one page of registry clips visible to the org: its own plus
55818
+ * public+approved cross-org entries, used by installs and connections.
55721
55819
  */
55722
- async listOrgRegistryClips(orgId) {
55723
- const resp = await this.request("GET", `${ENDPOINTS.ORG_CLIP_REGISTRY(orgId)}?limit=100`);
55820
+ async listOrgRegistryClips(orgId, opts) {
55821
+ const params = new URLSearchParams({ limit: String(opts?.limit ?? 100) });
55822
+ if (opts?.offset !== void 0)
55823
+ params.set("offset", String(opts.offset));
55824
+ const resp = await this.request("GET", `${ENDPOINTS.ORG_CLIP_REGISTRY(orgId)}?${params}`);
55724
55825
  return resp ?? [];
55725
55826
  }
55726
55827
  /**
@@ -55801,7 +55902,6 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
55801
55902
  /**
55802
55903
  * Read a clip's per-org exec access (by user id — humans and agents alike).
55803
55904
  * 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
55905
  * `CLIP_AGENT_NOT_ALLOWED` / `CLIP_AGENT_CONNECTION_NOT_ALLOWED`). Writing
55806
55906
  * it (`putClipAgentExecAccess`) is an org owner/admin's act.
55807
55907
  */
@@ -55882,69 +55982,6 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
55882
55982
  async getEdgeProfileOperation(orgId, edgeId, operationId) {
55883
55983
  return this.request("GET", ENDPOINTS.ORG_EDGE_PROFILE_OPERATION(orgId, edgeId, operationId));
55884
55984
  }
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
55985
  /**
55949
55986
  * Read a hosted Cloud Profile's egress-proxy status (manager-only: hosted
55950
55987
  * human maintainer or org admin). Sanitized — the password never comes back.
@@ -56054,7 +56091,6 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
56054
56091
  * - `EDGE_HOSTED_DISABLED_FOR_ORG` 403, `EDGE_REPAIR` 503 (operator-held),
56055
56092
  * `EDGE_RUNTIME_UNAVAILABLE` 503 (deployment has no hosted runtime).
56056
56093
  * - Access errors (before side effects): human `CONNECTION_NOT_ALLOWED`.
56057
- * `CLIP_NOT_ALLOWED` / `CLIP_CONNECTION_NOT_ALLOWED` are retired.
56058
56094
  * Agent principals receive the historical
56059
56095
  * `CLIP_AGENT_NOT_ALLOWED` / `CLIP_AGENT_CONNECTION_NOT_ALLOWED` for one
56060
56096
  * release. An org owner/admin can widen access in the clip's settings.
@@ -56138,17 +56174,6 @@ var ParallClient = class _ParallClient extends BrowserProfileClient {
56138
56174
  timeoutMs: 4e4
56139
56175
  });
56140
56176
  }
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
56177
  /** Re-authorize an existing MCP credential slot. */
56153
56178
  async initiateClipMCPOAuthById(orgId, clipId, configId, serverUrl, expectedVersion, oauthClient) {
56154
56179
  const req = {
@@ -56337,6 +56362,11 @@ function describeWsStateDetail(detail) {
56337
56362
  function isRetryableNetworkError(err) {
56338
56363
  return err instanceof Error && err.name === "ApiError" && "status" in err && err.status === 0;
56339
56364
  }
56365
+ function authRejectionFrom(err) {
56366
+ if (!(err instanceof ApiError) || err.status !== 401)
56367
+ return null;
56368
+ return { status: err.status, code: err.code, message: err.message };
56369
+ }
56340
56370
  function isBrowserRuntime() {
56341
56371
  return typeof window !== "undefined";
56342
56372
  }
@@ -56345,9 +56375,12 @@ var ParallWs = class {
56345
56375
  options;
56346
56376
  listeners = /* @__PURE__ */ new Map();
56347
56377
  stateListeners = /* @__PURE__ */ new Set();
56378
+ authInvalidListeners = /* @__PURE__ */ new Set();
56348
56379
  heartbeatTimer = null;
56349
56380
  reconnectTimer = null;
56350
56381
  reconnectAttempts = 0;
56382
+ /** Ticket attempts in the current auth-invalid episode (reset on success). */
56383
+ authAttempts = 0;
56351
56384
  lastSeq = 0;
56352
56385
  _state = "disconnected";
56353
56386
  intentionalClose = false;
@@ -56360,6 +56393,7 @@ var ParallWs = class {
56360
56393
  reconnect: true,
56361
56394
  reconnectInterval: 1e3,
56362
56395
  maxReconnectInterval: 3e4,
56396
+ maxAuthAttempts: 3,
56363
56397
  ...options
56364
56398
  };
56365
56399
  this.lastSeq = options.lastSeq ?? 0;
@@ -56369,12 +56403,25 @@ var ParallWs = class {
56369
56403
  }
56370
56404
  async connect() {
56371
56405
  this.intentionalClose = false;
56406
+ if (this._state === "auth-invalid")
56407
+ this.authAttempts = 0;
56372
56408
  this.setupBrowserListeners();
56373
56409
  this.setState("connecting");
56374
56410
  let ticket;
56375
56411
  try {
56376
56412
  ticket = await this.options.getTicket();
56377
56413
  } catch (err) {
56414
+ const authRejection = authRejectionFrom(err);
56415
+ if (authRejection) {
56416
+ this.authAttempts += 1;
56417
+ if (this.authAttempts < this.options.maxAuthAttempts && this.options.reconnect) {
56418
+ console.warn(`WS ticket auth rejected (attempt ${this.authAttempts}/${this.options.maxAuthAttempts}):`, err);
56419
+ this.scheduleReconnect({ cause: "ticket_failed" });
56420
+ } else {
56421
+ this.enterAuthInvalid(authRejection);
56422
+ }
56423
+ return;
56424
+ }
56378
56425
  if (isBrowserRuntime() && isRetryableNetworkError(err)) {
56379
56426
  console.warn("Failed to get WS ticket:", err);
56380
56427
  } else {
@@ -56421,6 +56468,7 @@ var ParallWs = class {
56421
56468
  this.ws.onopen = () => {
56422
56469
  clearTimeout(connectTimeout);
56423
56470
  this.reconnectAttempts = 0;
56471
+ this.authAttempts = 0;
56424
56472
  this.lastReceivedAt = Date.now();
56425
56473
  this.setState("connected");
56426
56474
  };
@@ -56508,6 +56556,20 @@ var ParallWs = class {
56508
56556
  this.stateListeners.delete(handler);
56509
56557
  };
56510
56558
  }
56559
+ /**
56560
+ * Subscribe to the auth-invalid transition. Fired exactly once when the
56561
+ * client enters the terminal 'auth-invalid' state (a second episode after
56562
+ * a recovered reconnect fires again — one event per transition edge).
56563
+ * Consumers: runtime bridges exit for supervisor re-mint (see
56564
+ * @parall/agent-core gateway-base), the daemon logs recovery guidance for
56565
+ * its machine key, UIs surface a re-authenticate action.
56566
+ */
56567
+ onAuthInvalid(handler) {
56568
+ this.authInvalidListeners.add(handler);
56569
+ return () => {
56570
+ this.authInvalidListeners.delete(handler);
56571
+ };
56572
+ }
56511
56573
  // ---- Internal ----
56512
56574
  send(frame) {
56513
56575
  if (this.ws?.readyState === WebSocket.OPEN) {
@@ -56553,6 +56615,8 @@ var ParallWs = class {
56553
56615
  }
56554
56616
  }
56555
56617
  scheduleReconnect(detail) {
56618
+ if (this._state === "auth-invalid")
56619
+ return;
56556
56620
  this.setState("reconnecting", { ...detail, attempt: this.reconnectAttempts + 1 });
56557
56621
  this.clearReconnect();
56558
56622
  const base = Math.min(this.options.reconnectInterval * Math.pow(2, this.reconnectAttempts), this.options.maxReconnectInterval);
@@ -56568,6 +56632,20 @@ var ParallWs = class {
56568
56632
  this.reconnectTimer = null;
56569
56633
  }
56570
56634
  }
56635
+ /**
56636
+ * Enter the terminal auth-invalid state and notify consumers once. Loud on
56637
+ * purpose: before this branch existed, a revoked key produced an infinite
56638
+ * silent connecting→reconnecting loop with no indication that the
56639
+ * credential — not the network — was the problem.
56640
+ */
56641
+ enterAuthInvalid(event) {
56642
+ this.clearReconnect();
56643
+ this.setState("auth-invalid");
56644
+ 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.`);
56645
+ for (const listener of this.authInvalidListeners) {
56646
+ listener(event);
56647
+ }
56648
+ }
56571
56649
  /** Force-close a dead/stale connection and trigger reconnect. */
56572
56650
  forceReconnect(cause) {
56573
56651
  this.stopHeartbeat();
@@ -56672,6 +56750,16 @@ var ParallWs = class {
56672
56750
  }
56673
56751
  };
56674
56752
 
56753
+ // ../sdk/dist/browser-use-contract.generated.js
56754
+ 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 } } };
56755
+
56756
+ // ../sdk/dist/browser-use-validation.js
56757
+ var BROWSER_USE_ACTION_SCHEMAS = browserUseContract.actions;
56758
+ var BROWSER_USE_ACTIONS = Object.freeze(Object.keys(BROWSER_USE_ACTION_SCHEMAS));
56759
+
56760
+ // ../sdk/dist/browser-use-receipt.js
56761
+ 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(" ");
56762
+
56675
56763
  // ../agent-core/dist/fork-state.js
56676
56764
  var FORK_CONTINUATION_RETRY_CAP = 256;
56677
56765
  function forkContinuationRetryKey(event) {
@@ -56803,12 +56891,209 @@ function isTypedEvent(event) {
56803
56891
  return event.type !== "message" && laneTargetUri(event) === void 0;
56804
56892
  }
56805
56893
 
56806
- // ../agent-core/dist/lane-ledger.js
56894
+ // ../agent-core/dist/lane-outcome.js
56807
56895
  function errorWireClass(cls) {
56808
56896
  if (!cls || cls === "ok" || cls === "usage_limit")
56809
56897
  return void 0;
56810
56898
  return cls;
56811
56899
  }
56900
+
56901
+ // ../agent-core/dist/runtime-instance.js
56902
+ function isTurnToken(token) {
56903
+ return token.startsWith("turn_");
56904
+ }
56905
+
56906
+ // ../agent-core/dist/lane-input-receipts.js
56907
+ var LaneInputReceipts = class {
56908
+ opts;
56909
+ confirmations;
56910
+ constructor(opts, confirmations) {
56911
+ this.opts = opts;
56912
+ this.confirmations = confirmations;
56913
+ }
56914
+ lifecycle(lane, messageIds) {
56915
+ const explicit = lane.coverageMode === "explicit";
56916
+ const unique = [...new Set(messageIds)];
56917
+ const dispatchEventIds = unique.map((messageId) => lane.folded.get(messageId)).filter((id) => Boolean(id));
56918
+ if (explicit && dispatchEventIds.length !== unique.length) {
56919
+ throw new Error(`explicit lane ${lane.lane} is missing a folded WorkItem mapping`);
56920
+ }
56921
+ if (!explicit && dispatchEventIds.length === 0)
56922
+ return void 0;
56923
+ return {
56924
+ deliveryKey: dispatchEventIds.join(","),
56925
+ dispatchEventIds,
56926
+ update: explicit ? (state, failure) => this.updateInputState(lane, dispatchEventIds, state, failure) : async () => void 0
56927
+ };
56928
+ }
56929
+ async updateInputState(lane, dispatchEventIds, state, failure) {
56930
+ lane.observedInputs ??= /* @__PURE__ */ new Map();
56931
+ for (const id of dispatchEventIds)
56932
+ lane.observedInputs.set(id, state);
56933
+ let confirmed;
56934
+ const ids = [...dispatchEventIds];
56935
+ const failed = new Set(state === "failed" ? ids : []);
56936
+ const sourceIds = [...lane.folded].filter(([, id]) => failed.has(id)).map(([id]) => id);
56937
+ const request3 = {
56938
+ lane: lane.lane,
56939
+ target_uri: lane.targetUri,
56940
+ thread_root_id: lane.threadRootId,
56941
+ dispatch_event_ids: ids,
56942
+ state,
56943
+ session_id: lane.sessionAmbiguous ? void 0 : lane.sessionId,
56944
+ ...state === "failed" && failure ? {
56945
+ outcome_class: errorWireClass(failure.outcomeClass),
56946
+ detail: failure.detail ? redactSecrets(failure.detail, this.opts.knownSecrets).slice(0, 500) : void 0
56947
+ } : {}
56948
+ };
56949
+ await this.confirmations.submit(lane.lane, {
56950
+ label: `input ${state}`,
56951
+ send: async () => {
56952
+ const restore = sourceIds.length ? this.opts.releaseLocalClaims?.(sourceIds) : void 0;
56953
+ let result;
56954
+ try {
56955
+ result = await this.opts.client.updateDispatchInputState(this.opts.orgId, request3);
56956
+ } catch (err) {
56957
+ restore?.();
56958
+ throw err;
56959
+ }
56960
+ if (result.recognized !== ids.length && !isTurnToken(lane.lane)) {
56961
+ restore?.();
56962
+ throw new ApiError(409, "input acknowledgement scope changed", "INPUT_SCOPE_CHANGED");
56963
+ }
56964
+ if (state === "failed") {
56965
+ for (const sourceId of sourceIds) {
56966
+ lane.folded.delete(sourceId);
56967
+ lane.seen.delete(sourceId);
56968
+ }
56969
+ }
56970
+ confirmed = { retry: state === "failed" && result.released > 0 };
56971
+ }
56972
+ });
56973
+ return confirmed ?? { retry: false, pending: true };
56974
+ }
56975
+ async syncAttribution(lane) {
56976
+ if (lane.settlementMode !== "receipt" || lane.settling || lane.sessionAmbiguous || !lane.sessionId || !lane.observedInputs?.size)
56977
+ return;
56978
+ const sessionId = lane.sessionId;
56979
+ const groups = /* @__PURE__ */ new Map();
56980
+ for (const [id, state] of lane.observedInputs) {
56981
+ const ids = groups.get(state) ?? [];
56982
+ ids.push(id);
56983
+ groups.set(state, ids);
56984
+ }
56985
+ for (const [state, ids] of groups) {
56986
+ await this.confirmations.submit(lane.lane, {
56987
+ label: "input session",
56988
+ send: async () => {
56989
+ await this.opts.client.updateDispatchInputState(this.opts.orgId, {
56990
+ lane: lane.lane,
56991
+ target_uri: lane.targetUri,
56992
+ thread_root_id: lane.threadRootId,
56993
+ dispatch_event_ids: ids,
56994
+ state,
56995
+ session_id: sessionId
56996
+ });
56997
+ }
56998
+ });
56999
+ }
57000
+ }
57001
+ };
57002
+
57003
+ // ../agent-core/dist/lane-confirmations.js
57004
+ var LaneConfirmations = class {
57005
+ opts;
57006
+ pending = /* @__PURE__ */ new Map();
57007
+ disposed = false;
57008
+ constructor(opts) {
57009
+ this.opts = opts;
57010
+ }
57011
+ /** Try once inline; a retained write returns without blocking native execution. */
57012
+ async submit(token, write) {
57013
+ if (this.disposed)
57014
+ return;
57015
+ let pending = this.pending.get(token);
57016
+ if (pending) {
57017
+ if (pending.blocked)
57018
+ return;
57019
+ pending.writes.push(write);
57020
+ return;
57021
+ }
57022
+ pending = { writes: [write], failures: 0, blocked: false };
57023
+ this.pending.set(token, pending);
57024
+ await this.run(token, pending);
57025
+ }
57026
+ async run(token, pending) {
57027
+ if (pending.running || pending.blocked || this.pending.get(token) !== pending)
57028
+ return;
57029
+ const recovered = pending.failures > 0;
57030
+ pending.running = Promise.resolve().then(async () => {
57031
+ while (pending.writes.length && this.pending.get(token) === pending) {
57032
+ const write = pending.writes[0];
57033
+ try {
57034
+ await write.send();
57035
+ pending.writes.shift();
57036
+ } catch (err) {
57037
+ if (this.pending.get(token) !== pending)
57038
+ return;
57039
+ if (err instanceof ApiError && err.status === 409 && err.code === "STALE_LANE") {
57040
+ this.pending.delete(token);
57041
+ this.opts.onStale(token);
57042
+ this.opts.onRecovered?.();
57043
+ return;
57044
+ }
57045
+ const transient = !(err instanceof ApiError) || err.status === 0 || err.status >= 500 || err.status === 408 || err.status === 429;
57046
+ if (!transient) {
57047
+ pending.blocked = true;
57048
+ this.opts.log?.error(`lane ${write.label} rejected for ${token}; confirmation retained: ${String(err)}`);
57049
+ return;
57050
+ }
57051
+ pending.failures += 1;
57052
+ this.opts.log?.warn(`lane ${write.label} unconfirmed for ${token}; retry ${pending.failures}: ${String(err)}`);
57053
+ const delays = this.opts.delaysMs?.length ? this.opts.delaysMs : [2e3, 1e4, 3e4, 6e4, 12e4];
57054
+ const delay = delays[Math.min(pending.failures - 1, delays.length - 1)];
57055
+ pending.timer = setTimeout(() => {
57056
+ pending.timer = void 0;
57057
+ void this.run(token, pending);
57058
+ }, delay);
57059
+ pending.timer.unref?.();
57060
+ return;
57061
+ }
57062
+ }
57063
+ if (this.pending.get(token) === pending)
57064
+ this.pending.delete(token);
57065
+ if (recovered)
57066
+ this.opts.onRecovered?.();
57067
+ });
57068
+ try {
57069
+ await pending.running;
57070
+ } finally {
57071
+ pending.running = void 0;
57072
+ }
57073
+ }
57074
+ /** Reconnect/shutdown may accelerate retries; permanent rejections stay visible. */
57075
+ async retryNow() {
57076
+ await Promise.all([...this.pending].map(async ([token, pending]) => {
57077
+ if (pending.timer)
57078
+ clearTimeout(pending.timer);
57079
+ pending.timer = void 0;
57080
+ await this.run(token, pending);
57081
+ }));
57082
+ }
57083
+ forget(token) {
57084
+ const pending = this.pending.get(token);
57085
+ if (pending?.timer)
57086
+ clearTimeout(pending.timer);
57087
+ this.pending.delete(token);
57088
+ }
57089
+ dispose() {
57090
+ this.disposed = true;
57091
+ for (const token of this.pending.keys())
57092
+ this.forget(token);
57093
+ }
57094
+ };
57095
+
57096
+ // ../agent-core/dist/lane-ledger.js
56812
57097
  var LedgerUnsupportedError = class extends Error {
56813
57098
  };
56814
57099
  function bindLaneSession(lane, agentSessionId) {
@@ -56834,8 +57119,24 @@ function isEndpointMissing2(err) {
56834
57119
  var LaneLedger = class {
56835
57120
  opts;
56836
57121
  lanes = /* @__PURE__ */ new Map();
57122
+ confirmations;
57123
+ inputs;
57124
+ claimResponses = /* @__PURE__ */ new Map();
57125
+ claims = /* @__PURE__ */ new Map();
56837
57126
  constructor(opts) {
56838
57127
  this.opts = opts;
57128
+ this.confirmations = new LaneConfirmations({
57129
+ delaysMs: opts.terminalRetryDelaysMs,
57130
+ log: opts.log,
57131
+ onRecovered: opts.onRecovered,
57132
+ onStale: (token) => {
57133
+ for (const lane of this.lanes.values()) {
57134
+ if (lane.lane === token)
57135
+ this.forgetLane(lane);
57136
+ }
57137
+ }
57138
+ });
57139
+ this.inputs = new LaneInputReceipts(opts, this.confirmations);
56839
57140
  }
56840
57141
  get contextDir() {
56841
57142
  return this.opts.contextDir;
@@ -56934,7 +57235,16 @@ ${frame}` : frame;
56934
57235
  * incumbent completes.
56935
57236
  */
56936
57237
  async ensureLane(events) {
56937
- return this.ensureLaneAttempt(events, false);
57238
+ const key = this.laneKeyFor(events[events.length - 1]);
57239
+ const prior = this.claims.get(key);
57240
+ const claim = (prior ? prior.catch(() => null) : Promise.resolve()).then(() => this.ensureLaneAttempt(events, false));
57241
+ this.claims.set(key, claim);
57242
+ try {
57243
+ return await claim;
57244
+ } finally {
57245
+ if (this.claims.get(key) === claim)
57246
+ this.claims.delete(key);
57247
+ }
56938
57248
  }
56939
57249
  /**
56940
57250
  * One ensureLane pass. `reclaimed` marks the arbitration retry: STALE_LANE
@@ -56952,22 +57262,22 @@ ${frame}` : frame;
56952
57262
  const trigger = events[events.length - 1];
56953
57263
  const laneKey = this.laneKeyFor(trigger);
56954
57264
  let lane = this.lanes.get(laneKey);
57265
+ if (lane?.settling)
57266
+ return null;
56955
57267
  const reused = lane != null;
56956
57268
  if (!lane) {
56957
57269
  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
- }
57270
+ const res = await this.claimRecoverably(laneKey, {
57271
+ target_uri: targetUri,
57272
+ thread_root_id: trigger.threadRootId,
57273
+ limit: 100,
57274
+ coverage_mode: this.opts.coverageMode ?? "implicit",
57275
+ settlement_mode: this.opts.instanceId ? this.opts.settlementMode : void 0,
57276
+ resume_received: this.opts.instanceId ? true : void 0,
57277
+ instance_id: this.opts.instanceId
57278
+ });
57279
+ if (!res)
57280
+ return null;
56971
57281
  if (!res.claimed || !res.lane) {
56972
57282
  if (res.reason === "empty") {
56973
57283
  this.opts.log?.warn(`claim for ${targetUri} came back empty \u2014 nothing foldable; leaving to the reconciler`);
@@ -56990,6 +57300,7 @@ ${frame}` : frame;
56990
57300
  targetUri,
56991
57301
  threadRootId: trigger.threadRootId,
56992
57302
  coverageMode: actualCoverage,
57303
+ settlementMode: res.settlement_mode ?? "legacy",
56993
57304
  folded: /* @__PURE__ */ new Map(),
56994
57305
  framed: [],
56995
57306
  seen: /* @__PURE__ */ new Set(),
@@ -57024,9 +57335,8 @@ ${frame}` : frame;
57024
57335
  }
57025
57336
  return null;
57026
57337
  }
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;
57338
+ this.opts.log?.warn(`steer fold failed for ${ev.messageId}; preserving prior members: ${String(err)}`);
57339
+ continue;
57030
57340
  }
57031
57341
  }
57032
57342
  return lane;
@@ -57040,7 +57350,7 @@ ${frame}` : frame;
57040
57350
  async steerLive(event) {
57041
57351
  const laneKey = this.laneKeyFor(event);
57042
57352
  const lane = this.lanes.get(laneKey);
57043
- if (!lane)
57353
+ if (!lane || lane.settling)
57044
57354
  return null;
57045
57355
  if (lane.seen.has(event.messageId))
57046
57356
  return null;
@@ -57086,54 +57396,7 @@ ${frame}` : frame;
57086
57396
  * prompt or injection actually delivers (frame coverage ∪ buffered group).
57087
57397
  */
57088
57398
  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" };
57399
+ return this.inputs.lifecycle(lane, messageIds);
57137
57400
  }
57138
57401
  /** Carry the runtime error into the lane's forced completion. */
57139
57402
  markTurnError(laneKey, info = {}) {
@@ -57162,62 +57425,62 @@ ${frame}` : frame;
57162
57425
  const lane = this.lanes.get(laneKey);
57163
57426
  if (!lane || hasMoreLocal)
57164
57427
  return;
57165
- this.lanes.delete(laneKey);
57166
- this.removeLaneContext(lane);
57428
+ if (lane.settling)
57429
+ return;
57430
+ lane.settling = true;
57167
57431
  const deferred = !lane.turnError && !this.deferredUnsupported ? lane.turnDeferred : void 0;
57168
57432
  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`);
57433
+ const request3 = {
57434
+ lane: lane.lane,
57435
+ target_uri: lane.targetUri,
57436
+ thread_root_id: lane.threadRootId,
57437
+ // An error turn releases its members for retry; a deferred turn
57438
+ // re-delivers them at retry_at without burning redrive budget
57439
+ // (ignored by older servers, which 400 on the unknown enum — see the
57440
+ // fallback below).
57441
+ turn_outcome: outcome,
57442
+ ...deferred ? {
57443
+ outcome_class: deferred.outcomeClass,
57444
+ ...deferred.retryAt ? { retry_at: deferred.retryAt } : {}
57445
+ } : {},
57446
+ // An error complete carries the bridge's classification and redacted
57447
+ // evidence so the server can persist WHY on the released row
57448
+ // (agent-health-alerting-design.md §4.5). usage_limit never travels
57449
+ // with error: it is the deferred contract, and a server that rejected
57450
+ // deferred (deferredUnsupported) also predates the class field.
57451
+ ...outcome === "error" && lane.turnError ? {
57452
+ ...errorWireClass(lane.turnError.outcomeClass) ? { outcome_class: errorWireClass(lane.turnError.outcomeClass) } : {},
57453
+ ...lane.turnError.detail ? { detail: lane.turnError.detail.slice(0, 500) } : {}
57454
+ } : {},
57455
+ session_id: lane.sessionAmbiguous ? void 0 : lane.sessionId
57456
+ };
57457
+ let fallback = false;
57458
+ await this.confirmations.submit(lane.lane, {
57459
+ label: "complete",
57460
+ send: async () => {
57205
57461
  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,
57462
+ await this.opts.client.completeDispatch(this.opts.orgId, fallback ? {
57463
+ lane: request3.lane,
57464
+ target_uri: request3.target_uri,
57465
+ thread_root_id: request3.thread_root_id,
57210
57466
  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)}`);
57467
+ } : request3);
57468
+ } catch (err) {
57469
+ if (!fallback && outcome === "deferred" && err instanceof ApiError && err.status === 400) {
57470
+ this.deferredUnsupported = true;
57471
+ fallback = true;
57472
+ await this.opts.client.completeDispatch(this.opts.orgId, {
57473
+ lane: request3.lane,
57474
+ target_uri: request3.target_uri,
57475
+ thread_root_id: request3.thread_root_id,
57476
+ turn_outcome: "error"
57477
+ });
57478
+ } else
57479
+ throw err;
57216
57480
  }
57217
- return;
57481
+ this.forgetLane(lane);
57218
57482
  }
57219
- this.opts.log?.warn(`lane complete failed for ${lane.targetUri}: ${String(err)}`);
57220
- }
57483
+ });
57221
57484
  }
57222
57485
  /**
57223
57486
  * Renew one lane by its key — the external runtime-activity hook for
@@ -57239,6 +57502,8 @@ ${frame}` : frame;
57239
57502
  * write's incumbency check anyway.
57240
57503
  */
57241
57504
  maybeRenew(lane) {
57505
+ if (isTurnToken(lane.lane))
57506
+ return;
57242
57507
  const now = Date.now();
57243
57508
  const ttl = lane.leaseTtlMs ?? 10 * 6e4;
57244
57509
  const until = lane.leaseUntilMs ?? now;
@@ -57269,26 +57534,40 @@ ${frame}` : frame;
57269
57534
  */
57270
57535
  async release(laneKey, reason = "unspecified", info) {
57271
57536
  const lane = this.lanes.get(laneKey);
57272
- if (!lane)
57537
+ if (!lane || lane.settling)
57273
57538
  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
- }
57539
+ lane.settling = true;
57540
+ const wireClass = errorWireClass(info?.outcomeClass);
57541
+ const outcome = info && (wireClass || info.detail) ? {
57542
+ ...wireClass ? { outcome_class: wireClass } : {},
57543
+ ...info.detail ? { detail: redactSecrets(info.detail, this.opts.knownSecrets).slice(0, 500) } : {}
57544
+ } : void 0;
57545
+ await this.confirmations.submit(lane.lane, {
57546
+ label: "release",
57547
+ send: async () => {
57548
+ await this.opts.client.releaseDispatchLane(this.opts.orgId, lane.lane, reason, outcome);
57549
+ this.forgetLane(lane);
57550
+ }
57551
+ });
57286
57552
  }
57287
57553
  async releaseAll(reason = "shutdown") {
57554
+ await this.confirmations.retryNow();
57288
57555
  const keys = [...this.lanes.keys()];
57289
57556
  for (const key of keys) {
57290
57557
  await this.release(key, reason);
57291
57558
  }
57559
+ for (const [key, claim] of this.claimResponses) {
57560
+ if (!claim.response?.lane)
57561
+ continue;
57562
+ const token = claim.response.lane;
57563
+ await this.confirmations.submit(token, {
57564
+ label: "unconsumed claim release",
57565
+ send: async () => {
57566
+ await this.opts.client.releaseDispatchLane(this.opts.orgId, token, reason);
57567
+ this.claimResponses.delete(key);
57568
+ }
57569
+ });
57570
+ }
57292
57571
  }
57293
57572
  /** True when any lane is currently active (used by shutdown logging). */
57294
57573
  get activeCount() {
@@ -57303,18 +57582,57 @@ ${frame}` : frame;
57303
57582
  * — the caller must skip processing.
57304
57583
  */
57305
57584
  async claimTyped(ref) {
57306
- let res;
57585
+ const key = `typed:${ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`}`;
57586
+ if (this.claims.has(key))
57587
+ return null;
57588
+ if (ref.dispatchEventId && this.lanes.has(laneKeyForTarget(`dsp:${ref.dispatchEventId}`)))
57589
+ return null;
57590
+ const claim = this.claimTypedAttempt(ref);
57591
+ this.claims.set(key, claim);
57307
57592
  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
57593
+ return await claim;
57594
+ } finally {
57595
+ this.claims.delete(key);
57596
+ }
57597
+ }
57598
+ async claimRecoverably(key, request3) {
57599
+ let claim = this.claimResponses.get(key);
57600
+ if (!claim) {
57601
+ claim = { request: request3 };
57602
+ this.claimResponses.set(key, claim);
57603
+ const captured = claim;
57604
+ await this.confirmations.submit(`claim:${key}`, {
57605
+ label: "claim",
57606
+ send: async () => {
57607
+ try {
57608
+ captured.response = await this.opts.client.claimDispatch(this.opts.orgId, request3);
57609
+ } catch (err) {
57610
+ if (isEndpointMissing2(err))
57611
+ captured.unsupported = true;
57612
+ else
57613
+ throw err;
57614
+ }
57615
+ }
57312
57616
  });
57313
- } catch (err) {
57314
- if (isEndpointMissing2(err))
57315
- throw new LedgerUnsupportedError("claim endpoint unavailable");
57316
- throw err;
57317
57617
  }
57618
+ if (claim.unsupported) {
57619
+ this.claimResponses.delete(key);
57620
+ throw new LedgerUnsupportedError("claim endpoint unavailable");
57621
+ }
57622
+ if (!claim.response)
57623
+ return null;
57624
+ this.claimResponses.delete(key);
57625
+ return claim.response;
57626
+ }
57627
+ async claimTypedAttempt(ref) {
57628
+ const res = await this.claimRecoverably(`typed:${ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`}`, {
57629
+ dispatch_event_id: ref.dispatchEventId,
57630
+ source_type: ref.dispatchEventId ? void 0 : ref.sourceType,
57631
+ source_id: ref.dispatchEventId ? void 0 : ref.sourceId,
57632
+ instance_id: this.opts.instanceId
57633
+ });
57634
+ if (!res)
57635
+ return null;
57318
57636
  if (!res.claimed || !res.lane || !res.events?.length) {
57319
57637
  if (res.reason === "stale") {
57320
57638
  this.opts.log?.info(`typed dispatch ${ref.dispatchEventId ?? `${ref.sourceType}:${ref.sourceId}`} resolved by the server as stale (${res.stale_reason ?? "unspecified"})`);
@@ -57326,6 +57644,8 @@ ${frame}` : frame;
57326
57644
  }
57327
57645
  const workItem = res.events[0];
57328
57646
  const targetUri = `dsp:${workItem.id}`;
57647
+ if (this.lanes.has(laneKeyForTarget(targetUri)))
57648
+ return null;
57329
57649
  const leaseUntilMs = Date.parse(res.lease_until ?? "");
57330
57650
  const lane = {
57331
57651
  laneKey: laneKeyForTarget(targetUri),
@@ -57354,7 +57674,34 @@ ${frame}` : frame;
57354
57674
  const lane = this.lanes.get(laneKey);
57355
57675
  if (!lane)
57356
57676
  return;
57357
- this.lanes.delete(laneKey);
57677
+ this.confirmations.forget(lane.lane);
57678
+ this.forgetLane(lane);
57679
+ }
57680
+ syncInputAttribution(lane) {
57681
+ return this.inputs.syncAttribution(lane);
57682
+ }
57683
+ async confirmTyped(lane, send) {
57684
+ if (lane.settling)
57685
+ return;
57686
+ lane.settling = true;
57687
+ await this.confirmations.submit(lane.lane, {
57688
+ label: "typed complete",
57689
+ send: async () => {
57690
+ await send();
57691
+ this.forgetLane(lane);
57692
+ }
57693
+ });
57694
+ }
57695
+ retryConfirmations() {
57696
+ return this.confirmations.retryNow();
57697
+ }
57698
+ dispose() {
57699
+ this.confirmations.dispose();
57700
+ }
57701
+ forgetLane(lane) {
57702
+ if (this.lanes.get(lane.laneKey) !== lane)
57703
+ return;
57704
+ this.lanes.delete(lane.laneKey);
57358
57705
  this.removeLaneContext(lane);
57359
57706
  }
57360
57707
  /**
@@ -58297,7 +58644,7 @@ async function dispatchLaneGroup(host, opts) {
58297
58644
  }
58298
58645
  async function dispatchLaneGroupInner(host, opts) {
58299
58646
  const ledger = host.laneLedger;
58300
- const event = opts.events[opts.events.length - 1];
58647
+ let event = opts.events[opts.events.length - 1];
58301
58648
  let lane;
58302
58649
  try {
58303
58650
  lane = await runInPhaseSpan("claim", () => ledger.ensureLane(opts.events));
@@ -58315,18 +58662,51 @@ async function dispatchLaneGroupInner(host, opts) {
58315
58662
  }
58316
58663
  return "foreign";
58317
58664
  }
58665
+ const foldedEvents = opts.events.filter((ev) => lane.folded.has(ev.messageId));
58666
+ for (const ev of opts.events) {
58667
+ if (!lane.folded.has(ev.messageId))
58668
+ host.dispatchedMessages.delete(ev.messageId);
58669
+ }
58670
+ if (foldedEvents.length === 0) {
58671
+ await ledger.completeIfIdle(lane.laneKey, unsettledInjections(host, opts.sessionKey) || opts.hasMoreLocal());
58672
+ return "foreign";
58673
+ }
58674
+ opts = { ...opts, events: foldedEvents, earlier: foldedEvents.slice(0, -1) };
58675
+ event = foldedEvents[foldedEvents.length - 1];
58318
58676
  const pending = ledger.takeFrame(lane);
58319
58677
  const frame = pending.frame;
58320
58678
  if (!frame && opts.events.every((ev) => lane.seen.has(ev.messageId))) {
58321
58679
  host.opts.log?.info(`lane group for ${event.messageId} already rendered by the server frame \u2014 no turn`);
58322
58680
  const acknowledge = host.opts.dispatchAdapter.acknowledgeDiscardedInjection?.bind(host.opts.dispatchAdapter);
58681
+ const withdrawn = [];
58323
58682
  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
- }
58683
+ try {
58684
+ for (const ev of opts.events) {
58685
+ const deliveryKey = lane.folded.get(ev.messageId);
58686
+ if (!deliveryKey)
58687
+ continue;
58688
+ if (await acknowledge(opts.sessionKey, deliveryKey) === "cancelled")
58689
+ withdrawn.push(ev);
58690
+ }
58691
+ } catch (err) {
58692
+ host.opts.log?.warn(`discarded input acknowledgement failed for ${event.messageId}: ${String(err)}`);
58693
+ }
58694
+ }
58695
+ if (withdrawn.length > 0 && !opts.withdrawnRetry) {
58696
+ for (const ev of withdrawn)
58697
+ host.dispatchedMessages.delete(ev.messageId);
58698
+ await ledger.completeIfIdle(lane.laneKey, unsettledInjections(host, opts.sessionKey));
58699
+ if (ledger.getForEvent(event))
58700
+ return "no_turn";
58701
+ return dispatchLaneGroupInner(host, {
58702
+ ...opts,
58703
+ events: withdrawn,
58704
+ earlier: withdrawn.slice(0, -1),
58705
+ withdrawnRetry: true
58706
+ });
58329
58707
  }
58708
+ for (const ev of opts.events)
58709
+ host.dispatchedMessages.delete(ev.messageId);
58330
58710
  await runInPhaseSpan("settle", () => ledger.completeIfIdle(lane.laneKey, unsettledInjections(host, opts.sessionKey) || opts.hasMoreLocal()));
58331
58711
  return "no_turn";
58332
58712
  }
@@ -58512,17 +58892,24 @@ async function consumeTypedDispatch(host, ref, run, hooks) {
58512
58892
  viaLegacyAck = true;
58513
58893
  resolved = settleAck(await runInPhaseSpan("settle", () => hooks.legacyAck(claimed.typedDispatchEventId)));
58514
58894
  } 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
- }
58895
+ await runInPhaseSpan("settle", () => ledger.confirmTyped(claimed, async () => {
58896
+ try {
58897
+ await host.opts.client.completeDispatch(host.opts.config.org_id, {
58898
+ dispatch_event_id: claimed.typedDispatchEventId,
58899
+ lane: claimed.lane,
58900
+ turn_outcome: "ok"
58901
+ });
58902
+ } catch (err) {
58903
+ if (!isByIDCompleteUnsupported(err))
58904
+ throw err;
58905
+ host.typedByIdCompleteUnsupported = true;
58906
+ if (!settleAck(await hooks.legacyAck(claimed.typedDispatchEventId))) {
58907
+ throw new Error("legacy typed acknowledgement unconfirmed");
58908
+ }
58909
+ await host.opts.client.releaseDispatchLane(host.opts.config.org_id, claimed.lane);
58910
+ }
58911
+ }));
58912
+ resolved = true;
58526
58913
  }
58527
58914
  }
58528
58915
  } catch (err) {
@@ -58532,7 +58919,8 @@ async function consumeTypedDispatch(host, ref, run, hooks) {
58532
58919
  settle(resolved);
58533
58920
  span.setAttribute("dispatch.lane_result", resolved ? "dispatched" : "released");
58534
58921
  if (resolved && !viaLegacyAck) {
58535
- ledger.dropLocal(claimed.laneKey);
58922
+ if (!claimed.settling)
58923
+ ledger.dropLocal(claimed.laneKey);
58536
58924
  } else {
58537
58925
  await runInPhaseSpan("settle", () => ledger.completeIfIdle(claimed.laneKey, false).catch(() => {
58538
58926
  }));
@@ -58972,6 +59360,19 @@ var DispatchInactivityDeadline = class {
58972
59360
  this.onExpire();
58973
59361
  }, delayMs);
58974
59362
  }
59363
+ /**
59364
+ * Re-arm after an expiry that was only reported, not acted on (the
59365
+ * no-abort default: a long silent turn is logged at intervals, never
59366
+ * killed — attached-turn-ownership).
59367
+ */
59368
+ rearm() {
59369
+ if (this.disposed || this.timeoutMs <= 0)
59370
+ return;
59371
+ this.expired = false;
59372
+ this.lastActivityAt = Date.now();
59373
+ if (!this.timer)
59374
+ this.arm(this.timeoutMs);
59375
+ }
58975
59376
  dispose() {
58976
59377
  if (this.disposed)
58977
59378
  return;
@@ -59881,6 +60282,7 @@ function recordToolCall(sessionKey) {
59881
60282
  }
59882
60283
 
59883
60284
  // ../agent-core/dist/gateway-base.js
60285
+ var AUTH_INVALID_EXIT_CODE = 86;
59884
60286
  var TYPED_EVENT_KINDS = {
59885
60287
  task_assign: { type: "task", ackSourceType: "task_activity" },
59886
60288
  task_update: { type: "task", ackSourceType: "task_activity" },
@@ -60050,14 +60452,9 @@ var ParallAgentGateway = class {
60050
60452
  pendingRestartNotification = null;
60051
60453
  laneLedger;
60052
60454
  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.
60455
+ // Session lifecycle owns ordering, retries and stale-finish rejection.
60057
60456
  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).
60457
+ // ForkSessionFinalizer owns seal → drain → close → release.
60061
60458
  forkFinalizer;
60062
60459
  // Sticky fallback: flipped when the server predates the ledger (claim
60063
60460
  // endpoint 404) so every subsequent dispatch uses the legacy flow.
@@ -60073,10 +60470,12 @@ var ParallAgentGateway = class {
60073
60470
  SHUTDOWN_DEADLINE_MS;
60074
60471
  FORK_DEADLINE_MS;
60075
60472
  DISPATCH_DEADLINE_MS;
60473
+ INACTIVITY_WARN_MS;
60076
60474
  constructor(opts) {
60077
60475
  this.opts = opts;
60078
60476
  opts = this.opts = { ...opts, log: redactLogger(opts.log, [opts.config.api_key]) };
60079
60477
  if (opts.dispatchContextDir) {
60478
+ const recovery = new DispatchRecovery(() => this.catchUpFromDispatch(), () => this.shuttingDown, (err) => opts.log?.warn(`dispatch recovery failed: ${String(err)}`));
60080
60479
  this.laneLedger = new LaneLedger({
60081
60480
  client: opts.client,
60082
60481
  orgId: opts.config.org_id,
@@ -60084,12 +60483,17 @@ var ParallAgentGateway = class {
60084
60483
  log: opts.log,
60085
60484
  knownSecrets: [opts.config.api_key],
60086
60485
  coverageMode: opts.dispatchAdapter.inputLifecycleMode ?? "implicit",
60486
+ settlementMode: opts.dispatchAdapter.inputSettlementMode,
60487
+ instanceId: opts.instanceId,
60488
+ terminalRetryDelaysMs: opts.laneTerminalRetryDelaysMs,
60489
+ onRecovered: recovery.request,
60087
60490
  releaseLocalClaims: (sourceIds) => releaseLocalMessageClaims(this.dispatchedMessages, sourceIds)
60088
60491
  });
60089
60492
  }
60090
60493
  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;
60494
+ this.FORK_DEADLINE_MS = opts.forkDeadlineMs ?? 0;
60495
+ this.DISPATCH_DEADLINE_MS = opts.dispatchDeadlineMs ?? 0;
60496
+ this.INACTIVITY_WARN_MS = opts.inactivityWarnMs ?? 30 * 6e4;
60093
60497
  this.unsubscribeRuntimeActivity = opts.dispatchAdapter.subscribeRuntimeActivity?.((event) => this.handleRuntimeActivity(event));
60094
60498
  this.stepPersister = new StepPersister({
60095
60499
  client: opts.client,
@@ -60167,12 +60571,34 @@ var ParallAgentGateway = class {
60167
60571
  }
60168
60572
  });
60169
60573
  this.opts.log?.info(`connecting to ${this.opts.connectionLabel ?? "Parall WS"}...`);
60574
+ let settled = false;
60575
+ let onSettled = null;
60576
+ const finish = async (opts) => {
60577
+ if (settled)
60578
+ return;
60579
+ settled = true;
60580
+ await this.shutdown(opts);
60581
+ onSettled?.();
60582
+ };
60583
+ ws.onAuthInvalid((event) => {
60584
+ const exitCode = this.opts.authInvalidExitCode === void 0 ? AUTH_INVALID_EXIT_CODE : this.opts.authInvalidExitCode;
60585
+ 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.`);
60586
+ if (exitCode === null) {
60587
+ 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.");
60588
+ } else {
60589
+ 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.`);
60590
+ process.exitCode = exitCode;
60591
+ }
60592
+ void finish({ skipDrain: true });
60593
+ });
60170
60594
  await ws.connect();
60171
60595
  return new Promise((resolve3) => {
60172
- abortSignal.addEventListener("abort", async () => {
60173
- await this.shutdown();
60596
+ if (settled) {
60174
60597
  resolve3();
60175
- });
60598
+ return;
60599
+ }
60600
+ onSettled = resolve3;
60601
+ abortSignal.addEventListener("abort", () => void finish());
60176
60602
  });
60177
60603
  }
60178
60604
  tryClaimMessage(id) {
@@ -60590,6 +61016,7 @@ var ParallAgentGateway = class {
60590
61016
  step_id: null,
60591
61017
  dispatch_event_id: activeLane?.typedDispatchEventId ?? activeLane?.folded.get(event.messageId) ?? null,
60592
61018
  lane: activeLane?.lane ?? null,
61019
+ settlement_mode: activeLane?.settlementMode ?? "legacy",
60593
61020
  target_uri: activeLane?.targetUri ?? null,
60594
61021
  thread_root_id: activeLane?.threadRootId ?? null,
60595
61022
  // Typed binding hint for the CLI: which task this dispatch is about
@@ -60609,13 +61036,19 @@ var ParallAgentGateway = class {
60609
61036
  threadRootId: contextBody.thread_root_id
60610
61037
  });
60611
61038
  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)}`);
61039
+ const abortAfterMs = this.DISPATCH_DEADLINE_MS;
61040
+ const dispatchDeadline = this.dispatchInactivityDeadlines.start(sessionKey, abortAfterMs > 0 ? abortAfterMs : this.INACTIVITY_WARN_MS, () => {
61041
+ if (abortAfterMs > 0) {
61042
+ this.opts.log?.warn(`dispatch inactivity deadline exceeded (${abortAfterMs}ms) for ${event.messageId} on ${sessionKey}; aborting`);
61043
+ try {
61044
+ this.opts.dispatchAdapter.abortDispatch?.(sessionKey);
61045
+ } catch (err) {
61046
+ this.opts.log?.warn(`abortDispatch threw for ${sessionKey}: ${String(err)}`);
61047
+ }
61048
+ return;
60618
61049
  }
61050
+ 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`);
61051
+ dispatchDeadline.rearm();
60619
61052
  });
60620
61053
  let binding = this.sessionBindings.get(sessionKey);
60621
61054
  let inputStepsCreated = false;
@@ -60646,6 +61079,7 @@ var ParallAgentGateway = class {
60646
61079
  binding = await runInPhaseSpan("session", () => this.bindRuntimeSession(sessionKey, runtimeEvent, contextFilePath, laneContextFilePath2));
60647
61080
  if (activeLane) {
60648
61081
  bindLaneSession(activeLane, binding.agentSessionId);
61082
+ await this.laneLedger?.syncInputAttribution(activeLane);
60649
61083
  }
60650
61084
  if (event.targetType === "channel_conversation" && binding.agentSessionId !== priorAgentSessionId) {
60651
61085
  try {
@@ -61285,9 +61719,11 @@ var ParallAgentGateway = class {
61285
61719
  const activeFork = createActiveForkState(fork, event.targetId, continuationPrefix);
61286
61720
  this.forkStates.set(event.targetId, activeFork);
61287
61721
  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);
61722
+ if (this.FORK_DEADLINE_MS > 0) {
61723
+ activeFork.deadlineTimer = setTimeout(() => {
61724
+ this.abortFork(event.targetId, `deadline exceeded (${this.FORK_DEADLINE_MS}ms)`);
61725
+ }, this.FORK_DEADLINE_MS);
61726
+ }
61291
61727
  const firstEventPromise = new Promise((resolve3) => {
61292
61728
  activeFork.queue.push({ event, resolve: resolve3 });
61293
61729
  });
@@ -61486,12 +61922,16 @@ ${fullSummary}` : fullSummary;
61486
61922
  this.sessionId = data.session_id ?? "";
61487
61923
  if (this.forkStates.size > 0) {
61488
61924
  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");
61925
+ if (this.opts.instanceId) {
61926
+ log?.info(`${targetIds.length} active fork(s) continue across the reconnect`);
61927
+ } else {
61928
+ log?.info(`aborting ${targetIds.length} active fork(s) on reconnect`);
61929
+ for (const targetId of targetIds) {
61930
+ this.abortFork(targetId, "ws reconnect");
61931
+ }
61492
61932
  }
61493
61933
  }
61494
- if (this.laneLedger && this.inFlightDispatches === 0 && this.laneLedger.activeCount > 0) {
61934
+ if (this.laneLedger && !this.opts.instanceId && this.inFlightDispatches === 0 && this.laneLedger.activeCount > 0) {
61495
61935
  log?.info(`releasing ${this.laneLedger.activeCount} stale lane(s) on reconnect`);
61496
61936
  await this.laneLedger.releaseAll();
61497
61937
  }
@@ -61538,6 +61978,7 @@ ${fullSummary}` : fullSummary;
61538
61978
  uptime: os.uptime()
61539
61979
  });
61540
61980
  }, intervalSec * 1e3);
61981
+ await this.laneLedger?.retryConfirmations();
61541
61982
  this.catchUpFromDispatch().catch((err) => {
61542
61983
  log?.warn(`dispatch catch-up failed: ${String(err)}`);
61543
61984
  });
@@ -61623,10 +62064,12 @@ ${fullSummary}` : fullSummary;
61623
62064
  notifyDrainWaiters() {
61624
62065
  this.drainGate.notify();
61625
62066
  }
61626
- async shutdown() {
62067
+ async shutdown(opts = { skipDrain: false }) {
61627
62068
  this.shuttingDown = true;
61628
62069
  this.idleCompact.abort?.();
61629
- if (!this.isDrained()) {
62070
+ if (opts.skipDrain && !this.isDrained()) {
62071
+ this.opts.log?.warn(`skipping drain (credential rejected): ${this.inFlightDispatches} dispatch(es), ${this.inFlightRuntimeTurns} runtime-initiated turn(s) abandoned to server redelivery`);
62072
+ } else if (!this.isDrained()) {
61630
62073
  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
62074
  await this.drainGate.wait(this.SHUTDOWN_DEADLINE_MS);
61632
62075
  if (!this.isDrained()) {
@@ -61637,7 +62080,7 @@ ${fullSummary}` : fullSummary;
61637
62080
  }
61638
62081
  if (this.heartbeatTimer)
61639
62082
  clearInterval(this.heartbeatTimer);
61640
- if (this.laneLedger && this.laneLedger.activeCount > 0) {
62083
+ if (this.laneLedger) {
61641
62084
  this.opts.log?.info(`releasing ${this.laneLedger.activeCount} lane(s) on shutdown`);
61642
62085
  await this.laneLedger.releaseAll();
61643
62086
  }
@@ -61657,6 +62100,8 @@ ${fullSummary}` : fullSummary;
61657
62100
  this.opts.log?.warn(`${lifecycleRemaining} session lifecycle write(s) unreconciled at shutdown`);
61658
62101
  }
61659
62102
  this.sessionLifecycle.dispose();
62103
+ await this.laneLedger?.retryConfirmations();
62104
+ this.laneLedger?.dispose();
61660
62105
  this.opts.ws.disconnect();
61661
62106
  this.unsubscribeRuntimeActivity?.();
61662
62107
  this.opts.log?.info(`disconnected`);
@@ -63429,13 +63874,18 @@ var parallGateway = {
63429
63874
  runtimeKey: orchestratorKey,
63430
63875
  runtimeRef: { hostname: os2.hostname(), pid: process.pid },
63431
63876
  dispatchAdapter,
63877
+ // One OpenClaw host embeds many account gateways in one process: a
63878
+ // rejected credential stops THIS account's gateway only — it must
63879
+ // not set the host-wide process exit code or claim the process is
63880
+ // exiting.
63881
+ authInvalidExitCode: null,
63432
63882
  // Opts openclaw into the dispatch ledger (claim/fold/complete +
63433
63883
  // idempotent reply effects) — the same shared-gateway machinery
63434
63884
  // claude/codex ride; openclaw stays buffer-only (no mid-turn steer),
63435
63885
  // which the ledger does not require. Replies already flow through
63436
63886
  // @parall/cli, which reads PRLL_CONTEXT_DIR (injected in hooks.ts)
63437
63887
  // to bind dispatch_lane + reply effect keys.
63438
- // Design: docs/engineering-design/dispatch-convergence-design.md §7 (S2).
63888
+ // Design: docs/engineering-design/agent-dispatch-idempotency-design.md#effects.
63439
63889
  dispatchContextDir: dispatchLaneContextDir(stateDir),
63440
63890
  // Per-session context file (PRLL_CONTEXT_FILE contract) — the CLI's
63441
63891
  // TYPED dispatch binding (parall tasks update → task_update effect)