@tekmidian/pai 0.65.2 → 0.66.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.
Files changed (54) hide show
  1. package/dist/{chain-CT3SI-3z.mjs → chain-D4PuQ2c-.mjs} +4 -4
  2. package/dist/{chain-CT3SI-3z.mjs.map → chain-D4PuQ2c-.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-BEoVgVfJ.mjs} +9 -9
  9. package/dist/{daemon-SaPnmoyg.mjs.map → daemon-BEoVgVfJ.mjs.map} +1 -1
  10. package/dist/{daemon-DoTVK9z6.mjs → daemon-DPAYU9hp.mjs} +7 -7
  11. package/dist/daemon-mcp/index.mjs +6 -6
  12. package/dist/daemon-mcp/index.mjs.map +1 -1
  13. package/dist/{fallback-vHjFZP0Y.mjs → fallback-B5svk75f.mjs} +15 -7
  14. package/dist/fallback-B5svk75f.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 +10 -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 +16 -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-EJPZnnif.mjs} +5 -5
  34. package/dist/{planner-Dzs9-8AS.mjs.map → planner-EJPZnnif.mjs.map} +1 -1
  35. package/dist/{program-DK0mRRlf.mjs → program-DSETzrWg.mjs} +56 -16
  36. package/dist/{program-DK0mRRlf.mjs.map → program-DSETzrWg.mjs.map} +1 -1
  37. package/dist/{run-DMaCpvMW.mjs → run-Z4akoa8G.mjs} +325 -23
  38. package/dist/run-Z4akoa8G.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-BDhytXEo.mjs} +4 -4
  44. package/dist/{session-keepalive-DVZ9-7qg.mjs.map → session-keepalive-BDhytXEo.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.ts +4 -0
  52. package/dist/fallback-vHjFZP0Y.mjs.map +0 -1
  53. package/dist/run-DMaCpvMW.mjs.map +0 -1
  54. 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",
@@ -3115,6 +3254,118 @@ function relShort(p, cwd) {
3115
3254
  return r && !r.startsWith("..") ? r : p;
3116
3255
  }
3117
3256
 
3257
+ //#endregion
3258
+ //#region src/workers/deadline.ts
3259
+ /** Local HH:MM → epoch ms: today, or tomorrow when already past. */
3260
+ function parseDeadline(hhmm, now = /* @__PURE__ */ new Date()) {
3261
+ const m = /^(\d{1,2}):(\d{2})$/.exec(hhmm.trim());
3262
+ 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}"`);
3263
+ const at = new Date(now);
3264
+ at.setHours(Number(m[1]), Number(m[2]), 0, 0);
3265
+ if (at.getTime() <= now.getTime()) at.setDate(at.getDate() + 1);
3266
+ return at.getTime();
3267
+ }
3268
+ /** The run's limit, or null when disabled (--max-minutes 0 / default 0). */
3269
+ function resolveLimit(flags, defaultMinutes, now = /* @__PURE__ */ new Date()) {
3270
+ if (flags.maxMinutes !== void 0 && flags.deadline !== void 0) throw new Error("--max-minutes and --deadline are mutually exclusive");
3271
+ if (flags.deadline !== void 0) {
3272
+ const deadlineAt = parseDeadline(flags.deadline, now);
3273
+ return {
3274
+ deadlineAt,
3275
+ minutes: Math.max(1, Math.round((deadlineAt - now.getTime()) / 6e4))
3276
+ };
3277
+ }
3278
+ const minutes = flags.maxMinutes ?? defaultMinutes;
3279
+ if (!Number.isFinite(minutes) || minutes < 0) throw new Error(`--max-minutes: expected a non-negative number, got ${minutes}`);
3280
+ return minutes === 0 ? null : {
3281
+ deadlineAt: now.getTime() + minutes * 6e4,
3282
+ minutes
3283
+ };
3284
+ }
3285
+ const pad = (n) => String(n).padStart(2, "0");
3286
+ /** The one line injected into the worker's system prompt. */
3287
+ function deadlinePromptLine(limit) {
3288
+ const d = new Date(limit.deadlineAt);
3289
+ 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.`;
3290
+ }
3291
+ /** ps column: "36s left", "12m left", "1h05m left" for running; "OVERDUE ..." for overdue; "timed out" for stopped; null otherwise. */
3292
+ function deadlineLabel(s, now = Date.now()) {
3293
+ if (s.timedOut) return "timed out";
3294
+ if (s.state !== "running" || !s.deadlineAt) return null;
3295
+ const secs = Math.abs(s.deadlineAt - now) / 1e3;
3296
+ let label;
3297
+ if (secs < 60) label = `${Math.floor(secs)}s`;
3298
+ else if (secs < 3600) label = `${Math.floor(secs / 60)}m`;
3299
+ else {
3300
+ const hours = Math.floor(secs / 3600);
3301
+ const mins = Math.floor(secs % 3600 / 60);
3302
+ label = `${hours}h${String(mins).padStart(2, "0")}m`;
3303
+ }
3304
+ return s.deadlineAt >= now ? `${label} left` : `OVERDUE ${label}`;
3305
+ }
3306
+ /** Grace between SIGTERM and SIGKILL. */
3307
+ const DEADLINE_GRACE_MS = 1e4;
3308
+ /**
3309
+ * Stop `proc` at the deadline: `onFire`, SIGTERM, SIGKILL after `graceMs`
3310
+ * when it is still alive. Returns the disarm function.
3311
+ */
3312
+ function armDeadline(proc, limit, onFire, graceMs = DEADLINE_GRACE_MS) {
3313
+ let kill = null;
3314
+ const t = setTimeout(() => {
3315
+ onFire();
3316
+ try {
3317
+ proc.kill("SIGTERM");
3318
+ } catch {}
3319
+ kill = setTimeout(() => {
3320
+ if (proc.exitCode === null && proc.signalCode === null) try {
3321
+ proc.kill("SIGKILL");
3322
+ } catch {}
3323
+ }, graceMs);
3324
+ kill.unref();
3325
+ }, Math.max(0, limit.deadlineAt - Date.now()));
3326
+ return () => {
3327
+ clearTimeout(t);
3328
+ if (kill) clearTimeout(kill);
3329
+ };
3330
+ }
3331
+
3332
+ //#endregion
3333
+ //#region src/workers/browser-tools.ts
3334
+ /**
3335
+ * browser-tools.ts — what `--no-browser` strips: --chrome, every browser MCP
3336
+ * tool grant (mcp__claude-in-chrome__*, browsr, playwright, …) and browser
3337
+ * server names, from the caller's claude args. The PreToolUse worker guard
3338
+ * denies the same tools (hooks/ts/lib/worker-guard.ts) when
3339
+ * PAI_WORKER_NO_BROWSER=1.
3340
+ */
3341
+ const BROWSER_SERVER = /chrome|browsr|browser|playwright|puppeteer/i;
3342
+ /** A browser MCP server name (bare, e.g. "browsr") or tool name (mcp__browsr__open). */
3343
+ function isBrowserTool(name) {
3344
+ const server = /^mcp__(.+?)(__|$)/.exec(name)?.[1] ?? name;
3345
+ return BROWSER_SERVER.test(server) && (name.startsWith("mcp__") || !name.includes("__"));
3346
+ }
3347
+ const keepNonBrowser = (list) => list.split(",").map((s) => s.trim()).filter((s) => s && !isBrowserTool(s));
3348
+ /** claude args without --chrome and browser tool grants (an emptied --allowedTools goes too). */
3349
+ function stripBrowserArgs(rest) {
3350
+ const out = [];
3351
+ for (let i = 0; i < rest.length; i++) {
3352
+ const a = rest[i];
3353
+ if (a === "--chrome") continue;
3354
+ if (a === "--allowedTools" && i + 1 < rest.length && !rest[i + 1].startsWith("-")) {
3355
+ const kept = keepNonBrowser(rest[++i]);
3356
+ if (kept.length) out.push(a, kept.join(","));
3357
+ } else if (a.startsWith("--allowedTools=")) {
3358
+ const kept = keepNonBrowser(a.slice(15));
3359
+ if (kept.length) out.push(`--allowedTools=${kept.join(",")}`);
3360
+ } else out.push(a);
3361
+ }
3362
+ return out;
3363
+ }
3364
+ /** Comma-list entries (--mcp values, class MCP) without browser servers. */
3365
+ function stripBrowserNames(names) {
3366
+ return names.map((n) => keepNonBrowser(n).join(",")).filter(Boolean);
3367
+ }
3368
+
3118
3369
  //#endregion
3119
3370
  //#region src/workers/codex.ts
3120
3371
  /**
@@ -3523,7 +3774,7 @@ async function runWorker(opts) {
3523
3774
  if (gc?.length) process.stderr.write(`pai worker: archived ${gc.length} leftover worktree(s) (pai worker gc)\n`);
3524
3775
  } catch {}
3525
3776
  if (opts.className === "plan" && !opts._planner) {
3526
- const { runPlanner } = await import("./planner-Dzs9-8AS.mjs");
3777
+ const { runPlanner } = await import("./planner-EJPZnnif.mjs");
3527
3778
  return runPlanner(opts);
3528
3779
  }
3529
3780
  const parent = launchParent(opts.parent);
@@ -3544,7 +3795,9 @@ async function runWorker(opts) {
3544
3795
  className: opts.className
3545
3796
  });
3546
3797
  assertProviderRunnable(target.providerName, target.provider);
3547
- const parsed = parseRunnerArgs(opts.claudeArgs);
3798
+ const limit = resolveLimit(opts, config.defaultMaxMinutes);
3799
+ const noBrowser = opts.browser === void 0 ? config.noBrowserByDefault : !opts.browser;
3800
+ const parsed = parseRunnerArgs(noBrowser ? stripBrowserArgs(opts.claudeArgs) : opts.claudeArgs);
3548
3801
  const label = opts.label ?? shortText(parsed.prompt ?? UNLABELED, 70);
3549
3802
  const model = capabilityResolution ? capabilityResolution.model : resolveRunModel(target, opts.className, opts.modelFlag);
3550
3803
  const reportFormat = resolveReportFormat(opts.reportFormatFlag);
@@ -3593,7 +3846,9 @@ async function runWorker(opts) {
3593
3846
  worktreeFlag: opts.worktreeFlag,
3594
3847
  className: opts.className,
3595
3848
  reportFormat,
3596
- capability: capability ?? void 0
3849
+ capability: capability ?? void 0,
3850
+ limit,
3851
+ noBrowser
3597
3852
  });
3598
3853
  else rc = await executeRun({
3599
3854
  config,
@@ -3618,7 +3873,9 @@ async function runWorker(opts) {
3618
3873
  reroutes: opts._reroutes ?? 0,
3619
3874
  printCmd: opts.printCmd,
3620
3875
  reportFormat,
3621
- noReportRetry: opts.noReportRetry ?? false
3876
+ noReportRetry: opts.noReportRetry ?? false,
3877
+ limit,
3878
+ noBrowser
3622
3879
  });
3623
3880
  await returnControlsIfHeld(logDir, workerId);
3624
3881
  return rc;
@@ -3736,6 +3993,16 @@ function interactiveMcpTools(explicitMcp, callerTools, projectLaunch) {
3736
3993
  */
3737
3994
  /** Per-git-call bound for the salvage a kill signal runs before exiting. */
3738
3995
  const SIGNAL_SALVAGE_MS = 5e3;
3996
+ /** The deadline fired: record it on the status and in the ledger (the caller then stops the child). */
3997
+ function fireDeadline(logDir, status, ledger, limit) {
3998
+ status.timedOut = true;
3999
+ status.last = `deadline reached after ${limit.minutes} min`;
4000
+ saveStatus(logDir, status);
4001
+ appendLedger(ledger, "WORKER-DEADLINE", {
4002
+ id: status.id,
4003
+ minutes: limit.minutes
4004
+ });
4005
+ }
3739
4006
  async function finaliseRun(f) {
3740
4007
  const { logDir, ledger, ok, rc } = f;
3741
4008
  let { status, report } = f;
@@ -3749,8 +4016,19 @@ async function finaliseRun(f) {
3749
4016
  result: report?.result ?? "-"
3750
4017
  };
3751
4018
  }
4019
+ if (f.timedOut) {
4020
+ const reason = `deadline reached after ${f.timedOut} min`;
4021
+ status.timedOut = true;
4022
+ status.last = reason;
4023
+ report = {
4024
+ ...report ?? {},
4025
+ result: "~",
4026
+ why: reason,
4027
+ notes: [reason, report?.notes ?? f.fallbackText].filter(Boolean).join("\n")
4028
+ };
4029
+ }
3752
4030
  const orphanReason = checkOrphanedChildren(logDir, wid);
3753
- const succeeded = ok && !orphanReason;
4031
+ const succeeded = ok && !orphanReason && !f.timedOut;
3754
4032
  status.state = succeeded ? "done" : "failed";
3755
4033
  status.rc = rc;
3756
4034
  status.secs = f.secs;
@@ -3918,7 +4196,7 @@ async function announcePane(logDir, config, wid, term, noPane, ledger) {
3918
4196
  }
3919
4197
  }
3920
4198
  async function executeRun(a) {
3921
- const { config, logDir, target, model, label, parsed, noPane } = a;
4199
+ const { config, logDir, target, model, label, parsed, noPane, limit, noBrowser } = a;
3922
4200
  const headless = parsed.headless;
3923
4201
  let proxyUrl;
3924
4202
  if (target.provider.protocol === "openai") proxyUrl = `${await ensureProxyRunning(DEFAULT_PROXY_PORT, logDir)}/${target.providerName}`;
@@ -3943,19 +4221,23 @@ async function executeRun(a) {
3943
4221
  });
3944
4222
  }
3945
4223
  env.PAI_WORKER_ID = wid;
3946
- const chromeArgs = chromeGrantArgs([
3947
- ...a.mcpFlag ? [a.mcpFlag] : [],
4224
+ if (noBrowser) env.PAI_WORKER_NO_BROWSER = "1";
4225
+ const mcpFlag = noBrowser ? stripBrowserNames(a.mcpFlag ? [a.mcpFlag] : [])[0] : a.mcpFlag;
4226
+ const classMcp = noBrowser && target.classMcp ? stripBrowserNames(target.classMcp) : target.classMcp;
4227
+ if (noBrowser) parsed.mcp = stripBrowserNames(parsed.mcp);
4228
+ const chromeArgs = noBrowser ? [] : chromeGrantArgs([
4229
+ ...mcpFlag ? [mcpFlag] : [],
3948
4230
  ...parsed.mcp,
3949
- ...target.classMcp ?? [],
4231
+ ...classMcp ?? [],
3950
4232
  ...parsed.allowedTools
3951
4233
  ], parsed.rest);
3952
4234
  let mcpArgs = [];
3953
4235
  let toolsFlag = headless ? headlessToolsFlag(parsed.allowedTools) : [];
3954
4236
  if (headless && !parsed.callerMcpConfig) {
3955
4237
  const wanted = [
3956
- ...a.mcpFlag ? [a.mcpFlag] : [],
4238
+ ...mcpFlag ? [mcpFlag] : [],
3957
4239
  ...parsed.mcp,
3958
- ...target.classMcp ?? [],
4240
+ ...classMcp ?? [],
3959
4241
  ...mcpServersFromToolGrants(parsed.allowedTools)
3960
4242
  ];
3961
4243
  if (wanted.length) mcpArgs = [
@@ -3969,7 +4251,7 @@ async function executeRun(a) {
3969
4251
  ensureNoMcpConfig(logDir)
3970
4252
  ];
3971
4253
  } else if (!headless && !parsed.callerMcpConfig) {
3972
- const explicitMcp = [...a.mcpFlag ? [a.mcpFlag] : [], ...parsed.mcp];
4254
+ const explicitMcp = [...mcpFlag ? [mcpFlag] : [], ...parsed.mcp];
3973
4255
  const projectLaunch = await projectLaunchConfig(cwd);
3974
4256
  const picked = interactiveMcpTools(explicitMcp, parsed.callerTools, projectLaunch);
3975
4257
  const applied = projectLaunch && {
@@ -3996,13 +4278,19 @@ async function executeRun(a) {
3996
4278
  cmd.push(...appendSystemPromptArgs([
3997
4279
  ...headless && !parsed.callerSystemPrompt ? [workerContractPrompt(a.className, a.reportFormat)] : [],
3998
4280
  ...headless ? [worktree ? worktreeSystemPrompt(wid, worktree.branch, worktree.dir, worktree.snapshot) : inPlaceSystemPrompt()] : [],
4281
+ ...headless && limit ? [deadlinePromptLine(limit)] : [],
3999
4282
  ...parsed.callerSystemPrompts
4000
4283
  ]));
4001
4284
  cmd = ensureToolSearch(cmd);
4002
4285
  if (a.printCmd) {
4003
4286
  console.log(JSON.stringify({
4004
4287
  cmd,
4005
- cwd: worktree?.dir ?? cwd
4288
+ cwd: worktree?.dir ?? cwd,
4289
+ browser: noBrowser ? "off" : "on",
4290
+ ...limit ? {
4291
+ deadline: new Date(limit.deadlineAt).toISOString(),
4292
+ maxMinutes: limit.minutes
4293
+ } : {}
4006
4294
  }));
4007
4295
  return 0;
4008
4296
  }
@@ -4038,7 +4326,11 @@ async function executeRun(a) {
4038
4326
  worktreeBase: worktree.base,
4039
4327
  worktreeSnapshot: worktree.snapshot
4040
4328
  } : {},
4041
- ...a.specPath ? { spec: a.specPath } : {}
4329
+ ...a.specPath ? { spec: a.specPath } : {},
4330
+ ...limit ? {
4331
+ deadlineAt: limit.deadlineAt,
4332
+ maxMinutes: limit.minutes
4333
+ } : {}
4042
4334
  };
4043
4335
  saveStatus(logDir, status);
4044
4336
  a.onWorkerStart?.(wid);
@@ -4106,7 +4398,9 @@ async function executeRun(a) {
4106
4398
  });
4107
4399
  }) : null;
4108
4400
  let killed = false;
4401
+ const stopDeadline = limit ? armDeadline(proc, limit, () => fireDeadline(logDir, status, ledger, limit)) : null;
4109
4402
  const cleanup = () => {
4403
+ stopDeadline?.();
4110
4404
  if (closeTimer) clearTimeout(closeTimer);
4111
4405
  operatorServer?.close();
4112
4406
  };
@@ -4284,6 +4578,7 @@ async function executeRun(a) {
4284
4578
  report: ctx.resultReport,
4285
4579
  fallbackText: resultEvent?.result,
4286
4580
  worktree,
4581
+ timedOut: status.timedOut ? limit?.minutes : void 0,
4287
4582
  beforeHandoff: async (st, rep) => {
4288
4583
  if (headless && !a.quiet) printResult(parsed.outputFormat, resultEvent, rc, logDir, wid, rep, worktreeExtras(st));
4289
4584
  const resultText = resultEvent?.result ?? "";
@@ -4385,7 +4680,7 @@ async function executeCodexRun(a) {
4385
4680
  });
4386
4681
  }
4387
4682
  env.PAI_WORKER_ID = wid;
4388
- const prompt = (worktree ? worktreeSystemPrompt(wid, worktree.branch, worktree.dir, worktree.snapshot) + "\n\n" : "") + parsed.prompt;
4683
+ const prompt = (a.limit ? deadlinePromptLine(a.limit) + "\n\n" : "") + (worktree ? worktreeSystemPrompt(wid, worktree.branch, worktree.dir, worktree.snapshot) + "\n\n" : "") + parsed.prompt;
4389
4684
  let status = {
4390
4685
  id: wid,
4391
4686
  pid: process.pid,
@@ -4418,7 +4713,11 @@ async function executeCodexRun(a) {
4418
4713
  worktreeBase: worktree.base,
4419
4714
  worktreeSnapshot: worktree.snapshot
4420
4715
  } : {},
4421
- ...a.specPath ? { spec: a.specPath } : {}
4716
+ ...a.specPath ? { spec: a.specPath } : {},
4717
+ ...a.limit ? {
4718
+ deadlineAt: a.limit.deadlineAt,
4719
+ maxMinutes: a.limit.minutes
4720
+ } : {}
4422
4721
  };
4423
4722
  saveStatus(logDir, status);
4424
4723
  a.onWorkerStart?.(wid);
@@ -4467,6 +4766,7 @@ async function executeCodexRun(a) {
4467
4766
  ...target.provider.contextWindow ? { context_window: target.provider.contextWindow } : {}
4468
4767
  });
4469
4768
  let killed = false;
4769
+ const stopDeadline = a.limit ? armDeadline(proc, a.limit, () => fireDeadline(logDir, status, ledger, a.limit)) : null;
4470
4770
  process.once("SIGTERM", onCodexSignal("SIGTERM"));
4471
4771
  process.once("SIGINT", onCodexSignal("SIGINT"));
4472
4772
  process.once("SIGHUP", onCodexSignal("SIGHUP"));
@@ -4507,6 +4807,7 @@ async function executeCodexRun(a) {
4507
4807
  proc.on("error", reject);
4508
4808
  proc.on("close", (code) => resolve(code ?? (killed ? 143 : 1)));
4509
4809
  });
4810
+ stopDeadline?.();
4510
4811
  closeSync(eventsFd);
4511
4812
  const secs = Math.floor((Date.now() - t0) / 1e3);
4512
4813
  const finalText = fold.finalText ?? "";
@@ -4554,6 +4855,7 @@ async function executeCodexRun(a) {
4554
4855
  report,
4555
4856
  fallbackText: finalText,
4556
4857
  worktree,
4858
+ timedOut: status.timedOut ? a.limit?.minutes : void 0,
4557
4859
  beforeHandoff: (st, rep) => {
4558
4860
  if (!a.quiet) printResult(parsed.outputFormat, resultEvent, rc, logDir, wid, rep, worktreeExtras(st));
4559
4861
  }
@@ -4690,5 +4992,5 @@ function resultFromOutput(out) {
4690
4992
  }
4691
4993
 
4692
4994
  //#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
4995
+ export { projectLaunchConfig as $, worktreesDir as A, isLive as B, workerInScope as C, mergeWorker as D, git as E, alive as F, ownsPid as G, loadStatuses as H, contextLabel as I, waitForTerminalStatus as J, saveStatus as K, contextPercent as L, ledgerSummary as M, UNLABELED as N, uncommittedPaths as O, ageOf as P, pinNotices as Q, elapsedOf as R, sessionTag as S, gcWorktrees as T, newWorkerId as U, loadStatus as V, nowStamp as W, parseRunnerArgs as X, longInlinePromptHint as Y, shortText as Z, currentTabKey as _, deadlineLabel as a, resolveSession as b, handoffFromInside as c, workerDepth as d, describeMcp as et, checkPaneForWorker as f, markControlsHeld as g, clickrControlsArgv as h, usageContextTokens as i, appendLedger as j, verifyWorker as k, readInbox as l, openPaneForWorker as m, runWorker as n, parseWorkerReport as o, openFollowPane as p, setWorkerLabel as q, testProvider as r, renderReport as s, printResult as t, expandMcpNames as tt, sayToWorker as u, itermForClaudeSession as v, discardWorker as w, sessionMapPath as x, itermUuid as y, isChatPane as z };
4996
+ //# sourceMappingURL=run-Z4akoa8G.mjs.map