@tekmidian/pai 0.65.2 → 0.66.1

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.
Files changed (56) hide show
  1. package/dist/{chain-CT3SI-3z.mjs → chain-CZfKIEq9.mjs} +4 -4
  2. package/dist/{chain-CT3SI-3z.mjs.map → chain-CZfKIEq9.mjs.map} +1 -1
  3. package/dist/cli/index.mjs +9 -9
  4. package/dist/cli/program.mjs +9 -9
  5. package/dist/{context-handover-cache-CRSFdkNi.mjs → context-handover-cache-pkHzmL3c.mjs} +3 -3
  6. package/dist/{context-handover-cache-CRSFdkNi.mjs.map → context-handover-cache-pkHzmL3c.mjs.map} +1 -1
  7. package/dist/daemon/index.mjs +7 -7
  8. package/dist/{daemon-SaPnmoyg.mjs → daemon-DXlaVCkw.mjs} +9 -9
  9. package/dist/{daemon-SaPnmoyg.mjs.map → daemon-DXlaVCkw.mjs.map} +1 -1
  10. package/dist/{daemon-DoTVK9z6.mjs → daemon-DyORmxZu.mjs} +7 -7
  11. package/dist/daemon-mcp/index.mjs +8 -6
  12. package/dist/daemon-mcp/index.mjs.map +1 -1
  13. package/dist/{fallback-vHjFZP0Y.mjs → fallback-CIiq2Raw.mjs} +18 -8
  14. package/dist/fallback-CIiq2Raw.mjs.map +1 -0
  15. package/dist/hooks/block-sleep-poll.mjs.map +2 -2
  16. package/dist/hooks/load-project-context.mjs +16 -0
  17. package/dist/hooks/load-project-context.mjs.map +2 -2
  18. package/dist/hooks/route-agents-to-worker.mjs +16 -0
  19. package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
  20. package/dist/hooks/security-validator.mjs +16 -0
  21. package/dist/hooks/security-validator.mjs.map +2 -2
  22. package/dist/hooks/worker-guard.mjs +15 -0
  23. package/dist/hooks/worker-guard.mjs.map +3 -3
  24. package/dist/hooks/worker-proxy.mjs +16 -0
  25. package/dist/hooks/worker-proxy.mjs.map +2 -2
  26. package/dist/hooks/worker-status-line.mjs +17 -0
  27. package/dist/hooks/worker-status-line.mjs.map +2 -2
  28. package/dist/hooks/worker-supervision.mjs +16 -0
  29. package/dist/hooks/worker-supervision.mjs.map +2 -2
  30. package/dist/{main-resolver-VSM5ytnA.mjs → main-resolver-BKz_OuEh.mjs} +2 -2
  31. package/dist/{main-resolver-CKNGux1B.mjs → main-resolver-DPPwHNtn.mjs} +2 -2
  32. package/dist/{main-resolver-CKNGux1B.mjs.map → main-resolver-DPPwHNtn.mjs.map} +1 -1
  33. package/dist/{planner-Dzs9-8AS.mjs → planner-Dm5Acyo1.mjs} +5 -5
  34. package/dist/{planner-Dzs9-8AS.mjs.map → planner-Dm5Acyo1.mjs.map} +1 -1
  35. package/dist/{program-DK0mRRlf.mjs → program-JJ0TwSHI.mjs} +59 -16
  36. package/dist/program-JJ0TwSHI.mjs.map +1 -0
  37. package/dist/{run-DMaCpvMW.mjs → run-BCpbxqA5.mjs} +490 -26
  38. package/dist/run-BCpbxqA5.mjs.map +1 -0
  39. package/dist/{run-env-BHWnXqle.mjs → run-env-DRcp7A8K.mjs} +16 -2
  40. package/dist/run-env-DRcp7A8K.mjs.map +1 -0
  41. package/dist/{server-DlL1QI2k.mjs → server-DgmAHyFK.mjs} +2 -2
  42. package/dist/{server-DlL1QI2k.mjs.map → server-DgmAHyFK.mjs.map} +1 -1
  43. package/dist/{session-keepalive-DVZ9-7qg.mjs → session-keepalive-DxjTHGcK.mjs} +4 -4
  44. package/dist/{session-keepalive-DVZ9-7qg.mjs.map → session-keepalive-DxjTHGcK.mjs.map} +1 -1
  45. package/dist/{work-queue-worker-DZiEMvBQ.mjs → work-queue-worker-4tLa5gpQ.mjs} +2 -2
  46. package/dist/{work-queue-worker-DZiEMvBQ.mjs.map → work-queue-worker-4tLa5gpQ.mjs.map} +1 -1
  47. package/dist/{work-queue-worker-D7GHuj2I.mjs → work-queue-worker-rFZvTO3p.mjs} +4 -4
  48. package/docs/commands/README.md +1 -0
  49. package/docs/commands/worker.md +30 -0
  50. package/package.json +1 -1
  51. package/src/hooks/ts/lib/worker-guard.test.ts +12 -0
  52. package/src/hooks/ts/lib/worker-guard.ts +13 -0
  53. package/dist/fallback-vHjFZP0Y.mjs.map +0 -1
  54. package/dist/program-DK0mRRlf.mjs.map +0 -1
  55. package/dist/run-DMaCpvMW.mjs.map +0 -1
  56. package/dist/run-env-BHWnXqle.mjs.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { i as getRegistryBackend } from "./factory-CNpPQQ-5.mjs";
