@agentproto/runtime 5.4.1 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -19,9 +19,12 @@ import { resolvePairPageUrl, DEFAULT_PAIR_PAGE } from '@agentproto/secrets/pairi
19
19
  import { ListToolsRequestSchema, CallToolRequestSchema, SUPPORTED_PROTOCOL_VERSIONS, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/sdk/types.js';
20
20
  import { makeCredsStore, makeAdapterResolver, makeAdapterLister, discoverAdapterPackages, makeSetupLedger, makeListTool, makeSetupTool } from '@agentproto/provider-kit';
21
21
  import { syncPiModels, getConfiguredEndpoints, checkHarnessFit, connectorById } from '@agentproto/llm-endpoint';
22
+ import { createServer } from 'http';
23
+ import { createServer as createServer$1 } from 'net';
24
+ import { Readable } from 'stream';
25
+ import { pipeline } from 'stream/promises';
22
26
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
23
27
  import { StreamableHTTPError, StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
24
- import { createServer as createServer$1 } from 'net';
25
28
  import { SandboxSpecSchema, resolveLifecyclePolicy, createSandboxAgentSessionHost, isSandboxBoxGoneError, SandboxHostBootFailedError } from '@agentproto/sandbox';
26
29
  import { APP_UI_DISCOVERY_TOOLS, RUNNER_SELECT_SCRIPT } from '@agentproto/app-client/runner-select';
27
30
  import { DISPLAY_MODE_SCRIPT } from '@agentproto/app-client/display-mode';
@@ -35,8 +38,6 @@ import { createBrainManager, parseKnowledgeConfig } from '@agentproto/workspace-
35
38
  import { browserAdapters, getBrowserAdapter } from '@agentproto/adapter-browser';
36
39
  import { defineHttpDriver } from '@agentproto/driver-http';
37
40
  import matter2 from 'gray-matter';
38
- import { createServer } from 'http';
39
- import { Readable } from 'stream';
40
41
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
41
42
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
42
43
  import { WebSocketServer } from 'ws';
@@ -67,7 +68,8 @@ import { createRequire } from 'module';
67
68
  import { signArtifact, recordAuditEvent } from '@agentproto/governance-engine';
68
69
  import { parseRoutineManifest } from '@agentproto/routine';
69
70
  import { navigateOptionsSchema, evaluateOptionsSchema, clickOptionsSchema, fillOptionsSchema, screenshotOptionsSchema, assertToolSupported } from '@agentproto/driver-browser';
70
- import { pipeline } from 'stream/promises';
71
+ import { lookup } from 'dns/promises';
72
+ import { request } from 'https';
71
73
  import { identityFingerprint } from '@agentproto/secrets/identity';
72
74
 
73
75
  /**
@@ -4874,16 +4876,16 @@ var init_workspaces_config = __esm({
4874
4876
  function normalizeWorktreeField(field) {
4875
4877
  if (field === void 0 || field === false) return void 0;
4876
4878
  if (field === true) return {};
4877
- const request = {};
4878
- if (field.slug !== void 0) request.slug = field.slug;
4879
- if (field.base !== void 0) request.base = field.base;
4880
- if (field.async !== void 0) request.async = field.async;
4881
- return request;
4879
+ const request2 = {};
4880
+ if (field.slug !== void 0) request2.slug = field.slug;
4881
+ if (field.base !== void 0) request2.base = field.base;
4882
+ if (field.async !== void 0) request2.async = field.async;
4883
+ return request2;
4882
4884
  }
4883
4885
  function decideWorktreeIsolation(input) {
4884
- const request = normalizeWorktreeField(input.field);
4886
+ const request2 = normalizeWorktreeField(input.field);
4885
4887
  if (input.depth > 0) {
4886
- if (request !== void 0) {
4888
+ if (request2 !== void 0) {
4887
4889
  return {
4888
4890
  action: "reject",
4889
4891
  message: `agent_start: \`worktree\` isolation was requested on a nested spawn (depth ${input.depth}), but a spawn made through an orchestrator inherits its parent's working tree and cannot provision a worktree of its own. Remove the \`worktree\` field; for an isolated child use \`sandbox\` (the box already isolates) instead.`
@@ -4899,7 +4901,7 @@ function decideWorktreeIsolation(input) {
4899
4901
  }
4900
4902
  switch (input.mode) {
4901
4903
  case "never":
4902
- if (request !== void 0) {
4904
+ if (request2 !== void 0) {
4903
4905
  return {
4904
4906
  action: "reject",
4905
4907
  message: `agent_start: \`worktree\` was requested but this daemon's \`worktrees.isolation\` policy is set to "never". Remove the \`worktree\` field, or change the policy (config \`worktrees.isolation\` / env ${WORKTREE_ISOLATION_ENV}).`
@@ -4907,9 +4909,9 @@ function decideWorktreeIsolation(input) {
4907
4909
  }
4908
4910
  return { action: "spawn-in-place" };
4909
4911
  case "on-request":
4910
- return request !== void 0 ? { action: "provision", request, implicit: false } : { action: "spawn-in-place" };
4912
+ return request2 !== void 0 ? { action: "provision", request: request2, implicit: false } : { action: "spawn-in-place" };
4911
4913
  case "always":
4912
- return { action: "provision", request: request ?? {}, implicit: request === void 0 };
4914
+ return { action: "provision", request: request2 ?? {}, implicit: request2 === void 0 };
4913
4915
  }
4914
4916
  }
4915
4917
  function parseWorktreeIsolationMode(raw) {
@@ -4942,16 +4944,16 @@ function normalizeAttachField(field) {
4942
4944
  if (field === void 0) return void 0;
4943
4945
  if (field === false) return { detached: true };
4944
4946
  if (field === true) return { optIn: true };
4945
- const request = { optIn: true };
4946
- if (field.parent !== void 0) request.parent = field.parent;
4947
- return request;
4947
+ const request2 = { optIn: true };
4948
+ if (field.parent !== void 0) request2.parent = field.parent;
4949
+ return request2;
4948
4950
  }
4949
4951
  function decideSpawnAttach(input) {
4950
- const request = normalizeAttachField(input.field);
4951
- if (request?.detached) return { parent: void 0, detached: true };
4952
- if (request?.parent) return { parent: request.parent, detached: false };
4952
+ const request2 = normalizeAttachField(input.field);
4953
+ if (request2?.detached) return { parent: void 0, detached: true };
4954
+ if (request2?.parent) return { parent: request2.parent, detached: false };
4953
4955
  const candidate = input.hint ?? input.autoParent;
4954
- if (request?.optIn) return { parent: candidate, detached: false };
4956
+ if (request2?.optIn) return { parent: candidate, detached: false };
4955
4957
  switch (input.mode) {
4956
4958
  case "always":
4957
4959
  return { parent: candidate, detached: false };
@@ -5676,8 +5678,8 @@ function pathSegmentsMatch(entryPath, concretePath) {
5676
5678
  return entrySegs.every((seg, i) => seg === "*" || seg === concreteSegs[i]);
5677
5679
  }
5678
5680
  function findConfigKey(path) {
5679
- const lookup = path.startsWith("models.") && path.length > "models.".length ? "models.*" : path;
5680
- return CONFIG_KEYS.find((entry) => pathSegmentsMatch(entry.path, lookup));
5681
+ const lookup2 = path.startsWith("models.") && path.length > "models.".length ? "models.*" : path;
5682
+ return CONFIG_KEYS.find((entry) => pathSegmentsMatch(entry.path, lookup2));
5681
5683
  }
5682
5684
  function validateConfig(raw) {
5683
5685
  const result = agentprotoConfigSchema.safeParse(raw);
@@ -9243,6 +9245,139 @@ var init_sandbox_agent_session_proxy = __esm({
9243
9245
  ]);
9244
9246
  }
9245
9247
  });
9248
+ function collectHeaders(raw) {
9249
+ const headers = {};
9250
+ for (const [k, v] of Object.entries(raw)) {
9251
+ if (v === void 0 || HOP_BY_HOP.has(k.toLowerCase())) continue;
9252
+ headers[k] = Array.isArray(v) ? v.join(", ") : v;
9253
+ }
9254
+ return headers;
9255
+ }
9256
+ function isPrematureCloseError(err) {
9257
+ return typeof err === "object" && err !== null && "code" in err && err.code === "ERR_STREAM_PREMATURE_CLOSE";
9258
+ }
9259
+ async function readBody(req) {
9260
+ const chunks = [];
9261
+ for await (const chunk of req) chunks.push(chunk);
9262
+ if (chunks.length === 0) return void 0;
9263
+ return new Uint8Array(Buffer.concat(chunks));
9264
+ }
9265
+ async function startDeviceSandboxBridge(opts) {
9266
+ const { hostRegistry, target } = opts;
9267
+ const server = createServer((req, res) => {
9268
+ void handleRequest(req, res).catch((err) => {
9269
+ if (res.writableEnded) return;
9270
+ if (!res.headersSent) {
9271
+ res.writeHead(502, { "content-type": "application/json" });
9272
+ res.end(
9273
+ JSON.stringify({
9274
+ error: "device_sandbox_bridge_failed",
9275
+ message: err instanceof Error ? err.message : String(err)
9276
+ })
9277
+ );
9278
+ return;
9279
+ }
9280
+ res.end();
9281
+ });
9282
+ });
9283
+ async function handleRequest(req, res) {
9284
+ const body = await readBody(req);
9285
+ let upstream;
9286
+ try {
9287
+ upstream = await hostRegistry.forwardHttpStream(target, {
9288
+ method: req.method ?? "GET",
9289
+ path: `/device-spawn${req.url ?? "/"}`,
9290
+ headers: collectHeaders(req.headers),
9291
+ ...body ? { body } : {}
9292
+ });
9293
+ } catch (err) {
9294
+ res.writeHead(502, { "content-type": "application/json" });
9295
+ res.end(
9296
+ JSON.stringify({
9297
+ error: "device_unreachable",
9298
+ message: err instanceof Error ? err.message : String(err)
9299
+ })
9300
+ );
9301
+ return;
9302
+ }
9303
+ const resHeaders = {};
9304
+ for (const [k, v] of Object.entries(upstream.headers)) {
9305
+ if (HOP_BY_HOP.has(k.toLowerCase()) || k.toLowerCase() === "content-length") continue;
9306
+ resHeaders[k] = v;
9307
+ }
9308
+ res.writeHead(upstream.status, resHeaders);
9309
+ try {
9310
+ await pipeline(
9311
+ Readable.fromWeb(upstream.body),
9312
+ res
9313
+ );
9314
+ } catch (err) {
9315
+ if (!isPrematureCloseError(err)) throw err;
9316
+ }
9317
+ }
9318
+ const port = await getFreePort();
9319
+ await new Promise((resolve41, reject) => {
9320
+ server.once("error", reject);
9321
+ server.listen(port, "127.0.0.1", () => resolve41());
9322
+ });
9323
+ return {
9324
+ mcpUrl: `http://127.0.0.1:${port}/mcp`,
9325
+ async close() {
9326
+ await new Promise((resolve41) => server.close(() => resolve41()));
9327
+ }
9328
+ };
9329
+ }
9330
+ async function getFreePort() {
9331
+ return new Promise((resolve41, reject) => {
9332
+ const srv = createServer$1();
9333
+ srv.once("error", reject);
9334
+ srv.listen(0, "127.0.0.1", () => {
9335
+ const address = srv.address();
9336
+ if (address === null || typeof address === "string") {
9337
+ srv.close(() => reject(new Error("failed to allocate a free port")));
9338
+ return;
9339
+ }
9340
+ const { port } = address;
9341
+ srv.close(() => resolve41(port));
9342
+ });
9343
+ });
9344
+ }
9345
+ var HOP_BY_HOP;
9346
+ var init_device_sandbox_bridge = __esm({
9347
+ "src/device-sandbox-bridge.ts"() {
9348
+ HOP_BY_HOP = /* @__PURE__ */ new Set(["connection", "keep-alive", "transfer-encoding", "upgrade", "host"]);
9349
+ }
9350
+ });
9351
+ function isDeviceSandboxTarget(sandbox) {
9352
+ const slug = typeof sandbox === "string" ? sandbox : sandbox?.provider ?? "";
9353
+ return slug.startsWith("device:");
9354
+ }
9355
+ function createDeviceSandboxProvider(target, hostRegistry) {
9356
+ return {
9357
+ async boot(_spec, _opts) {
9358
+ const hosts = await hostRegistry.list();
9359
+ const known = hosts.some((h) => h.fingerprint === target || h.name === target);
9360
+ if (!known) {
9361
+ throw new Error(
9362
+ `device sandbox: no paired host device matches "${target}" \u2014 run \`agentproto devices add <offer-url>\` here first (the offer must come from \`agentproto pair offer --host\` run ON that device). Check \`agentproto devices list\` for known devices.`
9363
+ );
9364
+ }
9365
+ const bridge = await startDeviceSandboxBridge({ hostRegistry, target });
9366
+ return {
9367
+ mcpUrl: bridge.mcpUrl,
9368
+ sandboxId: `device-${target}-${randomUUID()}`,
9369
+ async stop() {
9370
+ await bridge.close();
9371
+ }
9372
+ };
9373
+ }
9374
+ };
9375
+ }
9376
+ var init_device = __esm({
9377
+ "src/sandbox-providers/device.ts"() {
9378
+ init_device_sandbox_bridge();
9379
+ }
9380
+ });
9246
9381
  async function connectBoxToolClient(mcpUrl) {
9247
9382
  const client = new Client(
9248
9383
  { name: "agentproto-sandbox-app-serve", version: "0.1.0" },
@@ -9471,7 +9606,7 @@ var init_sandbox_app_serve = __esm({
9471
9606
  REQUIRED_ALLOWLIST_BASENAMES = ["sh", "agentproto"];
9472
9607
  }
9473
9608
  });
9474
- async function getFreePort() {
9609
+ async function getFreePort2() {
9475
9610
  return new Promise((resolve41, reject) => {
9476
9611
  const srv = createServer$1();
9477
9612
  srv.once("error", reject);
@@ -9505,7 +9640,7 @@ var init_local = __esm({
9505
9640
  POLL_INTERVAL_MS = 250;
9506
9641
  localSandboxProvider = {
9507
9642
  async boot(_spec, opts) {
9508
- const port = await getFreePort();
9643
+ const port = await getFreePort2();
9509
9644
  const workspace = await mkdtemp(join(tmpdir(), "agentproto-sandbox-local-"));
9510
9645
  const child = spawn(
9511
9646
  "agentproto",
@@ -10159,6 +10294,9 @@ ${callerPrompt}`;
10159
10294
  const pre = composed.slice(0, composed.length - tail.length);
10160
10295
  return pre || void 0;
10161
10296
  }
10297
+ function deviceContractLine(device) {
10298
+ return `This session was spawned remotely on paired device "${device}" by the controlling daemon \u2014 its own workspace (and any AGENTS.md along that path) is on ANOTHER machine and has not been injected here. If your resolved cwd has an AGENTS.md, treat it as binding on your next tool call (read it first); if you \`cd\` somewhere else (a nested repo included), re-resolve and re-read the AGENTS.md of wherever you end up.`;
10299
+ }
10162
10300
  async function spawnAgentSession(deps2, input) {
10163
10301
  if (input.inference) {
10164
10302
  if (input.model || input.route || input.access) {
@@ -10972,22 +11110,30 @@ async function spawnAgentSession(deps2, input) {
10972
11110
  let effectivePrompt = input.prompt;
10973
11111
  let agentsMdResolution;
10974
11112
  const resolveAgentsMdForSpawn = resolveAgentsMd2 ?? (async (cwdArg, _kb, o) => resolveAgentsMd(cwdArg, await loadAgentsMdInlineMaxKb(), void 0, o?.stopAt));
10975
- try {
10976
- agentsMdResolution = appBoundary ? await resolveAgentsMdForSpawn(cwd, void 0, { stopAt: appBoundary.root }) : await resolveAgentsMdForSpawn(cwd);
10977
- } catch {
10978
- agentsMdResolution = { mode: "absent", contractLine: cdContractLine };
11113
+ const isDeviceSandboxSpawn = isDeviceSandboxTarget(input.sandbox);
11114
+ const deviceTargetName = typeof input.sandbox === "string" ? input.sandbox.slice("device:".length) : (input.sandbox?.provider ?? "").slice("device:".length);
11115
+ if (isDeviceSandboxSpawn) {
11116
+ agentsMdResolution = { mode: "absent", contractLine: deviceContractLine(deviceTargetName) };
11117
+ } else {
11118
+ try {
11119
+ agentsMdResolution = appBoundary ? await resolveAgentsMdForSpawn(cwd, void 0, { stopAt: appBoundary.root }) : await resolveAgentsMdForSpawn(cwd);
11120
+ } catch {
11121
+ agentsMdResolution = { mode: "absent", contractLine: cdContractLine };
11122
+ }
10979
11123
  }
10980
11124
  const agentsMdParts = [agentsMdResolution.block, agentsMdResolution.contractLine].filter(
10981
11125
  (p) => !!p
10982
11126
  );
10983
- const agentsMdReadPaths = additionalReadPathsForAgentsMd(agentsMdResolution, cwd);
11127
+ const agentsMdReadPaths = isDeviceSandboxSpawn ? void 0 : additionalReadPathsForAgentsMd(agentsMdResolution, cwd);
10984
11128
  const additionalReadPaths = [...agentsMdReadPaths ?? [], ...browserReadPaths];
10985
11129
  let workspaceRulesResolution = {};
10986
11130
  const resolveWorkspaceRulesForSpawn = resolveWorkspaceRules2 ?? resolveWorkspaceRules;
10987
- try {
10988
- workspaceRulesResolution = await resolveWorkspaceRulesForSpawn(resolvedSlug);
10989
- } catch {
10990
- workspaceRulesResolution = {};
11131
+ if (!isDeviceSandboxSpawn) {
11132
+ try {
11133
+ workspaceRulesResolution = await resolveWorkspaceRulesForSpawn(resolvedSlug);
11134
+ } catch {
11135
+ workspaceRulesResolution = {};
11136
+ }
10991
11137
  }
10992
11138
  const rulesMdParts = workspaceRulesResolution.block ? [workspaceRulesResolution.block] : [];
10993
11139
  if (input.prompt && typeof input.prompt === "string") {
@@ -11941,6 +12087,7 @@ var init_session_spawn = __esm({
11941
12087
  init_inference_binding();
11942
12088
  init_mcp_credential_deps();
11943
12089
  init_sandbox_agent_session_proxy();
12090
+ init_device();
11944
12091
  init_sandbox_ledger();
11945
12092
  init_sandbox_app_serve();
11946
12093
  init_sandbox_adapters();
@@ -12667,21 +12814,21 @@ function injectStandaloneAppBridge(html, baseUrl) {
12667
12814
  return injectAfterStructuralTag(html, script);
12668
12815
  }
12669
12816
  function createUiHtmlCache() {
12670
- const cache2 = /* @__PURE__ */ new Map();
12817
+ const cache3 = /* @__PURE__ */ new Map();
12671
12818
  return {
12672
12819
  async get(path, version) {
12673
12820
  const st = await stat(path);
12674
12821
  const stamp = `${version}:${st.mtimeMs}:${st.size}`;
12675
- const cached = cache2.get(path);
12822
+ const cached = cache3.get(path);
12676
12823
  if (cached && cached.stamp === stamp) return cached.html;
12677
12824
  const raw = await readFile(path, "utf8");
12678
12825
  const html = injectMcpAppBridge(raw);
12679
- cache2.set(path, { stamp, html });
12826
+ cache3.set(path, { stamp, html });
12680
12827
  return html;
12681
12828
  }
12682
12829
  };
12683
12830
  }
12684
- async function makeInstalledAppUiApps(appRegistry, cache2, existingToolNames) {
12831
+ async function makeInstalledAppUiApps(appRegistry, cache3, existingToolNames) {
12685
12832
  const apps = [];
12686
12833
  const seen = new Set(existingToolNames);
12687
12834
  for (const app of appRegistry.listApps()) {
@@ -12714,7 +12861,7 @@ async function makeInstalledAppUiApps(appRegistry, cache2, existingToolNames) {
12714
12861
  });
12715
12862
  }
12716
12863
  try {
12717
- return await cache2.get(ui.path, app.updatedAt);
12864
+ return await cache3.get(ui.path, app.updatedAt);
12718
12865
  } catch (err) {
12719
12866
  return renderAppUiErrorHtml({
12720
12867
  appName,
@@ -24217,7 +24364,7 @@ function createDefaultHostProbes() {
24217
24364
  );
24218
24365
  if (child.pid !== void 0) own.add(child.pid);
24219
24366
  });
24220
- const sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
24367
+ const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
24221
24368
  const probes = {
24222
24369
  platform,
24223
24370
  table: (t) => createProcessTableSource(t)(),
@@ -24245,7 +24392,7 @@ function createDefaultHostProbes() {
24245
24392
  const read = () => Promise.all([readFile("/proc/stat", "utf8"), readFile("/proc/diskstats", "utf8").catch(() => "")]);
24246
24393
  const t0 = Date.now();
24247
24394
  const [statBefore, diskBefore] = await read();
24248
- await sleep2(400);
24395
+ await sleep3(400);
24249
24396
  const [statAfter, diskAfter] = await read();
24250
24397
  return { statBefore, statAfter, diskBefore, diskAfter, intervalSec: (Date.now() - t0) / 1e3 };
24251
24398
  };
@@ -24790,7 +24937,7 @@ async function callJevSystemOne(state, questions, opts) {
24790
24937
  const fetchImpl = opts.fetchImpl ?? fetch;
24791
24938
  const url = (opts.baseUrl ?? JEV_DEFAULT_URL).replace(/\/+$/, "");
24792
24939
  const maxRetries = opts.maxRetries ?? 3;
24793
- const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
24940
+ const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
24794
24941
  const model = opts.model ?? JEV_DEFAULT_MODEL;
24795
24942
  const timeoutMs = opts.timeoutMs ?? 3e4;
24796
24943
  let attempt = 0;
@@ -24831,7 +24978,7 @@ async function callJevSystemOne(state, questions, opts) {
24831
24978
  }
24832
24979
  if (RETRYABLE_STATUSES.has(res.status) && attempt < maxRetries) {
24833
24980
  attempt++;
24834
- await sleep2(backoffDelayMs(attempt, res.headers.get("retry-after")));
24981
+ await sleep3(backoffDelayMs(attempt, res.headers.get("retry-after")));
24835
24982
  continue;
24836
24983
  }
24837
24984
  const body = await res.text().catch(() => "");
@@ -34907,6 +35054,9 @@ var DEFAULT_PROBE_BACKOFF_MAX_MS = 3e5;
34907
35054
  var DEFAULT_PROBE_CONCURRENCY = 4;
34908
35055
  var RESUME_STAGGER_MS = 500;
34909
35056
  var MAX_LAST_ERROR_CHARS = 300;
35057
+ var HOST_HANDSHAKE_FAILURE_HINT_THRESHOLD = 5;
35058
+ var HOST_HANDSHAKE_REMEDIATION_HINT = "host handshake failing \u2014 the controller should re-run `agentproto devices add` with a fresh `pair offer --host` (a reboot/reinstall on the host invalidates the old registration)";
35059
+ var handshakeFailures = /* @__PURE__ */ new Map();
34910
35060
  var DEFAULT_LAST_SEEN_PERSIST_MS = 3e4;
34911
35061
  var DEFAULT_SNAPSHOT_INTERVAL_MS = 15e3;
34912
35062
  var DEFAULT_SNAPSHOT_ACTIVE_INTERVAL_MS = 5e3;
@@ -35186,6 +35336,7 @@ function createHostRegistry(deps2) {
35186
35336
  async function markReached(record2) {
35187
35337
  const t = now();
35188
35338
  lastReachedAt.set(record2.fingerprint, t);
35339
+ handshakeFailures.delete(record2.fingerprint);
35189
35340
  record2.lastSeen = new Date(t).toISOString();
35190
35341
  record2.lastProbeAt = record2.lastSeen;
35191
35342
  const hadError = record2.lastError !== void 0;
@@ -35198,6 +35349,13 @@ function createHostRegistry(deps2) {
35198
35349
  async function markUnreachable(record2, message) {
35199
35350
  record2.lastProbeAt = new Date(now()).toISOString();
35200
35351
  const error = message.slice(0, MAX_LAST_ERROR_CHARS);
35352
+ const failures = (handshakeFailures.get(record2.fingerprint) ?? 0) + 1;
35353
+ handshakeFailures.set(record2.fingerprint, failures);
35354
+ if (failures === HOST_HANDSHAKE_FAILURE_HINT_THRESHOLD) {
35355
+ log(
35356
+ `[hosts] ${record2.fingerprint} (${record2.name}): failed to dial/handshake ${failures} times in a row \u2014 ` + HOST_HANDSHAKE_REMEDIATION_HINT
35357
+ );
35358
+ }
35201
35359
  if (record2.lastError === error) return;
35202
35360
  record2.lastError = error;
35203
35361
  await persist().catch((err) => log(`[hosts] lastError persist failed: ${errMsg3(err)}`));
@@ -35207,7 +35365,8 @@ function createHostRegistry(deps2) {
35207
35365
  const attempts = [epoch, epoch - 1];
35208
35366
  let lastErr;
35209
35367
  let hungUpOnHello = 0;
35210
- for (const e of attempts) {
35368
+ for (let attempt = 1; attempt <= attempts.length; attempt++) {
35369
+ const e = attempts[attempt - 1];
35211
35370
  const { route, auth } = await deriveEpochTokens(record2.pairRoot, e);
35212
35371
  let raw;
35213
35372
  try {
@@ -35217,32 +35376,45 @@ function createHostRegistry(deps2) {
35217
35376
  continue;
35218
35377
  }
35219
35378
  try {
35220
- const started = await startClientHandshake({
35221
- daemonX25519Pub: record2.daemonX25519Pub,
35222
- daemonEd25519Pub: record2.daemonEd25519Pub,
35223
- authToken: auth,
35224
- clientName: record2.name
35379
+ let rawCloseReason;
35380
+ const offRawClose = raw.onClose((reason) => {
35381
+ rawCloseReason = reason ?? "unknown";
35225
35382
  });
35226
- const wrapped = await clientHandshakeOverSink(
35227
- raw,
35228
- encodePairingMessage(started.hello),
35229
- (replyBytes) => started.complete(decodePairingReply(replyBytes)),
35230
- { timeoutMs: handshakeTimeoutMs }
35231
- );
35232
- const client = createTunnelClient({ sink: wrapped });
35233
- await client.ready();
35383
+ let client;
35384
+ try {
35385
+ const started = await startClientHandshake({
35386
+ daemonX25519Pub: record2.daemonX25519Pub,
35387
+ daemonEd25519Pub: record2.daemonEd25519Pub,
35388
+ authToken: auth,
35389
+ clientName: record2.name
35390
+ });
35391
+ const wrapped = await clientHandshakeOverSink(
35392
+ raw,
35393
+ encodePairingMessage(started.hello),
35394
+ (replyBytes) => started.complete(decodePairingReply(replyBytes)),
35395
+ { timeoutMs: handshakeTimeoutMs }
35396
+ );
35397
+ client = createTunnelClient({ sink: wrapped });
35398
+ await awaitTunnelHello(client, wrapped, record2, () => rawCloseReason);
35399
+ } finally {
35400
+ offRawClose();
35401
+ }
35234
35402
  await markReached(record2);
35235
35403
  return client;
35236
35404
  } catch (err) {
35237
35405
  lastErr = err;
35238
35406
  if (err instanceof Error && /transport closed during handshake/.test(err.message)) hungUpOnHello++;
35407
+ log(
35408
+ `[hosts] ${record2.name} (${record2.fingerprint}) attempt ${attempt}/${attempts.length} failed: ${errMsg3(err)}`
35409
+ );
35239
35410
  try {
35240
35411
  raw.close("handshake failed");
35241
35412
  } catch {
35242
35413
  }
35243
35414
  }
35244
35415
  }
35245
- const hint = hungUpOnHello === attempts.length ? ` \u2014 the host hung up on the pair/v2 hello; if it runs an older agentproto (pair/v1), upgrade it and re-pair: run \`agentproto pair offer --host\` on the host, then \`agentproto devices add\` here. (${PAIRING_PROTOCOL_OUTDATED_MESSAGE})` : "";
35416
+ const helloTimedOut = lastErr instanceof Error && /did not send hello/.test(lastErr.message);
35417
+ const hint = hungUpOnHello === attempts.length || helloTimedOut ? ` \u2014 the host hung up on (or never sent) the post-handshake tunnel hello; if it runs an older agentproto (pair/v1), upgrade it and re-pair: run \`agentproto pair offer --host\` on the host, then \`agentproto devices add\` here. (${PAIRING_PROTOCOL_OUTDATED_MESSAGE})` : "";
35246
35418
  const failure = `could not reach host ${record2.fingerprint} via ${record2.rendezvousUrl}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}${hint}`;
35247
35419
  await markUnreachable(record2, errMsg3(lastErr));
35248
35420
  throw new Error(failure);
@@ -35479,6 +35651,35 @@ function createHostRegistry(deps2) {
35479
35651
  start: sweep
35480
35652
  };
35481
35653
  }
35654
+ async function awaitTunnelHello(client, sink, record2, closeReason) {
35655
+ return new Promise((resolve41, reject) => {
35656
+ if (!sink.isOpen) {
35657
+ reject(
35658
+ new Error(
35659
+ `host ${record2.fingerprint} (${record2.name}) closed the channel after handshake: ${closeReason() ?? "unknown"}`
35660
+ )
35661
+ );
35662
+ return;
35663
+ }
35664
+ const offClose = sink.onClose((reason) => {
35665
+ reject(
35666
+ new Error(
35667
+ `host ${record2.fingerprint} (${record2.name}) closed the channel after handshake: ${reason ?? "unknown"}`
35668
+ )
35669
+ );
35670
+ });
35671
+ client.ready().then(
35672
+ () => {
35673
+ offClose();
35674
+ resolve41();
35675
+ },
35676
+ (err) => {
35677
+ offClose();
35678
+ reject(err);
35679
+ }
35680
+ );
35681
+ });
35682
+ }
35482
35683
  function wrapStreamWithCleanup(source, cleanup) {
35483
35684
  const reader = source.getReader();
35484
35685
  let cleanedUp = false;
@@ -35627,6 +35828,16 @@ function isHostsFile(v) {
35627
35828
  if (!Array.isArray(rec["hosts"])) return false;
35628
35829
  return rec["hosts"].every(isHostRecord);
35629
35830
  }
35831
+ async function readHostsSnapshot(path) {
35832
+ try {
35833
+ const raw = await readFile(path ?? defaultHostsPath(), "utf8");
35834
+ const parsed = JSON.parse(raw);
35835
+ if (!isHostsFile(parsed)) return [];
35836
+ return parsed.hosts.map((rec) => ({ ...rec }));
35837
+ } catch {
35838
+ return [];
35839
+ }
35840
+ }
35630
35841
  function isHostRecord(v) {
35631
35842
  if (typeof v !== "object" || v === null) return false;
35632
35843
  const r = v;
@@ -35731,6 +35942,114 @@ function createDeviceRegistry(pairing, hosts) {
35731
35942
  }
35732
35943
  };
35733
35944
  }
35945
+ var DEVICE_PROMPT_PATH_PREFIX = "/device-prompt";
35946
+ function isValidPrompt(prompt) {
35947
+ if (typeof prompt === "string") return prompt.length > 0;
35948
+ if (Array.isArray(prompt)) {
35949
+ return prompt.length > 0 && prompt.every((b) => b !== null && typeof b === "object");
35950
+ }
35951
+ return prompt !== null && typeof prompt === "object";
35952
+ }
35953
+ function sleep(ms) {
35954
+ return new Promise((resolve41) => setTimeout(resolve41, ms));
35955
+ }
35956
+ function httpErrorMessage(body) {
35957
+ const raw = Buffer.from(body).toString("utf8");
35958
+ try {
35959
+ const parsed = JSON.parse(raw);
35960
+ if (parsed !== null && typeof parsed === "object" && typeof parsed["message"] === "string") {
35961
+ return parsed["message"];
35962
+ }
35963
+ } catch {
35964
+ }
35965
+ return raw;
35966
+ }
35967
+ async function promptHostSession(hosts, target, sessionId, input) {
35968
+ if (!hosts) {
35969
+ throw new Error(
35970
+ "this daemon has no host registry wired \u2014 device prompt is unavailable (internal configuration issue, not a user error)"
35971
+ );
35972
+ }
35973
+ if (!isValidPrompt(input.prompt)) {
35974
+ return {
35975
+ ok: false,
35976
+ message: "`prompt` must be a non-empty string, a content block object, or an array of content blocks."
35977
+ };
35978
+ }
35979
+ const pollMs = Math.min(Math.max(input.pollMs ?? 1e3, 10), 1e4);
35980
+ const delay2 = input.sleep ?? sleep;
35981
+ const enqueueBody = { prompt: input.prompt, queue: true };
35982
+ if (input.interrupt) enqueueBody.interrupt = true;
35983
+ if (input.force) enqueueBody.force = true;
35984
+ const startedAt = Date.now();
35985
+ let enqueueRes;
35986
+ try {
35987
+ enqueueRes = await hosts.forwardHttp(target, {
35988
+ method: "POST",
35989
+ path: `${DEVICE_PROMPT_PATH_PREFIX}/${encodeURIComponent(sessionId)}?wait=false`,
35990
+ headers: { "content-type": "application/json" },
35991
+ body: new Uint8Array(Buffer.from(JSON.stringify(enqueueBody), "utf8"))
35992
+ });
35993
+ } catch (err) {
35994
+ return { ok: false, message: err instanceof Error ? err.message : String(err) };
35995
+ }
35996
+ if (enqueueRes.status < 200 || enqueueRes.status >= 300) {
35997
+ return { ok: false, status: enqueueRes.status, message: httpErrorMessage(enqueueRes.body) };
35998
+ }
35999
+ let enqueueParsed = {};
36000
+ try {
36001
+ const parsed = JSON.parse(Buffer.from(enqueueRes.body).toString("utf8"));
36002
+ if (parsed !== null && typeof parsed === "object") enqueueParsed = parsed;
36003
+ } catch {
36004
+ }
36005
+ if (!input.wait) {
36006
+ const pending = enqueueParsed["pending"] === true;
36007
+ return {
36008
+ ok: true,
36009
+ ...pending ? {
36010
+ pending: true,
36011
+ ...typeof enqueueParsed["queueId"] === "string" ? { queueId: enqueueParsed["queueId"] } : {},
36012
+ ...typeof enqueueParsed["queuePosition"] === "number" ? { queuePosition: enqueueParsed["queuePosition"] } : {}
36013
+ } : {}
36014
+ };
36015
+ }
36016
+ for (; ; ) {
36017
+ await delay2(pollMs);
36018
+ const waitedMs = Date.now() - startedAt;
36019
+ let descRes;
36020
+ try {
36021
+ descRes = await hosts.forwardHttp(target, {
36022
+ method: "GET",
36023
+ path: `/sessions/${encodeURIComponent(sessionId)}`
36024
+ });
36025
+ } catch (err) {
36026
+ return { ok: false, waitedMs, message: err instanceof Error ? err.message : String(err) };
36027
+ }
36028
+ if (descRes.status === 404) {
36029
+ return { ok: false, waitedMs, status: 404, message: `session ${sessionId} not found on host ${target}` };
36030
+ }
36031
+ if (descRes.status < 200 || descRes.status >= 300) {
36032
+ return { ok: false, waitedMs, status: descRes.status, message: httpErrorMessage(descRes.body) };
36033
+ }
36034
+ let descriptor = {};
36035
+ try {
36036
+ const parsed = JSON.parse(Buffer.from(descRes.body).toString("utf8"));
36037
+ if (parsed !== null && typeof parsed === "object") descriptor = parsed;
36038
+ } catch {
36039
+ return { ok: false, waitedMs, status: descRes.status, message: "host returned a non-JSON session descriptor" };
36040
+ }
36041
+ if (descriptor["alive"] === false) {
36042
+ return { ok: false, waitedMs, message: `session ${sessionId} ended while waiting for its turn to drain` };
36043
+ }
36044
+ const queue = Array.isArray(descriptor["promptQueue"]) ? descriptor["promptQueue"] : [];
36045
+ if (descriptor["busy"] !== true && queue.length === 0) {
36046
+ return { ok: true, waitedMs };
36047
+ }
36048
+ if (input.maxWaitMs !== void 0 && input.maxWaitMs > 0 && waitedMs >= input.maxWaitMs) {
36049
+ return { ok: false, waitedMs, message: `timed out after ${waitedMs}ms waiting for the turn to drain` };
36050
+ }
36051
+ }
36052
+ }
35734
36053
 
35735
36054
  // src/app-tools.ts
35736
36055
  init_app_ui_build();
@@ -37318,7 +37637,7 @@ function createLedgerAppender(app, appRunId, runId, item) {
37318
37637
  };
37319
37638
  return { append, flush: () => chain };
37320
37639
  }
37321
- async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvents, cache2, cacheKey, input, persist, appRegistry, eventLog, lease, runsRoot) {
37640
+ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvents, cache3, cacheKey, input, persist, appRegistry, eventLog, lease, runsRoot) {
37322
37641
  if (lease) {
37323
37642
  const renew = () => {
37324
37643
  state.run.lease = { ownerId: lease.ownerId, heartbeatAt: lease.now().toISOString() };
@@ -37350,7 +37669,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
37350
37669
  workspaceSlug: state.workspaceSlug,
37351
37670
  ...workspacePaths ? { workspace: workspacePaths.scratch, artifactsDir: workspacePaths.artifactsDir, runId: state.run.runId, onArtifact } : {},
37352
37671
  input,
37353
- ...cache2 ? { cache: cache2, cacheKey } : {},
37672
+ ...cache3 ? { cache: cache3, cacheKey } : {},
37354
37673
  // WP-S — human-resolved approval steps: a `kind: "approval"` step parks
37355
37674
  // the run as "awaiting-approval" with a pending inbox entry, instead of
37356
37675
  // the engine's silent auto-approve. A human answers through
@@ -38036,9 +38355,9 @@ function createWorkflowRunner(opts) {
38036
38355
  }
38037
38356
  );
38038
38357
  state.agents = agents;
38039
- const cache2 = input.cacheKey ? journal ? teeIntoInternalJournal(createFileStepCache(input.cacheKey), journal) : createFileStepCache(input.cacheKey) : journal?.cache;
38358
+ const cache3 = input.cacheKey ? journal ? teeIntoInternalJournal(createFileStepCache(input.cacheKey), journal) : createFileStepCache(input.cacheKey) : journal?.cache;
38040
38359
  const effectiveCacheKey = input.cacheKey ?? journal?.cacheKey;
38041
- void executeRunWorkflow(state, workflow, agents, abort.signal, sessionEvents, cache2, effectiveCacheKey, void 0, persist, opts.appRegistry, eventLog, leaseOpts, workspace).then(() => {
38360
+ void executeRunWorkflow(state, workflow, agents, abort.signal, sessionEvents, cache3, effectiveCacheKey, void 0, persist, opts.appRegistry, eventLog, leaseOpts, workspace).then(() => {
38042
38361
  for (const [sid, binding] of sessionToRun) {
38043
38362
  if (binding.runId === runId) sessionToRun.delete(sid);
38044
38363
  }
@@ -38169,7 +38488,7 @@ function createWorkflowRunner(opts) {
38169
38488
  );
38170
38489
  state.agents = agents;
38171
38490
  const journal = internalJournal(workspaceRoot, runId);
38172
- const cache2 = args.cacheKey ? journal ? teeIntoInternalJournal(createFileStepCache(args.cacheKey), journal) : createFileStepCache(args.cacheKey) : journal?.cache;
38491
+ const cache3 = args.cacheKey ? journal ? teeIntoInternalJournal(createFileStepCache(args.cacheKey), journal) : createFileStepCache(args.cacheKey) : journal?.cache;
38173
38492
  const effectiveCacheKey = args.cacheKey ?? journal?.cacheKey;
38174
38493
  const execWorkflow = journal && args.cacheKey === void 0 ? forceCacheableWorkflow(workflow) : workflow;
38175
38494
  void executeRunWorkflow(
@@ -38178,7 +38497,7 @@ function createWorkflowRunner(opts) {
38178
38497
  agents,
38179
38498
  abort.signal,
38180
38499
  sessionEvents,
38181
- cache2,
38500
+ cache3,
38182
38501
  effectiveCacheKey,
38183
38502
  args.input,
38184
38503
  persist,
@@ -38965,6 +39284,36 @@ async function assertCronAdapterResolvable(action, deps2) {
38965
39284
  }
38966
39285
  var DEFAULT_PERSIST_PATH3 = () => join(homedir(), ".agentproto", "cron-jobs.json");
38967
39286
  var TICK_INTERVAL_MS = 2e4;
39287
+ var MAX_RUNS_PER_JOB = 50;
39288
+ function ledgerPath(persistPath) {
39289
+ return `${persistPath}.runs.json`;
39290
+ }
39291
+ function loadRuns2(path) {
39292
+ try {
39293
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
39294
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return /* @__PURE__ */ new Map();
39295
+ const runs = /* @__PURE__ */ new Map();
39296
+ for (const [jobId, value] of Object.entries(parsed)) {
39297
+ if (Array.isArray(value)) {
39298
+ runs.set(jobId, value.filter(
39299
+ (run) => run && typeof run.runId === "string" && run.jobId === jobId && typeof run.startedAt === "string" && typeof run.endedAt === "string" && typeof run.ok === "boolean" && typeof run.result === "string"
39300
+ ).slice(-MAX_RUNS_PER_JOB));
39301
+ }
39302
+ }
39303
+ return runs;
39304
+ } catch {
39305
+ return /* @__PURE__ */ new Map();
39306
+ }
39307
+ }
39308
+ function saveRuns2(runs, path) {
39309
+ try {
39310
+ mkdirSync(dirname(path), { recursive: true });
39311
+ const tmp = `${path}.tmp.${process.pid}`;
39312
+ writeFileSync(tmp, JSON.stringify(Object.fromEntries(runs), null, 2) + "\n", "utf8");
39313
+ renameSync(tmp, path);
39314
+ } catch {
39315
+ }
39316
+ }
38968
39317
  function loadJobs(persistPath) {
38969
39318
  const result = /* @__PURE__ */ new Map();
38970
39319
  if (!existsSync(persistPath)) return result;
@@ -38987,6 +39336,9 @@ function loadJobs(persistPath) {
38987
39336
  const job = item;
38988
39337
  job.recurring = job.recurring ?? true;
38989
39338
  job.active = job.active ?? true;
39339
+ if (job.finished === void 0 && !job.recurring && !job.active && !!job.lastRunAt) {
39340
+ job.finished = true;
39341
+ }
38990
39342
  result.set(job.id, { job });
38991
39343
  }
38992
39344
  return result;
@@ -39024,6 +39376,14 @@ function createCronScheduler(opts) {
39024
39376
  const persistPath = opts.persistPath ?? DEFAULT_PERSIST_PATH3();
39025
39377
  const shouldPersist = opts.persist ?? opts.persistPath !== void 0;
39026
39378
  const jobs = shouldPersist ? loadJobs(persistPath) : /* @__PURE__ */ new Map();
39379
+ const runHistory = shouldPersist ? loadRuns2(ledgerPath(persistPath)) : /* @__PURE__ */ new Map();
39380
+ const appendRun = (run) => {
39381
+ const history = runHistory.get(run.jobId) ?? [];
39382
+ history.push(run);
39383
+ if (history.length > MAX_RUNS_PER_JOB) history.splice(0, history.length - MAX_RUNS_PER_JOB);
39384
+ runHistory.set(run.jobId, history);
39385
+ if (shouldPersist) saveRuns2(runHistory, ledgerPath(persistPath));
39386
+ };
39027
39387
  for (const state of jobs.values()) {
39028
39388
  if (!state.job.active) continue;
39029
39389
  try {
@@ -39146,7 +39506,15 @@ function createCronScheduler(opts) {
39146
39506
  if (!ok4) {
39147
39507
  throw new Error(`cron job '${job.id}': tool '${action.tool}' failed: ${summary2}`);
39148
39508
  }
39149
- return { ok: true, summary: `tool '${action.tool}': ${summary2}` };
39509
+ let sessionId2;
39510
+ if (action.tool === "agent_start") {
39511
+ try {
39512
+ const body = JSON.parse(summary2);
39513
+ if (typeof body.id === "string") sessionId2 = body.id;
39514
+ } catch {
39515
+ }
39516
+ }
39517
+ return { ok: true, summary: `tool '${action.tool}': ${summary2}`, sessionId: sessionId2 };
39150
39518
  }
39151
39519
  if (!dispatchTool) {
39152
39520
  throw new Error(
@@ -39169,6 +39537,7 @@ function createCronScheduler(opts) {
39169
39537
  }
39170
39538
  return {
39171
39539
  ok: true,
39540
+ sessionId,
39172
39541
  summary: sessionId ? `spawned session ${sessionId} (adapter=${action.adapter ?? action.harness ?? "preset"})` : `agent_start: ${summary}`
39173
39542
  };
39174
39543
  };
@@ -39177,45 +39546,37 @@ function createCronScheduler(opts) {
39177
39546
  state.running = true;
39178
39547
  try {
39179
39548
  const { job } = state;
39180
- const now = (/* @__PURE__ */ new Date()).toISOString();
39181
- job.lastRunAt = now;
39182
- sessionEvents.emit({ type: "cron:fired", jobId: job.id, label: job.label, ts: now });
39549
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
39550
+ job.lastRunAt = startedAt;
39551
+ sessionEvents.emit({ type: "cron:fired", jobId: job.id, label: job.label, ts: startedAt });
39183
39552
  let result;
39553
+ let error;
39184
39554
  try {
39185
39555
  result = await executeAction(job);
39186
39556
  } catch (err) {
39187
- const error = err instanceof Error ? err.message : String(err);
39557
+ error = err instanceof Error ? err.message : String(err);
39188
39558
  result = { ok: false, summary: error };
39189
- sessionEvents.emit({
39190
- type: "cron:failed",
39191
- jobId: job.id,
39192
- label: job.label,
39193
- error,
39194
- ts: (/* @__PURE__ */ new Date()).toISOString()
39195
- });
39196
- job.lastResult = result;
39197
- if (!job.recurring) {
39198
- job.active = false;
39199
- job.nextRunAt = void 0;
39200
- state.cronInstance?.stop();
39201
- state.cronInstance = void 0;
39202
- } else if (state.cronInstance) {
39203
- const next = nextFireDate(state.cronInstance);
39204
- job.nextRunAt = next?.toISOString();
39205
- }
39206
- persistNow();
39207
- return;
39208
39559
  }
39209
- sessionEvents.emit({
39210
- type: "cron:succeeded",
39560
+ const endedAt = (/* @__PURE__ */ new Date()).toISOString();
39561
+ appendRun({
39562
+ runId: `run_${randomUUID()}`,
39211
39563
  jobId: job.id,
39212
- label: job.label,
39213
- summary: result.summary,
39214
- ts: (/* @__PURE__ */ new Date()).toISOString()
39564
+ startedAt,
39565
+ endedAt,
39566
+ ok: result.ok,
39567
+ ...result.sessionId ? { sessionId: result.sessionId } : {},
39568
+ result: result.summary,
39569
+ ...error ? { error } : {}
39215
39570
  });
39216
- job.lastResult = result;
39571
+ if (error) {
39572
+ sessionEvents.emit({ type: "cron:failed", jobId: job.id, label: job.label, error, ts: endedAt });
39573
+ } else {
39574
+ sessionEvents.emit({ type: "cron:succeeded", jobId: job.id, label: job.label, summary: result.summary, ts: endedAt });
39575
+ }
39576
+ job.lastResult = { ok: result.ok, summary: result.summary };
39217
39577
  if (!job.recurring) {
39218
39578
  job.active = false;
39579
+ job.finished = true;
39219
39580
  job.nextRunAt = void 0;
39220
39581
  state.cronInstance?.stop();
39221
39582
  state.cronInstance = void 0;
@@ -39262,6 +39623,7 @@ function createCronScheduler(opts) {
39262
39623
  action,
39263
39624
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
39264
39625
  active: true,
39626
+ finished: false,
39265
39627
  nextRunAt: next?.toISOString()
39266
39628
  };
39267
39629
  jobs.set(id, { job, cronInstance });
@@ -39274,11 +39636,55 @@ function createCronScheduler(opts) {
39274
39636
  get(id) {
39275
39637
  return jobs.get(id)?.job;
39276
39638
  },
39639
+ async update(id, patch) {
39640
+ const state = jobs.get(id);
39641
+ if (!state) throw new Error(`cron job not found: ${id}`);
39642
+ if (state.running) throw new Error(`cron job is running: ${id}`);
39643
+ const job = state.job;
39644
+ const schedule = patch.schedule ?? job.schedule;
39645
+ const timezone = patch.timezone === null ? void 0 : patch.timezone ?? job.timezone;
39646
+ const action = patch.action ?? job.action;
39647
+ if (!schedule) throw new Error("cron schedule must not be empty");
39648
+ assertCronActionAllowed(action);
39649
+ const cronInstance = parseCron(schedule, timezone);
39650
+ await assertCronAdapterResolvable(action, {
39651
+ resolveAgentAdapter,
39652
+ getAuthProfile: opts.getAuthProfile,
39653
+ listAgentAdapters: opts.listAgentAdapters
39654
+ });
39655
+ const active = patch.active ?? job.active;
39656
+ const next = active ? nextFireDate(cronInstance) : null;
39657
+ state.cronInstance?.stop();
39658
+ state.cronInstance = active ? cronInstance : void 0;
39659
+ job.schedule = schedule;
39660
+ job.timezone = timezone;
39661
+ job.action = action;
39662
+ job.recurring = patch.recurring ?? job.recurring;
39663
+ if (patch.label !== void 0) job.label = patch.label === null ? void 0 : patch.label;
39664
+ job.active = active;
39665
+ if (active) job.finished = false;
39666
+ job.nextRunAt = next?.toISOString();
39667
+ persistNow();
39668
+ return job;
39669
+ },
39670
+ runs({ jobId, limit = 20, cursor } = {}) {
39671
+ if (jobId && !jobs.has(jobId)) throw new Error(`cron job not found: ${jobId}`);
39672
+ if (!Number.isInteger(limit) || limit < 1 || limit > 200) {
39673
+ throw new Error("limit must be an integer from 1 to 200");
39674
+ }
39675
+ const all = (jobId ? runHistory.get(jobId) ?? [] : Array.from(runHistory.values()).flat()).slice().sort((a, b) => b.startedAt.localeCompare(a.startedAt) || b.runId.localeCompare(a.runId));
39676
+ const start = cursor ? all.findIndex((run) => run.runId === cursor) + 1 : 0;
39677
+ if (cursor && start === 0) throw new Error("invalid cron runs cursor");
39678
+ const runs = all.slice(start, start + limit);
39679
+ return { runs, ...start + limit < all.length ? { nextCursor: runs[runs.length - 1]?.runId } : {} };
39680
+ },
39277
39681
  delete(id) {
39278
39682
  const state = jobs.get(id);
39279
39683
  if (!state) throw new Error(`cron job not found: ${id}`);
39280
39684
  state.cronInstance?.stop();
39281
39685
  jobs.delete(id);
39686
+ runHistory.delete(id);
39687
+ if (shouldPersist) saveRuns2(runHistory, ledgerPath(persistPath));
39282
39688
  persistNow();
39283
39689
  },
39284
39690
  async run(id) {
@@ -39555,7 +39961,7 @@ function createTaskLedger(opts) {
39555
39961
  conflict: false,
39556
39962
  error: `task "${taskId}" not found`
39557
39963
  });
39558
- const lookup = (taskId, caller) => {
39964
+ const lookup2 = (taskId, caller) => {
39559
39965
  const task = tasks.get(taskId);
39560
39966
  if (!task) return void 0;
39561
39967
  if (!canAccessBoard(caller, task.boardId)) return void 0;
@@ -39700,7 +40106,7 @@ function createTaskLedger(opts) {
39700
40106
  return Array.from(tasks.values()).filter((t) => t.boardId === boardId).filter((t) => filter?.status !== void 0 ? t.status === filter.status : true).filter((t) => includeClosed || !isClosedTaskStatus(t.status)).sort((a, b) => a.createdAt.localeCompare(b.createdAt));
39701
40107
  };
39702
40108
  const claim = (input, caller) => {
39703
- const task = lookup(input.taskId, caller);
40109
+ const task = lookup2(input.taskId, caller);
39704
40110
  if (!task) return notFound(input.taskId);
39705
40111
  if (input.rev !== task.rev || task.owner !== void 0) {
39706
40112
  return { ok: false, conflict: true, current: task };
@@ -39723,7 +40129,7 @@ function createTaskLedger(opts) {
39723
40129
  return { ok: true, task };
39724
40130
  };
39725
40131
  const update = (input, caller) => {
39726
- const task = lookup(input.taskId, caller);
40132
+ const task = lookup2(input.taskId, caller);
39727
40133
  if (!task) return notFound(input.taskId);
39728
40134
  if (input.rev !== task.rev) {
39729
40135
  return { ok: false, conflict: true, current: task };
@@ -39906,7 +40312,7 @@ function createTaskLedger(opts) {
39906
40312
  return { ok: true, task };
39907
40313
  };
39908
40314
  const linkApproval = (taskId, approvalId, caller) => {
39909
- const task = lookup(taskId, caller);
40315
+ const task = lookup2(taskId, caller);
39910
40316
  if (!task) return notFound(taskId);
39911
40317
  const manager = isManager(caller, task);
39912
40318
  const owner = isOwner(caller, task);
@@ -39951,7 +40357,7 @@ function createTaskLedger(opts) {
39951
40357
  return {
39952
40358
  create,
39953
40359
  list,
39954
- get: (taskId, caller) => lookup(taskId, caller),
40360
+ get: (taskId, caller) => lookup2(taskId, caller),
39955
40361
  claim,
39956
40362
  update,
39957
40363
  linkApproval,
@@ -42093,6 +42499,7 @@ function registerOrchestrationTools(rawServer, opts) {
42093
42499
  "When false, fires once then deactivates. Default true."
42094
42500
  ),
42095
42501
  label: z.string().optional().describe("Human-readable label for the job."),
42502
+ timezone: z.string().optional().describe("IANA timezone for interpreting the schedule. Defaults to host local time."),
42096
42503
  action: cronActionSchema.describe(
42097
42504
  "What to do when the job fires. 'command' runs an allowlisted shell command; 'agent' spawns a brand-new agent session and takes every agent_start field (access.profileRef, role, worktree, \u2026; not `wait`); 'prompt-session' re-prompts an existing, already-running session in place (for durable check-in jobs); 'tool' calls a registered daemon MCP tool in-process (cron_* refused)."
42098
42505
  )
@@ -42107,6 +42514,7 @@ function registerOrchestrationTools(rawServer, opts) {
42107
42514
  const job = await cronScheduler.create({
42108
42515
  label: input.label,
42109
42516
  schedule: input.schedule,
42517
+ timezone: input.timezone,
42110
42518
  recurring: input.recurring ?? true,
42111
42519
  action: input.action
42112
42520
  });
@@ -42144,6 +42552,7 @@ function registerOrchestrationTools(rawServer, opts) {
42144
42552
  schedule: j.schedule,
42145
42553
  recurring: j.recurring,
42146
42554
  active: j.active,
42555
+ finished: j.finished ?? false,
42147
42556
  nextRunAt: j.nextRunAt,
42148
42557
  lastRunAt: j.lastRunAt,
42149
42558
  // A failed last run must be visible without `full: true`.
@@ -42151,7 +42560,7 @@ function registerOrchestrationTools(rawServer, opts) {
42151
42560
  });
42152
42561
  registerBuiltinTool(server, {
42153
42562
  id: "cron_list",
42154
- description: "List all cron jobs (active and inactive) with their schedule, last result, and next fire time. COMPACT BY DEFAULT: each entry is a slim projection (id/label/schedule/recurring/active/nextRunAt/lastRunAt/lastOk); pass `full: true` (or `compact: false`) for the complete job record including action/createdAt/lastResult.",
42563
+ description: "List all cron jobs (active and inactive) with their schedule, last result, and next fire time. COMPACT BY DEFAULT: each entry is a slim projection (id/label/schedule/recurring/active/finished/nextRunAt/lastRunAt/lastOk); pass `full: true` (or `compact: false`) for the complete job record including action/createdAt/lastResult.",
42155
42564
  inputSchema: cronListSchema,
42156
42565
  handler: async () => cronScheduler.list(),
42157
42566
  transformers: [
@@ -42164,6 +42573,47 @@ function registerOrchestrationTools(rawServer, opts) {
42164
42573
  })
42165
42574
  ]
42166
42575
  });
42576
+ server.tool(
42577
+ "cron_update",
42578
+ "Edit a cron job's schedule, label, timezone, recurrence, action, or active state. Set active false to pause and true to resume.",
42579
+ {
42580
+ jobId: z.string().min(1),
42581
+ schedule: z.string().min(1).optional(),
42582
+ label: z.string().nullable().optional(),
42583
+ timezone: z.string().nullable().optional(),
42584
+ recurring: z.boolean().optional(),
42585
+ active: z.boolean().optional(),
42586
+ action: cronActionSchema.optional()
42587
+ },
42588
+ async (input) => {
42589
+ try {
42590
+ if (input.action?.kind === "tool" && opts.toolSubset && !opts.toolSubset.has(input.action.tool)) {
42591
+ throw new Error(`cron action kind "tool" cannot dispatch '${input.action.tool}': not in this server's tool subset`);
42592
+ }
42593
+ const { jobId, ...patch } = input;
42594
+ const job = await cronScheduler.update(jobId, patch);
42595
+ return { content: [{ type: "text", text: JSON.stringify(job) }] };
42596
+ } catch (err) {
42597
+ return { content: [{ type: "text", text: JSON.stringify({ error: err instanceof Error ? err.message : String(err) }) }], isError: true };
42598
+ }
42599
+ }
42600
+ );
42601
+ server.tool(
42602
+ "cron_runs",
42603
+ "Read cron fire history, newest first. Use nextCursor for the next page.",
42604
+ {
42605
+ jobId: z.string().min(1).optional(),
42606
+ limit: z.number().int().min(1).max(200).optional(),
42607
+ cursor: z.string().min(1).optional()
42608
+ },
42609
+ async (input) => {
42610
+ try {
42611
+ return { content: [{ type: "text", text: JSON.stringify(cronScheduler.runs(input)) }] };
42612
+ } catch (err) {
42613
+ return { content: [{ type: "text", text: JSON.stringify({ error: err instanceof Error ? err.message : String(err) }) }], isError: true };
42614
+ }
42615
+ }
42616
+ );
42167
42617
  server.tool(
42168
42618
  "cron_delete",
42169
42619
  "Permanently remove a cron job. Throws if the job is not found.",
@@ -42354,7 +42804,7 @@ function createRemoteCatalogClient(options = {}) {
42354
42804
  const timeoutMs = options.timeoutMs ?? DEFAULT_REMOTE_CATALOG_TIMEOUT_MS;
42355
42805
  const fetchImpl = options.fetchImpl ?? fetch;
42356
42806
  const now = options.now ?? Date.now;
42357
- const cache2 = /* @__PURE__ */ new Map();
42807
+ const cache3 = /* @__PURE__ */ new Map();
42358
42808
  async function fetchOne(url) {
42359
42809
  let body;
42360
42810
  try {
@@ -42386,13 +42836,13 @@ function createRemoteCatalogClient(options = {}) {
42386
42836
  async fetchSources(sources, opts = {}) {
42387
42837
  const results = await Promise.all(
42388
42838
  sources.map(async ({ url }) => {
42389
- const hit = cache2.get(url);
42839
+ const hit = cache3.get(url);
42390
42840
  if (!opts.refresh && hit && now() - hit.at < ttlMs) return hit.result;
42391
42841
  const result = await fetchOne(url);
42392
42842
  if (result.entries.length > 0 || result.warnings.length === 0) {
42393
- cache2.set(url, { at: now(), result });
42843
+ cache3.set(url, { at: now(), result });
42394
42844
  } else {
42395
- cache2.delete(url);
42845
+ cache3.delete(url);
42396
42846
  }
42397
42847
  return result;
42398
42848
  })
@@ -43728,7 +44178,7 @@ function send(res, status, body) {
43728
44178
  res.writeHead(status, { "content-type": "application/json" });
43729
44179
  res.end(JSON.stringify(body));
43730
44180
  }
43731
- async function readBody(req) {
44181
+ async function readBody2(req) {
43732
44182
  const chunks = [];
43733
44183
  let size = 0;
43734
44184
  for await (const chunk of req) {
@@ -43765,7 +44215,7 @@ async function handleA2aTaskRequest(req, res, path, deps2) {
43765
44215
  return true;
43766
44216
  }
43767
44217
  if (appId.includes("/.well-known/")) return false;
43768
- const body = await readBody(req);
44218
+ const body = await readBody2(req);
43769
44219
  if ("tooLarge" in body) {
43770
44220
  send(res, 413, { jsonrpc: "2.0", id: null, error: { code: JSON_RPC_ERROR.invalidRequest, message: "request body too large" } });
43771
44221
  return true;
@@ -45151,8 +45601,8 @@ async function startHttpServer(opts) {
45151
45601
  }))
45152
45602
  };
45153
45603
  });
45154
- server2.server.setRequestHandler(CallToolRequestSchema, async (request) => {
45155
- const out = await proxy.callTool(importId, request.params.name, request.params.arguments ?? {});
45604
+ server2.server.setRequestHandler(CallToolRequestSchema, async (request2) => {
45605
+ const out = await proxy.callTool(importId, request2.params.name, request2.params.arguments ?? {});
45156
45606
  if (!out.ok) {
45157
45607
  return { content: [{ type: "text", text: out.error }], isError: true };
45158
45608
  }
@@ -46841,6 +47291,16 @@ async function startHttpServer(opts) {
46841
47291
  );
46842
47292
  if (handled) return;
46843
47293
  }
47294
+ if (path.startsWith("/device-prompt/")) {
47295
+ const handled = await handleDevicePrompt(
47296
+ req,
47297
+ res,
47298
+ path,
47299
+ opts.port,
47300
+ opts.deviceSpawnAllow === true
47301
+ );
47302
+ if (handled) return;
47303
+ }
46844
47304
  if (opts.remote && path.startsWith("/remote")) {
46845
47305
  const handled = await handleRemoteControl(req, res, path, opts.remote);
46846
47306
  if (handled) return;
@@ -49471,6 +49931,79 @@ async function handleDeviceSpawn(req, res, path, port, spawnAllowed) {
49471
49931
  Readable.fromWeb(upstreamRes.body).pipe(res);
49472
49932
  return true;
49473
49933
  }
49934
+ async function handleDevicePrompt(req, res, path, port, spawnAllowed) {
49935
+ const json3 = (status, body2) => {
49936
+ res.writeHead(status, { "content-type": "application/json" });
49937
+ res.end(JSON.stringify(body2));
49938
+ };
49939
+ const promptMatch = path.match(/^\/device-prompt\/([^/?]+)(\?.*)?$/);
49940
+ if (!promptMatch) return false;
49941
+ const sessionId = decodeURIComponent(promptMatch[1] ?? "");
49942
+ if (!sessionId) return false;
49943
+ if (req.headers[HOST_SCOPE_HEADER] !== "1") {
49944
+ json3(403, {
49945
+ error: "host_scope_required",
49946
+ message: "device-prompt is reachable only over a host-scoped pairing (the far end must have registered this daemon via `agentproto pair offer --host` + `agentproto devices add`)"
49947
+ });
49948
+ return true;
49949
+ }
49950
+ if (!spawnAllowed) {
49951
+ json3(403, {
49952
+ error: "spawn_disabled",
49953
+ message: "this host has not opted in \u2014 run `agentproto devices allow-spawn on` on it"
49954
+ });
49955
+ return true;
49956
+ }
49957
+ if (req.method !== "POST") {
49958
+ json3(405, { error: "method_not_allowed", message: "POST /device-prompt/:sessionId" });
49959
+ return true;
49960
+ }
49961
+ const chunks = [];
49962
+ for await (const chunk of req) chunks.push(chunk);
49963
+ const body = Buffer.concat(chunks);
49964
+ const headers = {};
49965
+ for (const [k, v] of Object.entries(req.headers)) {
49966
+ if (v === void 0) continue;
49967
+ const lower = k.toLowerCase();
49968
+ if (lower === "connection" || lower === "keep-alive" || lower === "transfer-encoding") continue;
49969
+ if (lower === "upgrade" || lower === "host" || lower === "content-length") continue;
49970
+ if (lower === HOST_SCOPE_HEADER) continue;
49971
+ headers[k] = Array.isArray(v) ? v.join(", ") : v;
49972
+ }
49973
+ const rawUrl = req.url ?? "";
49974
+ const query = rawUrl.includes("?") ? rawUrl.slice(rawUrl.indexOf("?")) : "";
49975
+ let upstreamRes;
49976
+ try {
49977
+ upstreamRes = await fetch(
49978
+ `http://127.0.0.1:${port}/sessions/${encodeURIComponent(sessionId)}/prompt${query}`,
49979
+ {
49980
+ method: "POST",
49981
+ headers,
49982
+ ...body.length > 0 ? { body } : {}
49983
+ // Cast rationale — see `handleDeviceSpawn`'s identical cast above.
49984
+ }
49985
+ );
49986
+ } catch (err) {
49987
+ json3(502, {
49988
+ error: "self_proxy_unreachable",
49989
+ message: err instanceof Error ? err.message : String(err)
49990
+ });
49991
+ return true;
49992
+ }
49993
+ const resHeaders = {};
49994
+ upstreamRes.headers.forEach((v, k) => {
49995
+ if (k === "content-length" || k === "connection") return;
49996
+ resHeaders[k] = v;
49997
+ });
49998
+ res.writeHead(upstreamRes.status, resHeaders);
49999
+ if (!upstreamRes.body) {
50000
+ res.end();
50001
+ return true;
50002
+ }
50003
+ const resBuf = Buffer.from(await upstreamRes.arrayBuffer());
50004
+ res.end(resBuf);
50005
+ return true;
50006
+ }
49474
50007
  async function handleRemoteControl(req, res, path, controller) {
49475
50008
  const json3 = (status, body) => {
49476
50009
  res.writeHead(status, { "content-type": "application/json" });
@@ -50234,6 +50767,33 @@ async function handleDevices(req, res, path, registry, hostRegistry, joinTokens)
50234
50767
  }
50235
50768
  return true;
50236
50769
  }
50770
+ const deviceSessionPromptMatch = path.match(/^\/devices\/([^/]+)\/sessions\/([^/]+)\/prompt$/);
50771
+ if (deviceSessionPromptMatch && req.method === "POST") {
50772
+ if (!hostRegistry) {
50773
+ json3(404, { error: "no_host_registry", message: "this daemon has no host registry wired" });
50774
+ return true;
50775
+ }
50776
+ const target = decodeURIComponent(deviceSessionPromptMatch[1] ?? "");
50777
+ const sessionId = decodeURIComponent(deviceSessionPromptMatch[2] ?? "");
50778
+ const body = await readJsonBody2(req);
50779
+ const b = body && typeof body === "object" ? body : {};
50780
+ const reqUrl = req.url ?? "";
50781
+ const qs = new URLSearchParams(reqUrl.includes("?") ? reqUrl.slice(reqUrl.indexOf("?") + 1) : "");
50782
+ try {
50783
+ const result = await promptHostSession(hostRegistry, target, sessionId, {
50784
+ prompt: b["prompt"],
50785
+ interrupt: b["interrupt"] === true,
50786
+ force: b["force"] === true,
50787
+ wait: ["1", "true"].includes(qs.get("wait") ?? ""),
50788
+ ...qs.get("waitPollMs") ? { pollMs: Number(qs.get("waitPollMs")) } : {},
50789
+ ...qs.get("maxWaitMs") ? { maxWaitMs: Number(qs.get("maxWaitMs")) } : {}
50790
+ });
50791
+ json3(result.ok ? 200 : result.status ?? 400, result);
50792
+ } catch (err) {
50793
+ json3(502, { error: "prompt_forward_failed", message: err instanceof Error ? err.message : String(err) });
50794
+ }
50795
+ return true;
50796
+ }
50237
50797
  const idMatch = path.match(/^\/devices\/([^/]+)$/);
50238
50798
  if (idMatch && req.method === "PATCH") {
50239
50799
  const target = decodeURIComponent(idMatch[1] ?? "");
@@ -50280,6 +50840,22 @@ async function handleCron(req, res, path, scheduler) {
50280
50840
  json3(200, { jobs: scheduler.list() });
50281
50841
  return true;
50282
50842
  }
50843
+ if (path === "/cron/runs" && req.method === "GET") {
50844
+ const params = new URL(req.url ?? "/", "http://localhost").searchParams;
50845
+ const rawLimit = params.get("limit");
50846
+ const limit = rawLimit === null ? void 0 : Number(rawLimit);
50847
+ try {
50848
+ json3(200, scheduler.runs({
50849
+ jobId: params.get("jobId") ?? void 0,
50850
+ limit,
50851
+ cursor: params.get("cursor") ?? void 0
50852
+ }));
50853
+ } catch (err) {
50854
+ const message = err instanceof Error ? err.message : String(err);
50855
+ json3(message.includes("not found") ? 404 : 400, { error: "runs_failed", message });
50856
+ }
50857
+ return true;
50858
+ }
50283
50859
  if (path === "/cron" && req.method === "POST") {
50284
50860
  const body = await readJsonBody2(req);
50285
50861
  if (!body || typeof body !== "object") {
@@ -50301,6 +50877,7 @@ async function handleCron(req, res, path, scheduler) {
50301
50877
  const job = await scheduler.create({
50302
50878
  label: typeof b.label === "string" ? b.label : void 0,
50303
50879
  schedule,
50880
+ timezone: typeof b.timezone === "string" ? b.timezone : void 0,
50304
50881
  recurring: typeof b.recurring === "boolean" ? b.recurring : true,
50305
50882
  action
50306
50883
  });
@@ -50348,6 +50925,27 @@ async function handleCron(req, res, path, scheduler) {
50348
50925
  }
50349
50926
  return true;
50350
50927
  }
50928
+ if (req.method === "PATCH") {
50929
+ const body = await readJsonBody2(req);
50930
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
50931
+ json3(400, { error: "invalid_body" });
50932
+ return true;
50933
+ }
50934
+ const b = body;
50935
+ const allowed = /* @__PURE__ */ new Set(["schedule", "label", "timezone", "recurring", "active", "action"]);
50936
+ if (Object.keys(b).some((key) => !allowed.has(key)) || b.schedule !== void 0 && (typeof b.schedule !== "string" || !b.schedule) || b.label !== void 0 && b.label !== null && typeof b.label !== "string" || b.timezone !== void 0 && b.timezone !== null && typeof b.timezone !== "string" || b.recurring !== void 0 && typeof b.recurring !== "boolean" || b.active !== void 0 && typeof b.active !== "boolean" || b.action !== void 0 && (!b.action || typeof b.action !== "object" || Array.isArray(b.action))) {
50937
+ json3(400, { error: "invalid_body" });
50938
+ return true;
50939
+ }
50940
+ try {
50941
+ const job = await scheduler.update(jobId, b);
50942
+ json3(200, job);
50943
+ } catch (err) {
50944
+ const message = err instanceof Error ? err.message : String(err);
50945
+ json3(message.includes("not found") ? 404 : 400, { error: "update_failed", message });
50946
+ }
50947
+ return true;
50948
+ }
50351
50949
  return false;
50352
50950
  }
50353
50951
  async function handleRoutineDefs(req, res, path, registrar) {
@@ -55603,7 +56201,7 @@ function waitingOnKey(w) {
55603
56201
  var PR_SETTLE_SWEEP_INTERVAL_MS = 5 * 6e4;
55604
56202
  var PR_SETTLE_SWEEP_MAX_URLS = 10;
55605
56203
  function createActivityProjector(opts) {
55606
- const cache2 = /* @__PURE__ */ new Map();
56204
+ const cache3 = /* @__PURE__ */ new Map();
55607
56205
  const prStates = /* @__PURE__ */ new Map();
55608
56206
  const projectOwnerRaw = (owner) => {
55609
56207
  switch (owner) {
@@ -55664,9 +56262,9 @@ function createActivityProjector(opts) {
55664
56262
  const seen = /* @__PURE__ */ new Set();
55665
56263
  for (const rec of next) {
55666
56264
  seen.add(rec.id);
55667
- const prev = cache2.get(rec.id);
56265
+ const prev = cache3.get(rec.id);
55668
56266
  const changed = !prev || prev.state !== rec.state || prev.taskId !== rec.taskId || waitingOnKey(prev.waitingOn) !== waitingOnKey(rec.waitingOn);
55669
- cache2.set(rec.id, rec);
56267
+ cache3.set(rec.id, rec);
55670
56268
  if (changed && announce) {
55671
56269
  opts.sessionEvents.emit({
55672
56270
  type: "activity:changed",
@@ -55675,8 +56273,8 @@ function createActivityProjector(opts) {
55675
56273
  });
55676
56274
  }
55677
56275
  }
55678
- for (const [id, rec] of cache2) {
55679
- if (OWNER_OF_SOURCE[rec.source] === owner && !seen.has(id)) cache2.delete(id);
56276
+ for (const [id, rec] of cache3) {
56277
+ if (OWNER_OF_SOURCE[rec.source] === owner && !seen.has(id)) cache3.delete(id);
55680
56278
  }
55681
56279
  };
55682
56280
  const ownersTouchedBy = (type) => {
@@ -55788,7 +56386,7 @@ function createActivityProjector(opts) {
55788
56386
  dispose() {
55789
56387
  unsubscribe();
55790
56388
  if (sweepTimer) clearInterval(sweepTimer);
55791
- cache2.clear();
56389
+ cache3.clear();
55792
56390
  prStates.clear();
55793
56391
  }
55794
56392
  };
@@ -55973,7 +56571,7 @@ function createApprovalsEngine(opts) {
55973
56571
  const record2 = approvals.get(id);
55974
56572
  return record2 ? checkExpiry(record2) : void 0;
55975
56573
  };
55976
- const request = (input, requester) => {
56574
+ const request2 = (input, requester) => {
55977
56575
  const id = `apr_${randomUUID().replace(/-/g, "").slice(0, 20)}`;
55978
56576
  const requestedChannels = input.channels && input.channels.length > 0 ? input.channels.filter((c) => enabledChannels.includes(c)) : enabledChannels;
55979
56577
  const canonicalJson2 = canonicalPayloadFor(input.payload);
@@ -56139,7 +56737,7 @@ function createApprovalsEngine(opts) {
56139
56737
  };
56140
56738
  return {
56141
56739
  enabledChannels,
56142
- request,
56740
+ request: request2,
56143
56741
  get,
56144
56742
  list,
56145
56743
  wait,
@@ -58045,127 +58643,9 @@ function registerPairingTools(server, opts) {
58045
58643
  }
58046
58644
  );
58047
58645
  }
58048
- var HOP_BY_HOP = /* @__PURE__ */ new Set(["connection", "keep-alive", "transfer-encoding", "upgrade", "host"]);
58049
- function collectHeaders(raw) {
58050
- const headers = {};
58051
- for (const [k, v] of Object.entries(raw)) {
58052
- if (v === void 0 || HOP_BY_HOP.has(k.toLowerCase())) continue;
58053
- headers[k] = Array.isArray(v) ? v.join(", ") : v;
58054
- }
58055
- return headers;
58056
- }
58057
- function isPrematureCloseError(err) {
58058
- return typeof err === "object" && err !== null && "code" in err && err.code === "ERR_STREAM_PREMATURE_CLOSE";
58059
- }
58060
- async function readBody2(req) {
58061
- const chunks = [];
58062
- for await (const chunk of req) chunks.push(chunk);
58063
- if (chunks.length === 0) return void 0;
58064
- return new Uint8Array(Buffer.concat(chunks));
58065
- }
58066
- async function startDeviceSandboxBridge(opts) {
58067
- const { hostRegistry, target } = opts;
58068
- const server = createServer((req, res) => {
58069
- void handleRequest(req, res).catch((err) => {
58070
- if (res.writableEnded) return;
58071
- if (!res.headersSent) {
58072
- res.writeHead(502, { "content-type": "application/json" });
58073
- res.end(
58074
- JSON.stringify({
58075
- error: "device_sandbox_bridge_failed",
58076
- message: err instanceof Error ? err.message : String(err)
58077
- })
58078
- );
58079
- return;
58080
- }
58081
- res.end();
58082
- });
58083
- });
58084
- async function handleRequest(req, res) {
58085
- const body = await readBody2(req);
58086
- let upstream;
58087
- try {
58088
- upstream = await hostRegistry.forwardHttpStream(target, {
58089
- method: req.method ?? "GET",
58090
- path: `/device-spawn${req.url ?? "/"}`,
58091
- headers: collectHeaders(req.headers),
58092
- ...body ? { body } : {}
58093
- });
58094
- } catch (err) {
58095
- res.writeHead(502, { "content-type": "application/json" });
58096
- res.end(
58097
- JSON.stringify({
58098
- error: "device_unreachable",
58099
- message: err instanceof Error ? err.message : String(err)
58100
- })
58101
- );
58102
- return;
58103
- }
58104
- const resHeaders = {};
58105
- for (const [k, v] of Object.entries(upstream.headers)) {
58106
- if (HOP_BY_HOP.has(k.toLowerCase()) || k.toLowerCase() === "content-length") continue;
58107
- resHeaders[k] = v;
58108
- }
58109
- res.writeHead(upstream.status, resHeaders);
58110
- try {
58111
- await pipeline(
58112
- Readable.fromWeb(upstream.body),
58113
- res
58114
- );
58115
- } catch (err) {
58116
- if (!isPrematureCloseError(err)) throw err;
58117
- }
58118
- }
58119
- const port = await getFreePort2();
58120
- await new Promise((resolve41, reject) => {
58121
- server.once("error", reject);
58122
- server.listen(port, "127.0.0.1", () => resolve41());
58123
- });
58124
- return {
58125
- mcpUrl: `http://127.0.0.1:${port}/mcp`,
58126
- async close() {
58127
- await new Promise((resolve41) => server.close(() => resolve41()));
58128
- }
58129
- };
58130
- }
58131
- async function getFreePort2() {
58132
- return new Promise((resolve41, reject) => {
58133
- const srv = createServer$1();
58134
- srv.once("error", reject);
58135
- srv.listen(0, "127.0.0.1", () => {
58136
- const address = srv.address();
58137
- if (address === null || typeof address === "string") {
58138
- srv.close(() => reject(new Error("failed to allocate a free port")));
58139
- return;
58140
- }
58141
- const { port } = address;
58142
- srv.close(() => resolve41(port));
58143
- });
58144
- });
58145
- }
58146
58646
 
58147
- // src/sandbox-providers/device.ts
58148
- function createDeviceSandboxProvider(target, hostRegistry) {
58149
- return {
58150
- async boot(_spec, _opts) {
58151
- const hosts = await hostRegistry.list();
58152
- const known = hosts.some((h) => h.fingerprint === target || h.name === target);
58153
- if (!known) {
58154
- throw new Error(
58155
- `device sandbox: no paired host device matches "${target}" \u2014 run \`agentproto devices add <offer-url>\` here first (the offer must come from \`agentproto pair offer --host\` run ON that device). Check \`agentproto devices list\` for known devices.`
58156
- );
58157
- }
58158
- const bridge = await startDeviceSandboxBridge({ hostRegistry, target });
58159
- return {
58160
- mcpUrl: bridge.mcpUrl,
58161
- sandboxId: `device-${target}-${randomUUID()}`,
58162
- async stop() {
58163
- await bridge.close();
58164
- }
58165
- };
58166
- }
58167
- };
58168
- }
58647
+ // src/index.ts
58648
+ init_device();
58169
58649
  function text9(value) {
58170
58650
  return {
58171
58651
  content: [
@@ -58177,7 +58657,7 @@ function text9(value) {
58177
58657
  };
58178
58658
  }
58179
58659
  function registerDeviceTools(server, opts) {
58180
- const { registry, joinTokens } = opts;
58660
+ const { registry, hosts: registryHosts, joinTokens } = opts;
58181
58661
  server.tool(
58182
58662
  "device_list",
58183
58663
  "List devices known to this daemon: fingerprint, name, role (client|host), kind (browser|cli|daemon, best-effort from the client's self-reported name; always daemon for a host), rendezvous, createdAt, lastSeen, scope (host, when granted via `pair offer --host`), online (a channel/forward is active right now or a recent contact succeeded), and for hosts lastProbeAt/lastError (last contact attempt and why it failed). Joined CI hosts that are gone (said goodbye, or unreachable past the TTL) are hidden unless includeEnded is set; a manually added host unreachable past the TTL shows stale: true. Read-only.",
@@ -58259,6 +58739,37 @@ function registerDeviceTools(server, opts) {
58259
58739
  }
58260
58740
  }
58261
58741
  );
58742
+ if (registryHosts) {
58743
+ server.tool(
58744
+ "device_prompt",
58745
+ "Send a prompt (a follow-up turn) to a session on a registered HOST device \u2014 the write counterpart of device_sessions, forwarded live over the host's E2E channel. Queueing rules are identical to a local agent_prompt: fire-and-forget by default, queued behind the session's in-flight turn, `interrupt`/`force` to redirect or jump the queue. wait: true blocks until the prompted turn drains. Requires the HOST daemon to have opted in (agentproto devices allow-spawn on) over a host-scoped pairing \u2014 the same gate as device spawn; refused for a plain remote-control pairing or an unknown target.",
58746
+ {
58747
+ target: z.string().describe("The host's fingerprint or name (see device_list)."),
58748
+ sessionId: z.string().min(1).describe("The session id ON the host to prompt."),
58749
+ prompt: z.union([z.string(), z.record(z.string(), z.unknown()), z.array(z.record(z.string(), z.unknown()))]).describe("Non-empty string, a content block, or an array of content blocks."),
58750
+ wait: z.boolean().optional().describe("Block until the prompted turn drains. Default false."),
58751
+ interrupt: z.boolean().optional().describe("Mid-turn: redirect instead of queueing (agent_prompt's interrupt). Default false."),
58752
+ force: z.boolean().optional().describe("Mid-turn: jump the FRONT of the FIFO (agent_prompt's force). Default false."),
58753
+ pollMs: z.number().int().min(10).max(1e4).optional().describe("Poll cadence while waiting, ms. Default 1000. Each poll is a fresh E2E dial."),
58754
+ maxWaitMs: z.number().int().positive().optional().describe("Give up waiting after this long (ms). Default: wait forever.")
58755
+ },
58756
+ async ({ target, sessionId, prompt, wait, interrupt, force, pollMs, maxWaitMs }) => {
58757
+ try {
58758
+ const result = await promptHostSession(registryHosts, target, sessionId, {
58759
+ prompt,
58760
+ ...wait ? { wait: true } : {},
58761
+ ...interrupt ? { interrupt: true } : {},
58762
+ ...force ? { force: true } : {},
58763
+ ...pollMs !== void 0 ? { pollMs } : {},
58764
+ ...maxWaitMs !== void 0 ? { maxWaitMs } : {}
58765
+ });
58766
+ return text9(result);
58767
+ } catch (err) {
58768
+ return text9({ ok: false, message: err instanceof Error ? err.message : String(err) });
58769
+ }
58770
+ }
58771
+ );
58772
+ }
58262
58773
  if (joinTokens) {
58263
58774
  server.tool(
58264
58775
  "join_token_create",
@@ -59926,6 +60437,440 @@ function createWorkspaceFs(opts) {
59926
60437
 
59927
60438
  // src/index.ts
59928
60439
  init_role();
60440
+ var WHSEC_PREFIX = "whsec_";
60441
+ function encodeWhsecSecret(raw) {
60442
+ return WHSEC_PREFIX + Buffer.from(raw).toString("base64");
60443
+ }
60444
+ function decodeWhsecSecret(secret) {
60445
+ if (typeof secret !== "string" || !secret.startsWith(WHSEC_PREFIX)) return null;
60446
+ const encoded = secret.slice(WHSEC_PREFIX.length);
60447
+ if (encoded.length === 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(encoded) || encoded.length % 4 !== 0) {
60448
+ return null;
60449
+ }
60450
+ let key;
60451
+ try {
60452
+ key = Buffer.from(encoded, "base64");
60453
+ } catch {
60454
+ return null;
60455
+ }
60456
+ if (key.length < 24 || key.length > 64) return null;
60457
+ return key;
60458
+ }
60459
+ function signWebhook(input) {
60460
+ if (input.secrets.length === 0) throw new Error("signWebhook: at least one secret is required");
60461
+ const prefix = Buffer.from(`${input.msgId}.${input.timestamp}.`, "utf8");
60462
+ const payload = Buffer.from(input.payload.buffer, input.payload.byteOffset, input.payload.byteLength);
60463
+ const segments = input.secrets.map((secret) => {
60464
+ const key = decodeWhsecSecret(secret);
60465
+ if (!key) throw new Error(`signWebhook: invalid webhooks secret format: ${redactPrefix(secret)}`);
60466
+ const mac = createHmac("sha256", key).update(prefix).update(payload).digest();
60467
+ return `v1,${mac.toString("base64")}`;
60468
+ });
60469
+ return {
60470
+ "webhook-id": input.msgId,
60471
+ "webhook-timestamp": String(input.timestamp),
60472
+ "webhook-signature": segments.join(" ")
60473
+ };
60474
+ }
60475
+ function redactPrefix(secret) {
60476
+ return `${WHSEC_PREFIX}<${secret.length - WHSEC_PREFIX.length} b64 chars, invalid>`;
60477
+ }
60478
+ var SsrfFetchError = class extends Error {
60479
+ constructor(reason, message) {
60480
+ super(message);
60481
+ this.reason = reason;
60482
+ this.name = "SsrfFetchError";
60483
+ }
60484
+ reason;
60485
+ };
60486
+ var MAX_REQUEST_BODY_BYTES = 262144;
60487
+ var MAX_RESPONSE_DIAGNOSTIC_BYTES = 4096;
60488
+ function isPubliclyRoutable(ipRaw) {
60489
+ const ip = ipRaw.includes("%") ? ipRaw.slice(0, ipRaw.indexOf("%")) : ipRaw;
60490
+ const v4 = parseIpv4(ip);
60491
+ if (v4) return ipv4Public(v4[0], v4[1], v4[2], v4[3]);
60492
+ const v6 = parseIpv6Bytes(ip);
60493
+ if (v6) return ipv6Public(v6);
60494
+ return false;
60495
+ }
60496
+ function ipv4Public(a, b, c, d) {
60497
+ if (a === 0) return false;
60498
+ if (a === 10) return false;
60499
+ if (a === 127) return false;
60500
+ if (a === 100 && b >= 64 && b <= 127) return false;
60501
+ if (a === 169 && b === 254) return false;
60502
+ if (a === 172 && b >= 16 && b <= 31) return false;
60503
+ if (a === 192 && b === 168) return false;
60504
+ if (a === 192 && b === 0 && c === 0) return false;
60505
+ if (a === 192 && b === 0 && c === 2) return false;
60506
+ if (a === 198 && b === 51 && c === 100) return false;
60507
+ if (a === 198 && (b === 18 || b === 19)) return false;
60508
+ if (a === 203 && b === 0 && c === 113) return false;
60509
+ if (a >= 224) return false;
60510
+ return true;
60511
+ }
60512
+ function ipv6Public(v6) {
60513
+ if (v6.mapped) return ipv4Public(v6.mapped[0], v6.mapped[1], v6.mapped[2], v6.mapped[3]);
60514
+ const b = v6.bytes;
60515
+ const v = new DataView(b.buffer, b.byteOffset, b.byteLength);
60516
+ const b0 = v.getUint8(0);
60517
+ const b1 = v.getUint8(1);
60518
+ const b2 = v.getUint8(2);
60519
+ const b3 = v.getUint8(3);
60520
+ const b15 = v.getUint8(15);
60521
+ const tailZero = b.slice(1, 15).every((x) => x === 0);
60522
+ if (b.every((x) => x === 0)) return false;
60523
+ if (tailZero && b0 === 0 && b15 === 1) return false;
60524
+ if ((b0 & 254) === 252) return false;
60525
+ if (b0 === 254 && (b1 & 192) === 128) return false;
60526
+ if (b0 === 255) return false;
60527
+ if (b0 === 32 && b1 === 1 && b2 === 13 && b3 === 184) return false;
60528
+ return true;
60529
+ }
60530
+ function parseIpv4(ip) {
60531
+ const parts = ip.split(".");
60532
+ if (parts.length !== 4) return null;
60533
+ const o = [];
60534
+ for (const part of parts) {
60535
+ if (!/^(?:0|[1-9]\d{0,2})$/.test(part)) return null;
60536
+ const n = Number(part);
60537
+ if (n > 255) return null;
60538
+ o.push(n);
60539
+ }
60540
+ const first = o[0];
60541
+ const second = o[1];
60542
+ const third = o[2];
60543
+ const fourth = o[3];
60544
+ if (first === void 0 || second === void 0 || third === void 0 || fourth === void 0) return null;
60545
+ return [first, second, third, fourth];
60546
+ }
60547
+ function parseIpv6Bytes(rawIp) {
60548
+ const ip = rawIp.replace(/^\[|\]$/g, "");
60549
+ if (!ip.includes(":")) return null;
60550
+ if (ip.lastIndexOf("::") !== ip.indexOf("::")) return null;
60551
+ const v4Match = ip.match(/^(?:.*:)(\d{1,3}(?:\.\d{1,3}){3})$/);
60552
+ const compressed = ip.includes("::");
60553
+ if (compressed) {
60554
+ const halves = ip.split("::");
60555
+ const before = halves[0] ?? "";
60556
+ const after = halves[1] ?? "";
60557
+ const head = before === "" ? [] : before.split(":");
60558
+ if (head.some((g) => !isHexGroup(g))) return null;
60559
+ const tail = after === "" ? [] : after.replace(/(\d{1,3}(?:\.\d{1,3}){3})$/, "").split(":").filter((g) => g !== "");
60560
+ if (tail.some((g) => !isHexGroup(g))) return null;
60561
+ return fromGroups(head, tail, v4Match?.[1]);
60562
+ }
60563
+ const all = ip.split(":");
60564
+ const v4 = v4Match ? [all[all.length - 1]] : [];
60565
+ const hexGroups = all.slice(0, all.length - (v4Match ? 1 : 0));
60566
+ if (all.length !== (v4Match ? 6 : 8) + (v4Match ? 1 : 0)) return null;
60567
+ if (hexGroups.some((g) => !isHexGroup(g))) return null;
60568
+ return fromGroups(hexGroups, [], v4 ? v4[0] : void 0);
60569
+ }
60570
+ function isHexGroup(g) {
60571
+ return /^[\da-fA-F]{1,4}$/.test(g);
60572
+ }
60573
+ function fromGroups(head, tail, v4) {
60574
+ if (head.length + tail.length > (v4 ? 6 : 8)) return null;
60575
+ const bytes = new Uint8Array(16);
60576
+ const writeGroup = (g, offset) => {
60577
+ if (g === void 0) return false;
60578
+ const v = parseInt(g, 16);
60579
+ bytes[offset] = v >> 8;
60580
+ bytes[offset + 1] = v & 255;
60581
+ return true;
60582
+ };
60583
+ head.forEach((g, i) => writeGroup(g, i * 2));
60584
+ const tailStart = 16 - tail.length * 2 - (v4 ? 4 : 0);
60585
+ tail.forEach((g, i) => writeGroup(g, tailStart + i * 2));
60586
+ if (v4) {
60587
+ const o = parseIpv4(v4);
60588
+ if (!o) return null;
60589
+ bytes[12] = o[0];
60590
+ bytes[13] = o[1];
60591
+ bytes[14] = o[2];
60592
+ bytes[15] = o[3];
60593
+ const mapped = bytes.slice(0, 10).every((x) => x === 0) && bytes[10] === 255 && bytes[11] === 255;
60594
+ return { bytes, mapped: mapped ? o : void 0 };
60595
+ }
60596
+ return { bytes };
60597
+ }
60598
+ async function resolveHost(hostname6, resolvers) {
60599
+ if (parseIpv4(hostname6)) return [hostname6];
60600
+ const bracketless = hostname6.replace(/^\[|\]$/g, "");
60601
+ if (bracketless.includes(":")) return [bracketless];
60602
+ if (resolvers.lookup) return (await resolvers.lookup(bracketless)).map((r) => r.address);
60603
+ const found2 = await lookup(bracketless, { all: true, verbatim: true });
60604
+ return found2.map((f) => f.address);
60605
+ }
60606
+ var testDispatcher = null;
60607
+ function setEgressDispatcherForTests(dispatcher) {
60608
+ testDispatcher = dispatcher;
60609
+ }
60610
+ async function ssrfFetch(url, init, io = {}) {
60611
+ let parsed;
60612
+ try {
60613
+ parsed = new URL(url);
60614
+ } catch {
60615
+ throw new SsrfFetchError("non_https", "unparseable callback URL");
60616
+ }
60617
+ if (parsed.protocol !== "https:") {
60618
+ throw new SsrfFetchError("non_https", `scheme ${parsed.protocol} \u2014 https only`);
60619
+ }
60620
+ if ((init.body?.byteLength ?? 0) > MAX_REQUEST_BODY_BYTES) {
60621
+ throw new SsrfFetchError("connect", `request body exceeds the ${MAX_REQUEST_BODY_BYTES}-byte clamp`);
60622
+ }
60623
+ if (testDispatcher) {
60624
+ const literal = parseIpv4(parsed.hostname);
60625
+ if (literal && !ipv4Public(literal[0], literal[1], literal[2], literal[3])) {
60626
+ throw new SsrfFetchError("private_target", `literal non-public address ${parsed.hostname}`);
60627
+ }
60628
+ return await testDispatcher({ url, headers: init.headers ?? {}, body: init.body, timeoutMs: init.timeoutMs });
60629
+ }
60630
+ const candidates = await resolveHost(parsed.hostname, io.resolvers ?? {});
60631
+ if (candidates.length === 0) {
60632
+ throw new SsrfFetchError("connect", `no addresses resolved for ${parsed.hostname}`);
60633
+ }
60634
+ const rejected = candidates.find((ip) => !isPubliclyRoutable(ip));
60635
+ if (rejected !== void 0) {
60636
+ throw new SsrfFetchError("private_target", `${parsed.hostname} resolves to non-public address ${rejected}`);
60637
+ }
60638
+ const connectIp = candidates[0] ?? candidates[candidates.length - 1];
60639
+ if (connectIp === void 0) throw new SsrfFetchError("connect", "no address available for connect");
60640
+ const port = parsed.port ? Number(parsed.port) : 443;
60641
+ return await rawPost({
60642
+ connectIp,
60643
+ servername: parsed.hostname,
60644
+ port,
60645
+ path: `${parsed.pathname}${parsed.search}`,
60646
+ headers: init.headers ?? {},
60647
+ body: init.body,
60648
+ timeoutMs: init.timeoutMs
60649
+ });
60650
+ }
60651
+ function rawPost(args) {
60652
+ return new Promise((resolve41, reject) => {
60653
+ const req = request({
60654
+ // Connect to the PRE-VALIDATED address; keep the original hostname in
60655
+ // `servername` so SNI and the certificate check never loosen.
60656
+ host: args.connectIp,
60657
+ port: args.port,
60658
+ path: args.path,
60659
+ method: "POST",
60660
+ servername: args.servername,
60661
+ headers: args.headers,
60662
+ rejectUnauthorized: true
60663
+ });
60664
+ let settled = false;
60665
+ const fail3 = (reason, message) => {
60666
+ if (settled) return;
60667
+ settled = true;
60668
+ req.destroy();
60669
+ reject(new SsrfFetchError(reason, message));
60670
+ };
60671
+ const ok3 = (status, body) => {
60672
+ if (settled) return;
60673
+ settled = true;
60674
+ resolve41({ status, body });
60675
+ };
60676
+ req.setTimeout(args.timeoutMs, () => fail3("timeout", `no response within ${args.timeoutMs}ms`));
60677
+ req.on("error", (err) => {
60678
+ fail3("connect", err.message);
60679
+ });
60680
+ req.on("response", (res) => {
60681
+ const status = res.statusCode ?? 0;
60682
+ let diags = Buffer.alloc(0);
60683
+ res.on("data", (chunk) => {
60684
+ if (diags.length < MAX_RESPONSE_DIAGNOSTIC_BYTES) {
60685
+ diags = Buffer.concat([diags, chunk.subarray(0, MAX_RESPONSE_DIAGNOSTIC_BYTES - diags.length)]);
60686
+ }
60687
+ if (diags.length >= MAX_RESPONSE_DIAGNOSTIC_BYTES) res.destroy();
60688
+ });
60689
+ res.on("end", () => {
60690
+ if (status >= 300 && status < 400) {
60691
+ const location = res.headers["location"];
60692
+ if (typeof location === "string" && !new URL(location, `https://${args.servername}`).protocol.startsWith("https")) {
60693
+ fail3("redirect", `redirect target ${location} is not https \u2014 never chased`);
60694
+ return;
60695
+ }
60696
+ }
60697
+ ok3(status, diags.toString("utf8"));
60698
+ });
60699
+ res.on("error", (err) => {
60700
+ fail3("connect", err.message);
60701
+ });
60702
+ });
60703
+ if (args.body) req.write(Buffer.from(args.body));
60704
+ req.end();
60705
+ });
60706
+ }
60707
+ function challengeReasonForSsrf(reason) {
60708
+ if (reason === "non_https") return "non_https";
60709
+ if (reason === "private_target" || reason === "redirect") return "ssrf_blocked";
60710
+ return "timeout";
60711
+ }
60712
+ var CHALLENGE_TTL_MS = 10 * 60 * 1e3;
60713
+ var encoder = new TextEncoder();
60714
+ var cache2 = /* @__PURE__ */ new Map();
60715
+ function challengeCacheSize() {
60716
+ return cache2.size;
60717
+ }
60718
+ function resetChallengeCacheForTests() {
60719
+ cache2.clear();
60720
+ }
60721
+ function normalizeCallbackUrl(url) {
60722
+ try {
60723
+ const u = new URL(url);
60724
+ const port = u.port === "443" ? "" : `:${u.port}`;
60725
+ return `${u.protocol.toLowerCase()}//${u.hostname.toLowerCase()}${port}${u.pathname}${u.search}`;
60726
+ } catch {
60727
+ return null;
60728
+ }
60729
+ }
60730
+ function hashSecret(secret) {
60731
+ return createHash("sha256").update(secret, "utf8").digest("hex");
60732
+ }
60733
+ function constantTimeEq(a, b) {
60734
+ const da = createHash("sha256").update(a, "utf8").digest();
60735
+ const db = createHash("sha256").update(b, "utf8").digest();
60736
+ return timingSafeEqual(da, db);
60737
+ }
60738
+ async function verifyCallback(input, deps2 = {}) {
60739
+ const now = deps2.now ?? (() => Date.now());
60740
+ const normalized = normalizeCallbackUrl(input.url);
60741
+ if (normalized === null || !normalized.startsWith("https://")) {
60742
+ return { ok: false, reason: "non_https", detail: "callback URL must be an https URL" };
60743
+ }
60744
+ if (decodeWhsecSecret(input.secret) === null) {
60745
+ return {
60746
+ ok: false,
60747
+ reason: "challenge_failed",
60748
+ detail: "invalid secret format \u2014 must be whsec_ + base64 decoding to 24..64 bytes (the caller validates before this; a bad secret is never cached)"
60749
+ };
60750
+ }
60751
+ const cacheKey = `${input.principal}|${normalized}|${hashSecret(input.secret)}`;
60752
+ const cached = cache2.get(cacheKey);
60753
+ if (cached && cached.expiresAt > now()) {
60754
+ return { ok: true, verificationBytes: cached.requestBytes };
60755
+ }
60756
+ if (cached) cache2.delete(cacheKey);
60757
+ const challenge = randomBytes(32).toString("hex");
60758
+ const requestBytes = encoder.encode(JSON.stringify({ type: "verification", challenge }));
60759
+ const msgId = `msg_verification_${randomBytes(12).toString("hex")}`;
60760
+ const timestamp = Math.floor(now() / 1e3);
60761
+ let headers;
60762
+ try {
60763
+ const signed = signWebhook({ msgId, timestamp, payload: requestBytes, secrets: [input.secret] });
60764
+ headers = {
60765
+ ...signed,
60766
+ "content-type": "application/json",
60767
+ "X-MCP-Subscription-Id": input.subscriptionId
60768
+ };
60769
+ } catch (err) {
60770
+ return { ok: false, reason: "challenge_failed", detail: `signing failed: ${err.message}` };
60771
+ }
60772
+ let response;
60773
+ try {
60774
+ response = await (deps2.fetch ?? ssrfFetch)(input.url, {
60775
+ method: "POST",
60776
+ headers,
60777
+ body: requestBytes,
60778
+ timeoutMs: 1e4
60779
+ // default challenge timeout (delivery uses 15_000)
60780
+ });
60781
+ } catch (err) {
60782
+ if (err instanceof SsrfFetchError) {
60783
+ return { ok: false, reason: challengeReasonForSsrf(err.reason), detail: `${err.reason}: ${err.message}` };
60784
+ }
60785
+ return { ok: false, reason: "timeout", detail: err.message };
60786
+ }
60787
+ if (response.status < 200 || response.status > 299) {
60788
+ return { ok: false, reason: "non_2xx", detail: `callback responded with status ${response.status}` };
60789
+ }
60790
+ let parsedBody;
60791
+ try {
60792
+ parsedBody = JSON.parse(response.body);
60793
+ } catch {
60794
+ return { ok: false, reason: "challenge_failed", detail: "response body is not JSON" };
60795
+ }
60796
+ const echoed = parsedBody?.challenge;
60797
+ if (typeof echoed !== "string" || !constantTimeEq(echoed, challenge)) {
60798
+ return { ok: false, reason: "challenge_failed", detail: "response did not echo the fresh challenge" };
60799
+ }
60800
+ cache2.set(cacheKey, { requestBytes, expiresAt: now() + CHALLENGE_TTL_MS });
60801
+ return { ok: true, verificationBytes: requestBytes };
60802
+ }
60803
+
60804
+ // src/webhook-egress/delivery.ts
60805
+ var CLAMP_LIMIT_BYTES = 262144;
60806
+ var MAX_ATTEMPTS = 5;
60807
+ var BACKOFF_BASE_MS = 250;
60808
+ var BACKOFF_CAP_MS = 4e3;
60809
+ function deliveryBackoffDelay(attempt) {
60810
+ return Math.min(BACKOFF_BASE_MS * 2 ** attempt, BACKOFF_CAP_MS);
60811
+ }
60812
+ var TERMINAL_NO_RETRY_STATUSES = /* @__PURE__ */ new Set([410, 413]);
60813
+ function serializeEnvelope(envelope) {
60814
+ return new TextEncoder().encode(JSON.stringify(envelope));
60815
+ }
60816
+ function envelopeClampedBytes(event) {
60817
+ let bytes = serializeEnvelope(event);
60818
+ if (bytes.byteLength <= CLAMP_LIMIT_BYTES) return { bytes, clamped: false };
60819
+ const data = event.data;
60820
+ const clampedEvent = {
60821
+ ...event,
60822
+ data: {
60823
+ summary: `Payload exceeded the ${CLAMP_LIMIT_BYTES}-byte wire clamp and was truncated`,
60824
+ subject: data && typeof data === "object" && "subject" in data ? data.subject : null
60825
+ },
60826
+ truncated: true
60827
+ };
60828
+ bytes = serializeEnvelope(clampedEvent);
60829
+ return { bytes, clamped: true };
60830
+ }
60831
+ async function deliverEventEnvelope(replay, event, deps2 = {}) {
60832
+ const now = deps2.now ?? (() => Date.now());
60833
+ const sleep3 = deps2.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
60834
+ const fetcher = deps2.fetch ?? ((url, init) => ssrfFetch(url, init));
60835
+ const { bytes: payloadBytes } = envelopeClampedBytes(event);
60836
+ const delivery = { attempts: 0 };
60837
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
60838
+ if (attempt > 0) await sleep3(deliveryBackoffDelay(attempt));
60839
+ delivery.attempts += 1;
60840
+ delivery.lastAt = new Date(now()).toISOString();
60841
+ const timestamp = Math.floor(now() / 1e3);
60842
+ let signed;
60843
+ try {
60844
+ signed = signWebhook({ msgId: event.eventId, timestamp, payload: payloadBytes, secrets: replay.secrets });
60845
+ } catch (err) {
60846
+ delivery.lastError = `signing failed: ${err.message}`;
60847
+ return { ok: false, reason: "invalid_secret", delivery };
60848
+ }
60849
+ let response;
60850
+ try {
60851
+ response = await fetcher(replay.callbackUrl, {
60852
+ method: "POST",
60853
+ headers: { ...signed, "content-type": "application/json" },
60854
+ body: payloadBytes,
60855
+ timeoutMs: 15e3
60856
+ // delivery default (challenge uses 10_000)
60857
+ });
60858
+ } catch (err) {
60859
+ delivery.lastError = err instanceof Error ? err.message : String(err);
60860
+ if (attempt < MAX_ATTEMPTS - 1) continue;
60861
+ return { ok: false, reason: "network", delivery };
60862
+ }
60863
+ if (response.status >= 200 && response.status <= 299) {
60864
+ return { ok: true, delivery };
60865
+ }
60866
+ if (TERMINAL_NO_RETRY_STATUSES.has(response.status)) {
60867
+ delivery.lastError = `terminal status ${response.status}`;
60868
+ return { ok: false, reason: `http_${response.status}`, delivery };
60869
+ }
60870
+ delivery.lastError = `retryable status ${response.status}`;
60871
+ }
60872
+ return { ok: false, reason: "retries_exhausted", delivery };
60873
+ }
59929
60874
  async function hasNamedLlmEndpoints() {
59930
60875
  const override = process.env.LLM_ENDPOINT_ENDPOINTS_FILE?.trim();
59931
60876
  const path = override && override.length > 0 ? override : join(homedir(), ".agentproto", "llm-endpoints.json");
@@ -60186,13 +61131,13 @@ function createJoinTokenRegistry(deps2) {
60186
61131
  } catch (err) {
60187
61132
  if (signal.aborted) break;
60188
61133
  log(`[join-tokens] "${record2.name}": dial failed: ${errMsg4(err)}`);
60189
- await sleep(backoff, signal);
61134
+ await sleep2(backoff, signal);
60190
61135
  backoff = Math.min(backoff * 2, reconnectMaxMs);
60191
61136
  continue;
60192
61137
  }
60193
61138
  if (!sink.isOpen) {
60194
61139
  if (signal.aborted) break;
60195
- await sleep(backoff, signal);
61140
+ await sleep2(backoff, signal);
60196
61141
  backoff = Math.min(backoff * 2, reconnectMaxMs);
60197
61142
  continue;
60198
61143
  }
@@ -60221,12 +61166,17 @@ function createJoinTokenRegistry(deps2) {
60221
61166
  capturedSession = result.session;
60222
61167
  return { reply: encodePairingMessage(result.reply), keys: result.session };
60223
61168
  },
60224
- { timeoutMs: handshakeTimeoutMs }
61169
+ {
61170
+ timeoutMs: handshakeTimeoutMs,
61171
+ // Post-handshake close reason (BOOTSTRAP P4 item 2) — same
61172
+ // diagnostic hook the pairing-host accept-loop wires.
61173
+ log: (line) => log(`[join-tokens] "${record2.name}": ${line}`)
61174
+ }
60225
61175
  );
60226
61176
  } catch (err) {
60227
61177
  if (signal.aborted) break;
60228
61178
  log(`[join-tokens] "${record2.name}": handshake failed: ${errMsg4(err)}`);
60229
- await sleep(backoff, signal);
61179
+ await sleep2(backoff, signal);
60230
61180
  backoff = Math.min(backoff * 2, reconnectMaxMs);
60231
61181
  continue;
60232
61182
  }
@@ -60252,7 +61202,7 @@ function createJoinTokenRegistry(deps2) {
60252
61202
  }
60253
61203
  return { create, list, revoke, startAutoconnect, shutdown };
60254
61204
  }
60255
- function sleep(ms, signal) {
61205
+ function sleep2(ms, signal) {
60256
61206
  return new Promise((resolve41) => {
60257
61207
  if (signal.aborted) return resolve41();
60258
61208
  const timer = setTimeout(resolve41, ms);
@@ -60988,6 +61938,7 @@ async function createGateway(opts) {
60988
61938
  registerPairingTools(server, { registry: opts.pairingRegistry });
60989
61939
  registerDeviceTools(server, {
60990
61940
  registry: createDeviceRegistry(opts.pairingRegistry, opts.hostRegistry),
61941
+ ...opts.hostRegistry ? { hosts: opts.hostRegistry } : {},
60991
61942
  ...opts.joinTokens ? { joinTokens: opts.joinTokens } : {}
60992
61943
  });
60993
61944
  }
@@ -61614,6 +62565,6 @@ var export_readPairingsSnapshot = pairing_registry_exports.readPairingsSnapshot;
61614
62565
  var export_removeProviderKey = providers_store_exports.removeProviderKey;
61615
62566
  var export_setProviderKey = providers_store_exports.setProviderKey;
61616
62567
 
61617
- export { AnthropicRemainingQuotaReader, AuthResolutionError, BUCKETS_ROOT, CLAUDE_CODE_OAUTH_SOURCE, CONTINUE_PROMPT_SOURCE, DEAD_SESSION_STATUSES, DEFAULT_ALWAYS_ON_TOOLS, DEFAULT_BUCKET, DEFAULT_CONTINUE_PROMPT, DEFAULT_HOST_LOAD_BUDGET_MS, DEFAULT_HOST_LOAD_THRESHOLDS, DEFAULT_MODEL_ROLES, DEFAULT_ORCHESTRATOR_TOOLS, DEFAULT_WORKTREE_ISOLATION, DEFAULT_WORKTREE_PROVISION_CONCURRENCY, GC_REAPABLE_STATES, HOSTS_VERSION, HOST_SCOPE_HEADER, HarnessPresetValidationError, INBOUND_PROVIDERS, JOIN_TOKENS_VERSION, LEGACY_SESSIONS_FILE, MAX_AUTO_CONTINUE_ATTEMPTS, MAX_MESSAGE_DATA_BYTES, MCP_APP_BRIDGE_SCRIPT, MESSAGE_KINDS, MESSAGE_PREAMBLE, MESSAGE_TAG, MESSAGE_URGENCIES, MODEL_ROLE_REF_PREFIX, export_PAIRINGS_VERSION as PAIRINGS_VERSION, POSTURE_NATIVE_ALIASES, POSTURE_PREAMBLES, export_PROVIDER_ENV_VARS as PROVIDER_ENV_VARS, PrLookupError, ResumeDisabledError, SESSION_END_REASONS, SESSION_ID_ENV, SIGN_NAMESPACE, SubscriptionSourceError, TASK_STATUSES, WORKSPACE_SLUG_ENV, WORKTREE_ISOLATION_ENV, WORKTREE_PROVISION_CONCURRENCY_ENV, activityCounts, addHarnessPreset, allowedSignersLine, appendConversationRecord, attachSandbox, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogModels, buildCatalogProviderModels, buildHostLoadReport, buildMcpConfigSnippet, buildRouteAwareLaunchConfig, canonicalForModeId, claudeProjectSlug, collectGcCandidates, collectHostSample, compactOutcome, composeMode, composeSessionObservers, composedFromRangeSha, computeHostWarnings, continueInterruptedSessions, continueSkipReason, conversationIndexPath, createActivityProjector, createDaemonReviewerHost, createDefaultHostProbes, createDeviceRegistry, createFileStepCache, createGateway, createHostLoadService, createHostRegistry, createInboundEndpointStore, createJoinTokenRegistry, createOrchestratorInjector, createOrchestratorMcpServerFactory, export_createPairingRegistry as createPairingRegistry, createPrProvenanceReconciler, createProcessStatsService, createReviewLaneExecutor, createReviewLedger, createReviewPackLoader, createReviewRunner, createScopeTokenRegistry, createSessionMessage, createSupervisorTaskGateRunner, createTaskLedger, createWorkspaceBrainSubscriber, createWorkspaceBrains, createWorkspaceFs, credentialFingerprint, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, decomposeMode, defaultBrowserAdapterIds, defaultBrowserAdapterResolution, defaultReviewKeysDir, defaultReviewLedgerRoot, defaultReviewPackCacheDir, defineBraveSearchHttpDriver, defineSerperHttpDriver, deleteUserPreset, deriveSessionOutcome, deriveSessionUsage, describeOwner, enrichRollupWithProviderQuota, enrichWithRemainingQuota, ensureReviewSigningKey, escapeHumanPrompt, escapeMessageBody, evaluateCostBudget, execGh, extractHelpSection, fetchPrStatus, fileConversationStore, fileExists3 as fileExists, filterActivities, findComposeCandidate, findConversationRecord, findNativeMode, findPrForCommit, formatToolCall, formatToolResult, getDefaultHarnessPreset, getHarnessPreset, getHostLoadService, getMcpCredentialDeps, getProcessStatsService, export_getProviderKey as getProviderKey, getToolHelp, getUserPreset, githubRepoOf, harnessPresetsPath, hostPlaceholders, injectMcpAppBridge, export_injectProviderKeysIntoEnv as injectProviderKeysIntoEnv, isAgentCliAuthConfigured, isClosedTaskStatus, isKnownSessionEndReason, isMessageAllowed, isProviderLimitError, isSafeBucketSlug, isTerminalActivityState, listBuckets, listHarnessPresets, listModelRoles, listPresets, listToolHelpTopics, listUserPresets, loadHarnessPresets, export_loadProviders as loadProviders, loadQuotaStore, loadUserPresets, loadWorkspaceModelRoles, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, makeSandboxCredsStore, makeSandboxResolver, matchesMessageFilter, measureTool, measureToolList, messageFrom, migrateLegacySessionsFile, migrationMarkerPath, mintMessageId, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeCommand, normalizeInbound, normalizeModeId, normalizeRemote, normalizeSkillsOption, normalizeWorktreeField, parseAnthropicRateLimitHeaders, parseDuration, parseModelRoleRef, parsePrUrl, parseTaskStatus, parseWindow, parseWorktreeIsolationMode, pickUsageDetail, policyToActivities, policyWatchesSession, prHeadSha, prToActivities, projectBrowserTools, projectSessionUsage, export_providerEnvVar as providerEnvVar, export_providersPath as providersPath, readConversationIndex, readDaemonRegistry, export_readPairingsSnapshot as readPairingsSnapshot, readProfileQuota, readRegisteredSlugs, readRuntimeMeta, readSandboxLedger, readSessionForBrain, readUsageSnapshots, reapGcEntry, reapOrphanedDescendants, reconcileSandboxLedger, recordProfileQuota, recordSandboxBoot, recordSandboxLiveness, recordSandboxOrigin, recordSandboxState, registerBrainTools, registerBuiltinRoutes, registerDeviceTools, registerPairingTools, registerReviewTools, registerSandboxAttachTool, registerWebSearchTools, removeHarnessPreset, export_removeProviderKey as removeProviderKey, removeSandboxLedgerEntry, renderInboxDigest, renderSessionMessage, renderSessionMessages, repoSlug, resolveAuthSpec, resolveBucketSlug, resolveDeferredToolsGatewayOption, resolveEffectiveLlmEndpointFlag, resolveModelRole, resolveNativeLink, resolvePosture, resolvePrincipal, resolveRelation, resolveReuseFromLedger, resolveReviewerPreset, resolveSpawnDefaults, resolveSubscriptionCredential, rollupSessionSubtree, rollupUsage, runContinueOnBootPass, runCrashDetectPass, runEagerResumePass, runIdleReapPass, runShellLane, runStallWatchdogPass, sandboxLedgerPath, sanitizeLabel, saveUserPreset, setDefaultPreset, setMcpCredentialDeps, export_setProviderKey as setProviderKey, signAttestation, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, toWorktreeStatusView, trackWorktreeProvision, turnToActivities, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, updateHarnessPreset, upsertSandboxLedger, userPresetsPath, verifyInboundSignature, verifySignedAttestation, webSearchTool, withPr, withPrStatus, workflowToActivities, writeDaemonRegistryEntry };
62568
+ export { AnthropicRemainingQuotaReader, AuthResolutionError, BUCKETS_ROOT, CLAMP_LIMIT_BYTES, CLAUDE_CODE_OAUTH_SOURCE, CONTINUE_PROMPT_SOURCE, DEAD_SESSION_STATUSES, DEFAULT_ALWAYS_ON_TOOLS, DEFAULT_BUCKET, DEFAULT_CONTINUE_PROMPT, DEFAULT_HOST_LOAD_BUDGET_MS, DEFAULT_HOST_LOAD_THRESHOLDS, DEFAULT_MODEL_ROLES, DEFAULT_ORCHESTRATOR_TOOLS, DEFAULT_WORKTREE_ISOLATION, DEFAULT_WORKTREE_PROVISION_CONCURRENCY, DEVICE_PROMPT_PATH_PREFIX, GC_REAPABLE_STATES, HOSTS_VERSION, HOST_HANDSHAKE_REMEDIATION_HINT, HOST_SCOPE_HEADER, HarnessPresetValidationError, INBOUND_PROVIDERS, JOIN_TOKENS_VERSION, LEGACY_SESSIONS_FILE, MAX_ATTEMPTS, MAX_AUTO_CONTINUE_ATTEMPTS, MAX_MESSAGE_DATA_BYTES, MCP_APP_BRIDGE_SCRIPT, MESSAGE_KINDS, MESSAGE_PREAMBLE, MESSAGE_TAG, MESSAGE_URGENCIES, MODEL_ROLE_REF_PREFIX, export_PAIRINGS_VERSION as PAIRINGS_VERSION, POSTURE_NATIVE_ALIASES, POSTURE_PREAMBLES, export_PROVIDER_ENV_VARS as PROVIDER_ENV_VARS, PrLookupError, ResumeDisabledError, SESSION_END_REASONS, SESSION_ID_ENV, SIGN_NAMESPACE, SsrfFetchError, SubscriptionSourceError, TASK_STATUSES, WORKSPACE_SLUG_ENV, WORKTREE_ISOLATION_ENV, WORKTREE_PROVISION_CONCURRENCY_ENV, activityCounts, addHarnessPreset, allowedSignersLine, appendConversationRecord, attachSandbox, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogModels, buildCatalogProviderModels, buildHostLoadReport, buildMcpConfigSnippet, buildRouteAwareLaunchConfig, canonicalForModeId, challengeCacheSize, challengeReasonForSsrf, claudeProjectSlug, collectGcCandidates, collectHostSample, compactOutcome, composeMode, composeSessionObservers, composedFromRangeSha, computeHostWarnings, continueInterruptedSessions, continueSkipReason, conversationIndexPath, createActivityProjector, createDaemonReviewerHost, createDefaultHostProbes, createDeviceRegistry, createFileStepCache, createGateway, createHostLoadService, createHostRegistry, createInboundEndpointStore, createJoinTokenRegistry, createOrchestratorInjector, createOrchestratorMcpServerFactory, export_createPairingRegistry as createPairingRegistry, createPrProvenanceReconciler, createProcessStatsService, createReviewLaneExecutor, createReviewLedger, createReviewPackLoader, createReviewRunner, createScopeTokenRegistry, createSessionMessage, createSupervisorTaskGateRunner, createTaskLedger, createWorkspaceBrainSubscriber, createWorkspaceBrains, createWorkspaceFs, credentialFingerprint, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, decodeWhsecSecret, decomposeMode, defaultBrowserAdapterIds, defaultBrowserAdapterResolution, defaultReviewKeysDir, defaultReviewLedgerRoot, defaultReviewPackCacheDir, defineBraveSearchHttpDriver, defineSerperHttpDriver, deleteUserPreset, deliverEventEnvelope, deliveryBackoffDelay, deriveSessionOutcome, deriveSessionUsage, describeOwner, encodeWhsecSecret, enrichRollupWithProviderQuota, enrichWithRemainingQuota, ensureReviewSigningKey, envelopeClampedBytes, escapeHumanPrompt, escapeMessageBody, evaluateCostBudget, execGh, extractHelpSection, fetchPrStatus, fileConversationStore, fileExists3 as fileExists, filterActivities, findComposeCandidate, findConversationRecord, findNativeMode, findPrForCommit, formatToolCall, formatToolResult, getDefaultHarnessPreset, getHarnessPreset, getHostLoadService, getMcpCredentialDeps, getProcessStatsService, export_getProviderKey as getProviderKey, getToolHelp, getUserPreset, githubRepoOf, harnessPresetsPath, hostPlaceholders, injectMcpAppBridge, export_injectProviderKeysIntoEnv as injectProviderKeysIntoEnv, isAgentCliAuthConfigured, isClosedTaskStatus, isKnownSessionEndReason, isMessageAllowed, isProviderLimitError, isPubliclyRoutable, isSafeBucketSlug, isTerminalActivityState, listBuckets, listHarnessPresets, listModelRoles, listPresets, listToolHelpTopics, listUserPresets, loadHarnessPresets, export_loadProviders as loadProviders, loadQuotaStore, loadUserPresets, loadWorkspaceModelRoles, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, makeSandboxCredsStore, makeSandboxResolver, matchesMessageFilter, measureTool, measureToolList, messageFrom, migrateLegacySessionsFile, migrationMarkerPath, mintMessageId, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeCallbackUrl, normalizeCommand, normalizeInbound, normalizeModeId, normalizeRemote, normalizeSkillsOption, normalizeWorktreeField, parseAnthropicRateLimitHeaders, parseDuration, parseModelRoleRef, parsePrUrl, parseTaskStatus, parseWindow, parseWorktreeIsolationMode, pickUsageDetail, policyToActivities, policyWatchesSession, prHeadSha, prToActivities, projectBrowserTools, projectSessionUsage, promptHostSession, export_providerEnvVar as providerEnvVar, export_providersPath as providersPath, readConversationIndex, readDaemonRegistry, readHostsSnapshot, export_readPairingsSnapshot as readPairingsSnapshot, readProfileQuota, readRegisteredSlugs, readRuntimeMeta, readSandboxLedger, readSessionForBrain, readUsageSnapshots, reapGcEntry, reapOrphanedDescendants, reconcileSandboxLedger, recordProfileQuota, recordSandboxBoot, recordSandboxLiveness, recordSandboxOrigin, recordSandboxState, registerBrainTools, registerBuiltinRoutes, registerDeviceTools, registerPairingTools, registerReviewTools, registerSandboxAttachTool, registerWebSearchTools, removeHarnessPreset, export_removeProviderKey as removeProviderKey, removeSandboxLedgerEntry, renderInboxDigest, renderSessionMessage, renderSessionMessages, repoSlug, resetChallengeCacheForTests, resolveAuthSpec, resolveBucketSlug, resolveDeferredToolsGatewayOption, resolveEffectiveLlmEndpointFlag, resolveModelRole, resolveNativeLink, resolvePosture, resolvePrincipal, resolveRelation, resolveReuseFromLedger, resolveReviewerPreset, resolveSpawnDefaults, resolveSubscriptionCredential, rollupSessionSubtree, rollupUsage, runContinueOnBootPass, runCrashDetectPass, runEagerResumePass, runIdleReapPass, runShellLane, runStallWatchdogPass, sandboxLedgerPath, sanitizeLabel, saveUserPreset, serializeEnvelope, setDefaultPreset, setEgressDispatcherForTests, setMcpCredentialDeps, export_setProviderKey as setProviderKey, signAttestation, signWebhook, ssrfFetch, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, toWorktreeStatusView, trackWorktreeProvision, turnToActivities, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, updateHarnessPreset, upsertSandboxLedger, userPresetsPath, verifyCallback, verifyInboundSignature, verifySignedAttestation, webSearchTool, withPr, withPrStatus, workflowToActivities, writeDaemonRegistryEntry };
61618
62569
  //# sourceMappingURL=index.mjs.map
61619
62570
  //# sourceMappingURL=index.mjs.map