@tekmidian/pai 0.66.0 → 0.66.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/{chain-D4PuQ2c-.mjs → chain-CZfKIEq9.mjs} +2 -2
  2. package/dist/{chain-D4PuQ2c-.mjs.map → chain-CZfKIEq9.mjs.map} +1 -1
  3. package/dist/cli/index.mjs +5 -5
  4. package/dist/cli/program.mjs +5 -5
  5. package/dist/daemon/index.mjs +3 -3
  6. package/dist/{daemon-BEoVgVfJ.mjs → daemon-DXlaVCkw.mjs} +3 -3
  7. package/dist/{daemon-BEoVgVfJ.mjs.map → daemon-DXlaVCkw.mjs.map} +1 -1
  8. package/dist/{daemon-DPAYU9hp.mjs → daemon-DyORmxZu.mjs} +3 -3
  9. package/dist/daemon-mcp/index.mjs +6 -4
  10. package/dist/daemon-mcp/index.mjs.map +1 -1
  11. package/dist/{fallback-B5svk75f.mjs → fallback-CIiq2Raw.mjs} +6 -4
  12. package/dist/fallback-CIiq2Raw.mjs.map +1 -0
  13. package/dist/hooks/block-sleep-poll.mjs.map +2 -2
  14. package/dist/hooks/load-project-context.mjs.map +2 -2
  15. package/dist/hooks/worker-guard.mjs +5 -0
  16. package/dist/hooks/worker-guard.mjs.map +2 -2
  17. package/dist/hooks/worker-status-line.mjs +1 -0
  18. package/dist/hooks/worker-status-line.mjs.map +2 -2
  19. package/dist/{planner-EJPZnnif.mjs → planner-Dm5Acyo1.mjs} +3 -3
  20. package/dist/{planner-EJPZnnif.mjs.map → planner-Dm5Acyo1.mjs.map} +1 -1
  21. package/dist/{program-DSETzrWg.mjs → program-JJ0TwSHI.mjs} +9 -6
  22. package/dist/{program-DSETzrWg.mjs.map → program-JJ0TwSHI.mjs.map} +1 -1
  23. package/dist/{run-Z4akoa8G.mjs → run-BCpbxqA5.mjs} +168 -6
  24. package/dist/run-BCpbxqA5.mjs.map +1 -0
  25. package/dist/{session-keepalive-BDhytXEo.mjs → session-keepalive-DxjTHGcK.mjs} +2 -2
  26. package/dist/{session-keepalive-BDhytXEo.mjs.map → session-keepalive-DxjTHGcK.mjs.map} +1 -1
  27. package/package.json +1 -1
  28. package/src/hooks/ts/lib/worker-guard.test.ts +12 -0
  29. package/src/hooks/ts/lib/worker-guard.ts +9 -0
  30. package/dist/fallback-B5svk75f.mjs.map +0 -1
  31. package/dist/run-Z4akoa8G.mjs.map +0 -1
@@ -1774,6 +1774,126 @@ function itermForClaudeSession(logDir, claudeSession, now = Date.now()) {
1774
1774
  return term ? itermUuid(term) : null;
1775
1775
  }
1776
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
+
1777
1897
  //#endregion
1778
1898
  //#region src/workers/controls.ts