2
- import { E as readWorkersSection, O as resolveModelCapability, T as providerKeyPath, b as isModelCapability, d as NATIVE_ANTHROPIC_MODELS, g as classModelCapability, h as assertProviderRunnable, k as resolveProviderKey, m as WorkersConfigError, n as claudeCommand, t as buildRunEnv } from "./run-env-BHWnXqle.mjs";
3
- import { _ as statusPath, d as resolveTarget, f as setCooldown, g as panesDir, h as ledgerPath, i as capabilityForRun, m as eventsPath, n as ensureProxyRunning, o as isQuotaFailure, p as ensureNoMcpConfig, s as nextAutoProvider, t as DEFAULT_PROXY_PORT, u as resolveCapabilityRun, v as workersLogDir } from "./server-DlL1QI2k.mjs";
2
+ import { A as resolveProviderKey, D as readWorkersSection, E as providerKeyPath, _ as classModelCapability, f as NATIVE_ANTHROPIC_MODELS, g as assertProviderRunnable, h as WorkersConfigError, k as resolveModelCapability, n as claudeCommand, t as buildRunEnv, x as isModelCapability } from "./run-env-DRcp7A8K.mjs";
3
+ import { _ as statusPath, d as resolveTarget, f as setCooldown, g as panesDir, h as ledgerPath, i as capabilityForRun, m as eventsPath, n as ensureProxyRunning, o as isQuotaFailure, p as ensureNoMcpConfig, s as nextAutoProvider, t as DEFAULT_PROXY_PORT, u as resolveCapabilityRun, v as workersLogDir } from "./server-DgmAHyFK.mjs";
4
4
  import { appendFileSync, closeSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
5
5
  import { homedir, platform, tmpdir } from "node:os";
6
6
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
@@ -1159,7 +1159,7 @@ function assertNoSnapshotDrift(cwd, snapshot, paths, id) {
1159
1159
  * started from. Works even if `cwd`'s HEAD moved since the snapshot: the
1160
1160
  * diff is applied to files, not merged into a tree.
1161
1161
  */
1162
- function mergeSnapshotWorker(logDir, id, st) {
1162
+ function mergeSnapshotWorker(logDir, id, st, noCommit = false) {
1163
1163
  const snapshot = st.worktreeBase;
1164
1164
  const branch = st.branch;
1165
1165
  const worktreeDir = st.worktreeDir;
@@ -1212,6 +1212,7 @@ function mergeSnapshotWorker(logDir, id, st) {
1212
1212
  const why = (typeof err.stderr === "string" ? err.stderr : err.stderr?.toString("utf8")) || err.message || String(e);
1213
1213
  throw new Error(`worker ${id}: git refused to apply ${branch}'s changes to ${st.cwd} — ${why.trim()}. The worktree ${worktreeDir} was kept: resolve by hand, then re-run merge`);
1214
1214
  }
1215
+ if (noCommit) return noCommitNextSteps(id, branch, worktreeDir, changedPaths);
1215
1216
  removeWorktree(st.cwd, worktreeDir, true);
1216
1217
  try {
1217
1218
  git(st.cwd, [
@@ -1230,6 +1231,143 @@ function mergeSnapshotWorker(logDir, id, st) {
1230
1231
  if (salvage.skipped.length) notes.push(`skipped ${salvage.skipped.join(", ")}`);
1231
1232
  return notes.length ? `${base}; ${notes.join("; ")}` : base;
1232
1233
  }
1234
+ function noCommitNextSteps(id, branch, worktreeDir, paths) {
1235
+ return `applied worker ${id}'s changes (branch ${branch}) to the checkout as uncommitted changes: ${paths.join(", ")}. Branch and worktree ${worktreeDir} were kept. Next: review (git diff), commit, then run: pai worker gc`;
1236
+ }
1237
+ /**
1238
+ * `merge --no-commit`: apply base..branch to the checkout with a 3-way
1239
+ * `git apply`, leaving the result uncommitted. On conflict the tree is left
1240
+ * as is (markers + unmerged index entries) and the conflicted paths are listed.
1241
+ */
1242
+ function applyNoCommit(cwd, id, branch, worktreeDir, base, paths) {
1243
+ const diff = execFileSync("git", [
1244
+ "-C",
1245
+ cwd,
1246
+ "diff",
1247
+ "--binary",
1248
+ `${base}..${branch}`
1249
+ ], {
1250
+ encoding: "buffer",
1251
+ timeout: 3e4,
1252
+ maxBuffer: 64 * 1024 * 1024,
1253
+ env: {
1254
+ ...process.env,
1255
+ LC_ALL: "C"
1256
+ }
1257
+ });
1258
+ try {
1259
+ execFileSync("git", [
1260
+ "-C",
1261
+ cwd,
1262
+ "apply",
1263
+ "--3way",
1264
+ "--binary"
1265
+ ], {
1266
+ input: diff,
1267
+ timeout: 3e4,
1268
+ stdio: [
1269
+ "pipe",
1270
+ "pipe",
1271
+ "pipe"
1272
+ ],
1273
+ env: {
1274
+ ...process.env,
1275
+ LC_ALL: "C"
1276
+ }
1277
+ });
1278
+ } catch (e) {
1279
+ const conflicted = git(cwd, [
1280
+ "diff",
1281
+ "--name-only",
1282
+ "--diff-filter=U"
1283
+ ]).split("\n").filter(Boolean);
1284
+ const err = e;
1285
+ const why = ((typeof err.stderr === "string" ? err.stderr : err.stderr?.toString("utf8")) || err.message || "").trim();
1286
+ throw new Error(conflicted.length ? `worker ${id}: applying ${branch} conflicted in ${conflicted.join(", ")}. The tree was left for you: resolve those paths, review, commit; branch and worktree ${worktreeDir} were kept` : `worker ${id}: git refused to apply ${branch} to ${cwd} — ${why}. Nothing was changed; branch and worktree were kept`);
1287
+ }
1288
+ return noCommitNextSteps(id, branch, worktreeDir, paths);
1289
+ }
1290
+ /**
1291
+ * `pai worker verify <id>`: for every file the worker's branch (or its
1292
+ * refs/pai-archive/<id> archive once the branch is gone) changed since its
1293
+ * base, compare bytes with the working tree or `--against <ref>`.
1294
+ * identical = the target already holds the worker's version (a worker delete
1295
+ * counts as identical when the target lacks the file too); differs = other
1296
+ * bytes; missing = the worker's file is absent in the target; deleted = the
1297
+ * worker deleted it but the target still has it. ok = every file identical.
1298
+ */
1299
+ function verifyWorker(logDir, id, opts = {}) {
1300
+ const st = loadStatus(logDir, id);
1301
+ if (!st) throw new Error(`no worker named "${id}"`);
1302
+ const env = {
1303
+ ...process.env,
1304
+ LC_ALL: "C"
1305
+ };
1306
+ const bytes = (args) => {
1307
+ try {
1308
+ return execFileSync("git", [
1309
+ "-C",
1310
+ st.cwd,
1311
+ ...args
1312
+ ], {
1313
+ env,
1314
+ timeout: 3e4,
1315
+ maxBuffer: 256 * 1024 * 1024,
1316
+ stdio: [
1317
+ "ignore",
1318
+ "pipe",
1319
+ "pipe"
1320
+ ]
1321
+ });
1322
+ } catch {
1323
+ return null;
1324
+ }
1325
+ };
1326
+ const has = (ref) => bytes([
1327
+ "rev-parse",
1328
+ "--verify",
1329
+ "-q",
1330
+ `${ref}^{commit}`
1331
+ ]) !== null;
1332
+ const ref = [`refs/heads/${worktreeBranch(id)}`, `refs/pai-archive/${id}`].find(has);
1333
+ if (!ref) throw new Error(`worker ${id}: neither ${worktreeBranch(id)} nor refs/pai-archive/${id} exists in ${st.cwd}`);
1334
+ const base = st.worktreeSnapshot && st.worktreeBase ? st.worktreeBase : git(st.cwd, [
1335
+ "merge-base",
1336
+ "HEAD",
1337
+ ref
1338
+ ]);
1339
+ const list = (bytes([
1340
+ "diff",
1341
+ "--name-status",
1342
+ "--no-renames",
1343
+ "-z",
1344
+ `${base}..${ref}`
1345
+ ]) ?? Buffer.alloc(0)).toString("utf8").split("\0").filter(Boolean);
1346
+ const files = [];
1347
+ for (let i = 0; i + 1 < list.length; i += 2) {
1348
+ const path = list[i + 1];
1349
+ const deleted = list[i] === "D";
1350
+ const target = opts.against ? bytes(["show", `${opts.against}:${path}`]) : existsSync(join(st.cwd, path)) ? readFileSync(join(st.cwd, path)) : null;
1351
+ let state;
1352
+ if (deleted) state = target ? "deleted" : "identical";
1353
+ else {
1354
+ const mine = bytes(["show", `${ref}:${path}`]);
1355
+ state = !target ? "missing" : mine && mine.equals(target) ? "identical" : "differs";
1356
+ }
1357
+ files.push({
1358
+ path,
1359
+ state
1360
+ });
1361
+ }
1362
+ return {
1363
+ id,
1364
+ ref,
1365
+ base,
1366
+ against: opts.against ?? "working tree",
1367
+ files,
1368
+ ok: files.every((f) => f.state === "identical")
1369
+ };
1370
+ }
1233
1371
  /**
1234
1372
  * `pai worker merge <id>`: salvage whatever the worker left uncommitted onto
1235
1373
  * its branch, refuse when the original checkout is dirty in paths the branch
@@ -1239,10 +1377,10 @@ function mergeSnapshotWorker(logDir, id, st) {
1239
1377
  * genuinely means it holds nothing, and reporting success there would
1240
1378
  * destroy the worktree for no gain.
1241
1379
  */
1242
- function mergeWorker(logDir, id) {
1380
+ function mergeWorker(logDir, id, opts = {}) {
1243
1381
  const st = mustHaveBranch(logDir, id);
1244
1382
  if (st.merged) return `worker ${id}: branch ${st.branch} already merged`;
1245
- if (st.worktreeSnapshot) return mergeSnapshotWorker(logDir, id, st);
1383
+ if (st.worktreeSnapshot) return mergeSnapshotWorker(logDir, id, st, opts.noCommit);
1246
1384
  const salvage = existsSync(st.worktreeDir) ? salvageUncommitted(st.worktreeDir, st.label || UNLABELED) : {
1247
1385
  committed: [],
1248
1386
  skipped: []
@@ -1264,6 +1402,7 @@ function mergeWorker(logDir, id) {
1264
1402
  st.branch
1265
1403
  ]).split("\n").filter(Boolean);
1266
1404
  assertNoDirtyOverlap(st.cwd, incomingPaths, id, st.branch);
1405
+ if (opts.noCommit) return applyNoCommit(st.cwd, id, st.branch, st.worktreeDir, mergeBase, incomingPaths);
1267
1406
  try {
1268
1407
  git(st.cwd, [
1269
1408
  "merge",
@@ -1635,6 +1774,126 @@ function itermForClaudeSession(logDir, claudeSession, now = Date.now()) {
1635
1774
  return term ? itermUuid(term) : null;
1636
1775
  }
1637
1776
 
1777
+ //#endregion
1778
+ //#region src/workers/endreason.ts
1779
+ /**
1780
+ * Who or what ended a worker. One place decides the reason; the runner's
1781
+ * signal handlers and the deadline record it, `pai worker kill` and the daemon
1782
+ * leave a kill-request marker (`<logDir>/<id>.kill`) before signalling so the
1783
+ * runner can tell their stops from an external one.
1784
+ */
1785
+ const killMarkerPath = (logDir, id) => join(logDir, `${id}.kill`);
1786
+ /** Best description of the invoking session: PAI parent/AIBroker session env, else the Claude Code session id. */
1787
+ function callerDescription(env = process.env) {
1788
+ const name = env.PAI_WORKER_PARENT ?? env.PAI_SESSION_NAME ?? env.AIBROKER_SESSION_NAME ?? env.AIBROKER_SESSION;
1789
+ if (name) return name;
1790
+ if (env.CLAUDE_CODE_SESSION_ID) return `claude-session:${env.CLAUDE_CODE_SESSION_ID}`;
1791
+ return env.CLAUDECODE ? "claude-code" : "shell";
1792
+ }
1793
+ /** Write the kill-request marker and the WORKER-KILL ledger line; call right before signalling. */
1794
+ function requestKill(logDir, ledger, id, by = callerDescription()) {
1795
+ const req = {
1796
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
1797
+ by,
1798
+ pid: process.pid,
1799
+ ppid: process.ppid,
1800
+ cwd: process.cwd()
1801
+ };
1802
+ writeFileSync(killMarkerPath(logDir, id), JSON.stringify(req));
1803
+ appendLedger(ledger, "WORKER-KILL", {
1804
+ id,
1805
+ by,
1806
+ pid: req.pid,
1807
+ ppid: req.ppid,
1808
+ cwd: req.cwd
1809
+ });
1810
+ }
1811
+ function readMarker(logDir, id) {
1812
+ const p = killMarkerPath(logDir, id);
1813
+ if (!existsSync(p)) return null;
1814
+ try {
1815
+ return JSON.parse(readFileSync(p, "utf8"));
1816
+ } catch {
1817
+ return {
1818
+ ts: "",
1819
+ by: "unknown",
1820
+ pid: 0,
1821
+ ppid: 0,
1822
+ cwd: ""
1823
+ };
1824
+ } finally {
1825
+ try {
1826
+ unlinkSync(p);
1827
+ } catch {}
1828
+ }
1829
+ }
1830
+ function pidAlive(pid) {
1831
+ try {
1832
+ process.kill(pid, 0);
1833
+ return true;
1834
+ } catch (e) {
1835
+ return e.code === "EPERM";
1836
+ }
1837
+ }
1838
+ /** Command name of a pid (`ps -o comm=`), or undefined when it is gone or ps fails. */
1839
+ function commOf(pid) {
1840
+ try {
1841
+ return execFileSync("ps", [
1842
+ "-o",
1843
+ "comm=",
1844
+ "-p",
1845
+ String(pid)
1846
+ ], { encoding: "utf8" }).trim() || void 0;
1847
+ } catch {
1848
+ return;
1849
+ }
1850
+ }
1851
+ /** Parent facts recorded at start, for the end report ("parent: claude"). */
1852
+ function parentInfo() {
1853
+ const parentPid = process.ppid;
1854
+ return {
1855
+ parentPid,
1856
+ parentComm: commOf(parentPid),
1857
+ parentBackground: process.env.CLAUDECODE ? true : void 0
1858
+ };
1859
+ }
1860
+ /**
1861
+ * The single decision. `signal` = the signal the runner received (absent for a
1862
+ * deadline stop or a normal exit); `timedOut` = the deadline fired. Reads (and
1863
+ * consumes) the kill marker.
1864
+ */
1865
+ function decideEndReason(a) {
1866
+ if (a.timedOut) return { reason: "deadline" };
1867
+ if (!a.signal) return { reason: "exit" };
1868
+ const marker = readMarker(a.logDir, a.id);
1869
+ if (marker) return {
1870
+ reason: "pai-worker-kill",
1871
+ by: marker.by,
1872
+ note: `${a.signal} requested by pid ${marker.pid}`
1873
+ };
1874
+ const alive = (a.isAlive ?? pidAlive)(a.parentPid ?? process.ppid);
1875
+ const parent = `parent ${a.parentPid ?? "?"}${a.parentComm ? ` (${a.parentComm})` : ""}`;
1876
+ if (!alive) return {
1877
+ reason: "parent-gone",
1878
+ note: `${a.signal}; ${parent} not alive`
1879
+ };
1880
+ return {
1881
+ reason: "external-signal",
1882
+ note: `${a.signal}; sender unknown (not deadline, not pai worker kill); a Claude Code background task stop (TaskStop, Bash timeout, session end) is the usual source; ${parent} alive` + (a.parentBackground ? "; started from a Claude Code session" : "")
1883
+ };
1884
+ }
1885
+ /** The status/ledger fields for an end. */
1886
+ function endFields(e) {
1887
+ return {
1888
+ reason: e.reason,
1889
+ ...e.by ? { by: e.by } : {}
1890
+ };
1891
+ }
1892
+ /** "external-signal", "pai-worker-kill by <caller>" for a finished worker; null when nothing was recorded. */
1893
+ function endLabel(s) {
1894
+ return s.endReason ? `${s.endReason}${s.endBy ? ` by ${s.endBy}` : ""}` : null;
1895
+ }
1896
+
1638
1897
  //#endregion
1639
1898
  //#region src/workers/controls.ts
1640
1899
  /**
@@ -2951,6 +3210,7 @@ function fullContractPrompt(format) {
2951
3210
  "116 s). Ending your turn ends this process, so a background job gets no later wake-up here:",
2952
3211
  "run_in_background is denied. Run the long command in the FOREGROUND with a Bash timeout up to 600000 ms",
2953
3212
  "and split longer jobs into steps that each fit it.",
3213
+ "Kill only processes you started, by PID (`cmd & echo $! > /tmp/x.pid`, then `kill $(cat /tmp/x.pid)`); never `pkill`/`killall`/`kill $(pgrep ...)` — other workers' command lines contain your spec text.",
2954
3214
  ...fileNotInlineBlock(),
2955
3215
  "",
2956
3216
  `Operator messages: a user turn starting with ${OPERATOR_MARK} was typed by the operator while you`,
@@ -3115,6 +3375,118 @@ function relShort(p, cwd) {
3115
3375
  return r && !r.startsWith("..") ? r : p;
3116
3376
  }
3117
3377
 
3378
+ //#endregion
3379
+ //#region src/workers/deadline.ts
3380
+ /** Local HH:MM → epoch ms: today, or tomorrow when already past. */
3381
+ function parseDeadline(hhmm, now = /* @__PURE__ */ new Date()) {
3382
+ const m = /^(\d{1,2}):(\d{2})$/.exec(hhmm.trim());
3383
+ if (!m || Number(m[1]) > 23 || Number(m[2]) > 59) throw new Error(`--deadline: expected HH:MM (local time, 00:00-23:59), got "${hhmm}"`);
3384
+ const at = new Date(now);
3385
+ at.setHours(Number(m[1]), Number(m[2]), 0, 0);
3386
+ if (at.getTime() <= now.getTime()) at.setDate(at.getDate() + 1);
3387
+ return at.getTime();
3388
+ }
3389
+ /** The run's limit, or null when disabled (--max-minutes 0 / default 0). */
3390
+ function resolveLimit(flags, defaultMinutes, now = /* @__PURE__ */ new Date()) {
3391
+ if (flags.maxMinutes !== void 0 && flags.deadline !== void 0) throw new Error("--max-minutes and --deadline are mutually exclusive");
3392
+ if (flags.deadline !== void 0) {
3393
+ const deadlineAt = parseDeadline(flags.deadline, now);
3394
+ return {
3395
+ deadlineAt,
3396
+ minutes: Math.max(1, Math.round((deadlineAt - now.getTime()) / 6e4))
3397
+ };
3398
+ }
3399
+ const minutes = flags.maxMinutes ?? defaultMinutes;
3400
+ if (!Number.isFinite(minutes) || minutes < 0) throw new Error(`--max-minutes: expected a non-negative number, got ${minutes}`);
3401
+ return minutes === 0 ? null : {
3402
+ deadlineAt: now.getTime() + minutes * 6e4,
3403
+ minutes
3404
+ };
3405
+ }
3406
+ const pad = (n) => String(n).padStart(2, "0");
3407
+ /** The one line injected into the worker's system prompt. */
3408
+ function deadlinePromptLine(limit) {
3409
+ const d = new Date(limit.deadlineAt);
3410
+ return `Hard deadline ${pad(d.getHours())}:${pad(d.getMinutes())} (${limit.minutes} min). Finish and report before it; at the deadline the run is stopped and whatever you have is salvaged.`;
3411
+ }
3412
+ /** ps column: "36s left", "12m left", "1h05m left" for running; "OVERDUE ..." for overdue; "timed out" for stopped; null otherwise. */
3413
+ function deadlineLabel(s, now = Date.now()) {
3414
+ if (s.timedOut) return "timed out";
3415
+ if (s.state !== "running" || !s.deadlineAt) return null;
3416
+ const secs = Math.abs(s.deadlineAt - now) / 1e3;
3417
+ let label;
3418
+ if (secs < 60) label = `${Math.floor(secs)}s`;
3419
+ else if (secs < 3600) label = `${Math.floor(secs / 60)}m`;
3420
+ else {
3421
+ const hours = Math.floor(secs / 3600);
3422
+ const mins = Math.floor(secs % 3600 / 60);
3423
+ label = `${hours}h${String(mins).padStart(2, "0")}m`;
3424
+ }
3425
+ return s.deadlineAt >= now ? `${label} left` : `OVERDUE ${label}`;
3426
+ }
3427
+ /** Grace between SIGTERM and SIGKILL. */
3428
+ const DEADLINE_GRACE_MS = 1e4;
3429
+ /**
3430
+ * Stop `proc` at the deadline: `onFire`, SIGTERM, SIGKILL after `graceMs`
3431
+ * when it is still alive. Returns the disarm function.
3432
+ */
3433
+ function armDeadline(proc, limit, onFire, graceMs = DEADLINE_GRACE_MS) {
3434
+ let kill = null;
3435
+ const t = setTimeout(() => {
3436
+ onFire();
3437
+ try {
3438
+ proc.kill("SIGTERM");
3439
+ } catch {}
3440
+ kill = setTimeout(() => {
3441
+ if (proc.exitCode === null && proc.signalCode === null) try {
3442
+ proc.kill("SIGKILL");
3443
+ } catch {}
3444
+ }, graceMs);
3445
+ kill.unref();
3446
+ }, Math.max(0, limit.deadlineAt - Date.now()));
3447
+ return () => {
3448
+ clearTimeout(t);
3449
+ if (kill) clearTimeout(kill);
3450
+ };
3451
+ }
3452
+
3453
+ //#endregion
3454
+ //#region src/workers/browser-tools.ts
3455
+ /**
3456
+ * browser-tools.ts — what `--no-browser` strips: --chrome, every browser MCP
3457
+ * tool grant (mcp__claude-in-chrome__*, browsr, playwright, …) and browser
3458
+ * server names, from the caller's claude args. The PreToolUse worker guard
3459
+ * denies the same tools (hooks/ts/lib/worker-guard.ts) when
3460
+ * PAI_WORKER_NO_BROWSER=1.
3461
+ */
3462
+ const BROWSER_SERVER = /chrome|browsr|browser|playwright|puppeteer/i;
3463
+ /** A browser MCP server name (bare, e.g. "browsr") or tool name (mcp__browsr__open). */
3464
+ function isBrowserTool(name) {
3465
+ const server = /^mcp__(.+?)(__|$)/.exec(name)?.[1] ?? name;
3466
+ return BROWSER_SERVER.test(server) && (name.startsWith("mcp__") || !name.includes("__"));
3467
+ }
3468
+ const keepNonBrowser = (list) => list.split(",").map((s) => s.trim()).filter((s) => s && !isBrowserTool(s));
3469
+ /** claude args without --chrome and browser tool grants (an emptied --allowedTools goes too). */
3470
+ function stripBrowserArgs(rest) {
3471
+ const out = [];
3472
+ for (let i = 0; i < rest.length; i++) {
3473
+ const a = rest[i];
3474
+ if (a === "--chrome") continue;
3475
+ if (a === "--allowedTools" && i + 1 < rest.length && !rest[i + 1].startsWith("-")) {
3476
+ const kept = keepNonBrowser(rest[++i]);
3477
+ if (kept.length) out.push(a, kept.join(","));
3478
+ } else if (a.startsWith("--allowedTools=")) {
3479
+ const kept = keepNonBrowser(a.slice(15));
3480
+ if (kept.length) out.push(`--allowedTools=${kept.join(",")}`);
3481
+ } else out.push(a);
3482
+ }
3483
+ return out;
3484
+ }
3485
+ /** Comma-list entries (--mcp values, class MCP) without browser servers. */
3486
+ function stripBrowserNames(names) {
3487
+ return names.map((n) => keepNonBrowser(n).join(",")).filter(Boolean);
3488
+ }
3489
+
3118
3490
  //#endregion
3119
3491
  //#region src/workers/codex.ts
3120
3492
  /**
@@ -3523,7 +3895,7 @@ async function runWorker(opts) {
3523
3895
  if (gc?.length) process.stderr.write(`pai worker: archived ${gc.length} leftover worktree(s) (pai worker gc)\n`);
3524
3896
  } catch {}
3525
3897
  if (opts.className === "plan" && !opts._planner) {
3526
- const { runPlanner } = await import("./planner-Dzs9-8AS.mjs");
3898
+ const { runPlanner } = await import("./planner-Dm5Acyo1.mjs");
3527
3899
  return runPlanner(opts);
3528
3900
  }
3529
3901
  const parent = launchParent(opts.parent);
@@ -3544,7 +3916,9 @@ async function runWorker(opts) {
3544
3916
  className: opts.className
3545
3917
  });
3546
3918
  assertProviderRunnable(target.providerName, target.provider);
3547
- const parsed = parseRunnerArgs(opts.claudeArgs);
3919
+ const limit = resolveLimit(opts, config.defaultMaxMinutes);
3920
+ const noBrowser = opts.browser === void 0 ? config.noBrowserByDefault : !opts.browser;
3921
+ const parsed = parseRunnerArgs(noBrowser ? stripBrowserArgs(opts.claudeArgs) : opts.claudeArgs);
3548
3922
  const label = opts.label ?? shortText(parsed.prompt ?? UNLABELED, 70);
3549
3923
  const model = capabilityResolution ? capabilityResolution.model : resolveRunModel(target, opts.className, opts.modelFlag);
3550
3924
  const reportFormat = resolveReportFormat(opts.reportFormatFlag);
@@ -3593,7 +3967,9 @@ async function runWorker(opts) {
3593
3967
  worktreeFlag: opts.worktreeFlag,
3594
3968
  className: opts.className,
3595
3969
  reportFormat,
3596
- capability: capability ?? void 0
3970
+ capability: capability ?? void 0,
3971
+ limit,
3972
+ noBrowser
3597
3973
  });
3598
3974
  else rc = await executeRun({
3599
3975
  config,
@@ -3618,7 +3994,9 @@ async function runWorker(opts) {
3618
3994
  reroutes: opts._reroutes ?? 0,
3619
3995
  printCmd: opts.printCmd,
3620
3996
  reportFormat,
3621
- noReportRetry: opts.noReportRetry ?? false
3997
+ noReportRetry: opts.noReportRetry ?? false,
3998
+ limit,
3999
+ noBrowser
3622
4000
  });
3623
4001
  await returnControlsIfHeld(logDir, workerId);
3624
4002
  return rc;
@@ -3736,6 +4114,32 @@ function interactiveMcpTools(explicitMcp, callerTools, projectLaunch) {
3736
4114
  */
3737
4115
  /** Per-git-call bound for the salvage a kill signal runs before exiting. */
3738
4116
  const SIGNAL_SALVAGE_MS = 5e3;
4117
+ /** The deadline fired: record it on the status and in the ledger (the caller then stops the child). */
4118
+ function fireDeadline(logDir, status, ledger, limit) {
4119
+ status.timedOut = true;
4120
+ status.last = `deadline reached after ${limit.minutes} min`;
4121
+ saveStatus(logDir, status);
4122
+ appendLedger(ledger, "WORKER-DEADLINE", {
4123
+ id: status.id,
4124
+ minutes: limit.minutes
4125
+ });
4126
+ }
4127
+ /** Decide why a signal ended the run and put it on the status; the caller writes WORKER-END. */
4128
+ function recordSignalEnd(logDir, status, signal) {
4129
+ const end = decideEndReason({
4130
+ logDir,
4131
+ id: status.id,
4132
+ signal,
4133
+ timedOut: status.timedOut,
4134
+ parentPid: status.parentPid,
4135
+ parentComm: status.parentComm,
4136
+ parentBackground: status.parentBackground
4137
+ });
4138
+ status.endReason = end.reason;
4139
+ status.endBy = end.by;
4140
+ status.endNote = end.note;
4141
+ return end;
4142
+ }
3739
4143
  async function finaliseRun(f) {
3740
4144
  const { logDir, ledger, ok, rc } = f;
3741
4145
  let { status, report } = f;
@@ -3749,8 +4153,19 @@ async function finaliseRun(f) {
3749
4153
  result: report?.result ?? "-"
3750
4154
  };
3751
4155
  }
4156
+ if (f.timedOut) {
4157
+ const reason = `deadline reached after ${f.timedOut} min`;
4158
+ status.timedOut = true;
4159
+ status.last = reason;
4160
+ report = {
4161
+ ...report ?? {},
4162
+ result: "~",
4163
+ why: reason,
4164
+ notes: [reason, report?.notes ?? f.fallbackText].filter(Boolean).join("\n")
4165
+ };
4166
+ }
3752
4167
  const orphanReason = checkOrphanedChildren(logDir, wid);
3753
- const succeeded = ok && !orphanReason;
4168
+ const succeeded = ok && !orphanReason && !f.timedOut;
3754
4169
  status.state = succeeded ? "done" : "failed";
3755
4170
  status.rc = rc;
3756
4171
  status.secs = f.secs;
@@ -3766,12 +4181,19 @@ async function finaliseRun(f) {
3766
4181
  reason: orphanReason
3767
4182
  });
3768
4183
  }
4184
+ const end = decideEndReason({
4185
+ logDir,
4186
+ id: wid,
4187
+ timedOut: f.timedOut !== void 0
4188
+ });
4189
+ status.endReason = end.reason;
3769
4190
  saveStatus(logDir, status);
3770
4191
  appendLedger(ledger, "WORKER-END", {
3771
4192
  id: wid,
3772
4193
  ...f.end,
3773
4194
  rc,
3774
- secs: f.secs
4195
+ secs: f.secs,
4196
+ ...endFields(end)
3775
4197
  });
3776
4198
  if (f.worktree) status = recordWorktree(logDir, status, f.worktree, ok);
3777
4199
  const early = await f.beforeHandoff?.(status, report);
@@ -3918,7 +4340,7 @@ async function announcePane(logDir, config, wid, term, noPane, ledger) {
3918
4340
  }
3919
4341
  }
3920
4342
  async function executeRun(a) {
3921
- const { config, logDir, target, model, label, parsed, noPane } = a;
4343
+ const { config, logDir, target, model, label, parsed, noPane, limit, noBrowser } = a;
3922
4344
  const headless = parsed.headless;
3923
4345
  let proxyUrl;
3924
4346
  if (target.provider.protocol === "openai") proxyUrl = `${await ensureProxyRunning(DEFAULT_PROXY_PORT, logDir)}/${target.providerName}`;
@@ -3943,19 +4365,23 @@ async function executeRun(a) {
3943
4365
  });
3944
4366
  }
3945
4367
  env.PAI_WORKER_ID = wid;
3946
- const chromeArgs = chromeGrantArgs([
3947
- ...a.mcpFlag ? [a.mcpFlag] : [],
4368
+ if (noBrowser) env.PAI_WORKER_NO_BROWSER = "1";
4369
+ const mcpFlag = noBrowser ? stripBrowserNames(a.mcpFlag ? [a.mcpFlag] : [])[0] : a.mcpFlag;
4370
+ const classMcp = noBrowser && target.classMcp ? stripBrowserNames(target.classMcp) : target.classMcp;
4371
+ if (noBrowser) parsed.mcp = stripBrowserNames(parsed.mcp);
4372
+ const chromeArgs = noBrowser ? [] : chromeGrantArgs([
4373
+ ...mcpFlag ? [mcpFlag] : [],
3948
4374
  ...parsed.mcp,
3949
- ...target.classMcp ?? [],
4375
+ ...classMcp ?? [],
3950
4376
  ...parsed.allowedTools
3951
4377
  ], parsed.rest);
3952
4378
  let mcpArgs = [];
3953
4379
  let toolsFlag = headless ? headlessToolsFlag(parsed.allowedTools) : [];
3954
4380
  if (headless && !parsed.callerMcpConfig) {
3955
4381
  const wanted = [
3956
- ...a.mcpFlag ? [a.mcpFlag] : [],
4382
+ ...mcpFlag ? [mcpFlag] : [],
3957
4383
  ...parsed.mcp,
3958
- ...target.classMcp ?? [],
4384
+ ...classMcp ?? [],
3959
4385
  ...mcpServersFromToolGrants(parsed.allowedTools)
3960
4386
  ];
3961
4387
  if (wanted.length) mcpArgs = [
@@ -3969,7 +4395,7 @@ async function executeRun(a) {
3969
4395
  ensureNoMcpConfig(logDir)
3970
4396
  ];
3971
4397
  } else if (!headless && !parsed.callerMcpConfig) {
3972
- const explicitMcp = [...a.mcpFlag ? [a.mcpFlag] : [], ...parsed.mcp];
4398
+ const explicitMcp = [...mcpFlag ? [mcpFlag] : [], ...parsed.mcp];
3973
4399
  const projectLaunch = await projectLaunchConfig(cwd);
3974
4400
  const picked = interactiveMcpTools(explicitMcp, parsed.callerTools, projectLaunch);
3975
4401
  const applied = projectLaunch && {
@@ -3996,19 +4422,26 @@ async function executeRun(a) {
3996
4422
  cmd.push(...appendSystemPromptArgs([
3997
4423
  ...headless && !parsed.callerSystemPrompt ? [workerContractPrompt(a.className, a.reportFormat)] : [],
3998
4424
  ...headless ? [worktree ? worktreeSystemPrompt(wid, worktree.branch, worktree.dir, worktree.snapshot) : inPlaceSystemPrompt()] : [],
4425
+ ...headless && limit ? [deadlinePromptLine(limit)] : [],
3999
4426
  ...parsed.callerSystemPrompts
4000
4427
  ]));
4001
4428
  cmd = ensureToolSearch(cmd);
4002
4429
  if (a.printCmd) {
4003
4430
  console.log(JSON.stringify({
4004
4431
  cmd,
4005
- cwd: worktree?.dir ?? cwd
4432
+ cwd: worktree?.dir ?? cwd,
4433
+ browser: noBrowser ? "off" : "on",
4434
+ ...limit ? {
4435
+ deadline: new Date(limit.deadlineAt).toISOString(),
4436
+ maxMinutes: limit.minutes
4437
+ } : {}
4006
4438
  }));
4007
4439
  return 0;
4008
4440
  }
4009
4441
  let status = {
4010
4442
  id: wid,
4011
4443
  pid: process.pid,
4444
+ ...parentInfo(),
4012
4445
  label,
4013
4446
  cwd,
4014
4447
  term,
@@ -4038,7 +4471,11 @@ async function executeRun(a) {
4038
4471
  worktreeBase: worktree.base,
4039
4472
  worktreeSnapshot: worktree.snapshot
4040
4473
  } : {},
4041
- ...a.specPath ? { spec: a.specPath } : {}
4474
+ ...a.specPath ? { spec: a.specPath } : {},
4475
+ ...limit ? {
4476
+ deadlineAt: limit.deadlineAt,
4477
+ maxMinutes: limit.minutes
4478
+ } : {}
4042
4479
  };
4043
4480
  saveStatus(logDir, status);
4044
4481
  a.onWorkerStart?.(wid);
@@ -4106,7 +4543,9 @@ async function executeRun(a) {
4106
4543
  });
4107
4544
  }) : null;
4108
4545
  let killed = false;
4546
+ const stopDeadline = limit ? armDeadline(proc, limit, () => fireDeadline(logDir, status, ledger, limit)) : null;
4109
4547
  const cleanup = () => {
4548
+ stopDeadline?.();
4110
4549
  if (closeTimer) clearTimeout(closeTimer);
4111
4550
  operatorServer?.close();
4112
4551
  };
@@ -4115,7 +4554,8 @@ async function executeRun(a) {
4115
4554
  status.state = "killed";
4116
4555
  status.rc = 143;
4117
4556
  status.secs = Math.floor((Date.now() - t0) / 1e3);
4118
- status.last = `killed by signal ${sig}`;
4557
+ const end = recordSignalEnd(logDir, status, sig);
4558
+ status.last = `killed by signal ${sig} (${end.reason}${end.by ? ` by ${end.by}` : ""})`;
4119
4559
  saveStatus(logDir, status);
4120
4560
  appendLedger(ledger, "WORKER-END", {
4121
4561
  id: wid,
@@ -4125,6 +4565,8 @@ async function executeRun(a) {
4125
4565
  rc: 143,
4126
4566
  secs: status.secs,
4127
4567
  killed: 1,
4568
+ ...endFields(end),
4569
+ note: end.note,
4128
4570
  label
4129
4571
  });
4130
4572
  if (worktree) {
@@ -4284,6 +4726,7 @@ async function executeRun(a) {
4284
4726
  report: ctx.resultReport,
4285
4727
  fallbackText: resultEvent?.result,
4286
4728
  worktree,
4729
+ timedOut: status.timedOut ? limit?.minutes : void 0,
4287
4730
  beforeHandoff: async (st, rep) => {
4288
4731
  if (headless && !a.quiet) printResult(parsed.outputFormat, resultEvent, rc, logDir, wid, rep, worktreeExtras(st));
4289
4732
  const resultText = resultEvent?.result ?? "";
@@ -4385,10 +4828,11 @@ async function executeCodexRun(a) {
4385
4828
  });
4386
4829
  }
4387
4830
  env.PAI_WORKER_ID = wid;
4388
- const prompt = (worktree ? worktreeSystemPrompt(wid, worktree.branch, worktree.dir, worktree.snapshot) + "\n\n" : "") + parsed.prompt;
4831
+ const prompt = (a.limit ? deadlinePromptLine(a.limit) + "\n\n" : "") + (worktree ? worktreeSystemPrompt(wid, worktree.branch, worktree.dir, worktree.snapshot) + "\n\n" : "") + parsed.prompt;
4389
4832
  let status = {
4390
4833
  id: wid,
4391
4834
  pid: process.pid,
4835
+ ...parentInfo(),
4392
4836
  label,
4393
4837
  cwd,
4394
4838
  term,
@@ -4418,7 +4862,11 @@ async function executeCodexRun(a) {
4418
4862
  worktreeBase: worktree.base,
4419
4863
  worktreeSnapshot: worktree.snapshot
4420
4864
  } : {},
4421
- ...a.specPath ? { spec: a.specPath } : {}
4865
+ ...a.specPath ? { spec: a.specPath } : {},
4866
+ ...a.limit ? {
4867
+ deadlineAt: a.limit.deadlineAt,
4868
+ maxMinutes: a.limit.minutes
4869
+ } : {}
4422
4870
  };
4423
4871
  saveStatus(logDir, status);
4424
4872
  a.onWorkerStart?.(wid);
@@ -4467,6 +4915,7 @@ async function executeCodexRun(a) {
4467
4915
  ...target.provider.contextWindow ? { context_window: target.provider.contextWindow } : {}
4468
4916
  });
4469
4917
  let killed = false;
4918
+ const stopDeadline = a.limit ? armDeadline(proc, a.limit, () => fireDeadline(logDir, status, ledger, a.limit)) : null;
4470
4919
  process.once("SIGTERM", onCodexSignal("SIGTERM"));
4471
4920
  process.once("SIGINT", onCodexSignal("SIGINT"));
4472
4921
  process.once("SIGHUP", onCodexSignal("SIGHUP"));
@@ -4476,8 +4925,21 @@ async function executeCodexRun(a) {
4476
4925
  status.state = "killed";
4477
4926
  status.rc = 143;
4478
4927
  status.secs = Math.floor((Date.now() - t0) / 1e3);
4479
- status.last = `killed by signal ${sig}`;
4928
+ const end = recordSignalEnd(logDir, status, sig);
4929
+ status.last = `killed by signal ${sig} (${end.reason}${end.by ? ` by ${end.by}` : ""})`;
4480
4930
  saveStatus(logDir, status);
4931
+ appendLedger(ledger, "WORKER-END", {
4932
+ id: wid,
4933
+ provider: target.providerName,
4934
+ mode: "headless",
4935
+ model,
4936
+ rc: 143,
4937
+ secs: status.secs,
4938
+ killed: 1,
4939
+ ...endFields(end),
4940
+ note: end.note,
4941
+ label
4942
+ });
4481
4943
  if (worktree) {
4482
4944
  salvageOnExit(ledger, wid, label, worktree.dir, SIGNAL_SALVAGE_MS);
4483
4945
  try {
@@ -4507,6 +4969,7 @@ async function executeCodexRun(a) {
4507
4969
  proc.on("error", reject);
4508
4970
  proc.on("close", (code) => resolve(code ?? (killed ? 143 : 1)));
4509
4971
  });
4972
+ stopDeadline?.();
4510
4973
  closeSync(eventsFd);
4511
4974
  const secs = Math.floor((Date.now() - t0) / 1e3);
4512
4975
  const finalText = fold.finalText ?? "";
@@ -4554,6 +5017,7 @@ async function executeCodexRun(a) {
4554
5017
  report,
4555
5018
  fallbackText: finalText,
4556
5019
  worktree,
5020
+ timedOut: status.timedOut ? a.limit?.minutes : void 0,
4557
5021
  beforeHandoff: (st, rep) => {
4558
5022
  if (!a.quiet) printResult(parsed.outputFormat, resultEvent, rc, logDir, wid, rep, worktreeExtras(st));
4559
5023
  }
@@ -4690,5 +5154,5 @@ function resultFromOutput(out) {
4690
5154
  }
4691
5155
 
4692
5156
  //#endregion
4693
- export { expandMcpNames as $, ledgerSummary as A, loadStatuses as B, discardWorker as C, uncommittedPaths as D, mergeWorker as E, contextPercent as F, setWorkerLabel as G, nowStamp as H, elapsedOf as I, parseRunnerArgs as J, waitForTerminalStatus as K, isChatPane as L, ageOf as M, alive as N, worktreesDir as O, contextLabel as P, describeMcp as Q, isLive as R, workerInScope as S, git as T, ownsPid as U, newWorkerId as V, saveStatus as W, pinNotices as X, shortText as Y, projectLaunchConfig as Z, itermForClaudeSession as _, parseWorkerReport as a, sessionMapPath as b, readInbox as c, checkPaneForWorker as d, openFollowPane as f, currentTabKey as g, markControlsHeld as h, usageContextTokens as i, UNLABELED as j, appendLedger as k, sayToWorker as l, clickrControlsArgv as m, runWorker as n, renderReport as o, openPaneForWorker as p, longInlinePromptHint as q, testProvider as r, handoffFromInside as s, printResult as t, workerDepth as u, itermUuid as v, gcWorktrees as w, sessionTag as x, resolveSession as y, loadStatus as z };
4694
- //# sourceMappingURL=run-DMaCpvMW.mjs.map
5157
+ export { parseRunnerArgs as $, mergeWorker as A, contextPercent as B, resolveSession as C, discardWorker as D, workerInScope as E, ledgerSummary as F, loadStatuses as G, isChatPane as H, UNLABELED as I, ownsPid as J, newWorkerId as K, ageOf as L, verifyWorker as M, worktreesDir as N, gcWorktrees as O, appendLedger as P, longInlinePromptHint as Q, alive as R, itermUuid as S, sessionTag as T, isLive as U, elapsedOf as V, loadStatus as W, setWorkerLabel as X, saveStatus as Y, waitForTerminalStatus as Z, callerDescription as _, deadlineLabel as a, currentTabKey as b, handoffFromInside as c, workerDepth as d, shortText as et, checkPaneForWorker as f, markControlsHeld as g, clickrControlsArgv as h, usageContextTokens as i, expandMcpNames as it, uncommittedPaths as j, git as k, readInbox as l, openPaneForWorker as m, runWorker as n, projectLaunchConfig as nt, parseWorkerReport as o, openFollowPane as p, nowStamp as q, testProvider as r, describeMcp as rt, renderReport as s, printResult as t, pinNotices as tt, sayToWorker as u, endLabel as v, sessionMapPath as w, itermForClaudeSession as x, requestKill as y, contextLabel as z };
5158
+ //# sourceMappingURL=run-BCpbxqA5.mjs.map