@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.
- package/dist/{chain-CT3SI-3z.mjs → chain-D4PuQ2c-.mjs} +4 -4
- package/dist/{chain-CT3SI-3z.mjs.map → chain-D4PuQ2c-.mjs.map} +1 -1
- package/dist/cli/index.mjs +9 -9
- package/dist/cli/program.mjs +9 -9
- package/dist/{context-handover-cache-CRSFdkNi.mjs → context-handover-cache-pkHzmL3c.mjs} +3 -3
- package/dist/{context-handover-cache-CRSFdkNi.mjs.map → context-handover-cache-pkHzmL3c.mjs.map} +1 -1
- package/dist/daemon/index.mjs +7 -7
- package/dist/{daemon-SaPnmoyg.mjs → daemon-BEoVgVfJ.mjs} +9 -9
- package/dist/{daemon-SaPnmoyg.mjs.map → daemon-BEoVgVfJ.mjs.map} +1 -1
- package/dist/{daemon-DoTVK9z6.mjs → daemon-DPAYU9hp.mjs} +7 -7
- package/dist/daemon-mcp/index.mjs +6 -6
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/{fallback-vHjFZP0Y.mjs → fallback-B5svk75f.mjs} +15 -7
- package/dist/fallback-B5svk75f.mjs.map +1 -0
- package/dist/hooks/block-sleep-poll.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs +16 -0
- package/dist/hooks/load-project-context.mjs.map +2 -2
- package/dist/hooks/route-agents-to-worker.mjs +16 -0
- package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
- package/dist/hooks/security-validator.mjs +16 -0
- package/dist/hooks/security-validator.mjs.map +2 -2
- package/dist/hooks/worker-guard.mjs +10 -0
- package/dist/hooks/worker-guard.mjs.map +3 -3
- package/dist/hooks/worker-proxy.mjs +16 -0
- package/dist/hooks/worker-proxy.mjs.map +2 -2
- package/dist/hooks/worker-status-line.mjs +16 -0
- package/dist/hooks/worker-status-line.mjs.map +2 -2
- package/dist/hooks/worker-supervision.mjs +16 -0
- package/dist/hooks/worker-supervision.mjs.map +2 -2
- package/dist/{main-resolver-VSM5ytnA.mjs → main-resolver-BKz_OuEh.mjs} +2 -2
- package/dist/{main-resolver-CKNGux1B.mjs → main-resolver-DPPwHNtn.mjs} +2 -2
- package/dist/{main-resolver-CKNGux1B.mjs.map → main-resolver-DPPwHNtn.mjs.map} +1 -1
- package/dist/{planner-Dzs9-8AS.mjs → planner-EJPZnnif.mjs} +5 -5
- package/dist/{planner-Dzs9-8AS.mjs.map → planner-EJPZnnif.mjs.map} +1 -1
- package/dist/{program-DK0mRRlf.mjs → program-DSETzrWg.mjs} +56 -16
- package/dist/{program-DK0mRRlf.mjs.map → program-DSETzrWg.mjs.map} +1 -1
- package/dist/{run-DMaCpvMW.mjs → run-Z4akoa8G.mjs} +325 -23
- package/dist/run-Z4akoa8G.mjs.map +1 -0
- package/dist/{run-env-BHWnXqle.mjs → run-env-DRcp7A8K.mjs} +16 -2
- package/dist/run-env-DRcp7A8K.mjs.map +1 -0
- package/dist/{server-DlL1QI2k.mjs → server-DgmAHyFK.mjs} +2 -2
- package/dist/{server-DlL1QI2k.mjs.map → server-DgmAHyFK.mjs.map} +1 -1
- package/dist/{session-keepalive-DVZ9-7qg.mjs → session-keepalive-BDhytXEo.mjs} +4 -4
- package/dist/{session-keepalive-DVZ9-7qg.mjs.map → session-keepalive-BDhytXEo.mjs.map} +1 -1
- package/dist/{work-queue-worker-DZiEMvBQ.mjs → work-queue-worker-4tLa5gpQ.mjs} +2 -2
- package/dist/{work-queue-worker-DZiEMvBQ.mjs.map → work-queue-worker-4tLa5gpQ.mjs.map} +1 -1
- package/dist/{work-queue-worker-D7GHuj2I.mjs → work-queue-worker-rFZvTO3p.mjs} +4 -4
- package/docs/commands/README.md +1 -0
- package/docs/commands/worker.md +30 -0
- package/package.json +1 -1
- package/src/hooks/ts/lib/worker-guard.ts +4 -0
- package/dist/fallback-vHjFZP0Y.mjs.map +0 -1
- package/dist/run-DMaCpvMW.mjs.map +0 -1
- 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 {
|
|
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-
|
|
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-
|
|
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
|
|
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
|
-
|
|
3947
|
-
|
|
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
|
-
...
|
|
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
|
-
...
|
|
4238
|
+
...mcpFlag ? [mcpFlag] : [],
|
|
3957
4239
|
...parsed.mcp,
|
|
3958
|
-
...
|
|
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 = [...
|
|
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 {
|
|
4694
|
-
//# sourceMappingURL=run-
|
|
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
|