@agentproto/runtime 5.13.0 → 5.14.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
@@ -7,7 +7,7 @@ import { ensureChromeDevtoolsMcp, resolveChrome, buildHeadlessBrowserMcpEntry, h
7
7
  import { selectPricingTier, getModelProvider, resolveContextWindow, formatTokens, splitContextWindowHint, resolvePricing, LLM_PRICING_CATALOG, resolvePricingExact, MODEL_ALIASES, isKnownLlmId } from '@agentproto/model-catalog/llm';
8
8
  import { resolveCustomRoute, registerCustomRoute, tryParseModelRef, resolveLlmModelRoute, formatModelRef, stripRouteSuffix, stripFixedNativeVendor } from '@agentproto/model-catalog/route-identity';
9
9
  import { randomBytes, createHmac, randomUUID, timingSafeEqual, createHash } from 'crypto';
10
- import { createReadStream, promises, readFileSync, mkdirSync, writeFileSync, renameSync, readdirSync, openSync, fstatSync, readSync, closeSync, existsSync, statSync, mkdtempSync, chmodSync, realpathSync, cpSync, appendFileSync, createWriteStream } from 'fs';
10
+ import { createReadStream, promises, readFileSync, mkdirSync, writeFileSync, renameSync, readdirSync, openSync, fstatSync, readSync, closeSync, existsSync, statSync, mkdtempSync, chmodSync, writeSync, fsyncSync, unlinkSync, realpathSync, cpSync, appendFileSync, createWriteStream } from 'fs';
11
11
  import { createInterface } from 'readline';
12
12
  import * as providers_store_star from '@agentproto/providers-store';
13
13
  import { findAnthropicGatewayPreset, anthropicGatewayPresetList, getAnthropicGatewayPreset } from '@agentproto/provider-presets';
@@ -16,7 +16,7 @@ import { z, ZodError } from 'zod';
16
16
  import { listAuthProfiles, eligibleProfiles, getAuthProfile, createAuthProfile, AuthProfileValidationError, deleteAuthProfile, updateAuthProfile, KeychainStore, removeAuthProfile, addAuthProfile, setAuthProfileEnabled, setAuthProfileModels, refreshAuthProfileModels, credentialIdentity } from '@agentproto/auth';
17
17
  import { CatalogProviderSchema, getModelsByProvider, getStaticModelProvider } from '@agentproto/model-catalog';
18
18
  import { resolvePairPageUrl, DEFAULT_PAIR_PAGE } from '@agentproto/secrets/pairing/browser';
19
- import { ListToolsRequestSchema, CallToolRequestSchema, SUPPORTED_PROTOCOL_VERSIONS, LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/sdk/types.js';
19
+ import { ListToolsRequestSchema, CallToolRequestSchema, McpError, ErrorCode, 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
22
  import { createServer, request as request$1 } from 'http';
@@ -38,6 +38,7 @@ import { createBrainManager, parseKnowledgeConfig } from '@agentproto/workspace-
38
38
  import { browserAdapters, getBrowserAdapter } from '@agentproto/adapter-browser';
39
39
  import { defineHttpDriver } from '@agentproto/driver-http';
40
40
  import matter2 from 'gray-matter';
41
+ import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
41
42
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
42
43
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
43
44
  import { WebSocketServer } from 'ws';
@@ -48,8 +49,8 @@ import { createReconnectLogGate } from '@agentproto/pairing-host';
48
49
  export { createReconnectLogGate } from '@agentproto/pairing-host';
49
50
  import { loadAppBundledTools, loadAppHandle, peekAppUi, unpackApp, safeId, versionSatisfies } from '@agentproto/app-kit';
50
51
  import { loadAgent } from '@agentproto/agent';
51
- import { normalizeToolId } from '@agentproto/driver';
52
- import { compileWorkflow, runWorkflow, isCompilableJsonSchema, validateAgainstJsonSchema, validateWorkflowInput, buildAgentStep, StepOutcomeError, MissingArtifactError } from '@agentproto/workflow-runtime';
52
+ import { normalizeToolId, runTool } from '@agentproto/driver';
53
+ import { compileWorkflow, runWorkflow, isCompilableJsonSchema, validateAgainstJsonSchema, validateWorkflowInput, buildAgentStep, AgentSessionLostError, StepOutcomeError, MissingArtifactError } from '@agentproto/workflow-runtime';
53
54
  import { loadWorkflowHandle } from '@agentproto/workflow-loader';
54
55
  import { Cron } from 'croner';
55
56
  import { brotliCompressSync, constants, gzipSync } from 'zlib';
@@ -145,15 +146,15 @@ function hasMarker(command, marker) {
145
146
  return false;
146
147
  }
147
148
  function listProcesses() {
148
- return new Promise((resolve45) => {
149
+ return new Promise((resolve47) => {
149
150
  execFile("ps", ["-axo", "pid=,command="], { maxBuffer: 32 * 1024 * 1024 }, (err, stdout) => {
150
- if (err) return resolve45([]);
151
+ if (err) return resolve47([]);
151
152
  const out = [];
152
153
  for (const line of stdout.split("\n")) {
153
154
  const m = line.match(/^\s*(\d+)\s+(.*)$/);
154
155
  if (m) out.push({ pid: Number(m[1]), command: m[2] ?? "" });
155
156
  }
156
- resolve45(out);
157
+ resolve47(out);
157
158
  });
158
159
  });
159
160
  }
@@ -523,7 +524,7 @@ function resolveSessionPricing(model) {
523
524
  function tokenCost(tokens, pricePer1M) {
524
525
  return tokens === void 0 ? 0 : tokens * pricePer1M / 1e6;
525
526
  }
526
- function deriveSessionUsage(input, resolve45 = defaultResolver) {
527
+ function deriveSessionUsage(input, resolve47 = defaultResolver) {
527
528
  const contextUsed = plausibleContextUsed(input.contextSize, input.contextUsed);
528
529
  const base = {
529
530
  ...input.model !== void 0 ? { model: input.model } : {},
@@ -538,7 +539,7 @@ function deriveSessionUsage(input, resolve45 = defaultResolver) {
538
539
  }
539
540
  const hasTokens = input.tokensIn !== void 0 || input.tokensOut !== void 0 || input.cacheReadTokens !== void 0 || input.cacheWriteTokens !== void 0;
540
541
  if (hasTokens) {
541
- const basePricing = input.model !== void 0 ? resolve45(input.model) : void 0;
542
+ const basePricing = input.model !== void 0 ? resolve47(input.model) : void 0;
542
543
  if (!basePricing) {
543
544
  return { ...base, source: "no-pricing" };
544
545
  }
@@ -1045,8 +1046,8 @@ function createTranscriptWriter(opts) {
1045
1046
  const state = states.get(sessionId);
1046
1047
  if (!state) return Promise.resolve();
1047
1048
  flushBuffers(sessionId, state);
1048
- return new Promise((resolve45) => {
1049
- state.stream.write("", () => resolve45());
1049
+ return new Promise((resolve47) => {
1050
+ state.stream.write("", () => resolve47());
1050
1051
  });
1051
1052
  },
1052
1053
  close(sessionId) {
@@ -1054,8 +1055,8 @@ function createTranscriptWriter(opts) {
1054
1055
  if (!state) return Promise.resolve();
1055
1056
  flushBuffers(sessionId, state);
1056
1057
  states.delete(sessionId);
1057
- return new Promise((resolve45) => {
1058
- state.stream.end(() => resolve45());
1058
+ return new Promise((resolve47) => {
1059
+ state.stream.end(() => resolve47());
1059
1060
  });
1060
1061
  },
1061
1062
  closeAll() {
@@ -1064,7 +1065,7 @@ function createTranscriptWriter(opts) {
1064
1065
  const state = states.get(sessionId);
1065
1066
  if (!state) continue;
1066
1067
  flushBuffers(sessionId, state);
1067
- closings.push(new Promise((resolve45) => state.stream.end(() => resolve45())));
1068
+ closings.push(new Promise((resolve47) => state.stream.end(() => resolve47())));
1068
1069
  }
1069
1070
  states.clear();
1070
1071
  return Promise.all(closings).then(() => void 0);
@@ -1225,9 +1226,9 @@ async function exportClaudeCodeSession(adapterSessionId, cwd, configDir) {
1225
1226
  let stream;
1226
1227
  try {
1227
1228
  stream = createReadStream(filePath, { encoding: "utf8" });
1228
- await new Promise((resolve45, reject) => {
1229
+ await new Promise((resolve47, reject) => {
1229
1230
  stream.once("error", reject);
1230
- stream.once("open", resolve45);
1231
+ stream.once("open", resolve47);
1231
1232
  });
1232
1233
  } catch (err) {
1233
1234
  const code = err.code;
@@ -1501,7 +1502,7 @@ async function discoverHermesSessions(cwd, since, expectedId) {
1501
1502
  }
1502
1503
  async function defaultHermesRunner(adapterSessionId) {
1503
1504
  const { spawn: spawn8 } = await import('child_process');
1504
- return new Promise((resolve45, reject) => {
1505
+ return new Promise((resolve47, reject) => {
1505
1506
  const chunks = [];
1506
1507
  const errChunks = [];
1507
1508
  const proc = spawn8(
@@ -1520,7 +1521,7 @@ async function defaultHermesRunner(adapterSessionId) {
1520
1521
  )
1521
1522
  );
1522
1523
  } else {
1523
- resolve45(Buffer.concat(chunks).toString("utf8"));
1524
+ resolve47(Buffer.concat(chunks).toString("utf8"));
1524
1525
  }
1525
1526
  });
1526
1527
  });
@@ -1557,9 +1558,9 @@ async function exportDaemonEventsSession(sessionId, desc, baseDir) {
1557
1558
  let stream;
1558
1559
  try {
1559
1560
  stream = createReadStream(filePath, { encoding: "utf8" });
1560
- await new Promise((resolve45, reject) => {
1561
+ await new Promise((resolve47, reject) => {
1561
1562
  stream.once("error", reject);
1562
- stream.once("open", resolve45);
1563
+ stream.once("open", resolve47);
1563
1564
  });
1564
1565
  } catch (err) {
1565
1566
  const code = err.code;
@@ -1781,7 +1782,11 @@ async function readFirstJsonLine(path) {
1781
1782
  function codexHome() {
1782
1783
  return process.env.CODEX_HOME ?? join(homedir(), ".codex");
1783
1784
  }
1784
- function codexSessionsDir() {
1785
+ function codexSessionsDir(configDir) {
1786
+ if (configDir) {
1787
+ const isolated = join(configDir, "sessions");
1788
+ if (existsSync(isolated)) return isolated;
1789
+ }
1785
1790
  return join(codexHome(), "sessions");
1786
1791
  }
1787
1792
  async function walkCodexRollouts(dir) {
@@ -1816,8 +1821,8 @@ function codexBlocksText(content) {
1816
1821
  }
1817
1822
  return acc;
1818
1823
  }
1819
- async function findCodexRolloutFile(conversationId) {
1820
- const files = await walkCodexRollouts(codexSessionsDir());
1824
+ async function findCodexRolloutFile(conversationId, configDir) {
1825
+ const files = await walkCodexRollouts(codexSessionsDir(configDir));
1821
1826
  const byName = files.find((f) => f.endsWith(`-${conversationId}.jsonl`));
1822
1827
  if (byName) return byName;
1823
1828
  for (const f of files) {
@@ -1834,11 +1839,11 @@ async function findCodexRolloutFile(conversationId) {
1834
1839
  }
1835
1840
  return void 0;
1836
1841
  }
1837
- async function exportCodexSession(conversationId) {
1838
- const file = await findCodexRolloutFile(conversationId);
1842
+ async function exportCodexSession(conversationId, configDir) {
1843
+ const file = await findCodexRolloutFile(conversationId, configDir);
1839
1844
  if (!file) {
1840
1845
  throw new Error(
1841
- `codex: no rollout file for conversation "${conversationId}" under ${codexSessionsDir()}.
1846
+ `codex: no rollout file for conversation "${conversationId}" under ${codexSessionsDir(configDir)}.
1842
1847
  The session may predate persistence, or CODEX_HOME points elsewhere.`
1843
1848
  );
1844
1849
  }
@@ -2488,7 +2493,7 @@ var init_transcript_export = __esm({
2488
2493
  exportSession: (id) => exportHermesSession(id)
2489
2494
  },
2490
2495
  codex: {
2491
- exportSession: (id) => exportCodexSession(id)
2496
+ exportSession: (id, _cwd, configDir) => exportCodexSession(id, configDir)
2492
2497
  },
2493
2498
  opencode: {
2494
2499
  exportSession: (id) => exportOpenCodeSession(id)
@@ -3907,8 +3912,8 @@ async function enrichWithRemainingQuota(rollup, opts) {
3907
3912
  })
3908
3913
  );
3909
3914
  const capMs = opts.timeoutMs ?? DEFAULT_ENRICH_CAP_MS;
3910
- const cap = new Promise((resolve45) => {
3911
- setTimeout(() => resolve45(null), capMs);
3915
+ const cap = new Promise((resolve47) => {
3916
+ setTimeout(() => resolve47(null), capMs);
3912
3917
  });
3913
3918
  const settled = await Promise.race([work, cap]);
3914
3919
  if (settled === null) return rollup;
@@ -3972,8 +3977,8 @@ async function enrichWithAccountCredits(rollup, opts) {
3972
3977
  })
3973
3978
  );
3974
3979
  const capMs = opts.timeoutMs ?? DEFAULT_ENRICH_CAP_MS;
3975
- const cap = new Promise((resolve45) => {
3976
- setTimeout(() => resolve45(null), capMs);
3980
+ const cap = new Promise((resolve47) => {
3981
+ setTimeout(() => resolve47(null), capMs);
3977
3982
  });
3978
3983
  const settled = await Promise.race([work, cap]);
3979
3984
  if (settled === null) return rollup;
@@ -4228,14 +4233,14 @@ function parseProcStat(statLine, cmdline, uid, ctx) {
4228
4233
  };
4229
4234
  }
4230
4235
  function execPs(platform, timeoutMs) {
4231
- return new Promise((resolve45, reject) => {
4236
+ return new Promise((resolve47, reject) => {
4232
4237
  execFile(
4233
4238
  "ps",
4234
4239
  psArgs(platform),
4235
4240
  // LC_ALL=C pins the decimal separator of `pcpu` to `.` regardless of the
4236
4241
  // operator's locale.
4237
4242
  { maxBuffer: 32 * 1024 * 1024, timeout: timeoutMs, env: { ...process.env, LC_ALL: "C" } },
4238
- (err, stdout) => err ? reject(new Error(`ps failed${err.killed ? " (timed out, host overloaded?)" : ""}: ${err.message.trim()}`)) : resolve45(stdout)
4243
+ (err, stdout) => err ? reject(new Error(`ps failed${err.killed ? " (timed out, host overloaded?)" : ""}: ${err.message.trim()}`)) : resolve47(stdout)
4239
4244
  );
4240
4245
  });
4241
4246
  }
@@ -4539,8 +4544,8 @@ function parseVmStatAvailable(text13) {
4539
4544
  return total * Number(size[1]);
4540
4545
  }
4541
4546
  function execText(cmd, args) {
4542
- return new Promise((resolve45, reject) => {
4543
- execFile(cmd, args, { timeout: 3e3 }, (err, stdout) => err ? reject(err) : resolve45(stdout));
4547
+ return new Promise((resolve47, reject) => {
4548
+ execFile(cmd, args, { timeout: 3e3 }, (err, stdout) => err ? reject(err) : resolve47(stdout));
4544
4549
  });
4545
4550
  }
4546
4551
  function createProcessStatsService(opts = {}) {
@@ -4566,14 +4571,14 @@ function createProcessStatsService(opts = {}) {
4566
4571
  nowMs: now()
4567
4572
  }).claimedPids;
4568
4573
  const byPid = new Map(rows.map((r) => [r.pid, r]));
4569
- const keyOf2 = (pid) => `${pid}:${Math.floor((now() - (byPid.get(pid)?.elapsedSec ?? 0) * 1e3) / 2e3)}`;
4574
+ const keyOf3 = (pid) => `${pid}:${Math.floor((now() - (byPid.get(pid)?.elapsedSec ?? 0) * 1e3) / 2e3)}`;
4570
4575
  const candidates = strayEnvCandidates(rows, claimed, uid);
4571
- const unread = candidates.filter((p) => !envMemo.has(keyOf2(p)));
4576
+ const unread = candidates.filter((p) => !envMemo.has(keyOf3(p)));
4572
4577
  const read = await readEnv(unread);
4573
- for (const p of unread) envMemo.set(keyOf2(p), read.get(p) ?? null);
4578
+ for (const p of unread) envMemo.set(keyOf3(p), read.get(p) ?? null);
4574
4579
  const envHints = /* @__PURE__ */ new Map();
4575
4580
  for (const p of candidates) {
4576
- const h = envMemo.get(keyOf2(p));
4581
+ const h = envMemo.get(keyOf3(p));
4577
4582
  if (h) envHints.set(p, h);
4578
4583
  }
4579
4584
  if (envMemo.size > 4096) envMemo.clear();
@@ -5419,9 +5424,9 @@ async function readFirstTurn(sessionId) {
5419
5424
  let stream;
5420
5425
  try {
5421
5426
  stream = createReadStream(sessionEventsPath(sessionId), { encoding: "utf8" });
5422
- await new Promise((resolve45, reject) => {
5427
+ await new Promise((resolve47, reject) => {
5423
5428
  stream.once("error", reject);
5424
- stream.once("open", resolve45);
5429
+ stream.once("open", resolve47);
5425
5430
  });
5426
5431
  } catch {
5427
5432
  return null;
@@ -9271,7 +9276,7 @@ async function* pollAgentOutputFallback(host, remoteSessionId, since) {
9271
9276
  `sandbox proxy: ${consecutivePollFailures} consecutive poll failures against the box daemon (session "${remoteSessionId}") \u2014 giving up. Last error: ${pollErr instanceof Error ? pollErr.message : String(pollErr)}`
9272
9277
  );
9273
9278
  }
9274
- await new Promise((resolve45) => setTimeout(resolve45, POLL_RETRY_DELAY_MS));
9279
+ await new Promise((resolve47) => setTimeout(resolve47, POLL_RETRY_DELAY_MS));
9275
9280
  continue;
9276
9281
  }
9277
9282
  if (result.timedOut) continue;
@@ -9506,19 +9511,19 @@ async function startDeviceSandboxBridge(opts) {
9506
9511
  }
9507
9512
  }
9508
9513
  const port = await getFreePort();
9509
- await new Promise((resolve45, reject) => {
9514
+ await new Promise((resolve47, reject) => {
9510
9515
  server.once("error", reject);
9511
- server.listen(port, "127.0.0.1", () => resolve45());
9516
+ server.listen(port, "127.0.0.1", () => resolve47());
9512
9517
  });
9513
9518
  return {
9514
9519
  mcpUrl: `http://127.0.0.1:${port}/mcp`,
9515
9520
  async close() {
9516
- await new Promise((resolve45) => server.close(() => resolve45()));
9521
+ await new Promise((resolve47) => server.close(() => resolve47()));
9517
9522
  }
9518
9523
  };
9519
9524
  }
9520
9525
  async function getFreePort() {
9521
- return new Promise((resolve45, reject) => {
9526
+ return new Promise((resolve47, reject) => {
9522
9527
  const srv = createServer$1();
9523
9528
  srv.once("error", reject);
9524
9529
  srv.listen(0, "127.0.0.1", () => {
@@ -9528,7 +9533,7 @@ async function getFreePort() {
9528
9533
  return;
9529
9534
  }
9530
9535
  const { port } = address;
9531
- srv.close(() => resolve45(port));
9536
+ srv.close(() => resolve47(port));
9532
9537
  });
9533
9538
  });
9534
9539
  }
@@ -9673,7 +9678,7 @@ async function pollServeReady(url, opts) {
9673
9678
  for (; ; ) {
9674
9679
  if (await probe(url)) return true;
9675
9680
  if (Date.now() >= deadline) return false;
9676
- await new Promise((resolve45) => setTimeout(resolve45, intervalMs));
9681
+ await new Promise((resolve47) => setTimeout(resolve47, intervalMs));
9677
9682
  }
9678
9683
  }
9679
9684
  async function seedAllowlist(client) {
@@ -9798,7 +9803,7 @@ var init_sandbox_app_serve = __esm({
9798
9803
  }
9799
9804
  });
9800
9805
  async function getFreePort2() {
9801
- return new Promise((resolve45, reject) => {
9806
+ return new Promise((resolve47, reject) => {
9802
9807
  const srv = createServer$1();
9803
9808
  srv.once("error", reject);
9804
9809
  srv.listen(0, "127.0.0.1", () => {
@@ -9808,7 +9813,7 @@ async function getFreePort2() {
9808
9813
  return;
9809
9814
  }
9810
9815
  const { port } = address;
9811
- srv.close(() => resolve45(port));
9816
+ srv.close(() => resolve47(port));
9812
9817
  });
9813
9818
  });
9814
9819
  }
@@ -9821,7 +9826,7 @@ async function probeHealth(url, timeoutMs) {
9821
9826
  } catch {
9822
9827
  }
9823
9828
  if (Date.now() >= deadline) return false;
9824
- await new Promise((resolve45) => setTimeout(resolve45, POLL_INTERVAL_MS));
9829
+ await new Promise((resolve47) => setTimeout(resolve47, POLL_INTERVAL_MS));
9825
9830
  }
9826
9831
  }
9827
9832
  var HEALTH_TIMEOUT_MS, POLL_INTERVAL_MS, localSandboxProvider;
@@ -11398,8 +11403,8 @@ async function spawnAgentSession(deps2, input) {
11398
11403
  }
11399
11404
  let resolveClaim;
11400
11405
  claims.set(key, {
11401
- result: new Promise((resolve45) => {
11402
- resolveClaim = resolve45;
11406
+ result: new Promise((resolve47) => {
11407
+ resolveClaim = resolve47;
11403
11408
  }),
11404
11409
  // An implicit claim expires sooner than an explicit one — see
11405
11410
  // `IMPLICIT_SPAWN_CLAIM_WINDOW_MS`'s docblock.
@@ -11958,13 +11963,13 @@ async function isSharedDirtyCwd(cwd) {
11958
11963
  const identity = resolveWorktreeIdentity(cwd);
11959
11964
  if (identity?.worktreeId !== void 0) return false;
11960
11965
  const { spawn: spawn8 } = await import('child_process');
11961
- return await new Promise((resolve45) => {
11966
+ return await new Promise((resolve47) => {
11962
11967
  let stdout = "";
11963
11968
  let settled = false;
11964
11969
  const done = (v) => {
11965
11970
  if (!settled) {
11966
11971
  settled = true;
11967
- resolve45(v);
11972
+ resolve47(v);
11968
11973
  }
11969
11974
  };
11970
11975
  const child = spawn8("git", ["-C", cwd, "status", "--porcelain"], {
@@ -11979,15 +11984,15 @@ async function isSharedDirtyCwd(cwd) {
11979
11984
  }
11980
11985
  async function resolveMcpCredentialHeaders(mcpServers) {
11981
11986
  if (!mcpServers || mcpServers.length === 0) return mcpServers;
11982
- const { resolveMcpCredentialHeaders: resolve45 } = getMcpCredentialDeps();
11983
- if (!resolve45) return mcpServers;
11987
+ const { resolveMcpCredentialHeaders: resolve47 } = getMcpCredentialDeps();
11988
+ if (!resolve47) return mcpServers;
11984
11989
  return Promise.all(
11985
11990
  mcpServers.map(async (entry) => {
11986
11991
  const ref = entry.credentialRef;
11987
11992
  if (!ref) return entry;
11988
11993
  let brokered;
11989
11994
  try {
11990
- brokered = await resolve45({ credentialRef: ref });
11995
+ brokered = await resolve47({ credentialRef: ref });
11991
11996
  } catch (err) {
11992
11997
  console.warn(
11993
11998
  `[agent_start] credentialRef resolution failed for "${entry.name}" (${ref}): ${err instanceof Error ? err.message : String(err)}`
@@ -12083,7 +12088,7 @@ async function startDeviceSessionWithRetry(host, args, target) {
12083
12088
  lastErr = err;
12084
12089
  if (!isDeviceTransportError(err)) throw err;
12085
12090
  if (attempt < attempts) {
12086
- await new Promise((resolve45) => setTimeout(resolve45, DEVICE_SPAWN_RETRY_DELAY_MS));
12091
+ await new Promise((resolve47) => setTimeout(resolve47, DEVICE_SPAWN_RETRY_DELAY_MS));
12087
12092
  }
12088
12093
  }
12089
12094
  }
@@ -12335,10 +12340,10 @@ function markFailedBootLedger(opts) {
12335
12340
  }
12336
12341
  }
12337
12342
  async function resolveSandboxSecret(slug) {
12338
- const { resolveSandboxSecret: resolve45 } = getMcpCredentialDeps();
12339
- if (!resolve45) return null;
12343
+ const { resolveSandboxSecret: resolve47 } = getMcpCredentialDeps();
12344
+ if (!resolve47) return null;
12340
12345
  try {
12341
- return await resolve45(slug);
12346
+ return await resolve47(slug);
12342
12347
  } catch (err) {
12343
12348
  console.warn(
12344
12349
  `[agent_start] sandbox secret resolution failed for "${slug}": ${err instanceof Error ? err.message : String(err)}`
@@ -13044,8 +13049,8 @@ async function readLogTailSafe(dir, n) {
13044
13049
  async function resolveAppUiBuildState(input) {
13045
13050
  const { dir, uiPath } = input;
13046
13051
  const pending = peekInFlightBuild(uiPath) ?? ensureAppUiBuilt(input);
13047
- const timeout = new Promise((resolve45) => {
13048
- const timer = setTimeout(() => resolve45(RESOLVE_TIMEOUT), RESOLVE_FAST_PATH_MS);
13052
+ const timeout = new Promise((resolve47) => {
13053
+ const timer = setTimeout(() => resolve47(RESOLVE_TIMEOUT), RESOLVE_FAST_PATH_MS);
13049
13054
  timer.unref();
13050
13055
  });
13051
13056
  const raced = await Promise.race([pending, timeout]);
@@ -16402,8 +16407,8 @@ function createTerminalTranscriptWriter(opts) {
16402
16407
  const stream = streams.get(sessionId);
16403
16408
  if (!stream) return Promise.resolve();
16404
16409
  streams.delete(sessionId);
16405
- return new Promise((resolve45) => {
16406
- stream.end(() => resolve45());
16410
+ return new Promise((resolve47) => {
16411
+ stream.end(() => resolve47());
16407
16412
  });
16408
16413
  },
16409
16414
  closeAll() {
@@ -16412,8 +16417,8 @@ function createTerminalTranscriptWriter(opts) {
16412
16417
  const stream = streams.get(sessionId);
16413
16418
  if (!stream) continue;
16414
16419
  closings.push(
16415
- new Promise((resolve45) => {
16416
- stream.end(() => resolve45());
16420
+ new Promise((resolve47) => {
16421
+ stream.end(() => resolve47());
16417
16422
  })
16418
16423
  );
16419
16424
  }
@@ -16860,8 +16865,8 @@ function createRegistryHandoffAsker(registry, desc, baseDir) {
16860
16865
  const turn = registry.sendPrompt(sessionId, prompt, { source: HANDOFF_PROMPT_SOURCE });
16861
16866
  let timer;
16862
16867
  const timedOut = /* @__PURE__ */ Symbol("timeout");
16863
- const deadline = new Promise((resolve45) => {
16864
- timer = setTimeout(() => resolve45(timedOut), timeoutMs);
16868
+ const deadline = new Promise((resolve47) => {
16869
+ timer = setTimeout(() => resolve47(timedOut), timeoutMs);
16865
16870
  });
16866
16871
  try {
16867
16872
  const outcome = await Promise.race([turn, deadline]);
@@ -16876,7 +16881,7 @@ function createRegistryHandoffAsker(registry, desc, baseDir) {
16876
16881
  for (let attempt = 0; attempt < REPLY_POLL_ATTEMPTS; attempt++) {
16877
16882
  const reply = readLastAssistantTextSync(eventsPath2);
16878
16883
  if (reply && reply !== before) return reply;
16879
- await new Promise((resolve45) => setTimeout(resolve45, REPLY_POLL_MS));
16884
+ await new Promise((resolve47) => setTimeout(resolve47, REPLY_POLL_MS));
16880
16885
  }
16881
16886
  throw new Error("source session produced no reply");
16882
16887
  };
@@ -16980,14 +16985,14 @@ function buildRecentDigest(messages) {
16980
16985
  }
16981
16986
  async function captureGitStatus(cwd) {
16982
16987
  if (!cwd) return void 0;
16983
- return new Promise((resolve45) => {
16988
+ return new Promise((resolve47) => {
16984
16989
  execFile("git", ["status", "--porcelain"], { cwd }, (err, stdout) => {
16985
16990
  if (err) {
16986
- resolve45(void 0);
16991
+ resolve47(void 0);
16987
16992
  return;
16988
16993
  }
16989
16994
  const trimmed = stdout.trim();
16990
- resolve45(trimmed || "(working tree clean)");
16995
+ resolve47(trimmed || "(working tree clean)");
16991
16996
  });
16992
16997
  });
16993
16998
  }
@@ -19135,11 +19140,11 @@ function createSessionsRegistry(opts) {
19135
19140
  };
19136
19141
  const waitForTurnSettled = (rt, id, caller) => {
19137
19142
  if (!rt.busy) return Promise.resolve();
19138
- return new Promise((resolve45, reject) => {
19143
+ return new Promise((resolve47, reject) => {
19139
19144
  const onBusy = (busy) => {
19140
19145
  if (busy) return;
19141
19146
  cleanup();
19142
- resolve45();
19147
+ resolve47();
19143
19148
  };
19144
19149
  const cleanup = () => {
19145
19150
  clearTimeout(timer);
@@ -19217,9 +19222,9 @@ function createSessionsRegistry(opts) {
19217
19222
  );
19218
19223
  return;
19219
19224
  }
19220
- const answer = isChildPromptSource(next.source) ? void 0 : matchStructuredQuestionAnswer(liveRt, next.message);
19221
- if (answer) {
19222
- await answerStructuredQuestion(liveRt, answer);
19225
+ const answer2 = isChildPromptSource(next.source) ? void 0 : matchStructuredQuestionAnswer(liveRt, next.message);
19226
+ if (answer2) {
19227
+ await answerStructuredQuestion(liveRt, answer2);
19223
19228
  return;
19224
19229
  }
19225
19230
  await runAgentTurn(
@@ -23034,14 +23039,14 @@ var init_pr_provenance_stamp = __esm({
23034
23039
  init_pr_provenance();
23035
23040
  defaultGhRunner = async (args, cwd) => {
23036
23041
  const { spawn: spawn8 } = await import('child_process');
23037
- return await new Promise((resolve45) => {
23042
+ return await new Promise((resolve47) => {
23038
23043
  let stdout = "";
23039
23044
  const child = spawn8("gh", [...args], { cwd, shell: false });
23040
23045
  child.stdout?.on("data", (d) => {
23041
23046
  stdout += d.toString("utf8");
23042
23047
  });
23043
- child.on("error", () => resolve45({ exitCode: 1, stdout }));
23044
- child.on("close", (code) => resolve45({ exitCode: code ?? 1, stdout }));
23048
+ child.on("error", () => resolve47({ exitCode: 1, stdout }));
23049
+ child.on("close", (code) => resolve47({ exitCode: code ?? 1, stdout }));
23045
23050
  });
23046
23051
  };
23047
23052
  }
@@ -23575,6 +23580,19 @@ var init_command_tools = __esm({
23575
23580
  }
23576
23581
  });
23577
23582
 
23583
+ // src/liveness-tick-guard.ts
23584
+ function createLivenessTickGuard(intervalMs, graceMs, now = Date.now) {
23585
+ let last = now();
23586
+ return {
23587
+ shouldSkip() {
23588
+ const t = now();
23589
+ const late = t - last - intervalMs;
23590
+ last = t;
23591
+ return late > graceMs;
23592
+ }
23593
+ };
23594
+ }
23595
+
23578
23596
  // src/index.ts
23579
23597
  init_browser_mount();
23580
23598
  async function writeRuntimeMeta(workspace, meta) {
@@ -25319,10 +25337,10 @@ function sandboxAuthForBox(auth) {
25319
25337
  };
25320
25338
  }
25321
25339
  async function resolveSandboxSecretFromBroker(slug) {
25322
- const { resolveSandboxSecret: resolve45 } = getMcpCredentialDeps();
25323
- if (!resolve45) return null;
25340
+ const { resolveSandboxSecret: resolve47 } = getMcpCredentialDeps();
25341
+ if (!resolve47) return null;
25324
25342
  try {
25325
- return await resolve45(slug);
25343
+ return await resolve47(slug);
25326
25344
  } catch (err) {
25327
25345
  console.warn(
25328
25346
  `[restartAgentSession] sandbox secret resolution failed for "${slug}": ${err instanceof Error ? err.message : String(err)}`
@@ -25916,6 +25934,12 @@ function createMcpObservationStore(opts = {}) {
25916
25934
  const obs = touch(sessionId);
25917
25935
  obs.httpError = { at: obs.lastSeenAt, status };
25918
25936
  },
25937
+ recordResult(sessionId, method, message, clientProtocolVersion) {
25938
+ try {
25939
+ record2(sessionId, method, message, clientProtocolVersion);
25940
+ } catch {
25941
+ }
25942
+ },
25919
25943
  get: (sessionId) => bySession.get(sessionId),
25920
25944
  delete: (sessionId) => {
25921
25945
  bySession.delete(sessionId);
@@ -26240,7 +26264,7 @@ async function runContinueOnBootPass(opts) {
26240
26264
 
26241
26265
  // src/session-tools.ts
26242
26266
  init_session_outcome();
26243
- var defaultPsExecutor = () => new Promise((resolve45, reject) => {
26267
+ var defaultPsExecutor = () => new Promise((resolve47, reject) => {
26244
26268
  if (process.platform === "win32") {
26245
26269
  reject(new Error("processTreeRss: ps is not available on win32"));
26246
26270
  return;
@@ -26251,7 +26275,7 @@ var defaultPsExecutor = () => new Promise((resolve45, reject) => {
26251
26275
  { maxBuffer: 16 * 1024 * 1024 },
26252
26276
  (err, stdout) => {
26253
26277
  if (err) reject(err);
26254
- else resolve45(stdout);
26278
+ else resolve47(stdout);
26255
26279
  }
26256
26280
  );
26257
26281
  });
@@ -26646,15 +26670,15 @@ function computeHostWarnings(input) {
26646
26670
  function createDefaultHostProbes() {
26647
26671
  const platform = process.platform;
26648
26672
  const own = /* @__PURE__ */ new Set();
26649
- const run = (cmd, args, timeoutMs) => new Promise((resolve45, reject) => {
26673
+ const run = (cmd, args, timeoutMs) => new Promise((resolve47, reject) => {
26650
26674
  const child = execFile(
26651
26675
  cmd,
26652
26676
  args,
26653
26677
  { maxBuffer: 16 * 1024 * 1024, timeout: timeoutMs, env: { ...process.env, LC_ALL: "C" } },
26654
26678
  (err, stdout) => {
26655
- if (!err) return resolve45(stdout);
26679
+ if (!err) return resolve47(stdout);
26656
26680
  if (err.killed) return reject(new Error(`${cmd} timed out after ${timeoutMs}ms`));
26657
- return stdout ? resolve45(stdout) : reject(err);
26681
+ return stdout ? resolve47(stdout) : reject(err);
26658
26682
  }
26659
26683
  );
26660
26684
  if (child.pid !== void 0) own.add(child.pid);
@@ -27419,9 +27443,9 @@ async function callJevSystemOne(state, questions, opts) {
27419
27443
  const answers = {};
27420
27444
  const answerErrors = {};
27421
27445
  for (const [key, value] of Object.entries(parsed.data.answers)) {
27422
- const answer = jevChoiceAnswerSchema.safeParse(value);
27423
- if (answer.success) answers[key] = answer.data;
27424
- else answerErrors[key] = describeZodIssues(answer.error);
27446
+ const answer2 = jevChoiceAnswerSchema.safeParse(value);
27447
+ if (answer2.success) answers[key] = answer2.data;
27448
+ else answerErrors[key] = describeZodIssues(answer2.error);
27425
27449
  }
27426
27450
  return {
27427
27451
  ok: true,
@@ -27505,24 +27529,24 @@ async function judgeSessionWithJev(input) {
27505
27529
  }
27506
27530
  );
27507
27531
  if (!outcome.ok) return fail4(outcome.error);
27508
- const answer = outcome.answers[VERDICT_QUESTION_KEY];
27509
- if (!answer) {
27532
+ const answer2 = outcome.answers[VERDICT_QUESTION_KEY];
27533
+ if (!answer2) {
27510
27534
  const why = outcome.answerErrors?.[VERDICT_QUESTION_KEY];
27511
27535
  return fail4(`response missing a valid verdict answer${why ? ` \u2014 ${why}` : ""}`);
27512
27536
  }
27513
- if (!WRAPUP_VERDICTS.includes(answer.choice)) {
27514
- return fail4(`unknown verdict choice '${answer.choice}'`);
27537
+ if (!WRAPUP_VERDICTS.includes(answer2.choice)) {
27538
+ return fail4(`unknown verdict choice '${answer2.choice}'`);
27515
27539
  }
27516
- const confidence = answer.confidence ?? answer.probabilities[answer.choice];
27540
+ const confidence = answer2.confidence ?? answer2.probabilities[answer2.choice];
27517
27541
  if (typeof confidence !== "number" || !Number.isFinite(confidence) || confidence < 0 || confidence > 1) {
27518
27542
  return fail4("no usable confidence for the chosen verdict");
27519
27543
  }
27520
27544
  return {
27521
27545
  ok: true,
27522
27546
  sessionId: input.sessionId,
27523
- verdict: answer.choice,
27547
+ verdict: answer2.choice,
27524
27548
  confidence,
27525
- probabilities: answer.probabilities,
27549
+ probabilities: answer2.probabilities,
27526
27550
  model: outcome.model ?? model
27527
27551
  };
27528
27552
  }
@@ -27723,7 +27747,6 @@ function createBackgroundJobRegistry(opts) {
27723
27747
  jobId: job.id,
27724
27748
  status: "running",
27725
27749
  startedAt: job.startedAt,
27726
- resultPath: resultPathFor(job.id),
27727
27750
  followUp: { tool: followUp.tool, args: { jobId: job.id }, pollAfterMs: JOB_POLL_AFTER_MS, hint: followUp.hint }
27728
27751
  }),
27729
27752
  progressView: (job) => job.status === "failed" ? { jobId: job.id, status: job.status, endedAt: job.endedAt, error: job.error } : {
@@ -27731,22 +27754,86 @@ function createBackgroundJobRegistry(opts) {
27731
27754
  status: job.status,
27732
27755
  startedAt: job.startedAt,
27733
27756
  elapsedMs: Date.now() - job.startedMs,
27734
- resultPath: resultPathFor(job.id),
27735
27757
  followUp: { pollAfterMs: JOB_POLL_AFTER_MS }
27736
27758
  }
27737
27759
  };
27738
27760
  }
27739
27761
  function timedOutWaiting(promise, waitMs) {
27740
- return new Promise((resolve45) => {
27741
- const handle = setTimeout(() => resolve45(true), waitMs);
27762
+ return new Promise((resolve47) => {
27763
+ const handle = setTimeout(() => resolve47(true), waitMs);
27742
27764
  const settled = () => {
27743
27765
  clearTimeout(handle);
27744
- resolve45(false);
27766
+ resolve47(false);
27745
27767
  };
27746
27768
  promise.then(settled, settled);
27747
27769
  });
27748
27770
  }
27749
27771
 
27772
+ // src/branch-gc.ts
27773
+ function summarizeBranchGcApply(result) {
27774
+ const zero = () => ({ deleted: 0, skipped: 0, failed: 0 });
27775
+ const byScope = { local: zero(), remote: zero(), orphan: zero() };
27776
+ const totals = zero();
27777
+ const byResult = {};
27778
+ let aborted = 0;
27779
+ for (const o of result.outcomes) {
27780
+ byResult[o.result] = (byResult[o.result] ?? 0) + 1;
27781
+ const bucket = o.result === "deleted" ? "deleted" : o.result === "failed" ? "failed" : "skipped";
27782
+ byScope[o.kind][bucket]++;
27783
+ totals[bucket]++;
27784
+ if (o.result.startsWith("aborted-")) aborted++;
27785
+ }
27786
+ const status = totals.deleted === 0 ? totals.failed > 0 ? "failed" : "noop" : totals.failed > 0 || aborted > 0 ? "partial" : "ok";
27787
+ return { status, totals, byScope, byResult, restoreLog: result.restoreLog ?? null };
27788
+ }
27789
+ function withBranchGcApplySummary(result) {
27790
+ if (result.mode !== "apply") return result;
27791
+ const applySummary = summarizeBranchGcApply(result);
27792
+ return { ...result, status: applySummary.status, applySummary };
27793
+ }
27794
+ var BRANCH_GC_PAGE_DEFAULT = 100;
27795
+ var BRANCH_GC_PAGE_MAX = 500;
27796
+ var encodeOffset = (n) => Buffer.from(`o:${n}`).toString("base64url");
27797
+ function decodeOffset(cursor) {
27798
+ const m = /^o:(\d+)$/.exec(Buffer.from(cursor, "base64url").toString("utf8"));
27799
+ if (!m) throw new Error("branch_gc_status: invalid `cursor`");
27800
+ return Number(m[1]);
27801
+ }
27802
+ function sliceBranchGcResult(result, opts) {
27803
+ const section = opts.section ?? (result.mode === "apply" ? "outcomes" : "entries");
27804
+ const scopeOk = (kind) => !opts.scopes?.length || opts.scopes.includes(kind);
27805
+ let rows;
27806
+ if (section === "outcomes") {
27807
+ if (result.mode !== "apply") throw new Error("branch_gc_status: `section: outcomes` is only available on an apply result");
27808
+ rows = result.outcomes.filter((o) => scopeOk(o.kind) && (!opts.results?.length || opts.results.includes(o.result)));
27809
+ } else {
27810
+ rows = result.plan.entries.filter(
27811
+ (e) => scopeOk(e.kind) && (!opts.classes?.length || opts.classes.includes(e.class))
27812
+ );
27813
+ }
27814
+ const limit = Math.min(Math.max(Math.trunc(opts.limit ?? BRANCH_GC_PAGE_DEFAULT), 1), BRANCH_GC_PAGE_MAX);
27815
+ const start = opts.cursor !== void 0 ? decodeOffset(opts.cursor) : 0;
27816
+ const pageRows = rows.slice(start, start + limit);
27817
+ const next = start + limit;
27818
+ const page = {
27819
+ section,
27820
+ total: rows.length,
27821
+ returned: pageRows.length,
27822
+ ...next < rows.length ? { nextCursor: encodeOffset(next) } : {}
27823
+ };
27824
+ const { plan, ...rest } = result;
27825
+ const { entries: _entries, ...planMeta } = plan;
27826
+ const { outcomes: _outcomes, ...restNoOutcomes } = rest;
27827
+ return {
27828
+ page,
27829
+ result: {
27830
+ ...restNoOutcomes,
27831
+ plan: section === "entries" ? { ...planMeta, entries: pageRows } : planMeta,
27832
+ ...section === "outcomes" ? { outcomes: pageRows } : {}
27833
+ }
27834
+ };
27835
+ }
27836
+
27750
27837
  // src/session-tools.ts
27751
27838
  init_command_allowlist();
27752
27839
  init_agent_tools();
@@ -28366,25 +28453,27 @@ var sessionWrapupApplyJobs = createBackgroundJobRegistry({
28366
28453
  var BACKGROUND_DEFAULT_WAIT_MS = 25e3;
28367
28454
  var branchGcBackgroundView = (job) => branchGcJobs.backgroundView(job, {
28368
28455
  tool: "branch_gc_status",
28369
- hint: "Running in the background; a plan on a large repo takes a few minutes. Call branch_gc_status with this jobId about every 30 s. When done it returns the summary; the full result is written to resultPath (pass full: true to get it inline)."
28456
+ hint: "Running in the background; a plan on a large repo takes a few minutes. Call branch_gc_status with this jobId about every 30 s. When done it returns the summary and, once the file exists, a resultPath holding the full result (pass full: true to get a filtered/paged slice inline)."
28370
28457
  });
28371
- var branchGcDoneView = (jobId, resultPath, result, full, endedAt) => ({
28458
+ var branchGcDoneView = (jobId, resultPath, result, slice, endedAt) => ({
28372
28459
  jobId,
28373
28460
  status: "done",
28374
28461
  ...endedAt !== void 0 ? { endedAt } : {},
28375
- resultPath,
28462
+ ...resultPath !== void 0 ? { resultPath } : {},
28376
28463
  summary: result.summary,
28377
- // Apply results carry the restore log path and a per-outcome tally —
28378
- // exactly what a caller needs to decide "safe?" without fetching the
28379
- // full ~MB result with `full: true`.
28464
+ // Apply results carry the restore log path, a per-outcome tally and the
28465
+ // per-scope deleted/skipped/failed summary — exactly what a caller needs
28466
+ // to decide "safe?" without fetching the full ~MB result with `full: true`.
28467
+ // Result files written before `applySummary` existed are summarised here.
28380
28468
  ...result.mode === "apply" ? {
28381
28469
  restoreLog: result.restoreLog ?? null,
28382
28470
  outcomeCounts: result.outcomes.reduce((acc, o) => {
28383
28471
  acc[o.result] = (acc[o.result] ?? 0) + 1;
28384
28472
  return acc;
28385
- }, {})
28473
+ }, {}),
28474
+ applySummary: result.applySummary ?? summarizeBranchGcApply(result)
28386
28475
  } : {},
28387
- ...full ? { result } : {}
28476
+ ...slice ? sliceBranchGcResult(result, slice) : {}
28388
28477
  });
28389
28478
  var worktreeGcBackgroundView = (job) => worktreeGcJobs.backgroundView(job, {
28390
28479
  tool: "worktree_gc_status",
@@ -28424,7 +28513,7 @@ var backgroundStatusResult = async (toolName, jobs, jobId) => {
28424
28513
  jobId: job.id,
28425
28514
  status: "done",
28426
28515
  endedAt: job.endedAt,
28427
- resultPath: jobs.resultPathFor(job.id),
28516
+ ...job.resultPath !== void 0 ? { resultPath: job.resultPath } : {},
28428
28517
  result: job.result
28429
28518
  })
28430
28519
  }
@@ -28471,7 +28560,8 @@ function registerSessionTools(rawServer, opts) {
28471
28560
  project: args.project,
28472
28561
  keyOf: args.keyOf,
28473
28562
  maxLimit: 200,
28474
- itemKey: args.itemKey
28563
+ itemKey: args.itemKey,
28564
+ ...args.defaultLimit !== void 0 ? { defaultLimit: args.defaultLimit } : {}
28475
28565
  })
28476
28566
  ]
28477
28567
  });
@@ -29218,7 +29308,7 @@ function registerSessionTools(rawServer, opts) {
29218
29308
  });
29219
29309
  registerPaginatedListTool({
29220
29310
  id: "terminal_sessions_list",
29221
- description: "List terminal/PTY sessions tracked by the daemon. Equivalent to `session_list({kind: 'terminal'})`. Each entry includes `kind`, `pty`, `status`, age, etc. Use this when you only want the terminal subset. COMPACT BY DEFAULT: each entry is session_list's slim projection; pass `full: true` (or `compact: false`) for the complete, unprojected per-session record.",
29311
+ description: "List terminal/PTY sessions tracked by the daemon. Equivalent to `session_list({kind: 'terminal'})`. Each entry includes `kind`, `pty`, `status`, age, etc. Use this when you only want the terminal subset. COMPACT BY DEFAULT: each entry is session_list's slim projection; pass `full: true` (or `compact: false`) for the complete, unprojected per-session record. Without `limit`/`cursor` at most 50 rows come back; when there are more the reply carries `total`, `truncated: true` and a `nextCursor` \u2014 pass it as `cursor` (with a `limit`) for the rest, or filter with `onlyAlive` / `status`.",
29222
29312
  schema: terminalSessionsListSchema,
29223
29313
  body: async (input) => {
29224
29314
  let rows = registry.list({ includeArchived: true });
@@ -29242,7 +29332,10 @@ function registerSessionTools(rawServer, opts) {
29242
29332
  },
29243
29333
  project: compactSessionItemWithProvenance,
29244
29334
  keyOf: (s) => s.id,
29245
- itemKey: "sessions"
29335
+ itemKey: "sessions",
29336
+ // Dead terminals pile up: an unpaged call used to return every row
29337
+ // (65k+ chars, over the MCP output cap). Cap it and say so.
29338
+ defaultLimit: 50
29246
29339
  });
29247
29340
  const commandListSchema = z.object({
29248
29341
  kind: z.enum(["terminal", "agent-cli", "command", "all"]).optional().describe(
@@ -30124,7 +30217,7 @@ function registerSessionTools(rawServer, opts) {
30124
30217
  const branchGcKind = z.enum(["local", "remote", "orphan"]);
30125
30218
  server.tool(
30126
30219
  "branch_gc",
30127
- "Garbage-collect a repo's branches. DEFAULTS TO A DRY RUN: returns a plan classifying every local branch, base-remote branch and orphan tracking ref (refs/remotes/<ns>/* of a removed remote) as `reclaim` (work provably in base: the head commit of a merged PR, merged, squash-merged, patch-merged or content-merged), `review` (unmerged, old enough, not protected \u2014 carries coverage + the files not provably in base for a reviewer), or `hold` (base/protected, checked out in a worktree or its remote twin, open PR head, PR check unavailable, or younger than `minAgeDays`). `apply: true` (requires explicit `scopes`) deletes only `reclaim` entries, re-classifying each right before deleting it, and returns the path of a restore log (sha + re-create command per deleted ref). A plan can take minutes on a big repo: as an MCP caller, pass `wait: false` (or `waitMs: 40000`) and poll `branch_gc_status` with the returned jobId instead of blocking. A `wait: false` (or `waitMs` timed-out) response returns a jobId plus a `followUp` block naming the poll tool and cadence \u2014 poll `branch_gc_status` with that jobId.",
30220
+ "Garbage-collect a repo's branches. DEFAULTS TO A DRY RUN: returns a plan classifying every local branch, base-remote branch and orphan tracking ref (refs/remotes/<ns>/* of a removed remote) as `reclaim` (work provably in base: the head commit of a merged PR, merged, squash-merged, patch-merged or content-merged), `review` (unmerged, old enough, not protected \u2014 carries coverage + the files not provably in base for a reviewer), or `hold` (base/protected, checked out in a worktree or its remote twin, open PR head, PR check unavailable, or younger than `minAgeDays`). `apply: true` (requires explicit `scopes`) deletes only `reclaim` entries, re-classifying each right before deleting it, and returns the path of a restore log (sha + re-create command per deleted ref) plus a top-level `status` and an `applySummary` (deleted / skipped / failed counts per scope, and the restore log path). A plan can take minutes on a big repo: as an MCP caller, pass `wait: false` (or `waitMs: 40000`) and poll `branch_gc_status` with the returned jobId instead of blocking. A `wait: false` (or `waitMs` timed-out) response returns a jobId plus a `followUp` block naming the poll tool and cadence \u2014 poll `branch_gc_status` with that jobId.",
30128
30221
  {
30129
30222
  repoRoot: z.string().optional().describe("Absolute path to the git repo. Wins over `workspaceSlug`."),
30130
30223
  workspaceSlug: z.string().optional().describe("Workspace slug from `agentproto workspace list`. The active workspace when omitted."),
@@ -30174,7 +30267,7 @@ function registerSessionTools(rawServer, opts) {
30174
30267
  ...input.minAgeDays !== void 0 ? { minAgeDays: input.minAgeDays } : {},
30175
30268
  ...input.anchor ? { anchor: input.anchor } : {}
30176
30269
  };
30177
- const { job, promise } = branchGcJobs.start(() => runBranchGc(runInput));
30270
+ const { job, promise } = branchGcJobs.start(async () => withBranchGcApplySummary(await runBranchGc(runInput)));
30178
30271
  if (input.wait === false) {
30179
30272
  return { content: [{ type: "text", text: JSON.stringify(branchGcBackgroundView(job)) }] };
30180
30273
  }
@@ -30193,12 +30286,36 @@ function registerSessionTools(rawServer, opts) {
30193
30286
  );
30194
30287
  server.tool(
30195
30288
  "branch_gc_status",
30196
- "Poll a branch_gc run started with `wait: false` (or one that fell back to the background via `waitMs`). While running: status + elapsed time. When done: the plan's own summary, the path of the full result saved on disk (`resultPath`), and \u2014 with `full: true` \u2014 the full result itself. While running the view also carries `followUp.pollAfterMs` (poll every 30 s). When failed: the error.",
30289
+ 'Poll a branch_gc run started with `wait: false` (or one that fell back to the background via `waitMs`). While running: status + elapsed time. When done: the plan\'s own summary (plus, for an apply, `applySummary` \u2014 deleted/skipped/failed per scope and the restore log path) and the path of the full result saved on disk (`resultPath`). The full result is too big to return whole, so `full: true` (or any of `section`, `classes`, `scopes`, `results`, `limit`, `cursor`) returns ONE filtered page of it: `result` (summary + plan metadata + the selected list) and `page { section, total, returned, nextCursor }` \u2014 default 100 rows, pass `nextCursor` back as `cursor` for the next page. Use `classes: ["reclaim"]` to see only what an apply would delete. While running the view carries `followUp.pollAfterMs` (poll every 30 s). When failed: the error.',
30197
30290
  {
30198
30291
  jobId: z.string().describe("Job id returned by `branch_gc` (`bgc_\u2026`)."),
30199
- full: mcpBool2.optional().describe("Include the full result JSON, not just the summary. Default false.")
30292
+ full: mcpBool2.optional().describe("Include a filtered/paged slice of the full result (see description). Implied by any other slice param. Default false."),
30293
+ section: z.enum(["entries", "outcomes"]).optional().describe("Which list to return: plan `entries` or apply `outcomes`. Default `entries` for a plan, `outcomes` for an apply."),
30294
+ classes: z.array(z.enum(["reclaim", "review", "hold"])).optional().describe('Entries only: keep these classes (e.g. ["reclaim"]).'),
30295
+ scopes: z.array(branchGcKind).optional().describe("Keep these ref kinds (local, remote, orphan)."),
30296
+ results: z.array(
30297
+ z.enum(["deleted", "held", "skipped-review", "aborted-moved", "aborted-vanished", "aborted-reclassified", "failed"])
30298
+ ).optional().describe("Outcomes only: keep these outcome results."),
30299
+ limit: mcpNumber.optional().describe(`Rows per page. Default 100, max ${BRANCH_GC_PAGE_MAX}.`),
30300
+ cursor: z.string().optional().describe("`page.nextCursor` from the previous page.")
30200
30301
  },
30201
30302
  async (input) => {
30303
+ const wantsSlice = input.full === true || input.section !== void 0 || input.classes !== void 0 || input.scopes !== void 0 || input.results !== void 0 || input.limit !== void 0 || input.cursor !== void 0;
30304
+ const slice = wantsSlice ? {
30305
+ ...input.section ? { section: input.section } : {},
30306
+ ...input.classes ? { classes: input.classes } : {},
30307
+ ...input.scopes ? { scopes: input.scopes } : {},
30308
+ ...input.results ? { results: input.results } : {},
30309
+ ...input.limit !== void 0 ? { limit: input.limit } : {},
30310
+ ...input.cursor !== void 0 ? { cursor: input.cursor } : {}
30311
+ } : void 0;
30312
+ const doneView = (...args) => {
30313
+ try {
30314
+ return { content: [{ type: "text", text: JSON.stringify(branchGcDoneView(...args)) }] };
30315
+ } catch (err) {
30316
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: true };
30317
+ }
30318
+ };
30202
30319
  const job = branchGcJobs.get(input.jobId);
30203
30320
  if (!job) {
30204
30321
  const parsed = await branchGcJobs.readResultFile(input.jobId);
@@ -30213,14 +30330,12 @@ function registerSessionTools(rawServer, opts) {
30213
30330
  isError: true
30214
30331
  };
30215
30332
  }
30216
- const view3 = branchGcDoneView(input.jobId, branchGcJobs.resultPathFor(input.jobId), parsed, input.full === true);
30217
- return { content: [{ type: "text", text: JSON.stringify(view3) }] };
30333
+ return doneView(input.jobId, branchGcJobs.resultPathFor(input.jobId), parsed, slice);
30218
30334
  }
30219
30335
  if (job.status !== "done") {
30220
30336
  return { content: [{ type: "text", text: JSON.stringify(branchGcJobs.progressView(job)) }] };
30221
30337
  }
30222
- const view2 = branchGcDoneView(job.id, branchGcJobs.resultPathFor(job.id), job.result, input.full === true, job.endedAt);
30223
- return { content: [{ type: "text", text: JSON.stringify(view2) }] };
30338
+ return doneView(job.id, job.resultPath, job.result, slice, job.endedAt);
30224
30339
  }
30225
30340
  );
30226
30341
  const branchVerdictEnum = z.enum(["obsolete", "superseded", "salvage", "in-progress", "unclear"]);
@@ -32318,7 +32433,7 @@ function createWorkspaceBrainSubscriber(opts) {
32318
32433
  }
32319
32434
  };
32320
32435
  }
32321
- function makeBrowserHandle(entry, resolve45) {
32436
+ function makeBrowserHandle(entry, resolve47) {
32322
32437
  return {
32323
32438
  slug: entry.id,
32324
32439
  name: entry.name,
@@ -32329,7 +32444,7 @@ function makeBrowserHandle(entry, resolve45) {
32329
32444
  // check() is available for on-demand health probes but is never called
32330
32445
  // by the lister (kit invariant OQ-5). Returns true when the adapter
32331
32446
  // is present in the injected resolver map, false otherwise.
32332
- check: async () => resolve45 ? resolve45(entry.id) != null : true
32447
+ check: async () => resolve47 ? resolve47(entry.id) != null : true
32333
32448
  };
32334
32449
  }
32335
32450
  var noopLedger = {
@@ -34936,7 +35051,7 @@ function startHeartbeat(opts) {
34936
35051
  let timer = null;
34937
35052
  let currentEveryMs = null;
34938
35053
  let firing = false;
34939
- async function load() {
35054
+ async function load2() {
34940
35055
  if (!existsSync(path)) return null;
34941
35056
  let source;
34942
35057
  try {
@@ -34967,7 +35082,7 @@ function startHeartbeat(opts) {
34967
35082
  firing = true;
34968
35083
  let agentId;
34969
35084
  try {
34970
- const hb = await load();
35085
+ const hb = await load2();
34971
35086
  if (!hb || !hb.enabled) return;
34972
35087
  agentId = hb.agent;
34973
35088
  const agent = await opts.buildAgent(hb.agent);
@@ -35055,7 +35170,7 @@ function startHeartbeat(opts) {
35055
35170
  return {
35056
35171
  start() {
35057
35172
  if (timer) return;
35058
- void load().then((hb) => {
35173
+ void load2().then((hb) => {
35059
35174
  const everyMs = hb?.everyMs ?? 6e4;
35060
35175
  reschedule(everyMs);
35061
35176
  });
@@ -35104,6 +35219,356 @@ function preview(text13, max = 120) {
35104
35219
  // src/http-server.ts
35105
35220
  init_process_stats();
35106
35221
  init_browser_mount();
35222
+
35223
+ // src/mcp-modern/constants.ts
35224
+ var MODERN_PROTOCOL_VERSION = "2026-07-28";
35225
+ var DEFAULT_SUPPORTED_VERSIONS = [MODERN_PROTOCOL_VERSION];
35226
+ var META_PROTOCOL_VERSION = "io.modelcontextprotocol/protocolVersion";
35227
+ var META_CLIENT_INFO = "io.modelcontextprotocol/clientInfo";
35228
+ var META_CLIENT_CAPABILITIES = "io.modelcontextprotocol/clientCapabilities";
35229
+ var META_SERVER_INFO = "io.modelcontextprotocol/serverInfo";
35230
+ var ERR_PARSE = -32700;
35231
+ var ERR_INVALID_REQUEST = -32600;
35232
+ var ERR_METHOD_NOT_FOUND = -32601;
35233
+ var ERR_INVALID_PARAMS = -32602;
35234
+ var ERR_INTERNAL = -32603;
35235
+ var ERR_HEADER_MISMATCH = -32020;
35236
+ var ERR_UNSUPPORTED_VERSION = -32022;
35237
+ var FORWARDED_METHODS = /* @__PURE__ */ new Set([
35238
+ "tools/list",
35239
+ "tools/call",
35240
+ "events/list",
35241
+ "events/subscribe",
35242
+ "events/unsubscribe",
35243
+ "resources/list",
35244
+ "resources/templates/list",
35245
+ "resources/read",
35246
+ "prompts/list",
35247
+ "prompts/get"
35248
+ ]);
35249
+ var ALLOWED_METHODS = /* @__PURE__ */ new Set([...FORWARDED_METHODS, "server/discover"]);
35250
+ var CACHE_HINT_METHODS = /* @__PURE__ */ new Set([
35251
+ "server/discover",
35252
+ "tools/list",
35253
+ "prompts/list",
35254
+ "resources/list",
35255
+ "resources/templates/list",
35256
+ "resources/read"
35257
+ ]);
35258
+ var NAME_HEADER_SOURCE = /* @__PURE__ */ new Map([
35259
+ ["tools/call", "name"],
35260
+ ["prompts/get", "name"],
35261
+ ["resources/read", "uri"]
35262
+ ]);
35263
+
35264
+ // src/mcp-modern/bridge.ts
35265
+ var HANDSHAKE_VERSION = "2025-11-25";
35266
+ var BridgeError = class extends Error {
35267
+ code;
35268
+ data;
35269
+ constructor(code, message, data) {
35270
+ super(message);
35271
+ this.code = code;
35272
+ this.data = data;
35273
+ }
35274
+ };
35275
+ function forwardParams(params) {
35276
+ const { _meta, ...rest } = params;
35277
+ if (typeof _meta !== "object" || _meta === null || Array.isArray(_meta)) return rest;
35278
+ const handshake = /* @__PURE__ */ new Set([META_PROTOCOL_VERSION, META_CLIENT_INFO, META_CLIENT_CAPABILITIES]);
35279
+ const kept = Object.fromEntries(Object.entries(_meta).filter(([key]) => !handshake.has(key)));
35280
+ return Object.keys(kept).length > 0 ? { ...rest, _meta: kept } : rest;
35281
+ }
35282
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
35283
+ async function openBridge(server) {
35284
+ const [clientSide, serverSide] = InMemoryTransport.createLinkedPair();
35285
+ const pending = /* @__PURE__ */ new Map();
35286
+ let nextId = 1;
35287
+ clientSide.onmessage = (message) => {
35288
+ if (!("id" in message) || typeof message.id !== "number") return;
35289
+ const entry = pending.get(message.id);
35290
+ if (!entry) return;
35291
+ if ("error" in message) {
35292
+ pending.delete(message.id);
35293
+ entry.reject(new BridgeError(message.error.code, message.error.message, message.error.data));
35294
+ } else if ("result" in message) {
35295
+ pending.delete(message.id);
35296
+ entry.resolve(isRecord7(message.result) ? message.result : {});
35297
+ }
35298
+ };
35299
+ const close = async () => {
35300
+ for (const entry of pending.values()) entry.reject(new Error("bridge closed"));
35301
+ pending.clear();
35302
+ await clientSide.close().catch(() => {
35303
+ });
35304
+ await server.close().catch(() => {
35305
+ });
35306
+ };
35307
+ const call = (method, params, signal) => new Promise((resolve47, reject) => {
35308
+ if (signal?.aborted) {
35309
+ reject(signal.reason ?? new Error("aborted"));
35310
+ return;
35311
+ }
35312
+ const id = nextId++;
35313
+ const onAbort = () => {
35314
+ pending.delete(id);
35315
+ void clientSide.send({ jsonrpc: "2.0", method: "notifications/cancelled", params: { requestId: id, reason: "client disconnected" } }).catch(() => {
35316
+ });
35317
+ reject(signal?.reason ?? new Error("aborted"));
35318
+ };
35319
+ signal?.addEventListener("abort", onAbort, { once: true });
35320
+ pending.set(id, {
35321
+ resolve: (value) => {
35322
+ signal?.removeEventListener("abort", onAbort);
35323
+ resolve47(value);
35324
+ },
35325
+ reject: (error) => {
35326
+ signal?.removeEventListener("abort", onAbort);
35327
+ reject(error);
35328
+ }
35329
+ });
35330
+ clientSide.send({ jsonrpc: "2.0", id, method, params }).catch((error) => {
35331
+ pending.delete(id);
35332
+ signal?.removeEventListener("abort", onAbort);
35333
+ reject(error);
35334
+ });
35335
+ });
35336
+ try {
35337
+ await server.connect(serverSide);
35338
+ await clientSide.start();
35339
+ const init = await call("initialize", {
35340
+ protocolVersion: HANDSHAKE_VERSION,
35341
+ capabilities: {},
35342
+ clientInfo: { name: "agentproto-modern-bridge", version: "0.0.1" }
35343
+ });
35344
+ await clientSide.send({ jsonrpc: "2.0", method: "notifications/initialized" });
35345
+ const info = isRecord7(init.serverInfo) ? init.serverInfo : {};
35346
+ return {
35347
+ capabilities: isRecord7(init.capabilities) ? { ...init.capabilities } : {},
35348
+ serverInfo: {
35349
+ name: typeof info.name === "string" ? info.name : "agentproto",
35350
+ version: typeof info.version === "string" ? info.version : "0"
35351
+ },
35352
+ instructions: typeof init.instructions === "string" ? init.instructions : void 0,
35353
+ request: call,
35354
+ close
35355
+ };
35356
+ } catch (error) {
35357
+ await close();
35358
+ throw error;
35359
+ }
35360
+ }
35361
+
35362
+ // src/mcp-modern/validate.ts
35363
+ var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
35364
+ function normalizeHeaders(raw) {
35365
+ const out = {};
35366
+ for (const [name, value] of Object.entries(raw)) {
35367
+ if (value === void 0) continue;
35368
+ const joined = (Array.isArray(value) ? value.join(", ") : value).trim();
35369
+ const key = name.toLowerCase();
35370
+ const previous = out[key];
35371
+ out[key] = previous === void 0 ? joined : `${previous}, ${joined}`;
35372
+ }
35373
+ return out;
35374
+ }
35375
+ function acceptsJson(accept) {
35376
+ if (accept === void 0) return false;
35377
+ return accept.split(",").some((part) => {
35378
+ const type = (part.split(";")[0] ?? "").trim().toLowerCase();
35379
+ return type === "application/json" || type === "application/*" || type === "*/*";
35380
+ });
35381
+ }
35382
+ function isJsonContentType(contentType) {
35383
+ if (contentType === void 0) return false;
35384
+ return (contentType.split(";")[0] ?? "").trim().toLowerCase() === "application/json";
35385
+ }
35386
+ function decodeHeaderValue(raw) {
35387
+ const match = /^=\?base64\?(.*)\?=$/.exec(raw);
35388
+ if (!match) return raw;
35389
+ return Buffer.from(match[1] ?? "", "base64").toString("utf8");
35390
+ }
35391
+ function parseBody(body) {
35392
+ const fail4 = (status, code, message) => ({ ok: false, error: { status, code, message, id: null } });
35393
+ let value;
35394
+ try {
35395
+ value = JSON.parse(body);
35396
+ } catch {
35397
+ return fail4(400, ERR_PARSE, "Parse error");
35398
+ }
35399
+ if (Array.isArray(value)) return fail4(400, ERR_INVALID_REQUEST, "Batch requests are not supported");
35400
+ if (!isRecord8(value)) return fail4(400, ERR_INVALID_REQUEST, "The body must be a single JSON-RPC request or notification");
35401
+ const method = value.method;
35402
+ if (typeof method !== "string" && ("result" in value || "error" in value)) {
35403
+ return fail4(400, ERR_INVALID_REQUEST, "JSON-RPC responses must not be sent to the server");
35404
+ }
35405
+ if (value.jsonrpc !== "2.0" || typeof method !== "string") return fail4(400, ERR_INVALID_REQUEST, "Not a JSON-RPC 2.0 request");
35406
+ const hasId = "id" in value;
35407
+ const id = value.id;
35408
+ if (hasId && typeof id !== "string" && typeof id !== "number") return fail4(400, ERR_INVALID_REQUEST, "`id` must be a string or a number");
35409
+ if (value.params !== void 0 && !isRecord8(value.params)) return fail4(400, ERR_INVALID_PARAMS, "`params` must be an object");
35410
+ return {
35411
+ ok: true,
35412
+ request: {
35413
+ id: hasId ? id : void 0,
35414
+ method,
35415
+ params: isRecord8(value.params) ? value.params : {}
35416
+ }
35417
+ };
35418
+ }
35419
+ function validateRequest(request3, headers, supported, allowed) {
35420
+ const id = request3.id ?? null;
35421
+ const err = (status, code, message, data) => ({
35422
+ status,
35423
+ code,
35424
+ message,
35425
+ id,
35426
+ ...data !== void 0 ? { data } : {}
35427
+ });
35428
+ const { method, params } = request3;
35429
+ if (method === "initialize") {
35430
+ const requested = typeof params.protocolVersion === "string" ? params.protocolVersion : "unknown";
35431
+ return err(404, ERR_METHOD_NOT_FOUND, "Method not found: initialize (this endpoint is stateless, send server/discover)", {
35432
+ supported: [...supported],
35433
+ requested
35434
+ });
35435
+ }
35436
+ const meta = params._meta;
35437
+ const missing = [];
35438
+ if (!isRecord8(meta) || typeof meta[META_PROTOCOL_VERSION] !== "string") missing.push(META_PROTOCOL_VERSION);
35439
+ if (!isRecord8(meta) || !isRecord8(meta[META_CLIENT_CAPABILITIES])) missing.push(META_CLIENT_CAPABILITIES);
35440
+ if (!isRecord8(meta) || missing.length > 0) return err(400, ERR_INVALID_PARAMS, `Missing required _meta field(s): ${missing.join(", ")}`);
35441
+ const version = String(meta[META_PROTOCOL_VERSION]);
35442
+ if (!supported.includes(version)) {
35443
+ return err(400, ERR_UNSUPPORTED_VERSION, "Unsupported protocol version", { supported: [...supported], requested: version });
35444
+ }
35445
+ const headerVersion = headers["mcp-protocol-version"];
35446
+ if (headerVersion === void 0) return err(400, ERR_HEADER_MISMATCH, "Header mismatch: MCP-Protocol-Version header is missing");
35447
+ if (headerVersion !== version) {
35448
+ return err(400, ERR_HEADER_MISMATCH, `Header mismatch: MCP-Protocol-Version ${headerVersion} does not match _meta ${version}`);
35449
+ }
35450
+ const headerMethod = headers["mcp-method"];
35451
+ if (headerMethod === void 0) return err(400, ERR_HEADER_MISMATCH, "Header mismatch: Mcp-Method header is missing");
35452
+ if (headerMethod !== method) {
35453
+ return err(400, ERR_HEADER_MISMATCH, `Header mismatch: Mcp-Method ${headerMethod} does not match body ${method}`);
35454
+ }
35455
+ const source = NAME_HEADER_SOURCE.get(method);
35456
+ if (source !== void 0) {
35457
+ const expected = params[source];
35458
+ if (typeof expected !== "string") return err(400, ERR_INVALID_PARAMS, `Missing required params.${source}`);
35459
+ const rawName = headers["mcp-name"];
35460
+ if (rawName === void 0) return err(400, ERR_HEADER_MISMATCH, "Header mismatch: Mcp-Name header is missing");
35461
+ if (decodeHeaderValue(rawName) !== expected) {
35462
+ return err(400, ERR_HEADER_MISMATCH, `Header mismatch: Mcp-Name does not match params.${source}`);
35463
+ }
35464
+ }
35465
+ if (!allowed.has(method)) return err(404, ERR_METHOD_NOT_FOUND, `Method not found: ${method}`);
35466
+ return null;
35467
+ }
35468
+
35469
+ // src/mcp-modern/index.ts
35470
+ var DEFAULT_CACHE_POLICY = () => ({ ttlMs: 0, cacheScope: "private" });
35471
+ var JSON_HEADERS = { "content-type": "application/json" };
35472
+ function errorResponse(error, extraHeaders = {}) {
35473
+ return {
35474
+ status: error.status,
35475
+ headers: { ...JSON_HEADERS, ...extraHeaders },
35476
+ body: JSON.stringify({
35477
+ jsonrpc: "2.0",
35478
+ id: error.id,
35479
+ error: { code: error.code, message: error.message, ...error.data !== void 0 ? { data: error.data } : {} }
35480
+ })
35481
+ };
35482
+ }
35483
+ function okResponse(id, result) {
35484
+ return { status: 200, headers: { ...JSON_HEADERS }, body: JSON.stringify({ jsonrpc: "2.0", id, result }) };
35485
+ }
35486
+ function decorate(method, result, serverInfo, hints) {
35487
+ const meta = typeof result._meta === "object" && result._meta !== null && !Array.isArray(result._meta) ? result._meta : {};
35488
+ const resultType = typeof result.resultType === "string" ? result.resultType : "complete";
35489
+ const decorated = {
35490
+ ...result,
35491
+ resultType,
35492
+ _meta: { ...meta, [META_SERVER_INFO]: meta[META_SERVER_INFO] ?? serverInfo }
35493
+ };
35494
+ if (resultType === "complete" && CACHE_HINT_METHODS.has(method)) {
35495
+ decorated.ttlMs = typeof result.ttlMs === "number" ? result.ttlMs : hints.ttlMs;
35496
+ decorated.cacheScope = typeof result.cacheScope === "string" ? result.cacheScope : hints.cacheScope;
35497
+ }
35498
+ return decorated;
35499
+ }
35500
+ function fromThrown(error, id) {
35501
+ if (error instanceof BridgeError) {
35502
+ const message = error.message.replace(/^(?:MCP error -?\d+: )+/, "");
35503
+ const status = error.code === ERR_METHOD_NOT_FOUND ? 404 : 200;
35504
+ return errorResponse({ status, code: error.code, message, id, ...error.data !== void 0 ? { data: error.data } : {} });
35505
+ }
35506
+ return errorResponse({ status: 500, code: ERR_INTERNAL, message: "Internal error", id });
35507
+ }
35508
+ async function answer(request3, bridge, supported, hints, signal) {
35509
+ if (request3.method === "server/discover") {
35510
+ const capabilities = { ...bridge.capabilities };
35511
+ delete capabilities.logging;
35512
+ return decorate(
35513
+ "server/discover",
35514
+ {
35515
+ supportedVersions: [...supported],
35516
+ capabilities,
35517
+ ...bridge.instructions ? { instructions: bridge.instructions } : {}
35518
+ },
35519
+ bridge.serverInfo,
35520
+ hints
35521
+ );
35522
+ }
35523
+ return decorate(request3.method, await bridge.request(request3.method, forwardParams(request3.params), signal), bridge.serverInfo, hints);
35524
+ }
35525
+ async function handleModernRequest(input, deps2) {
35526
+ const supported = deps2.supportedVersions ?? DEFAULT_SUPPORTED_VERSIONS;
35527
+ const cachePolicy = deps2.cachePolicy ?? DEFAULT_CACHE_POLICY;
35528
+ const headers = normalizeHeaders(input.headers);
35529
+ if (input.method !== "POST") {
35530
+ return errorResponse({ status: 405, code: ERR_INVALID_REQUEST, message: "Only POST is supported", id: null }, { allow: "POST" });
35531
+ }
35532
+ if (!acceptsJson(headers["accept"])) {
35533
+ return errorResponse({ status: 406, code: ERR_INVALID_REQUEST, message: "Accept must allow application/json", id: null });
35534
+ }
35535
+ if (!isJsonContentType(headers["content-type"])) {
35536
+ return errorResponse({ status: 415, code: ERR_INVALID_REQUEST, message: "Content-Type must be application/json", id: null });
35537
+ }
35538
+ const parsed = parseBody(input.body);
35539
+ if (!parsed.ok) return errorResponse(parsed.error);
35540
+ const request3 = parsed.request;
35541
+ if (request3.id === void 0) return { status: 202, headers: {} };
35542
+ const invalid = validateRequest(request3, headers, supported, ALLOWED_METHODS);
35543
+ if (invalid) return errorResponse(invalid);
35544
+ const id = request3.id;
35545
+ let bridge;
35546
+ try {
35547
+ bridge = await openBridge(await deps2.createServer());
35548
+ const result = await answer({ ...request3, id }, bridge, supported, cachePolicy(request3.method), deps2.signal);
35549
+ if (deps2.signal?.aborted) return { status: 499, headers: {} };
35550
+ const response = okResponse(id, result);
35551
+ if ((request3.method === "server/discover" || request3.method === "tools/list") && deps2.onObserved) {
35552
+ try {
35553
+ deps2.onObserved(request3.method, { jsonrpc: "2.0", id, result });
35554
+ } catch {
35555
+ }
35556
+ }
35557
+ return response;
35558
+ } catch (error) {
35559
+ if (deps2.signal?.aborted) return { status: 499, headers: {} };
35560
+ const response = fromThrown(error, id);
35561
+ if ((request3.method === "server/discover" || request3.method === "tools/list") && deps2.onObserved && response.body) {
35562
+ try {
35563
+ deps2.onObserved(request3.method, JSON.parse(response.body));
35564
+ } catch {
35565
+ }
35566
+ }
35567
+ return response;
35568
+ } finally {
35569
+ await bridge?.close();
35570
+ }
35571
+ }
35107
35572
  init_sessions();
35108
35573
  init_session_retirement();
35109
35574
  init_session_message();
@@ -35795,7 +36260,7 @@ function createSentinelStore(opts) {
35795
36260
  const persist = opts?.persist ?? opts?.filePath !== void 0;
35796
36261
  let persistTimer = null;
35797
36262
  let secretsPersistTimer = null;
35798
- const load = () => {
36263
+ const load2 = () => {
35799
36264
  const out = /* @__PURE__ */ new Map();
35800
36265
  if (!persist) return out;
35801
36266
  let raw;
@@ -35819,7 +36284,7 @@ function createSentinelStore(opts) {
35819
36284
  }
35820
36285
  return out;
35821
36286
  };
35822
- const sentinels = load();
36287
+ const sentinels = load2();
35823
36288
  const loadSecrets = () => {
35824
36289
  const out = /* @__PURE__ */ new Map();
35825
36290
  if (!persist) return out;
@@ -36039,6 +36504,15 @@ function createSentinelStore(opts) {
36039
36504
  function singleMatch(subject, types) {
36040
36505
  return [{ subject, ...types ? { types } : {} }];
36041
36506
  }
36507
+ var SentinelBackingExpiredError = class extends Error {
36508
+ code = "backing_subscription_expired";
36509
+ remoteId;
36510
+ constructor(message, remoteId) {
36511
+ super(message);
36512
+ this.name = "SentinelBackingExpiredError";
36513
+ if (remoteId !== void 0) this.remoteId = remoteId;
36514
+ }
36515
+ };
36042
36516
  function deliveryPreferenceFor(provider, intervalMs) {
36043
36517
  if (provider.preferredDelivery) return provider.preferredDelivery(intervalMs);
36044
36518
  return provider.capabilities.poll ? { mode: "poll", intervalMs } : { mode: "push" };
@@ -36602,7 +37076,7 @@ function compactPrState(snapshot, extra) {
36602
37076
  const slim = snapshot.state !== "open" && extra.closedEmitted ? { fetchedAt: snapshot.fetchedAt, state: snapshot.state, reviews: [], checks: [] } : snapshot;
36603
37077
  return { snapshot: slim, ...extra };
36604
37078
  }
36605
- function decorate(events, pr) {
37079
+ function decorate2(events, pr) {
36606
37080
  const title = pr.title.length > 80 ? `${pr.title.slice(0, 77)}...` : pr.title;
36607
37081
  return events.map((e) => ({
36608
37082
  ...e,
@@ -36666,7 +37140,7 @@ function diffRepoPr(prev, pr, repo) {
36666
37140
  closedEmitted = true;
36667
37141
  }
36668
37142
  return {
36669
- events: decorate(events, pr),
37143
+ events: decorate2(events, pr),
36670
37144
  next: compactPrState(now, { draft: pr.draft, closedEmitted, ...ciReported ? { ciReported } : {} })
36671
37145
  };
36672
37146
  }
@@ -36698,7 +37172,7 @@ function diffRepo(cursor, prs, repo, fetchedAt) {
36698
37172
  })
36699
37173
  ];
36700
37174
  if (closed) fresh.push(closedEvent(pr.snapshot, { repo, number: pr.number, subject }, false));
36701
- events.push(...decorate(fresh, pr));
37175
+ events.push(...decorate2(fresh, pr));
36702
37176
  }
36703
37177
  const settled = verdict === "success" || verdict === "failure" || verdict === "neutral";
36704
37178
  next[key] = compactPrState(pr.snapshot, {
@@ -37636,11 +38110,11 @@ function verifySignatureV2(secret, headers, body, nowMs, skewMs = SIGNATURE_V2_S
37636
38110
  if (a.length !== b.length || !timingSafeEqual(a, b)) return { ok: false, reason: "signature_mismatch" };
37637
38111
  return { ok: true, timestampSec };
37638
38112
  }
37639
- var isRecord7 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
38113
+ var isRecord9 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
37640
38114
  function toSentinelEvent(raw, seq) {
37641
- if (!isRecord7(raw)) return null;
38115
+ if (!isRecord9(raw)) return null;
37642
38116
  const { id, source, type, subject, time, data, summary, subjects, terminal, consumerref } = raw;
37643
- if (raw.specversion !== "1.0" || typeof id !== "string" || id === "" || typeof source !== "string" || typeof type !== "string" || type === "" || typeof subject !== "string" || subject === "" || typeof time !== "string" || !isRecord7(data) || typeof summary !== "string" || !Array.isArray(subjects) || !subjects.every((s) => typeof s === "string") || typeof terminal !== "boolean") {
38117
+ if (raw.specversion !== "1.0" || typeof id !== "string" || id === "" || typeof source !== "string" || typeof type !== "string" || type === "" || typeof subject !== "string" || subject === "" || typeof time !== "string" || !isRecord9(data) || typeof summary !== "string" || !Array.isArray(subjects) || !subjects.every((s) => typeof s === "string") || typeof terminal !== "boolean") {
37644
38118
  return null;
37645
38119
  }
37646
38120
  const envSeq = typeof raw.seq === "number" ? raw.seq : seq;
@@ -37686,9 +38160,36 @@ function stateOf(handle) {
37686
38160
  mode: s.mode === "push" ? "push" : "poll",
37687
38161
  ...typeof s.consumerRef === "string" ? { consumerRef: s.consumerRef } : {},
37688
38162
  ...typeof s.hookKey === "string" ? { hookKey: s.hookKey } : {},
37689
- ...typeof s.callbackSecret === "string" ? { callbackSecret: s.callbackSecret } : {}
38163
+ ...typeof s.callbackSecret === "string" ? { callbackSecret: s.callbackSecret } : {},
38164
+ ...s.signature === "v2" ? { signature: "v2" } : {}
37690
38165
  };
37691
38166
  }
38167
+ function excerptOf(envelope) {
38168
+ if (!isRecord9(envelope)) return void 0;
38169
+ const cap = (v) => typeof v === "string" ? v.slice(0, 200) : void 0;
38170
+ const out = {};
38171
+ const id = cap(envelope.id);
38172
+ const type = cap(envelope.type);
38173
+ const subject = cap(envelope.subject);
38174
+ const source = cap(envelope.source);
38175
+ if (id !== void 0) out.id = id;
38176
+ if (type !== void 0) out.type = type;
38177
+ if (subject !== void 0) out.subject = subject;
38178
+ if (source !== void 0) out.source = source;
38179
+ return Object.keys(out).length > 0 ? out : void 0;
38180
+ }
38181
+ function whyNotEnvelope(envelope) {
38182
+ if (envelope === void 0 || envelope === null) return "item has no envelope";
38183
+ if (!isRecord9(envelope)) return "envelope is not a JSON object";
38184
+ const missing = [];
38185
+ if (envelope.specversion !== "1.0") missing.push("specversion");
38186
+ for (const k of ["id", "type", "subject"]) if (typeof envelope[k] !== "string" || envelope[k] === "") missing.push(k);
38187
+ for (const k of ["source", "time", "summary"]) if (typeof envelope[k] !== "string") missing.push(k);
38188
+ if (!isRecord9(envelope.data)) missing.push("data");
38189
+ if (!Array.isArray(envelope.subjects) || !envelope.subjects.every((x) => typeof x === "string")) missing.push("subjects");
38190
+ if (typeof envelope.terminal !== "boolean") missing.push("terminal");
38191
+ return `envelope failed validation (invalid or missing: ${missing.join(", ") || "unknown"})`;
38192
+ }
37692
38193
  var SUBJECT_SCHEME_RE = /^([A-Za-z][A-Za-z0-9_-]*):/;
37693
38194
  function defaultTypesFor(subject) {
37694
38195
  return subject.startsWith("github:") ? [...GITHUB_DEFAULT_PR_TYPES] : ["*"];
@@ -37746,7 +38247,7 @@ function agentpushSentinelProvider(opts = {}) {
37746
38247
  json3 = void 0;
37747
38248
  }
37748
38249
  if (!res.ok) {
37749
- const detail = isRecord7(json3) && typeof json3.error === "string" ? json3.error : text13.slice(0, 200);
38250
+ const detail = isRecord9(json3) && typeof json3.error === "string" ? json3.error : text13.slice(0, 200);
37750
38251
  throw new AgentpushHttpError(res.status, `agentpush ${method} ${path} \u2192 HTTP ${res.status}${detail ? `: ${detail}` : ""}`);
37751
38252
  }
37752
38253
  return json3;
@@ -37779,6 +38280,7 @@ function agentpushSentinelProvider(opts = {}) {
37779
38280
  // Credentials come from `setup_sentinel_provider` OR an imported alias, so
37780
38281
  // "set up" is decided by `readiness()`, not the kit's ledger/creds check.
37781
38282
  requiresSetup: false,
38283
+ exclusiveRemote: true,
37782
38284
  capabilities: {
37783
38285
  subjects: ["*"],
37784
38286
  push: true,
@@ -37832,7 +38334,7 @@ function agentpushSentinelProvider(opts = {}) {
37832
38334
  ...push ? { callback_url: callbackUrl2(delivery.callbackUrl, hookKey), callback_secret: callbackSecret } : {}
37833
38335
  }
37834
38336
  });
37835
- const id = isRecord7(json3) && isRecord7(json3.subscription) ? json3.subscription.id : void 0;
38337
+ const id = isRecord9(json3) && isRecord9(json3.subscription) ? json3.subscription.id : void 0;
37836
38338
  if (typeof id !== "string" || id === "") throw new Error("agentpush: create returned no subscription id");
37837
38339
  return {
37838
38340
  provider: AGENTPUSH_SLUG,
@@ -37841,6 +38343,7 @@ function agentpushSentinelProvider(opts = {}) {
37841
38343
  state: {
37842
38344
  mode: push ? "push" : "poll",
37843
38345
  consumerRef,
38346
+ signature: "v2",
37844
38347
  ...push ? { hookKey, callbackSecret } : {}
37845
38348
  }
37846
38349
  };
@@ -37859,7 +38362,7 @@ function agentpushSentinelProvider(opts = {}) {
37859
38362
  let sub;
37860
38363
  try {
37861
38364
  const json3 = await api("GET", subPath(handle));
37862
- sub = isRecord7(json3) && isRecord7(json3.subscription) ? json3.subscription : void 0;
38365
+ sub = isRecord9(json3) && isRecord9(json3.subscription) ? json3.subscription : void 0;
37863
38366
  } catch (err) {
37864
38367
  if (isHttp(err, 404)) {
37865
38368
  throw new Error(`agentpush subscription ${handle.remoteId} no longer exists \u2014 re-create the sentinel`);
@@ -37887,10 +38390,34 @@ function agentpushSentinelProvider(opts = {}) {
37887
38390
  if (!isHttp(err, 404)) throw err;
37888
38391
  }
37889
38392
  },
38393
+ /**
38394
+ * Extend the remote subscription's lifetime. `PATCH` accepts `until`
38395
+ * without rejecting an already-expired row, so the returned `status` is
38396
+ * checked: anything but `active` (or a 404) means the backing subscription
38397
+ * is gone and must be recreated — never silently re-provisioned here.
38398
+ */
38399
+ async renew(handle, until) {
38400
+ if (!handle.remoteId) throw new SentinelBackingExpiredError("agentpush: handle has no subscription id");
38401
+ let json3;
38402
+ try {
38403
+ json3 = await api("PATCH", subPath(handle), { body: { until } });
38404
+ } catch (err) {
38405
+ if (isHttp(err, 404)) {
38406
+ throw new SentinelBackingExpiredError(`the agentpush subscription ${handle.remoteId} was deleted`, handle.remoteId);
38407
+ }
38408
+ throw err;
38409
+ }
38410
+ const sub = isRecord9(json3) && isRecord9(json3.subscription) ? json3.subscription : void 0;
38411
+ const status = sub && typeof sub.status === "string" ? sub.status : void 0;
38412
+ if (status === "expired" || status === "deleted") {
38413
+ throw new SentinelBackingExpiredError(`the agentpush subscription ${handle.remoteId} is ${status}`, handle.remoteId);
38414
+ }
38415
+ return { ...handle };
38416
+ },
37890
38417
  async status(handle) {
37891
38418
  try {
37892
38419
  const json3 = await api("GET", subPath(handle));
37893
- const sub = isRecord7(json3) && isRecord7(json3.subscription) ? json3.subscription : void 0;
38420
+ const sub = isRecord9(json3) && isRecord9(json3.subscription) ? json3.subscription : void 0;
37894
38421
  const status = sub && typeof sub.status === "string" ? sub.status : "unknown";
37895
38422
  if (status === "active") return { ok: true };
37896
38423
  return { ok: false, detail: `the agentpush subscription is ${status}` };
@@ -37902,18 +38429,35 @@ function agentpushSentinelProvider(opts = {}) {
37902
38429
  const after = handle.cursor ?? "0";
37903
38430
  if (stateOf(handle).mode === "push") return { events: [], cursor: after };
37904
38431
  const json3 = await api("GET", `${subPath(handle)}/events`, { query: { after, limit: Math.max(1, Math.min(limit, 1e3)) } });
37905
- const items = isRecord7(json3) && Array.isArray(json3.items) ? json3.items : [];
38432
+ const items = isRecord9(json3) && Array.isArray(json3.items) ? json3.items : [];
37906
38433
  const events = [];
38434
+ const malformed = [];
37907
38435
  let cursor = Number(after);
37908
38436
  if (!Number.isFinite(cursor)) cursor = 0;
37909
38437
  for (const item of items) {
37910
- if (!isRecord7(item)) continue;
37911
- const seq = typeof item.seq === "number" ? item.seq : void 0;
38438
+ const seq = isRecord9(item) && typeof item.seq === "number" ? item.seq : void 0;
37912
38439
  if (seq !== void 0 && seq > cursor) cursor = seq;
37913
- const event = toSentinelEvent(item.envelope, seq);
37914
- if (event) events.push(event);
38440
+ const envelope = isRecord9(item) ? item.envelope : void 0;
38441
+ const event = toSentinelEvent(envelope, seq);
38442
+ if (event) {
38443
+ events.push(event);
38444
+ continue;
38445
+ }
38446
+ let serialized = "";
38447
+ try {
38448
+ serialized = JSON.stringify(item) ?? "";
38449
+ } catch {
38450
+ }
38451
+ const excerpt = excerptOf(envelope);
38452
+ malformed.push({
38453
+ ...seq !== void 0 ? { seq } : {},
38454
+ ...isRecord9(item) && typeof item.delivery_id === "string" ? { remoteDeliveryId: item.delivery_id } : {},
38455
+ error: isRecord9(item) ? whyNotEnvelope(envelope) : "poll item is not a JSON object",
38456
+ ...excerpt ? { excerpt } : {},
38457
+ ...serialized ? { digest: createHash("sha256").update(serialized).digest("hex"), bytes: Buffer.byteLength(serialized) } : {}
38458
+ });
37915
38459
  }
37916
- return { events, cursor: String(cursor) };
38460
+ return { events, cursor: String(cursor), ...malformed.length > 0 ? { malformed } : {} };
37917
38461
  },
37918
38462
  /** Called by the runtime after a polled batch is delivered. Nothing new
37919
38463
  * (cursor unchanged) is a no-op — no request on an empty tick. */
@@ -37930,6 +38474,7 @@ function agentpushSentinelProvider(opts = {}) {
37930
38474
  const signature = headerValue3(req.headers, "x-agentpush-signature");
37931
38475
  if (!signature || signature.trim() === "") return { ok: false, reason: "missing_signature" };
37932
38476
  if (signature.trim().startsWith("sha256=")) {
38477
+ if (st.signature === "v2") return { ok: false, reason: "legacy_signature_rejected" };
37933
38478
  const legacy = verifyInboundSignature("agentpush", {
37934
38479
  rawBody: req.rawBody,
37935
38480
  headers: req.headers,
@@ -38360,6 +38905,13 @@ async function createSentinelWatch(deps2, input) {
38360
38905
  async function cancelSentinelWatch(deps2, id) {
38361
38906
  const sentinel = deps2.store.get(id);
38362
38907
  if (!sentinel) return false;
38908
+ if (deps2.tombstones) {
38909
+ try {
38910
+ await deps2.tombstones.cancel(sentinel.handle, () => deps2.store.remove(id));
38911
+ return true;
38912
+ } catch {
38913
+ }
38914
+ }
38363
38915
  const provider = await deps2.resolveProvider(sentinel.provider);
38364
38916
  if (provider) {
38365
38917
  try {
@@ -38402,7 +38954,7 @@ var fail2 = (code, message) => ({
38402
38954
  var urgencyField2 = z.enum(MESSAGE_URGENCIES);
38403
38955
  var untilField = z.enum(["subject_terminal", "never"]);
38404
38956
  function registerSentinelTools(server, opts) {
38405
- const { store, runtime, resolveProvider, isSessionAlive, callerSessionId, activeIntervalMs } = opts;
38957
+ const { store, runtime, resolveProvider, isSessionAlive, callerSessionId, activeIntervalMs, tombstones } = opts;
38406
38958
  server.tool(
38407
38959
  "sentinel_watch",
38408
38960
  "Watch a subject \u2014 a GitHub PR/repo (default) or ANOTHER SESSION's own lifecycle \u2014 and deliver matching events as a `system`/`notice` message into a session's AIP-46 inbox. Provide either `prUrl` (sugar: `https://github.com/o/r/pull/N` -> subject `github:o/r#N`, the default PR type set, and `until: subject_terminal`) or a raw `subject`: a GitHub one (`github:o/r#N`, `github:o/r`), or `session:<id>` to watch another session \u2014 woken on its turn-end, awaiting-input, or exit, even if it never calls `message_parent`, with the watch self-expiring once it exits (`until` defaults to `subject_terminal` for this scheme too). Prefer this over `session_follow` for a single, known target that should auto-expire on exit; prefer `session_follow` for a broad selector (`all`/`cwdPrefix`) with coalesced digests across many sessions. `sessionId` (the DELIVERY target) defaults to the calling session \u2014 distinct from the `<id>` inside a `session:<id>` subject (the WATCHED session). `provider` picks the backend: `local-gh` (poll every ~15-60s over the host's authenticated `gh` CLI, zero infra), `webhook` (near-real-time push via a GitHub repo hook; needs a public daemon URL \u2014 a named tunnel or AGENTPROTO_PUBLIC_URL \u2014 and a `gh` token with admin:repo_hook), `agentpush` (hosted, durable subscription: events queue server-side across daemon downtime; needs an agentpush API key; one subject per sentinel) or `session` (in-process, zero infra, no credentials \u2014 the only backend that understands a `session:<id>` subject, and picked automatically for one); see `list_sentinel_adapters` for readiness. Omitted for a GitHub subject, it defaults to `agentpush` when set up, else `webhook` when a stable public URL exists and webhook is ready, else `local-gh`.",
@@ -38434,7 +38986,7 @@ function registerSentinelTools(server, opts) {
38434
38986
  "Stop a sentinel: best-effort cancels the provider-side watch, then removes the record. Idempotent-ish: an unknown id is reported, not thrown.",
38435
38987
  { id: z.string().describe("Sentinel id (sen_...).") },
38436
38988
  async ({ id }) => {
38437
- const removed = await cancelSentinelWatch({ store, resolveProvider }, id);
38989
+ const removed = await cancelSentinelWatch({ store, resolveProvider, ...tombstones ? { tombstones } : {} }, id);
38438
38990
  if (!removed) return fail2("not_found", `sentinel_unwatch: no sentinel "${id}"`);
38439
38991
  return ok2({ removed: true, id });
38440
38992
  }
@@ -38495,7 +39047,7 @@ function createSessionFollowStore(opts) {
38495
39047
  const debounceMs = opts?.debounceMs ?? PERSIST_DEBOUNCE_MS4;
38496
39048
  const persist = opts?.persist ?? true;
38497
39049
  let persistTimer = null;
38498
- const load = () => {
39050
+ const load2 = () => {
38499
39051
  const out = /* @__PURE__ */ new Map();
38500
39052
  if (!persist) return out;
38501
39053
  let raw;
@@ -38521,7 +39073,7 @@ function createSessionFollowStore(opts) {
38521
39073
  }
38522
39074
  return out;
38523
39075
  };
38524
- const follows = load();
39076
+ const follows = load2();
38525
39077
  const snapshot = () => {
38526
39078
  const out = {};
38527
39079
  for (const [id, follow] of follows.entries()) out[id] = follow;
@@ -38803,7 +39355,7 @@ function createHostRegistry(deps2) {
38803
39355
  let probesRunning = 0;
38804
39356
  const probeWaiters = [];
38805
39357
  async function withProbeSlot(fn) {
38806
- if (probesRunning >= probeConcurrency) await new Promise((resolve45) => probeWaiters.push(resolve45));
39358
+ if (probesRunning >= probeConcurrency) await new Promise((resolve47) => probeWaiters.push(resolve47));
38807
39359
  probesRunning++;
38808
39360
  try {
38809
39361
  return await fn();
@@ -39342,7 +39894,7 @@ function createHostRegistry(deps2) {
39342
39894
  };
39343
39895
  }
39344
39896
  async function awaitTunnelHello(client, sink, record2, closeReason) {
39345
- return new Promise((resolve45, reject) => {
39897
+ return new Promise((resolve47, reject) => {
39346
39898
  if (!sink.isOpen) {
39347
39899
  reject(
39348
39900
  new Error(
@@ -39361,7 +39913,7 @@ async function awaitTunnelHello(client, sink, record2, closeReason) {
39361
39913
  client.ready().then(
39362
39914
  () => {
39363
39915
  offClose();
39364
- resolve45();
39916
+ resolve47();
39365
39917
  },
39366
39918
  (err) => {
39367
39919
  offClose();
@@ -39406,13 +39958,13 @@ function isLegacyJoinedShape(rec) {
39406
39958
  return rec.addedVia === void 0 && rec.name === rec.fingerprint && rec.provider === void 0 && rec.sandboxId === void 0 && rec.labels === void 0 && rec.lastSeen === rec.createdAt;
39407
39959
  }
39408
39960
  function withTimeout(p, ms, what) {
39409
- return new Promise((resolve45, reject) => {
39961
+ return new Promise((resolve47, reject) => {
39410
39962
  const timer = setTimeout(() => reject(new Error(`${what} timed out after ${ms}ms`)), ms);
39411
39963
  if (typeof timer.unref === "function") timer.unref();
39412
39964
  p.then(
39413
39965
  (v) => {
39414
39966
  clearTimeout(timer);
39415
- resolve45(v);
39967
+ resolve47(v);
39416
39968
  },
39417
39969
  (e) => {
39418
39970
  clearTimeout(timer);
@@ -39641,7 +40193,7 @@ function isValidPrompt(prompt) {
39641
40193
  return prompt !== null && typeof prompt === "object";
39642
40194
  }
39643
40195
  function sleep(ms) {
39644
- return new Promise((resolve45) => setTimeout(resolve45, ms));
40196
+ return new Promise((resolve47) => setTimeout(resolve47, ms));
39645
40197
  }
39646
40198
  function httpErrorMessage(body) {
39647
40199
  const raw = Buffer.from(body).toString("utf8");
@@ -40374,6 +40926,8 @@ init_transcript_export();
40374
40926
  init_spawn_defaults();
40375
40927
  init_app_boundary();
40376
40928
  var WORKFLOW_PROMPT_SOURCE = "workflow:agent-step";
40929
+ var TRANSPORT_END_REASONS = /* @__PURE__ */ new Set([void 0, "crashed", "daemon-restart"]);
40930
+ var TRANSPORT_CLOSED_RE = /\bACP connection closed\b/i;
40377
40931
  function withReviewWorktreeZones(boundary, cwd) {
40378
40932
  const zones = reviewWorktreeReadZones(cwd);
40379
40933
  if (!zones) return boundary;
@@ -40681,7 +41235,35 @@ var SessionsRegistryAgentHost = class {
40681
41235
  * synchronously, before the event bus can fire the rejection asynchronously.
40682
41236
  */
40683
41237
  async sendPromptAndWait(sessionId, prompt) {
40684
- await Promise.all([this.waitTurnEnd(sessionId), this.registry.sendPrompt(sessionId, prompt, { source: WORKFLOW_PROMPT_SOURCE })]);
41238
+ await Promise.all([
41239
+ this.waitTurnEnd(sessionId),
41240
+ this.registry.sendPrompt(sessionId, prompt, { source: WORKFLOW_PROMPT_SOURCE }).catch((err) => {
41241
+ throw this.classifyPromptFailure(sessionId, err);
41242
+ })
41243
+ ]);
41244
+ }
41245
+ /** True when `sessionId` dying (with `reason`) is the session's transport
41246
+ * failing under the step — not this host's own cancel/release, and not a
41247
+ * deliberate end (see {@link TRANSPORT_END_REASONS}). */
41248
+ diedUnderStep(sessionId, reason) {
41249
+ if (this.opts?.signal?.aborted) return false;
41250
+ if (!this.unreleased.has(sessionId)) return false;
41251
+ return TRANSPORT_END_REASONS.has(reason);
41252
+ }
41253
+ /** The error a wait rejects with when the session ends mid-turn. */
41254
+ sessionEndedError(sessionId, status, reason) {
41255
+ const message = `session ${sessionId} ended with status '${status}'${reason ? ` (${reason})` : ""}`;
41256
+ return this.diedUnderStep(sessionId, reason) ? new AgentSessionLostError(sessionId, message) : new Error(message);
41257
+ }
41258
+ /** `registry.sendPrompt` rejected: a closed ACP connection or an already-
41259
+ * dead session is a transport loss ({@link AgentSessionLostError});
41260
+ * anything else propagates unchanged. */
41261
+ classifyPromptFailure(sessionId, err) {
41262
+ if (!(err instanceof Error)) return err;
41263
+ const desc = this.registry.get(sessionId);
41264
+ const dead = err instanceof SessionNotAliveError || TRANSPORT_CLOSED_RE.test(err.message) || desc?.status === "killed" || desc?.status === "error";
41265
+ if (!dead || !this.diedUnderStep(sessionId, desc?.endedReason)) return err;
41266
+ return new AgentSessionLostError(sessionId, `session ${sessionId} lost before its turn ended \u2014 ${err.message}`, err);
40685
41267
  }
40686
41268
  resolveByLabel(stepId) {
40687
41269
  return this.sessionsByLabel.get(stepId);
@@ -40769,15 +41351,15 @@ var SessionsRegistryAgentHost = class {
40769
41351
  }
40770
41352
  // ── Internal helpers ──────────────────────────────────────────────────
40771
41353
  waitTurnEnd(sessionId) {
40772
- return new Promise((resolve45, reject) => {
41354
+ return new Promise((resolve47, reject) => {
40773
41355
  const unsubs = [];
40774
41356
  const done = () => {
40775
41357
  for (const u of unsubs) u();
40776
- resolve45();
41358
+ resolve47();
40777
41359
  };
40778
41360
  const fail4 = (reason) => {
40779
41361
  for (const u of unsubs) u();
40780
- reject(new Error(reason));
41362
+ reject(typeof reason === "string" ? new Error(reason) : reason);
40781
41363
  };
40782
41364
  unsubs.push(
40783
41365
  this.sessionEvents.on("session:turn-end", (ev) => {
@@ -40804,7 +41386,7 @@ var SessionsRegistryAgentHost = class {
40804
41386
  this.sessionEvents.on("session:exited", (ev) => {
40805
41387
  if (ev.sessionId !== sessionId) return;
40806
41388
  if (ev.status === "killed" || ev.status === "error") {
40807
- fail4(`session ${sessionId} ended with status '${ev.status}'`);
41389
+ fail4(this.sessionEndedError(sessionId, ev.status, ev.reason));
40808
41390
  } else {
40809
41391
  done();
40810
41392
  }
@@ -40814,7 +41396,7 @@ var SessionsRegistryAgentHost = class {
40814
41396
  if (desc?.status === "exited" || desc?.awaitingInput === true) {
40815
41397
  done();
40816
41398
  } else if (desc?.status === "killed" || desc?.status === "error") {
40817
- fail4(`session ${sessionId} ended with status '${desc.status}'`);
41399
+ fail4(this.sessionEndedError(sessionId, desc.status, desc.endedReason));
40818
41400
  }
40819
41401
  });
40820
41402
  }
@@ -40824,11 +41406,11 @@ function sanitize(cacheKey) {
40824
41406
  const cleaned = cacheKey.replace(/[^a-zA-Z0-9._-]/g, "_");
40825
41407
  return cleaned.length > 0 ? cleaned : "default";
40826
41408
  }
40827
- function isRecord8(v) {
41409
+ function isRecord10(v) {
40828
41410
  return typeof v === "object" && v !== null && !Array.isArray(v);
40829
41411
  }
40830
41412
  function isEntry2(v) {
40831
- return isRecord8(v) && typeof v.resolvedInputHash === "string" && "output" in v;
41413
+ return isRecord10(v) && typeof v.resolvedInputHash === "string" && "output" in v;
40832
41414
  }
40833
41415
  function createFileStepCache(cacheKey, opts) {
40834
41416
  const dir = opts?.dir ?? DEFAULT_CACHE_DIR();
@@ -40841,7 +41423,7 @@ function createFileStepCache(cacheKey, opts) {
40841
41423
  } catch {
40842
41424
  return {};
40843
41425
  }
40844
- if (!isRecord8(parsed)) return {};
41426
+ if (!isRecord10(parsed)) return {};
40845
41427
  const out = {};
40846
41428
  for (const [k, v] of Object.entries(parsed)) if (isEntry2(v)) out[k] = v;
40847
41429
  return out;
@@ -41137,6 +41719,7 @@ function findStepDef(defs, stepId) {
41137
41719
  var DEFAULT_PERSIST_PATH2 = () => join(homedir(), ".agentproto", "workflow-runs.json");
41138
41720
  var DEFAULT_LEASE_TTL_MS = 6e4;
41139
41721
  var DEFAULT_HEARTBEAT_INTERVAL_MS = 15e3;
41722
+ var DEFAULT_PERSIST_MIN_INTERVAL_MS = 2e3;
41140
41723
  function cancelledStageStatus(stage) {
41141
41724
  return stage.steps.some((s) => s.status === "failed") ? "failed" : "cancelled";
41142
41725
  }
@@ -41228,9 +41811,7 @@ function saveRuns(runs, persistPath) {
41228
41811
  try {
41229
41812
  mkdirSync(dirname(persistPath), { recursive: true });
41230
41813
  const payload = JSON.stringify(
41231
- Array.from(runs.values()).map((s) => s.run),
41232
- null,
41233
- 2
41814
+ Array.from(runs.values()).map((s) => s.run)
41234
41815
  ) + "\n";
41235
41816
  const tmp = `${persistPath}.tmp.${process.pid}`;
41236
41817
  writeFileSync(tmp, payload, "utf8");
@@ -41306,7 +41887,7 @@ function createOnEscalate(state, persist) {
41306
41887
  state.run.status = "awaiting-input";
41307
41888
  persist();
41308
41889
  try {
41309
- return await new Promise((resolve45, reject) => {
41890
+ return await new Promise((resolve47, reject) => {
41310
41891
  const timeoutMs = policy.timeoutMs ?? 3e5;
41311
41892
  const timer = setTimeout(() => {
41312
41893
  state.pendingResolve = void 0;
@@ -41318,7 +41899,7 @@ function createOnEscalate(state, persist) {
41318
41899
  resolver: (response) => {
41319
41900
  clearTimeout(timer);
41320
41901
  state.pendingResolve = void 0;
41321
- resolve45(response);
41902
+ resolve47(response);
41322
41903
  }
41323
41904
  };
41324
41905
  });
@@ -41454,7 +42035,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
41454
42035
  // `resolveApproval` (wired to `workflow_escalation_resolve`'s approval
41455
42036
  // form); the decision lands on the app ledger (`kind: "approval"`,
41456
42037
  // `by: "human"`) per the trame rule.
41457
- approve: (req) => new Promise((resolve45) => {
42038
+ approve: (req) => new Promise((resolve47) => {
41458
42039
  const approvalId = `wfappr_${randomUUID()}`;
41459
42040
  const requestedAt = (/* @__PURE__ */ new Date()).toISOString();
41460
42041
  state.run.status = "awaiting-approval";
@@ -41511,7 +42092,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
41511
42092
  runId: state.run.runId
41512
42093
  }
41513
42094
  });
41514
- resolve45(decision);
42095
+ resolve47(decision);
41515
42096
  };
41516
42097
  if (req.timeoutMs !== void 0) {
41517
42098
  timer = setTimeout(() => {
@@ -41528,7 +42109,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
41528
42109
  // resumes through `resumeSuspend` (workflow_escalation_resolve's
41529
42110
  // suspend form); after a daemon restart the run stays suspended and
41530
42111
  // the pending entry is re-registered below.
41531
- resume: (req) => new Promise((resolve45) => {
42112
+ resume: (req) => new Promise((resolve47) => {
41532
42113
  const since = (/* @__PURE__ */ new Date()).toISOString();
41533
42114
  state.run.status = "awaiting-input";
41534
42115
  state.run.awaitingSuspend = {
@@ -41566,7 +42147,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
41566
42147
  });
41567
42148
  eventLog?.append({ stepId: req.stepId, type: "step.resumed", data: {} });
41568
42149
  eventLog?.append({ type: "run.resumed", data: { stepId: req.stepId } });
41569
- resolve45(payload);
42150
+ resolve47(payload);
41570
42151
  };
41571
42152
  signal.addEventListener("abort", onAbort, { once: true });
41572
42153
  state.pendingSuspend = { stepId: req.stepId, resolve: finish };
@@ -41582,7 +42163,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
41582
42163
  // read of the turn's text). On resume, `execAgentStep` sends the
41583
42164
  // validated payload back to the SAME session as its next prompt and
41584
42165
  // re-applies the outcome rule — this hook only parks and resolves.
41585
- onInputRequired: (req) => new Promise((resolve45) => {
42166
+ onInputRequired: (req) => new Promise((resolve47) => {
41586
42167
  const since = (/* @__PURE__ */ new Date()).toISOString();
41587
42168
  const suspend = {
41588
42169
  reason: "input-required",
@@ -41635,7 +42216,7 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
41635
42216
  });
41636
42217
  eventLog?.append({ stepId: req.stepId, type: "step.resumed", data: {} });
41637
42218
  eventLog?.append({ type: "run.resumed", data: { stepId: req.stepId } });
41638
- resolve45(payload);
42219
+ resolve47(payload);
41639
42220
  };
41640
42221
  signal.addEventListener("abort", onAbort, { once: true });
41641
42222
  state.pendingSuspend = { stepId: req.stepId, resolve: finish };
@@ -41671,6 +42252,19 @@ async function executeRunWorkflow(state, runtimeWf, agents, signal, sessionEvent
41671
42252
  }
41672
42253
  }
41673
42254
  },
42255
+ onAgentRetry: (ev) => {
42256
+ eventLog?.append({
42257
+ stepId: ev.stepId,
42258
+ type: "step.retrying",
42259
+ data: {
42260
+ attempt: ev.attempt,
42261
+ maxAttempts: ev.maxAttempts,
42262
+ error: ev.error,
42263
+ delayMs: ev.delayMs,
42264
+ ...ev.sessionId !== void 0 ? { sessionId: ev.sessionId } : {}
42265
+ }
42266
+ });
42267
+ },
41674
42268
  onStepStart: (stepId, info) => {
41675
42269
  const cached = info?.cached === true;
41676
42270
  if (nonLeafStepIds.has(stepId)) return;
@@ -41974,8 +42568,44 @@ function createWorkflowRunner(opts) {
41974
42568
  });
41975
42569
  };
41976
42570
  const runs = shouldPersist ? loadRuns(persistPath, runsRoot) : /* @__PURE__ */ new Map();
42571
+ const persistMinIntervalMs = opts.persistMinIntervalMs ?? DEFAULT_PERSIST_MIN_INTERVAL_MS;
42572
+ let lastFlushAt = 0;
42573
+ let lastStatusSig = "";
42574
+ let trailingFlush;
42575
+ let dirty = false;
42576
+ const flushRuns = () => {
42577
+ if (trailingFlush) {
42578
+ clearTimeout(trailingFlush);
42579
+ trailingFlush = void 0;
42580
+ }
42581
+ dirty = false;
42582
+ lastFlushAt = Date.now();
42583
+ saveRuns(runs, persistPath);
42584
+ };
42585
+ const statusSig = () => {
42586
+ let sig = "";
42587
+ for (const st of runs.values()) sig += `${st.run.runId}:${st.run.status}|`;
42588
+ return sig;
42589
+ };
42590
+ if (shouldPersist && persistMinIntervalMs > 0) {
42591
+ process.once("exit", () => {
42592
+ if (dirty) flushRuns();
42593
+ });
42594
+ }
41977
42595
  const persist = () => {
41978
- if (shouldPersist) saveRuns(runs, persistPath);
42596
+ if (!shouldPersist) return;
42597
+ if (persistMinIntervalMs <= 0) return flushRuns();
42598
+ const sig = statusSig();
42599
+ const wait = lastFlushAt + persistMinIntervalMs - Date.now();
42600
+ if (sig !== lastStatusSig || wait <= 0) {
42601
+ lastStatusSig = sig;
42602
+ return flushRuns();
42603
+ }
42604
+ dirty = true;
42605
+ if (!trailingFlush) {
42606
+ trailingFlush = setTimeout(flushRuns, wait);
42607
+ trailingFlush.unref?.();
42608
+ }
41979
42609
  };
41980
42610
  const newEventLog = (runId) => shouldPersist ? createRunEventLog(runId, runsRoot) : void 0;
41981
42611
  const workspaceRunsRoot = () => shouldPersist ? runsRoot : void 0;
@@ -43628,14 +44258,14 @@ function createSupervisorTaskGateRunner(opts) {
43628
44258
  const { supervisor, registry, workspace } = opts;
43629
44259
  const exec = opts.runCommand ?? runCommand;
43630
44260
  const settleTimeoutMs = opts.settleTimeoutMs ?? DEFAULT_VERIFY_SETTLE_TIMEOUT_MS;
43631
- const waitForSettle = (policyId) => new Promise((resolve45) => {
44261
+ const waitForSettle = (policyId) => new Promise((resolve47) => {
43632
44262
  let settled = false;
43633
44263
  const finish = (outcome) => {
43634
44264
  if (settled) return;
43635
44265
  settled = true;
43636
44266
  clearTimeout(timer);
43637
44267
  unsub();
43638
- resolve45(outcome);
44268
+ resolve47(outcome);
43639
44269
  };
43640
44270
  const read = () => {
43641
44271
  const state = supervisor.getStatus(policyId);
@@ -44982,7 +45612,7 @@ async function monitorSessionWait(opts) {
44982
45612
  };
44983
45613
  }
44984
45614
  }
44985
- return new Promise((resolve45) => {
45615
+ return new Promise((resolve47) => {
44986
45616
  const unsubs = [];
44987
45617
  let settled = false;
44988
45618
  const watcherLabel = callerScope?.ownerSessionId ? registry.get(callerScope.ownerSessionId)?.label ?? registry.get(callerScope.ownerSessionId)?.title : void 0;
@@ -45022,7 +45652,7 @@ async function monitorSessionWait(opts) {
45022
45652
  ts: (/* @__PURE__ */ new Date()).toISOString()
45023
45653
  });
45024
45654
  }
45025
- resolve45(result);
45655
+ resolve47(result);
45026
45656
  };
45027
45657
  const relevantTypes = targetEvent === "any" ? ["session:turn-end", "session:awaiting-input", "session:exited"] : targetEvent === "turn-end" ? ["session:turn-end", "session:awaiting-input"] : targetEvent === "awaiting-input" ? ["session:awaiting-input"] : ["session:exited"];
45028
45658
  const idSet = new Set(resolvedIds);
@@ -45063,13 +45693,13 @@ async function monitorPolicyWait(opts) {
45063
45693
  if (isSettledStatus(initial.status)) {
45064
45694
  return { timedOut: false, state: initial };
45065
45695
  }
45066
- return new Promise((resolve45) => {
45696
+ return new Promise((resolve47) => {
45067
45697
  let settled = false;
45068
45698
  const timer = setTimeout(() => {
45069
45699
  if (settled) return;
45070
45700
  settled = true;
45071
45701
  unsub();
45072
- resolve45({ timedOut: true });
45702
+ resolve47({ timedOut: true });
45073
45703
  }, timeoutMs);
45074
45704
  const unsub = supervisor.onSettle((id) => {
45075
45705
  if (id !== policyId) return;
@@ -45079,7 +45709,7 @@ async function monitorPolicyWait(opts) {
45079
45709
  settled = true;
45080
45710
  clearTimeout(timer);
45081
45711
  unsub();
45082
- resolve45({ timedOut: false, state });
45712
+ resolve47({ timedOut: false, state });
45083
45713
  });
45084
45714
  });
45085
45715
  }
@@ -46994,7 +47624,7 @@ async function waitForSessionTerminal(registry, sessionId) {
46994
47624
  for (let attempt = 0; attempt < MAX_SEQUENTIAL_POLLS; attempt++) {
46995
47625
  const status = registry.get(sessionId)?.status;
46996
47626
  if (isSessionTerminal(status)) return;
46997
- await new Promise((resolve45) => setTimeout(resolve45, SEQUENTIAL_POLL_INTERVAL_MS));
47627
+ await new Promise((resolve47) => setTimeout(resolve47, SEQUENTIAL_POLL_INTERVAL_MS));
46998
47628
  }
46999
47629
  }
47000
47630
  var MODEL_ROUTED_ADAPTER = "claude-code";
@@ -47160,7 +47790,7 @@ function buildAppInstallPreview(args) {
47160
47790
  runsBuildCommand: !isAgentapp && args.allowBuild === true
47161
47791
  };
47162
47792
  }
47163
- async function dispatchAllowlistedAppTool(declaredAllowlist, input, deps2) {
47793
+ async function dispatchAllowlistedAppTool(declaredAllowlist, input, deps2, appDir) {
47164
47794
  const effectiveAllowlist = [...declaredAllowlist, ...APP_UI_DISCOVERY_TOOLS];
47165
47795
  if (!effectiveAllowlist.includes(input.tool)) {
47166
47796
  return errorResult2(
@@ -47168,8 +47798,14 @@ async function dispatchAllowlistedAppTool(declaredAllowlist, input, deps2) {
47168
47798
  );
47169
47799
  }
47170
47800
  const args = input.args ?? {};
47171
- if (input.tool === "app_install" && appInstallConfirmationClass(args)?.confirmed === false) {
47172
- return textResult2(buildAppInstallPreview(args));
47801
+ let dispatchArgs = args;
47802
+ if (input.tool === "app_install") {
47803
+ const cls = appInstallConfirmationClass(args);
47804
+ if (cls?.confirmed === false) return textResult2(buildAppInstallPreview(args));
47805
+ if (cls?.confirmed === true) {
47806
+ const { confirm: _confirmNonce, ...rest } = args;
47807
+ dispatchArgs = rest;
47808
+ }
47173
47809
  }
47174
47810
  try {
47175
47811
  if (input.tool.startsWith("imported:")) {
@@ -47181,11 +47817,19 @@ async function dispatchAllowlistedAppTool(declaredAllowlist, input, deps2) {
47181
47817
  `app_tool_call: malformed imported tool id "${input.tool}" \u2014 expected "imported:<alias>/<toolName>".`
47182
47818
  );
47183
47819
  }
47184
- const result2 = await deps2.callImportedTool(rest.slice(0, slash), rest.slice(slash + 1), args);
47820
+ const result2 = await deps2.callImportedTool(rest.slice(0, slash), rest.slice(slash + 1), dispatchArgs);
47185
47821
  return textResult2(result2);
47186
47822
  }
47823
+ if (appDir !== void 0) {
47824
+ const bundled = await loadAppBundledTools(appDir);
47825
+ const bundledTool = bundled.tools.find((t) => t.id === input.tool);
47826
+ if (bundledTool) {
47827
+ const output = await runTool({ tool: bundledTool, candidates: bundled.drivers, input: args });
47828
+ return textResult2(output);
47829
+ }
47830
+ }
47187
47831
  if (!deps2.dispatchTool) return notEnabled("app_tool_call");
47188
- const result = await deps2.dispatchTool(input.tool, args);
47832
+ const result = await deps2.dispatchTool(input.tool, dispatchArgs);
47189
47833
  return textResult2(result);
47190
47834
  } catch (err) {
47191
47835
  return errorResult2(`app_tool_call: ${err instanceof Error ? err.message : String(err)}`);
@@ -47196,7 +47840,7 @@ async function performAppToolCall(appRegistry, input, deps2) {
47196
47840
  if (!installed || !installed.ui) {
47197
47841
  return errorResult2(`app_tool_call: app "${input.appId}" is not installed or has no UI.`);
47198
47842
  }
47199
- return dispatchAllowlistedAppTool(installed.ui.tools ?? [], input, deps2);
47843
+ return dispatchAllowlistedAppTool(installed.ui.tools ?? [], input, deps2, installed.dir);
47200
47844
  }
47201
47845
  async function performBuiltinPanelToolCall(tools, input, deps2) {
47202
47846
  if (tools === void 0) {
@@ -47323,7 +47967,7 @@ async function performAppCall(appRegistry, input, deps2) {
47323
47967
  error: `app_call: workflow "${input.workflow}" on "${input.appId}" did not finish within ${timeoutMs}ms (run ${run.runId} still ${status?.status ?? "unknown"}).`
47324
47968
  };
47325
47969
  }
47326
- await new Promise((resolve45) => setTimeout(resolve45, APP_CALL_POLL_INTERVAL_MS));
47970
+ await new Promise((resolve47) => setTimeout(resolve47, APP_CALL_POLL_INTERVAL_MS));
47327
47971
  }
47328
47972
  }
47329
47973
  function refIdOf(ref) {
@@ -48709,12 +49353,12 @@ async function readBody2(req) {
48709
49353
  }
48710
49354
  return { raw: Buffer.concat(chunks).toString("utf8") };
48711
49355
  }
48712
- var isRecord9 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
49356
+ var isRecord11 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
48713
49357
  function parseParts(raw) {
48714
49358
  if (!Array.isArray(raw)) throw new RpcFailure(JSON_RPC_ERROR.invalidParams, "message.parts must be an array");
48715
49359
  const parts = [];
48716
49360
  for (const p of raw) {
48717
- if (!isRecord9(p)) throw new RpcFailure(JSON_RPC_ERROR.invalidParams, "each message part must be an object");
49361
+ if (!isRecord11(p)) throw new RpcFailure(JSON_RPC_ERROR.invalidParams, "each message part must be an object");
48718
49362
  if ("text" in p) {
48719
49363
  if (typeof p.text !== "string") throw new RpcFailure(JSON_RPC_ERROR.invalidParams, "part.text must be a string");
48720
49364
  parts.push({ text: p.text });
@@ -48747,13 +49391,13 @@ async function handleA2aTaskRequest(req, res, path, deps2) {
48747
49391
  send(res, 200, { jsonrpc: "2.0", id: null, error: { code: JSON_RPC_ERROR.parse, message: "parse error: body is not valid JSON" } });
48748
49392
  return true;
48749
49393
  }
48750
- if (!isRecord9(rpc) || rpc.jsonrpc !== "2.0" || typeof rpc.method !== "string") {
49394
+ if (!isRecord11(rpc) || rpc.jsonrpc !== "2.0" || typeof rpc.method !== "string") {
48751
49395
  send(res, 200, { jsonrpc: "2.0", id: null, error: { code: JSON_RPC_ERROR.invalidRequest, message: 'invalid request: expected a JSON-RPC 2.0 object with a string "method" (batches are not supported)' } });
48752
49396
  return true;
48753
49397
  }
48754
49398
  const id = typeof rpc.id === "string" || typeof rpc.id === "number" ? rpc.id : null;
48755
49399
  const method = rpc.method;
48756
- const params = isRecord9(rpc.params) ? rpc.params : {};
49400
+ const params = isRecord11(rpc.params) ? rpc.params : {};
48757
49401
  const requested = negotiateVersion(
48758
49402
  typeof req.headers["a2a-version"] === "string" ? req.headers["a2a-version"] : void 0,
48759
49403
  new URL(req.url ?? "/", "http://localhost").searchParams.get("version")
@@ -48811,14 +49455,14 @@ async function handleA2aTaskRequest(req, res, path, deps2) {
48811
49455
  throw new RpcFailure(JSON_RPC_ERROR.unsupportedOperation, `method "SendMessage" is not allowed for app "${appId}": its manifest does not declare accepts.tasks: true`);
48812
49456
  }
48813
49457
  const rawMessage = params.message;
48814
- if (!isRecord9(rawMessage) || rawMessage.role !== "ROLE_USER") {
49458
+ if (!isRecord11(rawMessage) || rawMessage.role !== "ROLE_USER") {
48815
49459
  throw new RpcFailure(JSON_RPC_ERROR.invalidParams, 'params.message must be an object with role "ROLE_USER"');
48816
49460
  }
48817
49461
  const parts = parseParts(rawMessage.parts);
48818
49462
  const { skill, prompt } = extractSkillRequest(
48819
- { parts, ...isRecord9(rawMessage.metadata) ? { metadata: rawMessage.metadata } : {} },
49463
+ { parts, ...isRecord11(rawMessage.metadata) ? { metadata: rawMessage.metadata } : {} },
48820
49464
  appId,
48821
- isRecord9(params.metadata) ? params.metadata : void 0
49465
+ isRecord11(params.metadata) ? params.metadata : void 0
48822
49466
  );
48823
49467
  if (!skill) {
48824
49468
  throw new RpcFailure(JSON_RPC_ERROR.invalidParams, 'no skill requested: set message.metadata.skill or start the first text part with "skill:<id>"');
@@ -49828,6 +50472,7 @@ async function startHttpServer(opts) {
49828
50472
  const auth = readAuth();
49829
50473
  if (auth.mode !== "bearer") return true;
49830
50474
  if (path === "/health") return true;
50475
+ if (opts.eventsMcp && path.startsWith("/mcp/events/")) return true;
49831
50476
  if (/^\/inbound\/[^/]+$/.test(path)) return true;
49832
50477
  if (isAppUiShellRequest(method, path)) return true;
49833
50478
  const header = req.headers.authorization;
@@ -50094,6 +50739,47 @@ async function startHttpServer(opts) {
50094
50739
  const server2 = await opts.orchestratorMcpServerFactory(scope);
50095
50740
  await serveMcp(req, res, server2);
50096
50741
  }
50742
+ const EVENTS_BODY_CAP = 1024 * 1024;
50743
+ async function handleEventsMcp(req, res, presented) {
50744
+ const cfg = opts.eventsMcp;
50745
+ const send2 = (status, headers, body) => {
50746
+ res.writeHead(status, { "cache-control": "no-store", ...headers });
50747
+ res.end(body);
50748
+ };
50749
+ if (!cfg) return send2(404, { "content-type": "application/json" }, JSON.stringify({ error: "not_found" }));
50750
+ const want = Buffer.from(cfg.secret);
50751
+ const got = Buffer.from(presented);
50752
+ if (got.length !== want.length || !timingSafeEqual(got, want)) {
50753
+ return send2(404, { "content-type": "application/json" }, JSON.stringify({ error: "not_found" }));
50754
+ }
50755
+ const origin = req.headers.origin;
50756
+ if (typeof origin === "string" && !(cfg.allowedOrigins ?? []).includes(origin)) {
50757
+ return send2(403, { "content-type": "application/json" }, JSON.stringify({ error: "origin_not_allowed" }));
50758
+ }
50759
+ const chunks = [];
50760
+ let size = 0;
50761
+ for await (const chunk of req) {
50762
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
50763
+ size += buf.length;
50764
+ if (size > EVENTS_BODY_CAP) {
50765
+ return send2(413, { "content-type": "application/json" }, JSON.stringify({ error: "body_too_large" }));
50766
+ }
50767
+ chunks.push(buf);
50768
+ }
50769
+ const controller = new AbortController();
50770
+ res.on("close", () => {
50771
+ if (!res.writableEnded) controller.abort();
50772
+ });
50773
+ const out = await handleModernRequest(
50774
+ {
50775
+ method: req.method ?? "GET",
50776
+ headers: req.headers,
50777
+ body: Buffer.concat(chunks).toString("utf8")
50778
+ },
50779
+ { createServer: cfg.createServer, signal: controller.signal }
50780
+ );
50781
+ send2(out.status, out.headers, out.body);
50782
+ }
50097
50783
  async function handleImportedMcp(req, res, importId) {
50098
50784
  if (!authorizeMcp(req, res)) return;
50099
50785
  if (!opts.mcpProxy) {
@@ -50360,7 +51046,7 @@ async function startHttpServer(opts) {
50360
51046
  });
50361
51047
  }
50362
51048
  applyCors(req, res);
50363
- if (req.method === "OPTIONS") {
51049
+ if (req.method === "OPTIONS" && !(opts.eventsMcp && path.startsWith("/mcp/events/"))) {
50364
51050
  res.writeHead(204);
50365
51051
  res.end();
50366
51052
  return;
@@ -50387,6 +51073,21 @@ async function startHttpServer(opts) {
50387
51073
  handleEvents(req, res);
50388
51074
  return;
50389
51075
  }
51076
+ if (path.startsWith("/mcp/events/")) {
51077
+ let presented;
51078
+ try {
51079
+ presented = decodeURIComponent(path.slice("/mcp/events/".length));
51080
+ } catch (error) {
51081
+ if (!(error instanceof URIError)) throw error;
51082
+ }
51083
+ if (presented === void 0) {
51084
+ res.writeHead(404, { "content-type": "application/json", "cache-control": "no-store" });
51085
+ res.end(JSON.stringify({ error: "not_found" }));
51086
+ return;
51087
+ }
51088
+ await handleEventsMcp(req, res, presented);
51089
+ return;
51090
+ }
50390
51091
  if (path === "/mcp/orchestrator") {
50391
51092
  await handleOrchestratorMcp(req, res);
50392
51093
  return;
@@ -50994,15 +51695,17 @@ async function startHttpServer(opts) {
50994
51695
  try {
50995
51696
  const base = str3("base");
50996
51697
  const anchor = str3("anchor");
50997
- const result = await opts.runBranchGc({
50998
- repoRoot: resolved.repoRoot,
50999
- apply,
51000
- includeReviewed: bool3("includeReviewed"),
51001
- ...base ? { base } : {},
51002
- ...scopes?.length ? { scopes } : {},
51003
- ...minAgeDays !== void 0 && Number.isFinite(minAgeDays) ? { minAgeDays } : {},
51004
- ...anchor ? { anchor } : {}
51005
- });
51698
+ const result = withBranchGcApplySummary(
51699
+ await opts.runBranchGc({
51700
+ repoRoot: resolved.repoRoot,
51701
+ apply,
51702
+ includeReviewed: bool3("includeReviewed"),
51703
+ ...base ? { base } : {},
51704
+ ...scopes?.length ? { scopes } : {},
51705
+ ...minAgeDays !== void 0 && Number.isFinite(minAgeDays) ? { minAgeDays } : {},
51706
+ ...anchor ? { anchor } : {}
51707
+ })
51708
+ );
51006
51709
  res.writeHead(200, { "content-type": "application/json" });
51007
51710
  res.end(JSON.stringify(result));
51008
51711
  } catch (err) {
@@ -52074,16 +52777,16 @@ async function startHttpServer(opts) {
52074
52777
  });
52075
52778
  });
52076
52779
  const bind = opts.bind ?? "127.0.0.1";
52077
- await new Promise((resolve45, reject) => {
52780
+ await new Promise((resolve47, reject) => {
52078
52781
  server.once("error", reject);
52079
- server.listen(opts.port, bind, () => resolve45());
52782
+ server.listen(opts.port, bind, () => resolve47());
52080
52783
  });
52081
52784
  return {
52082
52785
  url: `http://${bind}:${opts.port}`,
52083
52786
  async stop() {
52084
52787
  wss.close();
52085
52788
  server.closeAllConnections();
52086
- await new Promise((resolve45) => server.close(() => resolve45()));
52789
+ await new Promise((resolve47) => server.close(() => resolve47()));
52087
52790
  }
52088
52791
  };
52089
52792
  }
@@ -52292,9 +52995,9 @@ async function currentTranscriptSeq(id) {
52292
52995
  let stream;
52293
52996
  try {
52294
52997
  stream = createReadStream(filePath, { encoding: "utf8" });
52295
- await new Promise((resolve45, reject) => {
52998
+ await new Promise((resolve47, reject) => {
52296
52999
  stream.once("error", reject);
52297
- stream.once("open", resolve45);
53000
+ stream.once("open", resolve47);
52298
53001
  });
52299
53002
  } catch (err) {
52300
53003
  if (err.code === "ENOENT") return 0;
@@ -52319,9 +53022,9 @@ async function* transcriptDiskRecords(id) {
52319
53022
  let stream;
52320
53023
  try {
52321
53024
  stream = createReadStream(filePath, { encoding: "utf8" });
52322
- await new Promise((resolve45, reject) => {
53025
+ await new Promise((resolve47, reject) => {
52323
53026
  stream.once("error", reject);
52324
- stream.once("open", resolve45);
53027
+ stream.once("open", resolve47);
52325
53028
  });
52326
53029
  } catch (err) {
52327
53030
  if (err.code === "ENOENT") return;
@@ -54077,9 +54780,9 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
54077
54780
  let fileStream;
54078
54781
  try {
54079
54782
  fileStream = createReadStream(filePath, { encoding: "utf8" });
54080
- await new Promise((resolve45, reject) => {
54783
+ await new Promise((resolve47, reject) => {
54081
54784
  fileStream.once("error", reject);
54082
- fileStream.once("open", resolve45);
54785
+ fileStream.once("open", resolve47);
54083
54786
  });
54084
54787
  } catch (err) {
54085
54788
  const code = err.code;
@@ -54135,9 +54838,9 @@ async function handleSessions(req, res, path, registry, resolveAgentAdapter, pty
54135
54838
  let fileStream;
54136
54839
  try {
54137
54840
  fileStream = createReadStream(filePath, { encoding: "utf8" });
54138
- await new Promise((resolve45, reject) => {
54841
+ await new Promise((resolve47, reject) => {
54139
54842
  fileStream.once("error", reject);
54140
- fileStream.once("open", resolve45);
54843
+ fileStream.once("open", resolve47);
54141
54844
  });
54142
54845
  } catch (err) {
54143
54846
  const code = err.code;
@@ -54581,7 +55284,10 @@ async function handleSentinels(req, res, path, deps2) {
54581
55284
  return true;
54582
55285
  }
54583
55286
  if (req.method === "DELETE") {
54584
- const removed = await cancelSentinelWatch({ store: deps2.store, resolveProvider: deps2.resolveProvider }, id);
55287
+ const removed = await cancelSentinelWatch(
55288
+ { store: deps2.store, resolveProvider: deps2.resolveProvider, ...deps2.tombstones ? { tombstones: deps2.tombstones } : {} },
55289
+ id
55290
+ );
54585
55291
  if (!removed) {
54586
55292
  json3(404, { error: "sentinel_not_found", id });
54587
55293
  return true;
@@ -56891,7 +57597,7 @@ function createTransmitterBindingStore(opts) {
56891
57597
  const nowMs = Date.now;
56892
57598
  const debounceMs = PERSIST_DEBOUNCE_MS6;
56893
57599
  let persistTimer = null;
56894
- const load = () => {
57600
+ const load2 = () => {
56895
57601
  const out = /* @__PURE__ */ new Map();
56896
57602
  let raw;
56897
57603
  try {
@@ -56916,7 +57622,7 @@ function createTransmitterBindingStore(opts) {
56916
57622
  }
56917
57623
  return out;
56918
57624
  };
56919
- const bindings = load();
57625
+ const bindings = load2();
56920
57626
  const snapshot = () => {
56921
57627
  const out = {};
56922
57628
  for (const [key, binding] of bindings.entries()) out[key] = binding;
@@ -56976,7 +57682,7 @@ function createInboundEndpointStore(opts) {
56976
57682
  const debounceMs = opts?.debounceMs ?? PERSIST_DEBOUNCE_MS7;
56977
57683
  const persist = opts?.persist ?? opts?.filePath !== void 0;
56978
57684
  let persistTimer = null;
56979
- const load = () => {
57685
+ const load2 = () => {
56980
57686
  const out = /* @__PURE__ */ new Map();
56981
57687
  if (!persist) return out;
56982
57688
  let raw;
@@ -57002,7 +57708,7 @@ function createInboundEndpointStore(opts) {
57002
57708
  }
57003
57709
  return out;
57004
57710
  };
57005
- const endpoints = load();
57711
+ const endpoints = load2();
57006
57712
  const seen = /* @__PURE__ */ new Map();
57007
57713
  let totalSeen = 0;
57008
57714
  const snapshot = () => {
@@ -57165,6 +57871,299 @@ init_session_message();
57165
57871
  init_sessions();
57166
57872
  init_session_end_reason();
57167
57873
  init_session_retirement();
57874
+ var tmpSeq6 = 0;
57875
+ var tmpPathFor2 = (filePath) => `${filePath}.tmp.${process.pid}.${++tmpSeq6}`;
57876
+ function isDirSyncUnsupported(err) {
57877
+ const code = err.code;
57878
+ return code === "EINVAL" || code === "EPERM" || code === "EISDIR" || code === "ENOTSUP";
57879
+ }
57880
+ async function writeFileDurable(filePath, data, opts = {}) {
57881
+ const mode = opts.mode ?? 384;
57882
+ const dir = dirname(filePath);
57883
+ await promises.mkdir(dir, { recursive: true });
57884
+ const tmp = tmpPathFor2(filePath);
57885
+ try {
57886
+ const fh = await promises.open(tmp, "w", mode);
57887
+ try {
57888
+ await fh.writeFile(data);
57889
+ await fh.chmod(mode);
57890
+ await fh.sync();
57891
+ } finally {
57892
+ await fh.close();
57893
+ }
57894
+ if (opts.commitIf && !opts.commitIf()) {
57895
+ await promises.unlink(tmp).catch(() => void 0);
57896
+ return;
57897
+ }
57898
+ await promises.rename(tmp, filePath);
57899
+ } catch (err) {
57900
+ await promises.unlink(tmp).catch(() => void 0);
57901
+ throw err;
57902
+ }
57903
+ try {
57904
+ const dh = await promises.open(dir, "r");
57905
+ try {
57906
+ await dh.sync();
57907
+ } finally {
57908
+ await dh.close();
57909
+ }
57910
+ } catch (err) {
57911
+ if (!isDirSyncUnsupported(err)) throw err;
57912
+ }
57913
+ }
57914
+ function writeFileDurableSync(filePath, data, mode = 384) {
57915
+ const dir = dirname(filePath);
57916
+ mkdirSync(dir, { recursive: true });
57917
+ const tmp = tmpPathFor2(filePath);
57918
+ try {
57919
+ const fd = openSync(tmp, "w", mode);
57920
+ try {
57921
+ writeSync(fd, data);
57922
+ fsyncSync(fd);
57923
+ } finally {
57924
+ closeSync(fd);
57925
+ }
57926
+ renameSync(tmp, filePath);
57927
+ } catch (err) {
57928
+ try {
57929
+ unlinkSync(tmp);
57930
+ } catch {
57931
+ }
57932
+ throw err;
57933
+ }
57934
+ try {
57935
+ const dfd = openSync(dir, "r");
57936
+ try {
57937
+ fsyncSync(dfd);
57938
+ } finally {
57939
+ closeSync(dfd);
57940
+ }
57941
+ } catch (err) {
57942
+ if (!isDirSyncUnsupported(err)) throw err;
57943
+ }
57944
+ }
57945
+ function appendLineDurableSync(filePath, line, mode = 384) {
57946
+ mkdirSync(dirname(filePath), { recursive: true });
57947
+ const existed = (() => {
57948
+ try {
57949
+ closeSync(openSync(filePath, "r"));
57950
+ return true;
57951
+ } catch {
57952
+ return false;
57953
+ }
57954
+ })();
57955
+ const fd = openSync(filePath, "a", mode);
57956
+ try {
57957
+ appendFileSync(fd, line.endsWith("\n") ? line : `${line}
57958
+ `);
57959
+ fsyncSync(fd);
57960
+ } finally {
57961
+ closeSync(fd);
57962
+ }
57963
+ if (!existed) {
57964
+ try {
57965
+ const dfd = openSync(dirname(filePath), "r");
57966
+ try {
57967
+ fsyncSync(dfd);
57968
+ } finally {
57969
+ closeSync(dfd);
57970
+ }
57971
+ } catch (err) {
57972
+ if (!isDirSyncUnsupported(err)) throw err;
57973
+ }
57974
+ }
57975
+ }
57976
+
57977
+ // src/sentinel-cancel-tombstones.ts
57978
+ function defaultCancelTombstonePath() {
57979
+ return resolve(process.env.AGENTPROTO_HOME ?? join(homedir(), ".agentproto"), "sentinel-cancel-tombstones.json");
57980
+ }
57981
+ var SECRETISH = /secret|token|key|password|credential|authorization/i;
57982
+ function stripSecrets(handle) {
57983
+ const state = {};
57984
+ for (const [k, v] of Object.entries(handle.state ?? {})) if (!SECRETISH.test(k)) state[k] = v;
57985
+ return {
57986
+ provider: handle.provider,
57987
+ ...handle.remoteId !== void 0 ? { remoteId: handle.remoteId } : {},
57988
+ ...handle.cursor !== void 0 ? { cursor: handle.cursor } : {},
57989
+ ...Object.keys(state).length > 0 ? { state } : {}
57990
+ };
57991
+ }
57992
+ var keyOf2 = (provider, remoteId) => `${provider}|${remoteId}`;
57993
+ function load(filePath) {
57994
+ try {
57995
+ const parsed = JSON.parse(readFileSync(filePath, "utf8"));
57996
+ if (!Array.isArray(parsed.tombstones)) return [];
57997
+ return parsed.tombstones.filter(
57998
+ (t) => typeof t === "object" && t !== null && typeof t.key === "string"
57999
+ );
58000
+ } catch {
58001
+ return [];
58002
+ }
58003
+ }
58004
+ function createCancelTombstoneStore(opts) {
58005
+ const nowMs = opts.nowMs ?? Date.now;
58006
+ const log = opts.log ?? ((line) => console.warn(line));
58007
+ const base = opts.baseBackoffMs ?? 3e4;
58008
+ const max = opts.maxBackoffMs ?? 60 * 60 * 1e3;
58009
+ const rows = /* @__PURE__ */ new Map();
58010
+ if (opts.filePath) for (const t of load(opts.filePath)) rows.set(t.key, t);
58011
+ let writeTail = Promise.resolve();
58012
+ function persist() {
58013
+ if (!opts.filePath) return Promise.resolve();
58014
+ const filePath = opts.filePath;
58015
+ const run = () => writeFileDurable(filePath, JSON.stringify({ version: 1, tombstones: [...rows.values()] }, null, 2));
58016
+ const next = writeTail.then(run, run);
58017
+ writeTail = next.catch(() => void 0);
58018
+ return next;
58019
+ }
58020
+ const inFlight2 = /* @__PURE__ */ new Map();
58021
+ async function attempt(t, explicit = false) {
58022
+ const running = inFlight2.get(t.key);
58023
+ if (running) return running;
58024
+ const p = (async () => {
58025
+ if (!explicit && opts.isRemoteInUse?.(t.provider, t.remoteId)) {
58026
+ rows.delete(t.key);
58027
+ await persist().catch((err) => log(`[sentinel-tombstones] persist failed: ${String(err)}`));
58028
+ return true;
58029
+ }
58030
+ const provider = await opts.resolveProvider(t.provider);
58031
+ let error;
58032
+ if (!provider) error = `unknown sentinel provider "${t.provider}"`;
58033
+ else {
58034
+ try {
58035
+ await provider.cancel(t.handle);
58036
+ } catch (err) {
58037
+ error = err instanceof Error ? err.message : String(err);
58038
+ }
58039
+ }
58040
+ const live = rows.get(t.key);
58041
+ if (error === void 0) {
58042
+ if (live) rows.delete(t.key);
58043
+ await persist().catch((err) => log(`[sentinel-tombstones] persist failed: ${String(err)}`));
58044
+ return true;
58045
+ }
58046
+ if (live) {
58047
+ live.attempts += 1;
58048
+ live.lastAttemptAt = nowMs();
58049
+ live.lastError = error.slice(0, 300);
58050
+ live.nextAttemptAt = nowMs() + Math.min(max, base * 2 ** Math.min(live.attempts, 20));
58051
+ await persist().catch((err) => log(`[sentinel-tombstones] persist failed: ${String(err)}`));
58052
+ }
58053
+ return false;
58054
+ })().finally(() => inFlight2.delete(t.key));
58055
+ inFlight2.set(t.key, p);
58056
+ return p;
58057
+ }
58058
+ return {
58059
+ async cancel(handle, afterRecorded) {
58060
+ const provider = await opts.resolveProvider(handle.provider);
58061
+ if (!handle.remoteId || !provider?.exclusiveRemote) {
58062
+ afterRecorded?.();
58063
+ let converged2 = true;
58064
+ try {
58065
+ await provider?.cancel(handle);
58066
+ } catch {
58067
+ converged2 = false;
58068
+ }
58069
+ return { converged: converged2 };
58070
+ }
58071
+ const key = keyOf2(handle.provider, handle.remoteId);
58072
+ let t = rows.get(key);
58073
+ if (!t) {
58074
+ const now = nowMs();
58075
+ t = {
58076
+ key,
58077
+ provider: handle.provider,
58078
+ remoteId: handle.remoteId,
58079
+ handle: stripSecrets(handle),
58080
+ createdAt: now,
58081
+ attempts: 0,
58082
+ nextAttemptAt: now
58083
+ };
58084
+ rows.set(key, t);
58085
+ try {
58086
+ await persist();
58087
+ } catch (err) {
58088
+ rows.delete(key);
58089
+ throw err;
58090
+ }
58091
+ }
58092
+ afterRecorded?.();
58093
+ const converged = await attempt(t, true);
58094
+ const remaining = rows.get(key);
58095
+ return { converged, ...remaining ? { tombstone: remaining } : {} };
58096
+ },
58097
+ async sweep() {
58098
+ const now = nowMs();
58099
+ const due = [...rows.values()].filter((t) => t.nextAttemptAt <= now);
58100
+ let converged = 0;
58101
+ for (const t of due) if (await attempt(t)) converged++;
58102
+ return { attempted: due.length, converged, remaining: rows.size };
58103
+ },
58104
+ list: () => [...rows.values()],
58105
+ has: (provider, remoteId) => rows.has(keyOf2(provider, remoteId))
58106
+ };
58107
+ }
58108
+ function defaultSentinelQuarantinePath() {
58109
+ return resolve(process.env.AGENTPROTO_HOME ?? join(homedir(), ".agentproto"), "sentinel-quarantine.jsonl");
58110
+ }
58111
+ function keyFor(sentinelId, provider, item) {
58112
+ const identity = item.remoteDeliveryId ?? (item.seq !== void 0 ? `seq:${item.seq}` : "");
58113
+ return [sentinelId, provider, identity, item.digest ?? ""].join("|");
58114
+ }
58115
+ function loadFile(filePath) {
58116
+ if (!existsSync(filePath)) return [];
58117
+ const out = [];
58118
+ for (const line of readFileSync(filePath, "utf8").split("\n")) {
58119
+ if (line.trim() === "") continue;
58120
+ try {
58121
+ const rec = JSON.parse(line);
58122
+ if (rec && typeof rec.key === "string") out.push(rec);
58123
+ } catch {
58124
+ }
58125
+ }
58126
+ return out;
58127
+ }
58128
+ function createSentinelQuarantine(opts = {}) {
58129
+ const nowMs = opts.nowMs ?? Date.now;
58130
+ const records = /* @__PURE__ */ new Map();
58131
+ let loaded = false;
58132
+ function ensureLoaded() {
58133
+ if (loaded) return;
58134
+ if (opts.filePath) for (const rec of loadFile(opts.filePath)) records.set(rec.key, rec);
58135
+ loaded = true;
58136
+ }
58137
+ return {
58138
+ record({ sentinelId, provider, remoteId, item }) {
58139
+ ensureLoaded();
58140
+ const key = keyFor(sentinelId, provider, item);
58141
+ const existing = records.get(key);
58142
+ if (existing) return existing;
58143
+ const rec = {
58144
+ key,
58145
+ quarantinedAt: new Date(nowMs()).toISOString(),
58146
+ sentinelId,
58147
+ provider,
58148
+ ...remoteId !== void 0 ? { remoteId } : {},
58149
+ ...item.seq !== void 0 ? { seq: item.seq } : {},
58150
+ ...item.remoteDeliveryId !== void 0 ? { remoteDeliveryId: item.remoteDeliveryId } : {},
58151
+ error: item.error,
58152
+ ...item.excerpt ? { excerpt: item.excerpt } : {},
58153
+ ...item.digest !== void 0 ? { digest: item.digest } : {},
58154
+ ...item.bytes !== void 0 ? { bytes: item.bytes } : {}
58155
+ };
58156
+ if (opts.filePath) appendLineDurableSync(opts.filePath, JSON.stringify(rec));
58157
+ records.set(key, rec);
58158
+ return rec;
58159
+ },
58160
+ list(filter) {
58161
+ ensureLoaded();
58162
+ const all = [...records.values()];
58163
+ return filter?.sentinelId ? all.filter((r) => r.sentinelId === filter.sentinelId) : all;
58164
+ }
58165
+ };
58166
+ }
57168
58167
 
57169
58168
  // src/webhook-egress/delivery.ts
57170
58169
  init_signing();
@@ -57352,7 +58351,7 @@ function finalHeaders(headers, hostname6, port) {
57352
58351
  return { ...headers, Host: port === 443 ? hostname6 : `${hostname6}:${port}` };
57353
58352
  }
57354
58353
  function rawPost(args) {
57355
- return new Promise((resolve45, reject) => {
58354
+ return new Promise((resolve47, reject) => {
57356
58355
  const req = request({
57357
58356
  // Connect to the PRE-VALIDATED address; keep the original hostname in
57358
58357
  // `servername` so SNI and the certificate check never loosen.
@@ -57376,7 +58375,7 @@ function rawPost(args) {
57376
58375
  const ok4 = (status, body) => {
57377
58376
  if (settled) return;
57378
58377
  settled = true;
57379
- resolve45({ status, body });
58378
+ resolve47({ status, body });
57380
58379
  };
57381
58380
  req.setTimeout(args.timeoutMs, () => fail4("timeout", `no response within ${args.timeoutMs}ms`));
57382
58381
  req.on("error", (err) => {
@@ -57459,7 +58458,7 @@ async function deliverEventEnvelope(replay, event, deps2 = {}) {
57459
58458
  try {
57460
58459
  response = await fetcher(replay.callbackUrl, {
57461
58460
  method: "POST",
57462
- headers: { ...signed, "content-type": "application/json" },
58461
+ headers: { ...signed, "content-type": "application/json", "X-MCP-Subscription-Id": replay.subId },
57463
58462
  body: payloadBytes,
57464
58463
  timeoutMs: 15e3
57465
58464
  // delivery default (challenge uses 10_000)
@@ -57891,13 +58890,16 @@ function invalidParams(reason, detail) {
57891
58890
  function callbackEndpointError(reason, detail) {
57892
58891
  return new McpEventsError(-32015, `CallbackEndpointError: ${detail}`, { reason });
57893
58892
  }
58893
+ function backingSubscriptionError(reason, detail, subscriptionId2) {
58894
+ return new McpEventsError(-32016, `BackingSubscriptionError: ${detail}`, { reason, subscriptionId: subscriptionId2 });
58895
+ }
57894
58896
  var mutationChain = Promise.resolve();
57895
58897
  function withSubscribeMutex(fn) {
57896
58898
  const run = mutationChain.then(fn, fn);
57897
58899
  mutationChain = run.catch(() => void 0);
57898
58900
  return run;
57899
58901
  }
57900
- function isRecord10(value) {
58902
+ function isRecord12(value) {
57901
58903
  return typeof value === "object" && value !== null && !Array.isArray(value);
57902
58904
  }
57903
58905
  var LIST_PAGE_DEFAULT = 50;
@@ -57918,7 +58920,7 @@ function decodeListCursor(cursor) {
57918
58920
  }
57919
58921
  }
57920
58922
  function requireWebhookDelivery(delivery) {
57921
- if (!isRecord10(delivery) || delivery.mode !== "webhook") {
58923
+ if (!isRecord12(delivery) || delivery.mode !== "webhook") {
57922
58924
  throw invalidParams("unsupported_delivery_mode", 'delivery.mode must be "webhook"');
57923
58925
  }
57924
58926
  if (typeof delivery.url !== "string" || delivery.url.length === 0) {
@@ -57927,7 +58929,7 @@ function requireWebhookDelivery(delivery) {
57927
58929
  return { mode: "webhook", url: delivery.url, ...typeof delivery.secret === "string" ? { secret: delivery.secret } : {} };
57928
58930
  }
57929
58931
  function validateArgsOrThrow(name, registration, args) {
57930
- if (!isRecord10(args)) throw invalidParams("invalid_arguments", "`arguments` must be an object");
58932
+ if (!isRecord12(args)) throw invalidParams("invalid_arguments", "`arguments` must be an object");
57931
58933
  const schemaCheck = validateAgainstInputSchema(registration.definition.inputSchema, args);
57932
58934
  if (!schemaCheck.ok) {
57933
58935
  throw invalidParams("invalid_arguments", `arguments for "${name}": ${schemaCheck.reason}`);
@@ -57983,10 +58985,30 @@ async function eventsSubscribe(input, ctx) {
57983
58985
  if (target.kind !== "webhook") {
57984
58986
  throw invalidParams("identity_conflict", `subscription ${id} exists with a non-webhook target`);
57985
58987
  }
58988
+ let handle2 = existing.handle;
58989
+ const provider2 = await ctx.resolveProvider(existing.provider);
58990
+ if (provider2?.renew) {
58991
+ try {
58992
+ handle2 = await provider2.renew(existing.handle, until);
58993
+ } catch (err) {
58994
+ if (err instanceof SentinelBackingExpiredError) {
58995
+ throw backingSubscriptionError(
58996
+ "backing_subscription_expired",
58997
+ `${err.message}; unsubscribe and subscribe again`,
58998
+ id
58999
+ );
59000
+ }
59001
+ throw backingSubscriptionError(
59002
+ "backing_renew_failed",
59003
+ err instanceof Error ? err.message : String(err),
59004
+ id
59005
+ );
59006
+ }
59007
+ }
57986
59008
  const ref = ctx.store.materializeWebhookTargetRef(target);
57987
59009
  if (ref !== void 0) ctx.store.putSentinelSecret(ref, { secret });
57988
- ctx.store.update(id, { spec: { ...existing.spec, until }, status: "active" });
57989
- return { id, refreshBefore: computeRefreshBefore(until), cursor: null };
59010
+ ctx.store.update(id, { spec: { ...existing.spec, until }, handle: handle2, status: "active" });
59011
+ return { id, refreshBefore: computeRefreshBefore(until), cursor: null, truncated: false };
57990
59012
  }
57991
59013
  const provider = await ctx.resolveProvider(mapped.providerSlug);
57992
59014
  if (!provider) {
@@ -58002,7 +59024,7 @@ async function eventsSubscribe(input, ctx) {
58002
59024
  sentinelId: id
58003
59025
  });
58004
59026
  ctx.store.create({ id, spec, provider: mapped.providerSlug, handle });
58005
- return { id, refreshBefore: computeRefreshBefore(until), cursor: null };
59027
+ return { id, refreshBefore: computeRefreshBefore(until), cursor: null, truncated: false };
58006
59028
  });
58007
59029
  }
58008
59030
  async function eventsUnsubscribe(input, ctx) {
@@ -58022,6 +59044,13 @@ async function eventsUnsubscribe(input, ctx) {
58022
59044
  await withSubscribeMutex(async () => {
58023
59045
  const existing = ctx.store.get(id);
58024
59046
  if (!existing) return;
59047
+ if (ctx.tombstones) {
59048
+ try {
59049
+ await ctx.tombstones.cancel(existing.handle, () => ctx.store.remove(id));
59050
+ return;
59051
+ } catch {
59052
+ }
59053
+ }
58025
59054
  const provider = await ctx.resolveProvider(existing.provider);
58026
59055
  if (provider) {
58027
59056
  try {
@@ -58051,7 +59080,6 @@ var PERSIST_DEBOUNCE_MS8 = 1500;
58051
59080
  var DELIVERED_REAP_MS = 24 * 60 * 60 * 1e3;
58052
59081
  var PENDING_AGE_OUT_MS = 7 * 24 * 60 * 60 * 1e3;
58053
59082
  var ROW_CAP = 2e3;
58054
- var tmpSeq6 = 0;
58055
59083
  function webhookRequestId(sentinelId, eventId) {
58056
59084
  const digest = createHash("sha256").update(`${sentinelId}:${eventId}`).digest("hex");
58057
59085
  return `sub_${digest.slice(0, 32)}`;
@@ -58067,7 +59095,7 @@ function createSentinelWebhookOutbox(opts) {
58067
59095
  const persist = opts.persist ?? opts.filePath !== void 0;
58068
59096
  const deliverEvent = opts.deliverEvent ?? (async (input) => deliverEventEnvelope(input.replay, input.row.envelope));
58069
59097
  const rowKey = (sentinelId, eventId) => `${sentinelId}::${eventId}`;
58070
- const load = () => {
59098
+ const load2 = () => {
58071
59099
  const out = /* @__PURE__ */ new Map();
58072
59100
  if (!persist) return out;
58073
59101
  let raw;
@@ -58091,28 +59119,34 @@ function createSentinelWebhookOutbox(opts) {
58091
59119
  }
58092
59120
  return out;
58093
59121
  };
58094
- const rows = load();
59122
+ const rows = load2();
58095
59123
  const snapshot = () => {
58096
59124
  const out = {};
58097
59125
  for (const [key, row] of rows.entries()) out[key] = row;
58098
59126
  return out;
58099
59127
  };
58100
59128
  let persistTimer = null;
59129
+ let writeTail = Promise.resolve();
59130
+ let syncGeneration = 0;
59131
+ const persistNow = () => {
59132
+ if (!persist) return Promise.resolve();
59133
+ const run = async () => {
59134
+ const generation = syncGeneration;
59135
+ const body = JSON.stringify(snapshot(), null, 2) + "\n";
59136
+ await writeFileDurable(filePath, body, { commitIf: () => generation === syncGeneration });
59137
+ };
59138
+ const next = writeTail.then(run, run);
59139
+ writeTail = next.catch(() => void 0);
59140
+ return next;
59141
+ };
58101
59142
  const schedulePersist = () => {
58102
59143
  if (!persist) return;
58103
59144
  if (persistTimer) clearTimeout(persistTimer);
58104
59145
  persistTimer = setTimeout(() => {
58105
- void (async () => {
58106
- try {
58107
- await promises.mkdir(dirname(filePath), { recursive: true });
58108
- const tmp = `${filePath}.tmp.${process.pid}.${++tmpSeq6}`;
58109
- await promises.writeFile(tmp, JSON.stringify(snapshot(), null, 2) + "\n", { mode: 384 });
58110
- await promises.chmod(tmp, 384);
58111
- await promises.rename(tmp, filePath);
58112
- } catch (err) {
58113
- log(`[sentinel-webhook-outbox] persist failed: ${err instanceof Error ? err.message : String(err)}`);
58114
- }
58115
- })();
59146
+ persistTimer = null;
59147
+ persistNow().catch((err) => {
59148
+ log(`[sentinel-webhook-outbox] persist failed: ${err instanceof Error ? err.message : String(err)}`);
59149
+ });
58116
59150
  }, debounceMs);
58117
59151
  };
58118
59152
  const flushSync = () => {
@@ -58121,12 +59155,9 @@ function createSentinelWebhookOutbox(opts) {
58121
59155
  clearTimeout(persistTimer);
58122
59156
  persistTimer = null;
58123
59157
  }
59158
+ syncGeneration++;
58124
59159
  try {
58125
- mkdirSync(dirname(filePath), { recursive: true });
58126
- const tmp = `${filePath}.tmp.${process.pid}.${++tmpSeq6}`;
58127
- writeFileSync(tmp, JSON.stringify(snapshot(), null, 2) + "\n", { mode: 384 });
58128
- chmodSync(tmp, 384);
58129
- renameSync(tmp, filePath);
59160
+ writeFileDurableSync(filePath, JSON.stringify(snapshot(), null, 2) + "\n");
58130
59161
  } catch {
58131
59162
  }
58132
59163
  };
@@ -58203,7 +59234,14 @@ function createSentinelWebhookOutbox(opts) {
58203
59234
  const key = rowKey(sentinelId, event.id);
58204
59235
  const existing = rows.get(key);
58205
59236
  if (existing) {
58206
- if (existing.status !== "pending") opts.onTerminal(sentinelId, existing);
59237
+ if (existing.status !== "pending") {
59238
+ opts.onTerminal(sentinelId, existing);
59239
+ return void 0;
59240
+ }
59241
+ await writeTail;
59242
+ if (rows.get(key) !== existing) {
59243
+ throw new Error(`webhook outbox row ${key} was not persisted`);
59244
+ }
58207
59245
  return void 0;
58208
59246
  }
58209
59247
  const envelope = toWebhookEnvelope(event);
@@ -58219,12 +59257,23 @@ function createSentinelWebhookOutbox(opts) {
58219
59257
  envelope,
58220
59258
  event
58221
59259
  };
59260
+ let evicted;
58222
59261
  if (rows.size >= ROW_CAP) {
58223
59262
  const oldest = oldestTerminalRowKey();
58224
- if (oldest) rows.delete(oldest);
59263
+ const evictedRow = oldest ? rows.get(oldest) : void 0;
59264
+ if (oldest && evictedRow) {
59265
+ evicted = [oldest, evictedRow];
59266
+ rows.delete(oldest);
59267
+ }
58225
59268
  }
58226
59269
  rows.set(key, row);
58227
- schedulePersist();
59270
+ try {
59271
+ await persistNow();
59272
+ } catch (err) {
59273
+ if (rows.get(key) === row) rows.delete(key);
59274
+ if (evicted && !rows.has(evicted[0])) rows.set(evicted[0], evicted[1]);
59275
+ throw err;
59276
+ }
58228
59277
  enqueueDispatch();
58229
59278
  return row;
58230
59279
  },
@@ -58354,14 +59403,27 @@ function createSentinelRuntime(opts) {
58354
59403
  if (store.get(sentinel.id)?.status !== "expired") {
58355
59404
  store.update(sentinel.id, { status: "expired" });
58356
59405
  }
58357
- const provider = await opts.resolveProvider(sentinel.provider);
58358
- if (!provider) return;
58359
59406
  try {
58360
- await provider.cancel(sentinel.handle);
59407
+ await cancelTombstones.cancel(sentinel.handle);
58361
59408
  } catch (err) {
58362
59409
  log(`[sentinel-runtime] expiry cancel failed for ${sentinel.id}: ${describeError(err)}`);
58363
59410
  }
58364
59411
  }
59412
+ const outboxPersistent = opts.outboxPersist ?? (opts.outboxPath !== void 0 || opts.deliverEvent === void 0);
59413
+ const quarantinePath = opts.quarantinePath ?? (outboxPersistent ? defaultSentinelQuarantinePath() : void 0);
59414
+ const quarantine = createSentinelQuarantine({
59415
+ ...quarantinePath !== void 0 ? { filePath: quarantinePath } : {},
59416
+ nowMs
59417
+ });
59418
+ const cancelTombstonePath = opts.cancelTombstonePath ?? (outboxPersistent ? defaultCancelTombstonePath() : void 0);
59419
+ const cancelTombstones = createCancelTombstoneStore({
59420
+ ...cancelTombstonePath !== void 0 ? { filePath: cancelTombstonePath } : {},
59421
+ resolveProvider: opts.resolveProvider,
59422
+ // A live local watch that owns the remote makes the tombstone stale.
59423
+ isRemoteInUse: (provider, remoteId) => store.list().some((s) => s.provider === provider && s.handle.remoteId === remoteId && s.status !== "expired"),
59424
+ nowMs,
59425
+ log
59426
+ });
58365
59427
  const outbox = createSentinelWebhookOutbox({
58366
59428
  ...opts.outboxPath !== void 0 ? { filePath: opts.outboxPath } : {},
58367
59429
  nowMs,
@@ -58383,7 +59445,7 @@ function createSentinelRuntime(opts) {
58383
59445
  if (stored.prevSecret && stored.rotatedAt !== void 0 && nowMs() - stored.rotatedAt < windowMs) {
58384
59446
  secretsOut.push(stored.prevSecret);
58385
59447
  }
58386
- const subId = `sub_${createHash("sha256").update(`${sentinelId}:${target.url}`).digest("hex").slice(0, 32)}`;
59448
+ const subId = sentinelId;
58387
59449
  return { subId, callbackUrl: target.url, secrets: secretsOut };
58388
59450
  },
58389
59451
  /** Expiry gate consulted BEFORE every dispatch (plan §4 W-B task 2b). */
@@ -58578,13 +59640,21 @@ function createSentinelRuntime(opts) {
58578
59640
  }
58579
59641
  async function processEvents(sentinelId, provider, events) {
58580
59642
  let haltedOnError = false;
59643
+ let stoppedEarly = false;
58581
59644
  let delivered = 0;
58582
59645
  for (const event of events) {
58583
59646
  const current = store.get(sentinelId);
58584
- if (!current) break;
58585
- if (!POLLABLE_STATUSES.has(current.status)) break;
59647
+ if (!current) {
59648
+ stoppedEarly = true;
59649
+ break;
59650
+ }
59651
+ if (!POLLABLE_STATUSES.has(current.status)) {
59652
+ stoppedEarly = true;
59653
+ break;
59654
+ }
58586
59655
  if (isExpiredSpec(current)) {
58587
59656
  await expireSentinel(current);
59657
+ stoppedEarly = true;
58588
59658
  break;
58589
59659
  }
58590
59660
  if (store.isSeen(current.id, event.id)) continue;
@@ -58623,7 +59693,7 @@ function createSentinelRuntime(opts) {
58623
59693
  });
58624
59694
  if (updated) await applyLifetime(updated, event, provider);
58625
59695
  }
58626
- return { haltedOnError, delivered };
59696
+ return { haltedOnError, delivered, stoppedEarly };
58627
59697
  }
58628
59698
  async function pollSentinel(sentinel) {
58629
59699
  const provider = await opts.resolveProvider(sentinel.provider);
@@ -58635,19 +59705,35 @@ function createSentinelRuntime(opts) {
58635
59705
  store.update(sentinel.id, { lastError: describeError(err) });
58636
59706
  return;
58637
59707
  }
58638
- const { haltedOnError } = await processEvents(sentinel.id, provider, result.events);
58639
- if (!haltedOnError) {
58640
- const latest = store.get(sentinel.id);
58641
- if (latest) {
58642
- if (provider.ack) {
58643
- try {
58644
- await provider.ack(latest.handle, result.cursor);
58645
- } catch (err) {
58646
- log(`[sentinel-runtime] ack failed for ${sentinel.id}: ${describeError(err)}`);
58647
- }
59708
+ let quarantineFailed = false;
59709
+ for (const item of result.malformed ?? []) {
59710
+ try {
59711
+ quarantine.record({
59712
+ sentinelId: sentinel.id,
59713
+ provider: sentinel.provider,
59714
+ ...sentinel.handle.remoteId !== void 0 ? { remoteId: sentinel.handle.remoteId } : {},
59715
+ item
59716
+ });
59717
+ } catch (err) {
59718
+ quarantineFailed = true;
59719
+ log(`[sentinel-runtime] quarantine write failed for ${sentinel.id}: ${describeError(err)}`);
59720
+ }
59721
+ }
59722
+ const { haltedOnError, stoppedEarly } = await processEvents(sentinel.id, provider, result.events);
59723
+ if (haltedOnError || stoppedEarly || quarantineFailed) {
59724
+ if (quarantineFailed) store.update(sentinel.id, { lastError: "quarantine write failed; batch not acknowledged" });
59725
+ return;
59726
+ }
59727
+ const latest = store.get(sentinel.id);
59728
+ if (latest) {
59729
+ if (provider.ack) {
59730
+ try {
59731
+ await provider.ack(latest.handle, result.cursor);
59732
+ } catch (err) {
59733
+ log(`[sentinel-runtime] ack failed for ${sentinel.id}: ${describeError(err)}`);
58648
59734
  }
58649
- store.update(sentinel.id, { handle: { ...latest.handle, cursor: result.cursor } });
58650
59735
  }
59736
+ store.update(sentinel.id, { handle: { ...latest.handle, cursor: result.cursor } });
58651
59737
  }
58652
59738
  }
58653
59739
  async function pollOnce() {
@@ -58655,6 +59741,7 @@ function createSentinelRuntime(opts) {
58655
59741
  inFlight2 = true;
58656
59742
  try {
58657
59743
  await sweepExpiredSentinels();
59744
+ await cancelTombstones.sweep().catch((err) => log(`[sentinel-runtime] tombstone sweep failed: ${describeError(err)}`));
58658
59745
  outbox.sweep();
58659
59746
  const pollable = store.list().filter((s) => POLLABLE_STATUSES.has(s.status));
58660
59747
  for (const sentinel of pollable) {
@@ -58724,6 +59811,7 @@ function createSentinelRuntime(opts) {
58724
59811
  async start() {
58725
59812
  await reattachAll();
58726
59813
  await sweepExpiredSentinels();
59814
+ await cancelTombstones.sweep().catch((err) => log(`[sentinel-runtime] tombstone sweep failed: ${describeError(err)}`));
58727
59815
  await outbox.dispatch();
58728
59816
  if (!timer) scheduleNext();
58729
59817
  },
@@ -58735,7 +59823,9 @@ function createSentinelRuntime(opts) {
58735
59823
  },
58736
59824
  pollOnce,
58737
59825
  deliverPushed,
58738
- webhookOutbox: outbox
59826
+ webhookOutbox: outbox,
59827
+ quarantine,
59828
+ cancelTombstones
58739
59829
  };
58740
59830
  }
58741
59831
  var BUILTIN_SENTINEL_PROVIDERS = {
@@ -58812,7 +59902,7 @@ async function spawnCloudflaredUntil(argv, opts) {
58812
59902
  };
58813
59903
  let settled = false;
58814
59904
  let forwardedLen = 0;
58815
- return await new Promise((resolve45, reject) => {
59905
+ return await new Promise((resolve47, reject) => {
58816
59906
  const poll = setInterval(() => {
58817
59907
  const text13 = readAll();
58818
59908
  if (opts.onLog) {
@@ -58830,7 +59920,7 @@ async function spawnCloudflaredUntil(argv, opts) {
58830
59920
  if (m) {
58831
59921
  settled = true;
58832
59922
  clearTimeout(timer);
58833
- resolve45({ proc, match: m[0], stopTail: () => clearInterval(poll) });
59923
+ resolve47({ proc, match: m[0], stopTail: () => clearInterval(poll) });
58834
59924
  }
58835
59925
  }, POLL_INTERVAL_MS2);
58836
59926
  if (poll.unref) poll.unref();
@@ -58967,15 +60057,15 @@ function quickTunnelProvider() {
58967
60057
  }
58968
60058
  }, 3e3);
58969
60059
  timer.unref();
58970
- await new Promise((resolve45) => {
60060
+ await new Promise((resolve47) => {
58971
60061
  if (proc.exitCode !== null) {
58972
60062
  clearTimeout(timer);
58973
- resolve45();
60063
+ resolve47();
58974
60064
  return;
58975
60065
  }
58976
60066
  proc.once("exit", () => {
58977
60067
  clearTimeout(timer);
58978
- resolve45();
60068
+ resolve47();
58979
60069
  });
58980
60070
  });
58981
60071
  }
@@ -59128,15 +60218,15 @@ function namedTunnelProvider(cfg) {
59128
60218
  }
59129
60219
  }, 3e3);
59130
60220
  timer.unref();
59131
- await new Promise((resolve45) => {
60221
+ await new Promise((resolve47) => {
59132
60222
  if (proc.exitCode !== null) {
59133
60223
  clearTimeout(timer);
59134
- resolve45();
60224
+ resolve47();
59135
60225
  return;
59136
60226
  }
59137
60227
  proc.once("exit", () => {
59138
60228
  clearTimeout(timer);
59139
- resolve45();
60229
+ resolve47();
59140
60230
  });
59141
60231
  });
59142
60232
  }
@@ -59260,7 +60350,7 @@ function ngrokTunnelProvider(opts) {
59260
60350
  };
59261
60351
  proc.stderr?.on("data", forwardLogs);
59262
60352
  proc.stdout?.on("data", forwardLogs);
59263
- const url = await new Promise((resolve45, reject) => {
60353
+ const url = await new Promise((resolve47, reject) => {
59264
60354
  let settled = false;
59265
60355
  const timer = setTimeout(() => {
59266
60356
  if (settled) return;
@@ -59288,7 +60378,7 @@ function ngrokTunnelProvider(opts) {
59288
60378
  settled = true;
59289
60379
  clearTimeout(timer);
59290
60380
  if (apiPollHandle) clearInterval(apiPollHandle);
59291
- resolve45(pubUrl);
60381
+ resolve47(pubUrl);
59292
60382
  }
59293
60383
  } catch {
59294
60384
  }
@@ -59303,7 +60393,7 @@ function ngrokTunnelProvider(opts) {
59303
60393
  settled = true;
59304
60394
  clearTimeout(timer);
59305
60395
  if (apiPollHandle) clearInterval(apiPollHandle);
59306
- resolve45(reMatch[1]);
60396
+ resolve47(reMatch[1]);
59307
60397
  return;
59308
60398
  }
59309
60399
  for (const line of text13.split(/\r?\n/)) {
@@ -59314,7 +60404,7 @@ function ngrokTunnelProvider(opts) {
59314
60404
  settled = true;
59315
60405
  clearTimeout(timer);
59316
60406
  if (apiPollHandle) clearInterval(apiPollHandle);
59317
- resolve45(parsed.url);
60407
+ resolve47(parsed.url);
59318
60408
  return;
59319
60409
  }
59320
60410
  } catch {
@@ -59361,15 +60451,15 @@ function ngrokTunnelProvider(opts) {
59361
60451
  }
59362
60452
  }, 3e3);
59363
60453
  timer.unref();
59364
- await new Promise((resolve45) => {
60454
+ await new Promise((resolve47) => {
59365
60455
  if (proc.exitCode !== null) {
59366
60456
  clearTimeout(timer);
59367
- resolve45();
60457
+ resolve47();
59368
60458
  return;
59369
60459
  }
59370
60460
  proc.once("exit", () => {
59371
60461
  clearTimeout(timer);
59372
- resolve45();
60462
+ resolve47();
59373
60463
  });
59374
60464
  });
59375
60465
  }
@@ -59581,7 +60671,7 @@ function sameCollapseGroup(a, b) {
59581
60671
  if ((a.key ?? "") !== (b.key ?? "")) return false;
59582
60672
  return a.event === b.event || TURN_FAMILY.has(a.event) && TURN_FAMILY.has(b.event);
59583
60673
  }
59584
- function excerptOf(text13) {
60674
+ function excerptOf2(text13) {
59585
60675
  if (!text13) return void 0;
59586
60676
  const flat = text13.replace(/\s+/g, " ").trim();
59587
60677
  if (!flat) return void 0;
@@ -59596,7 +60686,7 @@ function formatFollowDigest(entries, resolveText, desc) {
59596
60686
  const cwd = d?.cwd ?? entry.cwd;
59597
60687
  const parts = [`[session-follow] ${who} ${entry.event}`];
59598
60688
  if (cwd) parts.push(basename(cwd) || cwd);
59599
- const text13 = excerptOf(resolveText(entry));
60689
+ const text13 = excerptOf2(resolveText(entry));
59600
60690
  if (text13) parts.push(text13);
59601
60691
  return parts.join(" \u2014 ");
59602
60692
  });
@@ -59872,6 +60962,31 @@ function createSentinelAutoLinker(opts) {
59872
60962
  linkNow: link
59873
60963
  };
59874
60964
  }
60965
+ function isSubscriptionPermitted(params, repoAllowlist) {
60966
+ const args = params.arguments;
60967
+ if (typeof args !== "object" || args === null || Array.isArray(args)) return false;
60968
+ const repo = args.repo;
60969
+ return typeof repo === "string" && repoAllowlist.includes(repo);
60970
+ }
60971
+ function createEventsSurfaceServer(opts) {
60972
+ const server = new McpServer({ name: "agentproto-events", version: opts.version }, { capabilities: { tools: {} } });
60973
+ server.registerTool(
60974
+ "events_ping",
60975
+ { description: "Liveness probe for the events surface. Returns ok.", inputSchema: {} },
60976
+ async () => ({ content: [{ type: "text", text: "ok" }] })
60977
+ );
60978
+ registerEventsMethods(server, {
60979
+ list: (params) => opts.handlers.list(params),
60980
+ subscribe: async (params) => {
60981
+ if (!isSubscriptionPermitted(params, opts.repoAllowlist)) {
60982
+ throw new McpError(ErrorCode.InvalidParams, "subscription not permitted");
60983
+ }
60984
+ return opts.handlers.subscribe(params);
60985
+ },
60986
+ unsubscribe: (params) => opts.handlers.unsubscribe(params)
60987
+ });
60988
+ return server;
60989
+ }
59875
60990
  var TELEGRAM_BOT_CREDS_FAMILY = "telegram-bot";
59876
60991
  function makeTelegramBotCredsStore(home) {
59877
60992
  const store = makeCredsStore({
@@ -60350,12 +61465,12 @@ function canonicalJson(value) {
60350
61465
  }
60351
61466
  function isToolAppVisible(tool) {
60352
61467
  const meta = tool._meta;
60353
- const ui = isRecord11(meta?.ui) ? meta.ui : void 0;
61468
+ const ui = isRecord13(meta?.ui) ? meta.ui : void 0;
60354
61469
  const visibility = ui?.visibility;
60355
61470
  if (!Array.isArray(visibility)) return true;
60356
61471
  return visibility.includes("app");
60357
61472
  }
60358
- function isRecord11(v) {
61473
+ function isRecord13(v) {
60359
61474
  return typeof v === "object" && v !== null && !Array.isArray(v);
60360
61475
  }
60361
61476
  function buildUiToolIndex(tools) {
@@ -60880,19 +61995,19 @@ function classifyMcpError(err) {
60880
61995
  return { status: "unreachable", error };
60881
61996
  }
60882
61997
  function uiMetaOf(meta) {
60883
- if (!isRecord12(meta) || !isRecord12(meta.ui)) return {};
61998
+ if (!isRecord14(meta) || !isRecord14(meta.ui)) return {};
60884
61999
  const ui = meta.ui;
60885
62000
  const csp = cspOf(ui.csp);
60886
62001
  return {
60887
62002
  ...csp ? { csp } : {},
60888
- ...isRecord12(ui.permissions) ? { permissions: ui.permissions } : {},
62003
+ ...isRecord14(ui.permissions) ? { permissions: ui.permissions } : {},
60889
62004
  ...typeof ui.prefersBorder === "boolean" ? { prefersBorder: ui.prefersBorder } : {},
60890
62005
  ...typeof ui.domain === "string" ? { domain: ui.domain } : {}
60891
62006
  };
60892
62007
  }
60893
62008
  var CSP_DOMAIN_KEYS = ["connectDomains", "resourceDomains", "frameDomains", "baseUriDomains"];
60894
62009
  function cspOf(raw) {
60895
- if (!isRecord12(raw)) return void 0;
62010
+ if (!isRecord14(raw)) return void 0;
60896
62011
  const csp = {};
60897
62012
  for (const key of CSP_DOMAIN_KEYS) {
60898
62013
  const value = raw[key];
@@ -60902,7 +62017,7 @@ function cspOf(raw) {
60902
62017
  }
60903
62018
  return Object.keys(csp).length > 0 ? csp : void 0;
60904
62019
  }
60905
- function isRecord12(v) {
62020
+ function isRecord14(v) {
60906
62021
  return typeof v === "object" && v !== null && !Array.isArray(v);
60907
62022
  }
60908
62023
  function errorMessage(err) {
@@ -63139,14 +64254,14 @@ function createActivityProjector(opts) {
63139
64254
  if (current && isTerminalActivityState(current.state)) {
63140
64255
  return Promise.resolve(current);
63141
64256
  }
63142
- return new Promise((resolve45) => {
64257
+ return new Promise((resolve47) => {
63143
64258
  let settled = false;
63144
64259
  const finish = (value) => {
63145
64260
  if (settled) return;
63146
64261
  settled = true;
63147
64262
  clearTimeout(timer);
63148
64263
  unsubscribeWait();
63149
- resolve45(value);
64264
+ resolve47(value);
63150
64265
  };
63151
64266
  const timer = setTimeout(() => finish(null), timeoutMs);
63152
64267
  const unsubscribeWait = opts.sessionEvents.on("activity:changed", (ev) => {
@@ -63308,7 +64423,7 @@ function createApprovalsEngine(opts) {
63308
64423
  const list2 = waiters.get(record2.id);
63309
64424
  if (!list2) return;
63310
64425
  waiters.delete(record2.id);
63311
- for (const resolve45 of list2) resolve45(record2);
64426
+ for (const resolve47 of list2) resolve47(record2);
63312
64427
  };
63313
64428
  const emit = (type, record2) => {
63314
64429
  const ts = (/* @__PURE__ */ new Date()).toISOString();
@@ -63378,17 +64493,17 @@ function createApprovalsEngine(opts) {
63378
64493
  const current = liveGet(id);
63379
64494
  if (!current) return Promise.resolve(void 0);
63380
64495
  if (current.status !== "pending") return Promise.resolve(current);
63381
- return new Promise((resolve45) => {
64496
+ return new Promise((resolve47) => {
63382
64497
  const list2 = waiters.get(id) ?? [];
63383
- list2.push(resolve45);
64498
+ list2.push(resolve47);
63384
64499
  waiters.set(id, list2);
63385
64500
  const timer = setTimeout(() => {
63386
64501
  const list22 = waiters.get(id);
63387
64502
  if (!list22) return;
63388
- const idx = list22.indexOf(resolve45);
64503
+ const idx = list22.indexOf(resolve47);
63389
64504
  if (idx >= 0) list22.splice(idx, 1);
63390
64505
  if (list22.length === 0) waiters.delete(id);
63391
- resolve45(liveGet(id));
64506
+ resolve47(liveGet(id));
63392
64507
  }, timeoutMs);
63393
64508
  timer.unref?.();
63394
64509
  });
@@ -63551,7 +64666,7 @@ function humanizeKey(key) {
63551
64666
  const words = key.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_\-.]+/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
63552
64667
  return words.length === 0 ? "Value" : words.charAt(0).toUpperCase() + words.slice(1);
63553
64668
  }
63554
- function isRecord13(value) {
64669
+ function isRecord15(value) {
63555
64670
  return value !== null && typeof value === "object" && !Array.isArray(value);
63556
64671
  }
63557
64672
  function scalarText(value) {
@@ -63581,7 +64696,7 @@ function flattenRows(value, label, depth, out) {
63581
64696
  });
63582
64697
  return;
63583
64698
  }
63584
- if (isRecord13(value)) {
64699
+ if (isRecord15(value)) {
63585
64700
  if (depth >= MAX_DEPTH) return;
63586
64701
  for (const [key, child] of Object.entries(value)) {
63587
64702
  flattenRows(child, label ? `${label} / ${humanizeKey(key)}` : humanizeKey(key), depth + 1, out);
@@ -63603,7 +64718,7 @@ function renderMail(preview2) {
63603
64718
  const cc = addressList(preview2.cc);
63604
64719
  const subject = scalarText(preview2.subject) ?? "";
63605
64720
  const body = scalarText(preview2.body) ?? "";
63606
- const attachments = Array.isArray(preview2.attachments) ? preview2.attachments.filter(isRecord13).map((a) => {
64721
+ const attachments = Array.isArray(preview2.attachments) ? preview2.attachments.filter(isRecord15).map((a) => {
63607
64722
  const name = scalarText(a.fileName) ?? "";
63608
64723
  const size = typeof a.sizeBytes === "number" ? humanSize(a.sizeBytes) : "";
63609
64724
  return size ? `${name} (${size})` : name;
@@ -63618,9 +64733,9 @@ function renderMail(preview2) {
63618
64733
  }
63619
64734
  function renderPreview(record2) {
63620
64735
  const preview2 = record2.preview;
63621
- if (isRecord13(preview2) && preview2.kind === "mail") return renderMail(preview2);
64736
+ if (isRecord15(preview2) && preview2.kind === "mail") return renderMail(preview2);
63622
64737
  const rows = [];
63623
- if (isRecord13(preview2)) {
64738
+ if (isRecord15(preview2)) {
63624
64739
  for (const [key, value] of Object.entries(preview2)) flattenRows(value, humanizeKey(key), 1, rows);
63625
64740
  } else {
63626
64741
  flattenRows(preview2, "Details", 1, rows);
@@ -65717,9 +66832,9 @@ async function* readEventRecords(path) {
65717
66832
  let stream;
65718
66833
  try {
65719
66834
  stream = createReadStream(path, { encoding: "utf8" });
65720
- await new Promise((resolve45, reject) => {
66835
+ await new Promise((resolve47, reject) => {
65721
66836
  stream.once("error", reject);
65722
- stream.once("open", resolve45);
66837
+ stream.once("open", resolve47);
65723
66838
  });
65724
66839
  } catch (err) {
65725
66840
  if (err.code === "ENOENT") return;
@@ -66177,13 +67292,13 @@ function createLinkGuard(opts) {
66177
67292
  return {
66178
67293
  async start() {
66179
67294
  server = createServer(onRequest);
66180
- return new Promise((resolve45, reject) => {
67295
+ return new Promise((resolve47, reject) => {
66181
67296
  server.once("error", reject);
66182
67297
  server.listen(0, "127.0.0.1", () => {
66183
67298
  log(
66184
67299
  `[link-guard] listening on 127.0.0.1:${currentPort()} -> ${opts.target.host}:${opts.target.port}`
66185
67300
  );
66186
- resolve45(handle());
67301
+ resolve47(handle());
66187
67302
  });
66188
67303
  });
66189
67304
  },
@@ -66191,8 +67306,8 @@ function createLinkGuard(opts) {
66191
67306
  if (!server) return;
66192
67307
  const toClose = server;
66193
67308
  server = null;
66194
- await new Promise((resolve45, reject) => {
66195
- toClose.close((err) => err ? reject(err) : resolve45());
67309
+ await new Promise((resolve47, reject) => {
67310
+ toClose.close((err) => err ? reject(err) : resolve47());
66196
67311
  });
66197
67312
  },
66198
67313
  revoke() {
@@ -68203,13 +69318,13 @@ function createJoinTokenRegistry(deps2) {
68203
69318
  return { create, list, revoke, startAutoconnect, shutdown };
68204
69319
  }
68205
69320
  function sleep2(ms, signal) {
68206
- return new Promise((resolve45) => {
68207
- if (signal.aborted) return resolve45();
68208
- const timer = setTimeout(resolve45, ms);
69321
+ return new Promise((resolve47) => {
69322
+ if (signal.aborted) return resolve47();
69323
+ const timer = setTimeout(resolve47, ms);
68209
69324
  if (typeof timer.unref === "function") timer.unref();
68210
69325
  signal.addEventListener("abort", () => {
68211
69326
  clearTimeout(timer);
68212
- resolve45();
69327
+ resolve47();
68213
69328
  });
68214
69329
  });
68215
69330
  }
@@ -68899,7 +70014,14 @@ async function createGateway(opts) {
68899
70014
  registry: sessions,
68900
70015
  followStore: sessionFollowStore,
68901
70016
  sentinelStore,
68902
- cancelSentinel: (id) => cancelSentinelWatch({ store: sentinelStore, resolveProvider: resolveSentinelProviderResolved }, id)
70017
+ cancelSentinel: (id) => cancelSentinelWatch(
70018
+ {
70019
+ store: sentinelStore,
70020
+ resolveProvider: resolveSentinelProviderResolved,
70021
+ tombstones: sentinelRuntime.cancelTombstones
70022
+ },
70023
+ id
70024
+ )
68903
70025
  });
68904
70026
  const inboundWatcher = opts.resolveAgentAdapter ? createInboundWatcher({
68905
70027
  mcpProxy,
@@ -69281,6 +70403,7 @@ async function createGateway(opts) {
69281
70403
  registerSentinelTools(server, {
69282
70404
  store: sentinelStore,
69283
70405
  runtime: sentinelRuntime,
70406
+ tombstones: sentinelRuntime.cancelTombstones,
69284
70407
  resolveProvider: resolveSentinelProviderResolved,
69285
70408
  isSessionAlive,
69286
70409
  ...callerSessionId ? { callerSessionId } : {}
@@ -69310,11 +70433,39 @@ async function createGateway(opts) {
69310
70433
  unsubscribe: (params) => eventsUnsubscribe(params, {
69311
70434
  principal: eventsPrincipal,
69312
70435
  store: sentinelStore,
69313
- resolveProvider: resolveSentinelProviderResolved
70436
+ resolveProvider: resolveSentinelProviderResolved,
70437
+ tombstones: sentinelRuntime.cancelTombstones
69314
70438
  })
69315
70439
  });
69316
70440
  return server;
69317
70441
  };
70442
+ const eventsSecret = process.env.AGENTPROTO_MCP_EVENTS_SECRET ?? "";
70443
+ const eventsRepos = (process.env.AGENTPROTO_MCP_EVENTS_REPOS ?? "").split(",").map((s) => s.trim()).filter(Boolean);
70444
+ const eventsMcp = eventsSecret.length >= 32 ? {
70445
+ secret: eventsSecret,
70446
+ allowedOrigins: (process.env.AGENTPROTO_MCP_EVENTS_ORIGINS ?? "").split(",").map((s) => s.trim()).filter(Boolean),
70447
+ createServer: async () => {
70448
+ const principal = sessionPrincipal("mcp-events-origin");
70449
+ return createEventsSurfaceServer({
70450
+ version: opts.version ?? "0.1.0-alpha",
70451
+ repoAllowlist: eventsRepos,
70452
+ handlers: {
70453
+ list: (params) => eventsList(params, { principal }),
70454
+ subscribe: (params) => eventsSubscribe(params, {
70455
+ principal,
70456
+ store: sentinelStore,
70457
+ resolveProvider: resolveSentinelProviderResolved
70458
+ }),
70459
+ unsubscribe: (params) => eventsUnsubscribe(params, {
70460
+ principal,
70461
+ store: sentinelStore,
70462
+ resolveProvider: resolveSentinelProviderResolved,
70463
+ tombstones: sentinelRuntime.cancelTombstones
70464
+ })
70465
+ }
70466
+ });
70467
+ }
70468
+ } : void 0;
69318
70469
  let internalToolServerPromise;
69319
70470
  dispatchToolBox.fn = async (name, inputs) => {
69320
70471
  if (!internalToolServerPromise) internalToolServerPromise = mcpServerFactory();
@@ -69385,6 +70536,7 @@ async function createGateway(opts) {
69385
70536
  },
69386
70537
  mcpServerFactory,
69387
70538
  orchestratorMcpServerFactory,
70539
+ ...eventsMcp ? { eventsMcp } : {},
69388
70540
  verifyOrchestratorScope: scopeTokens.verify,
69389
70541
  conversations,
69390
70542
  events,
@@ -69402,7 +70554,8 @@ async function createGateway(opts) {
69402
70554
  store: sentinelStore,
69403
70555
  resolveProvider: resolveSentinelProviderResolved,
69404
70556
  isSessionAlive,
69405
- runtime: sentinelRuntime
70557
+ runtime: sentinelRuntime,
70558
+ tombstones: sentinelRuntime.cancelTombstones
69406
70559
  },
69407
70560
  follows: {
69408
70561
  store: sessionFollowStore,
@@ -69595,7 +70748,12 @@ async function createGateway(opts) {
69595
70748
  }, pendingPromptSweepMs);
69596
70749
  pendingPromptTimer.unref?.();
69597
70750
  const livenessSweepIntervalMs = 3e4;
70751
+ const livenessTickGuard = createLivenessTickGuard(livenessSweepIntervalMs, 5e3);
69598
70752
  const livenessSweepTimer = setInterval(() => {
70753
+ if (livenessTickGuard.shouldSkip()) {
70754
+ console.warn("[liveness-sweep] tick fired late (event loop starved) \u2014 skipping this sweep");
70755
+ return;
70756
+ }
69599
70757
  try {
69600
70758
  const orphanedRuns = workflowRunner?.sweep().orphaned ?? [];
69601
70759
  if (orphanedRuns.length > 0) {
@@ -69746,6 +70904,6 @@ var export_readPairingsSnapshot = pairing_registry_exports.readPairingsSnapshot;
69746
70904
  var export_removeProviderKey = providers_store_exports.removeProviderKey;
69747
70905
  var export_setProviderKey = providers_store_exports.setProviderKey;
69748
70906
 
69749
- 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_TTL_MS, DEFAULT_WORKTREE_ISOLATION, DEFAULT_WORKTREE_PROVISION_CONCURRENCY, DEVICE_PROMPT_PATH_PREFIX, EVENT_REGISTRY, FOLLOW_EVENTS, GC_REAPABLE_STATES, GITHUB_SCHEME, HOSTS_VERSION, HOST_HANDSHAKE_REMEDIATION_HINT, HOST_SCOPE_HEADER, HarnessPresetValidationError, INBOUND_PROVIDERS, INDEX_DEFAULT_LIMIT, INDEX_MAX_OUTPUT, INDEX_MAX_PROMPT, INDEX_TAIL_BYTES, JOIN_TOKENS_VERSION, LEGACY_SESSIONS_FILE, MAX_ATTEMPTS, MAX_AUTO_CONTINUE_ATTEMPTS, MAX_MESSAGE_DATA_BYTES, MAX_TTL_MS, MCP_APP_BRIDGE_SCRIPT, MESSAGE_KINDS, MESSAGE_PREAMBLE, MESSAGE_TAG, MESSAGE_URGENCIES, MIN_TTL_MS, MODEL_ROLE_REF_PREFIX, McpEventsError, NO_OUTPUT_STALL_TURN_ERROR, 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, SessionRetiredError, SsrfFetchError, SubscriptionSourceError, TASK_STATUSES, WORKSPACE_SLUG_ENV, WORKTREE_ISOLATION_ENV, WORKTREE_PROVISION_CONCURRENCY_ENV, activityCounts, addHarnessPreset, allowedSignersLine, appendConversationRecord, attachSandbox, backfillSessionIndexes, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogModels, buildCatalogProviderModels, buildHostLoadReport, buildMcpConfigSnippet, buildRouteAwareLaunchConfig, buildSessionRecap, canonicalForModeId, canonicalizeJcs, challengeCacheSize, challengeReasonForSsrf, claudeProjectSlug, collectGcCandidates, collectHostSample, compactOutcome, composeMode, composeSessionObservers, composedFromRangeSha, computeHostWarnings, computeRefreshBefore, 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, createSentinelWebhookOutbox, createSessionFollowStore, createSessionMessage, createSupervisorTaskGateRunner, createTaskLedger, createWorkspaceBrainSubscriber, createWorkspaceBrains, createWorkspaceFs, credentialFingerprint, daemonBearerPrincipal, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, decodeWhsecSecret, decomposeMode, defaultBrowserAdapterIds, defaultBrowserAdapterResolution, defaultReviewKeysDir, defaultReviewLedgerRoot, defaultReviewPackCacheDir, defaultTranscriptBaseDir, defineBraveSearchHttpDriver, defineSerperHttpDriver, deleteUserPreset, deliverEventEnvelope, deliveryBackoffDelay, deriveIndexFromTranscript, deriveSessionOutcome, deriveSessionUsage, describeOwner, encodeWhsecSecret, enrichRollupWithProviderQuota, enrichWithRemainingQuota, ensureReviewSigningKey, envelopeClampedBytes, escapeHumanPrompt, escapeMessageBody, evaluateCostBudget, eventsList, eventsSubscribe, eventsUnsubscribe, execGh, extractHelpSection, fetchHostTurns, fetchPrStatus, fileConversationStore, fileExists3 as fileExists, filterActivities, findComposeCandidate, findConversationRecord, findEventDefinition, findNativeMode, findPrForCommit, formatToolCall, formatToolResult, getDefaultHarnessPreset, getHarnessPreset, getHostLoadService, getMcpCredentialDeps, getProcessStatsService, export_getProviderKey as getProviderKey, getToolHelp, getUserPreset, githubRepoOf, harnessPresetsPath, hostPlaceholders, hostRecordToMirrorRecord, indexEntryFromDescriptor, injectMcpAppBridge, export_injectProviderKeysIntoEnv as injectProviderKeysIntoEnv, isAgentCliAuthConfigured, isAutomatedPromptSource, isClosedTaskStatus, isKnownSessionEndReason, isMessageAllowed, isNoSessionPromptFailure, isProviderLimitError, isPubliclyRoutable, isRetired, isSafeBucketSlug, isTerminalActivityState, listBuckets, listHarnessPresets, listModelRoles, listPresets, listToolHelpTopics, listUserPresets, loadHarnessPresets, export_loadProviders as loadProviders, loadQuotaStore, loadUserPresets, loadWorkspaceModelRoles, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, makeRestartForRouting, makeSandboxCredsStore, makeSandboxResolver, matchesMessageFilter, matchesSessionQuery, measureTool, measureToolList, messageFrom, migrateLegacySessionsFile, migrationMarkerPath, mintMessageId, mirrorCursorForSession, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeCallbackUrl, normalizeCommand, normalizeInbound, normalizeModeId, normalizeRemote, normalizeSkillsOption, normalizeWorktreeField, parseAnthropicRateLimitHeaders, parseDuration, parseModelRoleRef, parsePrUrl, parseTaskStatus, parseWindow, parseWorktreeIsolationMode, pickUsageDetail, policyToActivities, policyWatchesSession, prHeadSha, prToActivities, projectBrowserTools, projectSessionUsage, promptDeviceSession, promptHostSession, export_providerEnvVar as providerEnvVar, export_providersPath as providersPath, readAllSessionIndexes, readConversationIndex, readDaemonRegistry, readHostsSnapshot, export_readPairingsSnapshot as readPairingsSnapshot, readProfileQuota, readRegisteredSlugs, readRuntimeMeta, readSandboxLedger, readSessionForBrain, readSessionIndex, readTranscriptTail, 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, resolveDevicePromptMapping, resolveEffectiveLlmEndpointFlag, resolveModelRole, resolveNativeLink, resolvePosture, resolvePrincipal, resolveRelation, resolveReuseFromLedger, resolveReviewerPreset, resolveSpawnDefaults, resolveSubscriptionCredential, resolveSuccessor, rollupSessionSubtree, rollupUsage, runContinueOnBootPass, runCrashDetectPass, runEagerResumePass, runIdleReapPass, runShellLane, runStallWatchdogPass, sandboxLedgerPath, sanitizeLabel, saveUserPreset, searchSessionIndexes, seedWalkForTranscript, serializeEnvelope, sessionEventsPath, sessionIndexPath, sessionPrincipal, sessionTranscriptDir, setDefaultPreset, setDefaultSessionsBaseDir, setEgressDispatcherForTests, setMcpCredentialDeps, export_setProviderKey as setProviderKey, signAttestation, signWebhook, ssrfFetch, subscriptionId, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, syncDeviceMirror, tenantScope, toMcpEvent, toWebhookEnvelope, toWorktreeStatusView, trackWorktreeProvision, turnToActivities, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, untilForTtl, updateHarnessPreset, upsertSandboxLedger, userPresetsPath, validateAgainstInputSchema, verifyCallback, verifyInboundSignature, verifySignedAttestation, webSearchTool, webhookRequestId, wireSessionFollow, withPr, withPrStatus, workflowToActivities, writeDaemonRegistryEntry, writeSessionIndex };
70907
+ 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_TTL_MS, DEFAULT_WORKTREE_ISOLATION, DEFAULT_WORKTREE_PROVISION_CONCURRENCY, DEVICE_PROMPT_PATH_PREFIX, EVENT_REGISTRY, FOLLOW_EVENTS, GC_REAPABLE_STATES, GITHUB_SCHEME, HOSTS_VERSION, HOST_HANDSHAKE_REMEDIATION_HINT, HOST_SCOPE_HEADER, HarnessPresetValidationError, INBOUND_PROVIDERS, INDEX_DEFAULT_LIMIT, INDEX_MAX_OUTPUT, INDEX_MAX_PROMPT, INDEX_TAIL_BYTES, JOIN_TOKENS_VERSION, LEGACY_SESSIONS_FILE, MAX_ATTEMPTS, MAX_AUTO_CONTINUE_ATTEMPTS, MAX_MESSAGE_DATA_BYTES, MAX_TTL_MS, MCP_APP_BRIDGE_SCRIPT, MESSAGE_KINDS, MESSAGE_PREAMBLE, MESSAGE_TAG, MESSAGE_URGENCIES, MIN_TTL_MS, MODEL_ROLE_REF_PREFIX, McpEventsError, NO_OUTPUT_STALL_TURN_ERROR, 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, SentinelBackingExpiredError, SessionRetiredError, SsrfFetchError, SubscriptionSourceError, TASK_STATUSES, WORKSPACE_SLUG_ENV, WORKTREE_ISOLATION_ENV, WORKTREE_PROVISION_CONCURRENCY_ENV, activityCounts, addHarnessPreset, allowedSignersLine, appendConversationRecord, attachSandbox, backfillSessionIndexes, bucketDir, bucketSessionsFile, bucketTranscriptDir, buildCatalogModels, buildCatalogProviderModels, buildHostLoadReport, buildMcpConfigSnippet, buildRouteAwareLaunchConfig, buildSessionRecap, canonicalForModeId, canonicalizeJcs, challengeCacheSize, challengeReasonForSsrf, claudeProjectSlug, collectGcCandidates, collectHostSample, compactOutcome, composeMode, composeSessionObservers, composedFromRangeSha, computeHostWarnings, computeRefreshBefore, 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, createSentinelWebhookOutbox, createSessionFollowStore, createSessionMessage, createSupervisorTaskGateRunner, createTaskLedger, createWorkspaceBrainSubscriber, createWorkspaceBrains, createWorkspaceFs, credentialFingerprint, daemonBearerPrincipal, daemonRegistryDir, decideWorktreeIsolation, declaredPresetToProviderPreset, decodeWhsecSecret, decomposeMode, defaultBrowserAdapterIds, defaultBrowserAdapterResolution, defaultReviewKeysDir, defaultReviewLedgerRoot, defaultReviewPackCacheDir, defaultTranscriptBaseDir, defineBraveSearchHttpDriver, defineSerperHttpDriver, deleteUserPreset, deliverEventEnvelope, deliveryBackoffDelay, deriveIndexFromTranscript, deriveSessionOutcome, deriveSessionUsage, describeOwner, encodeWhsecSecret, enrichRollupWithProviderQuota, enrichWithRemainingQuota, ensureReviewSigningKey, envelopeClampedBytes, escapeHumanPrompt, escapeMessageBody, evaluateCostBudget, eventsList, eventsSubscribe, eventsUnsubscribe, execGh, extractHelpSection, fetchHostTurns, fetchPrStatus, fileConversationStore, fileExists3 as fileExists, filterActivities, findComposeCandidate, findConversationRecord, findEventDefinition, findNativeMode, findPrForCommit, formatToolCall, formatToolResult, getDefaultHarnessPreset, getHarnessPreset, getHostLoadService, getMcpCredentialDeps, getProcessStatsService, export_getProviderKey as getProviderKey, getToolHelp, getUserPreset, githubRepoOf, harnessPresetsPath, hostPlaceholders, hostRecordToMirrorRecord, indexEntryFromDescriptor, injectMcpAppBridge, export_injectProviderKeysIntoEnv as injectProviderKeysIntoEnv, isAgentCliAuthConfigured, isAutomatedPromptSource, isClosedTaskStatus, isKnownSessionEndReason, isMessageAllowed, isNoSessionPromptFailure, isProviderLimitError, isPubliclyRoutable, isRetired, isSafeBucketSlug, isTerminalActivityState, listBuckets, listHarnessPresets, listModelRoles, listPresets, listToolHelpTopics, listUserPresets, loadHarnessPresets, export_loadProviders as loadProviders, loadQuotaStore, loadUserPresets, loadWorkspaceModelRoles, loadWorktreeIsolation, locateConversationByNativePath, locateConversationBySessionId, makeBrowserAdapterLister, makeRestartForRouting, makeSandboxCredsStore, makeSandboxResolver, matchesMessageFilter, matchesSessionQuery, measureTool, measureToolList, messageFrom, migrateLegacySessionsFile, migrationMarkerPath, mintMessageId, mirrorCursorForSession, monitorPolicyWait, monitorSessionWait, narrowOrchestratorTools, normalizeCallbackUrl, normalizeCommand, normalizeInbound, normalizeModeId, normalizeRemote, normalizeSkillsOption, normalizeWorktreeField, parseAnthropicRateLimitHeaders, parseDuration, parseModelRoleRef, parsePrUrl, parseTaskStatus, parseWindow, parseWorktreeIsolationMode, pickUsageDetail, policyToActivities, policyWatchesSession, prHeadSha, prToActivities, projectBrowserTools, projectSessionUsage, promptDeviceSession, promptHostSession, export_providerEnvVar as providerEnvVar, export_providersPath as providersPath, readAllSessionIndexes, readConversationIndex, readDaemonRegistry, readHostsSnapshot, export_readPairingsSnapshot as readPairingsSnapshot, readProfileQuota, readRegisteredSlugs, readRuntimeMeta, readSandboxLedger, readSessionForBrain, readSessionIndex, readTranscriptTail, 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, resolveDevicePromptMapping, resolveEffectiveLlmEndpointFlag, resolveModelRole, resolveNativeLink, resolvePosture, resolvePrincipal, resolveRelation, resolveReuseFromLedger, resolveReviewerPreset, resolveSpawnDefaults, resolveSubscriptionCredential, resolveSuccessor, rollupSessionSubtree, rollupUsage, runContinueOnBootPass, runCrashDetectPass, runEagerResumePass, runIdleReapPass, runShellLane, runStallWatchdogPass, sandboxLedgerPath, sanitizeLabel, saveUserPreset, searchSessionIndexes, seedWalkForTranscript, serializeEnvelope, sessionEventsPath, sessionIndexPath, sessionPrincipal, sessionTranscriptDir, setDefaultPreset, setDefaultSessionsBaseDir, setEgressDispatcherForTests, setMcpCredentialDeps, export_setProviderKey as setProviderKey, signAttestation, signWebhook, ssrfFetch, subscriptionId, summarizeBranchGcApply, sweepStaleDaemonRegistry, sweepStaleRuntimeMetas, syncDeviceMirror, tenantScope, toMcpEvent, toWebhookEnvelope, toWorktreeStatusView, trackWorktreeProvision, turnToActivities, unlinkDaemonRegistryEntry, unlinkRuntimeMeta, untilForTtl, updateHarnessPreset, upsertSandboxLedger, userPresetsPath, validateAgainstInputSchema, verifyCallback, verifyInboundSignature, verifySignedAttestation, webSearchTool, webhookRequestId, wireSessionFollow, withBranchGcApplySummary, withPr, withPrStatus, workflowToActivities, writeDaemonRegistryEntry, writeSessionIndex };
69750
70908
  //# sourceMappingURL=index.mjs.map
69751
70909
  //# sourceMappingURL=index.mjs.map