@agentproto/runtime 5.5.0 → 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(() => "");
@@ -35218,7 +35365,8 @@ function createHostRegistry(deps2) {
35218
35365
  const attempts = [epoch, epoch - 1];
35219
35366
  let lastErr;
35220
35367
  let hungUpOnHello = 0;
35221
- for (const e of attempts) {
35368
+ for (let attempt = 1; attempt <= attempts.length; attempt++) {
35369
+ const e = attempts[attempt - 1];
35222
35370
  const { route, auth } = await deriveEpochTokens(record2.pairRoot, e);
35223
35371
  let raw;
35224
35372
  try {
@@ -35228,32 +35376,45 @@ function createHostRegistry(deps2) {
35228
35376
  continue;
35229
35377
  }
35230
35378
  try {
35231
- const started = await startClientHandshake({
35232
- daemonX25519Pub: record2.daemonX25519Pub,
35233
- daemonEd25519Pub: record2.daemonEd25519Pub,
35234
- authToken: auth,
35235
- clientName: record2.name
35379
+ let rawCloseReason;
35380
+ const offRawClose = raw.onClose((reason) => {
35381
+ rawCloseReason = reason ?? "unknown";
35236
35382
  });
35237
- const wrapped = await clientHandshakeOverSink(
35238
- raw,
35239
- encodePairingMessage(started.hello),
35240
- (replyBytes) => started.complete(decodePairingReply(replyBytes)),
35241
- { timeoutMs: handshakeTimeoutMs }
35242
- );
35243
- const client = createTunnelClient({ sink: wrapped });
35244
- 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
+ }
35245
35402
  await markReached(record2);
35246
35403
  return client;
35247
35404
  } catch (err) {
35248
35405
  lastErr = err;
35249
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
+ );
35250
35410
  try {
35251
35411
  raw.close("handshake failed");
35252
35412
  } catch {
35253
35413
  }
35254
35414
  }
35255
35415
  }
35256
- 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})` : "";
35257
35418
  const failure = `could not reach host ${record2.fingerprint} via ${record2.rendezvousUrl}: ${lastErr instanceof Error ? lastErr.message : String(lastErr)}${hint}`;
35258
35419
  await markUnreachable(record2, errMsg3(lastErr));
35259
35420
  throw new Error(failure);
@@ -35490,6 +35651,35 @@ function createHostRegistry(deps2) {
35490
35651
  start: sweep
35491
35652
  };
35492
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
+ }
35493
35683
  function wrapStreamWithCleanup(source, cleanup) {
35494
35684
  const reader = source.getReader();
35495
35685
  let cleanedUp = false;
@@ -35752,6 +35942,114 @@ function createDeviceRegistry(pairing, hosts) {
35752
35942
  }
35753
35943
  };
35754
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
+ }
35755
36053
 
35756
36054
  // src/app-tools.ts
35757
36055
  init_app_ui_build();
@@ -37339,7 +37637,7 @@ function createLedgerAppender(app, appRunId, runId, item) {
37339
37637
  };
37340
37638
  return { append, flush: () => chain };
37341
37639
  }
37342
- 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) {
37343
37641
  if (lease) {
37344
37642
  const renew = () => {
37345
37643
  state.run.lease = { ownerId: lease.ownerId, heartbeatAt: lease.now().toISOString() };
@@ -37371,7 +37669,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
37371
37669
  workspaceSlug: state.workspaceSlug,
37372
37670
  ...workspacePaths ? { workspace: workspacePaths.scratch, artifactsDir: workspacePaths.artifactsDir, runId: state.run.runId, onArtifact } : {},
37373
37671
  input,
37374
- ...cache2 ? { cache: cache2, cacheKey } : {},
37672
+ ...cache3 ? { cache: cache3, cacheKey } : {},
37375
37673
  // WP-S — human-resolved approval steps: a `kind: "approval"` step parks
37376
37674
  // the run as "awaiting-approval" with a pending inbox entry, instead of
37377
37675
  // the engine's silent auto-approve. A human answers through
@@ -38057,9 +38355,9 @@ function createWorkflowRunner(opts) {
38057
38355
  }
38058
38356
  );
38059
38357
  state.agents = agents;
38060
- 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;
38061
38359
  const effectiveCacheKey = input.cacheKey ?? journal?.cacheKey;
38062
- 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(() => {
38063
38361
  for (const [sid, binding] of sessionToRun) {
38064
38362
  if (binding.runId === runId) sessionToRun.delete(sid);
38065
38363
  }
@@ -38190,7 +38488,7 @@ function createWorkflowRunner(opts) {
38190
38488
  );
38191
38489
  state.agents = agents;
38192
38490
  const journal = internalJournal(workspaceRoot, runId);
38193
- 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;
38194
38492
  const effectiveCacheKey = args.cacheKey ?? journal?.cacheKey;
38195
38493
  const execWorkflow = journal && args.cacheKey === void 0 ? forceCacheableWorkflow(workflow) : workflow;
38196
38494
  void executeRunWorkflow(
@@ -38199,7 +38497,7 @@ function createWorkflowRunner(opts) {
38199
38497
  agents,
38200
38498
  abort.signal,
38201
38499
  sessionEvents,
38202
- cache2,
38500
+ cache3,
38203
38501
  effectiveCacheKey,
38204
38502
  args.input,
38205
38503
  persist,
@@ -39663,7 +39961,7 @@ function createTaskLedger(opts) {
39663
39961
  conflict: false,
39664
39962
  error: `task "${taskId}" not found`
39665
39963
  });
39666
- const lookup = (taskId, caller) => {
39964
+ const lookup2 = (taskId, caller) => {
39667
39965
  const task = tasks.get(taskId);
39668
39966
  if (!task) return void 0;
39669
39967
  if (!canAccessBoard(caller, task.boardId)) return void 0;
@@ -39808,7 +40106,7 @@ function createTaskLedger(opts) {
39808
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));
39809
40107
  };
39810
40108
  const claim = (input, caller) => {
39811
- const task = lookup(input.taskId, caller);
40109
+ const task = lookup2(input.taskId, caller);
39812
40110
  if (!task) return notFound(input.taskId);
39813
40111
  if (input.rev !== task.rev || task.owner !== void 0) {
39814
40112
  return { ok: false, conflict: true, current: task };
@@ -39831,7 +40129,7 @@ function createTaskLedger(opts) {
39831
40129
  return { ok: true, task };
39832
40130
  };
39833
40131
  const update = (input, caller) => {
39834
- const task = lookup(input.taskId, caller);
40132
+ const task = lookup2(input.taskId, caller);
39835
40133
  if (!task) return notFound(input.taskId);
39836
40134
  if (input.rev !== task.rev) {
39837
40135
  return { ok: false, conflict: true, current: task };
@@ -40014,7 +40312,7 @@ function createTaskLedger(opts) {
40014
40312
  return { ok: true, task };
40015
40313
  };
40016
40314
  const linkApproval = (taskId, approvalId, caller) => {
40017
- const task = lookup(taskId, caller);
40315
+ const task = lookup2(taskId, caller);
40018
40316
  if (!task) return notFound(taskId);
40019
40317
  const manager = isManager(caller, task);
40020
40318
  const owner = isOwner(caller, task);
@@ -40059,7 +40357,7 @@ function createTaskLedger(opts) {
40059
40357
  return {
40060
40358
  create,
40061
40359
  list,
40062
- get: (taskId, caller) => lookup(taskId, caller),
40360
+ get: (taskId, caller) => lookup2(taskId, caller),
40063
40361
  claim,
40064
40362
  update,
40065
40363
  linkApproval,
@@ -42506,7 +42804,7 @@ function createRemoteCatalogClient(options = {}) {
42506
42804
  const timeoutMs = options.timeoutMs ?? DEFAULT_REMOTE_CATALOG_TIMEOUT_MS;
42507
42805
  const fetchImpl = options.fetchImpl ?? fetch;
42508
42806
  const now = options.now ?? Date.now;
42509
- const cache2 = /* @__PURE__ */ new Map();
42807
+ const cache3 = /* @__PURE__ */ new Map();
42510
42808
  async function fetchOne(url) {
42511
42809
  let body;
42512
42810
  try {
@@ -42538,13 +42836,13 @@ function createRemoteCatalogClient(options = {}) {
42538
42836
  async fetchSources(sources, opts = {}) {
42539
42837
  const results = await Promise.all(
42540
42838
  sources.map(async ({ url }) => {
42541
- const hit = cache2.get(url);
42839
+ const hit = cache3.get(url);
42542
42840
  if (!opts.refresh && hit && now() - hit.at < ttlMs) return hit.result;
42543
42841
  const result = await fetchOne(url);
42544
42842
  if (result.entries.length > 0 || result.warnings.length === 0) {
42545
- cache2.set(url, { at: now(), result });
42843
+ cache3.set(url, { at: now(), result });
42546
42844
  } else {
42547
- cache2.delete(url);
42845
+ cache3.delete(url);
42548
42846
  }
42549
42847
  return result;
42550
42848
  })
@@ -43880,7 +44178,7 @@ function send(res, status, body) {
43880
44178
  res.writeHead(status, { "content-type": "application/json" });
43881
44179
  res.end(JSON.stringify(body));
43882
44180
  }
43883
- async function readBody(req) {
44181
+ async function readBody2(req) {
43884
44182
  const chunks = [];
43885
44183
  let size = 0;
43886
44184
  for await (const chunk of req) {
@@ -43917,7 +44215,7 @@ async function handleA2aTaskRequest(req, res, path, deps2) {
43917
44215
  return true;
43918
44216
  }
43919
44217
  if (appId.includes("/.well-known/")) return false;
43920
- const body = await readBody(req);
44218
+ const body = await readBody2(req);
43921
44219
  if ("tooLarge" in body) {
43922
44220
  send(res, 413, { jsonrpc: "2.0", id: null, error: { code: JSON_RPC_ERROR.invalidRequest, message: "request body too large" } });
43923
44221
  return true;
@@ -45303,8 +45601,8 @@ async function startHttpServer(opts) {
45303
45601
  }))
45304
45602
  };
45305
45603
  });
45306
- server2.server.setRequestHandler(CallToolRequestSchema, async (request) => {
45307
- 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 ?? {});
45308
45606
  if (!out.ok) {
45309
45607
  return { content: [{ type: "text", text: out.error }], isError: true };
45310
45608
  }
@@ -46993,6 +47291,16 @@ async function startHttpServer(opts) {
46993
47291
  );
46994
47292
  if (handled) return;
46995
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
+ }
46996
47304
  if (opts.remote && path.startsWith("/remote")) {
46997
47305
  const handled = await handleRemoteControl(req, res, path, opts.remote);
46998
47306
  if (handled) return;
@@ -49623,6 +49931,79 @@ async function handleDeviceSpawn(req, res, path, port, spawnAllowed) {
49623
49931
  Readable.fromWeb(upstreamRes.body).pipe(res);
49624
49932
  return true;
49625
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
+ }
49626
50007
  async function handleRemoteControl(req, res, path, controller) {
49627
50008
  const json3 = (status, body) => {
49628
50009
  res.writeHead(status, { "content-type": "application/json" });
@@ -50386,6 +50767,33 @@ async function handleDevices(req, res, path, registry, hostRegistry, joinTokens)
50386
50767
  }
50387
50768
  return true;
50388
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
+ }
50389
50797
  const idMatch = path.match(/^\/devices\/([^/]+)$/);
50390
50798
  if (idMatch && req.method === "PATCH") {
50391
50799
  const target = decodeURIComponent(idMatch[1] ?? "");
@@ -55793,7 +56201,7 @@ function waitingOnKey(w) {
55793
56201
  var PR_SETTLE_SWEEP_INTERVAL_MS = 5 * 6e4;
55794
56202
  var PR_SETTLE_SWEEP_MAX_URLS = 10;
55795
56203
  function createActivityProjector(opts) {
55796
- const cache2 = /* @__PURE__ */ new Map();
56204
+ const cache3 = /* @__PURE__ */ new Map();
55797
56205
  const prStates = /* @__PURE__ */ new Map();
55798
56206
  const projectOwnerRaw = (owner) => {
55799
56207
  switch (owner) {
@@ -55854,9 +56262,9 @@ function createActivityProjector(opts) {
55854
56262
  const seen = /* @__PURE__ */ new Set();
55855
56263
  for (const rec of next) {
55856
56264
  seen.add(rec.id);
55857
- const prev = cache2.get(rec.id);
56265
+ const prev = cache3.get(rec.id);
55858
56266
  const changed = !prev || prev.state !== rec.state || prev.taskId !== rec.taskId || waitingOnKey(prev.waitingOn) !== waitingOnKey(rec.waitingOn);
55859
- cache2.set(rec.id, rec);
56267
+ cache3.set(rec.id, rec);
55860
56268
  if (changed && announce) {
55861
56269
  opts.sessionEvents.emit({
55862
56270
  type: "activity:changed",
@@ -55865,8 +56273,8 @@ function createActivityProjector(opts) {
55865
56273
  });
55866
56274
  }
55867
56275
  }
55868
- for (const [id, rec] of cache2) {
55869
- 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);
55870
56278
  }
55871
56279
  };
55872
56280
  const ownersTouchedBy = (type) => {
@@ -55978,7 +56386,7 @@ function createActivityProjector(opts) {
55978
56386
  dispose() {
55979
56387
  unsubscribe();
55980
56388
  if (sweepTimer) clearInterval(sweepTimer);
55981
- cache2.clear();
56389
+ cache3.clear();
55982
56390
  prStates.clear();
55983
56391
  }
55984
56392
  };
@@ -56163,7 +56571,7 @@ function createApprovalsEngine(opts) {
56163
56571
  const record2 = approvals.get(id);
56164
56572
  return record2 ? checkExpiry(record2) : void 0;
56165
56573
  };
56166
- const request = (input, requester) => {
56574
+ const request2 = (input, requester) => {
56167
56575
  const id = `apr_${randomUUID().replace(/-/g, "").slice(0, 20)}`;
56168
56576
  const requestedChannels = input.channels && input.channels.length > 0 ? input.channels.filter((c) => enabledChannels.includes(c)) : enabledChannels;
56169
56577
  const canonicalJson2 = canonicalPayloadFor(input.payload);
@@ -56329,7 +56737,7 @@ function createApprovalsEngine(opts) {
56329
56737
  };
56330
56738
  return {
56331
56739
  enabledChannels,
56332
- request,
56740
+ request: request2,
56333
56741
  get,
56334
56742
  list,
56335
56743
  wait,
@@ -58235,127 +58643,9 @@ function registerPairingTools(server, opts) {
58235
58643
  }
58236
58644
  );
58237
58645
  }
58238
- var HOP_BY_HOP = /* @__PURE__ */ new Set(["connection", "keep-alive", "transfer-encoding", "upgrade", "host"]);
58239
- function collectHeaders(raw) {
58240
- const headers = {};
58241
- for (const [k, v] of Object.entries(raw)) {
58242
- if (v === void 0 || HOP_BY_HOP.has(k.toLowerCase())) continue;
58243
- headers[k] = Array.isArray(v) ? v.join(", ") : v;
58244
- }
58245
- return headers;
58246
- }
58247
- function isPrematureCloseError(err) {
58248
- return typeof err === "object" && err !== null && "code" in err && err.code === "ERR_STREAM_PREMATURE_CLOSE";
58249
- }
58250
- async function readBody2(req) {
58251
- const chunks = [];
58252
- for await (const chunk of req) chunks.push(chunk);
58253
- if (chunks.length === 0) return void 0;
58254
- return new Uint8Array(Buffer.concat(chunks));
58255
- }
58256
- async function startDeviceSandboxBridge(opts) {
58257
- const { hostRegistry, target } = opts;
58258
- const server = createServer((req, res) => {
58259
- void handleRequest(req, res).catch((err) => {
58260
- if (res.writableEnded) return;
58261
- if (!res.headersSent) {
58262
- res.writeHead(502, { "content-type": "application/json" });
58263
- res.end(
58264
- JSON.stringify({
58265
- error: "device_sandbox_bridge_failed",
58266
- message: err instanceof Error ? err.message : String(err)
58267
- })
58268
- );
58269
- return;
58270
- }
58271
- res.end();
58272
- });
58273
- });
58274
- async function handleRequest(req, res) {
58275
- const body = await readBody2(req);
58276
- let upstream;
58277
- try {
58278
- upstream = await hostRegistry.forwardHttpStream(target, {
58279
- method: req.method ?? "GET",
58280
- path: `/device-spawn${req.url ?? "/"}`,
58281
- headers: collectHeaders(req.headers),
58282
- ...body ? { body } : {}
58283
- });
58284
- } catch (err) {
58285
- res.writeHead(502, { "content-type": "application/json" });
58286
- res.end(
58287
- JSON.stringify({
58288
- error: "device_unreachable",
58289
- message: err instanceof Error ? err.message : String(err)
58290
- })
58291
- );
58292
- return;
58293
- }
58294
- const resHeaders = {};
58295
- for (const [k, v] of Object.entries(upstream.headers)) {
58296
- if (HOP_BY_HOP.has(k.toLowerCase()) || k.toLowerCase() === "content-length") continue;
58297
- resHeaders[k] = v;
58298
- }
58299
- res.writeHead(upstream.status, resHeaders);
58300
- try {
58301
- await pipeline(
58302
- Readable.fromWeb(upstream.body),
58303
- res
58304
- );
58305
- } catch (err) {
58306
- if (!isPrematureCloseError(err)) throw err;
58307
- }
58308
- }
58309
- const port = await getFreePort2();
58310
- await new Promise((resolve41, reject) => {
58311
- server.once("error", reject);
58312
- server.listen(port, "127.0.0.1", () => resolve41());
58313
- });
58314
- return {
58315
- mcpUrl: `http://127.0.0.1:${port}/mcp`,
58316
- async close() {
58317
- await new Promise((resolve41) => server.close(() => resolve41()));
58318
- }
58319
- };
58320
- }
58321
- async function getFreePort2() {
58322
- return new Promise((resolve41, reject) => {
58323
- const srv = createServer$1();
58324
- srv.once("error", reject);
58325
- srv.listen(0, "127.0.0.1", () => {
58326
- const address = srv.address();
58327
- if (address === null || typeof address === "string") {
58328
- srv.close(() => reject(new Error("failed to allocate a free port")));
58329
- return;
58330
- }
58331
- const { port } = address;
58332
- srv.close(() => resolve41(port));
58333
- });
58334
- });
58335
- }
58336
58646
 
