@ordewell/core 0.11.1 → 0.11.3
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/AUTHORS.md +4 -0
- package/dist/{IRunner-B5l9PCTv.d.ts → IRunner-DOMtDTVt.d.ts} +50 -14
- package/dist/{IRunner-DBgDx_Mr.d.mts → IRunner-ihalYfTo.d.mts} +50 -14
- package/dist/{chunk-4QXOWEGC.mjs → chunk-7WNLN63J.mjs} +11 -4
- package/dist/chunk-7WNLN63J.mjs.map +1 -0
- package/dist/index.d.mts +48 -13
- package/dist/index.d.ts +48 -13
- package/dist/index.js +1116 -524
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1085 -500
- package/dist/index.mjs.map +1 -1
- package/dist/plan-utils.d.mts +2 -2
- package/dist/plan-utils.d.ts +2 -2
- package/dist/plan-utils.js +7 -0
- package/dist/plan-utils.js.map +1 -1
- package/dist/plan-utils.mjs +3 -1
- package/dist/{runSettingsText-BKwWs052.d.mts → runSettingsText-BuLVBlXS.d.mts} +113 -67
- package/dist/{runSettingsText-CAXiTpsj.d.ts → runSettingsText-DVOd7vKC.d.ts} +113 -67
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/package.json +1 -1
- package/dist/chunk-4QXOWEGC.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
assignmentConsequence,
|
|
48
48
|
assignmentIssue,
|
|
49
49
|
assignmentIssueText,
|
|
50
|
+
attachingLabel,
|
|
50
51
|
automergeSummary,
|
|
51
52
|
autorunSummary,
|
|
52
53
|
awaitingLabel,
|
|
@@ -231,7 +232,7 @@ import {
|
|
|
231
232
|
withPatch,
|
|
232
233
|
withoutSealedMark,
|
|
233
234
|
worstSeverity
|
|
234
|
-
} from "./chunk-
|
|
235
|
+
} from "./chunk-7WNLN63J.mjs";
|
|
235
236
|
import {
|
|
236
237
|
DEFAULT_LOOP_ROUNDS,
|
|
237
238
|
DEFAULT_SCRIPT_TIMEOUT_MS,
|
|
@@ -918,9 +919,9 @@ function literalCdTarget(seg) {
|
|
|
918
919
|
return target === "" || target.startsWith("-") || target.startsWith("~") || /[*?[]/.test(target) ? void 0 : target;
|
|
919
920
|
}
|
|
920
921
|
function followCd(segments) {
|
|
921
|
-
if (segments.some((s) => s.joinedBy === "other" ||
|
|
922
|
+
if (segments.some((s) => s.joinedBy === "other" || movesShell(s))) return;
|
|
922
923
|
const inPipeline = (i) => segments[i].joinedBy === "pipe" || segments[i + 1]?.joinedBy === "pipe";
|
|
923
|
-
if (segments.some((s, i) => s.binary === "cd" && !inPipeline(i) && literalCdTarget(s) === void 0)) return;
|
|
924
|
+
if (segments.some((s, i) => s.binary.toLowerCase() === "cd" && !inPipeline(i) && literalCdTarget(s) === void 0)) return;
|
|
924
925
|
let chain = [];
|
|
925
926
|
segments.forEach((seg, i) => {
|
|
926
927
|
seg.cwd = chain;
|
|
@@ -929,7 +930,7 @@ function followCd(segments) {
|
|
|
929
930
|
});
|
|
930
931
|
}
|
|
931
932
|
function markShellState(segments, bareSegment) {
|
|
932
|
-
if (!bareSegment && !segments.some(
|
|
933
|
+
if (!bareSegment && !segments.some(movesShell)) return;
|
|
933
934
|
for (const seg of segments) if (seg.binary === "cd") seg.cdSteered = true;
|
|
934
935
|
}
|
|
935
936
|
var SHELL_STATE_COMMANDS = /* @__PURE__ */ new Set([
|
|
@@ -948,6 +949,7 @@ var SHELL_STATE_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
948
949
|
"exec",
|
|
949
950
|
"pushd",
|
|
950
951
|
"popd",
|
|
952
|
+
"chdir",
|
|
951
953
|
"alias",
|
|
952
954
|
"unalias",
|
|
953
955
|
"builtin",
|
|
@@ -956,6 +958,9 @@ var SHELL_STATE_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
956
958
|
"hash",
|
|
957
959
|
"trap"
|
|
958
960
|
]);
|
|
961
|
+
function movesShell(seg) {
|
|
962
|
+
return SHELL_STATE_COMMANDS.has(seg.binary.toLowerCase());
|
|
963
|
+
}
|
|
959
964
|
|
|
960
965
|
// src/services/commandRunners.ts
|
|
961
966
|
var HELP_FLAGS = ["--help", "--version"];
|
|
@@ -1811,8 +1816,9 @@ var FLAG_POLICY = {
|
|
|
1811
1816
|
sort: {
|
|
1812
1817
|
cluster: true,
|
|
1813
1818
|
// Absent on purpose: `-o`/`--output` writes a file with no redirect for the
|
|
1814
|
-
// redirect check to see,
|
|
1815
|
-
//
|
|
1819
|
+
// redirect check to see, `-T`/`--temporary-directory` names where sort writes
|
|
1820
|
+
// its run files, and `--compress-program` names a program that sort runs
|
|
1821
|
+
// over its own temporary files — a permitted binary as an interpreter.
|
|
1816
1822
|
booleans: [
|
|
1817
1823
|
"-b",
|
|
1818
1824
|
"-c",
|
|
@@ -1851,11 +1857,9 @@ var FLAG_POLICY = {
|
|
|
1851
1857
|
"-k",
|
|
1852
1858
|
"-t",
|
|
1853
1859
|
"-S",
|
|
1854
|
-
"-T",
|
|
1855
1860
|
"--key",
|
|
1856
1861
|
"--field-separator",
|
|
1857
1862
|
"--buffer-size",
|
|
1858
|
-
"--temporary-directory",
|
|
1859
1863
|
"--random-source",
|
|
1860
1864
|
"--batch-size",
|
|
1861
1865
|
"--parallel",
|
|
@@ -2482,23 +2486,28 @@ function scanFlags(seg) {
|
|
|
2482
2486
|
const booleans = /* @__PURE__ */ new Set([...spec.booleans ?? [], ...UNIVERSAL_FLAGS]);
|
|
2483
2487
|
const values = new Set(spec.values ?? []);
|
|
2484
2488
|
const operands = [];
|
|
2489
|
+
const operandAt = [];
|
|
2490
|
+
const operand = (at) => {
|
|
2491
|
+
operands.push(seg.args[at]);
|
|
2492
|
+
operandAt.push(at);
|
|
2493
|
+
};
|
|
2485
2494
|
let i = 0;
|
|
2486
2495
|
while (i < seg.args.length) {
|
|
2487
2496
|
const token = seg.args[i];
|
|
2488
2497
|
if (token === "--" && !spec.expressionArgs) {
|
|
2489
|
-
|
|
2498
|
+
for (let j = i + 1; j < seg.args.length; j++) operand(j);
|
|
2490
2499
|
break;
|
|
2491
2500
|
}
|
|
2492
2501
|
if (!/^-./.test(token)) {
|
|
2493
|
-
|
|
2502
|
+
operand(i);
|
|
2494
2503
|
i++;
|
|
2495
2504
|
continue;
|
|
2496
2505
|
}
|
|
2497
2506
|
const shape = matchFlag(spec, booleans, values, token);
|
|
2498
|
-
if (shape === void 0) return { unknown: flagLabel(token), operands };
|
|
2507
|
+
if (shape === void 0) return { unknown: flagLabel(token), operands, operandAt };
|
|
2499
2508
|
i += shape === "value" && takesNextToken(seg.args[i + 1]) ? 2 : 1;
|
|
2500
2509
|
}
|
|
2501
|
-
return { operands };
|
|
2510
|
+
return { operands, operandAt };
|
|
2502
2511
|
}
|
|
2503
2512
|
function subcommandOf(seg) {
|
|
2504
2513
|
const scan = scanFlags(seg);
|
|
@@ -2651,6 +2660,8 @@ function sedRefusalFor(binary, scripts) {
|
|
|
2651
2660
|
return `The "${binary}" script runs a shell command (the "e" command or the "e" flag of "s"), which this classifier cannot inspect. ${INSPECT_OR_TASK}`;
|
|
2652
2661
|
case "write":
|
|
2653
2662
|
return `The "${binary}" script writes a file (the "w"/"W" command or the "w" flag of "s"). You are a read-only planner \u2014 print the output instead, or describe the write as a task.`;
|
|
2663
|
+
case "read":
|
|
2664
|
+
return `The "${binary}" script reads a file it names itself (the "r"/"R" command), which path confinement cannot see. Read that file directly instead.`;
|
|
2654
2665
|
default:
|
|
2655
2666
|
return `This classifier could not read the "${binary}" script well enough to rule out a shell command or a file write in it. Rewrite it with plain print, delete and substitute commands, or describe the work as a task.`;
|
|
2656
2667
|
}
|
|
@@ -2771,6 +2782,7 @@ function sedScriptHazard(script) {
|
|
|
2771
2782
|
if (cmd === void 0) return "unreadable";
|
|
2772
2783
|
if (cmd === "e") return "exec";
|
|
2773
2784
|
if (cmd === "w" || cmd === "W") return "write";
|
|
2785
|
+
if (cmd === "r" || cmd === "R") return "read";
|
|
2774
2786
|
if (cmd === "{" || cmd === "}") continue;
|
|
2775
2787
|
if (cmd === "#") {
|
|
2776
2788
|
while (i < n && script[i] !== "\n") i++;
|
|
@@ -2792,7 +2804,7 @@ function sedScriptHazard(script) {
|
|
|
2792
2804
|
if (!commandEnds()) return "unreadable";
|
|
2793
2805
|
continue;
|
|
2794
2806
|
}
|
|
2795
|
-
if ("
|
|
2807
|
+
if ("aic".includes(cmd)) {
|
|
2796
2808
|
while (i < n && script[i] !== "\n") i += script[i] === "\\" ? 2 : 1;
|
|
2797
2809
|
continue;
|
|
2798
2810
|
}
|
|
@@ -2892,6 +2904,12 @@ function awkRefusalFor(binary, programs) {
|
|
|
2892
2904
|
return `The "${binary}" program pipes to or from a command ("|"), which runs it through a shell this classifier cannot inspect. ${INSPECT_OR_TASK}`;
|
|
2893
2905
|
case "write":
|
|
2894
2906
|
return `The "${binary}" program redirects its output to a file (">"). You are a read-only planner \u2014 print to standard output instead, or describe the write as a task.`;
|
|
2907
|
+
case "read":
|
|
2908
|
+
return `The "${binary}" program reads a file it names itself ("getline <" or an "ARGV" entry), which path confinement cannot see. Name the file as an operand instead.`;
|
|
2909
|
+
case "environment":
|
|
2910
|
+
return `The "${binary}" program reads the environment ("ENVIRON"), which holds credentials. Run printenv for the one variable you need, which asks first.`;
|
|
2911
|
+
case "symbol-table":
|
|
2912
|
+
return `The "${binary}" program uses "SYMTAB", which gawk reads as every global by name, "ENVIRON" and "ARGV" included. Name the variable you need directly.`;
|
|
2895
2913
|
case "extension":
|
|
2896
2914
|
return `The "${binary}" program uses an "@" form, which loads an extension, includes a source file or calls a function by name \u2014 none of which this classifier can inspect. ${INSPECT_OR_TASK}`;
|
|
2897
2915
|
default:
|
|
@@ -2933,6 +2951,7 @@ function awkProgramHazard(program) {
|
|
|
2933
2951
|
let slash = "regex";
|
|
2934
2952
|
let depth = 0;
|
|
2935
2953
|
let printDepth;
|
|
2954
|
+
let getlineDepth;
|
|
2936
2955
|
let continues = false;
|
|
2937
2956
|
const readRegex = () => {
|
|
2938
2957
|
i++;
|
|
@@ -2999,7 +3018,10 @@ function awkProgramHazard(program) {
|
|
|
2999
3018
|
continue;
|
|
3000
3019
|
}
|
|
3001
3020
|
if (c === "\n" || c === ";") {
|
|
3002
|
-
if (c === ";" || !continues)
|
|
3021
|
+
if (c === ";" || !continues) {
|
|
3022
|
+
printDepth = void 0;
|
|
3023
|
+
getlineDepth = void 0;
|
|
3024
|
+
}
|
|
3003
3025
|
slash = "regex";
|
|
3004
3026
|
i++;
|
|
3005
3027
|
continue;
|
|
@@ -3050,6 +3072,12 @@ function awkProgramHazard(program) {
|
|
|
3050
3072
|
slash = "unclear";
|
|
3051
3073
|
continue;
|
|
3052
3074
|
}
|
|
3075
|
+
if (c === "<") {
|
|
3076
|
+
if (getlineDepth !== void 0 && depth >= getlineDepth) return "read";
|
|
3077
|
+
i++;
|
|
3078
|
+
slash = "unclear";
|
|
3079
|
+
continue;
|
|
3080
|
+
}
|
|
3053
3081
|
if (c === "(" || c === "[") {
|
|
3054
3082
|
depth++;
|
|
3055
3083
|
i++;
|
|
@@ -3058,12 +3086,16 @@ function awkProgramHazard(program) {
|
|
|
3058
3086
|
}
|
|
3059
3087
|
if (c === ")" || c === "]") {
|
|
3060
3088
|
depth--;
|
|
3089
|
+
if (getlineDepth !== void 0 && depth < getlineDepth) getlineDepth = void 0;
|
|
3061
3090
|
i++;
|
|
3062
3091
|
slash = "divide";
|
|
3063
3092
|
continue;
|
|
3064
3093
|
}
|
|
3065
3094
|
if (c === "{" || c === "}") {
|
|
3066
|
-
if (c === "}")
|
|
3095
|
+
if (c === "}") {
|
|
3096
|
+
printDepth = void 0;
|
|
3097
|
+
getlineDepth = void 0;
|
|
3098
|
+
}
|
|
3067
3099
|
i++;
|
|
3068
3100
|
slash = "regex";
|
|
3069
3101
|
continue;
|
|
@@ -3084,6 +3116,10 @@ function awkProgramHazard(program) {
|
|
|
3084
3116
|
while (i < n && /[A-Za-z0-9_.]/.test(program[i])) i++;
|
|
3085
3117
|
const word = program.slice(start, i);
|
|
3086
3118
|
if (word === "system") return "exec";
|
|
3119
|
+
if (word === "ENVIRON") return "environment";
|
|
3120
|
+
if (word === "ARGV") return "read";
|
|
3121
|
+
if (word === "SYMTAB") return "symbol-table";
|
|
3122
|
+
if (word === "getline") getlineDepth = depth;
|
|
3087
3123
|
if (word === "print" || word === "printf") printDepth = depth;
|
|
3088
3124
|
slash = AWK_KEYWORDS.includes(word) ? "unclear" : "divide";
|
|
3089
3125
|
continue;
|
|
@@ -3970,6 +4006,36 @@ var GIT_READONLY_SUBCOMMANDS = [
|
|
|
3970
4006
|
"whatchanged",
|
|
3971
4007
|
"cat-file"
|
|
3972
4008
|
];
|
|
4009
|
+
var onlyAction = (...reads) => (after) => after[0] === void 0 || reads.includes(after[0]);
|
|
4010
|
+
var GIT_PROMPTABLE = {
|
|
4011
|
+
"ls-remote": () => true,
|
|
4012
|
+
"for-each-ref": () => true,
|
|
4013
|
+
"show-ref": () => true,
|
|
4014
|
+
"name-rev": () => true,
|
|
4015
|
+
"merge-base": () => true,
|
|
4016
|
+
"count-objects": () => true,
|
|
4017
|
+
"cherry": () => true,
|
|
4018
|
+
"range-diff": () => true,
|
|
4019
|
+
"diff-tree": () => true,
|
|
4020
|
+
"diff-files": () => true,
|
|
4021
|
+
"diff-index": () => true,
|
|
4022
|
+
"show-branch": () => true,
|
|
4023
|
+
"annotate": () => true,
|
|
4024
|
+
"check-ignore": () => true,
|
|
4025
|
+
"check-attr": () => true,
|
|
4026
|
+
"check-ref-format": () => true,
|
|
4027
|
+
"check-mailmap": () => true,
|
|
4028
|
+
"var": () => true,
|
|
4029
|
+
"version": () => true,
|
|
4030
|
+
"worktree": onlyAction("list"),
|
|
4031
|
+
"notes": onlyAction("list", "show"),
|
|
4032
|
+
"bisect": onlyAction("log"),
|
|
4033
|
+
"sparse-checkout": onlyAction("list"),
|
|
4034
|
+
"reflog": onlyAction("show", "exists"),
|
|
4035
|
+
"symbolic-ref": (after) => after.filter((a) => !a.startsWith("-")).length <= 1,
|
|
4036
|
+
// `-w` writes the object; git's shared flag set keeps it for `git diff -w`.
|
|
4037
|
+
"hash-object": (after) => !after.some((a) => /^-[^-]*w/.test(a))
|
|
4038
|
+
};
|
|
3973
4039
|
var REFUSED_COMMANDS = [
|
|
3974
4040
|
"rm",
|
|
3975
4041
|
"rmdir",
|
|
@@ -4061,7 +4127,28 @@ var REFUSED_SUBCOMMANDS = {
|
|
|
4061
4127
|
"clone",
|
|
4062
4128
|
"fetch",
|
|
4063
4129
|
"pull",
|
|
4064
|
-
"submodule"
|
|
4130
|
+
"submodule",
|
|
4131
|
+
"add",
|
|
4132
|
+
"rm",
|
|
4133
|
+
"mv",
|
|
4134
|
+
"format-patch",
|
|
4135
|
+
"replace",
|
|
4136
|
+
"mergetool",
|
|
4137
|
+
"repack",
|
|
4138
|
+
"update-index",
|
|
4139
|
+
"read-tree",
|
|
4140
|
+
"checkout-index",
|
|
4141
|
+
"merge-file",
|
|
4142
|
+
"bundle",
|
|
4143
|
+
"commit-tree",
|
|
4144
|
+
"write-tree",
|
|
4145
|
+
"mktree",
|
|
4146
|
+
"mktag",
|
|
4147
|
+
"unpack-objects",
|
|
4148
|
+
"fast-import",
|
|
4149
|
+
"pack-refs",
|
|
4150
|
+
"prune-packed",
|
|
4151
|
+
"maintenance"
|
|
4065
4152
|
],
|
|
4066
4153
|
npm: ["publish", "unpublish", "deprecate", "owner", "access", "token", "login", "logout"],
|
|
4067
4154
|
yarn: ["publish", "npm"],
|
|
@@ -4271,7 +4358,7 @@ function pathRefs(command, opts = {}) {
|
|
|
4271
4358
|
return lexAll(command.trim(), dialect, opts.cdpathSet).segments.flatMap((seg) => {
|
|
4272
4359
|
const patterns = patternArgs(seg);
|
|
4273
4360
|
const cwd = seg.cwd ?? [];
|
|
4274
|
-
return [...seg.args.filter((_, i) => !patterns.has(i)), ...seg.inputs].flatMap(pathIn).map((
|
|
4361
|
+
return [...seg.args.filter((_, i) => !patterns.has(i)), ...seg.inputs].flatMap(pathIn).map((path49) => ({ path: path49, cwd }));
|
|
4275
4362
|
});
|
|
4276
4363
|
}
|
|
4277
4364
|
function excludedBy(command, allow, opts = {}) {
|
|
@@ -4378,7 +4465,9 @@ function patternArgs(seg) {
|
|
|
4378
4465
|
var SCOPE_LEAD_ARGS = 2;
|
|
4379
4466
|
function scopeFor(seg) {
|
|
4380
4467
|
const name = seg.binaryPath ?? seg.binary;
|
|
4381
|
-
|
|
4468
|
+
const program = programName(seg.binary);
|
|
4469
|
+
if (!MULTIPLEXERS.includes(program)) return name;
|
|
4470
|
+
if (program === "git") return gitScope(seg, name);
|
|
4382
4471
|
const lead = [];
|
|
4383
4472
|
for (const arg of seg.args) {
|
|
4384
4473
|
if (arg.startsWith("-")) break;
|
|
@@ -4387,6 +4476,14 @@ function scopeFor(seg) {
|
|
|
4387
4476
|
}
|
|
4388
4477
|
return [name, ...lead].join(" ");
|
|
4389
4478
|
}
|
|
4479
|
+
function gitScope(seg, name) {
|
|
4480
|
+
const scan = scanFlags({ ...seg, binary: "git" });
|
|
4481
|
+
if (!scan || scan.unknown || scan.operands[0] === "ls-remote") return [name, ...seg.args].map(scopeWord).join(" ");
|
|
4482
|
+
return [name, ...scan.operands.slice(0, SCOPE_LEAD_ARGS)].join(" ");
|
|
4483
|
+
}
|
|
4484
|
+
function scopeWord(word) {
|
|
4485
|
+
return word === "" || /[\s"'\\]/.test(word) ? JSON.stringify(word) : word;
|
|
4486
|
+
}
|
|
4390
4487
|
function climbsByGlob(seg) {
|
|
4391
4488
|
if (!seg.globbed) return false;
|
|
4392
4489
|
const patterns = patternArgs(seg);
|
|
@@ -4408,6 +4505,27 @@ function swallowsRefName(arg) {
|
|
|
4408
4505
|
const values = FLAG_POLICY.git.values ?? [];
|
|
4409
4506
|
return [...arg.slice(1)].some((c) => c !== "n" && values.includes(`-${c}`));
|
|
4410
4507
|
}
|
|
4508
|
+
function gitArgsAfterSubcommand(seg) {
|
|
4509
|
+
const at = scanFlags(seg)?.operandAt[0];
|
|
4510
|
+
return at === void 0 ? [] : seg.args.slice(at + 1);
|
|
4511
|
+
}
|
|
4512
|
+
var REMOTE_NAME = /^[A-Za-z0-9_][A-Za-z0-9_.-]*$/;
|
|
4513
|
+
function namesConfiguredRemote(seg, after) {
|
|
4514
|
+
const destination2 = scanFlags({ ...seg, args: after })?.operands[0];
|
|
4515
|
+
return destination2 === void 0 || REMOTE_NAME.test(destination2);
|
|
4516
|
+
}
|
|
4517
|
+
function programName(binary) {
|
|
4518
|
+
return binary.toLowerCase().replace(/\.exe$/, "");
|
|
4519
|
+
}
|
|
4520
|
+
function isRemoteListing(seg) {
|
|
4521
|
+
const git2 = { ...seg, binary: programName(seg.binary) };
|
|
4522
|
+
return git2.binary === "git" && subcommandOf(git2) === "ls-remote";
|
|
4523
|
+
}
|
|
4524
|
+
function remoteListingTexts(seg) {
|
|
4525
|
+
if (!isRemoteListing(seg)) return [];
|
|
4526
|
+
const git2 = { ...seg, binary: "git" };
|
|
4527
|
+
return scanFlags({ ...git2, args: gitArgsAfterSubcommand(git2) })?.operands ?? [];
|
|
4528
|
+
}
|
|
4411
4529
|
function refusalFor(seg, trustedResolves) {
|
|
4412
4530
|
const web = webCommand(seg, trustedResolves);
|
|
4413
4531
|
if (web?.reason) return web.reason;
|
|
@@ -4445,8 +4563,24 @@ function refusalFor(seg, trustedResolves) {
|
|
|
4445
4563
|
}
|
|
4446
4564
|
if (seg.binary === "git") {
|
|
4447
4565
|
const sub = subcommandOf(seg);
|
|
4566
|
+
const after = gitArgsAfterSubcommand(seg);
|
|
4567
|
+
if (sub !== void 0 && !GIT_READONLY_SUBCOMMANDS.includes(sub)) {
|
|
4568
|
+
const reads = GIT_PROMPTABLE[sub];
|
|
4569
|
+
if (!reads) {
|
|
4570
|
+
return `"git ${sub}" is not a git subcommand known only to read, so it may write the repository. You are a read-only planner: ${EFFECT_ROUTE}`;
|
|
4571
|
+
}
|
|
4572
|
+
if (!reads(after)) {
|
|
4573
|
+
return `"git ${[sub, ...after].join(" ")}" is not a read-only form of "git ${sub}". You are a read-only planner: ${EFFECT_ROUTE}`;
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
if (sub === "ls-remote" && !namesConfiguredRemote(seg, after)) {
|
|
4577
|
+
return `"git ls-remote" given a URL, an address or a path reaches it through git's own transport, which resolves the host itself and follows a redirect, so the origin approval, address guard and pinned connection a web read gets (ADR-0027) cannot hold for it. Name a remote the repository configures, such as "git ls-remote origin", or fetch the page with your web fetch tool.`;
|
|
4578
|
+
}
|
|
4579
|
+
if (sub === "ls-remote" && (seg.expandable || seg.globbed || after.some((a) => a.includes("~")))) {
|
|
4580
|
+
return `The shell rewrites part of this "git ls-remote" before git sees it (a variable, substitution, glob or "~"), so what it sends the remote, and which repository it lists, cannot be checked for a secret or approved. Write the remote and patterns literally, in single quotes where they hold a "*", and re-run.`;
|
|
4581
|
+
}
|
|
4448
4582
|
if (sub === "branch" || sub === "tag") {
|
|
4449
|
-
const flag2 = seg.args.find((a) => ["-D", "-d", "-m", "-M", "--delete", "--move"].includes(a)) ??
|
|
4583
|
+
const flag2 = seg.args.find((a) => ["-D", "-d", "-m", "-M", "--delete", "--move"].includes(a)) ?? after.find(swallowsRefName);
|
|
4450
4584
|
if (flag2) {
|
|
4451
4585
|
return `"git ${sub} ${flag2}" mutates refs. You are a read-only planner: ${EFFECT_ROUTE}`;
|
|
4452
4586
|
}
|
|
@@ -4585,10 +4719,10 @@ function classifyCommand(command, opts = {}) {
|
|
|
4585
4719
|
return { tier: "refuse", scope: "", reason: `curl and wget are refused in Full and allowlist mode. ${web.effect ? EFFECT_REFUSAL_HINT : READ_REFUSAL_HINT}` };
|
|
4586
4720
|
}
|
|
4587
4721
|
const allow = opts.allow;
|
|
4588
|
-
if (web && allow && web.urls.some((url) => scopeExcluded(allow, `${seg.binary
|
|
4722
|
+
if (web && allow && web.urls.some((url) => scopeExcluded(allow, `${programName(seg.binary)} ${url.origin}/*`))) {
|
|
4589
4723
|
return { tier: "refuse", scope: "", reason: `The web origin is excluded by the planner allowlist. ${READ_REFUSAL_HINT}` };
|
|
4590
4724
|
}
|
|
4591
|
-
const reason = refusalFor({ ...seg, binary: seg.binary
|
|
4725
|
+
const reason = refusalFor({ ...seg, binary: programName(seg.binary) }, opts.trustedWebResolves);
|
|
4592
4726
|
if (reason) return { tier: "refuse", scope: "", reason: withWrapperNote(reason, wrappers) };
|
|
4593
4727
|
if (runner && !XARGS_TARGETS.includes(seg.binary)) {
|
|
4594
4728
|
return {
|
|
@@ -4598,23 +4732,24 @@ function classifyCommand(command, opts = {}) {
|
|
|
4598
4732
|
};
|
|
4599
4733
|
}
|
|
4600
4734
|
}
|
|
4601
|
-
if (unwrapped.some(({ seg }) => seg.binary
|
|
4735
|
+
if (unwrapped.some(({ seg }) => programName(seg.binary) === "wget") && wgetVersionCommands(command, opts.dialect).includes(void 0)) {
|
|
4602
4736
|
return { tier: "refuse", scope: "", reason: `The wget executable cannot be checked safely in this working directory. ${READ_REFUSAL_HINT}` };
|
|
4603
4737
|
}
|
|
4604
4738
|
const nonAuto = unwrapped.filter((u) => u.runner !== void 0 || u.wrappers.some((w) => w !== w.toLowerCase()) || !isAuto(u.seg, opts));
|
|
4605
4739
|
if (nonAuto.length === 0) return { tier: "auto", scope: "" };
|
|
4740
|
+
const changesDirectory = unwrapped.some(({ seg }) => seg.binary.toLowerCase() === "cd");
|
|
4606
4741
|
const lineScoped = nonAuto.some((u) => {
|
|
4607
4742
|
const name = u.seg.binary.toLowerCase();
|
|
4608
|
-
return name === "cd" || name !== "command" && SHELL_STATE_COMMANDS.has(name) || climbsByGlob(u.seg);
|
|
4743
|
+
return name === "cd" || name !== "command" && SHELL_STATE_COMMANDS.has(name) || climbsByGlob(u.seg) || isRemoteListing(u.seg) && (changesDirectory || u.wrappers.length > 0 || nonAuto.length > 1);
|
|
4609
4744
|
});
|
|
4610
4745
|
const webUrls = nonAuto.flatMap((u) => webCommand(u.seg, opts.trustedWebResolves)?.urls ?? []);
|
|
4611
|
-
const
|
|
4746
|
+
const outboundTexts = nonAuto.flatMap((u) => webCommand(u.seg, opts.trustedWebResolves)?.outboundTexts ?? remoteListingTexts(u.seg));
|
|
4612
4747
|
if (lineScoped && webUrls.length === 0) {
|
|
4613
|
-
return { tier: "ask", scope: trimmed };
|
|
4748
|
+
return { tier: "ask", scope: trimmed, ...outboundTexts.length ? { outboundTexts } : {} };
|
|
4614
4749
|
}
|
|
4615
4750
|
const scopes = [...new Set(nonAuto.flatMap((u) => {
|
|
4616
4751
|
const web = webCommand(u.seg, opts.trustedWebResolves);
|
|
4617
|
-
return web ? web.urls.map((url) => `${u.seg.binary
|
|
4752
|
+
return web ? web.urls.map((url) => `${programName(u.seg.binary)} ${url.origin}/*`) : [lineScoped ? trimmed : u.runner ? `${u.runner} ${scopeFor(u.seg)}` : scopeFor(u.seg)];
|
|
4618
4753
|
}))].sort();
|
|
4619
4754
|
const scope = scopes.join(" + ");
|
|
4620
4755
|
const perInvocation = nonAuto.some((u) => webCommand(u.seg, opts.trustedWebResolves)?.perInvocation);
|
|
@@ -4622,7 +4757,8 @@ function classifyCommand(command, opts = {}) {
|
|
|
4622
4757
|
return {
|
|
4623
4758
|
tier: "ask",
|
|
4624
4759
|
scope,
|
|
4625
|
-
...webUrls.length ? { webUrls
|
|
4760
|
+
...webUrls.length ? { webUrls } : {},
|
|
4761
|
+
...outboundTexts.length ? { outboundTexts } : {},
|
|
4626
4762
|
...scopes.length > 1 && webUrls.length ? { scopes } : {},
|
|
4627
4763
|
...perInvocation ? { perInvocation } : {},
|
|
4628
4764
|
...allowedBy ? { allowedBy } : {}
|
|
@@ -6176,7 +6312,7 @@ ${TASK_CONTINUE}`);
|
|
|
6176
6312
|
command = disableWebCommandStartup(command, this.researchShell.dialect);
|
|
6177
6313
|
const { approval, root } = site;
|
|
6178
6314
|
const allow = approval.allowlist?.();
|
|
6179
|
-
const { tier, scope, reason, allowedBy, perInvocation, scopes: commandScopes, webUrls,
|
|
6315
|
+
const { tier, scope, reason, allowedBy, perInvocation, scopes: commandScopes, webUrls, outboundTexts } = classifyCommand(command, {
|
|
6180
6316
|
...this.policyOptions(),
|
|
6181
6317
|
...approval.commandPolicy?.(),
|
|
6182
6318
|
...allow ? { allow } : {}
|
|
@@ -6201,8 +6337,9 @@ ${TASK_CONTINUE}`);
|
|
|
6201
6337
|
if (site.task?.sealed) return this.runSealed(site, command, request, "after", signal);
|
|
6202
6338
|
if (tier === "refuse") return this.sealedInstead(site, command, request, refusal(`Command refused: ${reason}${site.task ? `
|
|
6203
6339
|
${TASK_UNSEALED} ${TASK_CONTINUE}` : ""}`), signal);
|
|
6204
|
-
|
|
6205
|
-
|
|
6340
|
+
const outbound = [...outboundTexts ?? [], ...(webUrls ?? []).map((url) => url.href)];
|
|
6341
|
+
if (outbound.length && carriesSecret(outbound, [...this.outboundSecrets(), ...sensitiveEnvValues(process.env)])) {
|
|
6342
|
+
return refusal(webUrls?.length ? "Command refused: a web command contains what looks like a secret. Fetch the page with your web fetch tool, without credentials." : "Command refused: what this command sends over the network contains what looks like a secret. Run it without the value.");
|
|
6206
6343
|
}
|
|
6207
6344
|
const vettedAddresses = /* @__PURE__ */ new Map();
|
|
6208
6345
|
for (const url of webUrls ?? []) {
|
|
@@ -6305,7 +6442,7 @@ ${outcome.output}${outcome.success ? "" : `
|
|
|
6305
6442
|
}
|
|
6306
6443
|
let shell;
|
|
6307
6444
|
try {
|
|
6308
|
-
const sealed = site.task?.gitBefore ? { ...site.sealed, gitEntries: site.task.gitBefore.entries.map(({ path:
|
|
6445
|
+
const sealed = site.task?.gitBefore ? { ...site.sealed, gitEntries: site.task.gitBefore.entries.map(({ path: path49, directory }) => ({ path: path49, directory })) } : site.sealed;
|
|
6309
6446
|
shell = await site.sandbox.launchSealed(this.researchShell, sealed);
|
|
6310
6447
|
} catch (err) {
|
|
6311
6448
|
return { refused: refusal(`Command not run: its sealed sandbox could not be prepared (${err instanceof Error ? err.message : String(err)}).`) };
|
|
@@ -7984,6 +8121,7 @@ function enabledRunners(cfg) {
|
|
|
7984
8121
|
}
|
|
7985
8122
|
|
|
7986
8123
|
// src/plugins/builtin/claudeModels.ts
|
|
8124
|
+
import * as path12 from "path";
|
|
7987
8125
|
var ANTHROPIC_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
7988
8126
|
var ANTHROPIC_VARIANT_LABELS = {
|
|
7989
8127
|
adaptive: "Adaptive",
|
|
@@ -8077,6 +8215,53 @@ function parseClaudeHelp(stdout) {
|
|
|
8077
8215
|
}
|
|
8078
8216
|
return models;
|
|
8079
8217
|
}
|
|
8218
|
+
function claudeConfigDir(env) {
|
|
8219
|
+
return env.CLAUDE_CONFIG_DIR?.trim() || "~/.claude";
|
|
8220
|
+
}
|
|
8221
|
+
function claudeManagedSettingsDir(platform = process.platform) {
|
|
8222
|
+
if (platform === "darwin") return "/Library/Application Support/ClaudeCode";
|
|
8223
|
+
if (platform === "win32") return "C:\\Program Files\\ClaudeCode";
|
|
8224
|
+
return "/etc/claude-code";
|
|
8225
|
+
}
|
|
8226
|
+
function readClaudeSettingsModels(files, platform = process.platform) {
|
|
8227
|
+
const managed = claudeManagedSettingsDir(platform);
|
|
8228
|
+
const join45 = platform === "win32" ? path12.win32.join : path12.posix.join;
|
|
8229
|
+
const dropIns = files.list(join45(managed, "managed-settings.d")).filter((name) => name.endsWith(".json") && !name.startsWith(".")).sort().map((name) => join45(managed, "managed-settings.d", name));
|
|
8230
|
+
const ranked = [...dropIns.reverse(), join45(managed, "managed-settings.json"), join45(claudeConfigDir(files.env), "settings.json")];
|
|
8231
|
+
for (const file of ranked) {
|
|
8232
|
+
const content = files.read(file);
|
|
8233
|
+
const lineup = content === null ? null : parseClaudeSettingsModels(content);
|
|
8234
|
+
if (lineup) return lineup;
|
|
8235
|
+
}
|
|
8236
|
+
return [];
|
|
8237
|
+
}
|
|
8238
|
+
function parseClaudeSettingsModels(content) {
|
|
8239
|
+
let parsed;
|
|
8240
|
+
try {
|
|
8241
|
+
parsed = JSON.parse(content);
|
|
8242
|
+
} catch {
|
|
8243
|
+
return null;
|
|
8244
|
+
}
|
|
8245
|
+
const picker = parsed && typeof parsed === "object" ? parsed.modelPicker : void 0;
|
|
8246
|
+
const options = picker && typeof picker === "object" ? picker.options : void 0;
|
|
8247
|
+
if (!Array.isArray(options)) return null;
|
|
8248
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8249
|
+
const models = [];
|
|
8250
|
+
for (const row of options) {
|
|
8251
|
+
if (!row || typeof row !== "object") continue;
|
|
8252
|
+
const { model: raw, label } = row;
|
|
8253
|
+
const model = typeof raw === "string" ? raw.trim() : "";
|
|
8254
|
+
if (!model || seen.has(model)) continue;
|
|
8255
|
+
seen.add(model);
|
|
8256
|
+
models.push({
|
|
8257
|
+
modelId: model,
|
|
8258
|
+
modelLabel: typeof label === "string" && label.trim() ? label.trim() : model,
|
|
8259
|
+
runnerProvider: model.includes("/") ? model.split("/")[0] : void 0,
|
|
8260
|
+
variants: []
|
|
8261
|
+
});
|
|
8262
|
+
}
|
|
8263
|
+
return models;
|
|
8264
|
+
}
|
|
8080
8265
|
|
|
8081
8266
|
// src/plugins/builtin/claude-code.manifest.ts
|
|
8082
8267
|
var TURN_NOT_LOGGED_IN = /not logged in\s*·\s*please run \/login/i;
|
|
@@ -8115,18 +8300,24 @@ var CLAUDE_CODE_MANIFEST = {
|
|
|
8115
8300
|
// 3. canonicalAliases — stable --model contracts (opus/sonnet/haiku/fable)
|
|
8116
8301
|
// merged into any successful result to fill gaps, and used as the full
|
|
8117
8302
|
// last-resort list when both API and CLI are unavailable.
|
|
8303
|
+
//
|
|
8304
|
+
// Whichever source answers, the models the user added to Claude's own
|
|
8305
|
+
// picker (`modelPicker.options` in its managed or user settings, usually
|
|
8306
|
+
// gateway IDs) are appended (settingsModels): the CLI accepts them as
|
|
8307
|
+
// `--model` values though no catalog above lists them.
|
|
8118
8308
|
apiDiscovery: {
|
|
8119
8309
|
url: "https://api.anthropic.com/v1/models?limit=100",
|
|
8120
8310
|
headers: { "anthropic-version": "2023-06-01" },
|
|
8121
8311
|
auth: [
|
|
8122
8312
|
{ type: "env", varName: "ANTHROPIC_API_KEY", header: "X-Api-Key" },
|
|
8123
|
-
{ type: "file", path:
|
|
8313
|
+
{ type: "file", path: (env) => `${claudeConfigDir(env)}/.credentials.json`, jsonPath: "claudeAiOauth.accessToken", header: "Authorization", prefix: "Bearer " }
|
|
8124
8314
|
],
|
|
8125
8315
|
parser: parseAnthropicModels
|
|
8126
8316
|
},
|
|
8127
8317
|
discoveryCommands: [
|
|
8128
8318
|
{ command: "claude", args: ["--help"], parser: parseClaudeHelp }
|
|
8129
8319
|
],
|
|
8320
|
+
settingsModels: (files) => readClaudeSettingsModels(files),
|
|
8130
8321
|
canonicalAliases: [
|
|
8131
8322
|
{ modelId: "opus", modelLabel: "Opus" },
|
|
8132
8323
|
{ modelId: "sonnet", modelLabel: "Sonnet" },
|
|
@@ -8184,7 +8375,7 @@ import { join as join11, resolve as resolve4 } from "path";
|
|
|
8184
8375
|
|
|
8185
8376
|
// src/services/harness/repoConfig.ts
|
|
8186
8377
|
import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
|
|
8187
|
-
import * as
|
|
8378
|
+
import * as path13 from "path";
|
|
8188
8379
|
|
|
8189
8380
|
// src/services/mcp/OrdewellMcpServer.ts
|
|
8190
8381
|
import { createServer } from "http";
|
|
@@ -8897,12 +9088,12 @@ function checkpointReply(answer2) {
|
|
|
8897
9088
|
return { text: `The checkpoint was withdrawn: ${answer2.why}`, isError: true };
|
|
8898
9089
|
}
|
|
8899
9090
|
}
|
|
8900
|
-
function fieldPath(
|
|
8901
|
-
return
|
|
9091
|
+
function fieldPath(path49) {
|
|
9092
|
+
return path49.reduce((text4, part) => typeof part === "number" ? `${text4}[${part}]` : text4 ? `${text4}.${String(part)}` : String(part), "");
|
|
8902
9093
|
}
|
|
8903
|
-
function valueAt(value,
|
|
9094
|
+
function valueAt(value, path49) {
|
|
8904
9095
|
let current = value;
|
|
8905
|
-
for (const part of
|
|
9096
|
+
for (const part of path49) {
|
|
8906
9097
|
if (typeof part === "number") {
|
|
8907
9098
|
if (!Array.isArray(current)) return void 0;
|
|
8908
9099
|
current = current[part];
|
|
@@ -8924,7 +9115,7 @@ function boundedRefusal(issue, args) {
|
|
|
8924
9115
|
var LEAKED_PARAMETER = /<\/parameter>|<parameter\s+name\s*=\s*"([^"]*)">/g;
|
|
8925
9116
|
function leakedMarkupIn(value) {
|
|
8926
9117
|
const found = [];
|
|
8927
|
-
const walk = (node,
|
|
9118
|
+
const walk = (node, path49) => {
|
|
8928
9119
|
if (typeof node === "string") {
|
|
8929
9120
|
const names = [];
|
|
8930
9121
|
let closing = false;
|
|
@@ -8932,25 +9123,25 @@ function leakedMarkupIn(value) {
|
|
|
8932
9123
|
if (match[1] !== void 0) names.push(match[1]);
|
|
8933
9124
|
else closing = true;
|
|
8934
9125
|
}
|
|
8935
|
-
if (names.length > 0 || closing) found.push({ path:
|
|
9126
|
+
if (names.length > 0 || closing) found.push({ path: path49, names: [...new Set(names)], closing });
|
|
8936
9127
|
return;
|
|
8937
9128
|
}
|
|
8938
9129
|
if (Array.isArray(node)) {
|
|
8939
|
-
node.forEach((item, i) => walk(item, `${
|
|
9130
|
+
node.forEach((item, i) => walk(item, `${path49}[${i}]`));
|
|
8940
9131
|
return;
|
|
8941
9132
|
}
|
|
8942
9133
|
if (node !== null && typeof node === "object") {
|
|
8943
|
-
for (const [key, item] of Object.entries(node)) walk(item,
|
|
9134
|
+
for (const [key, item] of Object.entries(node)) walk(item, path49 ? `${path49}.${key}` : key);
|
|
8944
9135
|
}
|
|
8945
9136
|
};
|
|
8946
9137
|
walk(value, "");
|
|
8947
9138
|
return found;
|
|
8948
9139
|
}
|
|
8949
9140
|
function markupRefusal(leaks) {
|
|
8950
|
-
return leaks.map(({ path:
|
|
9141
|
+
return leaks.map(({ path: path49, names, closing }) => {
|
|
8951
9142
|
const tags = [...names.map((n) => `<parameter name="${n}">`), ...closing ? ["</parameter>"] : []];
|
|
8952
|
-
const guide = names.length > 0 ? ` The parameters ${names.map((n) => JSON.stringify(n)).join(", ")} are inside it; pass them as separate arguments, not inside ${
|
|
8953
|
-
return `${
|
|
9143
|
+
const guide = names.length > 0 ? ` The parameters ${names.map((n) => JSON.stringify(n)).join(", ")} are inside it; pass them as separate arguments, not inside ${path49}.` : " Pass each argument separately, not inside one string.";
|
|
9144
|
+
return `${path49} contains leaked tool-call markup: ${tags.join(", ")}.${guide}`;
|
|
8954
9145
|
}).join("\n");
|
|
8955
9146
|
}
|
|
8956
9147
|
function invalidArguments(name, error, args) {
|
|
@@ -9415,9 +9606,9 @@ function mcpClientConfig(credential) {
|
|
|
9415
9606
|
}
|
|
9416
9607
|
function ownerOnlyConfigFile(fileName, contents) {
|
|
9417
9608
|
const dir = mkdtempSync2(join9(tmpdir2(), "ordewell-mcp-"));
|
|
9418
|
-
const
|
|
9419
|
-
writeFileSync(
|
|
9420
|
-
return { path:
|
|
9609
|
+
const path49 = join9(dir, fileName);
|
|
9610
|
+
writeFileSync(path49, contents, { mode: 384 });
|
|
9611
|
+
return { path: path49, remove: () => rmSync2(dir, { recursive: true, force: true }) };
|
|
9421
9612
|
}
|
|
9422
9613
|
|
|
9423
9614
|
// src/services/harness/repoConfig.ts
|
|
@@ -9440,19 +9631,19 @@ function onDisk(target) {
|
|
|
9440
9631
|
try {
|
|
9441
9632
|
return realpathSync2.native(target);
|
|
9442
9633
|
} catch {
|
|
9443
|
-
return
|
|
9634
|
+
return path13.resolve(target);
|
|
9444
9635
|
}
|
|
9445
9636
|
}
|
|
9446
9637
|
function insideOnDisk(root, target) {
|
|
9447
|
-
const rel =
|
|
9448
|
-
return rel === "" || !rel.startsWith("..") && !
|
|
9638
|
+
const rel = path13.relative(onDisk(root), onDisk(target));
|
|
9639
|
+
return rel === "" || !rel.startsWith("..") && !path13.isAbsolute(rel);
|
|
9449
9640
|
}
|
|
9450
9641
|
function repoLevels(workspace) {
|
|
9451
9642
|
const levels = [];
|
|
9452
|
-
for (let dir =
|
|
9643
|
+
for (let dir = path13.resolve(workspace); ; dir = path13.dirname(dir)) {
|
|
9453
9644
|
levels.unshift(dir);
|
|
9454
|
-
if (existsSync3(
|
|
9455
|
-
if (
|
|
9645
|
+
if (existsSync3(path13.join(dir, ".git"))) return levels;
|
|
9646
|
+
if (path13.dirname(dir) === dir) return [path13.resolve(workspace)];
|
|
9456
9647
|
}
|
|
9457
9648
|
}
|
|
9458
9649
|
function repoRoot(workspace) {
|
|
@@ -9546,33 +9737,33 @@ var TomlReader = class {
|
|
|
9546
9737
|
if (this.i >= this.src.length) return root;
|
|
9547
9738
|
if (this.src.startsWith("[[", this.i)) {
|
|
9548
9739
|
this.i += 2;
|
|
9549
|
-
const
|
|
9740
|
+
const path49 = this.key();
|
|
9550
9741
|
this.expect("]]");
|
|
9551
|
-
const parent = this.tableAt(root,
|
|
9552
|
-
const last =
|
|
9742
|
+
const parent = this.tableAt(root, path49.slice(0, -1));
|
|
9743
|
+
const last = path49[path49.length - 1];
|
|
9553
9744
|
const list = Object.hasOwn(parent, last) ? parent[last] : parent[last] = [];
|
|
9554
|
-
if (!Array.isArray(list)) throw this.fail(`${
|
|
9745
|
+
if (!Array.isArray(list)) throw this.fail(`${path49.join(".")} is not an array of tables`);
|
|
9555
9746
|
table = newTable();
|
|
9556
9747
|
list.push(table);
|
|
9557
9748
|
} else if (this.src[this.i] === "[") {
|
|
9558
9749
|
this.i += 1;
|
|
9559
|
-
const
|
|
9750
|
+
const path49 = this.key();
|
|
9560
9751
|
this.expect("]");
|
|
9561
|
-
table = this.tableAt(root,
|
|
9752
|
+
table = this.tableAt(root, path49);
|
|
9562
9753
|
} else {
|
|
9563
|
-
const
|
|
9754
|
+
const path49 = this.key();
|
|
9564
9755
|
this.skipBlank(false);
|
|
9565
9756
|
this.expect("=");
|
|
9566
9757
|
this.skipBlank(false);
|
|
9567
|
-
const owner = this.tableAt(table,
|
|
9568
|
-
owner[
|
|
9758
|
+
const owner = this.tableAt(table, path49.slice(0, -1));
|
|
9759
|
+
owner[path49[path49.length - 1]] = this.value();
|
|
9569
9760
|
}
|
|
9570
9761
|
this.endOfLine();
|
|
9571
9762
|
}
|
|
9572
9763
|
}
|
|
9573
|
-
tableAt(from,
|
|
9764
|
+
tableAt(from, path49) {
|
|
9574
9765
|
let table = from;
|
|
9575
|
-
for (const part of
|
|
9766
|
+
for (const part of path49) {
|
|
9576
9767
|
const next = Object.hasOwn(table, part) ? table[part] : void 0;
|
|
9577
9768
|
if (next === void 0) table = table[part] = newTable();
|
|
9578
9769
|
else if (isRecord(next)) table = next;
|
|
@@ -9641,10 +9832,10 @@ var TomlReader = class {
|
|
|
9641
9832
|
this.i++;
|
|
9642
9833
|
return table;
|
|
9643
9834
|
}
|
|
9644
|
-
const
|
|
9835
|
+
const path49 = this.key();
|
|
9645
9836
|
this.expect("=");
|
|
9646
9837
|
this.skipBlank(false);
|
|
9647
|
-
this.tableAt(table,
|
|
9838
|
+
this.tableAt(table, path49.slice(0, -1))[path49[path49.length - 1]] = this.value();
|
|
9648
9839
|
this.skipBlank(true);
|
|
9649
9840
|
if (this.src[this.i] === ",") this.i++;
|
|
9650
9841
|
else if (this.src[this.i] !== "}") throw this.fail("expected , or } in an inline table");
|
|
@@ -9789,7 +9980,8 @@ var CODEX_MANIFEST = {
|
|
|
9789
9980
|
appServer: {
|
|
9790
9981
|
command: "codex",
|
|
9791
9982
|
args: ["app-server"],
|
|
9792
|
-
|
|
9983
|
+
// CODEX_HOME moves all of Codex's own files from `~/.codex`.
|
|
9984
|
+
cacheFile: (env) => `${env.CODEX_HOME?.trim() || "~/.codex"}/models_cache.json`
|
|
9793
9985
|
},
|
|
9794
9986
|
canonicalAliases: [
|
|
9795
9987
|
{ modelId: "gpt-5.6-sol", modelLabel: "GPT-5.6-Sol" },
|
|
@@ -10018,8 +10210,7 @@ var LineBuffer = class {
|
|
|
10018
10210
|
// src/services/harness/ClaudeCodeAdapter.ts
|
|
10019
10211
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
10020
10212
|
import { realpathSync as realpathSync7 } from "fs";
|
|
10021
|
-
import * as
|
|
10022
|
-
import * as path20 from "path";
|
|
10213
|
+
import * as path22 from "path";
|
|
10023
10214
|
|
|
10024
10215
|
// src/plugins/resolveArgs.ts
|
|
10025
10216
|
function claudeThinkingArgs(effort) {
|
|
@@ -10057,14 +10248,29 @@ function resolveTaskRunnerFlags(manifest, ctx) {
|
|
|
10057
10248
|
return flags;
|
|
10058
10249
|
}
|
|
10059
10250
|
|
|
10251
|
+
// src/utils/runnerHome.ts
|
|
10252
|
+
import * as os5 from "os";
|
|
10253
|
+
function runnerHomeDir(env, platform = process.platform) {
|
|
10254
|
+
const name = platform === "win32" ? "USERPROFILE" : "HOME";
|
|
10255
|
+
const key = platform === "win32" ? Object.keys(env).filter((k) => k.toUpperCase() === name).pop() : name;
|
|
10256
|
+
const home = key === void 0 ? void 0 : env[key];
|
|
10257
|
+
return home ?? os5.homedir();
|
|
10258
|
+
}
|
|
10259
|
+
function expandRunnerHome(filePath, env = process.env) {
|
|
10260
|
+
if (!filePath.startsWith("~")) return filePath;
|
|
10261
|
+
const home = runnerHomeDir(env);
|
|
10262
|
+
return home ? home + filePath.slice(1) : null;
|
|
10263
|
+
}
|
|
10264
|
+
|
|
10060
10265
|
// src/services/harness/runnerProcess.ts
|
|
10266
|
+
import { Socket } from "net";
|
|
10061
10267
|
import { StringDecoder } from "string_decoder";
|
|
10062
10268
|
|
|
10063
10269
|
// src/utils/shellPath.ts
|
|
10064
10270
|
import { exec } from "child_process";
|
|
10065
10271
|
import { promisify } from "util";
|
|
10066
|
-
import * as
|
|
10067
|
-
import * as
|
|
10272
|
+
import * as os6 from "os";
|
|
10273
|
+
import * as path14 from "path";
|
|
10068
10274
|
var execAsync = promisify(exec);
|
|
10069
10275
|
var LOGIN_SHELL_TIMEOUT_MS = 5e3;
|
|
10070
10276
|
var PATH_MARKER = "__ORDEWELL_PATH__";
|
|
@@ -10073,42 +10279,42 @@ var defaultExec = async (command, options) => {
|
|
|
10073
10279
|
return { stdout: String(stdout) };
|
|
10074
10280
|
};
|
|
10075
10281
|
function posixBinDirs() {
|
|
10076
|
-
const home =
|
|
10282
|
+
const home = os6.homedir();
|
|
10077
10283
|
return [
|
|
10078
|
-
|
|
10079
|
-
|
|
10080
|
-
|
|
10081
|
-
|
|
10082
|
-
|
|
10083
|
-
|
|
10084
|
-
|
|
10284
|
+
path14.join(home, ".local", "bin"),
|
|
10285
|
+
path14.join(home, ".opencode", "bin"),
|
|
10286
|
+
path14.join(home, ".claude", "bin"),
|
|
10287
|
+
path14.join(home, ".bun", "bin"),
|
|
10288
|
+
path14.join(home, ".volta", "bin"),
|
|
10289
|
+
path14.join(home, ".npm-global", "bin"),
|
|
10290
|
+
path14.join(home, "bin"),
|
|
10085
10291
|
"/usr/local/bin",
|
|
10086
10292
|
"/opt/homebrew/bin"
|
|
10087
10293
|
];
|
|
10088
10294
|
}
|
|
10089
10295
|
function windowsBinDirs(env, home) {
|
|
10090
|
-
const
|
|
10091
|
-
const under = (base, ...parts) => base ? [
|
|
10296
|
+
const join45 = (...parts) => path14.win32.join(...parts);
|
|
10297
|
+
const under = (base, ...parts) => base ? [join45(base, ...parts)] : [];
|
|
10092
10298
|
const appData = env.APPDATA;
|
|
10093
10299
|
const localAppData = env.LOCALAPPDATA;
|
|
10094
10300
|
const programData = env.ProgramData;
|
|
10095
10301
|
return [
|
|
10096
10302
|
// Native per-user installers, PowerShell-driven and otherwise.
|
|
10097
|
-
|
|
10098
|
-
|
|
10099
|
-
|
|
10100
|
-
|
|
10303
|
+
join45(home, ".local", "bin"),
|
|
10304
|
+
join45(home, ".opencode", "bin"),
|
|
10305
|
+
join45(home, ".claude", "bin"),
|
|
10306
|
+
join45(home, ".codex", "bin"),
|
|
10101
10307
|
...under(localAppData, "Programs", "claude"),
|
|
10102
10308
|
// Node package managers. npm's global prefix is `%APPDATA%\npm`; the
|
|
10103
10309
|
// literal spelling is kept as a fallback for a host that does not export
|
|
10104
10310
|
// APPDATA (a service account, a stripped CI container).
|
|
10105
10311
|
...under(appData, "npm"),
|
|
10106
|
-
|
|
10312
|
+
join45(home, "AppData", "Roaming", "npm"),
|
|
10107
10313
|
...under(localAppData, "pnpm"),
|
|
10108
10314
|
...under(localAppData, "Yarn", "bin"),
|
|
10109
|
-
|
|
10315
|
+
join45(home, ".bun", "bin"),
|
|
10110
10316
|
// Windows package managers.
|
|
10111
|
-
|
|
10317
|
+
join45(home, "scoop", "shims"),
|
|
10112
10318
|
...under(env.SCOOP, "shims"),
|
|
10113
10319
|
...under(env.SCOOP_GLOBAL ?? programData, "scoop", "shims"),
|
|
10114
10320
|
...under(programData, "chocolatey", "bin"),
|
|
@@ -10118,13 +10324,13 @@ function windowsBinDirs(env, home) {
|
|
|
10118
10324
|
...under(localAppData, "Microsoft", "WindowsApps"),
|
|
10119
10325
|
// Version managers. Volta's Windows home is not the POSIX `~/.volta`.
|
|
10120
10326
|
...under(localAppData, "Volta", "bin"),
|
|
10121
|
-
|
|
10327
|
+
join45(home, ".volta", "bin")
|
|
10122
10328
|
];
|
|
10123
10329
|
}
|
|
10124
10330
|
function wellKnownBinDirs(deps = {}) {
|
|
10125
10331
|
const platform = deps.platform ?? process.platform;
|
|
10126
10332
|
if (platform !== "win32") return posixBinDirs();
|
|
10127
|
-
return [...new Set(windowsBinDirs(deps.env ?? process.env, deps.home ??
|
|
10333
|
+
return [...new Set(windowsBinDirs(deps.env ?? process.env, deps.home ?? os6.homedir()))];
|
|
10128
10334
|
}
|
|
10129
10335
|
async function loginShellPath(execImpl) {
|
|
10130
10336
|
if (process.platform === "win32") return null;
|
|
@@ -10147,14 +10353,14 @@ function augmentedPath(execImpl = defaultExec) {
|
|
|
10147
10353
|
const segments = [];
|
|
10148
10354
|
const push = (p) => {
|
|
10149
10355
|
if (!p) return;
|
|
10150
|
-
for (const seg of p.split(
|
|
10356
|
+
for (const seg of p.split(path14.delimiter)) {
|
|
10151
10357
|
if (seg && !segments.includes(seg)) segments.push(seg);
|
|
10152
10358
|
}
|
|
10153
10359
|
};
|
|
10154
10360
|
push(process.env.PATH);
|
|
10155
10361
|
push(await loginShellPath(execImpl));
|
|
10156
10362
|
for (const dir of wellKnownBinDirs()) push(dir);
|
|
10157
|
-
return segments.join(
|
|
10363
|
+
return segments.join(path14.delimiter);
|
|
10158
10364
|
})();
|
|
10159
10365
|
}
|
|
10160
10366
|
return cachedPath;
|
|
@@ -10176,7 +10382,7 @@ function withPath(base, resolvedPath, overrides = {}) {
|
|
|
10176
10382
|
|
|
10177
10383
|
// src/utils/launch.ts
|
|
10178
10384
|
import * as fsSync4 from "fs";
|
|
10179
|
-
import * as
|
|
10385
|
+
import * as path15 from "path";
|
|
10180
10386
|
var DIRECT_EXTENSIONS = [".exe", ".com"];
|
|
10181
10387
|
var BATCH_EXTENSIONS = [".cmd", ".bat"];
|
|
10182
10388
|
var POWERSHELL_EXTENSIONS = [".ps1"];
|
|
@@ -10184,7 +10390,7 @@ var CMD_EXE_MAX_COMMAND_LINE = 8191;
|
|
|
10184
10390
|
var WINDOWS_MAX_COMMAND_LINE = 32767;
|
|
10185
10391
|
function defaultPowerShell() {
|
|
10186
10392
|
const root = process.env.SystemRoot || process.env.windir;
|
|
10187
|
-
return root ?
|
|
10393
|
+
return root ? path15.win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe") : "powershell.exe";
|
|
10188
10394
|
}
|
|
10189
10395
|
var POWERSHELL_FLAGS = ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File"];
|
|
10190
10396
|
var CommandLineTooLongError = class extends Error {
|
|
@@ -10250,7 +10456,7 @@ Searched: ${searchedPath || "(empty PATH)"}`);
|
|
|
10250
10456
|
};
|
|
10251
10457
|
function isResolvableOnPosixPath(command, PATH, exists) {
|
|
10252
10458
|
if (command.includes("/")) return exists(command);
|
|
10253
|
-
return PATH.split(
|
|
10459
|
+
return PATH.split(path15.delimiter).map((d) => d.trim()).filter(Boolean).some((dir) => exists(path15.join(dir, command)));
|
|
10254
10460
|
}
|
|
10255
10461
|
function isExecutableResolved(command, plan, PATH, deps = {}) {
|
|
10256
10462
|
if ((deps.platform ?? process.platform) === "win32") return plan.file !== command;
|
|
@@ -10290,9 +10496,9 @@ function cmdArgs(line) {
|
|
|
10290
10496
|
return ["/d", "/s", "/c", `"${line}"`];
|
|
10291
10497
|
}
|
|
10292
10498
|
function candidateExtensions(command, pathExt) {
|
|
10293
|
-
const declared = pathExt.split(
|
|
10499
|
+
const declared = pathExt.split(path15.win32.delimiter).map((e) => e.trim().toLowerCase()).filter(Boolean);
|
|
10294
10500
|
const known = /* @__PURE__ */ new Set([...DIRECT_EXTENSIONS, ...BATCH_EXTENSIONS, ...POWERSHELL_EXTENSIONS, ...declared]);
|
|
10295
|
-
const ext =
|
|
10501
|
+
const ext = path15.win32.extname(command).toLowerCase();
|
|
10296
10502
|
if (ext && known.has(ext)) {
|
|
10297
10503
|
const route = BATCH_EXTENSIONS.includes(ext) ? "cmd" : POWERSHELL_EXTENSIONS.includes(ext) ? "powershell" : "direct";
|
|
10298
10504
|
return { direct: [], cmd: [], powershell: [], [route]: [""] };
|
|
@@ -10313,13 +10519,13 @@ async function findWindowsExecutables(command, deps) {
|
|
|
10313
10519
|
const exists = deps.exists ?? defaultExists3;
|
|
10314
10520
|
const pathExt = (deps.pathExt ?? (() => process.env.PATHEXT ?? ""))();
|
|
10315
10521
|
const byRoute = candidateExtensions(command, pathExt);
|
|
10316
|
-
const dirs =
|
|
10317
|
-
const base =
|
|
10522
|
+
const dirs = path15.win32.basename(command) !== command ? [path15.win32.dirname(path15.win32.resolve(command))] : (await (deps.resolvePath ?? augmentedPath)() || "").split(path15.win32.delimiter).map((d) => d.trim().replace(/^"|"$/g, "")).filter(Boolean);
|
|
10523
|
+
const base = path15.win32.basename(command);
|
|
10318
10524
|
const found = [];
|
|
10319
10525
|
for (const route of ROUTES) {
|
|
10320
10526
|
outer: for (const ext of byRoute[route]) {
|
|
10321
10527
|
for (const dir of dirs) {
|
|
10322
|
-
const candidate =
|
|
10528
|
+
const candidate = path15.win32.join(dir, `${base}${ext}`);
|
|
10323
10529
|
if (exists(candidate)) {
|
|
10324
10530
|
found.push({ file: candidate, route });
|
|
10325
10531
|
break outer;
|
|
@@ -10367,7 +10573,7 @@ async function planDirectLaunch(command, args, deps = {}) {
|
|
|
10367
10573
|
|
|
10368
10574
|
// src/utils/workspace.ts
|
|
10369
10575
|
import * as fs3 from "fs";
|
|
10370
|
-
import * as
|
|
10576
|
+
import * as path16 from "path";
|
|
10371
10577
|
var WorkspaceNotFoundError = class extends Error {
|
|
10372
10578
|
constructor(workspace) {
|
|
10373
10579
|
super(`Workspace "${workspace}" does not exist.`);
|
|
@@ -10423,7 +10629,7 @@ function assertWorkspaceIsProject(workspace, deps = {}) {
|
|
|
10423
10629
|
throw new WorkspaceNotAProjectError(workspace);
|
|
10424
10630
|
}
|
|
10425
10631
|
const hasMarker = [...PROJECT_MARKER_DIRS, ...PROJECT_MARKER_FILES].some(
|
|
10426
|
-
(marker2) => exists(
|
|
10632
|
+
(marker2) => exists(path16.join(resolved, marker2))
|
|
10427
10633
|
);
|
|
10428
10634
|
if (!hasMarker) throw new WorkspaceNotAProjectError(workspace);
|
|
10429
10635
|
}
|
|
@@ -10431,12 +10637,12 @@ function assertWorkspaceIsProject(workspace, deps = {}) {
|
|
|
10431
10637
|
// src/services/workspaceEnv.ts
|
|
10432
10638
|
import { execFile as execFile3 } from "child_process";
|
|
10433
10639
|
import * as fs5 from "fs";
|
|
10434
|
-
import * as
|
|
10435
|
-
import * as
|
|
10640
|
+
import * as os7 from "os";
|
|
10641
|
+
import * as path18 from "path";
|
|
10436
10642
|
|
|
10437
10643
|
// src/utils/fsHelpers.ts
|
|
10438
10644
|
import * as fs4 from "fs";
|
|
10439
|
-
import * as
|
|
10645
|
+
import * as path17 from "path";
|
|
10440
10646
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
10441
10647
|
var STATE_DIR = ".ordewell";
|
|
10442
10648
|
function ensureDir(dir) {
|
|
@@ -10445,9 +10651,9 @@ function ensureDir(dir) {
|
|
|
10445
10651
|
}
|
|
10446
10652
|
}
|
|
10447
10653
|
function writeFileAtomic(filePath, content) {
|
|
10448
|
-
const dir =
|
|
10654
|
+
const dir = path17.dirname(filePath);
|
|
10449
10655
|
ensureDir(dir);
|
|
10450
|
-
const tempPath =
|
|
10656
|
+
const tempPath = path17.join(dir, `.${path17.basename(filePath)}.${randomUUID2()}.tmp`);
|
|
10451
10657
|
fs4.writeFileSync(tempPath, content, { flag: "wx" });
|
|
10452
10658
|
try {
|
|
10453
10659
|
fs4.renameSync(tempPath, filePath);
|
|
@@ -10460,7 +10666,7 @@ function writeFileAtomic(filePath, content) {
|
|
|
10460
10666
|
}
|
|
10461
10667
|
}
|
|
10462
10668
|
function getStateDir(baseDir) {
|
|
10463
|
-
return
|
|
10669
|
+
return path17.join(baseDir ?? process.cwd(), STATE_DIR);
|
|
10464
10670
|
}
|
|
10465
10671
|
var IGNORE_FILE = ".gitignore";
|
|
10466
10672
|
var STATE_DIR_IGNORE = "*\n!.gitignore\n!skills/\n!skills/**\n";
|
|
@@ -10468,7 +10674,7 @@ var LEGACY_IGNORES = /* @__PURE__ */ new Set(["*", "*\n"]);
|
|
|
10468
10674
|
function ensureStateDirIgnored(baseDir) {
|
|
10469
10675
|
const dir = getStateDir(baseDir);
|
|
10470
10676
|
ensureDir(dir);
|
|
10471
|
-
const file =
|
|
10677
|
+
const file = path17.join(dir, IGNORE_FILE);
|
|
10472
10678
|
try {
|
|
10473
10679
|
fs4.writeFileSync(file, STATE_DIR_IGNORE, { flag: "wx" });
|
|
10474
10680
|
return;
|
|
@@ -11003,7 +11209,7 @@ function defaultDeps() {
|
|
|
11003
11209
|
}
|
|
11004
11210
|
},
|
|
11005
11211
|
baseEnv: process.env,
|
|
11006
|
-
home:
|
|
11212
|
+
home: os7.homedir()
|
|
11007
11213
|
};
|
|
11008
11214
|
}
|
|
11009
11215
|
var REFUSED2 = new Set(SETTINGS_ENV_REFUSED);
|
|
@@ -11015,7 +11221,7 @@ async function resolveWorkspaceEnv(cwd, overrides = {}) {
|
|
|
11015
11221
|
const direnv = await fromDirenv(checkoutDirOf(cwd, deps), deps);
|
|
11016
11222
|
result.blockedEnvrc = direnv.blocked;
|
|
11017
11223
|
Object.assign(collected, direnv.env);
|
|
11018
|
-
const file = findUp(cwd,
|
|
11224
|
+
const file = findUp(cwd, path18.join(STATE_DIR, WORKSPACE_ENV_FILE), deps);
|
|
11019
11225
|
if (file) {
|
|
11020
11226
|
const content = deps.readFile(file);
|
|
11021
11227
|
if (content !== null) {
|
|
@@ -11032,7 +11238,7 @@ async function resolveWorkspaceEnv(cwd, overrides = {}) {
|
|
|
11032
11238
|
}
|
|
11033
11239
|
var evaluating = /* @__PURE__ */ new Map();
|
|
11034
11240
|
async function workspaceEnvOf(cwd) {
|
|
11035
|
-
const key =
|
|
11241
|
+
const key = path18.resolve(cwd);
|
|
11036
11242
|
let running = evaluating.get(key);
|
|
11037
11243
|
if (!running) {
|
|
11038
11244
|
running = resolveWorkspaceEnv(cwd).then((result) => result.env).finally(() => evaluating.delete(key));
|
|
@@ -11041,11 +11247,11 @@ async function workspaceEnvOf(cwd) {
|
|
|
11041
11247
|
return { ...await running };
|
|
11042
11248
|
}
|
|
11043
11249
|
function checkoutDirOf(cwd, deps) {
|
|
11044
|
-
const parts =
|
|
11250
|
+
const parts = path18.resolve(cwd).split(path18.sep);
|
|
11045
11251
|
const at = parts.findIndex((part, i) => part === STATE_DIR && parts[i + 1] === "worktrees");
|
|
11046
11252
|
if (at < 0 || parts.length < at + 4) return cwd;
|
|
11047
|
-
const root = parts.slice(0, at).join(
|
|
11048
|
-
const mapped =
|
|
11253
|
+
const root = parts.slice(0, at).join(path18.sep) || path18.sep;
|
|
11254
|
+
const mapped = path18.join(root, ...parts.slice(at + 4));
|
|
11049
11255
|
return deps.isDirectory(mapped) ? mapped : root;
|
|
11050
11256
|
}
|
|
11051
11257
|
async function fromDirenv(cwd, deps) {
|
|
@@ -11075,18 +11281,18 @@ async function fromDirenv(cwd, deps) {
|
|
|
11075
11281
|
return { env, blocked: null };
|
|
11076
11282
|
}
|
|
11077
11283
|
function findUp(start, relative17, deps) {
|
|
11078
|
-
let dir =
|
|
11284
|
+
let dir = path18.resolve(start);
|
|
11079
11285
|
for (; ; ) {
|
|
11080
|
-
const candidate =
|
|
11286
|
+
const candidate = path18.join(dir, relative17);
|
|
11081
11287
|
if (deps.readFile(candidate) !== null) return candidate;
|
|
11082
|
-
const parent =
|
|
11288
|
+
const parent = path18.dirname(dir);
|
|
11083
11289
|
if (parent === dir) return null;
|
|
11084
11290
|
dir = parent;
|
|
11085
11291
|
}
|
|
11086
11292
|
}
|
|
11087
11293
|
async function isTracked(file, deps) {
|
|
11088
11294
|
try {
|
|
11089
|
-
const out = await deps.run("git", ["ls-files", "--error-unmatch",
|
|
11295
|
+
const out = await deps.run("git", ["ls-files", "--error-unmatch", path18.basename(file)], { cwd: path18.dirname(file), env: deps.baseEnv });
|
|
11090
11296
|
return out.code === 0;
|
|
11091
11297
|
} catch {
|
|
11092
11298
|
return false;
|
|
@@ -11096,7 +11302,7 @@ function parseEnvFile(content, context, home) {
|
|
|
11096
11302
|
const env = {};
|
|
11097
11303
|
const lookup2 = (name) => env[name] ?? context[name] ?? "";
|
|
11098
11304
|
const expand = (value) => {
|
|
11099
|
-
const tilde = value.startsWith("~/") ?
|
|
11305
|
+
const tilde = value.startsWith("~/") ? path18.join(home, value.slice(2)) : value;
|
|
11100
11306
|
return tilde.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, braced, bare) => lookup2(braced ?? bare));
|
|
11101
11307
|
};
|
|
11102
11308
|
for (const raw of content.split(/\r?\n/)) {
|
|
@@ -11366,7 +11572,7 @@ function readOnlySessionRules(site) {
|
|
|
11366
11572
|
real = realpathSync4.native(root);
|
|
11367
11573
|
} catch {
|
|
11368
11574
|
}
|
|
11369
|
-
for (const
|
|
11575
|
+
for (const path49 of /* @__PURE__ */ new Set([root, real])) rules.push({ action: "external_directory", resource: `${path49.replace(/[\\/]$/, "")}/*`, effect: "allow" });
|
|
11370
11576
|
}
|
|
11371
11577
|
return rules;
|
|
11372
11578
|
}
|
|
@@ -11492,6 +11698,14 @@ function runnerEnv(resolvedPath, workspace = {}, launch = {}, drop) {
|
|
|
11492
11698
|
// src/services/harness/runnerProcess.ts
|
|
11493
11699
|
var STDERR_TAIL_CHARS = 4e3;
|
|
11494
11700
|
var CLOSE_GRACE_MS = 2e3;
|
|
11701
|
+
function releasePipes(child) {
|
|
11702
|
+
for (const stream of [child.stdin, child.stdout, child.stderr]) {
|
|
11703
|
+
if (stream instanceof Socket) stream.unref();
|
|
11704
|
+
}
|
|
11705
|
+
for (const stream of [child.stdout, child.stderr]) {
|
|
11706
|
+
if (stream instanceof Socket) stream.resume();
|
|
11707
|
+
}
|
|
11708
|
+
}
|
|
11495
11709
|
var RunnerProcess = class {
|
|
11496
11710
|
constructor(deps) {
|
|
11497
11711
|
this.deps = deps;
|
|
@@ -11564,6 +11778,7 @@ var RunnerProcess = class {
|
|
|
11564
11778
|
const grace = setTimeout(() => {
|
|
11565
11779
|
for (const flush of [...this.stdoutEnds]) flush();
|
|
11566
11780
|
this.end({ code, signal });
|
|
11781
|
+
releasePipes(child);
|
|
11567
11782
|
}, CLOSE_GRACE_MS);
|
|
11568
11783
|
grace.unref?.();
|
|
11569
11784
|
void this.ended.then(() => clearTimeout(grace));
|
|
@@ -11662,11 +11877,11 @@ ${tail}` : ""}`;
|
|
|
11662
11877
|
|
|
11663
11878
|
// src/services/sandbox/nativeShellLauncher.ts
|
|
11664
11879
|
import { chmodSync, mkdtempSync as mkdtempSync3, realpathSync as realpathSync5, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
11665
|
-
import * as
|
|
11666
|
-
import * as
|
|
11880
|
+
import * as os8 from "os";
|
|
11881
|
+
import * as path19 from "path";
|
|
11667
11882
|
function nativeShellInvocation(platform, env = process.env) {
|
|
11668
11883
|
const shell = platform === "darwin" ? "/bin/zsh" : env.SHELL;
|
|
11669
|
-
return { shell: shell &&
|
|
11884
|
+
return { shell: shell && path19.posix.isAbsolute(shell) ? shell : "/bin/sh", args: ["-c"] };
|
|
11670
11885
|
}
|
|
11671
11886
|
var live = /* @__PURE__ */ new Set();
|
|
11672
11887
|
var exitHook = false;
|
|
@@ -11698,10 +11913,10 @@ function launcherScript(launch) {
|
|
|
11698
11913
|
}
|
|
11699
11914
|
async function createNativeShellLauncher(opts) {
|
|
11700
11915
|
const invocation = nativeShellInvocation(opts.platform ?? process.platform, opts.env);
|
|
11701
|
-
const root = realpathSync5(mkdtempSync3(
|
|
11916
|
+
const root = realpathSync5(mkdtempSync3(path19.join(os8.tmpdir(), "ordewell-native-shell-")));
|
|
11702
11917
|
let shortTmp = null;
|
|
11703
11918
|
try {
|
|
11704
|
-
shortTmp = realpathSync5(mkdtempSync3(
|
|
11919
|
+
shortTmp = realpathSync5(mkdtempSync3(path19.join("/tmp", "ow-")));
|
|
11705
11920
|
const tmpdir7 = shortTmp;
|
|
11706
11921
|
const launch = await opts.sandbox.launchNativeShell({
|
|
11707
11922
|
workspace: opts.workspace,
|
|
@@ -11710,7 +11925,7 @@ async function createNativeShellLauncher(opts) {
|
|
|
11710
11925
|
shellArgs: invocation.args,
|
|
11711
11926
|
...opts.env ? { env: opts.env } : {}
|
|
11712
11927
|
});
|
|
11713
|
-
const prefix =
|
|
11928
|
+
const prefix = path19.join(root, "run");
|
|
11714
11929
|
writeFileSync3(prefix, launcherScript(launch), { mode: 448 });
|
|
11715
11930
|
chmodSync(prefix, 448);
|
|
11716
11931
|
keep(root);
|
|
@@ -12079,6 +12294,20 @@ ${lines.map((line) => `${line}
|
|
|
12079
12294
|
}
|
|
12080
12295
|
|
|
12081
12296
|
// src/services/harness/claudeOrdewell.ts
|
|
12297
|
+
import * as path20 from "path";
|
|
12298
|
+
function assertNoClaudeTaskCollision(cwd) {
|
|
12299
|
+
const text4 = readText(path20.join(cwd, ".mcp.json"));
|
|
12300
|
+
if (text4 === void 0) return;
|
|
12301
|
+
let parsed;
|
|
12302
|
+
try {
|
|
12303
|
+
parsed = JSON.parse(text4);
|
|
12304
|
+
} catch {
|
|
12305
|
+
return;
|
|
12306
|
+
}
|
|
12307
|
+
if (isRecord(parsed) && isRecord(parsed.mcpServers) && Object.hasOwn(parsed.mcpServers, ORDEWELL_MCP_SERVER_NAME)) {
|
|
12308
|
+
throw new Error(".mcp.json declares an MCP server named ordewell; rename or remove the ordewell server before starting this task.");
|
|
12309
|
+
}
|
|
12310
|
+
}
|
|
12082
12311
|
var CLAUDE_ORDEWELL = {
|
|
12083
12312
|
toolName: MCP_CLIENT_TOOL_NAMES.toolName,
|
|
12084
12313
|
toolNames: MCP_CLIENT_TOOL_NAMES.toolNames,
|
|
@@ -12226,9 +12455,8 @@ var ReplyRuns = class {
|
|
|
12226
12455
|
};
|
|
12227
12456
|
|
|
12228
12457
|
// src/services/harness/claudeRepoConfig.ts
|
|
12229
|
-
import * as
|
|
12230
|
-
|
|
12231
|
-
var MEMORY_FILES = ["CLAUDE.md", path19.join(".claude", "CLAUDE.md"), "CLAUDE.local.md"];
|
|
12458
|
+
import * as path21 from "path";
|
|
12459
|
+
var MEMORY_FILES = ["CLAUDE.md", path21.join(".claude", "CLAUDE.md"), "CLAUDE.local.md"];
|
|
12232
12460
|
var MAX_IMPORT_DEPTH = 5;
|
|
12233
12461
|
function importsOf(text4) {
|
|
12234
12462
|
const prose = text4.replace(/```[\s\S]*?```/g, "").replace(/`[^`\n]*`/g, "");
|
|
@@ -12239,11 +12467,11 @@ function withImports(workspace, file, depth, seen, into) {
|
|
|
12239
12467
|
seen.add(file);
|
|
12240
12468
|
const text4 = readText(file);
|
|
12241
12469
|
if (text4 === void 0) return;
|
|
12242
|
-
into.push({ path:
|
|
12470
|
+
into.push({ path: path21.relative(workspace, file), text: text4 });
|
|
12243
12471
|
if (depth >= MAX_IMPORT_DEPTH) return;
|
|
12244
12472
|
for (const target of importsOf(text4)) {
|
|
12245
12473
|
if (target.startsWith("~")) continue;
|
|
12246
|
-
const abs =
|
|
12474
|
+
const abs = path21.resolve(path21.dirname(file), target);
|
|
12247
12475
|
if (insideOnDisk(workspace, abs)) withImports(workspace, abs, depth + 1, seen, into);
|
|
12248
12476
|
}
|
|
12249
12477
|
}
|
|
@@ -12264,12 +12492,12 @@ var CLAUDE_REPO_CONFIG = {
|
|
|
12264
12492
|
instructions(workspace) {
|
|
12265
12493
|
const files = [];
|
|
12266
12494
|
const seen = /* @__PURE__ */ new Set();
|
|
12267
|
-
for (const name of MEMORY_FILES) withImports(workspace,
|
|
12495
|
+
for (const name of MEMORY_FILES) withImports(workspace, path21.join(workspace, name), 0, seen, files);
|
|
12268
12496
|
return files;
|
|
12269
12497
|
},
|
|
12270
12498
|
projectServers(workspace) {
|
|
12271
12499
|
const source = ".mcp.json";
|
|
12272
|
-
const text4 = readText(
|
|
12500
|
+
const text4 = readText(path21.join(workspace, source));
|
|
12273
12501
|
if (text4 === void 0) return NO_SERVERS;
|
|
12274
12502
|
let parsed;
|
|
12275
12503
|
try {
|
|
@@ -12287,13 +12515,14 @@ var CLAUDE_REPO_CONFIG = {
|
|
|
12287
12515
|
return found;
|
|
12288
12516
|
}
|
|
12289
12517
|
};
|
|
12290
|
-
function claudeLocalServers(
|
|
12291
|
-
const
|
|
12292
|
-
const
|
|
12518
|
+
function claudeLocalServers(env, workspace) {
|
|
12519
|
+
const configDir2 = env.CLAUDE_CONFIG_DIR?.trim();
|
|
12520
|
+
const file = expandRunnerHome(configDir2 ? path21.join(configDir2, ".claude.json") : "~/.claude.json", env);
|
|
12521
|
+
const text4 = file === null ? void 0 : readText(file);
|
|
12293
12522
|
if (text4 === void 0) return {};
|
|
12294
12523
|
try {
|
|
12295
12524
|
const parsed = JSON.parse(text4);
|
|
12296
|
-
const project = isRecord(parsed) && isRecord(parsed.projects) ? parsed.projects[
|
|
12525
|
+
const project = isRecord(parsed) && isRecord(parsed.projects) ? parsed.projects[path21.resolve(workspace)] : void 0;
|
|
12297
12526
|
return isRecord(project) && isRecord(project.mcpServers) ? project.mcpServers : {};
|
|
12298
12527
|
} catch {
|
|
12299
12528
|
return {};
|
|
@@ -12548,13 +12777,13 @@ var NO_AUTO_MEMORY = { CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1" };
|
|
|
12548
12777
|
function inSavedOutput(abs, configDir2, sessionId) {
|
|
12549
12778
|
let projects;
|
|
12550
12779
|
try {
|
|
12551
|
-
projects = realpathSync7.native(
|
|
12780
|
+
projects = realpathSync7.native(path22.join(configDir2, "projects"));
|
|
12552
12781
|
} catch {
|
|
12553
12782
|
return false;
|
|
12554
12783
|
}
|
|
12555
|
-
const rel =
|
|
12556
|
-
if (rel.startsWith("..") ||
|
|
12557
|
-
const [project, session, folder] = rel.split(
|
|
12784
|
+
const rel = path22.relative(projects, abs);
|
|
12785
|
+
if (rel.startsWith("..") || path22.isAbsolute(rel)) return false;
|
|
12786
|
+
const [project, session, folder] = rel.split(path22.sep);
|
|
12558
12787
|
return Boolean(project) && session === sessionId && folder === "tool-results";
|
|
12559
12788
|
}
|
|
12560
12789
|
var PATH_TOOLS = {
|
|
@@ -12792,7 +13021,7 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12792
13021
|
"--append-system-prompt",
|
|
12793
13022
|
opts.systemPrompt
|
|
12794
13023
|
];
|
|
12795
|
-
if (opts.mcp) args.push(...this.ordewellServerArgs(opts.mcp, "planner", this.plannerServers(opts, workspace)));
|
|
13024
|
+
if (opts.mcp) args.push(...this.ordewellServerArgs(opts.mcp, "planner", this.plannerServers(opts, { ...process.env, ...workspace })));
|
|
12796
13025
|
if (opts.model) args.push("--model", opts.model);
|
|
12797
13026
|
if (opts.effort && opts.effort !== "adaptive") args.push("--effort", opts.effort);
|
|
12798
13027
|
if (opts.resumeSessionId) {
|
|
@@ -12800,7 +13029,8 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12800
13029
|
this.countFrom(opts.resumeSessionId, opts.resumeCost);
|
|
12801
13030
|
this.pendingResume = opts.resumeSessionId;
|
|
12802
13031
|
}
|
|
12803
|
-
|
|
13032
|
+
const env = { ...process.env, ...workspace };
|
|
13033
|
+
this.configDir = expandRunnerHome(claudeConfigDir(env), env);
|
|
12804
13034
|
return {
|
|
12805
13035
|
command: "claude",
|
|
12806
13036
|
args,
|
|
@@ -12844,6 +13074,7 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12844
13074
|
*/
|
|
12845
13075
|
taskSpawnSpec(opts) {
|
|
12846
13076
|
const readOnly = readOnlyTask(opts);
|
|
13077
|
+
if (!readOnly) assertNoClaudeTaskCollision(opts.cwd);
|
|
12847
13078
|
this.readOnlySite = readOnly ? opts : null;
|
|
12848
13079
|
this.requestedMode = readOnly ? PLANNER_PERMISSION_MODE : opts.flags.permissionMode;
|
|
12849
13080
|
this.followedModel = opts.model;
|
|
@@ -12870,13 +13101,12 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
|
|
|
12870
13101
|
/**
|
|
12871
13102
|
* What user-only settings stop the CLI loading that is not the
|
|
12872
13103
|
* repository's to choose (ADR-0028): the user's own local-scope servers,
|
|
12873
|
-
*
|
|
12874
|
-
* servers approved for it.
|
|
13104
|
+
* from the account in the environment the planner runs with, and the
|
|
13105
|
+
* project servers approved for it.
|
|
12875
13106
|
*/
|
|
12876
|
-
plannerServers(opts,
|
|
12877
|
-
const configDir2 = workspace.CLAUDE_CONFIG_DIR ?? process.env.CLAUDE_CONFIG_DIR;
|
|
13107
|
+
plannerServers(opts, env) {
|
|
12878
13108
|
return {
|
|
12879
|
-
...claudeLocalServers(
|
|
13109
|
+
...claudeLocalServers(env, opts.cwd),
|
|
12880
13110
|
...Object.fromEntries((opts.projectServers ?? []).map((server4) => [server4.name, server4.definition]))
|
|
12881
13111
|
};
|
|
12882
13112
|
}
|
|
@@ -13640,8 +13870,8 @@ function runProbe(deps, cwd, env, flags) {
|
|
|
13640
13870
|
}
|
|
13641
13871
|
|
|
13642
13872
|
// src/services/harness/codexRepoConfig.ts
|
|
13643
|
-
import * as
|
|
13644
|
-
var CONFIG_FILE =
|
|
13873
|
+
import * as path23 from "path";
|
|
13874
|
+
var CONFIG_FILE = path23.join(".codex", "config.toml");
|
|
13645
13875
|
function stringsOf(value) {
|
|
13646
13876
|
return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
|
|
13647
13877
|
}
|
|
@@ -13667,9 +13897,9 @@ var CODEX_REPO_CONFIG = {
|
|
|
13667
13897
|
const files = [];
|
|
13668
13898
|
for (const dir of repoLevels(workspace)) {
|
|
13669
13899
|
for (const name of ["AGENTS.override.md", "AGENTS.md"]) {
|
|
13670
|
-
const text4 = readText(
|
|
13900
|
+
const text4 = readText(path23.join(dir, name));
|
|
13671
13901
|
if (text4 === void 0) continue;
|
|
13672
|
-
files.push({ path:
|
|
13902
|
+
files.push({ path: path23.relative(workspace, path23.join(dir, name)) || name, text: text4 });
|
|
13673
13903
|
break;
|
|
13674
13904
|
}
|
|
13675
13905
|
}
|
|
@@ -13680,10 +13910,10 @@ var CODEX_REPO_CONFIG = {
|
|
|
13680
13910
|
const byName = /* @__PURE__ */ new Map();
|
|
13681
13911
|
const unreadable = [];
|
|
13682
13912
|
for (const dir of repoLevels(workspace)) {
|
|
13683
|
-
const file =
|
|
13913
|
+
const file = path23.join(dir, CONFIG_FILE);
|
|
13684
13914
|
const text4 = readText(file);
|
|
13685
13915
|
if (text4 === void 0) continue;
|
|
13686
|
-
const source =
|
|
13916
|
+
const source = path23.relative(workspace, file);
|
|
13687
13917
|
let parsed;
|
|
13688
13918
|
try {
|
|
13689
13919
|
parsed = parseToml(text4);
|
|
@@ -13703,9 +13933,9 @@ var CODEX_REPO_CONFIG = {
|
|
|
13703
13933
|
};
|
|
13704
13934
|
function untrustedProjects(workspace) {
|
|
13705
13935
|
const projects = {};
|
|
13706
|
-
for (let dir =
|
|
13936
|
+
for (let dir = path23.resolve(workspace); ; dir = path23.dirname(dir)) {
|
|
13707
13937
|
projects[dir] = { trust_level: "untrusted" };
|
|
13708
|
-
if (
|
|
13938
|
+
if (path23.dirname(dir) === dir) return projects;
|
|
13709
13939
|
}
|
|
13710
13940
|
}
|
|
13711
13941
|
|
|
@@ -14826,7 +15056,7 @@ ${this.exitMessage()}`);
|
|
|
14826
15056
|
case "webSearch":
|
|
14827
15057
|
return;
|
|
14828
15058
|
case "fileChange": {
|
|
14829
|
-
const paths = (item.changes ?? []).map((change) => change.path).filter((
|
|
15059
|
+
const paths = (item.changes ?? []).map((change) => change.path).filter((path49) => !!path49);
|
|
14830
15060
|
this.fileChangePaths.set(item.id, paths);
|
|
14831
15061
|
if (this.startOpts?.kind === "task") {
|
|
14832
15062
|
for (const change of item.changes ?? []) this.announceFileChange(item.id, change, emit, subagentId);
|
|
@@ -14929,20 +15159,20 @@ ${this.exitMessage()}`);
|
|
|
14929
15159
|
announceFileChange(itemId, change, emit, subagentId) {
|
|
14930
15160
|
const rows = this.fileChangeRows.get(itemId) ?? /* @__PURE__ */ new Map();
|
|
14931
15161
|
this.fileChangeRows.set(itemId, rows);
|
|
14932
|
-
const
|
|
14933
|
-
const known = rows.get(
|
|
15162
|
+
const path49 = this.shownChangePath(change);
|
|
15163
|
+
const known = rows.get(path49);
|
|
14934
15164
|
if (known) return known;
|
|
14935
|
-
const row = rows.size === 0 ? itemId : `${itemId}:${
|
|
14936
|
-
rows.set(
|
|
14937
|
-
emit({ type: "tool_call", id: row, name: changeName(change), args: { path:
|
|
15165
|
+
const row = rows.size === 0 ? itemId : `${itemId}:${path49}`;
|
|
15166
|
+
rows.set(path49, row);
|
|
15167
|
+
emit({ type: "tool_call", id: row, name: changeName(change), args: { path: path49 }, ...subagentId ? { subagentId } : {} });
|
|
14938
15168
|
return row;
|
|
14939
15169
|
}
|
|
14940
15170
|
/** Relative to the task's worktree, where every path but a stray one lies; a move names both ends. */
|
|
14941
15171
|
shownChangePath(change) {
|
|
14942
|
-
const shown = (
|
|
15172
|
+
const shown = (path49) => {
|
|
14943
15173
|
const cwd = this.startOpts?.cwd;
|
|
14944
|
-
const rel = cwd ? relative8(cwd,
|
|
14945
|
-
return rel && rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute6(rel) ? rel :
|
|
15174
|
+
const rel = cwd ? relative8(cwd, path49) : path49;
|
|
15175
|
+
return rel && rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute6(rel) ? rel : path49;
|
|
14946
15176
|
};
|
|
14947
15177
|
const from = shown(change.path ?? "");
|
|
14948
15178
|
return change.kind?.move_path ? `${from} \u2192 ${shown(change.kind.move_path)}` : from;
|
|
@@ -14993,7 +15223,7 @@ import { realpathSync as realpathSync8 } from "fs";
|
|
|
14993
15223
|
|
|
14994
15224
|
// src/services/harness/projectServers.ts
|
|
14995
15225
|
import { createHash } from "crypto";
|
|
14996
|
-
import * as
|
|
15226
|
+
import * as path24 from "path";
|
|
14997
15227
|
function canonical2(value) {
|
|
14998
15228
|
if (Array.isArray(value)) return value.map(canonical2);
|
|
14999
15229
|
if (!isRecord(value)) return value;
|
|
@@ -15004,7 +15234,7 @@ function projectServerScope(server4) {
|
|
|
15004
15234
|
return `mcp-server:${server4.name}@${hash}`;
|
|
15005
15235
|
}
|
|
15006
15236
|
var scopeName = (scope) => scope.slice(0, scope.lastIndexOf("@"));
|
|
15007
|
-
function fileProjectServerApprovals(file =
|
|
15237
|
+
function fileProjectServerApprovals(file = path24.join(globalDataDir(), "project-mcp-servers.json")) {
|
|
15008
15238
|
const load = () => {
|
|
15009
15239
|
const text4 = readText(file);
|
|
15010
15240
|
if (text4 === void 0) return {};
|
|
@@ -16167,8 +16397,8 @@ var OpenCodeAdapter = class extends ProcessLifecycle {
|
|
|
16167
16397
|
this.v2 = new OpenCodeV2({
|
|
16168
16398
|
version,
|
|
16169
16399
|
nativeShell: this.nativeShell?.prefix ?? null,
|
|
16170
|
-
request: (method,
|
|
16171
|
-
json: (method,
|
|
16400
|
+
request: (method, path49, body, signal, timeoutMs) => this.request(method, path49, body, signal, timeoutMs),
|
|
16401
|
+
json: (method, path49, body, signal, timeoutMs) => this.json(method, path49, body, signal, timeoutMs),
|
|
16172
16402
|
openEvents: (signal) => this.deps.fetch(`${this.baseUrl}/api/event`, { signal, headers: this.headers() }),
|
|
16173
16403
|
processEnded: this.processEnded,
|
|
16174
16404
|
isExited: () => this.exited,
|
|
@@ -16805,15 +17035,15 @@ var OpenCodeAdapter = class extends ProcessLifecycle {
|
|
|
16805
17035
|
onActivity
|
|
16806
17036
|
);
|
|
16807
17037
|
}
|
|
16808
|
-
async json(method,
|
|
16809
|
-
const response = await this.request(method,
|
|
16810
|
-
if (!response.ok) throw new OpenCodeHttpError(`${method} ${
|
|
17038
|
+
async json(method, path49, body, signal, timeoutMs) {
|
|
17039
|
+
const response = await this.request(method, path49, body, signal, timeoutMs);
|
|
17040
|
+
if (!response.ok) throw new OpenCodeHttpError(`${method} ${path49} failed: ${response.status} ${response.statusText}`);
|
|
16811
17041
|
return await response.json().catch(() => null);
|
|
16812
17042
|
}
|
|
16813
17043
|
/** `timeoutMs` null is for a turn's own POST, which the server answers when the turn is done. */
|
|
16814
|
-
request(method,
|
|
17044
|
+
request(method, path49, body, signal, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
16815
17045
|
const deadline = withDeadline(signal, timeoutMs);
|
|
16816
|
-
return this.deps.fetch(`${this.baseUrl}${
|
|
17046
|
+
return this.deps.fetch(`${this.baseUrl}${path49}`, {
|
|
16817
17047
|
method,
|
|
16818
17048
|
headers: this.headers({ "content-type": "application/json" }),
|
|
16819
17049
|
...body === void 0 ? {} : { body: JSON.stringify(body) },
|
|
@@ -16874,8 +17104,8 @@ var OpenCodeAdapter = class extends ProcessLifecycle {
|
|
|
16874
17104
|
};
|
|
16875
17105
|
|
|
16876
17106
|
// src/services/harness/openCodeRepoConfig.ts
|
|
16877
|
-
import * as
|
|
16878
|
-
var CONFIG_FILES = ["opencode.json", "opencode.jsonc",
|
|
17107
|
+
import * as path25 from "path";
|
|
17108
|
+
var CONFIG_FILES = ["opencode.json", "opencode.jsonc", path25.join(".opencode", "opencode.json"), path25.join(".opencode", "opencode.jsonc")];
|
|
16879
17109
|
var INSTRUCTION_NAMES = ["AGENTS.md", "CLAUDE.md", "CONTEXT.md"];
|
|
16880
17110
|
function server3(name, entry, source) {
|
|
16881
17111
|
if (!isRecord(entry)) return "not an object";
|
|
@@ -16894,8 +17124,8 @@ var OPENCODE_REPO_CONFIG = {
|
|
|
16894
17124
|
const levels = repoLevels(workspace);
|
|
16895
17125
|
for (const name of INSTRUCTION_NAMES) {
|
|
16896
17126
|
const files = levels.flatMap((dir) => {
|
|
16897
|
-
const text4 = readText(
|
|
16898
|
-
return text4 === void 0 ? [] : [{ path:
|
|
17127
|
+
const text4 = readText(path25.join(dir, name));
|
|
17128
|
+
return text4 === void 0 ? [] : [{ path: path25.relative(workspace, path25.join(dir, name)) || name, text: text4 }];
|
|
16899
17129
|
});
|
|
16900
17130
|
if (files.length > 0) return files;
|
|
16901
17131
|
}
|
|
@@ -16907,10 +17137,10 @@ var OPENCODE_REPO_CONFIG = {
|
|
|
16907
17137
|
const unreadable = [];
|
|
16908
17138
|
for (const dir of repoLevels(workspace)) {
|
|
16909
17139
|
for (const name of CONFIG_FILES) {
|
|
16910
|
-
const file =
|
|
17140
|
+
const file = path25.join(dir, name);
|
|
16911
17141
|
const text4 = readText(file);
|
|
16912
17142
|
if (text4 === void 0) continue;
|
|
16913
|
-
const source =
|
|
17143
|
+
const source = path25.relative(workspace, file);
|
|
16914
17144
|
let parsed;
|
|
16915
17145
|
try {
|
|
16916
17146
|
parsed = parseJsonc(text4);
|
|
@@ -17099,7 +17329,7 @@ function knownModelId(value, optionIds, shortcuts) {
|
|
|
17099
17329
|
|
|
17100
17330
|
// src/services/ModelCatalog.ts
|
|
17101
17331
|
import * as fs6 from "fs";
|
|
17102
|
-
import * as
|
|
17332
|
+
import * as path26 from "path";
|
|
17103
17333
|
var CACHE_KEY = "model-catalog";
|
|
17104
17334
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
17105
17335
|
function storageKey(baseUrl) {
|
|
@@ -17152,7 +17382,7 @@ var ModelCatalog = class {
|
|
|
17152
17382
|
const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
|
|
17153
17383
|
const key = storageKey(url);
|
|
17154
17384
|
const cacheDir = globalCacheDir();
|
|
17155
|
-
const cacheFile =
|
|
17385
|
+
const cacheFile = path26.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
17156
17386
|
if (fs6.existsSync(cacheFile)) fs6.rmSync(cacheFile);
|
|
17157
17387
|
} catch {
|
|
17158
17388
|
}
|
|
@@ -17160,7 +17390,7 @@ var ModelCatalog = class {
|
|
|
17160
17390
|
static readCache(key) {
|
|
17161
17391
|
try {
|
|
17162
17392
|
const cacheDir = globalCacheDir();
|
|
17163
|
-
const cacheFile =
|
|
17393
|
+
const cacheFile = path26.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
17164
17394
|
if (!fs6.existsSync(cacheFile)) return null;
|
|
17165
17395
|
const raw = fs6.readFileSync(cacheFile, "utf8");
|
|
17166
17396
|
const data = JSON.parse(raw);
|
|
@@ -17174,7 +17404,7 @@ var ModelCatalog = class {
|
|
|
17174
17404
|
try {
|
|
17175
17405
|
const cacheDir = globalCacheDir();
|
|
17176
17406
|
fs6.mkdirSync(cacheDir, { recursive: true });
|
|
17177
|
-
const cacheFile =
|
|
17407
|
+
const cacheFile = path26.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
|
|
17178
17408
|
fs6.writeFileSync(
|
|
17179
17409
|
cacheFile,
|
|
17180
17410
|
JSON.stringify({ models, cachedAt: Date.now() }, null, 2)
|
|
@@ -17185,15 +17415,17 @@ var ModelCatalog = class {
|
|
|
17185
17415
|
};
|
|
17186
17416
|
|
|
17187
17417
|
// src/services/ModelDiscovery.ts
|
|
17188
|
-
import * as
|
|
17418
|
+
import * as path27 from "path";
|
|
17189
17419
|
import { exec as exec2 } from "child_process";
|
|
17190
17420
|
import { promisify as promisify2 } from "util";
|
|
17191
17421
|
import * as fsSync5 from "fs";
|
|
17192
|
-
import * as osMod from "os";
|
|
17193
17422
|
|
|
17194
17423
|
// src/services/harness/appServerModels.ts
|
|
17195
17424
|
import { spawn as nodeSpawn } from "child_process";
|
|
17196
17425
|
var DEFAULT_TIMEOUT_MS3 = 2e4;
|
|
17426
|
+
function definedOnly(env) {
|
|
17427
|
+
return Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== void 0));
|
|
17428
|
+
}
|
|
17197
17429
|
function parseRpcLine(line) {
|
|
17198
17430
|
try {
|
|
17199
17431
|
const value = JSON.parse(line);
|
|
@@ -17206,10 +17438,9 @@ async function openAppServer(command, args, launch) {
|
|
|
17206
17438
|
const runner = new RunnerProcess({
|
|
17207
17439
|
spawn: nodeSpawn,
|
|
17208
17440
|
fetch: globalThis.fetch,
|
|
17209
|
-
// The cwd is no workspace
|
|
17210
|
-
//
|
|
17211
|
-
//
|
|
17212
|
-
// that can choose whose account is read.
|
|
17441
|
+
// The cwd is no workspace, so it pays no direnv round trip before a spawn
|
|
17442
|
+
// with a deadline. Discovery and the account monitor pass the workspace's
|
|
17443
|
+
// environment themselves, since that can choose whose account is read.
|
|
17213
17444
|
workspaceEnv: async () => ({}),
|
|
17214
17445
|
...launch.deps
|
|
17215
17446
|
});
|
|
@@ -17262,7 +17493,11 @@ async function openAppServer(command, args, launch) {
|
|
|
17262
17493
|
}
|
|
17263
17494
|
var APP_SERVER_CLIENT_INFO = { name: "ordewell", title: "Ordewell", version: "0.1.0" };
|
|
17264
17495
|
async function discoverAppServerModels(command, args, options = {}) {
|
|
17265
|
-
const
|
|
17496
|
+
const env = options.env;
|
|
17497
|
+
const server4 = await openAppServer(command, args, {
|
|
17498
|
+
cwd: process.cwd(),
|
|
17499
|
+
deps: { ...env ? { workspaceEnv: async () => definedOnly(env) } : {}, ...options.deps }
|
|
17500
|
+
});
|
|
17266
17501
|
if (!server4) return null;
|
|
17267
17502
|
const { rpc } = server4;
|
|
17268
17503
|
const asked = (async () => {
|
|
@@ -17289,10 +17524,10 @@ async function discoverAppServerModels(command, args, options = {}) {
|
|
|
17289
17524
|
var execAsync2 = promisify2(exec2);
|
|
17290
17525
|
var defaultExec2 = async (command, options) => {
|
|
17291
17526
|
const PATH = await augmentedPath();
|
|
17292
|
-
const { stdout } = await execAsync2(command, {
|
|
17527
|
+
const { stdout } = await execAsync2(command, { timeout: options?.timeout, maxBuffer: 32 * 1024 * 1024, env: withPath(options?.env ?? process.env, PATH) });
|
|
17293
17528
|
return { stdout: String(stdout) };
|
|
17294
17529
|
};
|
|
17295
|
-
var defaultAppServerClient = (command, args, options) => discoverAppServerModels(command, args, { timeout: options?.timeout });
|
|
17530
|
+
var defaultAppServerClient = (command, args, options) => discoverAppServerModels(command, args, { timeout: options?.timeout, env: options?.env });
|
|
17296
17531
|
function effortVariant(effort) {
|
|
17297
17532
|
return { id: effort, label: effort.charAt(0).toUpperCase() + effort.slice(1) };
|
|
17298
17533
|
}
|
|
@@ -17326,7 +17561,7 @@ function geminiDiscoveryCacheKey(apiKey) {
|
|
|
17326
17561
|
return {
|
|
17327
17562
|
key: `gemini-models:${hash}`,
|
|
17328
17563
|
dir,
|
|
17329
|
-
file:
|
|
17564
|
+
file: path27.join(dir, `gemini_models_${hash}.json`)
|
|
17330
17565
|
};
|
|
17331
17566
|
}
|
|
17332
17567
|
function readGeminiCache(apiKey) {
|
|
@@ -17413,15 +17648,22 @@ function applyVariants(models, manifest) {
|
|
|
17413
17648
|
if (!variants || variants.length === 0) return models;
|
|
17414
17649
|
return models.map((m) => m.variants.length > 0 ? m : { ...m, variants });
|
|
17415
17650
|
}
|
|
17416
|
-
function
|
|
17651
|
+
function listDir(dir) {
|
|
17652
|
+
try {
|
|
17653
|
+
return fsSync5.readdirSync(dir);
|
|
17654
|
+
} catch {
|
|
17655
|
+
return [];
|
|
17656
|
+
}
|
|
17657
|
+
}
|
|
17658
|
+
function resolveApiAuth(authMethods, env) {
|
|
17417
17659
|
for (const method of authMethods) {
|
|
17418
17660
|
try {
|
|
17419
17661
|
let token;
|
|
17420
17662
|
if (method.type === "env") {
|
|
17421
|
-
token =
|
|
17663
|
+
token = env[method.varName];
|
|
17422
17664
|
} else {
|
|
17423
|
-
const filePath = method.path.
|
|
17424
|
-
if (!fsSync5.existsSync(filePath)) continue;
|
|
17665
|
+
const filePath = expandRunnerHome(typeof method.path === "function" ? method.path(env) : method.path, env);
|
|
17666
|
+
if (!filePath || !fsSync5.existsSync(filePath)) continue;
|
|
17425
17667
|
const data = JSON.parse(fsSync5.readFileSync(filePath, "utf8"));
|
|
17426
17668
|
token = method.jsonPath.split(".").reduce((obj, key) => {
|
|
17427
17669
|
if (obj && typeof obj === "object") return obj[key];
|
|
@@ -17437,8 +17679,8 @@ function resolveApiAuth(authMethods) {
|
|
|
17437
17679
|
}
|
|
17438
17680
|
return null;
|
|
17439
17681
|
}
|
|
17440
|
-
async function discoverFromApi(manifest, config, fetchImpl) {
|
|
17441
|
-
const auth = resolveApiAuth(config.auth);
|
|
17682
|
+
async function discoverFromApi(manifest, config, env, fetchImpl) {
|
|
17683
|
+
const auth = resolveApiAuth(config.auth, env);
|
|
17442
17684
|
if (!auth) return null;
|
|
17443
17685
|
try {
|
|
17444
17686
|
const headers = { ...config.headers, [auth.header]: auth.value };
|
|
@@ -17454,7 +17696,7 @@ async function discoverFromApi(manifest, config, fetchImpl) {
|
|
|
17454
17696
|
function wasKilled(err) {
|
|
17455
17697
|
return !!err && typeof err === "object" && err.killed === true;
|
|
17456
17698
|
}
|
|
17457
|
-
async function discoverFromCommand(manifest, execImpl) {
|
|
17699
|
+
async function discoverFromCommand(manifest, execImpl, env) {
|
|
17458
17700
|
const discovery = manifest.modelDiscovery;
|
|
17459
17701
|
const commands = discovery.discoveryCommands ?? [
|
|
17460
17702
|
{ command: discovery.command, args: discovery.args || [], parser: discovery.parser }
|
|
@@ -17463,7 +17705,7 @@ async function discoverFromCommand(manifest, execImpl) {
|
|
|
17463
17705
|
let timedOut2 = false;
|
|
17464
17706
|
for (const cmd of commands) {
|
|
17465
17707
|
try {
|
|
17466
|
-
const { stdout } = await execImpl(`${cmd.command} ${cmd.args.join(" ")}`, { timeout: 45e3 });
|
|
17708
|
+
const { stdout } = await execImpl(`${cmd.command} ${cmd.args.join(" ")}`, { timeout: 45e3, env });
|
|
17467
17709
|
const models = parseModelOutput(stdout, cmd.parser, manifest);
|
|
17468
17710
|
if (models.length > 0) return { models: applyVariants(mergeCanonicalAliases(models, manifest), manifest), timedOut: timedOut2 };
|
|
17469
17711
|
} catch (err) {
|
|
@@ -17529,21 +17771,26 @@ var ModelDiscovery = class {
|
|
|
17529
17771
|
* same background service.
|
|
17530
17772
|
*/
|
|
17531
17773
|
inflight = /* @__PURE__ */ new Map();
|
|
17774
|
+
/** Bumped by every `clear()`: a discovery begun before one answers its own lookups but fills no cache. */
|
|
17775
|
+
generation = 0;
|
|
17532
17776
|
registry;
|
|
17533
17777
|
execImpl;
|
|
17534
17778
|
fetchImpl;
|
|
17535
17779
|
appServerClient;
|
|
17536
17780
|
readFileImpl;
|
|
17537
17781
|
now;
|
|
17538
|
-
|
|
17782
|
+
env;
|
|
17783
|
+
constructor(registry, execImpl = defaultExec2, fetchImpl, appServerClient = defaultAppServerClient, readFileImpl, now = Date.now, env = async () => process.env) {
|
|
17539
17784
|
this.registry = registry;
|
|
17540
17785
|
this.execImpl = execImpl;
|
|
17541
17786
|
this.fetchImpl = fetchImpl;
|
|
17542
17787
|
this.appServerClient = appServerClient;
|
|
17543
17788
|
this.now = now;
|
|
17789
|
+
this.env = env;
|
|
17544
17790
|
this.readFileImpl = readFileImpl ?? ((filePath) => {
|
|
17545
17791
|
try {
|
|
17546
|
-
|
|
17792
|
+
const file = expandRunnerHome(filePath);
|
|
17793
|
+
return file ? fsSync5.readFileSync(file, "utf8") : null;
|
|
17547
17794
|
} catch {
|
|
17548
17795
|
return null;
|
|
17549
17796
|
}
|
|
@@ -17555,16 +17802,17 @@ var ModelDiscovery = class {
|
|
|
17555
17802
|
* null when both fail so `discover()` falls through to the remaining
|
|
17556
17803
|
* methods.
|
|
17557
17804
|
*/
|
|
17558
|
-
async discoverFromAppServer(manifest) {
|
|
17805
|
+
async discoverFromAppServer(manifest, env) {
|
|
17559
17806
|
const config = manifest.modelDiscovery.appServer;
|
|
17560
17807
|
if (!config) return null;
|
|
17561
|
-
const raw = await this.appServerClient(config.command, config.args);
|
|
17808
|
+
const raw = await this.appServerClient(config.command, config.args, { env });
|
|
17562
17809
|
if (raw) {
|
|
17563
17810
|
const models = parseAppServerModels(raw);
|
|
17564
17811
|
if (models.length > 0) return models;
|
|
17565
17812
|
}
|
|
17566
|
-
|
|
17567
|
-
|
|
17813
|
+
const cacheFile = config.cacheFile && expandRunnerHome(typeof config.cacheFile === "function" ? config.cacheFile(env) : config.cacheFile, env);
|
|
17814
|
+
if (cacheFile) {
|
|
17815
|
+
const content = this.readFileImpl(cacheFile);
|
|
17568
17816
|
if (content) {
|
|
17569
17817
|
const models = parseCodexModelsCache(content);
|
|
17570
17818
|
if (models.length > 0) return models;
|
|
@@ -17602,37 +17850,60 @@ var ModelDiscovery = class {
|
|
|
17602
17850
|
if (!manifest) {
|
|
17603
17851
|
return [];
|
|
17604
17852
|
}
|
|
17605
|
-
|
|
17853
|
+
const generation2 = this.generation;
|
|
17854
|
+
const env = await this.env().catch(() => process.env);
|
|
17855
|
+
const found = await this.discoverFromSources(manifest, env);
|
|
17856
|
+
const models = this.withSettingsModels(found.models, manifest, env);
|
|
17857
|
+
if (generation2 !== this.generation) return models;
|
|
17858
|
+
if (found.cacheable && models.length > 0) this.cache.set(runner, models);
|
|
17859
|
+
else this.misses.set(runner, { models, at: this.now() });
|
|
17860
|
+
return models;
|
|
17861
|
+
}
|
|
17862
|
+
async discoverFromSources(manifest, env) {
|
|
17606
17863
|
let cacheable = true;
|
|
17607
17864
|
const discovery = manifest.modelDiscovery;
|
|
17608
17865
|
if (discovery.appServer) {
|
|
17609
|
-
const appServerModels = await this.discoverFromAppServer(manifest);
|
|
17610
|
-
if (appServerModels && appServerModels.length > 0) {
|
|
17611
|
-
this.cache.set(runner, appServerModels);
|
|
17612
|
-
return appServerModels;
|
|
17613
|
-
}
|
|
17866
|
+
const appServerModels = await this.discoverFromAppServer(manifest, env);
|
|
17867
|
+
if (appServerModels && appServerModels.length > 0) return { models: appServerModels, cacheable: true };
|
|
17614
17868
|
cacheable = false;
|
|
17615
17869
|
}
|
|
17616
17870
|
if (discovery.apiDiscovery) {
|
|
17617
|
-
const apiModels = await discoverFromApi(manifest, discovery.apiDiscovery, this.fetchImpl);
|
|
17871
|
+
const apiModels = await discoverFromApi(manifest, discovery.apiDiscovery, env, this.fetchImpl);
|
|
17618
17872
|
if (apiModels && apiModels.length > 0) {
|
|
17619
|
-
models
|
|
17620
|
-
if (cacheable && models.length > 0) this.cache.set(runner, models);
|
|
17621
|
-
return models;
|
|
17873
|
+
return { models: applyVariants(mergeCanonicalAliases(apiModels, manifest), manifest), cacheable };
|
|
17622
17874
|
}
|
|
17623
17875
|
}
|
|
17624
17876
|
if (discovery.method === "command" && (discovery.command || discovery.discoveryCommands?.length)) {
|
|
17625
|
-
const result = await discoverFromCommand(manifest, this.execImpl);
|
|
17626
|
-
models
|
|
17627
|
-
cacheable = !result.fromFallback;
|
|
17628
|
-
} else {
|
|
17629
|
-
models = buildFallbackModels(manifest);
|
|
17877
|
+
const result = await discoverFromCommand(manifest, this.execImpl, env);
|
|
17878
|
+
return { models: result.models, cacheable: !result.fromFallback };
|
|
17630
17879
|
}
|
|
17631
|
-
|
|
17632
|
-
|
|
17633
|
-
|
|
17880
|
+
return { models: buildFallbackModels(manifest), cacheable };
|
|
17881
|
+
}
|
|
17882
|
+
/**
|
|
17883
|
+
* Append the models registered in the runner's own settings. Read on every
|
|
17884
|
+
* discovery rather than cached apart, so a refresh picks up an edit;
|
|
17885
|
+
* whatever the sources already offer keeps its entry and metadata.
|
|
17886
|
+
*/
|
|
17887
|
+
withSettingsModels(models, manifest, env) {
|
|
17888
|
+
const read2 = manifest.modelDiscovery.settingsModels;
|
|
17889
|
+
if (!read2) return models;
|
|
17890
|
+
const files = {
|
|
17891
|
+
read: (filePath) => {
|
|
17892
|
+
const file = expandRunnerHome(filePath, env);
|
|
17893
|
+
return file ? this.readFileImpl(file) : null;
|
|
17894
|
+
},
|
|
17895
|
+
list: (dir) => {
|
|
17896
|
+
const expanded = expandRunnerHome(dir, env);
|
|
17897
|
+
return expanded ? listDir(expanded) : [];
|
|
17898
|
+
},
|
|
17899
|
+
env
|
|
17900
|
+
};
|
|
17901
|
+
const seen = new Set(models.map((m) => m.modelId));
|
|
17902
|
+
const added = read2(files).filter((m) => !seen.has(m.modelId));
|
|
17903
|
+
return added.length > 0 ? [...models, ...applyVariants(added, manifest)] : models;
|
|
17634
17904
|
}
|
|
17635
17905
|
clear() {
|
|
17906
|
+
this.generation++;
|
|
17636
17907
|
this.cache.clear();
|
|
17637
17908
|
this.misses.clear();
|
|
17638
17909
|
this.inflight.clear();
|
|
@@ -17798,7 +18069,7 @@ function parseMaxParallel(value) {
|
|
|
17798
18069
|
|
|
17799
18070
|
// src/services/SettingsService.ts
|
|
17800
18071
|
import * as fs12 from "fs";
|
|
17801
|
-
import * as
|
|
18072
|
+
import * as path34 from "path";
|
|
17802
18073
|
|
|
17803
18074
|
// src/utils/fileLock.ts
|
|
17804
18075
|
import * as fs7 from "fs";
|
|
@@ -18088,7 +18359,7 @@ function stable(value) {
|
|
|
18088
18359
|
}
|
|
18089
18360
|
|
|
18090
18361
|
// src/services/runnerExit.ts
|
|
18091
|
-
var USAGE_LIMIT_RE = /\b(?:usage|session|weekly|daily|monthly) limit\b|\brate limit (?:exceeded|reached)\b|\brate[- ]limited\b|\blimit (?:will )?reset\b|\bquota (?:exceeded|reached)\b|\btoo many requests\b/i;
|
|
18362
|
+
var USAGE_LIMIT_RE = /\b(?:usage|session|weekly|daily|monthly) limit\b|\byou['’]ve hit your (?:[\w'’-]+ ){0,3}?limit\b|\brate limit (?:exceeded|reached)\b|\brate[- ]limited\b|\blimit (?:will )?reset\b|\bquota (?:exceeded|reached)\b|\btoo many requests\b/i;
|
|
18092
18363
|
var USAGE_LIMIT_TAIL = 4096;
|
|
18093
18364
|
function classifyRunnerStop(said2) {
|
|
18094
18365
|
if (typeof said2 !== "string" && said2.usageLimit !== void 0) return said2.usageLimit ? "usage-limit" : "stopped";
|
|
@@ -18316,8 +18587,8 @@ var TurnStream = class {
|
|
|
18316
18587
|
};
|
|
18317
18588
|
|
|
18318
18589
|
// src/services/skillInvocation.ts
|
|
18319
|
-
import * as
|
|
18320
|
-
function abbreviateHome(file, home =
|
|
18590
|
+
import * as os9 from "os";
|
|
18591
|
+
function abbreviateHome(file, home = os9.homedir(), platform = process.platform) {
|
|
18321
18592
|
const win = platform === "win32";
|
|
18322
18593
|
const normal = (p) => win ? p.replace(/\\/g, "/") : p;
|
|
18323
18594
|
const shown = normal(file);
|
|
@@ -18332,13 +18603,13 @@ function modelInvocablePlannerSkills(skills) {
|
|
|
18332
18603
|
function modelInvocableTaskSkills(skills) {
|
|
18333
18604
|
return skills.filter((skill) => skill.appliesTo === "task" && skill.modelInvocable);
|
|
18334
18605
|
}
|
|
18335
|
-
function snapshotSkill(skill, invokedBy, home =
|
|
18606
|
+
function snapshotSkill(skill, invokedBy, home = os9.homedir()) {
|
|
18336
18607
|
return { invokedBy, name: skill.name, source: skill.source, path: abbreviateHome(skill.path, home), content: skill.content };
|
|
18337
18608
|
}
|
|
18338
18609
|
function attachSkill(skill, home) {
|
|
18339
18610
|
return { ...snapshotSkill(skill, "user", home), content: "", attaches: { description: skill.description } };
|
|
18340
18611
|
}
|
|
18341
|
-
function resolveSkillInvocation(text4, skillsService, home =
|
|
18612
|
+
function resolveSkillInvocation(text4, skillsService, home = os9.homedir()) {
|
|
18342
18613
|
const skills = skillTokens(text4).flatMap(({ name }) => {
|
|
18343
18614
|
const skill = skillsService.findSkill(name);
|
|
18344
18615
|
if (!skill?.userInvocable) return [];
|
|
@@ -18402,33 +18673,33 @@ ${skillBlock(entry.skill)}`, timestamp: entry.timestamp });
|
|
|
18402
18673
|
}
|
|
18403
18674
|
|
|
18404
18675
|
// src/services/taskSkills.ts
|
|
18405
|
-
import * as
|
|
18676
|
+
import * as path33 from "path";
|
|
18406
18677
|
|
|
18407
18678
|
// src/services/SkillsService.ts
|
|
18408
18679
|
import { createHash as createHash2 } from "crypto";
|
|
18409
18680
|
import * as fs11 from "fs";
|
|
18410
|
-
import * as
|
|
18681
|
+
import * as path32 from "path";
|
|
18411
18682
|
|
|
18412
18683
|
// src/services/builtinSkills.ts
|
|
18413
|
-
import * as
|
|
18684
|
+
import * as path28 from "path";
|
|
18414
18685
|
import { fileURLToPath } from "url";
|
|
18415
18686
|
function builtinSkillsDir() {
|
|
18416
|
-
const dir = typeof __dirname !== "undefined" && __dirname ? __dirname :
|
|
18687
|
+
const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path28.dirname(fileURLToPath(
|
|
18417
18688
|
// @ts-expect-error - import.meta is only valid under an ESM module setting
|
|
18418
18689
|
import.meta.url
|
|
18419
18690
|
));
|
|
18420
|
-
return
|
|
18691
|
+
return path28.join(dir, "..", "skills");
|
|
18421
18692
|
}
|
|
18422
18693
|
|
|
18423
18694
|
// src/services/repoGroup.ts
|
|
18424
18695
|
import * as fs10 from "fs";
|
|
18425
|
-
import * as
|
|
18696
|
+
import * as path31 from "path";
|
|
18426
18697
|
|
|
18427
18698
|
// src/services/worktreeBootstrap.ts
|
|
18428
18699
|
import { exec as exec3 } from "child_process";
|
|
18429
18700
|
import { promisify as promisify4 } from "util";
|
|
18430
18701
|
import * as fs9 from "fs";
|
|
18431
|
-
import * as
|
|
18702
|
+
import * as path30 from "path";
|
|
18432
18703
|
|
|
18433
18704
|
// src/services/gitExec.ts
|
|
18434
18705
|
import { execFile as execFile4 } from "child_process";
|
|
@@ -18510,7 +18781,7 @@ function subcommandOf2(args) {
|
|
|
18510
18781
|
|
|
18511
18782
|
// src/services/worktreeLink.ts
|
|
18512
18783
|
import * as fs8 from "fs";
|
|
18513
|
-
import * as
|
|
18784
|
+
import * as path29 from "path";
|
|
18514
18785
|
function linkPath(source, target, platform, hardLink = fs8.linkSync) {
|
|
18515
18786
|
const isDir = fs8.statSync(source).isDirectory();
|
|
18516
18787
|
if (platform !== "win32") {
|
|
@@ -18535,8 +18806,8 @@ function mirrorDir(source, target, opts) {
|
|
|
18535
18806
|
const mirror = (from, to, prefix, top) => {
|
|
18536
18807
|
fs8.mkdirSync(to, { recursive: true });
|
|
18537
18808
|
for (const entry of fs8.readdirSync(from, { withFileTypes: true })) {
|
|
18538
|
-
const name =
|
|
18539
|
-
const at = { from:
|
|
18809
|
+
const name = path29.join(prefix, entry.name);
|
|
18810
|
+
const at = { from: path29.join(from, entry.name), to: path29.join(to, entry.name), name };
|
|
18540
18811
|
if (entry.isSymbolicLink()) links.push(at);
|
|
18541
18812
|
else if (top && entry.isDirectory() && (entry.name.startsWith("@") || entry.name === ".bin")) mirror(at.from, at.to, name, false);
|
|
18542
18813
|
else if (linkPath(at.from, at.to, opts.platform, opts.hardLink) === "copy") copied.push(name);
|
|
@@ -18556,16 +18827,16 @@ function mirrorDir(source, target, opts) {
|
|
|
18556
18827
|
}
|
|
18557
18828
|
function relink(from, to, opts) {
|
|
18558
18829
|
const text4 = fs8.readlinkSync(from);
|
|
18559
|
-
const pointee =
|
|
18830
|
+
const pointee = path29.isAbsolute(text4) ? rebased(text4, opts) : text4;
|
|
18560
18831
|
if (opts.platform !== "win32") {
|
|
18561
|
-
if (!fs8.existsSync(
|
|
18562
|
-
fs8.symlinkSync(rebased(
|
|
18832
|
+
if (!fs8.existsSync(path29.resolve(path29.dirname(to), pointee)) && fs8.existsSync(path29.resolve(path29.dirname(from), pointee))) {
|
|
18833
|
+
fs8.symlinkSync(rebased(path29.resolve(path29.dirname(from), pointee), opts), to);
|
|
18563
18834
|
return "symlink";
|
|
18564
18835
|
}
|
|
18565
18836
|
fs8.symlinkSync(pointee, to);
|
|
18566
18837
|
return "symlink";
|
|
18567
18838
|
}
|
|
18568
|
-
const resolved = fs8.existsSync(
|
|
18839
|
+
const resolved = fs8.existsSync(path29.resolve(path29.dirname(to), pointee)) ? path29.resolve(path29.dirname(to), pointee) : rebased(path29.resolve(path29.dirname(from), pointee), opts);
|
|
18569
18840
|
try {
|
|
18570
18841
|
fs8.statSync(resolved);
|
|
18571
18842
|
} catch {
|
|
@@ -18574,9 +18845,9 @@ function relink(from, to, opts) {
|
|
|
18574
18845
|
return linkPath(resolved, to, opts.platform, opts.hardLink);
|
|
18575
18846
|
}
|
|
18576
18847
|
function rebased(pointee, opts) {
|
|
18577
|
-
const rel =
|
|
18578
|
-
const inside2 = rel !== "" && !rel.startsWith("..") && !
|
|
18579
|
-
return inside2 && !rel.split(/[\\/]/).includes("node_modules") ?
|
|
18848
|
+
const rel = path29.relative(opts.from, pointee);
|
|
18849
|
+
const inside2 = rel !== "" && !rel.startsWith("..") && !path29.isAbsolute(rel);
|
|
18850
|
+
return inside2 && !rel.split(/[\\/]/).includes("node_modules") ? path29.join(opts.to, rel) : pointee;
|
|
18580
18851
|
}
|
|
18581
18852
|
|
|
18582
18853
|
// src/services/worktreeBootstrap.ts
|
|
@@ -18593,7 +18864,7 @@ function lexists(target) {
|
|
|
18593
18864
|
return false;
|
|
18594
18865
|
}
|
|
18595
18866
|
}
|
|
18596
|
-
function
|
|
18867
|
+
function listDir2(dir) {
|
|
18597
18868
|
try {
|
|
18598
18869
|
return fs9.readdirSync(dir).sort();
|
|
18599
18870
|
} catch {
|
|
@@ -18613,18 +18884,18 @@ function matchLinks(root, patterns) {
|
|
|
18613
18884
|
}
|
|
18614
18885
|
function segmentMatches(root, base, segment) {
|
|
18615
18886
|
const under = (name) => base ? `${base}/${name}` : name;
|
|
18616
|
-
if (!/[*?]/.test(segment)) return lexists(
|
|
18887
|
+
if (!/[*?]/.test(segment)) return lexists(path30.join(root, base, segment)) ? [under(segment)] : [];
|
|
18617
18888
|
const pattern = new RegExp(`^${segment.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}$`);
|
|
18618
|
-
return
|
|
18889
|
+
return listDir2(path30.join(root, base)).filter((name) => !NEVER_SCANNED.has(name) && pattern.test(name)).map(under);
|
|
18619
18890
|
}
|
|
18620
18891
|
function installDirs(root) {
|
|
18621
18892
|
const packages = matchLinks(root, workspaceGlobs(root)).map((pkg) => `${pkg}/node_modules`);
|
|
18622
|
-
return ["node_modules", ...packages].filter((dir) => lexists(
|
|
18893
|
+
return ["node_modules", ...packages].filter((dir) => lexists(path30.join(root, dir)));
|
|
18623
18894
|
}
|
|
18624
18895
|
function workspaceGlobs(root) {
|
|
18625
18896
|
let manifest;
|
|
18626
18897
|
try {
|
|
18627
|
-
manifest = JSON.parse(fs9.readFileSync(
|
|
18898
|
+
manifest = JSON.parse(fs9.readFileSync(path30.join(root, "package.json"), "utf8"));
|
|
18628
18899
|
} catch {
|
|
18629
18900
|
return [];
|
|
18630
18901
|
}
|
|
@@ -18639,19 +18910,19 @@ async function bootstrap(repo, cwd, opts) {
|
|
|
18639
18910
|
const linked = [];
|
|
18640
18911
|
const copied = [];
|
|
18641
18912
|
const link = (name) => {
|
|
18642
|
-
const target =
|
|
18913
|
+
const target = path30.join(cwd, name);
|
|
18643
18914
|
if (lexists(target)) return;
|
|
18644
|
-
fs9.mkdirSync(
|
|
18645
|
-
if (linkPath(
|
|
18915
|
+
fs9.mkdirSync(path30.dirname(target), { recursive: true });
|
|
18916
|
+
if (linkPath(path30.join(repo.root, name), target, opts.platform) === "copy") copied.push(name);
|
|
18646
18917
|
linked.push(name);
|
|
18647
18918
|
};
|
|
18648
18919
|
const mirror = (name) => {
|
|
18649
|
-
const target =
|
|
18650
|
-
if (lexists(target) || !fs9.existsSync(
|
|
18651
|
-
const source =
|
|
18920
|
+
const target = path30.join(cwd, name);
|
|
18921
|
+
if (lexists(target) || !fs9.existsSync(path30.dirname(target))) return;
|
|
18922
|
+
const source = path30.join(repo.root, name);
|
|
18652
18923
|
if (!fs9.lstatSync(source).isDirectory()) return link(name);
|
|
18653
18924
|
const mirrored = mirrorDir(source, target, { platform: opts.platform, from: repo.root, to: cwd });
|
|
18654
|
-
copied.push(...mirrored.map((entry) =>
|
|
18925
|
+
copied.push(...mirrored.map((entry) => path30.join(name, entry)));
|
|
18655
18926
|
linked.push(name);
|
|
18656
18927
|
};
|
|
18657
18928
|
if (!setup) {
|
|
@@ -18686,12 +18957,12 @@ async function runSetup(command, repo, cwd, resolvePath) {
|
|
|
18686
18957
|
|
|
18687
18958
|
// src/services/repoGroup.ts
|
|
18688
18959
|
function hasGitEntry(dir) {
|
|
18689
|
-
return fs10.existsSync(
|
|
18960
|
+
return fs10.existsSync(path31.join(dir, ".git"));
|
|
18690
18961
|
}
|
|
18691
18962
|
function groupPathOf(listed3) {
|
|
18692
18963
|
const slashed = listed3.trim().replace(/\\/g, "/");
|
|
18693
|
-
if (
|
|
18694
|
-
const rel =
|
|
18964
|
+
if (path31.posix.isAbsolute(slashed) || path31.win32.isAbsolute(slashed)) return null;
|
|
18965
|
+
const rel = path31.posix.normalize(slashed).replace(/\/+$/, "");
|
|
18695
18966
|
return rel === "" || rel === "." || rel === ".." || rel.startsWith("../") ? null : rel;
|
|
18696
18967
|
}
|
|
18697
18968
|
function childDirs(dir) {
|
|
@@ -18702,14 +18973,14 @@ function childDirs(dir) {
|
|
|
18702
18973
|
}
|
|
18703
18974
|
}
|
|
18704
18975
|
function groupPaths(workspaceRoot, listed3) {
|
|
18705
|
-
if (listed3.length === 0) return childDirs(workspaceRoot).filter((name) => hasGitEntry(
|
|
18706
|
-
const paths = listed3.map(groupPathOf).filter((rel) => rel !== null && hasGitEntry(
|
|
18976
|
+
if (listed3.length === 0) return childDirs(workspaceRoot).filter((name) => hasGitEntry(path31.join(workspaceRoot, name)));
|
|
18977
|
+
const paths = listed3.map(groupPathOf).filter((rel) => rel !== null && hasGitEntry(path31.join(workspaceRoot, rel)));
|
|
18707
18978
|
return [...new Set(paths)].sort();
|
|
18708
18979
|
}
|
|
18709
18980
|
function workspaceRepoGroup(workspaceRoot, listed3) {
|
|
18710
|
-
for (let dir =
|
|
18981
|
+
for (let dir = path31.resolve(workspaceRoot); ; dir = path31.dirname(dir)) {
|
|
18711
18982
|
if (hasGitEntry(dir)) return [SELF_REPO];
|
|
18712
|
-
if (
|
|
18983
|
+
if (path31.dirname(dir) === dir) break;
|
|
18713
18984
|
}
|
|
18714
18985
|
return groupPaths(workspaceRoot, listed3);
|
|
18715
18986
|
}
|
|
@@ -18790,8 +19061,8 @@ function copyDirSync(src, dest) {
|
|
|
18790
19061
|
fs11.mkdirSync(dest, { recursive: true });
|
|
18791
19062
|
const entries = fs11.readdirSync(src, { withFileTypes: true });
|
|
18792
19063
|
for (const entry of entries) {
|
|
18793
|
-
const srcPath =
|
|
18794
|
-
const destPath =
|
|
19064
|
+
const srcPath = path32.join(src, entry.name);
|
|
19065
|
+
const destPath = path32.join(dest, entry.name);
|
|
18795
19066
|
if (entry.isDirectory()) copyDirSync(srcPath, destPath);
|
|
18796
19067
|
else fs11.copyFileSync(srcPath, destPath);
|
|
18797
19068
|
}
|
|
@@ -18815,7 +19086,7 @@ function readDir(dir, source) {
|
|
|
18815
19086
|
if (!fs11.existsSync(dir)) return { skills, invalid };
|
|
18816
19087
|
for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
|
|
18817
19088
|
if (!entry.isDirectory()) continue;
|
|
18818
|
-
const skillFile =
|
|
19089
|
+
const skillFile = path32.join(dir, entry.name, "SKILL.md");
|
|
18819
19090
|
if (!fs11.existsSync(skillFile)) continue;
|
|
18820
19091
|
if (!isSkillName(entry.name)) {
|
|
18821
19092
|
invalid.push({ name: entry.name, path: skillFile, source, reason: INVALID_SKILL_NAME_REASON });
|
|
@@ -18834,15 +19105,15 @@ function skillFileIn(dir, name) {
|
|
|
18834
19105
|
return void 0;
|
|
18835
19106
|
}
|
|
18836
19107
|
if (!entries.some((entry) => entry.isDirectory() && entry.name === name)) return void 0;
|
|
18837
|
-
const skillFile =
|
|
19108
|
+
const skillFile = path32.join(dir, name, "SKILL.md");
|
|
18838
19109
|
return fs11.existsSync(skillFile) ? skillFile : void 0;
|
|
18839
19110
|
}
|
|
18840
19111
|
function skillsDirOf(root) {
|
|
18841
|
-
return
|
|
19112
|
+
return path32.join(root, STATE_DIR, "skills");
|
|
18842
19113
|
}
|
|
18843
19114
|
function workspaceSkillRoots(workspaceRoot, repos, checkout = workspaceRoot) {
|
|
18844
19115
|
if (repos.length === 0 || repos.includes(SELF_REPO)) return [checkout];
|
|
18845
|
-
return [workspaceRoot, ...repos.map((repo) =>
|
|
19116
|
+
return [workspaceRoot, ...repos.map((repo) => path32.join(checkout, repo))];
|
|
18846
19117
|
}
|
|
18847
19118
|
var SkillsService = class _SkillsService {
|
|
18848
19119
|
workspaceRoots;
|
|
@@ -18865,11 +19136,11 @@ var SkillsService = class _SkillsService {
|
|
|
18865
19136
|
return this.workspaceRoots.map(skillsDirOf);
|
|
18866
19137
|
}
|
|
18867
19138
|
globalDir() {
|
|
18868
|
-
return
|
|
19139
|
+
return path32.join(globalDataDir(), "skills");
|
|
18869
19140
|
}
|
|
18870
19141
|
readSeedManifest() {
|
|
18871
19142
|
try {
|
|
18872
|
-
const parsed = JSON.parse(fs11.readFileSync(
|
|
19143
|
+
const parsed = JSON.parse(fs11.readFileSync(path32.join(this.globalDir(), SEED_MANIFEST), "utf8"));
|
|
18873
19144
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
18874
19145
|
return Object.fromEntries(
|
|
18875
19146
|
Object.entries(parsed).filter((entry) => typeof entry[1] === "string")
|
|
@@ -18885,7 +19156,7 @@ var SkillsService = class _SkillsService {
|
|
|
18885
19156
|
manifest[name] = hash;
|
|
18886
19157
|
try {
|
|
18887
19158
|
fs11.mkdirSync(this.globalDir(), { recursive: true });
|
|
18888
|
-
fs11.writeFileSync(
|
|
19159
|
+
fs11.writeFileSync(path32.join(this.globalDir(), SEED_MANIFEST), JSON.stringify(manifest, null, 2));
|
|
18889
19160
|
} catch (err) {
|
|
18890
19161
|
console.warn(`Could not record seeded skill ${name}: ${String(err)}`);
|
|
18891
19162
|
}
|
|
@@ -18896,11 +19167,11 @@ var SkillsService = class _SkillsService {
|
|
|
18896
19167
|
return this.readSeedManifest()[name] === currentHash || (PRIOR_BUILTIN_HASHES[name] ?? []).includes(currentHash);
|
|
18897
19168
|
}
|
|
18898
19169
|
seed(name) {
|
|
18899
|
-
const dest =
|
|
19170
|
+
const dest = path32.join(this.globalDir(), name);
|
|
18900
19171
|
const exists = fs11.existsSync(dest);
|
|
18901
19172
|
let srcDir;
|
|
18902
19173
|
try {
|
|
18903
|
-
srcDir =
|
|
19174
|
+
srcDir = path32.join(builtinSkillsDir(), name);
|
|
18904
19175
|
} catch (err) {
|
|
18905
19176
|
if (!exists) console.warn(`Could not locate built-in skills dir: ${String(err)}`);
|
|
18906
19177
|
return false;
|
|
@@ -18909,9 +19180,9 @@ var SkillsService = class _SkillsService {
|
|
|
18909
19180
|
if (!exists) console.warn(`Built-in skill not found in package: ${name}`);
|
|
18910
19181
|
return false;
|
|
18911
19182
|
}
|
|
18912
|
-
const srcHash = fileHash(
|
|
19183
|
+
const srcHash = fileHash(path32.join(srcDir, "SKILL.md"));
|
|
18913
19184
|
if (exists) {
|
|
18914
|
-
const currentHash = fileHash(
|
|
19185
|
+
const currentHash = fileHash(path32.join(dest, "SKILL.md"));
|
|
18915
19186
|
if (currentHash !== void 0 && currentHash === srcHash) {
|
|
18916
19187
|
this.recordSeed(name, currentHash);
|
|
18917
19188
|
return false;
|
|
@@ -18955,7 +19226,7 @@ var SkillsService = class _SkillsService {
|
|
|
18955
19226
|
if (entries.length !== 1 || entries[0].name !== "SKILL.md" || !entries[0].isFile()) return false;
|
|
18956
19227
|
let raw;
|
|
18957
19228
|
try {
|
|
18958
|
-
raw = readSkillText(
|
|
19229
|
+
raw = readSkillText(path32.join(dir, "SKILL.md"));
|
|
18959
19230
|
} catch {
|
|
18960
19231
|
return false;
|
|
18961
19232
|
}
|
|
@@ -18963,7 +19234,7 @@ var SkillsService = class _SkillsService {
|
|
|
18963
19234
|
}
|
|
18964
19235
|
pruneRetired() {
|
|
18965
19236
|
for (const name of RETIRED_BUILTIN_SKILL_NAMES) {
|
|
18966
|
-
const dest =
|
|
19237
|
+
const dest = path32.join(this.globalDir(), name);
|
|
18967
19238
|
if (!fs11.existsSync(dest)) continue;
|
|
18968
19239
|
if (!this.isUnmodifiedRetiredSeed(dest, name)) continue;
|
|
18969
19240
|
try {
|
|
@@ -19101,12 +19372,12 @@ var runGit = execFileWithTimeout(1e4);
|
|
|
19101
19372
|
async function committedInHead(files) {
|
|
19102
19373
|
const byDir = /* @__PURE__ */ new Map();
|
|
19103
19374
|
for (const file of new Set(files)) {
|
|
19104
|
-
const dir =
|
|
19375
|
+
const dir = path33.dirname(path33.dirname(file));
|
|
19105
19376
|
byDir.set(dir, [...byDir.get(dir) ?? [], file]);
|
|
19106
19377
|
}
|
|
19107
19378
|
const result = /* @__PURE__ */ new Map();
|
|
19108
19379
|
await Promise.all([...byDir].map(async ([dir, inDir]) => {
|
|
19109
|
-
const relative17 = (file) =>
|
|
19380
|
+
const relative17 = (file) => path33.relative(dir, file).split(path33.sep).join("/");
|
|
19110
19381
|
try {
|
|
19111
19382
|
const { stdout } = await runGit("git", ["ls-tree", "--name-only", "HEAD", "--", ...inDir.map(relative17)], { cwd: dir, env: cleanEnv() });
|
|
19112
19383
|
const listed3 = new Set(stdout.split("\n").map((line) => line.trim()).filter(Boolean));
|
|
@@ -19126,9 +19397,9 @@ function plannedSkillLookup(skillsAt, workspaceRoot, layout) {
|
|
|
19126
19397
|
searchedDirs: () => lookup2.searchedDirs(),
|
|
19127
19398
|
listSkills: () => lookup2.listSkills(),
|
|
19128
19399
|
uncommitted: async (skills) => {
|
|
19129
|
-
const checked = skills.filter((skill) => skill.source === "workspace" && !(readInPlace &&
|
|
19400
|
+
const checked = skills.filter((skill) => skill.source === "workspace" && !(readInPlace && path33.dirname(path33.dirname(skill.path)) === readInPlace));
|
|
19130
19401
|
const committed = await committedInHead(checked.map((skill) => skill.path));
|
|
19131
|
-
return new Map(checked.filter((skill) => committed.get(skill.path) === false).map((skill) => [skill.path,
|
|
19402
|
+
return new Map(checked.filter((skill) => committed.get(skill.path) === false).map((skill) => [skill.path, path33.relative(workspaceRoot, path33.dirname(skill.path)).split(path33.sep).join("/")]));
|
|
19132
19403
|
}
|
|
19133
19404
|
};
|
|
19134
19405
|
}
|
|
@@ -19141,13 +19412,13 @@ function checkPlanSkills(tasks, lookup2) {
|
|
|
19141
19412
|
function checkTaskSkillsEdit(task2, skills, lookup2) {
|
|
19142
19413
|
return check([{ taskId: task2.id, owner: `Task "${task2.title}"`, ...attached(skills) }], lookup2);
|
|
19143
19414
|
}
|
|
19144
|
-
function subtaskEntries(op, spec,
|
|
19415
|
+
function subtaskEntries(op, spec, path49 = []) {
|
|
19145
19416
|
const subtasks = spec?.subtasks;
|
|
19146
19417
|
if (!Array.isArray(subtasks)) return [];
|
|
19147
19418
|
return subtasks.flatMap((sub, i) => {
|
|
19148
19419
|
if (typeof sub !== "object" || sub === null) return [];
|
|
19149
19420
|
const child = sub;
|
|
19150
|
-
const at = [...
|
|
19421
|
+
const at = [...path49, typeof child.title === "string" && child.title.trim() ? `"${child.title}"` : `#${i + 1}`];
|
|
19151
19422
|
return [{ op, owner: `subtask ${at.join(" > ")}`, ...attached(child.skills) }, ...subtaskEntries(op, child, at)];
|
|
19152
19423
|
});
|
|
19153
19424
|
}
|
|
@@ -22989,7 +23260,7 @@ var RUN_SETTING_KEYS = Object.keys(RUN_SETTING_CHECKS);
|
|
|
22989
23260
|
function getSettingsPath() {
|
|
22990
23261
|
const override = process.env.ORDEWELL_SETTINGS_PATH;
|
|
22991
23262
|
if (override && override.trim()) return override.trim();
|
|
22992
|
-
return
|
|
23263
|
+
return path34.join(globalDataDir(), "settings.json");
|
|
22993
23264
|
}
|
|
22994
23265
|
function readPlannerModels(raw) {
|
|
22995
23266
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
|
|
@@ -23069,8 +23340,8 @@ var SettingsService = class {
|
|
|
23069
23340
|
* a watch on the old one pointing at nothing.
|
|
23070
23341
|
*/
|
|
23071
23342
|
watch(listener) {
|
|
23072
|
-
const dir =
|
|
23073
|
-
const name =
|
|
23343
|
+
const dir = path34.dirname(this.filePath);
|
|
23344
|
+
const name = path34.basename(this.filePath);
|
|
23074
23345
|
let seen = this.fileStamp();
|
|
23075
23346
|
let timer;
|
|
23076
23347
|
let dead = false;
|
|
@@ -23079,7 +23350,7 @@ var SettingsService = class {
|
|
|
23079
23350
|
fs12.mkdirSync(dir, { recursive: true });
|
|
23080
23351
|
watcher = fs12.watch(dir, (_event, changed) => {
|
|
23081
23352
|
if (dead) return;
|
|
23082
|
-
if (changed && changed !== name && changed !==
|
|
23353
|
+
if (changed && changed !== name && changed !== path34.basename(dir)) return;
|
|
23083
23354
|
clearTimeout(timer);
|
|
23084
23355
|
timer = setTimeout(() => {
|
|
23085
23356
|
const stamp2 = this.fileStamp();
|
|
@@ -23312,7 +23583,7 @@ var SettingsService = class {
|
|
|
23312
23583
|
if (this.unreadable) {
|
|
23313
23584
|
throw new Error(`Cannot save settings: ${this.filePath} could not be read, so it will not be overwritten. Fix or remove it and try again.`);
|
|
23314
23585
|
}
|
|
23315
|
-
const dir =
|
|
23586
|
+
const dir = path34.dirname(this.filePath);
|
|
23316
23587
|
if (!fs12.existsSync(dir)) {
|
|
23317
23588
|
fs12.mkdirSync(dir, { recursive: true });
|
|
23318
23589
|
}
|
|
@@ -23756,7 +24027,7 @@ var BaseConfig = class {
|
|
|
23756
24027
|
const { model, effort, override = "" } = configured;
|
|
23757
24028
|
const named3 = isPlannerBackend(configured.setting ?? "") || isPlannerBackend(process.env.AI_PROVIDER ?? "");
|
|
23758
24029
|
const pickedElsewhere = configured.pickedFor !== void 0 && configured.pickedFor !== provider;
|
|
23759
|
-
const kept = configured.pickedFor === provider || !pickedElsewhere && (isApiProvider(provider) || named3 && !isForeignModel(provider, model, this.agentCatalog(provider)));
|
|
24030
|
+
const kept = configured.pickedFor === provider || !pickedElsewhere && (isApiProvider(provider) || named3 && !isForeignModel(provider, model, () => this.agentCatalog(provider)));
|
|
23760
24031
|
if (kept) return { model: override || model, effort, source: override ? "request" : "configured" };
|
|
23761
24032
|
const own = pickedElsewhere || named3 ? rememberedOrDefault(provider) : detectedAgentChoice(provider, configured);
|
|
23762
24033
|
return !override || override === own.model ? own : { model: override, effort: "", source: "request" };
|
|
@@ -23765,7 +24036,8 @@ var BaseConfig = class {
|
|
|
23765
24036
|
* The model ids a coding agent was last discovered serving, empty while
|
|
23766
24037
|
* discovery is cold. A host that holds a runner catalog supplies it: it is
|
|
23767
24038
|
* the only evidence that tells OpenCode's `provider/model` ids from an
|
|
23768
|
-
* OpenRouter slug.
|
|
24039
|
+
* OpenRouter slug. Asked only when a named agent's configured model is
|
|
24040
|
+
* neither its alias nor its remembered pick.
|
|
23769
24041
|
*/
|
|
23770
24042
|
agentCatalog(_provider) {
|
|
23771
24043
|
return [];
|
|
@@ -23790,8 +24062,10 @@ function isVendorModelFor(agent, model) {
|
|
|
23790
24062
|
return !discoveryOf(agent)?.providerQualifiedIds && SLUG_SHAPE.test(model);
|
|
23791
24063
|
}
|
|
23792
24064
|
function isForeignModel(agent, model, catalog) {
|
|
23793
|
-
if (!model || isStableAlias(agent, model) ||
|
|
23794
|
-
|
|
24065
|
+
if (!model || isStableAlias(agent, model) || rememberedChoice(agent)?.model === model) return false;
|
|
24066
|
+
const listed3 = catalog();
|
|
24067
|
+
if (listed3.includes(model)) return false;
|
|
24068
|
+
return isVendorModelFor(agent, model) || listed3.length > 0 && SLUG_SHAPE.test(model);
|
|
23795
24069
|
}
|
|
23796
24070
|
function rememberedChoice(provider) {
|
|
23797
24071
|
const entry = new SettingsService().getPlannerModel(provider);
|
|
@@ -23826,11 +24100,11 @@ var EnvConfig = class extends BaseConfig {
|
|
|
23826
24100
|
// src/services/taskDiffRead.ts
|
|
23827
24101
|
import { createHash as createHash3 } from "crypto";
|
|
23828
24102
|
import * as fs13 from "fs";
|
|
23829
|
-
import * as
|
|
24103
|
+
import * as path35 from "path";
|
|
23830
24104
|
import { devNull } from "os";
|
|
23831
24105
|
var TASK_DIFF_UNAVAILABLE = "Task diff is not available for this run: no task-specific changes were recorded (shared runs cannot attribute workspace changes to one task).";
|
|
23832
24106
|
function allowedPath(file) {
|
|
23833
|
-
return file.length > 0 && !
|
|
24107
|
+
return file.length > 0 && !path35.isAbsolute(file) && !/^[A-Za-z]:/.test(file) && !file.split(/[\\/]/).some((part) => part === ".." || part === ".git" || part === ".ordewell");
|
|
23834
24108
|
}
|
|
23835
24109
|
function stats(raw) {
|
|
23836
24110
|
return raw.split("\0").flatMap((row) => {
|
|
@@ -23839,8 +24113,8 @@ function stats(raw) {
|
|
|
23839
24113
|
});
|
|
23840
24114
|
}
|
|
23841
24115
|
function inside(root, target) {
|
|
23842
|
-
const rel =
|
|
23843
|
-
return rel === "" || !rel.startsWith(`..${
|
|
24116
|
+
const rel = path35.relative(root, target);
|
|
24117
|
+
return rel === "" || !rel.startsWith(`..${path35.sep}`) && rel !== ".." && !path35.isAbsolute(rel);
|
|
23844
24118
|
}
|
|
23845
24119
|
function unquoted(quoted4) {
|
|
23846
24120
|
const bytes = [];
|
|
@@ -23888,10 +24162,10 @@ function patchFiles(patch) {
|
|
|
23888
24162
|
const sections = patch.toString("utf8").split(/(?<![^\n])(?=diff --git )/).filter((section) => section.startsWith("diff --git "));
|
|
23889
24163
|
const files = /* @__PURE__ */ new Map();
|
|
23890
24164
|
for (const section of sections) {
|
|
23891
|
-
const
|
|
24165
|
+
const path49 = sectionPath(section);
|
|
23892
24166
|
const counts = sectionStats(section);
|
|
23893
|
-
const earlier = files.get(
|
|
23894
|
-
files.set(
|
|
24167
|
+
const earlier = files.get(path49);
|
|
24168
|
+
files.set(path49, earlier ? { path: path49, section: earlier.section + section, added: summed(earlier.added, counts.added), removed: summed(earlier.removed, counts.removed) } : { path: path49, section, ...counts });
|
|
23895
24169
|
}
|
|
23896
24170
|
return [...files.values()];
|
|
23897
24171
|
}
|
|
@@ -23961,11 +24235,11 @@ async function readTaskDiff(invoker, run, taskId, options) {
|
|
|
23961
24235
|
files.push({ ...file, repo: repo.path, patch: () => git(reader, cwd, [...diffArgs, "--", `:(top,literal)${prefix}${file.path}`]) });
|
|
23962
24236
|
}
|
|
23963
24237
|
if (live2) {
|
|
23964
|
-
const workspace =
|
|
24238
|
+
const workspace = path35.join(entry.worktree, prefix);
|
|
23965
24239
|
const others = await git(reader, workspace, [...common, "ls-files", "--others", "--exclude-standard", "-z", "--", "."]);
|
|
23966
24240
|
for (const file of others.split("\0").filter((f) => allowed(f))) {
|
|
23967
|
-
const full =
|
|
23968
|
-
if (!inside(fs13.realpathSync(workspace), fs13.realpathSync(
|
|
24241
|
+
const full = path35.join(workspace, file);
|
|
24242
|
+
if (!inside(fs13.realpathSync(workspace), fs13.realpathSync(path35.dirname(full)))) continue;
|
|
23969
24243
|
const untracked = async (numstat) => {
|
|
23970
24244
|
const result = await tryGit(reader, workspace, [...common, "diff", "--no-index", "--no-renames", "--no-ext-diff", "--no-textconv", ...numstat ? ["--numstat", "-z"] : [], "--", devNull, file]);
|
|
23971
24245
|
if (!result.ok && result.code !== 1) throw new Error(result.stderr.trim() || "Could not read an untracked file diff.");
|
|
@@ -23995,11 +24269,11 @@ ${await file.patch()}`);
|
|
|
23995
24269
|
import { randomBytes as randomBytes6 } from "crypto";
|
|
23996
24270
|
import { spawnSync } from "child_process";
|
|
23997
24271
|
import * as fs15 from "fs";
|
|
23998
|
-
import * as
|
|
24272
|
+
import * as path37 from "path";
|
|
23999
24273
|
|
|
24000
24274
|
// src/utils/prdStore.ts
|
|
24001
24275
|
import * as fs14 from "fs";
|
|
24002
|
-
import * as
|
|
24276
|
+
import * as path36 from "path";
|
|
24003
24277
|
var PRD_BLOCK_RE = /<!--\s*ORDEWELL_PRD_START\s+slug="([^"]+)"\s*-->\s*([\s\S]*?)\s*<!--\s*ORDEWELL_PRD_END\s*-->/;
|
|
24004
24278
|
function extractPrdBlock(text4) {
|
|
24005
24279
|
const match = PRD_BLOCK_RE.exec(text4);
|
|
@@ -24013,9 +24287,9 @@ function sanitizeSlug(raw) {
|
|
|
24013
24287
|
return raw.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
24014
24288
|
}
|
|
24015
24289
|
function savePrdMarkdown(workspace, slug, markdown) {
|
|
24016
|
-
const dir =
|
|
24290
|
+
const dir = path36.join(workspace, ".scratch", sanitizeSlug(slug));
|
|
24017
24291
|
fs14.mkdirSync(dir, { recursive: true });
|
|
24018
|
-
const file =
|
|
24292
|
+
const file = path36.join(dir, "PRD.md");
|
|
24019
24293
|
fs14.writeFileSync(file, markdown.endsWith("\n") ? markdown : markdown + "\n", "utf8");
|
|
24020
24294
|
return file;
|
|
24021
24295
|
}
|
|
@@ -24047,12 +24321,12 @@ var NESTED_REPO_DEPTH = 2;
|
|
|
24047
24321
|
var GITLINK_MODE = "160000";
|
|
24048
24322
|
var READ_ONLY = ["--no-optional-locks", "-c", "core.fsmonitor=false"];
|
|
24049
24323
|
function isInside(parent, child) {
|
|
24050
|
-
const rel =
|
|
24051
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
24324
|
+
const rel = path37.relative(parent, child);
|
|
24325
|
+
return rel !== "" && !rel.startsWith("..") && !path37.isAbsolute(rel);
|
|
24052
24326
|
}
|
|
24053
24327
|
function resolvesAsNamed(base, p) {
|
|
24054
24328
|
try {
|
|
24055
|
-
return fs15.realpathSync(p) ===
|
|
24329
|
+
return fs15.realpathSync(p) === path37.join(fs15.realpathSync(base), path37.relative(base, p));
|
|
24056
24330
|
} catch {
|
|
24057
24331
|
return false;
|
|
24058
24332
|
}
|
|
@@ -24107,7 +24381,7 @@ function sameDir(a, b) {
|
|
|
24107
24381
|
try {
|
|
24108
24382
|
return fs15.realpathSync.native(p);
|
|
24109
24383
|
} catch {
|
|
24110
|
-
return
|
|
24384
|
+
return path37.resolve(p);
|
|
24111
24385
|
}
|
|
24112
24386
|
};
|
|
24113
24387
|
return real(a) === real(b);
|
|
@@ -24129,7 +24403,7 @@ function clearImmutableFlags(platform, dir) {
|
|
|
24129
24403
|
spawnSync("/usr/bin/chflags", ["-R", "nouchg", dir], { stdio: "ignore" });
|
|
24130
24404
|
}
|
|
24131
24405
|
function leftoverLinks(dir) {
|
|
24132
|
-
const named3 =
|
|
24406
|
+
const named3 = listDir2(dir).filter((name) => LINKED_ARTIFACTS.has(name) || isEnvFile(name) || isLink(path37.join(dir, name)));
|
|
24133
24407
|
return [.../* @__PURE__ */ new Set([...named3, ...installDirs(dir)])];
|
|
24134
24408
|
}
|
|
24135
24409
|
function isLink(target) {
|
|
@@ -24202,7 +24476,7 @@ var GitWorktreeIsolation = class {
|
|
|
24202
24476
|
const head = await this.headOf(root);
|
|
24203
24477
|
const repo = { path: repoPath, head };
|
|
24204
24478
|
if (scan.toplevel !== void 0) {
|
|
24205
|
-
const top =
|
|
24479
|
+
const top = path37.relative(workspaceRoot, scan.toplevel).split(path37.sep).join("/");
|
|
24206
24480
|
if (top !== "") repo.top = top;
|
|
24207
24481
|
}
|
|
24208
24482
|
if (enabled && (head.kind === "branch" || head.kind === "detached")) {
|
|
@@ -24259,9 +24533,9 @@ var GitWorktreeIsolation = class {
|
|
|
24259
24533
|
async nestedRepos(workspaceRoot, toplevel) {
|
|
24260
24534
|
const found = [];
|
|
24261
24535
|
const walk = (rel, depth) => {
|
|
24262
|
-
for (const name of childDirs(
|
|
24536
|
+
for (const name of childDirs(path37.join(workspaceRoot, rel))) {
|
|
24263
24537
|
const child = rel ? `${rel}/${name}` : name;
|
|
24264
|
-
if (hasGitEntry(
|
|
24538
|
+
if (hasGitEntry(path37.join(workspaceRoot, child))) found.push(child);
|
|
24265
24539
|
else if (depth < NESTED_REPO_DEPTH) walk(child, depth + 1);
|
|
24266
24540
|
}
|
|
24267
24541
|
};
|
|
@@ -24279,7 +24553,7 @@ var GitWorktreeIsolation = class {
|
|
|
24279
24553
|
const [meta, file] = entry.split(" ");
|
|
24280
24554
|
if (file !== void 0 && meta.startsWith(`${GITLINK_MODE} `)) paths.add(file);
|
|
24281
24555
|
}
|
|
24282
|
-
const declared = await this.tryGit(workspaceRoot, ["config", "-z", "-f",
|
|
24556
|
+
const declared = await this.tryGit(workspaceRoot, ["config", "-z", "-f", path37.join(toplevel, ".gitmodules"), "--get-regexp", "^submodule\\..*\\.path$"]);
|
|
24283
24557
|
for (const entry of declared.stdout.split("\0")) {
|
|
24284
24558
|
const value = entry.split("\n")[1];
|
|
24285
24559
|
if (value) paths.add(value.replace(/\/+$/, ""));
|
|
@@ -24337,7 +24611,7 @@ var GitWorktreeIsolation = class {
|
|
|
24337
24611
|
async acceptsWorktree(run, repo) {
|
|
24338
24612
|
const dir = this.integrationDir(run, repo);
|
|
24339
24613
|
ensureStateDirIgnored(run.workspaceRoot);
|
|
24340
|
-
fs15.mkdirSync(
|
|
24614
|
+
fs15.mkdirSync(path37.dirname(dir), { recursive: true });
|
|
24341
24615
|
const added = await this.tryGit(repo.root, ["worktree", "add", "-q", "--no-checkout", dir, repo.integrationBranch]);
|
|
24342
24616
|
await this.removeWorktreeDir(run, repo, dir, []);
|
|
24343
24617
|
return added.ok;
|
|
@@ -24352,12 +24626,12 @@ var GitWorktreeIsolation = class {
|
|
|
24352
24626
|
if (isolated.includes(SELF_REPO)) return [];
|
|
24353
24627
|
const shared2 = [];
|
|
24354
24628
|
const walk = (rel) => {
|
|
24355
|
-
for (const name of
|
|
24629
|
+
for (const name of listDir2(path37.join(workspaceRoot, rel))) {
|
|
24356
24630
|
if (!rel && (name === STATE_DIR || name === ".git")) continue;
|
|
24357
24631
|
const child = rel ? `${rel}/${name}` : name;
|
|
24358
24632
|
if (isolated.includes(child)) continue;
|
|
24359
24633
|
if (isolated.some((p) => p.startsWith(`${child}/`))) walk(child);
|
|
24360
|
-
else if (resolves(
|
|
24634
|
+
else if (resolves(path37.join(workspaceRoot, child))) shared2.push(child);
|
|
24361
24635
|
}
|
|
24362
24636
|
};
|
|
24363
24637
|
walk("");
|
|
@@ -24397,9 +24671,9 @@ var GitWorktreeIsolation = class {
|
|
|
24397
24671
|
try {
|
|
24398
24672
|
ensureStateDirIgnored(run.workspaceRoot);
|
|
24399
24673
|
for (const repo of run.repos) {
|
|
24400
|
-
const worktree =
|
|
24674
|
+
const worktree = path37.join(dir, repo.path);
|
|
24401
24675
|
const tip = (await this.git(repo.root, ["rev-parse", repo.integrationBranch])).trim();
|
|
24402
|
-
fs15.mkdirSync(
|
|
24676
|
+
fs15.mkdirSync(path37.dirname(worktree), { recursive: true });
|
|
24403
24677
|
await this.git(repo.root, ["worktree", "add", "-q", "-B", branch, worktree, tip]);
|
|
24404
24678
|
record.repos[repo.path] = { worktree, linked: [], baseRef: tip };
|
|
24405
24679
|
}
|
|
@@ -24424,13 +24698,13 @@ var GitWorktreeIsolation = class {
|
|
|
24424
24698
|
setupFailed.push({ repo: repo.path, detail: err.detail });
|
|
24425
24699
|
}
|
|
24426
24700
|
entry.linked = boot.linked;
|
|
24427
|
-
copied.push(...boot.copied.map((name) =>
|
|
24701
|
+
copied.push(...boot.copied.map((name) => path37.posix.join(repo.path, name)));
|
|
24428
24702
|
}
|
|
24429
24703
|
for (const rel of run.shared) {
|
|
24430
|
-
const source =
|
|
24431
|
-
const target =
|
|
24704
|
+
const source = path37.join(run.workspaceRoot, rel);
|
|
24705
|
+
const target = path37.join(cwd, rel);
|
|
24432
24706
|
if (!resolves(source) || lexists(target)) continue;
|
|
24433
|
-
fs15.mkdirSync(
|
|
24707
|
+
fs15.mkdirSync(path37.dirname(target), { recursive: true });
|
|
24434
24708
|
if (linkPath(source, target, this.platform) === "copy") copied.push(rel);
|
|
24435
24709
|
const entry = self ? record.repos[SELF_REPO] : void 0;
|
|
24436
24710
|
if (entry) entry.linked.push(rel);
|
|
@@ -24462,7 +24736,7 @@ var GitWorktreeIsolation = class {
|
|
|
24462
24736
|
*/
|
|
24463
24737
|
async clearLeftovers(run, task2, names) {
|
|
24464
24738
|
const removal = noRemoval();
|
|
24465
|
-
const holder = Object.values(run.tasks).find((r) => r.taskId !== task2.id && (r.branch === names.branch ||
|
|
24739
|
+
const holder = Object.values(run.tasks).find((r) => r.taskId !== task2.id && (r.branch === names.branch || path37.resolve(r.workspace) === path37.resolve(names.dir) || isInside(r.workspace, names.dir)));
|
|
24466
24740
|
if (holder) throw new Error(`Its worktree path or branch is held by the record of task ${holder.order} (${holder.title}); Ordewell will not remove it`);
|
|
24467
24741
|
const found = [];
|
|
24468
24742
|
for (const repo of run.repos) {
|
|
@@ -24472,14 +24746,14 @@ var GitWorktreeIsolation = class {
|
|
|
24472
24746
|
worktree: /^worktree (.+)$/m.exec(block)?.[1],
|
|
24473
24747
|
branch: /^branch refs\/heads\/(.+)$/m.exec(block)?.[1]
|
|
24474
24748
|
}));
|
|
24475
|
-
const at = /* @__PURE__ */ new Set([
|
|
24749
|
+
const at = /* @__PURE__ */ new Set([path37.join(names.dir, repo.path)]);
|
|
24476
24750
|
for (const entry of registered) {
|
|
24477
|
-
if (entry.worktree && entry.branch === names.branch && isInside(this.runRoot(run),
|
|
24751
|
+
if (entry.worktree && entry.branch === names.branch && isInside(this.runRoot(run), path37.resolve(entry.worktree))) at.add(entry.worktree);
|
|
24478
24752
|
}
|
|
24479
|
-
const base = { branch: names.branch, name:
|
|
24753
|
+
const base = { branch: names.branch, name: path37.basename(names.dir), title: task2.title };
|
|
24480
24754
|
for (const worktree of at) {
|
|
24481
24755
|
if (!fs15.existsSync(worktree)) continue;
|
|
24482
|
-
const linked = leftoverLinks(
|
|
24756
|
+
const linked = leftoverLinks(path37.join(worktree, await this.prefixOf(repo)));
|
|
24483
24757
|
found.push({ repo, worktree, target: { ...base, worktree, linked } });
|
|
24484
24758
|
}
|
|
24485
24759
|
found.push({ repo, worktree: names.dir, target: { ...base, linked: [] } });
|
|
@@ -24572,7 +24846,7 @@ var GitWorktreeIsolation = class {
|
|
|
24572
24846
|
*/
|
|
24573
24847
|
async inWorkspacePlace(repo, worktree) {
|
|
24574
24848
|
const prefix = await this.prefixOf(repo);
|
|
24575
|
-
return prefix ?
|
|
24849
|
+
return prefix ? path37.join(worktree, prefix) : worktree;
|
|
24576
24850
|
}
|
|
24577
24851
|
async prefixOf(repo) {
|
|
24578
24852
|
return (await this.git(repo.root, ["rev-parse", "--show-prefix"])).trim();
|
|
@@ -25102,15 +25376,15 @@ var GitWorktreeIsolation = class {
|
|
|
25102
25376
|
return landingMessage(kind, from, this.deps.plannedWith?.() ?? false);
|
|
25103
25377
|
}
|
|
25104
25378
|
integrationDir(run, repo) {
|
|
25105
|
-
return
|
|
25379
|
+
return path37.join(this.runRoot(run), INTEGRATION_DIR, repo.path);
|
|
25106
25380
|
}
|
|
25107
25381
|
async ensureIntegrationWorktree(run, repo) {
|
|
25108
25382
|
return this.admin(run.workspaceRoot, async () => {
|
|
25109
25383
|
const dir = this.integrationDir(run, repo);
|
|
25110
|
-
if (fs15.existsSync(
|
|
25384
|
+
if (fs15.existsSync(path37.join(dir, ".git"))) return dir;
|
|
25111
25385
|
await this.tryGit(repo.root, ["worktree", "prune"]);
|
|
25112
25386
|
ensureStateDirIgnored(run.workspaceRoot);
|
|
25113
|
-
fs15.mkdirSync(
|
|
25387
|
+
fs15.mkdirSync(path37.dirname(dir), { recursive: true });
|
|
25114
25388
|
await this.git(repo.root, ["worktree", "add", "-q", dir, repo.integrationBranch]);
|
|
25115
25389
|
return dir;
|
|
25116
25390
|
});
|
|
@@ -25133,7 +25407,7 @@ var GitWorktreeIsolation = class {
|
|
|
25133
25407
|
const removal = noRemoval();
|
|
25134
25408
|
for (const repo of opts.preserve === false || record.readOnly ? [] : run.repos) {
|
|
25135
25409
|
const entry = record.repos[repo.path];
|
|
25136
|
-
const target = { worktree: entry?.worktree, linked: entry?.linked ?? [], branch: record.branch, name:
|
|
25410
|
+
const target = { worktree: entry?.worktree, linked: entry?.linked ?? [], branch: record.branch, name: path37.basename(record.workspace), title: record.title, commit: record.commit };
|
|
25137
25411
|
try {
|
|
25138
25412
|
const kept = await this.preserve(run, repo, target, opts.intent);
|
|
25139
25413
|
if (kept) removal.preserved.push({ task: task2, ...kept });
|
|
@@ -25214,7 +25488,7 @@ var GitWorktreeIsolation = class {
|
|
|
25214
25488
|
return;
|
|
25215
25489
|
}
|
|
25216
25490
|
for (const entry of entries) {
|
|
25217
|
-
const target =
|
|
25491
|
+
const target = path37.join(dir, entry.name);
|
|
25218
25492
|
if (entry.isSymbolicLink()) this.unlinkIfLink(target);
|
|
25219
25493
|
else if (entry.isDirectory()) this.unlinkLinksUnder(target);
|
|
25220
25494
|
}
|
|
@@ -25236,20 +25510,20 @@ var GitWorktreeIsolation = class {
|
|
|
25236
25510
|
return false;
|
|
25237
25511
|
}
|
|
25238
25512
|
};
|
|
25239
|
-
const owned = new Set(Object.values(run.tasks).map((r) =>
|
|
25513
|
+
const owned = new Set(Object.values(run.tasks).map((r) => path37.resolve(r.workspace)));
|
|
25240
25514
|
const root = this.runRoot(run);
|
|
25241
25515
|
if (fs15.existsSync(root)) {
|
|
25242
25516
|
for (const entry of fs15.readdirSync(root)) {
|
|
25243
|
-
const dir =
|
|
25244
|
-
if (entry === INTEGRATION_DIR || owned.has(
|
|
25517
|
+
const dir = path37.join(root, entry);
|
|
25518
|
+
if (entry === INTEGRATION_DIR || owned.has(path37.resolve(dir))) continue;
|
|
25245
25519
|
let kept = true;
|
|
25246
25520
|
for (const repo of run.repos) {
|
|
25247
|
-
const worktree =
|
|
25248
|
-
const linked = leftoverLinks(
|
|
25521
|
+
const worktree = path37.join(dir, repo.path);
|
|
25522
|
+
const linked = leftoverLinks(path37.join(worktree, await this.prefixOf(repo)));
|
|
25249
25523
|
if (!await keep2(repo, { worktree, linked, branch: null, name: entry, title: entry }, worktree)) kept = false;
|
|
25250
25524
|
}
|
|
25251
25525
|
if (!kept) continue;
|
|
25252
|
-
for (const repo of run.repos) await this.removeWorktreeDir(run, repo,
|
|
25526
|
+
for (const repo of run.repos) await this.removeWorktreeDir(run, repo, path37.join(dir, repo.path), []);
|
|
25253
25527
|
this.removeTaskWorkspace(run, dir);
|
|
25254
25528
|
}
|
|
25255
25529
|
}
|
|
@@ -25267,13 +25541,13 @@ var GitWorktreeIsolation = class {
|
|
|
25267
25541
|
}
|
|
25268
25542
|
async removeWorktreeDir(run, repo, dir, linked) {
|
|
25269
25543
|
for (const name of /* @__PURE__ */ new Set([...linked, ...installDirs(dir)])) {
|
|
25270
|
-
const target =
|
|
25271
|
-
if (!resolvesAsNamed(this.runRoot(run),
|
|
25544
|
+
const target = path37.join(dir, name);
|
|
25545
|
+
if (!resolvesAsNamed(this.runRoot(run), path37.dirname(target))) continue;
|
|
25272
25546
|
this.unlinkIfLink(target);
|
|
25273
25547
|
this.unlinkLinksUnder(target);
|
|
25274
25548
|
}
|
|
25275
25549
|
for (const name of resolvesAsNamed(this.runRoot(run), dir) ? fs15.readdirSync(dir) : []) {
|
|
25276
|
-
if (LINKED_ARTIFACTS.has(name) || isEnvFile(name)) this.unlinkIfLink(
|
|
25550
|
+
if (LINKED_ARTIFACTS.has(name) || isEnvFile(name)) this.unlinkIfLink(path37.join(dir, name));
|
|
25277
25551
|
}
|
|
25278
25552
|
await this.tryGit(repo.root, ["worktree", "remove", "--force", dir]);
|
|
25279
25553
|
if (fs15.existsSync(dir) && isInside(this.runRoot(run), dir)) {
|
|
@@ -25281,7 +25555,7 @@ var GitWorktreeIsolation = class {
|
|
|
25281
25555
|
fs15.rmSync(dir, { recursive: true, force: true });
|
|
25282
25556
|
}
|
|
25283
25557
|
await this.tryGit(repo.root, ["worktree", "prune"]);
|
|
25284
|
-
for (let parent =
|
|
25558
|
+
for (let parent = path37.dirname(dir); isInside(this.runRoot(run), parent); parent = path37.dirname(parent)) this.removeIfEmpty(parent);
|
|
25285
25559
|
}
|
|
25286
25560
|
unlinkIfLink(target) {
|
|
25287
25561
|
try {
|
|
@@ -25296,14 +25570,14 @@ var GitWorktreeIsolation = class {
|
|
|
25296
25570
|
}
|
|
25297
25571
|
}
|
|
25298
25572
|
runRoot(run) {
|
|
25299
|
-
return
|
|
25573
|
+
return path37.join(run.workspaceRoot, STATE_DIR, "worktrees", run.id);
|
|
25300
25574
|
}
|
|
25301
25575
|
namesFor(run, task2) {
|
|
25302
25576
|
const slug = sanitizeSlug(task2.title).slice(0, 40).replace(/-+$/, "") || "task";
|
|
25303
25577
|
let name = `${task2.order}-${slug}`;
|
|
25304
25578
|
const clash = Object.values(run.tasks).some((r) => r.taskId !== task2.id && r.branch === `ordewell/${run.id}/${name}`);
|
|
25305
25579
|
if (clash) name = `${name}-${sanitizeSlug(task2.id) || "x"}`;
|
|
25306
|
-
return { branch: `ordewell/${run.id}/${name}`, dir:
|
|
25580
|
+
return { branch: `ordewell/${run.id}/${name}`, dir: path37.join(this.runRoot(run), name) };
|
|
25307
25581
|
}
|
|
25308
25582
|
async unmergedPaths(cwd) {
|
|
25309
25583
|
const listed3 = await this.tryGit(cwd, ["diff", "--name-only", "-z", "--diff-filter=U"]);
|
|
@@ -25317,7 +25591,7 @@ var GitWorktreeIsolation = class {
|
|
|
25317
25591
|
async rebaseInProgress(cwd) {
|
|
25318
25592
|
for (const dir of ["rebase-merge", "rebase-apply"]) {
|
|
25319
25593
|
const found = await this.tryGit(cwd, ["rev-parse", "--git-path", dir]);
|
|
25320
|
-
if (found.ok && fs15.existsSync(
|
|
25594
|
+
if (found.ok && fs15.existsSync(path37.resolve(cwd, found.stdout.trim()))) return true;
|
|
25321
25595
|
}
|
|
25322
25596
|
return false;
|
|
25323
25597
|
}
|
|
@@ -25344,6 +25618,29 @@ function createWorktreeIsolation(deps) {
|
|
|
25344
25618
|
return new GitWorktreeIsolation(deps);
|
|
25345
25619
|
}
|
|
25346
25620
|
|
|
25621
|
+
// src/services/repositoryRunnerConfig.ts
|
|
25622
|
+
var RUNNER_CONFIG_ROOTS = [".mcp.json", ".claude", ".claude-plugin", "opencode.json", "opencode.jsonc", ".opencode", ".codex"];
|
|
25623
|
+
function repositoryRunnerConfigPaths(paths) {
|
|
25624
|
+
return [...new Set(paths.filter(
|
|
25625
|
+
(path49) => (
|
|
25626
|
+
// Project MCP servers start commands or send data to a repository-chosen URL.
|
|
25627
|
+
path49 === ".mcp.json" || // Settings can run hooks and helpers, load plugins, or redirect traffic.
|
|
25628
|
+
path49 === ".claude/settings.json" || // Local settings have the same execution capabilities as project settings.
|
|
25629
|
+
path49 === ".claude/settings.local.json" || // Hook files provide commands invoked by the runner.
|
|
25630
|
+
path49.startsWith(".claude/hooks/") || // Plugin files can provide code and hooks loaded by the runner.
|
|
25631
|
+
path49.startsWith(".claude/plugins/") || // Plugin metadata can load plugins and their executable components.
|
|
25632
|
+
path49.startsWith(".claude-plugin/") || // Plugin and marketplace manifests can select code for the runner to load.
|
|
25633
|
+
/^\.claude\/(?:[^/]+\/)*(?:plugin|marketplace)\.json$/.test(path49) || // OpenCode config declares MCP servers and plugins; its plugin and tool
|
|
25634
|
+
// directories are code it loads, and an agent definition sets the prompt,
|
|
25635
|
+
// model and permissions of the built-in agent its id names, which a task
|
|
25636
|
+
// launches with. The rest of `.opencode/` is dependencies OpenCode
|
|
25637
|
+
// installs itself, which a later launch must not be held for.
|
|
25638
|
+
/^(?:\.opencode\/)?opencode\.jsonc?$/.test(path49) || /^\.opencode\/(?:plugins?|tools?|agents?)\//.test(path49) || // Codex project config carries MCP servers, hooks and `notify` commands.
|
|
25639
|
+
path49.startsWith(".codex/")
|
|
25640
|
+
)
|
|
25641
|
+
))].sort();
|
|
25642
|
+
}
|
|
25643
|
+
|
|
25347
25644
|
// src/interfaces/ILogger.ts
|
|
25348
25645
|
var ConsoleLogger = class {
|
|
25349
25646
|
warn(scope, message, err) {
|
|
@@ -28379,6 +28676,7 @@ var IsolationRunController = class {
|
|
|
28379
28676
|
/** Resolver task id → the conflicted task it resolves; see {@link linkResolver}. */
|
|
28380
28677
|
resolvers = {};
|
|
28381
28678
|
opening = null;
|
|
28679
|
+
opened = 0;
|
|
28382
28680
|
/** Bumped by an interrupt, so an activation in flight across it does not open the run it ended. */
|
|
28383
28681
|
activations = 0;
|
|
28384
28682
|
/** The start a dirty tree turned away, handed back once the user chooses how to go on. */
|
|
@@ -28410,6 +28708,10 @@ var IsolationRunController = class {
|
|
|
28410
28708
|
get isOpen() {
|
|
28411
28709
|
return this.mode !== null;
|
|
28412
28710
|
}
|
|
28711
|
+
/** How many runs have opened: what tells one run's launches from the next one's. */
|
|
28712
|
+
get epoch() {
|
|
28713
|
+
return this.opened;
|
|
28714
|
+
}
|
|
28413
28715
|
/** The open run has decided whether it isolates. */
|
|
28414
28716
|
get decided() {
|
|
28415
28717
|
return this.mode === "isolated" || this.mode === "shared";
|
|
@@ -28507,6 +28809,7 @@ var IsolationRunController = class {
|
|
|
28507
28809
|
open() {
|
|
28508
28810
|
if (this.mode) return;
|
|
28509
28811
|
this.mode = "undecided";
|
|
28812
|
+
this.opened += 1;
|
|
28510
28813
|
}
|
|
28511
28814
|
/**
|
|
28512
28815
|
* Decide once per run, as its first change task starts, whether it executes
|
|
@@ -29147,15 +29450,21 @@ var Landing = class {
|
|
|
29147
29450
|
* has landed. Its branch is on the integration branch by then, so it merges
|
|
29148
29451
|
* clean — and one the resolver did not really bring along conflicts again
|
|
29149
29452
|
* instead of being taken at its word. Null when there is nothing to land.
|
|
29453
|
+
* The task is named before its landing settles, so the caller can hold
|
|
29454
|
+
* its worktree for as long as the merge is in it; one the caller says is
|
|
29455
|
+
* `leaving` the plan is not landed at all.
|
|
29150
29456
|
*/
|
|
29151
|
-
|
|
29457
|
+
landResolved(resolverId, leaving = () => false) {
|
|
29152
29458
|
const conflictedId = this.runs.takeResolver(resolverId);
|
|
29153
|
-
if (!conflictedId) return null;
|
|
29459
|
+
if (!conflictedId || leaving(conflictedId)) return null;
|
|
29154
29460
|
const task2 = this.tasks.get(conflictedId);
|
|
29155
29461
|
if (!task2 || this.runs.current?.tasks[conflictedId]?.status !== "conflict") return null;
|
|
29462
|
+
return { task: task2, outcome: this.landConflicted(task2) };
|
|
29463
|
+
}
|
|
29464
|
+
async landConflicted(task2) {
|
|
29156
29465
|
const landing2 = await this.runs.integrate(task2);
|
|
29157
|
-
if (landing2 !== "merged") return
|
|
29158
|
-
return {
|
|
29466
|
+
if (landing2 !== "merged") return this.unmerged(task2, landing2);
|
|
29467
|
+
return { kind: "landed", messages: [{ level: "info", text: `Task "${task2.title}" landed through its conflict resolution.` }] };
|
|
29159
29468
|
}
|
|
29160
29469
|
/** The repair a conflicted task is owed next; null when repair is off, used up, or there is no isolated run to repair it in. */
|
|
29161
29470
|
nextRepair(taskId) {
|
|
@@ -29500,7 +29809,7 @@ function heldFrom(state) {
|
|
|
29500
29809
|
}
|
|
29501
29810
|
|
|
29502
29811
|
// src/services/Attempts.ts
|
|
29503
|
-
import * as
|
|
29812
|
+
import * as path41 from "path";
|
|
29504
29813
|
|
|
29505
29814
|
// src/services/VerdictEngine.ts
|
|
29506
29815
|
var IDLE_TIMEOUT_MS = 10 * 6e4;
|
|
@@ -29608,6 +29917,18 @@ var VerdictEngine = class {
|
|
|
29608
29917
|
this.resumeIdle(taskId);
|
|
29609
29918
|
this.toolCheckpoints.get(taskId)?.({ kind: "rejected", reason });
|
|
29610
29919
|
}
|
|
29920
|
+
/**
|
|
29921
|
+
* An attempt's runner is being started, with no session to watch yet. Its
|
|
29922
|
+
* silence counts from here, so one whose start never resolves — stuck
|
|
29923
|
+
* launching, or waiting for its tools to attach — is flagged quiet as well.
|
|
29924
|
+
* {@link watch} starts the count again once the runner is up.
|
|
29925
|
+
*/
|
|
29926
|
+
starting(taskId) {
|
|
29927
|
+
const gen = this.bumpGeneration(taskId);
|
|
29928
|
+
this.idlePaused.delete(taskId);
|
|
29929
|
+
this.openApprovals.delete(taskId);
|
|
29930
|
+
this.touchIdle(taskId, gen);
|
|
29931
|
+
}
|
|
29611
29932
|
/**
|
|
29612
29933
|
* Attach to a spawned session. The runner's `task_complete` call on the
|
|
29613
29934
|
* attempt's token is the only completion evidence (ADR-0022, V2); a session
|
|
@@ -29618,6 +29939,9 @@ var VerdictEngine = class {
|
|
|
29618
29939
|
*/
|
|
29619
29940
|
watch(task2, session) {
|
|
29620
29941
|
const gen = this.bumpGeneration(task2.id);
|
|
29942
|
+
this.idlePaused.delete(task2.id);
|
|
29943
|
+
this.openApprovals.delete(task2.id);
|
|
29944
|
+
this.touchIdle(task2.id, gen);
|
|
29621
29945
|
session.onOutput(() => {
|
|
29622
29946
|
if (this.generations.get(task2.id) === gen) this.touchIdle(task2.id, gen);
|
|
29623
29947
|
});
|
|
@@ -29873,10 +30197,132 @@ function reportedVerdict(report) {
|
|
|
29873
30197
|
return { outcome: "fail", reason: `The runner reported the task ${report.status}: ${why}`, checks, decidedAt, reported };
|
|
29874
30198
|
}
|
|
29875
30199
|
|
|
30200
|
+
// src/services/taskStartConfig.ts
|
|
30201
|
+
import * as path38 from "path";
|
|
30202
|
+
import { createHash as createHash4 } from "crypto";
|
|
30203
|
+
import { existsSync as existsSync15, lstatSync as lstatSync4, readdirSync as readdirSync8, readFileSync as readFileSync13, statSync as statSync12 } from "fs";
|
|
30204
|
+
var MAX_FILES = 2e3;
|
|
30205
|
+
var MAX_DEPTH = 8;
|
|
30206
|
+
function liveConfig(dir) {
|
|
30207
|
+
const found = /* @__PURE__ */ new Map();
|
|
30208
|
+
const visit = (rel, depth) => {
|
|
30209
|
+
if (found.size >= MAX_FILES || depth > MAX_DEPTH) return;
|
|
30210
|
+
const full = path38.join(dir, rel);
|
|
30211
|
+
try {
|
|
30212
|
+
const info = statSync12(full);
|
|
30213
|
+
if (info.isFile()) {
|
|
30214
|
+
found.set(rel, createHash4("sha1").update(readFileSync13(full)).digest("hex"));
|
|
30215
|
+
} else if (info.isDirectory() && path38.basename(rel) !== "node_modules") {
|
|
30216
|
+
for (const name of readdirSync8(full)) visit(path38.posix.join(rel, name), depth + 1);
|
|
30217
|
+
}
|
|
30218
|
+
} catch {
|
|
30219
|
+
}
|
|
30220
|
+
};
|
|
30221
|
+
for (const root of RUNNER_CONFIG_ROOTS) visit(root, 0);
|
|
30222
|
+
const selected = new Set(repositoryRunnerConfigPaths([...found.keys()]));
|
|
30223
|
+
return new Map([...found].filter(([name]) => selected.has(name)));
|
|
30224
|
+
}
|
|
30225
|
+
function linkedRoots(dir) {
|
|
30226
|
+
const linked = /* @__PURE__ */ new Set();
|
|
30227
|
+
for (const root of RUNNER_CONFIG_ROOTS) {
|
|
30228
|
+
try {
|
|
30229
|
+
if (lstatSync4(path38.join(dir, root)).isSymbolicLink()) linked.add(root);
|
|
30230
|
+
} catch {
|
|
30231
|
+
}
|
|
30232
|
+
}
|
|
30233
|
+
return linked;
|
|
30234
|
+
}
|
|
30235
|
+
function readDirs(root) {
|
|
30236
|
+
const dirs = [{ dir: root, prefix: "" }];
|
|
30237
|
+
for (let dir = root, prefix = ""; !existsSync15(path38.join(dir, ".git")); ) {
|
|
30238
|
+
const parent = path38.dirname(dir);
|
|
30239
|
+
if (parent === dir) return [dirs[0]];
|
|
30240
|
+
dir = parent;
|
|
30241
|
+
prefix += "../";
|
|
30242
|
+
dirs.push({ dir, prefix });
|
|
30243
|
+
}
|
|
30244
|
+
return dirs;
|
|
30245
|
+
}
|
|
30246
|
+
var TaskStartConfig = class {
|
|
30247
|
+
live = /* @__PURE__ */ new Map();
|
|
30248
|
+
/**
|
|
30249
|
+
* `epoch` names the run the check belongs to; a new run takes a new
|
|
30250
|
+
* baseline. `root` is the workspace root, the checkout a worktree run lands
|
|
30251
|
+
* in; `cwd` is where the runner starts, and `worktree` whether that is a
|
|
30252
|
+
* worktree prepared for it.
|
|
30253
|
+
*/
|
|
30254
|
+
async check(run, taskId, where) {
|
|
30255
|
+
const found = where.worktree ? await this.gitDifferences(run, taskId) : [];
|
|
30256
|
+
const id2 = String(where.epoch);
|
|
30257
|
+
const checkout = readDirs(where.root).flatMap(({ dir, prefix }) => this.liveDifferences(`${id2}:checkout:${dir}`, dir).map((name) => `${prefix}${name}`));
|
|
30258
|
+
if (!where.worktree) found.push(...checkout);
|
|
30259
|
+
else {
|
|
30260
|
+
const cwd = where.cwd ?? run?.tasks[taskId]?.workspace;
|
|
30261
|
+
if (cwd) found.push(...this.worktreeDifferences(id2, cwd, where.root));
|
|
30262
|
+
}
|
|
30263
|
+
return [...new Set(found)].sort();
|
|
30264
|
+
}
|
|
30265
|
+
/** The baselines go with the plan they belong to. */
|
|
30266
|
+
clear() {
|
|
30267
|
+
this.live.clear();
|
|
30268
|
+
}
|
|
30269
|
+
/**
|
|
30270
|
+
* What a worktree's runner reads that differs from where the run started.
|
|
30271
|
+
* The `.claude` and `.opencode` directories it links from the checkout are
|
|
30272
|
+
* the checkout's own, so they are compared with the checkout's first state,
|
|
30273
|
+
* which an earlier task's write there does not move. Anything else is the
|
|
30274
|
+
* worktree's, and is compared with its own first state: a setup command's
|
|
30275
|
+
* files are trusted, and a task's later write is not.
|
|
30276
|
+
*/
|
|
30277
|
+
worktreeDifferences(id2, dir, root) {
|
|
30278
|
+
const now = liveConfig(dir);
|
|
30279
|
+
const linked = linkedRoots(dir);
|
|
30280
|
+
const fromCheckout = (name) => linked.has(name.split("/")[0]);
|
|
30281
|
+
const key = `${id2}:worktree:${dir}`;
|
|
30282
|
+
let local = this.live.get(key);
|
|
30283
|
+
if (!local) {
|
|
30284
|
+
local = new Map([...now].filter(([name]) => !fromCheckout(name)));
|
|
30285
|
+
this.live.set(key, local);
|
|
30286
|
+
}
|
|
30287
|
+
const checkout = this.live.get(`${id2}:checkout:${root}`);
|
|
30288
|
+
return [...now].filter(([name, hash]) => fromCheckout(name) ? checkout?.get(name) !== hash : local.get(name) !== hash).map(([name]) => name);
|
|
30289
|
+
}
|
|
30290
|
+
liveDifferences(key, dir) {
|
|
30291
|
+
const now = liveConfig(dir);
|
|
30292
|
+
const before = this.live.get(key);
|
|
30293
|
+
if (!before) {
|
|
30294
|
+
this.live.set(key, now);
|
|
30295
|
+
return [];
|
|
30296
|
+
}
|
|
30297
|
+
return [...now].filter(([name, hash]) => before.get(name) !== hash).map(([name]) => name);
|
|
30298
|
+
}
|
|
30299
|
+
async gitDifferences(run, taskId) {
|
|
30300
|
+
if (!run) return [];
|
|
30301
|
+
const invoker = { exec: defaultExecFile2, resolvePath: augmentedPath };
|
|
30302
|
+
const found = [];
|
|
30303
|
+
for (const repo of run.repos) {
|
|
30304
|
+
const cwd = run.tasks[taskId]?.repos[repo.path]?.worktree;
|
|
30305
|
+
if (!cwd || !repo.baseRef || !existsSync15(cwd)) continue;
|
|
30306
|
+
const prefix = repo.path === "." ? (await tryGit(invoker, repo.root, ["rev-parse", "--show-prefix"])).stdout.trim() : "";
|
|
30307
|
+
const roots = [...RUNNER_CONFIG_ROOTS, ...prefix ? RUNNER_CONFIG_ROOTS.map((r) => `${prefix}${r}`) : []];
|
|
30308
|
+
const diff = await tryGit(invoker, cwd, ["diff", "--no-ext-diff", "--no-textconv", "--no-renames", "--name-only", "--diff-filter=ACMRTU", "-z", repo.baseRef, "--"]);
|
|
30309
|
+
const untracked = await tryGit(invoker, cwd, ["ls-files", "--others", "-z", "--", ...roots]);
|
|
30310
|
+
if (!diff.ok || !untracked.ok) continue;
|
|
30311
|
+
for (const name of [...diff.stdout.split("\0"), ...untracked.stdout.split("\0")]) {
|
|
30312
|
+
const here = prefix && name.startsWith(prefix) ? name.slice(prefix.length) : void 0;
|
|
30313
|
+
if (repositoryRunnerConfigPaths([name]).length || here !== void 0 && repositoryRunnerConfigPaths([here]).length) {
|
|
30314
|
+
found.push(repo.path === "." ? name : path38.posix.join(repo.path, name));
|
|
30315
|
+
}
|
|
30316
|
+
}
|
|
30317
|
+
}
|
|
30318
|
+
return found;
|
|
30319
|
+
}
|
|
30320
|
+
};
|
|
30321
|
+
|
|
29876
30322
|
// src/services/ScriptRunner.ts
|
|
29877
30323
|
import { spawn as nodeSpawn2 } from "child_process";
|
|
29878
30324
|
import * as fs16 from "fs";
|
|
29879
|
-
import * as
|
|
30325
|
+
import * as path39 from "path";
|
|
29880
30326
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
29881
30327
|
import { StringDecoder as StringDecoder2 } from "string_decoder";
|
|
29882
30328
|
|
|
@@ -30017,7 +30463,7 @@ var ScriptSession = class extends AbstractRunnerSession {
|
|
|
30017
30463
|
async launchOf(step) {
|
|
30018
30464
|
if ("argv" in step) {
|
|
30019
30465
|
const [named3, ...args] = step.argv;
|
|
30020
|
-
const program = this.setup.platform === "win32" &&
|
|
30466
|
+
const program = this.setup.platform === "win32" && path39.win32.basename(named3) !== named3 && !path39.win32.isAbsolute(named3) ? path39.win32.resolve(this.setup.cwd, named3) : named3;
|
|
30021
30467
|
const plan = await planDirectLaunch(program, args, this.setup.launch);
|
|
30022
30468
|
return { file: plan.file, args: plan.args, shell: false, ...plan.verbatim ? { verbatim: true } : {} };
|
|
30023
30469
|
}
|
|
@@ -30250,10 +30696,10 @@ function passed(status) {
|
|
|
30250
30696
|
}
|
|
30251
30697
|
|
|
30252
30698
|
// src/services/PatchCapture.ts
|
|
30253
|
-
import { createHash as
|
|
30699
|
+
import { createHash as createHash5, randomBytes as randomBytes7 } from "crypto";
|
|
30254
30700
|
import * as fs17 from "fs";
|
|
30255
|
-
import * as
|
|
30256
|
-
import * as
|
|
30701
|
+
import * as os10 from "os";
|
|
30702
|
+
import * as path40 from "path";
|
|
30257
30703
|
|
|
30258
30704
|
// src/services/readTask.ts
|
|
30259
30705
|
function readTask2(args, readerId, plan, log) {
|
|
@@ -30520,7 +30966,7 @@ function linkInPatch(patch) {
|
|
|
30520
30966
|
function readOwn(scratch, name, max) {
|
|
30521
30967
|
let fd;
|
|
30522
30968
|
try {
|
|
30523
|
-
fd = fs17.openSync(
|
|
30969
|
+
fd = fs17.openSync(path40.join(scratch, name), fs17.constants.O_RDONLY | (fs17.constants.O_NOFOLLOW ?? 0));
|
|
30524
30970
|
} catch (err) {
|
|
30525
30971
|
throw new Refusal(`the capture left no ${name.split(".").pop()} Ordewell could read (${err.code ?? String(err)})`);
|
|
30526
30972
|
}
|
|
@@ -30558,7 +31004,7 @@ async function captureHeld(runs, opts, signal) {
|
|
|
30558
31004
|
const [worktree] = runs.worktrees;
|
|
30559
31005
|
const scratch = runs.scratch;
|
|
30560
31006
|
const tag = `ordewell-patch-${randomBytes7(8).toString("hex")}`;
|
|
30561
|
-
const stem =
|
|
31007
|
+
const stem = path40.join(scratch, tag);
|
|
30562
31008
|
const own = (suffix, max = LISTING_BYTES) => readOwn(scratch, `${tag}.${suffix}`, max);
|
|
30563
31009
|
try {
|
|
30564
31010
|
const stage = await runs.run(stageScript(stem, opts.linked ?? []), worktree);
|
|
@@ -30596,7 +31042,7 @@ async function captureHeld(runs, opts, signal) {
|
|
|
30596
31042
|
return {
|
|
30597
31043
|
kind: "saved",
|
|
30598
31044
|
patch,
|
|
30599
|
-
stats: { base, ...countedIn(patch), bytes: patch.length, sha256:
|
|
31045
|
+
stats: { base, ...countedIn(patch), bytes: patch.length, sha256: createHash5("sha256").update(patch).digest("hex"), flags: [...flags, ...flag("normalized", normalized2)] }
|
|
30600
31046
|
};
|
|
30601
31047
|
} catch (err) {
|
|
30602
31048
|
if (err instanceof Refusal) return { kind: "refused", reason: `${err.message}.` };
|
|
@@ -30650,8 +31096,8 @@ async function indexEntries(invoker, worktree) {
|
|
|
30650
31096
|
return entries;
|
|
30651
31097
|
}
|
|
30652
31098
|
async function trialApply(invoker, worktree, patchFile, scratch) {
|
|
30653
|
-
const index =
|
|
30654
|
-
const copy =
|
|
31099
|
+
const index = path40.resolve(worktree, (await git(invoker, worktree, [...APPLY_GIT, "rev-parse", "--git-path", "index"])).trim());
|
|
31100
|
+
const copy = path40.join(scratch, "index");
|
|
30655
31101
|
fs17.copyFileSync(index, copy);
|
|
30656
31102
|
const trial = { ...invoker, exec: (file, args, opts) => invoker.exec(file, args, { ...opts, env: { ...opts.env, GIT_INDEX_FILE: copy } }) };
|
|
30657
31103
|
const tried = await tryGit(trial, worktree, [...APPLY_GIT, "apply", "--cached", "--3way", "--whitespace=nowarn", patchFile]);
|
|
@@ -30671,7 +31117,7 @@ function gitRefused(lead, stderr) {
|
|
|
30671
31117
|
function onDisk3(worktree, names) {
|
|
30672
31118
|
return names.map((name) => {
|
|
30673
31119
|
try {
|
|
30674
|
-
const stat = fs17.lstatSync(
|
|
31120
|
+
const stat = fs17.lstatSync(path40.join(worktree, name), { bigint: true, throwIfNoEntry: false });
|
|
30675
31121
|
return stat ? `${stat.mode} ${stat.ino} ${stat.size} ${stat.mtimeNs} ${stat.ctimeNs}` : "none";
|
|
30676
31122
|
} catch (err) {
|
|
30677
31123
|
return err.code ?? "unreadable";
|
|
@@ -30711,7 +31157,7 @@ function leavesWorktree(worktree, names, linked, held2, left) {
|
|
|
30711
31157
|
if (i < parts.length && held2.get(at)?.some((entry) => entry.mode === SUBMODULE)) {
|
|
30712
31158
|
return `the patch writes ${name}, under ${at}, a submodule, whose files are another repository's.`;
|
|
30713
31159
|
}
|
|
30714
|
-
const full =
|
|
31160
|
+
const full = path40.join(worktree, ...parts.slice(0, i));
|
|
30715
31161
|
const stat = fs17.lstatSync(full, { throwIfNoEntry: false });
|
|
30716
31162
|
if (!stat) break;
|
|
30717
31163
|
const last = i === parts.length;
|
|
@@ -30732,14 +31178,14 @@ async function applyPatch(invoker, worktree, patch, saved, opts = {}) {
|
|
|
30732
31178
|
if (patch.length !== saved.bytes) {
|
|
30733
31179
|
return refused3(`the patch is ${byteSize(patch.length)} where its artifact says ${byteSize(saved.bytes)}, so it was cut short or changed after it was saved.`);
|
|
30734
31180
|
}
|
|
30735
|
-
if (
|
|
31181
|
+
if (createHash5("sha256").update(patch).digest("hex") !== saved.sha256) {
|
|
30736
31182
|
return refused3("the patch is not the file that was saved: its hash is not the one its artifact records, so it was changed after it was saved.");
|
|
30737
31183
|
}
|
|
30738
31184
|
const link = linkInPatch(patch);
|
|
30739
31185
|
if (link !== void 0) {
|
|
30740
31186
|
return refused3(`the patch holds a link or a submodule (${JSON.stringify(link.slice(0, 80))}), which a saved patch never does, so it is not one that was saved.`);
|
|
30741
31187
|
}
|
|
30742
|
-
const dir = fs17.mkdtempSync(
|
|
31188
|
+
const dir = fs17.mkdtempSync(path40.join(os10.tmpdir(), "ordewell-apply-"));
|
|
30743
31189
|
try {
|
|
30744
31190
|
const toplevel = (await git(invoker, worktree, [...APPLY_GIT, "rev-parse", "--show-toplevel"])).trim();
|
|
30745
31191
|
if (fs17.realpathSync(toplevel) !== fs17.realpathSync(worktree)) return refused3(`${worktree} is not the root of a worktree of its own.`);
|
|
@@ -30749,7 +31195,7 @@ async function applyPatch(invoker, worktree, patch, saved, opts = {}) {
|
|
|
30749
31195
|
}
|
|
30750
31196
|
const before = await unmergedPaths(invoker, worktree);
|
|
30751
31197
|
if (before.length > 0) return refused3(`your worktree still has unresolved conflicts (${listed(before.slice(0, 10))}${before.length > 10 ? `, +${before.length - 10} more` : ""}). Resolve them and git add them, or undo them, then apply the patch.`);
|
|
30752
|
-
const file =
|
|
31198
|
+
const file = path40.join(dir, "saved.patch");
|
|
30753
31199
|
fs17.writeFileSync(file, patch, { flag: "wx", mode: 384 });
|
|
30754
31200
|
const held2 = await indexEntries(invoker, worktree);
|
|
30755
31201
|
const named3 = await pathsNamed(invoker, worktree, file);
|
|
@@ -30899,6 +31345,11 @@ var TaskWebReader = class {
|
|
|
30899
31345
|
};
|
|
30900
31346
|
|
|
30901
31347
|
// src/services/Attempts.ts
|
|
31348
|
+
var TaskStartConfigError = class extends Error {
|
|
31349
|
+
constructor(paths) {
|
|
31350
|
+
super(`repository configuration ${paths.map((p) => JSON.stringify(p)).join(", ")} differs from where this run started; remove it to continue, or start a new run once your branch holds it`);
|
|
31351
|
+
}
|
|
31352
|
+
};
|
|
30902
31353
|
var DEFAULT_GIT = { exec: defaultExecFile2, resolvePath: () => augmentedPath() };
|
|
30903
31354
|
var Attempts = class {
|
|
30904
31355
|
constructor(deps) {
|
|
@@ -30930,6 +31381,7 @@ var Attempts = class {
|
|
|
30930
31381
|
live = /* @__PURE__ */ new Map();
|
|
30931
31382
|
/** Bumped by every plan load: an ended attempt's wind-down belongs to the scope before it. */
|
|
30932
31383
|
planScope = 0;
|
|
31384
|
+
startConfig = new TaskStartConfig();
|
|
30933
31385
|
/** Each task's runners still winding down once stopped: a continue waits for them (ADR-0018, L1). */
|
|
30934
31386
|
windDowns = /* @__PURE__ */ new Map();
|
|
30935
31387
|
/**
|
|
@@ -30985,6 +31437,10 @@ var Attempts = class {
|
|
|
30985
31437
|
receipt(taskId) {
|
|
30986
31438
|
return this.live.get(taskId)?.receipt ?? null;
|
|
30987
31439
|
}
|
|
31440
|
+
/** Which spawn of the live attempt's runner waits for Ordewell's tools to connect; null when none does. */
|
|
31441
|
+
attaching(taskId) {
|
|
31442
|
+
return this.live.get(taskId)?.attaching ?? null;
|
|
31443
|
+
}
|
|
30988
31444
|
/** The kind a task's next attempt starts as, short of a repair or a continue: what is weighed before one exists. */
|
|
30989
31445
|
nextKind(taskId) {
|
|
30990
31446
|
const task2 = this.deps.store.get(taskId);
|
|
@@ -31025,7 +31481,9 @@ var Attempts = class {
|
|
|
31025
31481
|
shell: null,
|
|
31026
31482
|
web: null,
|
|
31027
31483
|
patch: null,
|
|
31028
|
-
receipt: null
|
|
31484
|
+
receipt: null,
|
|
31485
|
+
attaching: null,
|
|
31486
|
+
spawning: new AbortController()
|
|
31029
31487
|
};
|
|
31030
31488
|
this.spawnCounts.set(task2.id, attempt.attempt);
|
|
31031
31489
|
this.live.set(task2.id, attempt);
|
|
@@ -31040,7 +31498,8 @@ var Attempts = class {
|
|
|
31040
31498
|
/**
|
|
31041
31499
|
* The one way an attempt ends. Dropping the record is itself what
|
|
31042
31500
|
* invalidates a spawn still in flight ({@link run} kills the session it
|
|
31043
|
-
* receives for an attempt that is no longer current)
|
|
31501
|
+
* receives for an attempt that is no longer current), and its cancel takes
|
|
31502
|
+
* back a runner still starting meanwhile.
|
|
31044
31503
|
*
|
|
31045
31504
|
* A user interruption also bumps the verifier's generation *before* stopping
|
|
31046
31505
|
* the runner: a runner may fire onExit synchronously from
|
|
@@ -31056,6 +31515,7 @@ var Attempts = class {
|
|
|
31056
31515
|
const session = attempt?.session ?? null;
|
|
31057
31516
|
const nativeSessionId = session?.nativeSessionId() ?? null;
|
|
31058
31517
|
if (attempt) {
|
|
31518
|
+
attempt.spawning.abort();
|
|
31059
31519
|
store.endAttemptUsage(taskId, attempt.attempt, (/* @__PURE__ */ new Date()).toISOString(), session?.sessionCost?.() ?? void 0, nativeSessionId ?? void 0);
|
|
31060
31520
|
this.deps.output.detach(taskId);
|
|
31061
31521
|
attempt.shell?.end();
|
|
@@ -31091,6 +31551,7 @@ var Attempts = class {
|
|
|
31091
31551
|
const ended = this.endAll("load");
|
|
31092
31552
|
this.planScope += 1;
|
|
31093
31553
|
this.spawnCounts.clear();
|
|
31554
|
+
this.startConfig.clear();
|
|
31094
31555
|
return ended;
|
|
31095
31556
|
}
|
|
31096
31557
|
/** A task leaving the plan: no later attempt of it is numbered from its runs. */
|
|
@@ -31223,21 +31684,31 @@ var Attempts = class {
|
|
|
31223
31684
|
if (unavailable2) throw new Error(unavailable2);
|
|
31224
31685
|
}
|
|
31225
31686
|
if (kind.kind === "continuation") await this.windDowns.get(task2.id);
|
|
31687
|
+
if (!attempt.script && kind.kind === "repair") {
|
|
31688
|
+
const paths = await this.startConfig.check(runs.current, task2.id, { epoch: runs.epoch, root: this.deps.workspaceRoot(), cwd: null, worktree: true });
|
|
31689
|
+
if (paths.length) throw new TaskStartConfigError(paths);
|
|
31690
|
+
}
|
|
31226
31691
|
const { cwd, worktree, previousWork, workspace } = await attemptCwd(kind, task2, runs);
|
|
31227
31692
|
attempt.cwd = cwd;
|
|
31228
31693
|
attempt.worktree = worktree;
|
|
31694
|
+
if (!attempt.script && kind.kind !== "repair" && !landsNothing(kind)) {
|
|
31695
|
+
const paths = await this.startConfig.check(runs.current, task2.id, { epoch: runs.epoch, root: this.deps.workspaceRoot(), cwd, worktree });
|
|
31696
|
+
if (paths.length) throw new TaskStartConfigError(paths);
|
|
31697
|
+
}
|
|
31229
31698
|
if (checksTree(kind)) attempt.snapshot = await runs.snapshotWorkspace();
|
|
31230
31699
|
if (this.live.get(task2.id) !== attempt) {
|
|
31231
31700
|
if (attempt.worktree && !this.live.has(task2.id) && !this.host.completing(task2.id)) await runs.release(task2.id, { keep: kind.kind === "repair" });
|
|
31232
31701
|
this.abandonSpawn(task2, attempt);
|
|
31233
31702
|
return false;
|
|
31234
31703
|
}
|
|
31704
|
+
this.verifier.starting(task2.id);
|
|
31235
31705
|
const session = attempt.script ? await this.spawnScript(task2, attempt, cwd) : await this.spawnRunner(task2, attempt, cwd, worktree ? workspace : void 0, previousWork);
|
|
31236
31706
|
if (this.live.get(task2.id) !== attempt) {
|
|
31237
31707
|
this.abandonSpawn(task2, attempt, session);
|
|
31238
31708
|
return false;
|
|
31239
31709
|
}
|
|
31240
31710
|
attempt.phase = "running";
|
|
31711
|
+
attempt.attaching = null;
|
|
31241
31712
|
attempt.session = session;
|
|
31242
31713
|
store.setTaskRunnerSessionId(task2.id, void 0);
|
|
31243
31714
|
store.clearCarriedFrom(task2.id);
|
|
@@ -31306,6 +31777,9 @@ var Attempts = class {
|
|
|
31306
31777
|
return { kind: "usage-limited", taskId: task2.id, attempt, said: { message }, output: "" };
|
|
31307
31778
|
}
|
|
31308
31779
|
const failed = (failure2) => ({ kind: "spawn-failed", task: task2, attempt, failure: failure2 });
|
|
31780
|
+
if (err instanceof TaskStartConfigError) {
|
|
31781
|
+
return failed({ leaves: "held", hold: err.message, preserveWorktree: true, settles: false, notice: { level: "warn", text: `Task "${task2.title}" is on hold: ${err.message}` } });
|
|
31782
|
+
}
|
|
31309
31783
|
if (attempt.kind.kind === "continuation" && !attachTimeout) {
|
|
31310
31784
|
const refused3 = err instanceof RunnerStartupExit;
|
|
31311
31785
|
const reason = unresumedMessage(task2, refused3 ? `${attempt.runner} could not find its saved session` : `could not start: ${message}`);
|
|
@@ -31383,7 +31857,13 @@ var Attempts = class {
|
|
|
31383
31857
|
...web ? { webReads: web } : {},
|
|
31384
31858
|
applyPatch: (args) => this.applyPatch(task2, attempt, args),
|
|
31385
31859
|
workspaceRoot: this.deps.workspaceRoot(),
|
|
31386
|
-
onNotice: (message) => this.host.tell("warn", `Task "${task2.title}": ${message}`)
|
|
31860
|
+
onNotice: (message) => this.host.tell("warn", `Task "${task2.title}": ${message}`),
|
|
31861
|
+
cancelled: attempt.spawning.signal,
|
|
31862
|
+
onAttaching: (spawn) => {
|
|
31863
|
+
if (this.live.get(task2.id) !== attempt) return;
|
|
31864
|
+
attempt.attaching = spawn;
|
|
31865
|
+
this.host.emit("onTaskChanged");
|
|
31866
|
+
}
|
|
31387
31867
|
});
|
|
31388
31868
|
}
|
|
31389
31869
|
/**
|
|
@@ -31436,9 +31916,9 @@ var Attempts = class {
|
|
|
31436
31916
|
const repos = Object.values(this.deps.runs.openRecord(taskId)?.repos ?? {});
|
|
31437
31917
|
if (repos.length !== 1 || cwd === null) return [];
|
|
31438
31918
|
const [{ worktree, linked }] = repos;
|
|
31439
|
-
const prefix =
|
|
31440
|
-
if (prefix.startsWith("..") ||
|
|
31441
|
-
return linked.map((name) => (prefix ? `${prefix}/${name}` : name).split(
|
|
31919
|
+
const prefix = path41.relative(worktree, cwd).split(path41.sep).join("/");
|
|
31920
|
+
if (prefix.startsWith("..") || path41.isAbsolute(prefix)) return [];
|
|
31921
|
+
return linked.map((name) => (prefix ? `${prefix}/${name}` : name).split(path41.sep).join("/"));
|
|
31442
31922
|
}
|
|
31443
31923
|
/**
|
|
31444
31924
|
* A script task's start (ADR-0035): its body as the plan holds it now, run
|
|
@@ -31476,7 +31956,7 @@ var Attempts = class {
|
|
|
31476
31956
|
this.deps.notifications.warn(message);
|
|
31477
31957
|
};
|
|
31478
31958
|
if (resolved.blockedEnvrc) {
|
|
31479
|
-
warn(`blocked:${resolved.blockedEnvrc}`, `direnv has blocked ${resolved.blockedEnvrc}, so tasks start without its variables. Run \`direnv allow\` in ${
|
|
31959
|
+
warn(`blocked:${resolved.blockedEnvrc}`, `direnv has blocked ${resolved.blockedEnvrc}, so tasks start without its variables. Run \`direnv allow\` in ${path41.dirname(resolved.blockedEnvrc)} to use them.`);
|
|
31480
31960
|
}
|
|
31481
31961
|
if (resolved.trackedEnvFile) {
|
|
31482
31962
|
warn(`tracked:${resolved.trackedEnvFile}`, `Ignored ${resolved.trackedEnvFile}: git tracks it, and a committed file must not choose the environment agents run in. Untrack it to use it.`);
|
|
@@ -31502,7 +31982,7 @@ var Attempts = class {
|
|
|
31502
31982
|
uncommittedSkillFolder(name) {
|
|
31503
31983
|
const root = this.deps.workspaceRoot();
|
|
31504
31984
|
const skill = this.deps.skillsAt(this.skillRoots(root)).findSkill(name);
|
|
31505
|
-
return skill?.source === "workspace" ?
|
|
31985
|
+
return skill?.source === "workspace" ? path41.relative(root, path41.dirname(skill.path)).split(path41.sep).join("/") : void 0;
|
|
31506
31986
|
}
|
|
31507
31987
|
/**
|
|
31508
31988
|
* The roots whose `.ordewell/skills/` an attempt at `cwd` reads: in a repo
|
|
@@ -31871,12 +32351,12 @@ function ancestors(task2, store) {
|
|
|
31871
32351
|
|
|
31872
32352
|
// src/services/sessionLease.ts
|
|
31873
32353
|
import * as fs19 from "fs";
|
|
31874
|
-
import * as
|
|
32354
|
+
import * as path43 from "path";
|
|
31875
32355
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
31876
32356
|
|
|
31877
32357
|
// src/utils/sessionStore.ts
|
|
31878
32358
|
import * as fs18 from "fs";
|
|
31879
|
-
import * as
|
|
32359
|
+
import * as path42 from "path";
|
|
31880
32360
|
|
|
31881
32361
|
// src/utils/sessionId.ts
|
|
31882
32362
|
import { randomBytes as randomBytes8 } from "crypto";
|
|
@@ -31887,13 +32367,13 @@ function mintSessionId() {
|
|
|
31887
32367
|
// src/utils/sessionStore.ts
|
|
31888
32368
|
var SESSIONS_SUBDIR = "sessions";
|
|
31889
32369
|
function getSessionsDir(baseDir) {
|
|
31890
|
-
return
|
|
32370
|
+
return path42.join(getStateDir(baseDir), SESSIONS_SUBDIR);
|
|
31891
32371
|
}
|
|
31892
32372
|
function idSegment(id2) {
|
|
31893
32373
|
return /^[A-Za-z0-9_-][A-Za-z0-9._-]{0,127}$/.test(id2) ? id2 : `~${Buffer.from(id2, "utf-8").toString("hex")}`;
|
|
31894
32374
|
}
|
|
31895
32375
|
function sessionDataDir(sessionId, baseDir) {
|
|
31896
|
-
return
|
|
32376
|
+
return path42.join(getSessionsDir(baseDir), idSegment(sessionId));
|
|
31897
32377
|
}
|
|
31898
32378
|
var REJECTION_MESSAGE = "This session was created with an older version of Ordewell. Please re-plan.";
|
|
31899
32379
|
function isSessionMeta(value) {
|
|
@@ -31920,8 +32400,8 @@ function legacySessionFilename(session) {
|
|
|
31920
32400
|
return `${filenameStem(session)}_${session.id.slice(0, 8)}.json`;
|
|
31921
32401
|
}
|
|
31922
32402
|
function dropLegacyFile(sessionsDir, meta) {
|
|
31923
|
-
const legacy =
|
|
31924
|
-
if (legacy ===
|
|
32403
|
+
const legacy = path42.join(sessionsDir, legacySessionFilename(meta));
|
|
32404
|
+
if (legacy === path42.join(sessionsDir, sessionFilename(meta)) || !fs18.existsSync(legacy)) return;
|
|
31925
32405
|
try {
|
|
31926
32406
|
const held2 = JSON.parse(fs18.readFileSync(legacy, "utf-8")).meta?.id;
|
|
31927
32407
|
if (held2 === meta.id) fs18.unlinkSync(legacy);
|
|
@@ -31948,7 +32428,7 @@ function saveSession(plan, goal, baseDir, id2) {
|
|
|
31948
32428
|
};
|
|
31949
32429
|
dropRetiredPlanFields(session.plan);
|
|
31950
32430
|
const filename = sessionFilename(meta);
|
|
31951
|
-
const filePath =
|
|
32431
|
+
const filePath = path42.join(sessionsDir, filename);
|
|
31952
32432
|
writeFileAtomic(filePath, JSON.stringify(session, null, 2));
|
|
31953
32433
|
dropLegacyFile(sessionsDir, meta);
|
|
31954
32434
|
return meta;
|
|
@@ -31960,7 +32440,7 @@ function listSessions(baseDir, logger = defaultLogger) {
|
|
|
31960
32440
|
return files.map((f) => {
|
|
31961
32441
|
let parsed;
|
|
31962
32442
|
try {
|
|
31963
|
-
parsed = JSON.parse(fs18.readFileSync(
|
|
32443
|
+
parsed = JSON.parse(fs18.readFileSync(path42.join(sessionsDir, f), "utf-8"));
|
|
31964
32444
|
} catch (err) {
|
|
31965
32445
|
if (err instanceof SyntaxError) return moveAside(sessionsDir, f, "is not valid JSON", logger);
|
|
31966
32446
|
logger.warn("sessionStore", `failed to read session file ${f}; skipping`, err);
|
|
@@ -31981,7 +32461,7 @@ function listSessions(baseDir, logger = defaultLogger) {
|
|
|
31981
32461
|
function moveAside(sessionsDir, file, reason, logger) {
|
|
31982
32462
|
const aside = `${file}.${Date.now()}.corrupt`;
|
|
31983
32463
|
try {
|
|
31984
|
-
fs18.renameSync(
|
|
32464
|
+
fs18.renameSync(path42.join(sessionsDir, file), path42.join(sessionsDir, aside));
|
|
31985
32465
|
logger.warn("sessionStore", `session file ${file} ${reason}; moved aside to ${aside} rather than overwritten`);
|
|
31986
32466
|
} catch {
|
|
31987
32467
|
logger.warn("sessionStore", `failed to read session file ${file}; skipping`);
|
|
@@ -31992,7 +32472,7 @@ function findSessionFile(sessionsDir, sessionId, logger) {
|
|
|
31992
32472
|
const files = fs18.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
|
|
31993
32473
|
for (const f of files) {
|
|
31994
32474
|
try {
|
|
31995
|
-
const raw = fs18.readFileSync(
|
|
32475
|
+
const raw = fs18.readFileSync(path42.join(sessionsDir, f), "utf-8");
|
|
31996
32476
|
const session = JSON.parse(raw);
|
|
31997
32477
|
if (session.meta.id === sessionId) return f;
|
|
31998
32478
|
} catch (err) {
|
|
@@ -32007,7 +32487,7 @@ function loadSession(sessionId, baseDir, logger = defaultLogger) {
|
|
|
32007
32487
|
try {
|
|
32008
32488
|
const file = findSessionFile(sessionsDir, sessionId, logger);
|
|
32009
32489
|
if (file) {
|
|
32010
|
-
const raw = fs18.readFileSync(
|
|
32490
|
+
const raw = fs18.readFileSync(path42.join(sessionsDir, file), "utf-8");
|
|
32011
32491
|
const parsed = JSON.parse(raw);
|
|
32012
32492
|
if (hasScalarRunner(parsed)) {
|
|
32013
32493
|
logger.warn("sessionStore", `session file ${file}: ${REJECTION_MESSAGE}`);
|
|
@@ -32049,7 +32529,7 @@ function deleteSession(sessionId, baseDir, logger = defaultLogger) {
|
|
|
32049
32529
|
try {
|
|
32050
32530
|
const file = findSessionFile(sessionsDir, sessionId, logger);
|
|
32051
32531
|
if (file) {
|
|
32052
|
-
fs18.unlinkSync(
|
|
32532
|
+
fs18.unlinkSync(path42.join(sessionsDir, file));
|
|
32053
32533
|
return true;
|
|
32054
32534
|
}
|
|
32055
32535
|
} catch (err) {
|
|
@@ -32102,7 +32582,7 @@ var UNLEASED = {
|
|
|
32102
32582
|
};
|
|
32103
32583
|
var hostsHere = /* @__PURE__ */ new Set();
|
|
32104
32584
|
function leaseFile(sessionId, workspace) {
|
|
32105
|
-
return
|
|
32585
|
+
return path43.join(sessionDataDir(sessionId, workspace), LEASE_FILE);
|
|
32106
32586
|
}
|
|
32107
32587
|
function isRecord6(value) {
|
|
32108
32588
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -32191,7 +32671,7 @@ var LeaseHost = class {
|
|
|
32191
32671
|
*/
|
|
32192
32672
|
acquire(file, workspace, holder) {
|
|
32193
32673
|
ensureStateDirIgnored(workspace);
|
|
32194
|
-
ensurePrivateDir(
|
|
32674
|
+
ensurePrivateDir(path43.dirname(file));
|
|
32195
32675
|
const outcome = withFileLock(file, () => {
|
|
32196
32676
|
const current = readRecord(file);
|
|
32197
32677
|
const taken = current !== null && current.hostId !== this.id ? current : null;
|
|
@@ -32246,7 +32726,7 @@ var LeaseHost = class {
|
|
|
32246
32726
|
withFileLock(file, () => {
|
|
32247
32727
|
if (readRecord(file)?.hostId === this.id) fs19.rmSync(file, { force: true });
|
|
32248
32728
|
}, "release a session lease");
|
|
32249
|
-
fs19.rmdirSync(
|
|
32729
|
+
fs19.rmdirSync(path43.dirname(file));
|
|
32250
32730
|
} catch {
|
|
32251
32731
|
}
|
|
32252
32732
|
}
|
|
@@ -32439,10 +32919,14 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
32439
32919
|
/** Conflict repairs owed with every slot taken and no scheduler to claim them: a freed slot starts them. */
|
|
32440
32920
|
owedRepairs = /* @__PURE__ */ new Set();
|
|
32441
32921
|
/**
|
|
32442
|
-
* Tasks Mark complete is landing after it ended their attempt
|
|
32443
|
-
*
|
|
32922
|
+
* Tasks Mark complete is landing after it ended their attempt, or that
|
|
32923
|
+
* their conflict resolver is landing, and that landing: in progress with
|
|
32924
|
+
* no attempt for as long as it takes, and not orphans for it. Both can be
|
|
32925
|
+
* landing one task at once, so each holds it for itself (see {@link holdWhileLanding}).
|
|
32444
32926
|
*/
|
|
32445
|
-
completing = /* @__PURE__ */ new
|
|
32927
|
+
completing = /* @__PURE__ */ new Map();
|
|
32928
|
+
/** Tasks {@link releaseTask} is tearing down once their landings have settled: none may begin another. */
|
|
32929
|
+
releasing = /* @__PURE__ */ new Set();
|
|
32446
32930
|
/** Tasks paused on a usage limit, the run's hold on them, and each runner's wait for its reset (ADR-0018, U1). */
|
|
32447
32931
|
limitPauses;
|
|
32448
32932
|
/**
|
|
@@ -32578,6 +33062,10 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
32578
33062
|
getCompletionReceipt(taskId) {
|
|
32579
33063
|
return this.attempts.receipt(taskId);
|
|
32580
33064
|
}
|
|
33065
|
+
/** Which spawn of a live attempt's runner waits for Ordewell's tools to connect (ADR-0025); null when none does. */
|
|
33066
|
+
getToolsAttaching(taskId) {
|
|
33067
|
+
return this.attempts.attaching(taskId);
|
|
33068
|
+
}
|
|
32581
33069
|
/** Recent clean output of a task's latest attempt, running or ended; null if it never ran. */
|
|
32582
33070
|
getLiveOutput(taskId, opts) {
|
|
32583
33071
|
return this.attempts.liveTail(taskId, opts);
|
|
@@ -32742,12 +33230,13 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
32742
33230
|
/**
|
|
32743
33231
|
* Something can still start that nobody has to ask for again: a start
|
|
32744
33232
|
* admitted and not yet claimed, a force start queued behind its
|
|
32745
|
-
* dependencies, a
|
|
32746
|
-
*
|
|
32747
|
-
* replay. A Stop is owed to
|
|
33233
|
+
* dependencies, a conflict repair owed a slot or the end of a start hold,
|
|
33234
|
+
* a task that resumes on its own at its usage limit's reset, or a start a
|
|
33235
|
+
* dirty checkout parked for Stash and continue to replay. A Stop is owed to
|
|
33236
|
+
* it, whether or not anything runs.
|
|
32748
33237
|
*/
|
|
32749
33238
|
get startsPending() {
|
|
32750
|
-
return this.admissions > 0 || this.queuedStarts.size > 0 || this.runs.blocked || this.store.allTasks.some(waitsForReset);
|
|
33239
|
+
return this.admissions > 0 || this.queuedStarts.size > 0 || this.owedRepairs.size > 0 || this.runs.blocked || this.store.allTasks.some(waitsForReset);
|
|
32751
33240
|
}
|
|
32752
33241
|
/**
|
|
32753
33242
|
* The plan's activity as one word for every surface: a start on its way,
|
|
@@ -33183,7 +33672,9 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
33183
33672
|
this.store.markPending(task2.id);
|
|
33184
33673
|
this.hold(task2.id, failure2.hold, false);
|
|
33185
33674
|
}
|
|
33186
|
-
|
|
33675
|
+
if (failure2.leaves !== "held" || !failure2.preserveWorktree) {
|
|
33676
|
+
await this.runs.release(task2.id, { keep: failure2.leaves === "failed" && failure2.keepWorktree });
|
|
33677
|
+
}
|
|
33187
33678
|
const { level, text: text4, toastOnly } = failure2.notice;
|
|
33188
33679
|
if (toastOnly) this.notifications[level](text4);
|
|
33189
33680
|
else this.tell(level, text4);
|
|
@@ -33248,6 +33739,7 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
33248
33739
|
* Work that did not land waits on the user — it never fails the task or
|
|
33249
33740
|
* blocks its dependents — unless its conflict is owed a repair, which takes
|
|
33250
33741
|
* the slot the ending attempt freed and never one more than the run allows.
|
|
33742
|
+
* A planner turn's start hold holds it as it holds any new spawn (ADR-0029).
|
|
33251
33743
|
* The repair follows on from the start whose work it repairs: a Stop since
|
|
33252
33744
|
* that was admitted withdraws it.
|
|
33253
33745
|
*/
|
|
@@ -33255,20 +33747,22 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
33255
33747
|
this.store.markAwaitingUser(task2.id, "conflict");
|
|
33256
33748
|
this.say(outcome.messages);
|
|
33257
33749
|
if (outcome.kind !== "repair-needed") return;
|
|
33258
|
-
|
|
33750
|
+
const waitsFor = this.startHolds > 0 ? "the planner's turn ends" : this.slotsFull() ? "a slot is free" : null;
|
|
33751
|
+
if (!waitsFor) {
|
|
33259
33752
|
const claimed = this.claim(task2.id, admission);
|
|
33260
33753
|
if ("refused" in claimed) this.tell("warn", `The conflict of task "${task2.title}" is not repaired: ${claimed.refused}`);
|
|
33261
33754
|
else this.unawaited(claimed.spawned, `Could not start the repair of task "${task2.title}"`);
|
|
33262
33755
|
} else {
|
|
33263
33756
|
this.store.markPending(task2.id);
|
|
33264
33757
|
if (!this.isScheduling) this.owedRepairs.add(task2.id);
|
|
33265
|
-
this.tell("info", `Task "${task2.title}" is repaired once
|
|
33758
|
+
this.tell("info", `Task "${task2.title}" is repaired once ${waitsFor}.`);
|
|
33266
33759
|
}
|
|
33267
33760
|
}
|
|
33268
|
-
/** Start the repairs {@link settleUnlanded} left owed, as slots allow. */
|
|
33761
|
+
/** Start the repairs {@link settleUnlanded} left owed, as slots and any start hold allow. */
|
|
33269
33762
|
startOwedRepairs(admission) {
|
|
33763
|
+
if (this.startHolds > 0) return;
|
|
33270
33764
|
for (const id2 of [...this.owedRepairs]) {
|
|
33271
|
-
if (this.
|
|
33765
|
+
if (this.slotsFull()) return;
|
|
33272
33766
|
this.owedRepairs.delete(id2);
|
|
33273
33767
|
const task2 = this.store.get(id2);
|
|
33274
33768
|
if (!task2 || task2.status !== "pending" || this.attempts.has(id2) || !this.landing.nextRepair(id2)) continue;
|
|
@@ -33278,13 +33772,31 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
33278
33772
|
}
|
|
33279
33773
|
}
|
|
33280
33774
|
async landResolved(resolverId, admission) {
|
|
33281
|
-
const resolved =
|
|
33775
|
+
const resolved = this.landing.landResolved(resolverId, (id2) => this.releasing.has(id2));
|
|
33282
33776
|
if (!resolved) return;
|
|
33283
|
-
const { task: task2
|
|
33777
|
+
const { task: task2 } = resolved;
|
|
33778
|
+
const outcome = await this.holdWhileLanding(task2.id, resolved.outcome);
|
|
33779
|
+
if (this.store.get(task2.id)?.status === "completed") return;
|
|
33284
33780
|
await this.applyLanding(task2, outcome, admission, () => {
|
|
33285
33781
|
if (task2.verdict) this.logAndArchive(task2, task2.verdict);
|
|
33286
33782
|
});
|
|
33287
33783
|
}
|
|
33784
|
+
/**
|
|
33785
|
+
* Hold a task for as long as `landing` is merging its worktree. Its end lets
|
|
33786
|
+
* go of its own hold only: another landing of the task begun meanwhile
|
|
33787
|
+
* still holds it.
|
|
33788
|
+
*/
|
|
33789
|
+
async holdWhileLanding(taskId, landing2) {
|
|
33790
|
+
const holds2 = this.completing.get(taskId) ?? /* @__PURE__ */ new Set();
|
|
33791
|
+
holds2.add(landing2);
|
|
33792
|
+
this.completing.set(taskId, holds2);
|
|
33793
|
+
try {
|
|
33794
|
+
return await landing2;
|
|
33795
|
+
} finally {
|
|
33796
|
+
holds2.delete(landing2);
|
|
33797
|
+
if (holds2.size === 0 && this.completing.get(taskId) === holds2) this.completing.delete(taskId);
|
|
33798
|
+
}
|
|
33799
|
+
}
|
|
33288
33800
|
getReadyTasks() {
|
|
33289
33801
|
return this.readiness().ready;
|
|
33290
33802
|
}
|
|
@@ -33392,17 +33904,27 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
33392
33904
|
* as {@link cancelTask} does, but its worktree goes: no task is left to land
|
|
33393
33905
|
* it into. A spawn still in flight just loses its attempt,
|
|
33394
33906
|
* which is what makes {@link Attempts} kill the session it is about to
|
|
33395
|
-
* receive.
|
|
33396
|
-
*
|
|
33907
|
+
* receive. Every landing of it in flight — Mark complete's, its resolver's —
|
|
33908
|
+
* settles first, so its worktree is not torn down under a merge, and none
|
|
33909
|
+
* begins while it goes; a repair a landing claims is cancelled as any
|
|
33910
|
+
* running attempt is. The id's cross-attempt bookkeeping
|
|
33911
|
+
* goes too — a hold or retry count kept for a task that no longer exists
|
|
33912
|
+
* would be inherited by nothing.
|
|
33397
33913
|
*/
|
|
33398
33914
|
async releaseTask(taskId) {
|
|
33399
33915
|
this.limitPauses.cancel(taskId);
|
|
33400
|
-
|
|
33401
|
-
|
|
33402
|
-
|
|
33403
|
-
this.attempts.
|
|
33404
|
-
if (
|
|
33405
|
-
|
|
33916
|
+
for (let holds2 = this.completing.get(taskId); holds2; holds2 = this.completing.get(taskId)) await Promise.allSettled(holds2);
|
|
33917
|
+
this.releasing.add(taskId);
|
|
33918
|
+
try {
|
|
33919
|
+
const phase = this.attempts.phaseOf(taskId);
|
|
33920
|
+
if (phase === "running" || phase === "integrating") await this.cancelAttempt(taskId, { keep: false });
|
|
33921
|
+
else {
|
|
33922
|
+
this.attempts.end(taskId, "release");
|
|
33923
|
+
if (this.store.get(taskId)?.status === "in_progress") this.store.markPending(taskId);
|
|
33924
|
+
await this.runs.release(taskId, { keep: false });
|
|
33925
|
+
}
|
|
33926
|
+
} finally {
|
|
33927
|
+
this.releasing.delete(taskId);
|
|
33406
33928
|
}
|
|
33407
33929
|
this.onHold.delete(taskId);
|
|
33408
33930
|
this.retryCounts.delete(taskId);
|
|
@@ -33411,17 +33933,12 @@ var TaskOrchestrator = class _TaskOrchestrator {
|
|
|
33411
33933
|
async markTaskComplete(taskId) {
|
|
33412
33934
|
const task2 = this.store.get(taskId);
|
|
33413
33935
|
if (!task2 || task2.status === "completed") return;
|
|
33414
|
-
if (this.completing.has(taskId)) return;
|
|
33936
|
+
if (this.completing.has(taskId) || this.releasing.has(taskId)) return;
|
|
33415
33937
|
const admission = this.admittedNow();
|
|
33416
33938
|
const ended = this.attempts.end(taskId, "complete");
|
|
33417
33939
|
const verdict = this.attempts.markComplete(task2);
|
|
33418
|
-
this.
|
|
33419
|
-
|
|
33420
|
-
try {
|
|
33421
|
-
landing2 = await (ended?.integration ?? this.landing.landVouched(task2));
|
|
33422
|
-
} finally {
|
|
33423
|
-
this.completing.delete(taskId);
|
|
33424
|
-
}
|
|
33940
|
+
const landed2 = ended?.integration ?? this.landing.landVouched(task2);
|
|
33941
|
+
const landing2 = await this.holdWhileLanding(taskId, landed2);
|
|
33425
33942
|
this.store.setTaskVerdict(taskId, verdict);
|
|
33426
33943
|
const handoff = task2.type === "user" ? userTaskHandoff(task2.userSteps) : void 0;
|
|
33427
33944
|
this.attempts.handOver(taskId, summarizeOutput(verdict.reason, ""), handoff);
|
|
@@ -33710,7 +34227,7 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
33710
34227
|
if (!await this.openForAdmitted(taskId, origin, resume, admission)) return;
|
|
33711
34228
|
if (this.attempts.has(taskId)) return;
|
|
33712
34229
|
this.refuseOpsDuringMerge(taskId, forced);
|
|
33713
|
-
if (forced.queued && this.holdsQueued(taskId, forced)) {
|
|
34230
|
+
if (forced.queued && (this.holdsQueued(taskId, forced) || this.slotsFull())) {
|
|
33714
34231
|
this.queuedStarts.set(taskId, { pastGate: forced.pastGate });
|
|
33715
34232
|
return;
|
|
33716
34233
|
}
|
|
@@ -33769,8 +34286,9 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
33769
34286
|
this.queuedStarts.delete(id2);
|
|
33770
34287
|
continue;
|
|
33771
34288
|
}
|
|
34289
|
+
if (this.slotsFull()) continue;
|
|
33772
34290
|
const waits = this.startWaits(task2, task2.research ? void 0 : { queued: true, pastGate: queued.pastGate });
|
|
33773
|
-
if (task2.research &&
|
|
34291
|
+
if (task2.research && waits.length > 0) continue;
|
|
33774
34292
|
if (waits.some((w) => QUEUED_WAITS.has(w.kind))) continue;
|
|
33775
34293
|
if (this.leftToScheduler(task2) && queued.pastGate.length === 0) {
|
|
33776
34294
|
this.queuedStarts.delete(id2);
|
|
@@ -33799,6 +34317,9 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
33799
34317
|
const task2 = this.store.get(taskId);
|
|
33800
34318
|
return !!task2 && this.startWaits(task2, forced).some((w) => QUEUED_WAITS.has(w.kind) || w.kind === "dependencies" || w.kind === "merge-gate");
|
|
33801
34319
|
}
|
|
34320
|
+
slotsFull() {
|
|
34321
|
+
return this.attempts.size >= this.config.maxParallelSessions;
|
|
34322
|
+
}
|
|
33802
34323
|
sayQueuedAtGate(task2, queued, unmerged) {
|
|
33803
34324
|
const titles = unmerged.map((id2) => `"${this.store.get(id2)?.title ?? id2}"`).join(", ");
|
|
33804
34325
|
const notice = `Task "${task2.title}" waits for Merge all: the work of ${titles} is not merged into your branch yet. Merge all to start it, or force start it again to start it without that work.`;
|
|
@@ -33899,7 +34420,7 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
|
|
|
33899
34420
|
*/
|
|
33900
34421
|
async settleRun(after) {
|
|
33901
34422
|
if (!this.store.isFinished()) this.transition({ type: "reopen" });
|
|
33902
|
-
if (this.attempts.size > 0) return;
|
|
34423
|
+
if (this.attempts.size > 0 || this.owedRepairs.size > 0) return;
|
|
33903
34424
|
if (this.runState.kind === "held") return;
|
|
33904
34425
|
if (this.runState.kind !== "scheduling") {
|
|
33905
34426
|
if (after === "tick") {
|
|
@@ -34193,21 +34714,45 @@ var Planner = class {
|
|
|
34193
34714
|
};
|
|
34194
34715
|
|
|
34195
34716
|
// src/services/ModelResolver.ts
|
|
34196
|
-
var ModelResolver = class {
|
|
34717
|
+
var ModelResolver = class _ModelResolver {
|
|
34197
34718
|
constructor(registry, config, deps = {}) {
|
|
34198
34719
|
this.registry = registry;
|
|
34199
34720
|
this.config = config;
|
|
34200
|
-
this.discovery = new ModelDiscovery(registry, deps.execImpl, deps.fetchImpl);
|
|
34721
|
+
this.discovery = new ModelDiscovery(registry, deps.execImpl, deps.fetchImpl, void 0, void 0, void 0, deps.env);
|
|
34201
34722
|
this.fetchImpl = deps.fetchImpl;
|
|
34723
|
+
this.execImpl = deps.execImpl;
|
|
34724
|
+
this.shared = { vendor: { options: null, load: null, errors: {}, generation: 0 }, root: this, views: /* @__PURE__ */ new Map() };
|
|
34202
34725
|
}
|
|
34203
34726
|
registry;
|
|
34204
34727
|
config;
|
|
34205
34728
|
discovery;
|
|
34206
34729
|
fetchImpl;
|
|
34207
|
-
|
|
34208
|
-
|
|
34209
|
-
|
|
34210
|
-
|
|
34730
|
+
execImpl;
|
|
34731
|
+
shared;
|
|
34732
|
+
get vendor() {
|
|
34733
|
+
return this.shared.vendor;
|
|
34734
|
+
}
|
|
34735
|
+
/**
|
|
34736
|
+
* This resolver for runners launched with another environment, a
|
|
34737
|
+
* workspace's own: their catalogs are discovered and cached under `env`, as
|
|
34738
|
+
* `CLAUDE_CONFIG_DIR` there can name another account, while the vendor
|
|
34739
|
+
* catalogs stay this resolver's, fetched once. One view per `key`, made on
|
|
34740
|
+
* its first ask: a later ask with the same key gets that view, whatever
|
|
34741
|
+
* `env` it passes.
|
|
34742
|
+
*/
|
|
34743
|
+
withRunnerEnv(key, env) {
|
|
34744
|
+
let view = this.shared.views.get(key);
|
|
34745
|
+
if (!view) {
|
|
34746
|
+
view = new _ModelResolver(this.registry, this.config, { fetchImpl: this.fetchImpl, execImpl: this.execImpl, env });
|
|
34747
|
+
view.shared = this.shared;
|
|
34748
|
+
this.shared.views.set(key, view);
|
|
34749
|
+
}
|
|
34750
|
+
return view;
|
|
34751
|
+
}
|
|
34752
|
+
/** Every runner-environment catalog this resolver's family holds: its root's and each view's. */
|
|
34753
|
+
runnerCatalogs() {
|
|
34754
|
+
return [this.shared.root, ...this.shared.views.values()].map((resolver) => resolver.discovery);
|
|
34755
|
+
}
|
|
34211
34756
|
static builtinOptions() {
|
|
34212
34757
|
return toOrchestratorOptions({}, ORCHESTRATOR_SHORTCUTS);
|
|
34213
34758
|
}
|
|
@@ -34223,16 +34768,16 @@ var ModelResolver = class {
|
|
|
34223
34768
|
return Object.fromEntries(discovered);
|
|
34224
34769
|
}
|
|
34225
34770
|
async pickerOptions() {
|
|
34226
|
-
if (this.
|
|
34227
|
-
if (!this.
|
|
34771
|
+
if (this.vendor.options) return this.vendor.options;
|
|
34772
|
+
if (!this.vendor.load) {
|
|
34228
34773
|
const load = this.loadPickerOptions();
|
|
34229
|
-
this.
|
|
34774
|
+
this.vendor.load = load;
|
|
34230
34775
|
void load.finally(() => {
|
|
34231
|
-
if (this.
|
|
34776
|
+
if (this.vendor.load === load) this.vendor.load = null;
|
|
34232
34777
|
}).catch(() => {
|
|
34233
34778
|
});
|
|
34234
34779
|
}
|
|
34235
|
-
return this.
|
|
34780
|
+
return this.vendor.load;
|
|
34236
34781
|
}
|
|
34237
34782
|
/**
|
|
34238
34783
|
* Start loading the vendor catalogs without waiting for them, unless they
|
|
@@ -34240,20 +34785,25 @@ var ModelResolver = class {
|
|
|
34240
34785
|
* known yet reads it from them on a later call, never waiting on the fetch.
|
|
34241
34786
|
*/
|
|
34242
34787
|
warmPickerOptions() {
|
|
34243
|
-
if (this.
|
|
34788
|
+
if (this.vendor.options || this.vendor.load) return;
|
|
34244
34789
|
this.pickerOptions().catch(() => {
|
|
34245
34790
|
});
|
|
34246
34791
|
}
|
|
34247
34792
|
async loadPickerOptions() {
|
|
34793
|
+
const vendor = this.vendor;
|
|
34794
|
+
const generation2 = vendor.generation;
|
|
34248
34795
|
const { apiKeys, baseUrls } = collectProviderCredentials(this.config);
|
|
34249
34796
|
const { models, errors } = await fetchAllProviderModels({
|
|
34250
34797
|
apiKeys,
|
|
34251
34798
|
baseUrls,
|
|
34252
34799
|
fetchImpl: this.fetchImpl
|
|
34253
34800
|
});
|
|
34254
|
-
|
|
34255
|
-
|
|
34256
|
-
|
|
34801
|
+
const options = toOrchestratorOptions(models, ORCHESTRATOR_SHORTCUTS);
|
|
34802
|
+
if (vendor.generation === generation2) {
|
|
34803
|
+
vendor.errors = errors;
|
|
34804
|
+
vendor.options = options;
|
|
34805
|
+
}
|
|
34806
|
+
return options;
|
|
34257
34807
|
}
|
|
34258
34808
|
/**
|
|
34259
34809
|
* Per-provider failures from the most recent `pickerOptions()` fetch, keyed
|
|
@@ -34262,7 +34812,7 @@ var ModelResolver = class {
|
|
|
34262
34812
|
* key/endpoint is set but whose catalog could not be reached.
|
|
34263
34813
|
*/
|
|
34264
34814
|
getDiscoveryErrors() {
|
|
34265
|
-
return this.
|
|
34815
|
+
return this.vendor.errors;
|
|
34266
34816
|
}
|
|
34267
34817
|
async refresh() {
|
|
34268
34818
|
const options = await this.pickerOptions();
|
|
@@ -34273,8 +34823,25 @@ var ModelResolver = class {
|
|
|
34273
34823
|
this.config.setProviderModelLists(lists);
|
|
34274
34824
|
return lists;
|
|
34275
34825
|
}
|
|
34826
|
+
/** Forget every runner catalog, each workspace's included; the vendor catalogs stay. */
|
|
34276
34827
|
refreshRunnerModels() {
|
|
34277
|
-
this.discovery.clear();
|
|
34828
|
+
for (const discovery of this.runnerCatalogs()) discovery.clear();
|
|
34829
|
+
}
|
|
34830
|
+
/**
|
|
34831
|
+
* The models a pick for `provider` is judged against: a harness planner's
|
|
34832
|
+
* own runner catalog, or the vendor picker catalog (ADR-0009). Discovered
|
|
34833
|
+
* if not held. Never throws: a catalog that cannot be had is empty.
|
|
34834
|
+
*/
|
|
34835
|
+
async plannerCatalog(provider) {
|
|
34836
|
+
const runner = runnerForProvider(provider);
|
|
34837
|
+
if (runner) return candidatesOf((await this.modelsForRunners([runner]))[runner] ?? []);
|
|
34838
|
+
return (await this.pickerOptions().catch(() => [])).map((o) => ({ id: o.id }));
|
|
34839
|
+
}
|
|
34840
|
+
/** {@link plannerCatalog} from what is already held, so a cold catalog is empty; never discovers. */
|
|
34841
|
+
cachedPlannerCatalog(provider) {
|
|
34842
|
+
const runner = runnerForProvider(provider);
|
|
34843
|
+
if (runner) return candidatesOf(this.getCachedRunnerModels(runner));
|
|
34844
|
+
return this.getCachedPickerOptions().map((o) => ({ id: o.id }));
|
|
34278
34845
|
}
|
|
34279
34846
|
/**
|
|
34280
34847
|
* Synchronous reads of whatever the last `modelsForRunners`/`pickerOptions`
|
|
@@ -34285,7 +34852,7 @@ var ModelResolver = class {
|
|
|
34285
34852
|
return this.discovery.getCached(runner) ?? [];
|
|
34286
34853
|
}
|
|
34287
34854
|
getCachedPickerOptions() {
|
|
34288
|
-
return this.
|
|
34855
|
+
return this.vendor.options ?? [];
|
|
34289
34856
|
}
|
|
34290
34857
|
/**
|
|
34291
34858
|
* A model's context window from whatever catalog is already cached (#49),
|
|
@@ -34298,7 +34865,7 @@ var ModelResolver = class {
|
|
|
34298
34865
|
*/
|
|
34299
34866
|
contextWindowFor(source, model) {
|
|
34300
34867
|
if (!model) return void 0;
|
|
34301
|
-
const window2 = isApiProvider(source) ? this.
|
|
34868
|
+
const window2 = isApiProvider(source) ? this.vendor.options?.find((o) => o.apiProvider === source && o.id === prefixModelId(source, stripModelPrefix(model, source)))?.contextWindow : this.discovery.getCached(source)?.find((m) => m.modelId === model)?.contextWindow;
|
|
34302
34869
|
return window2 && window2 > 0 ? window2 : void 0;
|
|
34303
34870
|
}
|
|
34304
34871
|
/**
|
|
@@ -34308,14 +34875,16 @@ var ModelResolver = class {
|
|
|
34308
34875
|
*/
|
|
34309
34876
|
maxOutputTokensFor(source, model) {
|
|
34310
34877
|
if (!model || !isApiProvider(source)) return void 0;
|
|
34311
|
-
const cap = this.
|
|
34878
|
+
const cap = this.vendor.options?.find((o) => o.apiProvider === source && o.id === prefixModelId(source, stripModelPrefix(model, source)))?.maxOutputTokens;
|
|
34312
34879
|
return cap && cap > 0 ? cap : void 0;
|
|
34313
34880
|
}
|
|
34881
|
+
/** Forget every catalog: the vendor ones, and each runner environment's. */
|
|
34314
34882
|
invalidate() {
|
|
34315
|
-
this.
|
|
34316
|
-
this.
|
|
34317
|
-
this.
|
|
34318
|
-
this.
|
|
34883
|
+
this.refreshRunnerModels();
|
|
34884
|
+
this.vendor.generation++;
|
|
34885
|
+
this.vendor.options = null;
|
|
34886
|
+
this.vendor.load = null;
|
|
34887
|
+
this.vendor.errors = {};
|
|
34319
34888
|
if (!this.fetchImpl) {
|
|
34320
34889
|
for (const provider of PROVIDER_PRIORITY) {
|
|
34321
34890
|
const baseUrl = this.config.getProviderBaseUrl(provider);
|
|
@@ -34325,6 +34894,9 @@ var ModelResolver = class {
|
|
|
34325
34894
|
}
|
|
34326
34895
|
}
|
|
34327
34896
|
};
|
|
34897
|
+
function candidatesOf(models) {
|
|
34898
|
+
return models.map((m) => ({ id: m.modelId, variants: m.variants }));
|
|
34899
|
+
}
|
|
34328
34900
|
|
|
34329
34901
|
// src/services/RunnerInstallation.ts
|
|
34330
34902
|
import { exec as exec4 } from "child_process";
|
|
@@ -34918,7 +35490,7 @@ import { spawn as nodeSpawn3 } from "child_process";
|
|
|
34918
35490
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
34919
35491
|
import { mkdtempSync as mkdtempSync5, rmSync as rmSync12 } from "fs";
|
|
34920
35492
|
import { tmpdir as tmpdir6 } from "os";
|
|
34921
|
-
import { join as
|
|
35493
|
+
import { join as join39 } from "path";
|
|
34922
35494
|
var DEFAULT_TIMEOUT_MS5 = 2e4;
|
|
34923
35495
|
var INITIALIZE_TIMEOUT_MS2 = 1e4;
|
|
34924
35496
|
var USAGE_TIMEOUT_MS = 1e4;
|
|
@@ -34950,7 +35522,7 @@ function claudeAccountLimitCollector(options) {
|
|
|
34950
35522
|
};
|
|
34951
35523
|
}
|
|
34952
35524
|
async function readClaudeAccountLimits(options, signal) {
|
|
34953
|
-
const cwd = mkdtempSync5(
|
|
35525
|
+
const cwd = mkdtempSync5(join39(tmpdir6(), "ordewell-claude-limits-"));
|
|
34954
35526
|
const removeCwd = () => {
|
|
34955
35527
|
try {
|
|
34956
35528
|
rmSync12(cwd, { recursive: true, force: true });
|
|
@@ -35034,13 +35606,13 @@ function isoInstant(value) {
|
|
|
35034
35606
|
}
|
|
35035
35607
|
|
|
35036
35608
|
// src/services/harness/openCodeGoLimits.ts
|
|
35037
|
-
import { homedir as
|
|
35609
|
+
import { homedir as homedir9 } from "os";
|
|
35038
35610
|
|
|
35039
35611
|
// src/services/harness/openCodeCredentials.ts
|
|
35040
|
-
import { createHash as
|
|
35612
|
+
import { createHash as createHash6 } from "crypto";
|
|
35041
35613
|
import { promises as fs20 } from "fs";
|
|
35042
35614
|
import { homedir as osHomedir } from "os";
|
|
35043
|
-
import { join as
|
|
35615
|
+
import { join as join40, resolve as resolve13 } from "path";
|
|
35044
35616
|
import { Worker } from "worker_threads";
|
|
35045
35617
|
var DEFAULT_TIMEOUT_MS6 = 5e3;
|
|
35046
35618
|
var BUSY_MS = 250;
|
|
@@ -35095,19 +35667,19 @@ function walStore(path) {
|
|
|
35095
35667
|
parentPort.postMessage(select());
|
|
35096
35668
|
`;
|
|
35097
35669
|
var isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
35098
|
-
var digest = (...parts) =>
|
|
35670
|
+
var digest = (...parts) => createHash6("sha256").update(parts.join("\0")).digest("hex").slice(0, 32);
|
|
35099
35671
|
function openCodeStorePaths(env, home) {
|
|
35100
|
-
const data =
|
|
35672
|
+
const data = join40(env.XDG_DATA_HOME || join40(home, ".local", "share"), "opencode");
|
|
35101
35673
|
const file = env.OPENCODE_DB || "opencode.db";
|
|
35102
|
-
return { db: file === ":memory:" ? null : resolve13(data, file), auth:
|
|
35674
|
+
return { db: file === ":memory:" ? null : resolve13(data, file), auth: join40(data, "auth.json") };
|
|
35103
35675
|
}
|
|
35104
35676
|
function openCodeGoLoginReader(deps = {}) {
|
|
35105
|
-
const
|
|
35677
|
+
const homedir10 = deps.homedir ?? osHomedir;
|
|
35106
35678
|
let last;
|
|
35107
35679
|
return {
|
|
35108
35680
|
async read(env, signal) {
|
|
35109
35681
|
if (signal.aborted) return { kind: "unreadable" };
|
|
35110
|
-
const paths = openCodeStorePaths(env,
|
|
35682
|
+
const paths = openCodeStorePaths(env, homedir10());
|
|
35111
35683
|
const [db, wal, auth] = await Promise.all([
|
|
35112
35684
|
paths.db ? stamp(paths.db) : null,
|
|
35113
35685
|
paths.db ? stamp(`${paths.db}-wal`) : null,
|
|
@@ -35161,8 +35733,8 @@ function v2Login(db, row) {
|
|
|
35161
35733
|
login: { route: "console", owner: digest("console", db, row.id, account, orgID ?? ""), access: value.access, ...orgID ? { orgID } : {}, expires: value.expires }
|
|
35162
35734
|
};
|
|
35163
35735
|
}
|
|
35164
|
-
async function legacyLogin(
|
|
35165
|
-
const content = env.OPENCODE_AUTH_CONTENT ?? await readSmall(
|
|
35736
|
+
async function legacyLogin(path49, env) {
|
|
35737
|
+
const content = env.OPENCODE_AUTH_CONTENT ?? await readSmall(path49);
|
|
35166
35738
|
if (content === void 0) return env[ENV_KEY] ? { kind: "unsupported" } : { kind: "none" };
|
|
35167
35739
|
if (content === null) return { kind: "unreadable" };
|
|
35168
35740
|
const auth = parseJson(content);
|
|
@@ -35171,15 +35743,15 @@ async function legacyLogin(path46, env) {
|
|
|
35171
35743
|
if (entry === void 0) return env[ENV_KEY] ? { kind: "unsupported" } : { kind: "none" };
|
|
35172
35744
|
if (!isRecord9(entry) || entry.type !== "api") return { kind: "unsupported" };
|
|
35173
35745
|
if (typeof entry.key !== "string" || entry.key.length === 0) return { kind: "unreadable" };
|
|
35174
|
-
return { kind: "login", login: { route: "key", owner: digest("v1",
|
|
35746
|
+
return { kind: "login", login: { route: "key", owner: digest("v1", path49, entry.key), key: entry.key } };
|
|
35175
35747
|
}
|
|
35176
|
-
function queryStore(
|
|
35748
|
+
function queryStore(path49, signal, deps) {
|
|
35177
35749
|
return new Promise((settle) => {
|
|
35178
35750
|
let worker;
|
|
35179
35751
|
try {
|
|
35180
35752
|
worker = (deps.spawnWorker ?? ((source, options) => new Worker(source, options)))(WORKER_SOURCE, {
|
|
35181
35753
|
eval: true,
|
|
35182
|
-
workerData: { path:
|
|
35754
|
+
workerData: { path: path49, integrations: GO_INTEGRATIONS, busyMs: BUSY_MS, maxValueBytes: MAX_VALUE_BYTES },
|
|
35183
35755
|
execArgv: deps.workerExecArgv ?? [],
|
|
35184
35756
|
resourceLimits: { maxOldGenerationSizeMb: 64 }
|
|
35185
35757
|
});
|
|
@@ -35206,19 +35778,19 @@ function queryStore(path46, signal, deps) {
|
|
|
35206
35778
|
worker.once("exit", aborted);
|
|
35207
35779
|
});
|
|
35208
35780
|
}
|
|
35209
|
-
async function stamp(
|
|
35781
|
+
async function stamp(path49) {
|
|
35210
35782
|
try {
|
|
35211
|
-
const stat = await fs20.stat(
|
|
35783
|
+
const stat = await fs20.stat(path49);
|
|
35212
35784
|
return [stat.ino, stat.size, stat.mtimeMs];
|
|
35213
35785
|
} catch {
|
|
35214
35786
|
return null;
|
|
35215
35787
|
}
|
|
35216
35788
|
}
|
|
35217
|
-
async function readSmall(
|
|
35789
|
+
async function readSmall(path49) {
|
|
35218
35790
|
try {
|
|
35219
|
-
const stat = await fs20.stat(
|
|
35791
|
+
const stat = await fs20.stat(path49);
|
|
35220
35792
|
if (!stat.isFile() || stat.size > MAX_AUTH_FILE_BYTES) return null;
|
|
35221
|
-
return await fs20.readFile(
|
|
35793
|
+
return await fs20.readFile(path49, "utf8");
|
|
35222
35794
|
} catch (err) {
|
|
35223
35795
|
return err.code === "ENOENT" ? void 0 : null;
|
|
35224
35796
|
}
|
|
@@ -35251,7 +35823,7 @@ function openCodeGoAccountLimitCollector(options) {
|
|
|
35251
35823
|
label: "OpenCode Go",
|
|
35252
35824
|
provider: "opencode-go",
|
|
35253
35825
|
scope: () => {
|
|
35254
|
-
const paths = openCodeStorePaths(env(), (deps.reader?.homedir ??
|
|
35826
|
+
const paths = openCodeStorePaths(env(), (deps.reader?.homedir ?? homedir9)());
|
|
35255
35827
|
return ["opencode", paths.db ?? ":memory:", paths.auth].join("\0");
|
|
35256
35828
|
},
|
|
35257
35829
|
acquire: async (signal) => {
|
|
@@ -36347,20 +36919,20 @@ function createAiService(config, deps) {
|
|
|
36347
36919
|
}
|
|
36348
36920
|
|
|
36349
36921
|
// src/services/createSession.ts
|
|
36350
|
-
import * as
|
|
36922
|
+
import * as path46 from "path";
|
|
36351
36923
|
|
|
36352
36924
|
// src/services/handoffReferences.ts
|
|
36353
36925
|
import * as fs21 from "fs";
|
|
36354
|
-
import * as
|
|
36926
|
+
import * as path44 from "path";
|
|
36355
36927
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
36356
36928
|
function resolveHandoffReferences(handoff, workspace) {
|
|
36357
36929
|
const checked = /* @__PURE__ */ new Map();
|
|
36358
36930
|
const contained = (candidate) => {
|
|
36359
36931
|
try {
|
|
36360
36932
|
const root = fs21.realpathSync(workspace);
|
|
36361
|
-
const resolved = fs21.realpathSync(
|
|
36362
|
-
const relative17 =
|
|
36363
|
-
return relative17 !== ".." && !relative17.startsWith(`..${
|
|
36933
|
+
const resolved = fs21.realpathSync(path44.resolve(root, candidate));
|
|
36934
|
+
const relative17 = path44.relative(root, resolved);
|
|
36935
|
+
return relative17 !== ".." && !relative17.startsWith(`..${path44.sep}`) && !path44.isAbsolute(relative17) ? resolved : void 0;
|
|
36364
36936
|
} catch {
|
|
36365
36937
|
return void 0;
|
|
36366
36938
|
}
|
|
@@ -36381,7 +36953,7 @@ function resolveHandoffReferences(handoff, workspace) {
|
|
|
36381
36953
|
}
|
|
36382
36954
|
} else if (/^[0-9a-fA-F]{4,64}$/.test(ref.sha)) {
|
|
36383
36955
|
const repo = contained(ref.repo ?? ".");
|
|
36384
|
-
if (repo && fs21.existsSync(
|
|
36956
|
+
if (repo && fs21.existsSync(path44.join(repo, ".git"))) {
|
|
36385
36957
|
const result2 = spawnSync2("git", ["--no-optional-locks", "--no-replace-objects", "cat-file", "-t", ref.sha], {
|
|
36386
36958
|
cwd: repo,
|
|
36387
36959
|
encoding: "utf8",
|
|
@@ -37733,7 +38305,8 @@ var SessionEventRelay = class {
|
|
|
37733
38305
|
this.checkpointQuestion(t.id),
|
|
37734
38306
|
this.waitsForApproval(t.id),
|
|
37735
38307
|
this.orchestrator.holdReason(t.id),
|
|
37736
|
-
this.orchestrator.getCompletionReceipt(t.id)
|
|
38308
|
+
this.orchestrator.getCompletionReceipt(t.id),
|
|
38309
|
+
this.orchestrator.getToolsAttaching(t.id)
|
|
37737
38310
|
)),
|
|
37738
38311
|
...runMerge ? { runMerge } : {},
|
|
37739
38312
|
activity: this.orchestrator.activity
|
|
@@ -37855,10 +38428,10 @@ var SessionEventRelay = class {
|
|
|
37855
38428
|
|
|
37856
38429
|
// src/utils/taskLogStore.ts
|
|
37857
38430
|
import * as fs22 from "fs";
|
|
37858
|
-
import * as
|
|
38431
|
+
import * as path45 from "path";
|
|
37859
38432
|
var EXTENSION = ".jsonl";
|
|
37860
38433
|
function taskDir({ baseDir, sessionId }, taskId) {
|
|
37861
|
-
return
|
|
38434
|
+
return path45.join(sessionDataDir(sessionId, baseDir), "tasks", idSegment(taskId));
|
|
37862
38435
|
}
|
|
37863
38436
|
function listTaskLogAttempts(location, taskId) {
|
|
37864
38437
|
let names;
|
|
@@ -37876,7 +38449,7 @@ function openTaskLog(location, taskId, attempt) {
|
|
|
37876
38449
|
const next = (listTaskLogAttempts(location, taskId).pop() ?? 0) + 1;
|
|
37877
38450
|
let number = attempt !== void 0 && Number.isInteger(attempt) && attempt >= 1 ? attempt : next;
|
|
37878
38451
|
for (; ; ) {
|
|
37879
|
-
const file =
|
|
38452
|
+
const file = path45.join(dir, `${number}${EXTENSION}`);
|
|
37880
38453
|
try {
|
|
37881
38454
|
fs22.writeFileSync(file, "", { flag: "wx" });
|
|
37882
38455
|
} catch (err) {
|
|
@@ -37900,15 +38473,15 @@ function prepareTaskOutputFile(location, taskId, attempt) {
|
|
|
37900
38473
|
ensureStateDirIgnored(location.baseDir);
|
|
37901
38474
|
const dir = taskDir(location, taskId);
|
|
37902
38475
|
fs22.mkdirSync(dir, { recursive: true });
|
|
37903
|
-
return
|
|
38476
|
+
return path45.join(dir, `${attempt}.output.txt`);
|
|
37904
38477
|
}
|
|
37905
38478
|
function taskPatchFile(location, taskId, attempt) {
|
|
37906
|
-
return
|
|
38479
|
+
return path45.join(taskDir(location, taskId), `${attempt}.patch`);
|
|
37907
38480
|
}
|
|
37908
38481
|
function saveTaskPatch(location, taskId, attempt, patch) {
|
|
37909
38482
|
ensureStateDirIgnored(location.baseDir);
|
|
37910
38483
|
const file = taskPatchFile(location, taskId, attempt);
|
|
37911
|
-
fs22.mkdirSync(
|
|
38484
|
+
fs22.mkdirSync(path45.dirname(file), { recursive: true });
|
|
37912
38485
|
const partial = `${file}.${process.pid}-${Date.now()}.partial`;
|
|
37913
38486
|
fs22.writeFileSync(partial, patch, { flag: "wx" });
|
|
37914
38487
|
fs22.renameSync(partial, file);
|
|
@@ -37929,7 +38502,7 @@ function readTaskLog(location, taskId, attempt) {
|
|
|
37929
38502
|
if (!Number.isInteger(attempt) || attempt < 1) return [];
|
|
37930
38503
|
let raw;
|
|
37931
38504
|
try {
|
|
37932
|
-
raw = fs22.readFileSync(
|
|
38505
|
+
raw = fs22.readFileSync(path45.join(taskDir(location, taskId), `${attempt}${EXTENSION}`), "utf-8");
|
|
37933
38506
|
} catch {
|
|
37934
38507
|
return [];
|
|
37935
38508
|
}
|
|
@@ -39958,7 +40531,7 @@ function createSession(deps) {
|
|
|
39958
40531
|
...deps.openTaskLog ? {} : {
|
|
39959
40532
|
keepPatch: (taskId, attempt, patch) => {
|
|
39960
40533
|
const location = session.taskLogLocation;
|
|
39961
|
-
return
|
|
40534
|
+
return path46.relative(location.baseDir, saveTaskPatch(location, taskId, attempt, patch)).split(path46.sep).join("/");
|
|
39962
40535
|
}
|
|
39963
40536
|
},
|
|
39964
40537
|
recordHandoff: (sessionId, taskId, handoff) => taskLogs.note(sessionId, taskId, { type: "handoff", handoff }),
|
|
@@ -42087,6 +42660,7 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
42087
42660
|
*/
|
|
42088
42661
|
async startAttached(opts) {
|
|
42089
42662
|
const { server: server4, scope, shell, savePatch, webReads } = this.launch.tools;
|
|
42663
|
+
this.launch.onAttaching?.(1);
|
|
42090
42664
|
const spawned = await spawnAttached({
|
|
42091
42665
|
runner: this.launch.runner,
|
|
42092
42666
|
create: () => this.launch.createAdapter(this.launch.runner, this.launch.deps),
|
|
@@ -42123,7 +42697,10 @@ var StructuredSession = class extends AbstractRunnerSession {
|
|
|
42123
42697
|
},
|
|
42124
42698
|
cancelled: this.ended.signal,
|
|
42125
42699
|
startupExit: () => this.startupExit,
|
|
42126
|
-
onRespawn: (failure2) =>
|
|
42700
|
+
onRespawn: (failure2) => {
|
|
42701
|
+
this.launch.onNotice?.(`${this.launch.runner} started without Ordewell's tools (${failure2}); respawning once.`);
|
|
42702
|
+
this.launch.onAttaching?.(2);
|
|
42703
|
+
},
|
|
42127
42704
|
failed: (first, second) => `Could not start ${this.launch.runner} with Ordewell's tools, so the task was not sent to it. First spawn: ${first}. Respawn: ${second}.`
|
|
42128
42705
|
});
|
|
42129
42706
|
if (!spawned) return;
|
|
@@ -42706,16 +43283,22 @@ var StructuredRunner = class extends AbstractRunner {
|
|
|
42706
43283
|
checkHandoff: opts.checkHandoff ?? ((completion) => checkHandoff(completion, opts.taskId, [])),
|
|
42707
43284
|
...opts.readTask ? { readTask: opts.readTask } : {},
|
|
42708
43285
|
...opts.applyPatch ? { applyPatch: opts.applyPatch } : {},
|
|
42709
|
-
onNotice: opts.onNotice
|
|
43286
|
+
onNotice: opts.onNotice,
|
|
43287
|
+
onAttaching: opts.onAttaching
|
|
42710
43288
|
});
|
|
42711
43289
|
console.error(`[structured] Starting ${opts.runner} [${opts.mode ?? "default"}] (${opts.modelId || "default"}) for task ${opts.taskId.slice(0, 8)}`);
|
|
42712
43290
|
this.registerSession(id2, session);
|
|
43291
|
+
const cancel = () => void session.stop();
|
|
43292
|
+
opts.cancelled?.addEventListener("abort", cancel, { once: true });
|
|
42713
43293
|
try {
|
|
43294
|
+
if (opts.cancelled?.aborted) cancel();
|
|
42714
43295
|
await session.start(opts.prompt);
|
|
42715
43296
|
} catch (err) {
|
|
42716
43297
|
session.kill();
|
|
42717
43298
|
this.sessions.delete(id2);
|
|
42718
43299
|
throw err;
|
|
43300
|
+
} finally {
|
|
43301
|
+
opts.cancelled?.removeEventListener("abort", cancel);
|
|
42719
43302
|
}
|
|
42720
43303
|
return session;
|
|
42721
43304
|
}
|
|
@@ -42789,7 +43372,7 @@ var RunnerRegistry = class {
|
|
|
42789
43372
|
|
|
42790
43373
|
// src/plugins/removedPluginNotice.ts
|
|
42791
43374
|
import * as fs23 from "fs";
|
|
42792
|
-
import * as
|
|
43375
|
+
import * as path47 from "path";
|
|
42793
43376
|
function isManifestFile(file) {
|
|
42794
43377
|
try {
|
|
42795
43378
|
return fs23.statSync(file).isFile();
|
|
@@ -42798,10 +43381,10 @@ function isManifestFile(file) {
|
|
|
42798
43381
|
}
|
|
42799
43382
|
}
|
|
42800
43383
|
function removedPluginNotice(dataDir = globalDataDir()) {
|
|
42801
|
-
const directory =
|
|
43384
|
+
const directory = path47.join(dataDir, "plugins");
|
|
42802
43385
|
try {
|
|
42803
|
-
const hasManifest = isManifestFile(
|
|
42804
|
-
(entry) => (entry.isDirectory() || entry.isSymbolicLink()) && isManifestFile(
|
|
43386
|
+
const hasManifest = isManifestFile(path47.join(directory, "manifest.json")) || fs23.readdirSync(directory, { withFileTypes: true }).some(
|
|
43387
|
+
(entry) => (entry.isDirectory() || entry.isSymbolicLink()) && isManifestFile(path47.join(directory, entry.name, "manifest.json"))
|
|
42805
43388
|
);
|
|
42806
43389
|
if (hasManifest) return "Plugin runners were removed; manifests in ~/.ordewell/plugins/ are ignored. See the CHANGELOG.";
|
|
42807
43390
|
} catch {
|
|
@@ -42812,15 +43395,15 @@ function removedPluginNotice(dataDir = globalDataDir()) {
|
|
|
42812
43395
|
|
|
42813
43396
|
// src/utils/stateStore.ts
|
|
42814
43397
|
import * as fs24 from "fs";
|
|
42815
|
-
import * as
|
|
43398
|
+
import * as path48 from "path";
|
|
42816
43399
|
var STATE_FILE2 = "state.json";
|
|
42817
43400
|
function getStatePath(baseDir) {
|
|
42818
|
-
return
|
|
43401
|
+
return path48.join(getStateDir(baseDir), STATE_FILE2);
|
|
42819
43402
|
}
|
|
42820
43403
|
var SESSION_ID_KEY = "sessionId";
|
|
42821
43404
|
function saveState(plan, baseDir, sessionId) {
|
|
42822
43405
|
const statePath = getStatePath(baseDir ?? process.cwd());
|
|
42823
|
-
ensureDir(
|
|
43406
|
+
ensureDir(path48.dirname(statePath));
|
|
42824
43407
|
plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
42825
43408
|
const file = sessionId ? { ...plan, [SESSION_ID_KEY]: sessionId } : plan;
|
|
42826
43409
|
dropRetiredPlanFields(file);
|
|
@@ -43106,6 +43689,7 @@ export {
|
|
|
43106
43689
|
assignedRunnerOf,
|
|
43107
43690
|
assignmentIssue,
|
|
43108
43691
|
assignmentIssueText,
|
|
43692
|
+
attachingLabel,
|
|
43109
43693
|
augmentedPath,
|
|
43110
43694
|
automergeSummary,
|
|
43111
43695
|
autonomyLevelLabel,
|
|
@@ -43367,6 +43951,7 @@ export {
|
|
|
43367
43951
|
repoBaseLine,
|
|
43368
43952
|
repoResultLines,
|
|
43369
43953
|
reposWithWork,
|
|
43954
|
+
repositoryRunnerConfigPaths,
|
|
43370
43955
|
researchShellWarning,
|
|
43371
43956
|
researchToolsPath,
|
|
43372
43957
|
resetInstalledPlanner,
|