@tekmidian/pai 0.66.48 → 0.66.49
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/auto-route-CMARQbQ4.mjs +8 -0
- package/dist/{auto-route-DrncbMaz.mjs → auto-route-Dpm6nZ6X.mjs} +2 -2
- package/dist/{auto-route-DrncbMaz.mjs.map → auto-route-Dpm6nZ6X.mjs.map} +1 -1
- package/dist/{chain-Bba4Y8A8.mjs → chain-xSc3-u0X.mjs} +5 -5
- package/dist/{chain-Bba4Y8A8.mjs.map → chain-xSc3-u0X.mjs.map} +1 -1
- package/dist/cli/index.mjs +14 -14
- package/dist/cli/program.mjs +14 -14
- package/dist/{config-2pR0RqHM.mjs → config-CYjjGSWq.mjs} +41 -4
- package/dist/config-CYjjGSWq.mjs.map +1 -0
- package/dist/config-IuOG1W92.mjs +4 -0
- package/dist/daemon/index.mjs +10 -10
- package/dist/{daemon-C25MKGD8.mjs → daemon-BIG7XcqE.mjs} +10 -10
- package/dist/{daemon-C6K3kikA.mjs → daemon-CiSmbylJ.mjs} +7 -7
- package/dist/{daemon-C6K3kikA.mjs.map → daemon-CiSmbylJ.mjs.map} +1 -1
- package/dist/daemon-mcp/index.mjs +8 -8
- package/dist/{fallback-DyhAZAOe.mjs → fallback-BgAukP8r.mjs} +6 -5
- package/dist/fallback-BgAukP8r.mjs.map +1 -0
- package/dist/hooks/block-sleep-poll.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs +26 -2
- package/dist/hooks/load-project-context.mjs.map +2 -2
- package/dist/hooks/route-agents-to-worker.mjs +26 -2
- package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
- package/dist/hooks/security-validator.mjs +26 -2
- package/dist/hooks/security-validator.mjs.map +2 -2
- package/dist/hooks/worker-guard.mjs +26 -2
- package/dist/hooks/worker-guard.mjs.map +2 -2
- package/dist/hooks/worker-pool-host.mjs +242 -14
- package/dist/hooks/worker-pool-host.mjs.map +4 -4
- package/dist/hooks/worker-proxy.mjs +26 -2
- package/dist/hooks/worker-proxy.mjs.map +2 -2
- package/dist/hooks/worker-status-line.mjs +26 -2
- package/dist/hooks/worker-status-line.mjs.map +2 -2
- package/dist/hooks/worker-supervision.mjs +26 -2
- package/dist/hooks/worker-supervision.mjs.map +2 -2
- package/dist/index.mjs +4 -4
- package/dist/{local-EmOjw5jq.mjs → local-Bt-7L0oB.mjs} +3 -3
- package/dist/{local-EmOjw5jq.mjs.map → local-Bt-7L0oB.mjs.map} +1 -1
- package/dist/local-oqPQEmhb.mjs +7 -0
- package/dist/{local-tiers-BIINbhkY.mjs → local-tiers-C0QSQcP1.mjs} +2 -2
- package/dist/{local-tiers-8fmjiySw.mjs → local-tiers-_ZOTbjeP.mjs} +2 -2
- package/dist/{local-tiers-8fmjiySw.mjs.map → local-tiers-_ZOTbjeP.mjs.map} +1 -1
- package/dist/{main-resolver-DBXvnYKp.mjs → main-resolver-BwjexuSA.mjs} +3 -3
- package/dist/{main-resolver-B10-lsU0.mjs → main-resolver-CVo0ab7j.mjs} +3 -3
- package/dist/{main-resolver-B10-lsU0.mjs.map → main-resolver-CVo0ab7j.mjs.map} +1 -1
- package/dist/{pai-marker-CDwQOpQ8.mjs → pai-marker-CJ6WMt05.mjs} +4 -4
- package/dist/{pai-marker-CDwQOpQ8.mjs.map → pai-marker-CJ6WMt05.mjs.map} +1 -1
- package/dist/{planner-dO-3PLjD.mjs → planner-ZKXZf9JS.mjs} +8 -8
- package/dist/{planner-dO-3PLjD.mjs.map → planner-ZKXZf9JS.mjs.map} +1 -1
- package/dist/{pool-DLhqOY3P.mjs → pool-CgsnP3La.mjs} +3 -3
- package/dist/{pool-DLhqOY3P.mjs.map → pool-CgsnP3La.mjs.map} +1 -1
- package/dist/{pool-cli-DerrWWpP.mjs → pool-cli-kGGuZCzJ.mjs} +4 -4
- package/dist/{pool-cli-DerrWWpP.mjs.map → pool-cli-kGGuZCzJ.mjs.map} +1 -1
- package/dist/{program-B7gVKc-u.mjs → program-DgcwJN6Z.mjs} +25 -463
- package/dist/program-DgcwJN6Z.mjs.map +1 -0
- package/dist/{run-a_JzcVwv.mjs → run-CsrBbtj_.mjs} +686 -21
- package/dist/run-CsrBbtj_.mjs.map +1 -0
- package/dist/{run-env-DyFZ8C1s.mjs → run-env-Cyw2iwBh.mjs} +15 -5
- package/dist/{run-env-DyFZ8C1s.mjs.map → run-env-Cyw2iwBh.mjs.map} +1 -1
- package/dist/{status-C3fBJKU_.mjs → status-B0br9zV0.mjs} +2 -2
- package/dist/status-B0br9zV0.mjs.map +1 -0
- package/dist/{supervisor-B_2JKYxe.mjs → supervisor-BZA0KICt.mjs} +7 -7
- package/dist/{supervisor-B_2JKYxe.mjs.map → supervisor-BZA0KICt.mjs.map} +1 -1
- package/dist/workers-config-BifEz8ns.mjs +4 -0
- package/dist/{worktree-lnmAOFQv.mjs → worktree-B6d-1uIn.mjs} +2 -2
- package/dist/{worktree-lnmAOFQv.mjs.map → worktree-B6d-1uIn.mjs.map} +1 -1
- package/docs/worker.md +22 -0
- package/package.json +1 -1
- package/dist/auto-route-DYuVfXwc.mjs +0 -8
- package/dist/config-2pR0RqHM.mjs.map +0 -1
- package/dist/config-CQWpEdXb.mjs +0 -4
- package/dist/fallback-DyhAZAOe.mjs.map +0 -1
- package/dist/local-B5SekWOC.mjs +0 -7
- package/dist/program-B7gVKc-u.mjs.map +0 -1
- package/dist/run-a_JzcVwv.mjs.map +0 -1
- package/dist/status-C3fBJKU_.mjs.map +0 -1
- package/dist/workers-config-nXSiW07z.mjs +0 -4
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import "./config-DSdCEcHS.mjs";
|
|
2
|
+
import { $ as workersYamlLegacyNotice, G as initWorkersYaml, J as needsWorkersYamlRelocation, K as inlineWorkersYamlKeys, Q as starterWorkersYamlText, U as WORKERS_YAML_FILENAME, W as buildWorkersYamlText, X as readWorkersYaml, Y as parseWorkersYamlDocument, Z as relocateWorkersYaml, et as workersYamlPath, nt as writeWorkersYamlText, q as migrateWorkersToYaml, tt as writeWorkersYaml } from "./config-CYjjGSWq.mjs";
|
|
3
|
+
|
|
4
|
+
export { workersYamlPath };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { G as ledgerPath, J as statusPath, d as isLive, f as loadStatus, p as loadStatuses, t as UNLABELED, v as saveStatus } from "./status-
|
|
1
|
+
import { G as ledgerPath, J as statusPath, d as isLive, f as loadStatus, p as loadStatuses, t as UNLABELED, v as saveStatus } from "./status-B0br9zV0.mjs";
|
|
2
2
|
import { appendFileSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { platform, tmpdir } from "node:os";
|
|
4
4
|
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
@@ -1293,4 +1293,4 @@ function gcWorktreesThrottled(logDir, everyMin = 60, now = Date.now()) {
|
|
|
1293
1293
|
|
|
1294
1294
|
//#endregion
|
|
1295
1295
|
export { formatPending as _, gcWorktreesThrottled as a, appendLedger as b, markResumed as c, recordWorktree as d, salvageOnExit as f, worktreesDir as g, worktreeWanted as h, gcWorktrees as i, mergeWorker as l, worktreeSystemPrompt as m, adoptTree as n, git as o, verifyWorker as p, discardWorker as r, inPlaceSystemPrompt as s, addWorktree as t, ownedTree as u, pendingGate as v, ledgerSummary as x, pendingResults as y };
|
|
1296
|
-
//# sourceMappingURL=worktree-
|
|
1296
|
+
//# sourceMappingURL=worktree-B6d-1uIn.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worktree-lnmAOFQv.mjs","names":[],"sources":["../src/workers/ledger.ts","../src/workers/pending.ts","../src/workers/worktree.ts"],"sourcesContent":["/**\n * ledger.ts — the routing ledger, one append-only log for every worker event.\n *\n * Line shapes (whitespace-aligned so `tail` reads as a table):\n *\n * 2026-09-17 12:00:00 WORKER-START id=<id> provider=<p> mode=headless model=<m> cwd=<cwd> label=<label>\n * 2026-09-17 12:01:00 WORKER-END id=<id> provider=<p> mode=headless model=<m> rc=0 secs=60 turns=3 tools=8 label=<label>\n * 2026-09-17 12:00:20 WORKER-REROUTE from=<a> to=<b> reason=quota\n * 2026-09-17 12:00:00 DENIED-ANTHROPIC-AGENT cwd=<cwd> desc=<desc>\n * 2026-09-17 12:00:00 ALLOWED-ANTHROPIC-AGENT cwd=<cwd> desc=<desc>\n *\n * `pai worker log` (and glm-log before it) counts these; keep the tags stable.\n */\n\nimport { appendFileSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nexport function ledgerStamp(d: Date = new Date()): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ` +\n `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`\n );\n}\n\nexport interface LedgerLine {\n stamp: string;\n event: string;\n fields: Record<string, string>;\n}\n\n/** Parse `2026-09-17 12:00:00 TAG key=value …` into its parts. */\nexport function parseLedgerLine(line: string): LedgerLine | null {\n const m = line.match(/^(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}) (\\S+)(?: (.*))?$/);\n if (!m) return null;\n const fields: Record<string, string> = {};\n // values are whitespace-collapsed by the writers, so spaces safely delimit\n for (const part of (m[3] ?? \"\").split(\" \")) {\n if (!part) continue;\n const eq = part.indexOf(\"=\");\n if (eq <= 0) continue;\n fields[part.slice(0, eq)] = part.slice(eq + 1);\n }\n return { stamp: m[1], event: m[2], fields };\n}\n\nexport function parseLedger(text: string): LedgerLine[] {\n const out: LedgerLine[] = [];\n for (const line of text.split(\"\\n\")) {\n if (!line.trim()) continue;\n const parsed = parseLedgerLine(line);\n if (parsed) out.push(parsed);\n }\n return out;\n}\n\n/** Append one ledger line. `kv` order is the caller's; values are flattened. */\nexport function appendLedger(\n path: string,\n event: string,\n kv: Record<string, string | number | null | undefined>,\n now: Date = new Date()\n): void {\n const parts: string[] = [];\n for (const [k, v] of Object.entries(kv)) {\n if (v === undefined || v === null) continue;\n parts.push(`${k}=${String(v).replace(/\\s+/g, \" \")}`);\n }\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tail = parts.length ? \" \" + parts.join(\" \") : \"\";\n appendFileSync(path, `${ledgerStamp(now)} ${event}${tail}\\n`, \"utf8\");\n}\n\nexport interface LedgerSummary {\n scope: string;\n started: number;\n endedOk: number;\n endedFailed: number;\n denied: number;\n allowed: number;\n reroutes: number;\n lastLines: string[];\n}\n\n/** The counts `pai worker log` prints, over today's lines or the whole file. */\nexport function ledgerSummary(\n path: string,\n scope: \"today\" | \"all\",\n lastN = 15,\n now: Date = new Date()\n): LedgerSummary | null {\n if (!existsSync(path)) return null;\n const text = readFileSync(path, \"utf8\");\n const today = ledgerStamp(now).slice(0, 10);\n const lines = text.split(\"\\n\").filter((l) => l.trim());\n const scoped =\n scope === \"all\" ? lines : lines.filter((l) => l.startsWith(today));\n const parsed = scoped.map(parseLedgerLine).filter((x): x is LedgerLine => x !== null);\n const count = (ev: string) => parsed.filter((p) => p.event === ev).length;\n const ends = parsed.filter((p) => p.event === \"WORKER-END\");\n const ok = ends.filter((p) => p.fields.rc === \"0\").length;\n return {\n scope: scope === \"all\" ? \"all time\" : `today ${today}`,\n started: count(\"WORKER-START\"),\n endedOk: ok,\n endedFailed: ends.length - ok,\n denied: count(\"DENIED-ANTHROPIC-AGENT\"),\n allowed: count(\"ALLOWED-ANTHROPIC-AGENT\"),\n reroutes: count(\"WORKER-REROUTE\"),\n lastLines: lines.slice(-lastN),\n };\n}\n","/**\n * Pending worker results: finished workers whose worktree branch still holds\n * work that never reached the repo (commits not in HEAD, or uncommitted\n * files). Surfaced by `pai worker ps/pending`, the SessionStart hook, the\n * status line and the prepublish gate so an unmerged fix cannot be forgotten.\n */\n\nimport { existsSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { isLive, loadStatuses } from \"./status.js\";\nimport { statusPath } from \"./paths.js\";\nimport { git, uncommittedPaths } from \"./worktree.js\";\n\nexport interface PendingResult {\n id: string;\n label: string;\n repo: string;\n /** ms since the worker's status file last changed (its end). */\n ageMs: number;\n age: string;\n /** commits on the branch not in the repo's HEAD (patch-id aware). */\n commits: number;\n /** uncommitted paths in the worktree, node_modules excluded. */\n dirty: number;\n}\n\n/** Stale after this long: `pai worker gc` (archive) instead of merge. */\nexport const STALE_HOURS = 24;\n\nfunction toplevel(dir: string): string | null {\n try {\n return git(dir, [\"rev-parse\", \"--show-toplevel\"]);\n } catch {\n return null;\n }\n}\n\nfunction fmtAge(ms: number): string {\n const h = Math.floor(ms / 3_600_000);\n return h >= 48 ? `${Math.floor(h / 24)}d` : h >= 1 ? `${h}h` : `${Math.floor(ms / 60_000)}m`;\n}\n\n/** Finished workers with unmerged work; `repoRoot` limits to one repo, else every repo. */\nexport function pendingResults(logDir: string, repoRoot?: string, now = Date.now()): PendingResult[] {\n const want = repoRoot ? toplevel(repoRoot) : null;\n if (repoRoot && !want) return [];\n const out: PendingResult[] = [];\n for (const st of loadStatuses(logDir)) {\n if (st.archived || st.merged || isLive(st)) continue;\n if (!st.worktreeDir || !existsSync(st.worktreeDir)) continue;\n const repo = toplevel(st.cwd);\n if (!repo || (want && repo !== want)) continue;\n let commits = 0;\n let dirty = 0;\n try {\n if (st.branch) {\n commits = git(repo, [\"cherry\", \"HEAD\", `refs/heads/${st.branch}`])\n .split(\"\\n\")\n .filter((l) => l.startsWith(\"+\")).length;\n }\n dirty = uncommittedPaths(st.worktreeDir).filter((p) => !/(^|\\/)node_modules(\\/|$)/.test(p)).length;\n } catch {\n continue; // branch or worktree unreadable: nothing verifiable to report\n }\n if (!commits && !dirty) continue;\n let ageMs = 0;\n try {\n ageMs = Math.max(0, now - statSync(statusPath(logDir, st.id)).mtimeMs);\n } catch {\n // status vanished mid-scan: age unknown, keep the entry\n }\n out.push({ id: st.id, label: st.label, repo, ageMs, age: fmtAge(ageMs), commits, dirty });\n }\n return out;\n}\n\n/** One human line per result, with the exact next command. */\nexport function formatPending(results: PendingResult[], showRepo = false): string {\n return results\n .map((r) => {\n const next = r.ageMs > STALE_HOURS * 3_600_000 ? \"pai worker gc\" : `pai worker merge ${r.id}`;\n const repo = showRepo ? ` ${r.repo}` : \"\";\n return `${r.id} ${r.age} ${r.commits} commit(s), ${r.dirty} dirty ${r.label}${repo} -> ${next}`;\n })\n .join(\"\\n\");\n}\n\n/**\n * Release gate: exit code and message for `pai worker pending --gate`.\n * PAI_ALLOW_PENDING=1 overrides.\n */\nexport function pendingGate(\n results: PendingResult[],\n env: NodeJS.ProcessEnv = process.env\n): { code: number; message: string } {\n if (!results.length || env.PAI_ALLOW_PENDING === \"1\") return { code: 0, message: \"\" };\n return {\n code: 1,\n message:\n `${results.length} unmerged worker result(s) in this repo — merge or discard before publishing:\\n` +\n `${formatPending(results)}\\n` +\n \"Override deliberately with PAI_ALLOW_PENDING=1.\",\n };\n}\n\n/** Cached count of pending results for `cwd`'s repo (statusline/hook: cheap on repeat). */\nexport function pendingCountCached(logDir: string, cwd: string, ttlMs = 60_000, now = Date.now()): number {\n const file = join(logDir, \"pending-cache.json\");\n let cache: Record<string, { at: number; n: number }> = {};\n try {\n cache = JSON.parse(readFileSync(file, \"utf8\"));\n } catch {\n // no cache yet\n }\n const hit = cache[cwd];\n if (hit && now - hit.at < ttlMs) return hit.n;\n const n = pendingResults(logDir, cwd, now).length;\n try {\n writeFileSync(file, JSON.stringify({ ...cache, [cwd]: { at: now, n } }));\n } catch {\n // read-only log dir: recompute next time\n }\n return n;\n}\n\n/** Invalidate pending cache entries for a repo and its subdirectories. */\nexport function invalidatePendingCache(logDir: string, repoRoot: string): void {\n const file = join(logDir, \"pending-cache.json\");\n let cache: Record<string, { at: number; n: number }> = {};\n try {\n cache = JSON.parse(readFileSync(file, \"utf8\"));\n } catch {\n // no cache yet\n return;\n }\n const resolved = repoRoot.endsWith(\"/\") ? repoRoot : `${repoRoot}/`;\n let changed = false;\n for (const key of Object.keys(cache)) {\n if (key === repoRoot || key.startsWith(resolved)) {\n delete cache[key];\n changed = true;\n }\n }\n if (!changed) return;\n try {\n writeFileSync(file, JSON.stringify(cache));\n } catch {\n // read-only log dir: best effort\n }\n}\n","/**\n * worktree.ts — one git worktree per writing worker.\n *\n * A run whose class edits files (implement, complex, plan) and whose cwd is a\n * git repository gets its own worktree by default: `git worktree add\n * <logDir>/worktrees/<id> -b worker/<id>` from the current HEAD. The worker\n * commits its own work on that branch — the no-commit rule applies to the\n * main branch only — and the parent (or the operator) merges the result back:\n *\n * pai worker merge <id> git merge --no-ff worker/<id> + remove worktree + delete branch\n * pai worker discard <id> remove worktree and branch, keep nothing\n *\n * `ps` marks a worker with an unmerged branch `⎇`. Draft and review run in\n * place; `--no-worktree` opts out, `--worktree` forces one on.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, lstatSync, mkdirSync, readFileSync, mkdtempSync, writeFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, resolve, sep } from \"node:path\";\nimport { tmpdir, platform } from \"node:os\";\nimport { isLive, loadStatus, loadStatuses, saveStatus, UNLABELED, type WorkerStatus } from \"./status.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport { ledgerPath, statusPath } from \"./paths.js\";\nimport { invalidatePendingCache } from \"./pending.js\";\n\nexport function worktreesDir(logDir: string): string {\n return join(logDir, \"worktrees\");\n}\n\nexport function worktreeBranch(id: string): string {\n return `worker/${id}`;\n}\n\nexport function worktreePath(logDir: string, id: string): string {\n return join(worktreesDir(logDir), id);\n}\n\n/**\n * The one answer to \"which tree does this worker own?\": its recorded\n * worktreeDir + branch, or null when it has none or was superseded by a\n * resume (the tree moved to the resuming id, see `adoptTree`). discard, merge,\n * gc and ps all ask here.\n */\nexport function ownedTree(st: WorkerStatus): { dir: string; branch: string } | null {\n if (st.resumedBy || !st.worktreeDir || !st.branch) return null;\n return { dir: st.worktreeDir, branch: st.branch };\n}\n\n/**\n * `pai worker resume`: the resuming id takes over the old id's tree. Returns\n * the tree as a WorktreeInfo for the new run, or null when the old id owns\n * none (or its directory is gone). Nothing is written until `markResumed`.\n */\nexport function adoptTree(logDir: string, oldId: string): (WorktreeInfo & { cwd: string }) | null {\n const old = loadStatus(logDir, oldId);\n const t = old && ownedTree(old);\n if (!old || !t || !existsSync(t.dir)) return null;\n return { dir: t.dir, branch: t.branch, base: old.worktreeBase ?? \"\", snapshot: !!old.worktreeSnapshot, cwd: old.cwd };\n}\n\n/** Hand the old id's tree over: it records `resumedBy` and no longer owns anything. */\nexport function markResumed(logDir: string, oldId: string, newId: string): void {\n const old = loadStatus(logDir, oldId);\n if (!old) return;\n saveStatus(logDir, {\n ...old,\n resumedBy: newId,\n worktreeDir: null,\n branch: null,\n worktreeBase: null,\n worktreeSnapshot: null,\n commits: null,\n });\n invalidatePendingCache(logDir, old.cwd);\n}\n\n/** Run git in `cwd` with an optional env override, trimmed stdout, stderr on failure. */\nfunction runGit(cwd: string, args: string[], env?: NodeJS.ProcessEnv, timeout = 30_000): string {\n try {\n return execFileSync(\"git\", [\"-C\", cwd, ...args], {\n encoding: \"utf8\",\n timeout,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n ...(env ? { env } : {}),\n }).trim();\n } catch (e) {\n const err = e as { stderr?: Buffer | string; message?: string };\n const why =\n (typeof err.stderr === \"string\" ? err.stderr : err.stderr?.toString(\"utf8\")) ||\n err.message ||\n String(e);\n throw new Error(`git ${args.join(\" \")} in ${cwd}: ${why.trim()}`);\n }\n}\n\n/** Run git in `cwd`, returning trimmed stdout; throws with stderr on failure. */\nexport function git(cwd: string, args: string[], timeoutMs?: number): string {\n return runGit(cwd, args, undefined, timeoutMs);\n}\n\n/** Is `cwd` inside a git repository (a .git dir — worktrees: a .git file)? */\nexport function isGitRepo(cwd: string): boolean {\n try {\n return git(cwd, [\"rev-parse\", \"--git-dir\"]) !== \"\";\n } catch {\n return false;\n }\n}\n\n/** Tools that can write to the checkout (Bash can, via the shell). */\nconst WRITING_TOOLS = [\"Edit\", \"Write\", \"MultiEdit\", \"NotebookEdit\", \"Bash\"];\n\n/** Can a run with these --allowedTools write? No restriction at all means yes. */\nexport function toolsCanWrite(allowedTools: string[]): boolean {\n const names = allowedTools.flatMap((e) => e.split(\",\")).map((s) => s.trim().replace(/\\(.*$/, \"\")).filter(Boolean);\n return !names.length || names.some((n) => WRITING_TOOLS.includes(n));\n}\n\n/** How the run flags decide the worktree question; undefined = decide by default. */\nexport type WorktreeFlag = boolean | undefined;\n\n/** Should this run get a worktree? Explicit flag first, then: can it write? */\nexport function worktreeWanted(flag: WorktreeFlag, opts: { cwd: string; allowedTools: string[] }): boolean {\n if (flag !== undefined) return flag;\n return toolsCanWrite(opts.allowedTools) && isGitRepo(opts.cwd);\n}\n\nexport interface WorktreeInfo {\n dir: string;\n branch: string;\n base: string;\n /** `base` is a snapshot commit of uncommitted checkout state, not HEAD itself. */\n snapshot: boolean;\n}\n\n/** Paths PAI itself rewrites (Notes/, TODO*.md at the root or in Notes/): never part of a snapshot. */\nexport function isPaiBookkeepingPath(path: string): boolean {\n return /^notes\\//i.test(path) || /^TODO[^/]*\\.md$/.test(path);\n}\n\n/**\n * Commit `cwd`'s current index + working tree (including untracked files) as\n * a floating commit on top of HEAD, without touching the real index, working\n * tree or HEAD: a temporary `GIT_INDEX_FILE` collects the snapshot, `git\n * commit-tree` writes the commit object directly (it updates no ref). The\n * caller then branches a worktree from that commit, so the worktree sees\n * exactly what was on disk, uncommitted or not.\n */\nfunction snapshotUncommitted(cwd: string, id: string, head: string, bookkeeping: string[]): string {\n const idxDir = mkdtempSync(join(tmpdir(), \"pai-worktree-idx-\"));\n const idx = join(idxDir, \"index\");\n try {\n const env = { ...process.env, GIT_INDEX_FILE: idx };\n runGit(cwd, [\"read-tree\", head], env);\n runGit(cwd, [\"add\", \"-A\"], env);\n // PAI's own bookkeeping stays at its HEAD state in the snapshot\n if (bookkeeping.length) runGit(cwd, [\"--literal-pathspecs\", \"reset\", \"-q\", head, \"--\", ...bookkeeping], env);\n const tree = runGit(cwd, [\"write-tree\"], env);\n return runGit(cwd, [\n \"commit-tree\",\n tree,\n \"-p\",\n head,\n \"-m\",\n `worker ${id}: snapshot of uncommitted checkout`,\n ]);\n } finally {\n rmSync(idxDir, { recursive: true, force: true });\n }\n}\n\n/**\n * Create the worktree and branch for `id` from `cwd`'s HEAD — or, when the\n * checkout has uncommitted changes, from a snapshot commit of that dirty\n * state (see `snapshotUncommitted`), so a worktree worker sees the same\n * files the operator sees, not just what was last committed. Throws when git\n * refuses (no commits yet, branch exists, …) — the caller decides whether to\n * degrade to an in-place run.\n */\nexport function addWorktree(logDir: string, id: string, cwd: string): WorktreeInfo {\n const dir = worktreePath(logDir, id);\n const branch = worktreeBranch(id);\n const head = git(cwd, [\"rev-parse\", \"HEAD\"]);\n const all = dirtyPaths(cwd);\n const bookkeeping = all.filter(isPaiBookkeepingPath);\n const dirty = all.length > bookkeeping.length;\n const base = dirty ? snapshotUncommitted(cwd, id, head, bookkeeping) : head;\n git(cwd, [\"worktree\", \"add\", dir, \"-b\", branch, base]);\n provisionDeps(logDir, id, cwd, dir);\n return { dir, branch, base, snapshot: dirty };\n}\n\n/**\n * Give the worktree its own dependencies so the worker never has to improvise\n * (symlinking node_modules to the main checkout, building in a /tmp copy).\n * A real node_modules directory in the main checkout is APFS-cloned\n * (`cp -c`, copy-on-write, near instant); elsewhere the lockfile installs it.\n * Never a symlink. Time-boxed and best effort: a failure is logged, the run\n * goes on.\n */\nexport function provisionDeps(logDir: string, id: string, cwd: string, wtDir: string): void {\n const note = (n: string) => appendLedger(ledgerPath(logDir), \"WORKER-NOTE\", { id, note: n });\n try {\n const root = git(cwd, [\"rev-parse\", \"--show-toplevel\"]);\n const main = join(root, \"node_modules\");\n const dest = join(wtDir, \"node_modules\");\n if (!existsSync(main) || lstatSync(main).isSymbolicLink() || existsSync(dest)) return;\n try {\n // an unignored node_modules would be committed by salvage; the trailing\n // slash makes git test a directory (the path does not exist yet), so a\n // dir-only `node_modules/` rule matches too\n git(wtDir, [\"check-ignore\", \"-q\", \"node_modules/\"]);\n } catch {\n return;\n }\n const t0 = Date.now();\n const opts = { cwd: wtDir, stdio: \"ignore\" as const, timeout: 180_000 };\n if (platform() === \"darwin\") {\n try {\n execFileSync(\"cp\", [\"-cR\", main, dest], opts);\n note(`node_modules cloned (apfs) in ${Date.now() - t0}ms`);\n return;\n } catch {\n rmSync(dest, { recursive: true, force: true }); // half-copied\n }\n }\n if (existsSync(join(wtDir, \"bun.lock\"))) execFileSync(\"bun\", [\"install\", \"--frozen-lockfile\"], opts);\n else if (existsSync(join(wtDir, \"package-lock.json\"))) execFileSync(\"npm\", [\"ci\"], opts);\n else return;\n note(`node_modules installed from lockfile in ${Date.now() - t0}ms`);\n } catch (e) {\n try {\n note(`node_modules provisioning failed: ${(e as Error).message}`);\n } catch {\n /* ledger is best effort too */\n }\n }\n}\n\n/** Commits the branch collected on top of its base. */\nexport function commitsSince(dir: string, base: string): number {\n try {\n return parseInt(git(dir, [\"rev-list\", \"--count\", `${base}..HEAD`]), 10) || 0;\n } catch {\n return 0;\n }\n}\n\n/**\n * Record the worktree result in the status file: branch and commit count on\n * success, the worktree cleaned up on failure. Returns the updated status.\n */\nexport function recordWorktree(\n logDir: string,\n status: WorkerStatus,\n info: WorktreeInfo,\n ok: boolean\n): WorkerStatus {\n const s = { ...status };\n if (ok) {\n s.branch = info.branch;\n s.commits = commitsSince(info.dir, info.base);\n s.worktreeDir = info.dir;\n s.worktreeBase = info.base;\n s.worktreeSnapshot = info.snapshot;\n } else if (safeCommitsSince(info.dir, info.base) > 0) {\n // a failed or killed run that committed (or was salvaged) keeps its work:\n // branch and worktree stay for `pai worker merge` / `discard`\n s.branch = info.branch;\n s.commits = commitsSince(info.dir, info.base);\n s.worktreeDir = info.dir;\n s.worktreeBase = info.base;\n s.worktreeSnapshot = info.snapshot;\n } else {\n // a failed run with no commits leaves nothing to merge; the branch dies with the worktree\n removeWorktree(s.cwd, info.dir, true);\n try {\n git(s.cwd, [\"branch\", \"-D\", info.branch]);\n } catch {\n // already gone or never created\n }\n s.branch = null;\n s.worktreeDir = null;\n s.worktreeBase = null;\n s.commits = null;\n s.worktreeSnapshot = null;\n }\n saveStatus(logDir, s);\n invalidatePendingCache(logDir, s.cwd);\n return s;\n}\n\nfunction safeCommitsSince(dir: string, base: string): number {\n try {\n return commitsSince(dir, base);\n } catch {\n return 0;\n }\n}\n\n/**\n * Salvage for an exit path (run finaliser, kill signal): commit whatever the\n * worker left uncommitted, log WORKER-SALVAGE, never throw. Bounded by\n * `timeoutMs` per git call. Returns the committed paths.\n */\nexport function salvageOnExit(\n ledger: string,\n id: string,\n label: string,\n wtDir: string,\n timeoutMs?: number\n): string[] {\n try {\n const { committed, skipped } = salvageUncommitted(wtDir, label, timeoutMs);\n if (committed.length) appendLedger(ledger, \"WORKER-SALVAGE\", { id, paths: committed.join(\",\"), skipped: skipped.length });\n if (skipped.length) appendLedger(ledger, \"WORKER-NOTE\", { id, note: `salvage skipped: ${skipped.join(\"; \")}` });\n return committed;\n } catch (e) {\n try {\n appendLedger(ledger, \"WORKER-NOTE\", { id, note: `salvage failed: ${(e as Error).message}` });\n } catch {\n /* best effort */\n }\n return [];\n }\n}\n\n/** Remove a worktree directory from git's books and the filesystem. */\nfunction removeWorktree(cwd: string, dir: string, force: boolean): void {\n try {\n git(cwd, [\"worktree\", \"remove\", ...(force ? [\"--force\"] : []), dir]);\n return;\n } catch {\n // fall through to the manual cleanup\n }\n if (existsSync(dir)) {\n try {\n rmSync(dir, { recursive: true, force: true });\n git(cwd, [\"worktree\", \"prune\"]);\n } catch {\n // best effort: a leftover directory is visible in the logDir\n }\n }\n}\n\n/**\n * Tracked changes (staged or not) plus untracked files — everything a\n * `git add -A` in `wtDir` would commit. The same two calls the old patch\n * carry used; name-only, not porcelain: a worktree-only change renders as\n * \" M path\" and the shared git() helper trims that leading space away.\n */\nexport function uncommittedPaths(wtDir: string, timeoutMs?: number): string[] {\n const tracked = git(wtDir, [\"diff\", \"--name-only\", \"HEAD\"], timeoutMs).split(\"\\n\").filter(Boolean);\n const untracked = git(wtDir, [\"ls-files\", \"--others\", \"--exclude-standard\"], timeoutMs)\n .split(\"\\n\")\n .filter(Boolean);\n return [...tracked, ...untracked];\n}\n\n/**\n * True when `wtDir`/`relPath` is a symlink whose target resolves outside\n * `wtDir` — e.g. a worker's `node_modules -> <main checkout>/node_modules`\n * convenience link. A `.gitignore` using `dir/` (directories only) does not\n * ignore such a link, so it turns up as untracked; staging and merging it\n * lets git replace the real directory on the other side with a symlink to\n * itself. Never a candidate for salvage.\n */\nfunction symlinkEscapesWorktree(wtDir: string, relPath: string): boolean {\n const full = join(wtDir, relPath);\n let stat;\n try {\n stat = lstatSync(full);\n } catch {\n return false; // vanished; nothing to skip\n }\n if (!stat.isSymbolicLink()) return false;\n const target = readlinkSync(full);\n const resolvedTarget = isAbsolute(target) ? target : resolve(dirname(full), target);\n const realTarget = existsSync(resolvedTarget) ? realpathSync(resolvedTarget) : resolvedTarget;\n const realWtDir = realpathSync(wtDir);\n return realTarget !== realWtDir && !realTarget.startsWith(realWtDir + sep);\n}\n\nexport interface SalvageResult {\n /** Paths committed onto the branch. */\n committed: string[];\n /** Human-readable notes for paths left on disk, uncommitted, and why. */\n skipped: string[];\n}\n\n/**\n * Commit a worktree's uncommitted changes to its branch so the merge carries\n * them. `git merge` only moves committed work, so a worker that stopped\n * without committing would lose its edits to `worktree remove` — exactly\n * what happened live on 2026-09-17. Returns the salvaged paths, empty when\n * the worktree is clean. A symlink pointing outside the worktree (see\n * `symlinkEscapesWorktree`) is never staged; it is left on disk and reported\n * as skipped instead. A failed commit throws with the worktree untouched:\n * its edits are still on disk, so nothing is lost.\n */\nexport function salvageUncommitted(wtDir: string, label: string, timeoutMs?: number): SalvageResult {\n const paths = uncommittedPaths(wtDir, timeoutMs);\n if (!paths.length) return { committed: [], skipped: [] };\n const skipped: string[] = [];\n // tracked files gone from disk but not staged for removal: the worker never\n // ran `git rm`, so the absence is not its decision (see MAX_SALVAGE_DELETES)\n const missing = new Set(git(wtDir, [\"ls-files\", \"--deleted\"], timeoutMs).split(\"\\n\").filter(Boolean));\n const toStage: string[] = [];\n for (const p of paths) {\n if (missing.has(p)) {\n skipped.push(`${p} (missing, not committed)`);\n } else if (symlinkEscapesWorktree(wtDir, p)) {\n skipped.push(`symlink ${p} -> ${readlinkSync(join(wtDir, p))} (points outside the worktree)`);\n } else if (p === \"node_modules\" || p.startsWith(\"node_modules/\")) {\n skipped.push(`${p} (node_modules is never committed)`);\n } else {\n toStage.push(p);\n }\n }\n if (!toStage.length) return { committed: [], skipped };\n try {\n // --ignore-removal: additions and modifications only. Deletions the worker\n // staged itself (git rm) are already in the index and ride along.\n const excludes = paths.filter((p) => !toStage.includes(p)).map((p) => `:(exclude,literal)${p}`);\n git(wtDir, [\"add\", \"--ignore-removal\", \"--\", \".\", ...excludes], timeoutMs);\n const deleted = git(wtDir, [\"diff\", \"--cached\", \"--diff-filter=D\", \"--name-only\"], timeoutMs).split(\"\\n\").filter(Boolean);\n if (deleted.length > MAX_SALVAGE_DELETES) {\n git(wtDir, [\"reset\", \"-q\"], timeoutMs);\n throw new Error(`salvage would delete ${deleted.length} tracked files (${deleted.slice(0, 5).join(\", \")}, ...), refused`);\n }\n git(wtDir, [\"commit\", \"-m\", `salvaged: ${label}`], timeoutMs);\n } catch (e) {\n throw new Error(\n `cannot salvage the uncommitted changes in ${wtDir} — ${(e as Error).message}; ` +\n `nothing was lost: commit them there by hand, then re-run merge`\n );\n }\n return { committed: toStage, skipped };\n}\n\n/**\n * Refuse a merge whose branch adds or changes a top-level `node_modules` of\n * any type (a worker's own commit of its symlink included): merging it would\n * replace the checkout's real directory. Both merge paths call this.\n */\nexport function assertNoNodeModules(cwd: string, from: string, branch: string, id: string): void {\n if (git(cwd, [\"diff\", \"--name-only\", from, branch, \"--\", \"node_modules\"])) {\n throw new Error(\n `worker ${id}: branch ${branch} commits node_modules, which would replace the checkout's real directory. ` +\n `Remove it on the branch (git rm -r --cached node_modules, amend), then re-run merge`\n );\n }\n}\n\n/** A salvage commit that deletes more tracked files than this is refused. */\nconst MAX_SALVAGE_DELETES = 5;\n\n/**\n * The dirty paths of a checkout, parsed from `git status --porcelain -z`:\n * NUL-separated (a path with a newline in it cannot corrupt the parse),\n * rename entries contribute both sides, and any quoting is stripped.\n */\nfunction dirtyPaths(cwd: string): string[] {\n // raw execFileSync, not the shared git(): it trims stdout, which eats the\n // leading space of a worktree-only \" M path\" record and breaks the parse\n const raw = execFileSync(\"git\", [\"-C\", cwd, \"status\", \"--porcelain\", \"-z\"], {\n encoding: \"utf8\",\n timeout: 30_000,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n const fields = raw.split(\"\\0\");\n const strip = (p: string) => (p.startsWith('\"') && p.endsWith('\"') ? p.slice(1, -1) : p);\n const out: string[] = [];\n for (let i = 0; i < fields.length; i++) {\n const f = fields[i];\n if (!f || f.length < 4 || f.charAt(2) !== \" \") continue; // not an \"XY path\" record\n out.push(strip(f.slice(3)));\n const xy = f.slice(0, 2);\n if (xy.includes(\"R\") || xy.includes(\"C\")) {\n const orig = fields[i + 1]; // rename/copy records carry the source path next\n if (orig && orig.charAt(2) !== \" \") {\n out.push(strip(orig));\n i += 1;\n }\n }\n }\n return out;\n}\n\n/**\n * Refuse the merge when the original checkout is dirty in paths the branch\n * touches: the merge would overwrite those edits or fail on them, either way\n * leaving a half-state. No merge is made, the worktree stays.\n */\nfunction assertNoDirtyOverlap(\n cwd: string,\n incoming: string[],\n id: string,\n branch: string\n): void {\n const dirty = new Set(dirtyPaths(cwd));\n const overlap = [...new Set(incoming)].filter((p) => dirty.has(p)).sort();\n if (!overlap.length) return;\n throw new Error(\n `worker ${id}: the checkout ${cwd} has uncommitted changes in paths ${branch} touches: ` +\n `${overlap.join(\", \")}. Commit or stash them in the checkout, then re-run: pai worker merge ${id}. ` +\n `No merge was made; the worktree was kept.`\n );\n}\n\n/**\n * Never remove a worktree that still holds uncommitted changes — except a\n * symlink escaping the worktree (see `symlinkEscapesWorktree`): it was\n * deliberately left uncommitted by `salvageUncommitted`, and deleting the\n * link itself touches nothing outside the worktree.\n */\nexport function assertWorktreeClean(wtDir: string, id: string): void {\n const leftover = uncommittedPaths(wtDir).filter((p) => !symlinkEscapesWorktree(wtDir, p));\n if (leftover.length) {\n throw new Error(\n `worker ${id}: the worktree ${wtDir} still holds uncommitted changes ` +\n `(${leftover.join(\", \")}) — it was NOT removed; commit or copy them by hand, then re-run merge`\n );\n }\n}\n\n/** Blob sha of `path` at `rev` in `cwd`, or null when it does not exist there. */\nfunction blobAt(cwd: string, rev: string, path: string): string | null {\n try {\n return git(cwd, [\"rev-parse\", \"--verify\", \"-q\", `${rev}:${path}`]);\n } catch {\n return null;\n }\n}\n\n/** Blob sha of `path` as it sits on disk in `cwd`, or null when it is absent. */\nfunction workingBlob(cwd: string, path: string): string | null {\n if (!existsSync(join(cwd, path))) return null;\n try {\n return git(cwd, [\"hash-object\", \"--\", path]);\n } catch {\n return null;\n }\n}\n\n/**\n * Refuse a snapshot merge when the checkout differs from the snapshot the\n * worker branched from, for any path the branch touched: the operator edited\n * it, or it was created/deleted since — applying the branch's diff on top\n * would silently discard that. Absent-in-both counts as equal.\n */\nfunction assertNoSnapshotDrift(cwd: string, snapshot: string, paths: string[], id: string): void {\n const drifted = paths\n .filter((p) => blobAt(cwd, snapshot, p) !== workingBlob(cwd, p))\n .sort();\n if (!drifted.length) return;\n throw new Error(\n `worker ${id}: the checkout ${cwd} has changed since the worktree's snapshot in paths the branch touches: ` +\n `${drifted.join(\", \")}. Resolve by hand, then re-run: pai worker merge ${id}. ` +\n `No merge was made; the worktree was kept.`\n );\n}\n\n/**\n * `pai worker merge <id>` for a snapshot-based worktree (see `addWorktree`):\n * git's own merge only moves committed history, and the snapshot commit was\n * never on any branch reachable from `cwd`'s HEAD, so there is nothing to\n * `git merge` here. Instead the worker's own changes — `git diff --binary\n * <snapshot> <branch>` — are applied straight onto the checkout's working\n * tree as uncommitted edits, exactly like the uncommitted state the worker\n * started from. Works even if `cwd`'s HEAD moved since the snapshot: the\n * diff is applied to files, not merged into a tree.\n */\nfunction mergeSnapshotWorker(\n logDir: string,\n id: string,\n st: WorkerStatus & Required<Pick<WorkerStatus, \"branch\" | \"worktreeDir\">>,\n noCommit = false\n): string {\n const snapshot = st.worktreeBase!;\n const branch = st.branch!;\n const worktreeDir = st.worktreeDir!;\n const salvage = existsSync(worktreeDir)\n ? salvageUncommitted(worktreeDir, st.label || UNLABELED)\n : { committed: [], skipped: [] };\n const incoming = parseInt(git(st.cwd, [\"rev-list\", \"--count\", `${snapshot}..${branch}`]), 10) || 0;\n if (incoming <= 0) {\n throw new Error(\n `worker ${id}: branch ${branch} has no commits beyond its snapshot to merge. ` +\n `The worktree ${worktreeDir} was NOT removed — uncommitted work there would be destroyed. ` +\n `Commit it yourself, or drop everything with: pai worker discard ${id}`\n );\n }\n const changedPaths = git(st.cwd, [\"diff\", \"--name-only\", snapshot, branch]).split(\"\\n\").filter(Boolean);\n assertNoNodeModules(st.cwd, snapshot, branch, id);\n assertNoSnapshotDrift(st.cwd, snapshot, changedPaths, id);\n const diff = execFileSync(\"git\", [\"-C\", st.cwd, \"diff\", \"--binary\", snapshot, branch], {\n encoding: \"utf8\",\n timeout: 30_000,\n maxBuffer: 64 * 1024 * 1024,\n });\n if (diff.trim()) {\n try {\n execFileSync(\"git\", [\"-C\", st.cwd, \"apply\", \"--binary\"], {\n input: diff,\n encoding: \"utf8\",\n timeout: 30_000,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n } catch (e) {\n const err = e as { stderr?: Buffer | string; message?: string };\n const why =\n (typeof err.stderr === \"string\" ? err.stderr : err.stderr?.toString(\"utf8\")) ||\n err.message ||\n String(e);\n throw new Error(\n `worker ${id}: git refused to apply ${branch}'s changes to ${st.cwd} — ${why.trim()}. ` +\n `The worktree ${worktreeDir} was kept: resolve by hand, then re-run merge`\n );\n }\n }\n if (noCommit) return noCommitNextSteps(id, branch, worktreeDir, changedPaths);\n removeWorktree(st.cwd, worktreeDir, true);\n try {\n git(st.cwd, [\"branch\", \"-D\", branch]); // never merged by git — nothing for -d to see\n } catch {\n // already gone\n }\n const s = { ...st, merged: true };\n saveStatus(logDir, s);\n const base =\n `applied worker ${id}'s changes (branch ${branch}) to ${st.cwd} as uncommitted changes: ` +\n `${changedPaths.join(\", \")} (worktree removed, branch deleted)`;\n const notes: string[] = [];\n if (salvage.committed.length) {\n notes.push(\n `salvaged ${salvage.committed.length} uncommitted change(s) onto the branch first: ${salvage.committed.join(\", \")}`\n );\n }\n if (salvage.skipped.length) {\n notes.push(`skipped ${salvage.skipped.join(\", \")}`);\n }\n return notes.length ? `${base}; ${notes.join(\"; \")}` : base;\n}\n\nfunction noCommitNextSteps(id: string, branch: string, worktreeDir: string, paths: string[]): string {\n return (\n `applied worker ${id}'s changes (branch ${branch}) to the checkout as uncommitted changes: ${paths.join(\", \")}. ` +\n `Branch and worktree ${worktreeDir} were kept. Next: review (git diff), commit, then run: pai worker gc`\n );\n}\n\n/**\n * `merge --no-commit`: apply base..branch to the checkout with a 3-way\n * `git apply`, leaving the result uncommitted. On conflict the tree is left\n * as is (markers + unmerged index entries) and the conflicted paths are listed.\n */\nfunction applyNoCommit(cwd: string, id: string, branch: string, worktreeDir: string, base: string, paths: string[]): string {\n const diff = execFileSync(\"git\", [\"-C\", cwd, \"diff\", \"--binary\", `${base}..${branch}`], {\n encoding: \"buffer\",\n timeout: 30_000,\n maxBuffer: 64 * 1024 * 1024,\n env: { ...process.env, LC_ALL: \"C\" },\n });\n try {\n execFileSync(\"git\", [\"-C\", cwd, \"apply\", \"--3way\", \"--binary\"], {\n input: diff,\n timeout: 30_000,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: { ...process.env, LC_ALL: \"C\" },\n });\n } catch (e) {\n const conflicted = git(cwd, [\"diff\", \"--name-only\", \"--diff-filter=U\"]).split(\"\\n\").filter(Boolean);\n const err = e as { stderr?: Buffer | string; message?: string };\n const why = ((typeof err.stderr === \"string\" ? err.stderr : err.stderr?.toString(\"utf8\")) || err.message || \"\").trim();\n throw new Error(\n conflicted.length\n ? `worker ${id}: applying ${branch} conflicted in ${conflicted.join(\", \")}. The tree was left for you: ` +\n `resolve those paths, review, commit; branch and worktree ${worktreeDir} were kept`\n : `worker ${id}: git refused to apply ${branch} to ${cwd} — ${why}. Nothing was changed; branch and worktree were kept`\n );\n }\n return noCommitNextSteps(id, branch, worktreeDir, paths);\n}\n\nexport type VerifyState = \"identical\" | \"differs\" | \"missing\" | \"deleted\";\nexport interface VerifyResult {\n id: string;\n ref: string;\n base: string;\n against: string;\n files: { path: string; state: VerifyState }[];\n ok: boolean;\n}\n\n/**\n * `pai worker verify <id>`: for every file the worker's branch (or its\n * refs/pai-archive/<id> archive once the branch is gone) changed since its\n * base, compare bytes with the working tree or `--against <ref>`.\n * identical = the target already holds the worker's version (a worker delete\n * counts as identical when the target lacks the file too); differs = other\n * bytes; missing = the worker's file is absent in the target; deleted = the\n * worker deleted it but the target still has it. ok = every file identical.\n */\nexport function verifyWorker(logDir: string, id: string, opts: { against?: string } = {}): VerifyResult {\n const st = loadStatus(logDir, id);\n if (!st) throw new Error(`no worker named \"${id}\"`);\n const env = { ...process.env, LC_ALL: \"C\" };\n const bytes = (args: string[]): Buffer | null => {\n try {\n return execFileSync(\"git\", [\"-C\", st.cwd, ...args], {\n env,\n timeout: 30_000,\n maxBuffer: 256 * 1024 * 1024,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n } catch {\n return null;\n }\n };\n const has = (ref: string): boolean => bytes([\"rev-parse\", \"--verify\", \"-q\", `${ref}^{commit}`]) !== null;\n const branch = st.branch ?? worktreeBranch(id);\n const ref = [`refs/heads/${branch}`, `refs/pai-archive/${id}`].find(has);\n if (!ref) throw new Error(`worker ${id}: neither ${branch} nor refs/pai-archive/${id} exists in ${st.cwd}`);\n const base = st.worktreeSnapshot && st.worktreeBase ? st.worktreeBase : git(st.cwd, [\"merge-base\", \"HEAD\", ref]);\n const list = (bytes([\"diff\", \"--name-status\", \"--no-renames\", \"-z\", `${base}..${ref}`]) ?? Buffer.alloc(0))\n .toString(\"utf8\")\n .split(\"\\0\")\n .filter(Boolean);\n const files: VerifyResult[\"files\"] = [];\n for (let i = 0; i + 1 < list.length; i += 2) {\n const path = list[i + 1];\n const deleted = list[i] === \"D\";\n const target = opts.against\n ? bytes([\"show\", `${opts.against}:${path}`])\n : existsSync(join(st.cwd, path))\n ? readFileSync(join(st.cwd, path))\n : null;\n let state: VerifyState;\n if (deleted) state = target ? \"deleted\" : \"identical\";\n else {\n const mine = bytes([\"show\", `${ref}:${path}`]);\n state = !target ? \"missing\" : mine && mine.equals(target) ? \"identical\" : \"differs\";\n }\n files.push({ path, state });\n }\n return { id, ref, base, against: opts.against ?? \"working tree\", files, ok: files.every((f) => f.state === \"identical\") };\n}\n\n/**\n * `pai worker merge <id>`: salvage whatever the worker left uncommitted onto\n * its branch, refuse when the original checkout is dirty in paths the branch\n * touches, merge with --no-ff (the merge commit names the worker), then\n * remove the worktree and delete the branch; the status gains `merged: true`.\n * A branch with nothing to merge is refused loudly — after salvage that\n * genuinely means it holds nothing, and reporting success there would\n * destroy the worktree for no gain.\n */\nexport function mergeWorker(logDir: string, id: string, opts: { noCommit?: boolean } = {}): string {\n const st = mustHaveBranch(logDir, id);\n if (st.merged) return `worker ${id}: branch ${st.branch} already merged`;\n if (st.worktreeSnapshot) return mergeSnapshotWorker(logDir, id, st, opts.noCommit);\n // salvage first: only a commit can carry uncommitted work through the merge\n const salvage = existsSync(st.worktreeDir!)\n ? salvageUncommitted(st.worktreeDir!, st.label || UNLABELED)\n : { committed: [], skipped: [] };\n const incoming = parseInt(git(st.cwd, [\"rev-list\", \"--count\", `HEAD..${st.branch}`]), 10) || 0;\n if (incoming <= 0) {\n throw new Error(\n `worker ${id}: branch ${st.branch} has no commits to merge. ` +\n `The worktree ${st.worktreeDir} was NOT removed — uncommitted work there would be destroyed. ` +\n `Commit it yourself, or drop everything with: pai worker discard ${id}`\n );\n }\n const mergeBase = git(st.cwd, [\"merge-base\", \"HEAD\", st.branch!]);\n const incomingPaths = git(st.cwd, [\"diff\", \"--name-only\", mergeBase, st.branch!])\n .split(\"\\n\")\n .filter(Boolean);\n assertNoNodeModules(st.cwd, mergeBase, st.branch!, id);\n assertNoDirtyOverlap(st.cwd, incomingPaths, id, st.branch!);\n if (opts.noCommit) return applyNoCommit(st.cwd, id, st.branch!, st.worktreeDir!, mergeBase, incomingPaths);\n try {\n git(st.cwd, [\"merge\", \"--no-ff\", st.branch!, \"-m\", `merge worker ${id} (${st.label})`]);\n } catch (e) {\n throw new Error(\n `worker ${id}: git refused the merge of ${st.branch} — ${(e as Error).message}. ` +\n `The worktree ${st.worktreeDir} was kept: resolve the conflict, then re-run merge`\n );\n }\n if (existsSync(st.worktreeDir!)) assertWorktreeClean(st.worktreeDir!, id);\n // force: assertWorktreeClean already confirmed nothing but a skipped\n // outside-pointing symlink (if anything) remains uncommitted\n removeWorktree(st.cwd, st.worktreeDir!, true);\n let branchGone = true;\n try {\n git(st.cwd, [\"branch\", \"-d\", st.branch!]);\n } catch {\n branchGone = false; // -d refuses anything not fully merged; keep the branch, say so\n }\n const s = { ...st, merged: true };\n saveStatus(logDir, s);\n invalidatePendingCache(logDir, st.cwd);\n const base = `merged ${st.branch} into ${st.cwd} (worktree removed${branchGone ? \", branch deleted\" : \"; branch kept: git refused -d\"})`;\n const notes: string[] = [];\n if (salvage.committed.length) {\n notes.push(`salvaged ${salvage.committed.length} uncommitted change(s): ${salvage.committed.join(\", \")}`);\n }\n if (salvage.skipped.length) {\n notes.push(`skipped ${salvage.skipped.join(\", \")}`);\n }\n return notes.length ? `${base}; ${notes.join(\"; \")}` : base;\n}\n\n/** `pai worker discard <id>`: drop worktree and branch, keep nothing. */\nexport function discardWorker(logDir: string, id: string): string {\n const st = mustHaveBranch(logDir, id);\n removeWorktree(st.cwd, st.worktreeDir!, true);\n let branchGone = false;\n try {\n git(st.cwd, [\"branch\", \"-D\", st.branch!]);\n branchGone = true;\n } catch {\n branchGone = false;\n }\n const s = {\n ...st,\n branch: null,\n worktreeDir: null,\n worktreeBase: null,\n worktreeSnapshot: null,\n commits: null,\n merged: false,\n };\n saveStatus(logDir, s);\n invalidatePendingCache(logDir, st.cwd);\n return `discarded worker ${id}: worktree removed${branchGone ? `, branch ${st.branch} deleted` : \"\"}`;\n}\n\nfunction mustHaveBranch(logDir: string, id: string): WorkerStatus & Required<Pick<WorkerStatus, \"branch\" | \"worktreeDir\">> {\n const st = loadStatus(logDir, id);\n if (!st) throw new Error(`no worker named \"${id}\"`);\n if (st.resumedBy) throw new Error(`worker ${id} is superseded by ${st.resumedBy}, use that id`);\n if (!ownedTree(st)) {\n throw new Error(\n `worker ${id} has no worktree branch to ${st.branch ? \"clean up\" : \"merge\"} — ` +\n `it ran in place or its branch was already handled`\n );\n }\n return st as WorkerStatus & Required<Pick<WorkerStatus, \"branch\" | \"worktreeDir\">>;\n}\n\n/**\n * The paragraph a worktree run's system prompt gains: it may commit on its\n * own branch (the no-commit rule holds for the main branch only), it must not\n * merge or push itself, and the parent or operator merges.\n */\nexport function worktreeSystemPrompt(id: string, branch: string, dir: string, snapshot = false): string {\n return [\n \"You are running in your own git worktree:\",\n ` ${dir} on branch ${branch} (worker id ${id}).`,\n \"Commit your work on that branch as you go (git add / git commit) — committing here is expected;\",\n \"the no-commit rule applies to the main branch only, and this is not it.\",\n \"Do not merge, rebase or push; the operator merges your branch back with `pai worker merge`.\",\n \"Use ONLY relative paths inside the worktree, never absolute worktree paths — absolute paths break after merge and leak machine layout.\",\n ...(snapshot\n ? [\n \"This worktree was branched from a snapshot that includes files the parent checkout had uncommitted at spawn time — not just its last commit.\",\n ]\n : []),\n ].join(\"\\n\");\n}\n\n/**\n * For headless runs WITHOUT a worktree: the worker shares the operator's\n * checkout with other in-flight workers, so a hook blocks git stash (except\n * list/show), reset, clean, checkout/restore of paths, switch, rebase and\n * merge — take baselines from measurements before editing, not from stash.\n */\nexport function inPlaceSystemPrompt(): string {\n return [\n \"You are running in the operator's shared git checkout, alongside other workers editing it at the same time.\",\n \"A hook blocks git stash (except list/show), reset, clean, checkout/restore of files, switch, rebase and merge — take baselines from measurements taken before you edit, not from stashing.\",\n \"Read-only git (status, diff, log, show, stash list) is fine.\",\n ].join(\"\\n\");\n}\n\nexport interface GcEntry {\n id: string;\n /** archive ref, or the reason the worktree was skipped. */\n archive?: string;\n skipped?: string;\n}\n\nconst GC_STAMP = \"gc-last\";\n\n/**\n * Pid of a live process whose cwd is `dir` or below (shell drivers parked in a\n * worker tree), else 0. One `lsof -d cwd` listing, no tree walk. If lsof is\n * missing or fails, nothing is reported busy.\n */\nexport function busyPid(dir: string): number {\n let real = dir;\n try {\n real = realpathSync(dir);\n } catch {\n return 0;\n }\n let raw = \"\";\n try {\n raw = execFileSync(\"lsof\", [\"-d\", \"cwd\", \"-Fpn\"], { encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"ignore\"], maxBuffer: 64 << 20 });\n } catch (e) {\n // lsof exits 1 when some entries are unreadable but still prints the rest\n raw = String((e as { stdout?: string }).stdout ?? \"\");\n }\n let pid = 0;\n for (const line of raw.split(\"\\n\")) {\n if (line[0] === \"p\") pid = Number(line.slice(1));\n else if (line[0] === \"n\" && pid !== process.pid) {\n const name = line.slice(1);\n if (name === real || name.startsWith(real + sep)) return pid;\n }\n }\n return 0;\n}\n\n/**\n * Archive worktrees of finished workers older than `olderThanHours` and\n * status-less orphan directories: uncommitted state is salvaged onto the\n * branch, the branch is x, the worktree is\n * removed. Nothing is deleted without a salvage first; a failed salvage skips\n * that worktree. Running workers are never touched. `dryRun` reports only.\n */\nexport function gcWorktrees(\n logDir: string,\n olderThanHours = 24,\n dryRun = false,\n now = Date.now()\n): GcEntry[] {\n const cutoff = now - olderThanHours * 3_600_000;\n const out: GcEntry[] = [];\n const root = worktreesDir(logDir);\n const statuses = loadStatuses(logDir);\n const owned = new Set(statuses.map((s) => ownedTree(s)?.dir).filter((d): d is string => !!d).map((d) => resolve(d)));\n const archive = (id: string, dir: string, branch: string, st: WorkerStatus | null): void => {\n const target = `refs/pai-archive/${id}`;\n const pid = busyPid(dir);\n if (pid) {\n if (!dryRun) appendLedger(ledgerPath(logDir), \"WORKER-GC-SKIP\", { id, reason: \"busy\", pid: String(pid) });\n return void out.push({ id, skipped: `busy: pid ${pid} has its cwd inside` });\n }\n if (dryRun) return void out.push({ id, archive: target });\n try {\n // The shared object store and refs live in the main repo; the recorded cwd\n // may be another worker's worktree that gc already removed.\n let repo: string;\n try {\n const raw = git(dir, [\"rev-parse\", \"--git-common-dir\"]);\n repo = isAbsolute(raw) ? raw : resolve(dir, raw);\n } catch {\n throw new Error(\"not a git worktree\");\n }\n const label = `archive: leftover state of worker ${id}`;\n if (uncommittedPaths(dir).length) {\n // salvageUncommitted labels \"salvaged: <label>\"; keep the requested subject\n const res = salvageUncommitted(dir, label);\n if (res.committed.length) git(dir, [\"commit\", \"--amend\", \"-q\", \"-m\", label]);\n if (res.skipped.length && uncommittedPaths(dir).some((p) => !symlinkEscapesWorktree(dir, p))) {\n throw new Error(\"uncommitted paths remain\");\n }\n }\n const src = branch;\n let hasBranch = true;\n try {\n git(repo, [\"rev-parse\", \"--verify\", \"-q\", `refs/heads/${src}`]);\n } catch {\n hasBranch = false;\n }\n const tip = git(dir, [\"rev-parse\", \"HEAD\"]);\n // never drop a branch whose tip the archive ref would not keep reachable\n if (hasBranch) git(repo, [\"merge-base\", \"--is-ancestor\", `refs/heads/${src}`, tip]);\n git(repo, [\"update-ref\", target, tip]);\n git(repo, [\"worktree\", \"remove\", \"--force\", dir]);\n if (hasBranch) git(repo, [\"branch\", \"-D\", src]);\n if (st) saveStatus(logDir, { ...st, branch: null, worktreeDir: null, archived: target });\n appendLedger(ledgerPath(logDir), \"WORKER-GC\", { id, archive: target });\n out.push({ id, archive: target });\n } catch (e) {\n out.push({ id, skipped: (e as Error).message.split(\"\\n\")[0] });\n }\n };\n\n for (const st of statuses) {\n const tree = ownedTree(st);\n if (!tree || !existsSync(tree.dir) || isLive(st)) continue;\n let end = 0;\n try {\n end = statSync(statusPath(logDir, st.id)).mtimeMs;\n } catch {\n continue;\n }\n if (end > cutoff) continue;\n archive(st.id, tree.dir, tree.branch, st);\n }\n\n if (existsSync(root)) {\n for (const ent of readdirSync(root, { withFileTypes: true })) {\n const dir = join(root, ent.name);\n // owned by a status (the last id of a resume chain) is that status's business\n if (!ent.isDirectory() || owned.has(resolve(dir))) continue;\n try {\n if (statSync(dir).mtimeMs > cutoff) continue;\n } catch {\n continue;\n }\n archive(ent.name, dir, worktreeBranch(ent.name), null);\n }\n }\n if (!dryRun) {\n for (const repo of new Set(statuses.map((s) => s.cwd))) {\n try {\n git(repo, [\"worktree\", \"prune\"]);\n } catch {\n // repo gone\n }\n invalidatePendingCache(logDir, repo);\n }\n }\n return out;\n}\n\n/** Run `gcWorktrees` at most once per `everyMin` minutes (stamp file in the logDir). */\nexport function gcWorktreesThrottled(logDir: string, everyMin = 60, now = Date.now()): GcEntry[] | null {\n const stamp = join(logDir, GC_STAMP);\n try {\n if (now - statSync(stamp).mtimeMs < everyMin * 60_000) return null;\n } catch {\n // never ran\n }\n mkdirSync(logDir, { recursive: true });\n writeFileSync(stamp, String(now), \"utf8\");\n return gcWorktrees(logDir, 24, false, now);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"worktree-B6d-1uIn.mjs","names":[],"sources":["../src/workers/ledger.ts","../src/workers/pending.ts","../src/workers/worktree.ts"],"sourcesContent":["/**\n * ledger.ts — the routing ledger, one append-only log for every worker event.\n *\n * Line shapes (whitespace-aligned so `tail` reads as a table):\n *\n * 2026-09-17 12:00:00 WORKER-START id=<id> provider=<p> mode=headless model=<m> cwd=<cwd> label=<label>\n * 2026-09-17 12:01:00 WORKER-END id=<id> provider=<p> mode=headless model=<m> rc=0 secs=60 turns=3 tools=8 label=<label>\n * 2026-09-17 12:00:20 WORKER-REROUTE from=<a> to=<b> reason=quota\n * 2026-09-17 12:00:00 DENIED-ANTHROPIC-AGENT cwd=<cwd> desc=<desc>\n * 2026-09-17 12:00:00 ALLOWED-ANTHROPIC-AGENT cwd=<cwd> desc=<desc>\n *\n * `pai worker log` (and glm-log before it) counts these; keep the tags stable.\n */\n\nimport { appendFileSync, existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { dirname } from \"node:path\";\n\nexport function ledgerStamp(d: Date = new Date()): string {\n const p = (n: number) => String(n).padStart(2, \"0\");\n return (\n `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ` +\n `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`\n );\n}\n\nexport interface LedgerLine {\n stamp: string;\n event: string;\n fields: Record<string, string>;\n}\n\n/** Parse `2026-09-17 12:00:00 TAG key=value …` into its parts. */\nexport function parseLedgerLine(line: string): LedgerLine | null {\n const m = line.match(/^(\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}) (\\S+)(?: (.*))?$/);\n if (!m) return null;\n const fields: Record<string, string> = {};\n // values are whitespace-collapsed by the writers, so spaces safely delimit\n for (const part of (m[3] ?? \"\").split(\" \")) {\n if (!part) continue;\n const eq = part.indexOf(\"=\");\n if (eq <= 0) continue;\n fields[part.slice(0, eq)] = part.slice(eq + 1);\n }\n return { stamp: m[1], event: m[2], fields };\n}\n\nexport function parseLedger(text: string): LedgerLine[] {\n const out: LedgerLine[] = [];\n for (const line of text.split(\"\\n\")) {\n if (!line.trim()) continue;\n const parsed = parseLedgerLine(line);\n if (parsed) out.push(parsed);\n }\n return out;\n}\n\n/** Append one ledger line. `kv` order is the caller's; values are flattened. */\nexport function appendLedger(\n path: string,\n event: string,\n kv: Record<string, string | number | null | undefined>,\n now: Date = new Date()\n): void {\n const parts: string[] = [];\n for (const [k, v] of Object.entries(kv)) {\n if (v === undefined || v === null) continue;\n parts.push(`${k}=${String(v).replace(/\\s+/g, \" \")}`);\n }\n const dir = dirname(path);\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true });\n const tail = parts.length ? \" \" + parts.join(\" \") : \"\";\n appendFileSync(path, `${ledgerStamp(now)} ${event}${tail}\\n`, \"utf8\");\n}\n\nexport interface LedgerSummary {\n scope: string;\n started: number;\n endedOk: number;\n endedFailed: number;\n denied: number;\n allowed: number;\n reroutes: number;\n lastLines: string[];\n}\n\n/** The counts `pai worker log` prints, over today's lines or the whole file. */\nexport function ledgerSummary(\n path: string,\n scope: \"today\" | \"all\",\n lastN = 15,\n now: Date = new Date()\n): LedgerSummary | null {\n if (!existsSync(path)) return null;\n const text = readFileSync(path, \"utf8\");\n const today = ledgerStamp(now).slice(0, 10);\n const lines = text.split(\"\\n\").filter((l) => l.trim());\n const scoped =\n scope === \"all\" ? lines : lines.filter((l) => l.startsWith(today));\n const parsed = scoped.map(parseLedgerLine).filter((x): x is LedgerLine => x !== null);\n const count = (ev: string) => parsed.filter((p) => p.event === ev).length;\n const ends = parsed.filter((p) => p.event === \"WORKER-END\");\n const ok = ends.filter((p) => p.fields.rc === \"0\").length;\n return {\n scope: scope === \"all\" ? \"all time\" : `today ${today}`,\n started: count(\"WORKER-START\"),\n endedOk: ok,\n endedFailed: ends.length - ok,\n denied: count(\"DENIED-ANTHROPIC-AGENT\"),\n allowed: count(\"ALLOWED-ANTHROPIC-AGENT\"),\n reroutes: count(\"WORKER-REROUTE\"),\n lastLines: lines.slice(-lastN),\n };\n}\n","/**\n * Pending worker results: finished workers whose worktree branch still holds\n * work that never reached the repo (commits not in HEAD, or uncommitted\n * files). Surfaced by `pai worker ps/pending`, the SessionStart hook, the\n * status line and the prepublish gate so an unmerged fix cannot be forgotten.\n */\n\nimport { existsSync, readFileSync, statSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { isLive, loadStatuses } from \"./status.js\";\nimport { statusPath } from \"./paths.js\";\nimport { git, uncommittedPaths } from \"./worktree.js\";\n\nexport interface PendingResult {\n id: string;\n label: string;\n repo: string;\n /** ms since the worker's status file last changed (its end). */\n ageMs: number;\n age: string;\n /** commits on the branch not in the repo's HEAD (patch-id aware). */\n commits: number;\n /** uncommitted paths in the worktree, node_modules excluded. */\n dirty: number;\n}\n\n/** Stale after this long: `pai worker gc` (archive) instead of merge. */\nexport const STALE_HOURS = 24;\n\nfunction toplevel(dir: string): string | null {\n try {\n return git(dir, [\"rev-parse\", \"--show-toplevel\"]);\n } catch {\n return null;\n }\n}\n\nfunction fmtAge(ms: number): string {\n const h = Math.floor(ms / 3_600_000);\n return h >= 48 ? `${Math.floor(h / 24)}d` : h >= 1 ? `${h}h` : `${Math.floor(ms / 60_000)}m`;\n}\n\n/** Finished workers with unmerged work; `repoRoot` limits to one repo, else every repo. */\nexport function pendingResults(logDir: string, repoRoot?: string, now = Date.now()): PendingResult[] {\n const want = repoRoot ? toplevel(repoRoot) : null;\n if (repoRoot && !want) return [];\n const out: PendingResult[] = [];\n for (const st of loadStatuses(logDir)) {\n if (st.archived || st.merged || isLive(st)) continue;\n if (!st.worktreeDir || !existsSync(st.worktreeDir)) continue;\n const repo = toplevel(st.cwd);\n if (!repo || (want && repo !== want)) continue;\n let commits = 0;\n let dirty = 0;\n try {\n if (st.branch) {\n commits = git(repo, [\"cherry\", \"HEAD\", `refs/heads/${st.branch}`])\n .split(\"\\n\")\n .filter((l) => l.startsWith(\"+\")).length;\n }\n dirty = uncommittedPaths(st.worktreeDir).filter((p) => !/(^|\\/)node_modules(\\/|$)/.test(p)).length;\n } catch {\n continue; // branch or worktree unreadable: nothing verifiable to report\n }\n if (!commits && !dirty) continue;\n let ageMs = 0;\n try {\n ageMs = Math.max(0, now - statSync(statusPath(logDir, st.id)).mtimeMs);\n } catch {\n // status vanished mid-scan: age unknown, keep the entry\n }\n out.push({ id: st.id, label: st.label, repo, ageMs, age: fmtAge(ageMs), commits, dirty });\n }\n return out;\n}\n\n/** One human line per result, with the exact next command. */\nexport function formatPending(results: PendingResult[], showRepo = false): string {\n return results\n .map((r) => {\n const next = r.ageMs > STALE_HOURS * 3_600_000 ? \"pai worker gc\" : `pai worker merge ${r.id}`;\n const repo = showRepo ? ` ${r.repo}` : \"\";\n return `${r.id} ${r.age} ${r.commits} commit(s), ${r.dirty} dirty ${r.label}${repo} -> ${next}`;\n })\n .join(\"\\n\");\n}\n\n/**\n * Release gate: exit code and message for `pai worker pending --gate`.\n * PAI_ALLOW_PENDING=1 overrides.\n */\nexport function pendingGate(\n results: PendingResult[],\n env: NodeJS.ProcessEnv = process.env\n): { code: number; message: string } {\n if (!results.length || env.PAI_ALLOW_PENDING === \"1\") return { code: 0, message: \"\" };\n return {\n code: 1,\n message:\n `${results.length} unmerged worker result(s) in this repo — merge or discard before publishing:\\n` +\n `${formatPending(results)}\\n` +\n \"Override deliberately with PAI_ALLOW_PENDING=1.\",\n };\n}\n\n/** Cached count of pending results for `cwd`'s repo (statusline/hook: cheap on repeat). */\nexport function pendingCountCached(logDir: string, cwd: string, ttlMs = 60_000, now = Date.now()): number {\n const file = join(logDir, \"pending-cache.json\");\n let cache: Record<string, { at: number; n: number }> = {};\n try {\n cache = JSON.parse(readFileSync(file, \"utf8\"));\n } catch {\n // no cache yet\n }\n const hit = cache[cwd];\n if (hit && now - hit.at < ttlMs) return hit.n;\n const n = pendingResults(logDir, cwd, now).length;\n try {\n writeFileSync(file, JSON.stringify({ ...cache, [cwd]: { at: now, n } }));\n } catch {\n // read-only log dir: recompute next time\n }\n return n;\n}\n\n/** Invalidate pending cache entries for a repo and its subdirectories. */\nexport function invalidatePendingCache(logDir: string, repoRoot: string): void {\n const file = join(logDir, \"pending-cache.json\");\n let cache: Record<string, { at: number; n: number }> = {};\n try {\n cache = JSON.parse(readFileSync(file, \"utf8\"));\n } catch {\n // no cache yet\n return;\n }\n const resolved = repoRoot.endsWith(\"/\") ? repoRoot : `${repoRoot}/`;\n let changed = false;\n for (const key of Object.keys(cache)) {\n if (key === repoRoot || key.startsWith(resolved)) {\n delete cache[key];\n changed = true;\n }\n }\n if (!changed) return;\n try {\n writeFileSync(file, JSON.stringify(cache));\n } catch {\n // read-only log dir: best effort\n }\n}\n","/**\n * worktree.ts — one git worktree per writing worker.\n *\n * A run whose class edits files (implement, complex, plan) and whose cwd is a\n * git repository gets its own worktree by default: `git worktree add\n * <logDir>/worktrees/<id> -b worker/<id>` from the current HEAD. The worker\n * commits its own work on that branch — the no-commit rule applies to the\n * main branch only — and the parent (or the operator) merges the result back:\n *\n * pai worker merge <id> git merge --no-ff worker/<id> + remove worktree + delete branch\n * pai worker discard <id> remove worktree and branch, keep nothing\n *\n * `ps` marks a worker with an unmerged branch `⎇`. Draft and review run in\n * place; `--no-worktree` opts out, `--worktree` forces one on.\n */\n\nimport { execFileSync } from \"node:child_process\";\nimport { existsSync, lstatSync, mkdirSync, readFileSync, mkdtempSync, writeFileSync, readdirSync, readlinkSync, realpathSync, rmSync, statSync } from \"node:fs\";\nimport { dirname, isAbsolute, join, resolve, sep } from \"node:path\";\nimport { tmpdir, platform } from \"node:os\";\nimport { isLive, loadStatus, loadStatuses, saveStatus, UNLABELED, type WorkerStatus } from \"./status.js\";\nimport { appendLedger } from \"./ledger.js\";\nimport { ledgerPath, statusPath } from \"./paths.js\";\nimport { invalidatePendingCache } from \"./pending.js\";\n\nexport function worktreesDir(logDir: string): string {\n return join(logDir, \"worktrees\");\n}\n\nexport function worktreeBranch(id: string): string {\n return `worker/${id}`;\n}\n\nexport function worktreePath(logDir: string, id: string): string {\n return join(worktreesDir(logDir), id);\n}\n\n/**\n * The one answer to \"which tree does this worker own?\": its recorded\n * worktreeDir + branch, or null when it has none or was superseded by a\n * resume (the tree moved to the resuming id, see `adoptTree`). discard, merge,\n * gc and ps all ask here.\n */\nexport function ownedTree(st: WorkerStatus): { dir: string; branch: string } | null {\n if (st.resumedBy || !st.worktreeDir || !st.branch) return null;\n return { dir: st.worktreeDir, branch: st.branch };\n}\n\n/**\n * `pai worker resume`: the resuming id takes over the old id's tree. Returns\n * the tree as a WorktreeInfo for the new run, or null when the old id owns\n * none (or its directory is gone). Nothing is written until `markResumed`.\n */\nexport function adoptTree(logDir: string, oldId: string): (WorktreeInfo & { cwd: string }) | null {\n const old = loadStatus(logDir, oldId);\n const t = old && ownedTree(old);\n if (!old || !t || !existsSync(t.dir)) return null;\n return { dir: t.dir, branch: t.branch, base: old.worktreeBase ?? \"\", snapshot: !!old.worktreeSnapshot, cwd: old.cwd };\n}\n\n/** Hand the old id's tree over: it records `resumedBy` and no longer owns anything. */\nexport function markResumed(logDir: string, oldId: string, newId: string): void {\n const old = loadStatus(logDir, oldId);\n if (!old) return;\n saveStatus(logDir, {\n ...old,\n resumedBy: newId,\n worktreeDir: null,\n branch: null,\n worktreeBase: null,\n worktreeSnapshot: null,\n commits: null,\n });\n invalidatePendingCache(logDir, old.cwd);\n}\n\n/** Run git in `cwd` with an optional env override, trimmed stdout, stderr on failure. */\nfunction runGit(cwd: string, args: string[], env?: NodeJS.ProcessEnv, timeout = 30_000): string {\n try {\n return execFileSync(\"git\", [\"-C\", cwd, ...args], {\n encoding: \"utf8\",\n timeout,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n ...(env ? { env } : {}),\n }).trim();\n } catch (e) {\n const err = e as { stderr?: Buffer | string; message?: string };\n const why =\n (typeof err.stderr === \"string\" ? err.stderr : err.stderr?.toString(\"utf8\")) ||\n err.message ||\n String(e);\n throw new Error(`git ${args.join(\" \")} in ${cwd}: ${why.trim()}`);\n }\n}\n\n/** Run git in `cwd`, returning trimmed stdout; throws with stderr on failure. */\nexport function git(cwd: string, args: string[], timeoutMs?: number): string {\n return runGit(cwd, args, undefined, timeoutMs);\n}\n\n/** Is `cwd` inside a git repository (a .git dir — worktrees: a .git file)? */\nexport function isGitRepo(cwd: string): boolean {\n try {\n return git(cwd, [\"rev-parse\", \"--git-dir\"]) !== \"\";\n } catch {\n return false;\n }\n}\n\n/** Tools that can write to the checkout (Bash can, via the shell). */\nconst WRITING_TOOLS = [\"Edit\", \"Write\", \"MultiEdit\", \"NotebookEdit\", \"Bash\"];\n\n/** Can a run with these --allowedTools write? No restriction at all means yes. */\nexport function toolsCanWrite(allowedTools: string[]): boolean {\n const names = allowedTools.flatMap((e) => e.split(\",\")).map((s) => s.trim().replace(/\\(.*$/, \"\")).filter(Boolean);\n return !names.length || names.some((n) => WRITING_TOOLS.includes(n));\n}\n\n/** How the run flags decide the worktree question; undefined = decide by default. */\nexport type WorktreeFlag = boolean | undefined;\n\n/** Should this run get a worktree? Explicit flag first, then: can it write? */\nexport function worktreeWanted(flag: WorktreeFlag, opts: { cwd: string; allowedTools: string[] }): boolean {\n if (flag !== undefined) return flag;\n return toolsCanWrite(opts.allowedTools) && isGitRepo(opts.cwd);\n}\n\nexport interface WorktreeInfo {\n dir: string;\n branch: string;\n base: string;\n /** `base` is a snapshot commit of uncommitted checkout state, not HEAD itself. */\n snapshot: boolean;\n}\n\n/** Paths PAI itself rewrites (Notes/, TODO*.md at the root or in Notes/): never part of a snapshot. */\nexport function isPaiBookkeepingPath(path: string): boolean {\n return /^notes\\//i.test(path) || /^TODO[^/]*\\.md$/.test(path);\n}\n\n/**\n * Commit `cwd`'s current index + working tree (including untracked files) as\n * a floating commit on top of HEAD, without touching the real index, working\n * tree or HEAD: a temporary `GIT_INDEX_FILE` collects the snapshot, `git\n * commit-tree` writes the commit object directly (it updates no ref). The\n * caller then branches a worktree from that commit, so the worktree sees\n * exactly what was on disk, uncommitted or not.\n */\nfunction snapshotUncommitted(cwd: string, id: string, head: string, bookkeeping: string[]): string {\n const idxDir = mkdtempSync(join(tmpdir(), \"pai-worktree-idx-\"));\n const idx = join(idxDir, \"index\");\n try {\n const env = { ...process.env, GIT_INDEX_FILE: idx };\n runGit(cwd, [\"read-tree\", head], env);\n runGit(cwd, [\"add\", \"-A\"], env);\n // PAI's own bookkeeping stays at its HEAD state in the snapshot\n if (bookkeeping.length) runGit(cwd, [\"--literal-pathspecs\", \"reset\", \"-q\", head, \"--\", ...bookkeeping], env);\n const tree = runGit(cwd, [\"write-tree\"], env);\n return runGit(cwd, [\n \"commit-tree\",\n tree,\n \"-p\",\n head,\n \"-m\",\n `worker ${id}: snapshot of uncommitted checkout`,\n ]);\n } finally {\n rmSync(idxDir, { recursive: true, force: true });\n }\n}\n\n/**\n * Create the worktree and branch for `id` from `cwd`'s HEAD — or, when the\n * checkout has uncommitted changes, from a snapshot commit of that dirty\n * state (see `snapshotUncommitted`), so a worktree worker sees the same\n * files the operator sees, not just what was last committed. Throws when git\n * refuses (no commits yet, branch exists, …) — the caller decides whether to\n * degrade to an in-place run.\n */\nexport function addWorktree(logDir: string, id: string, cwd: string): WorktreeInfo {\n const dir = worktreePath(logDir, id);\n const branch = worktreeBranch(id);\n const head = git(cwd, [\"rev-parse\", \"HEAD\"]);\n const all = dirtyPaths(cwd);\n const bookkeeping = all.filter(isPaiBookkeepingPath);\n const dirty = all.length > bookkeeping.length;\n const base = dirty ? snapshotUncommitted(cwd, id, head, bookkeeping) : head;\n git(cwd, [\"worktree\", \"add\", dir, \"-b\", branch, base]);\n provisionDeps(logDir, id, cwd, dir);\n return { dir, branch, base, snapshot: dirty };\n}\n\n/**\n * Give the worktree its own dependencies so the worker never has to improvise\n * (symlinking node_modules to the main checkout, building in a /tmp copy).\n * A real node_modules directory in the main checkout is APFS-cloned\n * (`cp -c`, copy-on-write, near instant); elsewhere the lockfile installs it.\n * Never a symlink. Time-boxed and best effort: a failure is logged, the run\n * goes on.\n */\nexport function provisionDeps(logDir: string, id: string, cwd: string, wtDir: string): void {\n const note = (n: string) => appendLedger(ledgerPath(logDir), \"WORKER-NOTE\", { id, note: n });\n try {\n const root = git(cwd, [\"rev-parse\", \"--show-toplevel\"]);\n const main = join(root, \"node_modules\");\n const dest = join(wtDir, \"node_modules\");\n if (!existsSync(main) || lstatSync(main).isSymbolicLink() || existsSync(dest)) return;\n try {\n // an unignored node_modules would be committed by salvage; the trailing\n // slash makes git test a directory (the path does not exist yet), so a\n // dir-only `node_modules/` rule matches too\n git(wtDir, [\"check-ignore\", \"-q\", \"node_modules/\"]);\n } catch {\n return;\n }\n const t0 = Date.now();\n const opts = { cwd: wtDir, stdio: \"ignore\" as const, timeout: 180_000 };\n if (platform() === \"darwin\") {\n try {\n execFileSync(\"cp\", [\"-cR\", main, dest], opts);\n note(`node_modules cloned (apfs) in ${Date.now() - t0}ms`);\n return;\n } catch {\n rmSync(dest, { recursive: true, force: true }); // half-copied\n }\n }\n if (existsSync(join(wtDir, \"bun.lock\"))) execFileSync(\"bun\", [\"install\", \"--frozen-lockfile\"], opts);\n else if (existsSync(join(wtDir, \"package-lock.json\"))) execFileSync(\"npm\", [\"ci\"], opts);\n else return;\n note(`node_modules installed from lockfile in ${Date.now() - t0}ms`);\n } catch (e) {\n try {\n note(`node_modules provisioning failed: ${(e as Error).message}`);\n } catch {\n /* ledger is best effort too */\n }\n }\n}\n\n/** Commits the branch collected on top of its base. */\nexport function commitsSince(dir: string, base: string): number {\n try {\n return parseInt(git(dir, [\"rev-list\", \"--count\", `${base}..HEAD`]), 10) || 0;\n } catch {\n return 0;\n }\n}\n\n/**\n * Record the worktree result in the status file: branch and commit count on\n * success, the worktree cleaned up on failure. Returns the updated status.\n */\nexport function recordWorktree(\n logDir: string,\n status: WorkerStatus,\n info: WorktreeInfo,\n ok: boolean\n): WorkerStatus {\n const s = { ...status };\n if (ok) {\n s.branch = info.branch;\n s.commits = commitsSince(info.dir, info.base);\n s.worktreeDir = info.dir;\n s.worktreeBase = info.base;\n s.worktreeSnapshot = info.snapshot;\n } else if (safeCommitsSince(info.dir, info.base) > 0) {\n // a failed or killed run that committed (or was salvaged) keeps its work:\n // branch and worktree stay for `pai worker merge` / `discard`\n s.branch = info.branch;\n s.commits = commitsSince(info.dir, info.base);\n s.worktreeDir = info.dir;\n s.worktreeBase = info.base;\n s.worktreeSnapshot = info.snapshot;\n } else {\n // a failed run with no commits leaves nothing to merge; the branch dies with the worktree\n removeWorktree(s.cwd, info.dir, true);\n try {\n git(s.cwd, [\"branch\", \"-D\", info.branch]);\n } catch {\n // already gone or never created\n }\n s.branch = null;\n s.worktreeDir = null;\n s.worktreeBase = null;\n s.commits = null;\n s.worktreeSnapshot = null;\n }\n saveStatus(logDir, s);\n invalidatePendingCache(logDir, s.cwd);\n return s;\n}\n\nfunction safeCommitsSince(dir: string, base: string): number {\n try {\n return commitsSince(dir, base);\n } catch {\n return 0;\n }\n}\n\n/**\n * Salvage for an exit path (run finaliser, kill signal): commit whatever the\n * worker left uncommitted, log WORKER-SALVAGE, never throw. Bounded by\n * `timeoutMs` per git call. Returns the committed paths.\n */\nexport function salvageOnExit(\n ledger: string,\n id: string,\n label: string,\n wtDir: string,\n timeoutMs?: number\n): string[] {\n try {\n const { committed, skipped } = salvageUncommitted(wtDir, label, timeoutMs);\n if (committed.length) appendLedger(ledger, \"WORKER-SALVAGE\", { id, paths: committed.join(\",\"), skipped: skipped.length });\n if (skipped.length) appendLedger(ledger, \"WORKER-NOTE\", { id, note: `salvage skipped: ${skipped.join(\"; \")}` });\n return committed;\n } catch (e) {\n try {\n appendLedger(ledger, \"WORKER-NOTE\", { id, note: `salvage failed: ${(e as Error).message}` });\n } catch {\n /* best effort */\n }\n return [];\n }\n}\n\n/** Remove a worktree directory from git's books and the filesystem. */\nfunction removeWorktree(cwd: string, dir: string, force: boolean): void {\n try {\n git(cwd, [\"worktree\", \"remove\", ...(force ? [\"--force\"] : []), dir]);\n return;\n } catch {\n // fall through to the manual cleanup\n }\n if (existsSync(dir)) {\n try {\n rmSync(dir, { recursive: true, force: true });\n git(cwd, [\"worktree\", \"prune\"]);\n } catch {\n // best effort: a leftover directory is visible in the logDir\n }\n }\n}\n\n/**\n * Tracked changes (staged or not) plus untracked files — everything a\n * `git add -A` in `wtDir` would commit. The same two calls the old patch\n * carry used; name-only, not porcelain: a worktree-only change renders as\n * \" M path\" and the shared git() helper trims that leading space away.\n */\nexport function uncommittedPaths(wtDir: string, timeoutMs?: number): string[] {\n const tracked = git(wtDir, [\"diff\", \"--name-only\", \"HEAD\"], timeoutMs).split(\"\\n\").filter(Boolean);\n const untracked = git(wtDir, [\"ls-files\", \"--others\", \"--exclude-standard\"], timeoutMs)\n .split(\"\\n\")\n .filter(Boolean);\n return [...tracked, ...untracked];\n}\n\n/**\n * True when `wtDir`/`relPath` is a symlink whose target resolves outside\n * `wtDir` — e.g. a worker's `node_modules -> <main checkout>/node_modules`\n * convenience link. A `.gitignore` using `dir/` (directories only) does not\n * ignore such a link, so it turns up as untracked; staging and merging it\n * lets git replace the real directory on the other side with a symlink to\n * itself. Never a candidate for salvage.\n */\nfunction symlinkEscapesWorktree(wtDir: string, relPath: string): boolean {\n const full = join(wtDir, relPath);\n let stat;\n try {\n stat = lstatSync(full);\n } catch {\n return false; // vanished; nothing to skip\n }\n if (!stat.isSymbolicLink()) return false;\n const target = readlinkSync(full);\n const resolvedTarget = isAbsolute(target) ? target : resolve(dirname(full), target);\n const realTarget = existsSync(resolvedTarget) ? realpathSync(resolvedTarget) : resolvedTarget;\n const realWtDir = realpathSync(wtDir);\n return realTarget !== realWtDir && !realTarget.startsWith(realWtDir + sep);\n}\n\nexport interface SalvageResult {\n /** Paths committed onto the branch. */\n committed: string[];\n /** Human-readable notes for paths left on disk, uncommitted, and why. */\n skipped: string[];\n}\n\n/**\n * Commit a worktree's uncommitted changes to its branch so the merge carries\n * them. `git merge` only moves committed work, so a worker that stopped\n * without committing would lose its edits to `worktree remove` — exactly\n * what happened live on 2026-09-17. Returns the salvaged paths, empty when\n * the worktree is clean. A symlink pointing outside the worktree (see\n * `symlinkEscapesWorktree`) is never staged; it is left on disk and reported\n * as skipped instead. A failed commit throws with the worktree untouched:\n * its edits are still on disk, so nothing is lost.\n */\nexport function salvageUncommitted(wtDir: string, label: string, timeoutMs?: number): SalvageResult {\n const paths = uncommittedPaths(wtDir, timeoutMs);\n if (!paths.length) return { committed: [], skipped: [] };\n const skipped: string[] = [];\n // tracked files gone from disk but not staged for removal: the worker never\n // ran `git rm`, so the absence is not its decision (see MAX_SALVAGE_DELETES)\n const missing = new Set(git(wtDir, [\"ls-files\", \"--deleted\"], timeoutMs).split(\"\\n\").filter(Boolean));\n const toStage: string[] = [];\n for (const p of paths) {\n if (missing.has(p)) {\n skipped.push(`${p} (missing, not committed)`);\n } else if (symlinkEscapesWorktree(wtDir, p)) {\n skipped.push(`symlink ${p} -> ${readlinkSync(join(wtDir, p))} (points outside the worktree)`);\n } else if (p === \"node_modules\" || p.startsWith(\"node_modules/\")) {\n skipped.push(`${p} (node_modules is never committed)`);\n } else {\n toStage.push(p);\n }\n }\n if (!toStage.length) return { committed: [], skipped };\n try {\n // --ignore-removal: additions and modifications only. Deletions the worker\n // staged itself (git rm) are already in the index and ride along.\n const excludes = paths.filter((p) => !toStage.includes(p)).map((p) => `:(exclude,literal)${p}`);\n git(wtDir, [\"add\", \"--ignore-removal\", \"--\", \".\", ...excludes], timeoutMs);\n const deleted = git(wtDir, [\"diff\", \"--cached\", \"--diff-filter=D\", \"--name-only\"], timeoutMs).split(\"\\n\").filter(Boolean);\n if (deleted.length > MAX_SALVAGE_DELETES) {\n git(wtDir, [\"reset\", \"-q\"], timeoutMs);\n throw new Error(`salvage would delete ${deleted.length} tracked files (${deleted.slice(0, 5).join(\", \")}, ...), refused`);\n }\n git(wtDir, [\"commit\", \"-m\", `salvaged: ${label}`], timeoutMs);\n } catch (e) {\n throw new Error(\n `cannot salvage the uncommitted changes in ${wtDir} — ${(e as Error).message}; ` +\n `nothing was lost: commit them there by hand, then re-run merge`\n );\n }\n return { committed: toStage, skipped };\n}\n\n/**\n * Refuse a merge whose branch adds or changes a top-level `node_modules` of\n * any type (a worker's own commit of its symlink included): merging it would\n * replace the checkout's real directory. Both merge paths call this.\n */\nexport function assertNoNodeModules(cwd: string, from: string, branch: string, id: string): void {\n if (git(cwd, [\"diff\", \"--name-only\", from, branch, \"--\", \"node_modules\"])) {\n throw new Error(\n `worker ${id}: branch ${branch} commits node_modules, which would replace the checkout's real directory. ` +\n `Remove it on the branch (git rm -r --cached node_modules, amend), then re-run merge`\n );\n }\n}\n\n/** A salvage commit that deletes more tracked files than this is refused. */\nconst MAX_SALVAGE_DELETES = 5;\n\n/**\n * The dirty paths of a checkout, parsed from `git status --porcelain -z`:\n * NUL-separated (a path with a newline in it cannot corrupt the parse),\n * rename entries contribute both sides, and any quoting is stripped.\n */\nfunction dirtyPaths(cwd: string): string[] {\n // raw execFileSync, not the shared git(): it trims stdout, which eats the\n // leading space of a worktree-only \" M path\" record and breaks the parse\n const raw = execFileSync(\"git\", [\"-C\", cwd, \"status\", \"--porcelain\", \"-z\"], {\n encoding: \"utf8\",\n timeout: 30_000,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n const fields = raw.split(\"\\0\");\n const strip = (p: string) => (p.startsWith('\"') && p.endsWith('\"') ? p.slice(1, -1) : p);\n const out: string[] = [];\n for (let i = 0; i < fields.length; i++) {\n const f = fields[i];\n if (!f || f.length < 4 || f.charAt(2) !== \" \") continue; // not an \"XY path\" record\n out.push(strip(f.slice(3)));\n const xy = f.slice(0, 2);\n if (xy.includes(\"R\") || xy.includes(\"C\")) {\n const orig = fields[i + 1]; // rename/copy records carry the source path next\n if (orig && orig.charAt(2) !== \" \") {\n out.push(strip(orig));\n i += 1;\n }\n }\n }\n return out;\n}\n\n/**\n * Refuse the merge when the original checkout is dirty in paths the branch\n * touches: the merge would overwrite those edits or fail on them, either way\n * leaving a half-state. No merge is made, the worktree stays.\n */\nfunction assertNoDirtyOverlap(\n cwd: string,\n incoming: string[],\n id: string,\n branch: string\n): void {\n const dirty = new Set(dirtyPaths(cwd));\n const overlap = [...new Set(incoming)].filter((p) => dirty.has(p)).sort();\n if (!overlap.length) return;\n throw new Error(\n `worker ${id}: the checkout ${cwd} has uncommitted changes in paths ${branch} touches: ` +\n `${overlap.join(\", \")}. Commit or stash them in the checkout, then re-run: pai worker merge ${id}. ` +\n `No merge was made; the worktree was kept.`\n );\n}\n\n/**\n * Never remove a worktree that still holds uncommitted changes — except a\n * symlink escaping the worktree (see `symlinkEscapesWorktree`): it was\n * deliberately left uncommitted by `salvageUncommitted`, and deleting the\n * link itself touches nothing outside the worktree.\n */\nexport function assertWorktreeClean(wtDir: string, id: string): void {\n const leftover = uncommittedPaths(wtDir).filter((p) => !symlinkEscapesWorktree(wtDir, p));\n if (leftover.length) {\n throw new Error(\n `worker ${id}: the worktree ${wtDir} still holds uncommitted changes ` +\n `(${leftover.join(\", \")}) — it was NOT removed; commit or copy them by hand, then re-run merge`\n );\n }\n}\n\n/** Blob sha of `path` at `rev` in `cwd`, or null when it does not exist there. */\nfunction blobAt(cwd: string, rev: string, path: string): string | null {\n try {\n return git(cwd, [\"rev-parse\", \"--verify\", \"-q\", `${rev}:${path}`]);\n } catch {\n return null;\n }\n}\n\n/** Blob sha of `path` as it sits on disk in `cwd`, or null when it is absent. */\nfunction workingBlob(cwd: string, path: string): string | null {\n if (!existsSync(join(cwd, path))) return null;\n try {\n return git(cwd, [\"hash-object\", \"--\", path]);\n } catch {\n return null;\n }\n}\n\n/**\n * Refuse a snapshot merge when the checkout differs from the snapshot the\n * worker branched from, for any path the branch touched: the operator edited\n * it, or it was created/deleted since — applying the branch's diff on top\n * would silently discard that. Absent-in-both counts as equal.\n */\nfunction assertNoSnapshotDrift(cwd: string, snapshot: string, paths: string[], id: string): void {\n const drifted = paths\n .filter((p) => blobAt(cwd, snapshot, p) !== workingBlob(cwd, p))\n .sort();\n if (!drifted.length) return;\n throw new Error(\n `worker ${id}: the checkout ${cwd} has changed since the worktree's snapshot in paths the branch touches: ` +\n `${drifted.join(\", \")}. Resolve by hand, then re-run: pai worker merge ${id}. ` +\n `No merge was made; the worktree was kept.`\n );\n}\n\n/**\n * `pai worker merge <id>` for a snapshot-based worktree (see `addWorktree`):\n * git's own merge only moves committed history, and the snapshot commit was\n * never on any branch reachable from `cwd`'s HEAD, so there is nothing to\n * `git merge` here. Instead the worker's own changes — `git diff --binary\n * <snapshot> <branch>` — are applied straight onto the checkout's working\n * tree as uncommitted edits, exactly like the uncommitted state the worker\n * started from. Works even if `cwd`'s HEAD moved since the snapshot: the\n * diff is applied to files, not merged into a tree.\n */\nfunction mergeSnapshotWorker(\n logDir: string,\n id: string,\n st: WorkerStatus & Required<Pick<WorkerStatus, \"branch\" | \"worktreeDir\">>,\n noCommit = false\n): string {\n const snapshot = st.worktreeBase!;\n const branch = st.branch!;\n const worktreeDir = st.worktreeDir!;\n const salvage = existsSync(worktreeDir)\n ? salvageUncommitted(worktreeDir, st.label || UNLABELED)\n : { committed: [], skipped: [] };\n const incoming = parseInt(git(st.cwd, [\"rev-list\", \"--count\", `${snapshot}..${branch}`]), 10) || 0;\n if (incoming <= 0) {\n throw new Error(\n `worker ${id}: branch ${branch} has no commits beyond its snapshot to merge. ` +\n `The worktree ${worktreeDir} was NOT removed — uncommitted work there would be destroyed. ` +\n `Commit it yourself, or drop everything with: pai worker discard ${id}`\n );\n }\n const changedPaths = git(st.cwd, [\"diff\", \"--name-only\", snapshot, branch]).split(\"\\n\").filter(Boolean);\n assertNoNodeModules(st.cwd, snapshot, branch, id);\n assertNoSnapshotDrift(st.cwd, snapshot, changedPaths, id);\n const diff = execFileSync(\"git\", [\"-C\", st.cwd, \"diff\", \"--binary\", snapshot, branch], {\n encoding: \"utf8\",\n timeout: 30_000,\n maxBuffer: 64 * 1024 * 1024,\n });\n if (diff.trim()) {\n try {\n execFileSync(\"git\", [\"-C\", st.cwd, \"apply\", \"--binary\"], {\n input: diff,\n encoding: \"utf8\",\n timeout: 30_000,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n });\n } catch (e) {\n const err = e as { stderr?: Buffer | string; message?: string };\n const why =\n (typeof err.stderr === \"string\" ? err.stderr : err.stderr?.toString(\"utf8\")) ||\n err.message ||\n String(e);\n throw new Error(\n `worker ${id}: git refused to apply ${branch}'s changes to ${st.cwd} — ${why.trim()}. ` +\n `The worktree ${worktreeDir} was kept: resolve by hand, then re-run merge`\n );\n }\n }\n if (noCommit) return noCommitNextSteps(id, branch, worktreeDir, changedPaths);\n removeWorktree(st.cwd, worktreeDir, true);\n try {\n git(st.cwd, [\"branch\", \"-D\", branch]); // never merged by git — nothing for -d to see\n } catch {\n // already gone\n }\n const s = { ...st, merged: true };\n saveStatus(logDir, s);\n const base =\n `applied worker ${id}'s changes (branch ${branch}) to ${st.cwd} as uncommitted changes: ` +\n `${changedPaths.join(\", \")} (worktree removed, branch deleted)`;\n const notes: string[] = [];\n if (salvage.committed.length) {\n notes.push(\n `salvaged ${salvage.committed.length} uncommitted change(s) onto the branch first: ${salvage.committed.join(\", \")}`\n );\n }\n if (salvage.skipped.length) {\n notes.push(`skipped ${salvage.skipped.join(\", \")}`);\n }\n return notes.length ? `${base}; ${notes.join(\"; \")}` : base;\n}\n\nfunction noCommitNextSteps(id: string, branch: string, worktreeDir: string, paths: string[]): string {\n return (\n `applied worker ${id}'s changes (branch ${branch}) to the checkout as uncommitted changes: ${paths.join(\", \")}. ` +\n `Branch and worktree ${worktreeDir} were kept. Next: review (git diff), commit, then run: pai worker gc`\n );\n}\n\n/**\n * `merge --no-commit`: apply base..branch to the checkout with a 3-way\n * `git apply`, leaving the result uncommitted. On conflict the tree is left\n * as is (markers + unmerged index entries) and the conflicted paths are listed.\n */\nfunction applyNoCommit(cwd: string, id: string, branch: string, worktreeDir: string, base: string, paths: string[]): string {\n const diff = execFileSync(\"git\", [\"-C\", cwd, \"diff\", \"--binary\", `${base}..${branch}`], {\n encoding: \"buffer\",\n timeout: 30_000,\n maxBuffer: 64 * 1024 * 1024,\n env: { ...process.env, LC_ALL: \"C\" },\n });\n try {\n execFileSync(\"git\", [\"-C\", cwd, \"apply\", \"--3way\", \"--binary\"], {\n input: diff,\n timeout: 30_000,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: { ...process.env, LC_ALL: \"C\" },\n });\n } catch (e) {\n const conflicted = git(cwd, [\"diff\", \"--name-only\", \"--diff-filter=U\"]).split(\"\\n\").filter(Boolean);\n const err = e as { stderr?: Buffer | string; message?: string };\n const why = ((typeof err.stderr === \"string\" ? err.stderr : err.stderr?.toString(\"utf8\")) || err.message || \"\").trim();\n throw new Error(\n conflicted.length\n ? `worker ${id}: applying ${branch} conflicted in ${conflicted.join(\", \")}. The tree was left for you: ` +\n `resolve those paths, review, commit; branch and worktree ${worktreeDir} were kept`\n : `worker ${id}: git refused to apply ${branch} to ${cwd} — ${why}. Nothing was changed; branch and worktree were kept`\n );\n }\n return noCommitNextSteps(id, branch, worktreeDir, paths);\n}\n\nexport type VerifyState = \"identical\" | \"differs\" | \"missing\" | \"deleted\";\nexport interface VerifyResult {\n id: string;\n ref: string;\n base: string;\n against: string;\n files: { path: string; state: VerifyState }[];\n ok: boolean;\n}\n\n/**\n * `pai worker verify <id>`: for every file the worker's branch (or its\n * refs/pai-archive/<id> archive once the branch is gone) changed since its\n * base, compare bytes with the working tree or `--against <ref>`.\n * identical = the target already holds the worker's version (a worker delete\n * counts as identical when the target lacks the file too); differs = other\n * bytes; missing = the worker's file is absent in the target; deleted = the\n * worker deleted it but the target still has it. ok = every file identical.\n */\nexport function verifyWorker(logDir: string, id: string, opts: { against?: string } = {}): VerifyResult {\n const st = loadStatus(logDir, id);\n if (!st) throw new Error(`no worker named \"${id}\"`);\n const env = { ...process.env, LC_ALL: \"C\" };\n const bytes = (args: string[]): Buffer | null => {\n try {\n return execFileSync(\"git\", [\"-C\", st.cwd, ...args], {\n env,\n timeout: 30_000,\n maxBuffer: 256 * 1024 * 1024,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n } catch {\n return null;\n }\n };\n const has = (ref: string): boolean => bytes([\"rev-parse\", \"--verify\", \"-q\", `${ref}^{commit}`]) !== null;\n const branch = st.branch ?? worktreeBranch(id);\n const ref = [`refs/heads/${branch}`, `refs/pai-archive/${id}`].find(has);\n if (!ref) throw new Error(`worker ${id}: neither ${branch} nor refs/pai-archive/${id} exists in ${st.cwd}`);\n const base = st.worktreeSnapshot && st.worktreeBase ? st.worktreeBase : git(st.cwd, [\"merge-base\", \"HEAD\", ref]);\n const list = (bytes([\"diff\", \"--name-status\", \"--no-renames\", \"-z\", `${base}..${ref}`]) ?? Buffer.alloc(0))\n .toString(\"utf8\")\n .split(\"\\0\")\n .filter(Boolean);\n const files: VerifyResult[\"files\"] = [];\n for (let i = 0; i + 1 < list.length; i += 2) {\n const path = list[i + 1];\n const deleted = list[i] === \"D\";\n const target = opts.against\n ? bytes([\"show\", `${opts.against}:${path}`])\n : existsSync(join(st.cwd, path))\n ? readFileSync(join(st.cwd, path))\n : null;\n let state: VerifyState;\n if (deleted) state = target ? \"deleted\" : \"identical\";\n else {\n const mine = bytes([\"show\", `${ref}:${path}`]);\n state = !target ? \"missing\" : mine && mine.equals(target) ? \"identical\" : \"differs\";\n }\n files.push({ path, state });\n }\n return { id, ref, base, against: opts.against ?? \"working tree\", files, ok: files.every((f) => f.state === \"identical\") };\n}\n\n/**\n * `pai worker merge <id>`: salvage whatever the worker left uncommitted onto\n * its branch, refuse when the original checkout is dirty in paths the branch\n * touches, merge with --no-ff (the merge commit names the worker), then\n * remove the worktree and delete the branch; the status gains `merged: true`.\n * A branch with nothing to merge is refused loudly — after salvage that\n * genuinely means it holds nothing, and reporting success there would\n * destroy the worktree for no gain.\n */\nexport function mergeWorker(logDir: string, id: string, opts: { noCommit?: boolean } = {}): string {\n const st = mustHaveBranch(logDir, id);\n if (st.merged) return `worker ${id}: branch ${st.branch} already merged`;\n if (st.worktreeSnapshot) return mergeSnapshotWorker(logDir, id, st, opts.noCommit);\n // salvage first: only a commit can carry uncommitted work through the merge\n const salvage = existsSync(st.worktreeDir!)\n ? salvageUncommitted(st.worktreeDir!, st.label || UNLABELED)\n : { committed: [], skipped: [] };\n const incoming = parseInt(git(st.cwd, [\"rev-list\", \"--count\", `HEAD..${st.branch}`]), 10) || 0;\n if (incoming <= 0) {\n throw new Error(\n `worker ${id}: branch ${st.branch} has no commits to merge. ` +\n `The worktree ${st.worktreeDir} was NOT removed — uncommitted work there would be destroyed. ` +\n `Commit it yourself, or drop everything with: pai worker discard ${id}`\n );\n }\n const mergeBase = git(st.cwd, [\"merge-base\", \"HEAD\", st.branch!]);\n const incomingPaths = git(st.cwd, [\"diff\", \"--name-only\", mergeBase, st.branch!])\n .split(\"\\n\")\n .filter(Boolean);\n assertNoNodeModules(st.cwd, mergeBase, st.branch!, id);\n assertNoDirtyOverlap(st.cwd, incomingPaths, id, st.branch!);\n if (opts.noCommit) return applyNoCommit(st.cwd, id, st.branch!, st.worktreeDir!, mergeBase, incomingPaths);\n try {\n git(st.cwd, [\"merge\", \"--no-ff\", st.branch!, \"-m\", `merge worker ${id} (${st.label})`]);\n } catch (e) {\n throw new Error(\n `worker ${id}: git refused the merge of ${st.branch} — ${(e as Error).message}. ` +\n `The worktree ${st.worktreeDir} was kept: resolve the conflict, then re-run merge`\n );\n }\n if (existsSync(st.worktreeDir!)) assertWorktreeClean(st.worktreeDir!, id);\n // force: assertWorktreeClean already confirmed nothing but a skipped\n // outside-pointing symlink (if anything) remains uncommitted\n removeWorktree(st.cwd, st.worktreeDir!, true);\n let branchGone = true;\n try {\n git(st.cwd, [\"branch\", \"-d\", st.branch!]);\n } catch {\n branchGone = false; // -d refuses anything not fully merged; keep the branch, say so\n }\n const s = { ...st, merged: true };\n saveStatus(logDir, s);\n invalidatePendingCache(logDir, st.cwd);\n const base = `merged ${st.branch} into ${st.cwd} (worktree removed${branchGone ? \", branch deleted\" : \"; branch kept: git refused -d\"})`;\n const notes: string[] = [];\n if (salvage.committed.length) {\n notes.push(`salvaged ${salvage.committed.length} uncommitted change(s): ${salvage.committed.join(\", \")}`);\n }\n if (salvage.skipped.length) {\n notes.push(`skipped ${salvage.skipped.join(\", \")}`);\n }\n return notes.length ? `${base}; ${notes.join(\"; \")}` : base;\n}\n\n/** `pai worker discard <id>`: drop worktree and branch, keep nothing. */\nexport function discardWorker(logDir: string, id: string): string {\n const st = mustHaveBranch(logDir, id);\n removeWorktree(st.cwd, st.worktreeDir!, true);\n let branchGone = false;\n try {\n git(st.cwd, [\"branch\", \"-D\", st.branch!]);\n branchGone = true;\n } catch {\n branchGone = false;\n }\n const s = {\n ...st,\n branch: null,\n worktreeDir: null,\n worktreeBase: null,\n worktreeSnapshot: null,\n commits: null,\n merged: false,\n };\n saveStatus(logDir, s);\n invalidatePendingCache(logDir, st.cwd);\n return `discarded worker ${id}: worktree removed${branchGone ? `, branch ${st.branch} deleted` : \"\"}`;\n}\n\nfunction mustHaveBranch(logDir: string, id: string): WorkerStatus & Required<Pick<WorkerStatus, \"branch\" | \"worktreeDir\">> {\n const st = loadStatus(logDir, id);\n if (!st) throw new Error(`no worker named \"${id}\"`);\n if (st.resumedBy) throw new Error(`worker ${id} is superseded by ${st.resumedBy}, use that id`);\n if (!ownedTree(st)) {\n throw new Error(\n `worker ${id} has no worktree branch to ${st.branch ? \"clean up\" : \"merge\"} — ` +\n `it ran in place or its branch was already handled`\n );\n }\n return st as WorkerStatus & Required<Pick<WorkerStatus, \"branch\" | \"worktreeDir\">>;\n}\n\n/**\n * The paragraph a worktree run's system prompt gains: it may commit on its\n * own branch (the no-commit rule holds for the main branch only), it must not\n * merge or push itself, and the parent or operator merges.\n */\nexport function worktreeSystemPrompt(id: string, branch: string, dir: string, snapshot = false): string {\n return [\n \"You are running in your own git worktree:\",\n ` ${dir} on branch ${branch} (worker id ${id}).`,\n \"Commit your work on that branch as you go (git add / git commit) — committing here is expected;\",\n \"the no-commit rule applies to the main branch only, and this is not it.\",\n \"Do not merge, rebase or push; the operator merges your branch back with `pai worker merge`.\",\n \"Use ONLY relative paths inside the worktree, never absolute worktree paths — absolute paths break after merge and leak machine layout.\",\n ...(snapshot\n ? [\n \"This worktree was branched from a snapshot that includes files the parent checkout had uncommitted at spawn time — not just its last commit.\",\n ]\n : []),\n ].join(\"\\n\");\n}\n\n/**\n * For headless runs WITHOUT a worktree: the worker shares the operator's\n * checkout with other in-flight workers, so a hook blocks git stash (except\n * list/show), reset, clean, checkout/restore of paths, switch, rebase and\n * merge — take baselines from measurements before editing, not from stash.\n */\nexport function inPlaceSystemPrompt(): string {\n return [\n \"You are running in the operator's shared git checkout, alongside other workers editing it at the same time.\",\n \"A hook blocks git stash (except list/show), reset, clean, checkout/restore of files, switch, rebase and merge — take baselines from measurements taken before you edit, not from stashing.\",\n \"Read-only git (status, diff, log, show, stash list) is fine.\",\n ].join(\"\\n\");\n}\n\nexport interface GcEntry {\n id: string;\n /** archive ref, or the reason the worktree was skipped. */\n archive?: string;\n skipped?: string;\n}\n\nconst GC_STAMP = \"gc-last\";\n\n/**\n * Pid of a live process whose cwd is `dir` or below (shell drivers parked in a\n * worker tree), else 0. One `lsof -d cwd` listing, no tree walk. If lsof is\n * missing or fails, nothing is reported busy.\n */\nexport function busyPid(dir: string): number {\n let real = dir;\n try {\n real = realpathSync(dir);\n } catch {\n return 0;\n }\n let raw = \"\";\n try {\n raw = execFileSync(\"lsof\", [\"-d\", \"cwd\", \"-Fpn\"], { encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"ignore\"], maxBuffer: 64 << 20 });\n } catch (e) {\n // lsof exits 1 when some entries are unreadable but still prints the rest\n raw = String((e as { stdout?: string }).stdout ?? \"\");\n }\n let pid = 0;\n for (const line of raw.split(\"\\n\")) {\n if (line[0] === \"p\") pid = Number(line.slice(1));\n else if (line[0] === \"n\" && pid !== process.pid) {\n const name = line.slice(1);\n if (name === real || name.startsWith(real + sep)) return pid;\n }\n }\n return 0;\n}\n\n/**\n * Archive worktrees of finished workers older than `olderThanHours` and\n * status-less orphan directories: uncommitted state is salvaged onto the\n * branch, the branch is x, the worktree is\n * removed. Nothing is deleted without a salvage first; a failed salvage skips\n * that worktree. Running workers are never touched. `dryRun` reports only.\n */\nexport function gcWorktrees(\n logDir: string,\n olderThanHours = 24,\n dryRun = false,\n now = Date.now()\n): GcEntry[] {\n const cutoff = now - olderThanHours * 3_600_000;\n const out: GcEntry[] = [];\n const root = worktreesDir(logDir);\n const statuses = loadStatuses(logDir);\n const owned = new Set(statuses.map((s) => ownedTree(s)?.dir).filter((d): d is string => !!d).map((d) => resolve(d)));\n const archive = (id: string, dir: string, branch: string, st: WorkerStatus | null): void => {\n const target = `refs/pai-archive/${id}`;\n const pid = busyPid(dir);\n if (pid) {\n if (!dryRun) appendLedger(ledgerPath(logDir), \"WORKER-GC-SKIP\", { id, reason: \"busy\", pid: String(pid) });\n return void out.push({ id, skipped: `busy: pid ${pid} has its cwd inside` });\n }\n if (dryRun) return void out.push({ id, archive: target });\n try {\n // The shared object store and refs live in the main repo; the recorded cwd\n // may be another worker's worktree that gc already removed.\n let repo: string;\n try {\n const raw = git(dir, [\"rev-parse\", \"--git-common-dir\"]);\n repo = isAbsolute(raw) ? raw : resolve(dir, raw);\n } catch {\n throw new Error(\"not a git worktree\");\n }\n const label = `archive: leftover state of worker ${id}`;\n if (uncommittedPaths(dir).length) {\n // salvageUncommitted labels \"salvaged: <label>\"; keep the requested subject\n const res = salvageUncommitted(dir, label);\n if (res.committed.length) git(dir, [\"commit\", \"--amend\", \"-q\", \"-m\", label]);\n if (res.skipped.length && uncommittedPaths(dir).some((p) => !symlinkEscapesWorktree(dir, p))) {\n throw new Error(\"uncommitted paths remain\");\n }\n }\n const src = branch;\n let hasBranch = true;\n try {\n git(repo, [\"rev-parse\", \"--verify\", \"-q\", `refs/heads/${src}`]);\n } catch {\n hasBranch = false;\n }\n const tip = git(dir, [\"rev-parse\", \"HEAD\"]);\n // never drop a branch whose tip the archive ref would not keep reachable\n if (hasBranch) git(repo, [\"merge-base\", \"--is-ancestor\", `refs/heads/${src}`, tip]);\n git(repo, [\"update-ref\", target, tip]);\n git(repo, [\"worktree\", \"remove\", \"--force\", dir]);\n if (hasBranch) git(repo, [\"branch\", \"-D\", src]);\n if (st) saveStatus(logDir, { ...st, branch: null, worktreeDir: null, archived: target });\n appendLedger(ledgerPath(logDir), \"WORKER-GC\", { id, archive: target });\n out.push({ id, archive: target });\n } catch (e) {\n out.push({ id, skipped: (e as Error).message.split(\"\\n\")[0] });\n }\n };\n\n for (const st of statuses) {\n const tree = ownedTree(st);\n if (!tree || !existsSync(tree.dir) || isLive(st)) continue;\n let end = 0;\n try {\n end = statSync(statusPath(logDir, st.id)).mtimeMs;\n } catch {\n continue;\n }\n if (end > cutoff) continue;\n archive(st.id, tree.dir, tree.branch, st);\n }\n\n if (existsSync(root)) {\n for (const ent of readdirSync(root, { withFileTypes: true })) {\n const dir = join(root, ent.name);\n // owned by a status (the last id of a resume chain) is that status's business\n if (!ent.isDirectory() || owned.has(resolve(dir))) continue;\n try {\n if (statSync(dir).mtimeMs > cutoff) continue;\n } catch {\n continue;\n }\n archive(ent.name, dir, worktreeBranch(ent.name), null);\n }\n }\n if (!dryRun) {\n for (const repo of new Set(statuses.map((s) => s.cwd))) {\n try {\n git(repo, [\"worktree\", \"prune\"]);\n } catch {\n // repo gone\n }\n invalidatePendingCache(logDir, repo);\n }\n }\n return out;\n}\n\n/** Run `gcWorktrees` at most once per `everyMin` minutes (stamp file in the logDir). */\nexport function gcWorktreesThrottled(logDir: string, everyMin = 60, now = Date.now()): GcEntry[] | null {\n const stamp = join(logDir, GC_STAMP);\n try {\n if (now - statSync(stamp).mtimeMs < everyMin * 60_000) return null;\n } catch {\n // never ran\n }\n mkdirSync(logDir, { recursive: true });\n writeFileSync(stamp, String(now), \"utf8\");\n return gcWorktrees(logDir, 24, false, now);\n}\n"],"mappings":"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"}
|
package/docs/worker.md
CHANGED
|
@@ -324,6 +324,28 @@ own `order`), skipping:
|
|
|
324
324
|
Nothing qualifying fails the run with the exclusion reason of every provider
|
|
325
325
|
in the order.
|
|
326
326
|
|
|
327
|
+
### Budget policy (default)
|
|
328
|
+
|
|
329
|
+
By default the auto walk is budget-aware (`routing.policy: "budget"`,
|
|
330
|
+
`"order"` restores the plain walk above): candidates come from
|
|
331
|
+
`routing.priority` — falling back to `routing.order`, else
|
|
332
|
+
`anthropic, glm, kimi` — and every provider whose **live budget** reads at or
|
|
333
|
+
above its wall is skipped, so a run waits a full provider out on the next one
|
|
334
|
+
with room:
|
|
335
|
+
|
|
336
|
+
- budgets are read from each provider's `usage:` endpoint (anthropic: the
|
|
337
|
+
OAuth usage endpoint), cached for 5 minutes, fullest window wins; a failed
|
|
338
|
+
probe reads as "unknown" and never walls anyone,
|
|
339
|
+
- walls default to `anthropic: 95, glm: 90, kimi: 85` (95 for any other
|
|
340
|
+
provider) and are overridden per provider by `routing.budgetWalls`,
|
|
341
|
+
- a walled provider gets a cooldown until `max(now + 10 min, resetAt)`, so it
|
|
342
|
+
re-enters on its own once its window resets,
|
|
343
|
+
- the chosen worker's status carries a one-line `switchReason` when budget
|
|
344
|
+
moved it ("`glm: anthropic 96% until 08:02`"), shown by `pai worker ps`,
|
|
345
|
+
|
|
346
|
+
A run's provider is resolved once, at spawn: a running worker is never
|
|
347
|
+
switched mid-flight. A quota reroute applies the same walls.
|
|
348
|
+
|
|
327
349
|
A quota failure before the first tool call is re-run on the next provider and
|
|
328
350
|
logged as `WORKER-REROUTE`. `pai worker providers enable <name>` clears a
|
|
329
351
|
cooldown by hand.
|
package/package.json
CHANGED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import "./config-DSdCEcHS.mjs";
|
|
2
|
-
import "./config-2pR0RqHM.mjs";
|
|
3
|
-
import "./status-C3fBJKU_.mjs";
|
|
4
|
-
import "./worktree-lnmAOFQv.mjs";
|
|
5
|
-
import "./pai-marker-CDwQOpQ8.mjs";
|
|
6
|
-
import { n as formatAutoRouteJson, t as autoRoute } from "./auto-route-DrncbMaz.mjs";
|
|
7
|
-
|
|
8
|
-
export { autoRoute, formatAutoRouteJson };
|