58337
- // src/sandbox-providers/device.ts
58338
- function createDeviceSandboxProvider(target, hostRegistry) {
58339
- return {
58340
- async boot(_spec, _opts) {
58341
- const hosts = await hostRegistry.list();
58342
- const known = hosts.some((h) => h.fingerprint === target || h.name === target);
58343
- if (!known) {
58344
- throw new Error(
58345
- `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.`
58346
- );
58347
- }
58348
- const bridge = await startDeviceSandboxBridge({ hostRegistry, target });
58349
- return {
58350
- mcpUrl: bridge.mcpUrl,
58351
- sandboxId: `device-${target}-${randomUUID()}`,
58352
- async stop() {
58353
- await bridge.close();
58354
- }
58355
- };
58356
- }
58357
- };
58358
- }
58647
+ // src/index.ts
58648
+ init_device();
58359
58649
  function text9(value) {
58360
58650
  return {
58361
58651
  content: [
@@ -58367,7 +58657,7 @@ function text9(value) {
58367
58657
  };
58368
58658
  }
58369
58659
  function registerDeviceTools(server, opts) {
58370
- const { registry, joinTokens } = opts;
58660
+ const { registry, hosts: registryHosts, joinTokens } = opts;
58371
58661
  server.tool(
58372
58662
  "device_list",
58373
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.",
@@ -58449,6 +58739,37 @@ function registerDeviceTools(server, opts) {
58449
58739
  }
58450
58740
  }
58451
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
+ }
58452
58773
  if (joinTokens) {
58453
58774
  server.tool(
58454
58775
  "join_token_create",
@@ -60116,6 +60437,440 @@ function createWorkspaceFs(opts) {
60116
60437
 
60117
60438
  // src/index.ts
60118
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
+ }
60119
60874
  async function hasNamedLlmEndpoints() {
60120
60875
  const override = process.env.LLM_ENDPOINT_ENDPOINTS_FILE?.trim();
60121
60876
  const path = override && override.length > 0 ? override : join(homedir(), ".agentproto", "llm-endpoints.json");
@@ -60376,13 +61131,13 @@ function createJoinTokenRegistry(deps2) {
60376
61131
  } catch (err) {
60377
61132
  if (signal.aborted) break;
60378
61133
  log(`[join-tokens] "${record2.name}": dial failed: ${errMsg4(err)}`);
60379
- await sleep(backoff, signal);
61134
+ await sleep2(backoff, signal);
60380
61135
  backoff = Math.min(backoff * 2, reconnectMaxMs);
60381
61136
  continue;
60382
61137
  }
60383
61138
  if (!sink.isOpen) {
60384
61139
  if (signal.aborted) break;
60385
- await sleep(backoff, signal);
61140
+ await sleep2(backoff, signal);
60386
61141
  backoff = Math.min(backoff * 2, reconnectMaxMs);
60387
61142
  continue;
60388
61143
  }
@@ -60411,12 +61166,17 @@ function createJoinTokenRegistry(deps2) {
60411
61166
  capturedSession = result.session;
60412
61167
  return { reply: encodePairingMessage(result.reply), keys: result.session };
60413
61168
  },
60414
- { 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
+ }
60415
61175
  );
60416
61176
  } catch (err) {
60417
61177
  if (signal.aborted) break;
60418
61178
  log(`[join-tokens] "${record2.name}": handshake failed: ${errMsg4(err)}`);
60419
- await sleep(backoff, signal);
61179
+ await sleep2(backoff, signal);
60420
61180
  backoff = Math.min(backoff * 2, reconnectMaxMs);
60421
61181
  continue;
60422
61182
  }
@@ -60442,7 +61202,7 @@ function createJoinTokenRegistry(deps2) {
60442
61202
  }
60443
61203
  return { create, list, revoke, startAutoconnect, shutdown };
60444
61204
  }
60445
- function sleep(ms, signal) {
61205
+ function sleep2(ms, signal) {
60446
61206
  return new Promise((resolve41) => {
60447
61207
  if (signal.aborted) return resolve41();
60448
61208
  const timer = setTimeout(resolve41, ms);
@@ -61178,6 +61938,7 @@ async function createGateway(opts) {
61178
61938
  registerPairingTools(server, { registry: opts.pairingRegistry });
61179
61939
  registerDeviceTools(server, {
61180
61940
  registry: createDeviceRegistry(opts.pairingRegistry, opts.hostRegistry),
61941
+ ...opts.hostRegistry ? { hosts: opts.hostRegistry } : {},
61181
61942
  ...opts.joinTokens ? { joinTokens: opts.joinTokens } : {}
61182
61943
  });
61183
61944
  }
@@ -61804,6 +62565,6 @@ var export_readPairingsSnapshot = pairing_registry_exports.readPairingsSnapshot;
61804
62565
  var export_removeProviderKey = providers_store_exports.removeProviderKey;
61805
62566
  var export_setProviderKey = providers_store_exports.setProviderKey;
61806
62567
 
61807
- 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_HANDSHAKE_REMEDIATION_HINT, 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, 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, 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 };
61808
62569
  //# sourceMappingURL=index.mjs.map
61809
62570
  //# sourceMappingURL=index.mjs.map