@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.
- package/dist/{chain-CT3SI-3z.mjs → chain-CZfKIEq9.mjs} +4 -4
- package/dist/{chain-CT3SI-3z.mjs.map → chain-CZfKIEq9.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-DXlaVCkw.mjs} +9 -9
- package/dist/{daemon-SaPnmoyg.mjs.map → daemon-DXlaVCkw.mjs.map} +1 -1
- package/dist/{daemon-DoTVK9z6.mjs → daemon-DyORmxZu.mjs} +7 -7
- package/dist/daemon-mcp/index.mjs +8 -6
- package/dist/daemon-mcp/index.mjs.map +1 -1
- package/dist/{fallback-vHjFZP0Y.mjs → fallback-CIiq2Raw.mjs} +18 -8
- package/dist/fallback-CIiq2Raw.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 +15 -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 +17 -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-Dm5Acyo1.mjs} +5 -5
- package/dist/{planner-Dzs9-8AS.mjs.map → planner-Dm5Acyo1.mjs.map} +1 -1
- package/dist/{program-DK0mRRlf.mjs → program-JJ0TwSHI.mjs} +59 -16
- package/dist/program-JJ0TwSHI.mjs.map +1 -0
- package/dist/{run-DMaCpvMW.mjs → run-BCpbxqA5.mjs} +490 -26
- package/dist/run-BCpbxqA5.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-DxjTHGcK.mjs} +4 -4
- package/dist/{session-keepalive-DVZ9-7qg.mjs.map → session-keepalive-DxjTHGcK.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.test.ts +12 -0
- package/src/hooks/ts/lib/worker-guard.ts +13 -0
- package/dist/fallback-vHjFZP0Y.mjs.map +0 -1
- package/dist/program-DK0mRRlf.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",
|
|
@@ -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-
|
|
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
|
|
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
|
-
|
|
3947
|
-
|
|
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
|
-
...
|
|
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
|
-
...
|
|
4382
|
+
...mcpFlag ? [mcpFlag] : [],
|
|
3957
4383
|
...parsed.mcp,
|
|
3958
|
-
...
|
|
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 = [...
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
4694
|
-
//# sourceMappingURL=run-
|
|
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
|