1779
1899
  /**
@@ -3090,6 +3210,7 @@ function fullContractPrompt(format) {
3090
3210
  "116 s). Ending your turn ends this process, so a background job gets no later wake-up here:",
3091
3211
  "run_in_background is denied. Run the long command in the FOREGROUND with a Bash timeout up to 600000 ms",
3092
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.",
3093
3214
  ...fileNotInlineBlock(),
3094
3215
  "",
3095
3216
  `Operator messages: a user turn starting with ${OPERATOR_MARK} was typed by the operator while you`,
@@ -3774,7 +3895,7 @@ async function runWorker(opts) {
3774
3895
  if (gc?.length) process.stderr.write(`pai worker: archived ${gc.length} leftover worktree(s) (pai worker gc)\n`);
3775
3896
  } catch {}
3776
3897
  if (opts.className === "plan" && !opts._planner) {
3777
- const { runPlanner } = await import("./planner-EJPZnnif.mjs");
3898
+ const { runPlanner } = await import("./planner-Dm5Acyo1.mjs");
3778
3899
  return runPlanner(opts);
3779
3900
  }
3780
3901
  const parent = launchParent(opts.parent);
@@ -4003,6 +4124,22 @@ function fireDeadline(logDir, status, ledger, limit) {
4003
4124
  minutes: limit.minutes
4004
4125
  });
4005
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
+ }
4006
4143
  async function finaliseRun(f) {
4007
4144
  const { logDir, ledger, ok, rc } = f;
4008
4145
  let { status, report } = f;
@@ -4044,12 +4181,19 @@ async function finaliseRun(f) {
4044
4181
  reason: orphanReason
4045
4182
  });
4046
4183
  }
4184
+ const end = decideEndReason({
4185
+ logDir,
4186
+ id: wid,
4187
+ timedOut: f.timedOut !== void 0
4188
+ });
4189
+ status.endReason = end.reason;
4047
4190
  saveStatus(logDir, status);
4048
4191
  appendLedger(ledger, "WORKER-END", {
4049
4192
  id: wid,
4050
4193
  ...f.end,
4051
4194
  rc,
4052
- secs: f.secs
4195
+ secs: f.secs,
4196
+ ...endFields(end)
4053
4197
  });
4054
4198
  if (f.worktree) status = recordWorktree(logDir, status, f.worktree, ok);
4055
4199
  const early = await f.beforeHandoff?.(status, report);
@@ -4297,6 +4441,7 @@ async function executeRun(a) {
4297
4441
  let status = {
4298
4442
  id: wid,
4299
4443
  pid: process.pid,
4444
+ ...parentInfo(),
4300
4445
  label,
4301
4446
  cwd,
4302
4447
  term,
@@ -4409,7 +4554,8 @@ async function executeRun(a) {
4409
4554
  status.state = "killed";
4410
4555
  status.rc = 143;
4411
4556
  status.secs = Math.floor((Date.now() - t0) / 1e3);
4412
- status.last = `killed by signal ${sig}`;
4557
+ const end = recordSignalEnd(logDir, status, sig);
4558
+ status.last = `killed by signal ${sig} (${end.reason}${end.by ? ` by ${end.by}` : ""})`;
4413
4559
  saveStatus(logDir, status);
4414
4560
  appendLedger(ledger, "WORKER-END", {
4415
4561
  id: wid,
@@ -4419,6 +4565,8 @@ async function executeRun(a) {
4419
4565
  rc: 143,
4420
4566
  secs: status.secs,
4421
4567
  killed: 1,
4568
+ ...endFields(end),
4569
+ note: end.note,
4422
4570
  label
4423
4571
  });
4424
4572
  if (worktree) {
@@ -4684,6 +4832,7 @@ async function executeCodexRun(a) {
4684
4832
  let status = {
4685
4833
  id: wid,
4686
4834
  pid: process.pid,
4835
+ ...parentInfo(),
4687
4836
  label,
4688
4837
  cwd,
4689
4838
  term,
@@ -4776,8 +4925,21 @@ async function executeCodexRun(a) {
4776
4925
  status.state = "killed";
4777
4926
  status.rc = 143;
4778
4927
  status.secs = Math.floor((Date.now() - t0) / 1e3);
4779
- status.last = `killed by signal ${sig}`;
4928
+ const end = recordSignalEnd(logDir, status, sig);
4929
+ status.last = `killed by signal ${sig} (${end.reason}${end.by ? ` by ${end.by}` : ""})`;
4780
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
+ });
4781
4943
  if (worktree) {
4782
4944
  salvageOnExit(ledger, wid, label, worktree.dir, SIGNAL_SALVAGE_MS);
4783
4945
  try {
@@ -4992,5 +5154,5 @@ function resultFromOutput(out) {
4992
5154
  }
4993
5155
 
4994
5156
  //#endregion
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
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