@tekmidian/pai 0.61.0 → 0.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{async-CLvuY7b-.mjs → async-DNVWtQl2.mjs} +2 -2
- package/dist/{async-CLvuY7b-.mjs.map → async-DNVWtQl2.mjs.map} +1 -1
- package/dist/{chain-C5oeyUg4.mjs → chain-CuaDawK9.mjs} +2 -2
- package/dist/{chain-C5oeyUg4.mjs.map → chain-CuaDawK9.mjs.map} +1 -1
- package/dist/cli/index.mjs +11 -11
- package/dist/cli/program.mjs +11 -11
- package/dist/{context-handover-cache-BdtJZpgv.mjs → context-handover-cache-DFNnlWv2.mjs} +2 -2
- package/dist/{context-handover-cache-BdtJZpgv.mjs.map → context-handover-cache-DFNnlWv2.mjs.map} +1 -1
- package/dist/daemon/index.mjs +8 -8
- package/dist/{daemon-DSsIEqzW.mjs → daemon-Bf7Eock4.mjs} +32 -18
- package/dist/{daemon-DSsIEqzW.mjs.map → daemon-Bf7Eock4.mjs.map} +1 -1
- package/dist/daemon-bJi1gDkG.mjs +19 -0
- package/dist/daemon-mcp/index.mjs +4 -4
- package/dist/{factory-EUUhSSoq.mjs → factory-BozZa81q.mjs} +3 -3
- package/dist/{factory-EUUhSSoq.mjs.map → factory-BozZa81q.mjs.map} +1 -1
- package/dist/{factory-DW7BCJJT.mjs → factory-UO9_DFMN.mjs} +1 -1
- package/dist/{fallback-DDbJVaR8.mjs → fallback-F0TWGVM2.mjs} +2 -2
- package/dist/{fallback-DDbJVaR8.mjs.map → fallback-F0TWGVM2.mjs.map} +1 -1
- package/dist/{helpers-D9WkIf9R.mjs → helpers-Dj-B314s.mjs} +3 -2
- package/dist/{helpers-D9WkIf9R.mjs.map → helpers-Dj-B314s.mjs.map} +1 -1
- package/dist/hooks/block-sleep-poll.mjs.map +2 -2
- package/dist/hooks/load-project-context.mjs +8513 -133
- package/dist/hooks/load-project-context.mjs.map +4 -4
- package/dist/hooks/security-validator.mjs.map +1 -1
- package/dist/hooks/worker-guard.mjs +7859 -0
- package/dist/hooks/worker-guard.mjs.map +7 -0
- package/dist/hooks/worker-status-line.mjs +101 -8
- package/dist/hooks/worker-status-line.mjs.map +4 -4
- package/dist/index.mjs +1 -1
- package/dist/{indexer-backend-DcDtin1O.mjs → indexer-backend-BN9rpp9O.mjs} +2 -2
- package/dist/{main-resolver-DIQ6m_RA.mjs → main-resolver-BIft_FVF.mjs} +3 -3
- package/dist/{main-resolver-DIQ6m_RA.mjs.map → main-resolver-BIft_FVF.mjs.map} +1 -1
- package/dist/{main-resolver-CxOsxtOr.mjs → main-resolver-C9-poMdi.mjs} +1 -1
- package/dist/{planner-D8jAr_sx.mjs → planner-BG9ivPj0.mjs} +4 -4
- package/dist/{planner-D8jAr_sx.mjs.map → planner-BG9ivPj0.mjs.map} +1 -1
- package/dist/{postgres-nvU5pShF.mjs → postgres-CxD6_Dca.mjs} +3 -3
- package/dist/{postgres-nvU5pShF.mjs.map → postgres-CxD6_Dca.mjs.map} +1 -1
- package/dist/{program-BTdspOOa.mjs → program-BRvCzhjx.mjs} +157 -40
- package/dist/program-BRvCzhjx.mjs.map +1 -0
- package/dist/{run-CxxZKEZw.mjs → run-BVQZI79D.mjs} +281 -85
- package/dist/run-BVQZI79D.mjs.map +1 -0
- package/dist/{session-keepalive-urk--pfa.mjs → session-keepalive-DJA97K2X.mjs} +2 -2
- package/dist/{session-keepalive-urk--pfa.mjs.map → session-keepalive-DJA97K2X.mjs.map} +1 -1
- package/dist/{sqlite-D9p5a4qe.mjs → sqlite-CfKfuggn.mjs} +2 -2
- package/dist/{sqlite-D9p5a4qe.mjs.map → sqlite-CfKfuggn.mjs.map} +1 -1
- package/dist/{tools-r-7ZG8B5.mjs → tools-B52IvaU6.mjs} +3 -3
- package/dist/{tools-qXyHFwNj.mjs → tools-DFdPbX-X.mjs} +2 -2
- package/dist/{tools-qXyHFwNj.mjs.map → tools-DFdPbX-X.mjs.map} +1 -1
- package/dist/{vault-indexer-DHYBpRZ5.mjs → vault-indexer-mCFus_AM.mjs} +2 -2
- package/dist/{vault-indexer-DHYBpRZ5.mjs.map → vault-indexer-mCFus_AM.mjs.map} +1 -1
- package/dist/{wakeup-C48hcN6O.mjs → wakeup-DO1O4udw.mjs} +2 -2
- package/dist/{wakeup-C48hcN6O.mjs.map → wakeup-DO1O4udw.mjs.map} +1 -1
- package/dist/{work-queue-worker-DUvPiwGO.mjs → work-queue-worker-BbrC3I_R.mjs} +2 -2
- package/dist/{work-queue-worker-DUvPiwGO.mjs.map → work-queue-worker-BbrC3I_R.mjs.map} +1 -1
- package/dist/{work-queue-worker-1_VBfI_k.mjs → work-queue-worker-z-AATZ2t.mjs} +3 -3
- package/docs/commands/README.md +2 -0
- package/docs/commands/worker.md +27 -0
- package/package.json +2 -2
- package/scripts/build-hooks.mjs +6 -5
- package/scripts/build-skill-stubs.mjs +6 -5
- package/src/hooks/ts/lib/worker-guard.test.ts +188 -0
- package/src/hooks/ts/lib/worker-guard.ts +315 -0
- package/src/hooks/ts/pre-tool-use/worker-guard.ts +75 -0
- package/src/hooks/ts/session-start/load-project-context.ts +14 -0
- package/dist/daemon-MoFF4oeg.mjs +0 -19
- package/dist/program-BTdspOOa.mjs.map +0 -1
- package/dist/run-CxxZKEZw.mjs.map +0 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { i as getRegistryBackend } from "./factory-
|
|
1
|
+
import { i as getRegistryBackend } from "./factory-BozZa81q.mjs";
|
|
2
2
|
import { E as readWorkersSection, O as resolveModelCapability, T as providerKeyPath, b as isModelCapability, d as NATIVE_ANTHROPIC_MODELS, g as classModelCapability, h as assertProviderRunnable, k as resolveProviderKey, m as WorkersConfigError, n as claudeCommand, t as buildRunEnv } from "./run-env-BE5xyCwy.mjs";
|
|
3
3
|
import { _ as statusPath, d as resolveTarget, f as setCooldown, g as panesDir, h as ledgerPath, i as capabilityForRun, m as eventsPath, n as ensureProxyRunning, o as isQuotaFailure, p as ensureNoMcpConfig, s as nextAutoProvider, t as DEFAULT_PROXY_PORT, u as resolveCapabilityRun, v as workersLogDir } from "./server-BY6AedmC.mjs";
|
|
4
4
|
import { appendFileSync, closeSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
|
|
5
|
-
import { homedir, tmpdir } from "node:os";
|
|
5
|
+
import { homedir, platform, tmpdir } from "node:os";
|
|
6
6
|
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { execFileSync, spawn } from "node:child_process";
|
|
8
8
|
import { connect, createServer } from "node:net";
|
|
@@ -598,7 +598,7 @@ function worktreePath(logDir, id) {
|
|
|
598
598
|
return join(worktreesDir(logDir), id);
|
|
599
599
|
}
|
|
600
600
|
/** Run git in `cwd` with an optional env override, trimmed stdout, stderr on failure. */
|
|
601
|
-
function runGit(cwd, args, env) {
|
|
601
|
+
function runGit(cwd, args, env, timeout = 3e4) {
|
|
602
602
|
try {
|
|
603
603
|
return execFileSync("git", [
|
|
604
604
|
"-C",
|
|
@@ -606,7 +606,7 @@ function runGit(cwd, args, env) {
|
|
|
606
606
|
...args
|
|
607
607
|
], {
|
|
608
608
|
encoding: "utf8",
|
|
609
|
-
timeout
|
|
609
|
+
timeout,
|
|
610
610
|
stdio: [
|
|
611
611
|
"ignore",
|
|
612
612
|
"pipe",
|
|
@@ -621,8 +621,8 @@ function runGit(cwd, args, env) {
|
|
|
621
621
|
}
|
|
622
622
|
}
|
|
623
623
|
/** Run git in `cwd`, returning trimmed stdout; throws with stderr on failure. */
|
|
624
|
-
function git(cwd, args) {
|
|
625
|
-
return runGit(cwd, args);
|
|
624
|
+
function git(cwd, args, timeoutMs) {
|
|
625
|
+
return runGit(cwd, args, void 0, timeoutMs);
|
|
626
626
|
}
|
|
627
627
|
/** Is `cwd` inside a git repository (a .git dir — worktrees: a .git file)? */
|
|
628
628
|
function isGitRepo(cwd) {
|
|
@@ -632,30 +632,23 @@ function isGitRepo(cwd) {
|
|
|
632
632
|
return false;
|
|
633
633
|
}
|
|
634
634
|
}
|
|
635
|
-
/**
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
if (!p) return true;
|
|
643
|
-
const firstLine = p.split("\n", 1)[0];
|
|
644
|
-
if (/\b(read[- ]only|do not (modify|change|edit|write)|don'?t (modify|change|edit|write)|no changes)\b/i.test(firstLine)) return true;
|
|
645
|
-
return /^(review|read|analy[sz]e|research|summar[iy]|inspect|investigate|spotcheck|report|find|list|check|verify|describe|explain|show)\b/i.test(p);
|
|
646
|
-
}
|
|
647
|
-
/** The classes whose runs write files and therefore default to a worktree. */
|
|
648
|
-
const WORKTREE_CLASSES = [
|
|
649
|
-
"implement",
|
|
650
|
-
"complex",
|
|
651
|
-
"plan"
|
|
635
|
+
/** Tools that can write to the checkout (Bash can, via the shell). */
|
|
636
|
+
const WRITING_TOOLS = [
|
|
637
|
+
"Edit",
|
|
638
|
+
"Write",
|
|
639
|
+
"MultiEdit",
|
|
640
|
+
"NotebookEdit",
|
|
641
|
+
"Bash"
|
|
652
642
|
];
|
|
653
|
-
/**
|
|
643
|
+
/** Can a run with these --allowedTools write? No restriction at all means yes. */
|
|
644
|
+
function toolsCanWrite(allowedTools) {
|
|
645
|
+
const names = allowedTools.flatMap((e) => e.split(",")).map((s) => s.trim().replace(/\(.*$/, "")).filter(Boolean);
|
|
646
|
+
return !names.length || names.some((n) => WRITING_TOOLS.includes(n));
|
|
647
|
+
}
|
|
648
|
+
/** Should this run get a worktree? Explicit flag first, then: can it write? */
|
|
654
649
|
function worktreeWanted(flag, opts) {
|
|
655
650
|
if (flag !== void 0) return flag;
|
|
656
|
-
|
|
657
|
-
if (!isGitRepo(opts.cwd)) return false;
|
|
658
|
-
return !promptLooksReadonly(opts.prompt ?? "");
|
|
651
|
+
return toolsCanWrite(opts.allowedTools) && isGitRepo(opts.cwd);
|
|
659
652
|
}
|
|
660
653
|
/**
|
|
661
654
|
* Commit `cwd`'s current index + working tree (including untracked files) as
|
|
@@ -699,11 +692,6 @@ function snapshotUncommitted(cwd, id, head) {
|
|
|
699
692
|
* degrade to an in-place run.
|
|
700
693
|
*/
|
|
701
694
|
function addWorktree(logDir, id, cwd) {
|
|
702
|
-
const swept = sweepOrphanWorktrees(logDir);
|
|
703
|
-
if (swept.length) appendLedger(ledgerPath(logDir), "WORKER-NOTE", {
|
|
704
|
-
id,
|
|
705
|
-
note: `swept orphan worktree(s): ${swept.join(", ")}`
|
|
706
|
-
});
|
|
707
695
|
const dir = worktreePath(logDir, id);
|
|
708
696
|
const branch = worktreeBranch(id);
|
|
709
697
|
const head = git(cwd, ["rev-parse", "HEAD"]);
|
|
@@ -717,6 +705,7 @@ function addWorktree(logDir, id, cwd) {
|
|
|
717
705
|
branch,
|
|
718
706
|
base
|
|
719
707
|
]);
|
|
708
|
+
provisionDeps(logDir, id, cwd, dir);
|
|
720
709
|
return {
|
|
721
710
|
dir,
|
|
722
711
|
branch,
|
|
@@ -725,44 +714,60 @@ function addWorktree(logDir, id, cwd) {
|
|
|
725
714
|
};
|
|
726
715
|
}
|
|
727
716
|
/**
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
717
|
+
* Give the worktree its own dependencies so the worker never has to improvise
|
|
718
|
+
* (symlinking node_modules to the main checkout, building in a /tmp copy).
|
|
719
|
+
* A real node_modules directory in the main checkout is APFS-cloned
|
|
720
|
+
* (`cp -c`, copy-on-write, near instant); elsewhere the lockfile installs it.
|
|
721
|
+
* Never a symlink. Time-boxed and best effort: a failure is logged, the run
|
|
722
|
+
* goes on.
|
|
734
723
|
*/
|
|
735
|
-
function
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
const
|
|
742
|
-
const
|
|
743
|
-
if (existsSync(
|
|
744
|
-
try {
|
|
745
|
-
if ((Date.now() - statSync(dir).mtimeMs) / 6e4 < minAgeMin) continue;
|
|
746
|
-
} catch {}
|
|
747
|
-
let gitDir = null;
|
|
724
|
+
function provisionDeps(logDir, id, cwd, wtDir) {
|
|
725
|
+
const note = (n) => appendLedger(ledgerPath(logDir), "WORKER-NOTE", {
|
|
726
|
+
id,
|
|
727
|
+
note: n
|
|
728
|
+
});
|
|
729
|
+
try {
|
|
730
|
+
const main = join(git(cwd, ["rev-parse", "--show-toplevel"]), "node_modules");
|
|
731
|
+
const dest = join(wtDir, "node_modules");
|
|
732
|
+
if (!existsSync(main) || lstatSync(main).isSymbolicLink() || existsSync(dest)) return;
|
|
748
733
|
try {
|
|
749
|
-
|
|
750
|
-
|
|
734
|
+
git(wtDir, [
|
|
735
|
+
"check-ignore",
|
|
736
|
+
"-q",
|
|
737
|
+
"node_modules"
|
|
738
|
+
]);
|
|
751
739
|
} catch {
|
|
752
|
-
|
|
740
|
+
return;
|
|
753
741
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
742
|
+
const t0 = Date.now();
|
|
743
|
+
const opts = {
|
|
744
|
+
cwd: wtDir,
|
|
745
|
+
stdio: "ignore",
|
|
746
|
+
timeout: 18e4
|
|
747
|
+
};
|
|
748
|
+
if (platform() === "darwin") try {
|
|
749
|
+
execFileSync("cp", [
|
|
750
|
+
"-cR",
|
|
751
|
+
main,
|
|
752
|
+
dest
|
|
753
|
+
], opts);
|
|
754
|
+
note(`node_modules cloned (apfs) in ${Date.now() - t0}ms`);
|
|
755
|
+
return;
|
|
756
|
+
} catch {
|
|
757
|
+
rmSync(dest, {
|
|
758
|
+
recursive: true,
|
|
759
|
+
force: true
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
if (existsSync(join(wtDir, "bun.lock"))) execFileSync("bun", ["install", "--frozen-lockfile"], opts);
|
|
763
|
+
else if (existsSync(join(wtDir, "package-lock.json"))) execFileSync("npm", ["ci"], opts);
|
|
764
|
+
else return;
|
|
765
|
+
note(`node_modules installed from lockfile in ${Date.now() - t0}ms`);
|
|
766
|
+
} catch (e) {
|
|
767
|
+
try {
|
|
768
|
+
note(`node_modules provisioning failed: ${e.message}`);
|
|
762
769
|
} catch {}
|
|
763
|
-
swept.push(id);
|
|
764
770
|
}
|
|
765
|
-
return swept;
|
|
766
771
|
}
|
|
767
772
|
/** Commits the branch collected on top of its base. */
|
|
768
773
|
function commitsSince(dir, base) {
|
|
@@ -788,6 +793,12 @@ function recordWorktree(logDir, status, info, ok) {
|
|
|
788
793
|
s.worktreeDir = info.dir;
|
|
789
794
|
s.worktreeBase = info.base;
|
|
790
795
|
s.worktreeSnapshot = info.snapshot;
|
|
796
|
+
} else if (safeCommitsSince(info.dir, info.base) > 0) {
|
|
797
|
+
s.branch = info.branch;
|
|
798
|
+
s.commits = commitsSince(info.dir, info.base);
|
|
799
|
+
s.worktreeDir = info.dir;
|
|
800
|
+
s.worktreeBase = info.base;
|
|
801
|
+
s.worktreeSnapshot = info.snapshot;
|
|
791
802
|
} else {
|
|
792
803
|
removeWorktree(s.cwd, info.dir, true);
|
|
793
804
|
try {
|
|
@@ -806,6 +817,37 @@ function recordWorktree(logDir, status, info, ok) {
|
|
|
806
817
|
saveStatus(logDir, s);
|
|
807
818
|
return s;
|
|
808
819
|
}
|
|
820
|
+
function safeCommitsSince(dir, base) {
|
|
821
|
+
try {
|
|
822
|
+
return commitsSince(dir, base);
|
|
823
|
+
} catch {
|
|
824
|
+
return 0;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Salvage for an exit path (run finaliser, kill signal): commit whatever the
|
|
829
|
+
* worker left uncommitted, log WORKER-SALVAGE, never throw. Bounded by
|
|
830
|
+
* `timeoutMs` per git call. Returns the committed paths.
|
|
831
|
+
*/
|
|
832
|
+
function salvageOnExit(ledger, id, label, wtDir, timeoutMs) {
|
|
833
|
+
try {
|
|
834
|
+
const { committed, skipped } = salvageUncommitted(wtDir, label, timeoutMs);
|
|
835
|
+
if (committed.length) appendLedger(ledger, "WORKER-SALVAGE", {
|
|
836
|
+
id,
|
|
837
|
+
paths: committed.join(","),
|
|
838
|
+
skipped: skipped.length
|
|
839
|
+
});
|
|
840
|
+
return committed;
|
|
841
|
+
} catch (e) {
|
|
842
|
+
try {
|
|
843
|
+
appendLedger(ledger, "WORKER-NOTE", {
|
|
844
|
+
id,
|
|
845
|
+
note: `salvage failed: ${e.message}`
|
|
846
|
+
});
|
|
847
|
+
} catch {}
|
|
848
|
+
return [];
|
|
849
|
+
}
|
|
850
|
+
}
|
|
809
851
|
/** Remove a worktree directory from git's books and the filesystem. */
|
|
810
852
|
function removeWorktree(cwd, dir, force) {
|
|
811
853
|
try {
|
|
@@ -831,17 +873,17 @@ function removeWorktree(cwd, dir, force) {
|
|
|
831
873
|
* carry used; name-only, not porcelain: a worktree-only change renders as
|
|
832
874
|
* " M path" and the shared git() helper trims that leading space away.
|
|
833
875
|
*/
|
|
834
|
-
function uncommittedPaths(wtDir) {
|
|
876
|
+
function uncommittedPaths(wtDir, timeoutMs) {
|
|
835
877
|
const tracked = git(wtDir, [
|
|
836
878
|
"diff",
|
|
837
879
|
"--name-only",
|
|
838
880
|
"HEAD"
|
|
839
|
-
]).split("\n").filter(Boolean);
|
|
881
|
+
], timeoutMs).split("\n").filter(Boolean);
|
|
840
882
|
const untracked = git(wtDir, [
|
|
841
883
|
"ls-files",
|
|
842
884
|
"--others",
|
|
843
885
|
"--exclude-standard"
|
|
844
|
-
]).split("\n").filter(Boolean);
|
|
886
|
+
], timeoutMs).split("\n").filter(Boolean);
|
|
845
887
|
return [...tracked, ...untracked];
|
|
846
888
|
}
|
|
847
889
|
/**
|
|
@@ -877,8 +919,8 @@ function symlinkEscapesWorktree(wtDir, relPath) {
|
|
|
877
919
|
* as skipped instead. A failed commit throws with the worktree untouched:
|
|
878
920
|
* its edits are still on disk, so nothing is lost.
|
|
879
921
|
*/
|
|
880
|
-
function salvageUncommitted(wtDir, label) {
|
|
881
|
-
const paths = uncommittedPaths(wtDir);
|
|
922
|
+
function salvageUncommitted(wtDir, label, timeoutMs) {
|
|
923
|
+
const paths = uncommittedPaths(wtDir, timeoutMs);
|
|
882
924
|
if (!paths.length) return {
|
|
883
925
|
committed: [],
|
|
884
926
|
skipped: []
|
|
@@ -894,14 +936,16 @@ function salvageUncommitted(wtDir, label) {
|
|
|
894
936
|
try {
|
|
895
937
|
git(wtDir, [
|
|
896
938
|
"add",
|
|
939
|
+
"-A",
|
|
897
940
|
"--",
|
|
898
|
-
|
|
899
|
-
|
|
941
|
+
".",
|
|
942
|
+
...paths.filter((p) => !toStage.includes(p)).map((p) => `:(exclude,literal)${p}`)
|
|
943
|
+
], timeoutMs);
|
|
900
944
|
git(wtDir, [
|
|
901
945
|
"commit",
|
|
902
946
|
"-m",
|
|
903
947
|
`salvaged: ${label}`
|
|
904
|
-
]);
|
|
948
|
+
], timeoutMs);
|
|
905
949
|
} catch (e) {
|
|
906
950
|
throw new Error(`cannot salvage the uncommitted changes in ${wtDir} — ${e.message}; nothing was lost: commit them there by hand, then re-run merge`);
|
|
907
951
|
}
|
|
@@ -1215,6 +1259,139 @@ function inPlaceSystemPrompt() {
|
|
|
1215
1259
|
"Read-only git (status, diff, log, show, stash list) is fine."
|
|
1216
1260
|
].join("\n");
|
|
1217
1261
|
}
|
|
1262
|
+
const GC_STAMP = "gc-last";
|
|
1263
|
+
/**
|
|
1264
|
+
* Archive worktrees of finished workers older than `olderThanHours` and
|
|
1265
|
+
* status-less orphan directories: uncommitted state is salvaged onto the
|
|
1266
|
+
* branch, the branch is x, the worktree is
|
|
1267
|
+
* removed. Nothing is deleted without a salvage first; a failed salvage skips
|
|
1268
|
+
* that worktree. Running workers are never touched. `dryRun` reports only.
|
|
1269
|
+
*/
|
|
1270
|
+
function gcWorktrees(logDir, olderThanHours = 24, dryRun = false, now = Date.now()) {
|
|
1271
|
+
const cutoff = now - olderThanHours * 36e5;
|
|
1272
|
+
const out = [];
|
|
1273
|
+
const root = worktreesDir(logDir);
|
|
1274
|
+
const known = /* @__PURE__ */ new Set();
|
|
1275
|
+
const archive = (id, dir, repo, st) => {
|
|
1276
|
+
const target = `refs/pai-archive/${id}`;
|
|
1277
|
+
if (dryRun) return void out.push({
|
|
1278
|
+
id,
|
|
1279
|
+
archive: target
|
|
1280
|
+
});
|
|
1281
|
+
try {
|
|
1282
|
+
if (!repo) throw new Error("not a git worktree");
|
|
1283
|
+
const label = `archive: leftover state of worker ${id}`;
|
|
1284
|
+
if (uncommittedPaths(dir).length) {
|
|
1285
|
+
const res = salvageUncommitted(dir, label);
|
|
1286
|
+
if (res.committed.length) git(dir, [
|
|
1287
|
+
"commit",
|
|
1288
|
+
"--amend",
|
|
1289
|
+
"-q",
|
|
1290
|
+
"-m",
|
|
1291
|
+
label
|
|
1292
|
+
]);
|
|
1293
|
+
if (res.skipped.length && uncommittedPaths(dir).some((p) => !symlinkEscapesWorktree(dir, p))) throw new Error("uncommitted paths remain");
|
|
1294
|
+
}
|
|
1295
|
+
const src = worktreeBranch(id);
|
|
1296
|
+
let hasBranch = true;
|
|
1297
|
+
try {
|
|
1298
|
+
git(repo, [
|
|
1299
|
+
"rev-parse",
|
|
1300
|
+
"--verify",
|
|
1301
|
+
"-q",
|
|
1302
|
+
`refs/heads/${src}`
|
|
1303
|
+
]);
|
|
1304
|
+
} catch {
|
|
1305
|
+
hasBranch = false;
|
|
1306
|
+
}
|
|
1307
|
+
const tip = git(dir, ["rev-parse", "HEAD"]);
|
|
1308
|
+
if (hasBranch) git(repo, [
|
|
1309
|
+
"merge-base",
|
|
1310
|
+
"--is-ancestor",
|
|
1311
|
+
`refs/heads/${src}`,
|
|
1312
|
+
tip
|
|
1313
|
+
]);
|
|
1314
|
+
git(repo, [
|
|
1315
|
+
"update-ref",
|
|
1316
|
+
target,
|
|
1317
|
+
tip
|
|
1318
|
+
]);
|
|
1319
|
+
git(repo, [
|
|
1320
|
+
"worktree",
|
|
1321
|
+
"remove",
|
|
1322
|
+
"--force",
|
|
1323
|
+
dir
|
|
1324
|
+
]);
|
|
1325
|
+
if (hasBranch) git(repo, [
|
|
1326
|
+
"branch",
|
|
1327
|
+
"-D",
|
|
1328
|
+
src
|
|
1329
|
+
]);
|
|
1330
|
+
if (st) saveStatus(logDir, {
|
|
1331
|
+
...st,
|
|
1332
|
+
branch: null,
|
|
1333
|
+
worktreeDir: null,
|
|
1334
|
+
archived: target
|
|
1335
|
+
});
|
|
1336
|
+
appendLedger(ledgerPath(logDir), "WORKER-GC", {
|
|
1337
|
+
id,
|
|
1338
|
+
archive: target
|
|
1339
|
+
});
|
|
1340
|
+
out.push({
|
|
1341
|
+
id,
|
|
1342
|
+
archive: target
|
|
1343
|
+
});
|
|
1344
|
+
} catch (e) {
|
|
1345
|
+
out.push({
|
|
1346
|
+
id,
|
|
1347
|
+
skipped: e.message.split("\n")[0]
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
};
|
|
1351
|
+
for (const st of loadStatuses(logDir)) {
|
|
1352
|
+
known.add(st.id);
|
|
1353
|
+
if (!st.worktreeDir || !existsSync(st.worktreeDir) || isLive(st)) continue;
|
|
1354
|
+
let end = 0;
|
|
1355
|
+
try {
|
|
1356
|
+
end = statSync(statusPath(logDir, st.id)).mtimeMs;
|
|
1357
|
+
} catch {
|
|
1358
|
+
continue;
|
|
1359
|
+
}
|
|
1360
|
+
if (end > cutoff) continue;
|
|
1361
|
+
archive(st.id, st.worktreeDir, st.cwd, st);
|
|
1362
|
+
}
|
|
1363
|
+
if (existsSync(root)) for (const ent of readdirSync(root, { withFileTypes: true })) {
|
|
1364
|
+
if (!ent.isDirectory() || known.has(ent.name) || existsSync(statusPath(logDir, ent.name))) continue;
|
|
1365
|
+
const dir = join(root, ent.name);
|
|
1366
|
+
try {
|
|
1367
|
+
if (statSync(dir).mtimeMs > cutoff) continue;
|
|
1368
|
+
} catch {
|
|
1369
|
+
continue;
|
|
1370
|
+
}
|
|
1371
|
+
let repo = null;
|
|
1372
|
+
try {
|
|
1373
|
+
const raw = git(dir, ["rev-parse", "--git-common-dir"]);
|
|
1374
|
+
repo = isAbsolute(raw) ? raw : resolve(dir, raw);
|
|
1375
|
+
} catch {
|
|
1376
|
+
repo = null;
|
|
1377
|
+
}
|
|
1378
|
+
archive(ent.name, dir, repo, null);
|
|
1379
|
+
}
|
|
1380
|
+
if (!dryRun) for (const repo of new Set(loadStatuses(logDir).map((s) => s.cwd))) try {
|
|
1381
|
+
git(repo, ["worktree", "prune"]);
|
|
1382
|
+
} catch {}
|
|
1383
|
+
return out;
|
|
1384
|
+
}
|
|
1385
|
+
/** Run `gcWorktrees` at most once per `everyMin` minutes (stamp file in the logDir). */
|
|
1386
|
+
function gcWorktreesThrottled(logDir, everyMin = 60, now = Date.now()) {
|
|
1387
|
+
const stamp = join(logDir, GC_STAMP);
|
|
1388
|
+
try {
|
|
1389
|
+
if (now - statSync(stamp).mtimeMs < everyMin * 6e4) return null;
|
|
1390
|
+
} catch {}
|
|
1391
|
+
mkdirSync(logDir, { recursive: true });
|
|
1392
|
+
writeFileSync(stamp, String(now), "utf8");
|
|
1393
|
+
return gcWorktrees(logDir, 24, false, now);
|
|
1394
|
+
}
|
|
1218
1395
|
|
|
1219
1396
|
//#endregion
|
|
1220
1397
|
//#region src/workers/scope.ts
|
|
@@ -3233,8 +3410,12 @@ async function runWorker(opts) {
|
|
|
3233
3410
|
if (!config.enabled) throw new Error("workers are off. Turn them on with: pai worker on\n(then the Agent-tool hook stops denying Anthropic subagents only when you do)");
|
|
3234
3411
|
const logDir = workersLogDir(config);
|
|
3235
3412
|
mkdirSync(logDir, { recursive: true });
|
|
3413
|
+
try {
|
|
3414
|
+
const gc = gcWorktreesThrottled(logDir)?.filter((r) => r.archive);
|
|
3415
|
+
if (gc?.length) process.stderr.write(`pai worker: archived ${gc.length} leftover worktree(s) (pai worker gc)\n`);
|
|
3416
|
+
} catch {}
|
|
3236
3417
|
if (opts.className === "plan" && !opts._planner) {
|
|
3237
|
-
const { runPlanner } = await import("./planner-
|
|
3418
|
+
const { runPlanner } = await import("./planner-BG9ivPj0.mjs");
|
|
3238
3419
|
return runPlanner(opts);
|
|
3239
3420
|
}
|
|
3240
3421
|
const parent = launchParent(opts.parent);
|
|
@@ -3445,10 +3626,21 @@ function interactiveMcpTools(explicitMcp, callerTools, projectLaunch) {
|
|
|
3445
3626
|
* backgrounded a sub-worker and exited without waiting, the run must not
|
|
3446
3627
|
* report success, whatever the process returned.
|
|
3447
3628
|
*/
|
|
3629
|
+
/** Per-git-call bound for the salvage a kill signal runs before exiting. */
|
|
3630
|
+
const SIGNAL_SALVAGE_MS = 5e3;
|
|
3448
3631
|
async function finaliseRun(f) {
|
|
3449
3632
|
const { logDir, ledger, ok, rc } = f;
|
|
3450
3633
|
let { status, report } = f;
|
|
3451
3634
|
const wid = status.id;
|
|
3635
|
+
const salvaged = f.worktree ? salvageOnExit(ledger, wid, status.label || UNLABELED, f.worktree.dir) : [];
|
|
3636
|
+
if (salvaged.length) {
|
|
3637
|
+
const msg = `WORKER-SALVAGE: committed uncommitted edits (${salvaged.join(", ")})`;
|
|
3638
|
+
report = {
|
|
3639
|
+
...report ?? {},
|
|
3640
|
+
notes: [report?.notes ?? f.fallbackText, msg].filter(Boolean).join("\n"),
|
|
3641
|
+
result: report?.result ?? "-"
|
|
3642
|
+
};
|
|
3643
|
+
}
|
|
3452
3644
|
const orphanReason = checkOrphanedChildren(logDir, wid);
|
|
3453
3645
|
const succeeded = ok && !orphanReason;
|
|
3454
3646
|
status.state = succeeded ? "done" : "failed";
|
|
@@ -3631,8 +3823,7 @@ async function executeRun(a) {
|
|
|
3631
3823
|
let worktree = null;
|
|
3632
3824
|
if (headless && !a.printCmd && worktreeWanted(a.worktreeFlag, {
|
|
3633
3825
|
cwd,
|
|
3634
|
-
|
|
3635
|
-
prompt: parsed.prompt
|
|
3826
|
+
allowedTools: parsed.allowedTools
|
|
3636
3827
|
})) try {
|
|
3637
3828
|
worktree = addWorktree(logDir, wid, cwd);
|
|
3638
3829
|
} catch (e) {
|
|
@@ -3816,9 +4007,12 @@ async function executeRun(a) {
|
|
|
3816
4007
|
killed: 1,
|
|
3817
4008
|
label
|
|
3818
4009
|
});
|
|
3819
|
-
if (worktree)
|
|
3820
|
-
|
|
3821
|
-
|
|
4010
|
+
if (worktree) {
|
|
4011
|
+
salvageOnExit(ledger, wid, label, worktree.dir, SIGNAL_SALVAGE_MS);
|
|
4012
|
+
try {
|
|
4013
|
+
recordWorktree(logDir, status, worktree, false);
|
|
4014
|
+
} catch {}
|
|
4015
|
+
}
|
|
3822
4016
|
cleanup();
|
|
3823
4017
|
try {
|
|
3824
4018
|
proc.kill();
|
|
@@ -4059,8 +4253,7 @@ async function executeCodexRun(a) {
|
|
|
4059
4253
|
let worktree = null;
|
|
4060
4254
|
if (worktreeWanted(a.worktreeFlag, {
|
|
4061
4255
|
cwd,
|
|
4062
|
-
|
|
4063
|
-
prompt: parsed.prompt
|
|
4256
|
+
allowedTools: parsed.allowedTools
|
|
4064
4257
|
})) try {
|
|
4065
4258
|
worktree = addWorktree(logDir, wid, cwd);
|
|
4066
4259
|
} catch (e) {
|
|
@@ -4165,9 +4358,12 @@ async function executeCodexRun(a) {
|
|
|
4165
4358
|
status.secs = Math.floor((Date.now() - t0) / 1e3);
|
|
4166
4359
|
status.last = `killed by signal ${sig}`;
|
|
4167
4360
|
saveStatus(logDir, status);
|
|
4168
|
-
if (worktree)
|
|
4169
|
-
|
|
4170
|
-
|
|
4361
|
+
if (worktree) {
|
|
4362
|
+
salvageOnExit(ledger, wid, label, worktree.dir, SIGNAL_SALVAGE_MS);
|
|
4363
|
+
try {
|
|
4364
|
+
recordWorktree(logDir, status, worktree, false);
|
|
4365
|
+
} catch {}
|
|
4366
|
+
}
|
|
4171
4367
|
try {
|
|
4172
4368
|
proc.kill();
|
|
4173
4369
|
} catch {}
|
|
@@ -4374,5 +4570,5 @@ function resultFromOutput(out) {
|
|
|
4374
4570
|
}
|
|
4375
4571
|
|
|
4376
4572
|
//#endregion
|
|
4377
|
-
export {
|
|
4378
|
-
//# sourceMappingURL=run-
|
|
4573
|
+
export { appendLedger as A, loadStatus as B, workerInScope as C, mergeWorker as D, git as E, contextLabel as F, saveStatus as G, newWorkerId as H, contextPercent as I, longInlinePromptHint as J, setWorkerLabel as K, elapsedOf as L, UNLABELED as M, ageOf as N, uncommittedPaths as O, alive as P, expandMcpNames as Q, isChatPane as R, sessionTag as S, gcWorktrees as T, nowStamp as U, loadStatuses as V, ownsPid as W, shortText as X, parseRunnerArgs as Y, describeMcp as Z, currentTabKey as _, projectLaunchConfig as a, resolveSession as b, handoffFromInside as c, workerDepth as d, checkPaneForWorker as f, markControlsHeld as g, clickrControlsArgv as h, usageContextTokens as i, ledgerSummary as j, worktreesDir as k, readInbox as l, openPaneForWorker as m, runWorker as n, parseWorkerReport as o, openFollowPane as p, waitForTerminalStatus as q, testProvider as r, renderReport as s, printResult as t, sayToWorker as u, itermForClaudeSession as v, discardWorker as w, sessionMapPath as x, itermUuid as y, isLive as z };
|
|
4574
|
+
//# sourceMappingURL=run-BVQZI79D.mjs.map
|