@ordewell/core 0.11.0 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -210,6 +210,7 @@ __export(index_exports, {
210
210
  assignedRunnerOf: () => assignedRunnerOf,
211
211
  assignmentIssue: () => assignmentIssue,
212
212
  assignmentIssueText: () => assignmentIssueText,
213
+ attachingLabel: () => attachingLabel,
213
214
  augmentedPath: () => augmentedPath,
214
215
  automergeSummary: () => automergeSummary,
215
216
  autonomyLevelLabel: () => autonomyLevelLabel,
@@ -661,18 +662,18 @@ function dependencyTargetErrors(tasks, taskId, dependencies) {
661
662
  }
662
663
  function findDependencyCycle(tasks) {
663
664
  const byId = new Map([...walkPlan(tasks)].map(({ task: task2 }) => [task2.id, task2]));
664
- const path47 = [];
665
+ const path48 = [];
665
666
  const done = /* @__PURE__ */ new Set();
666
667
  const visit = (id2) => {
667
668
  if (done.has(id2)) return null;
668
- const at = path47.indexOf(id2);
669
- if (at >= 0) return path47.slice(at);
670
- path47.push(id2);
669
+ const at = path48.indexOf(id2);
670
+ if (at >= 0) return path48.slice(at);
671
+ path48.push(id2);
671
672
  for (const dep of byId.get(id2)?.dependencies ?? []) {
672
673
  const hit = visit(dep);
673
674
  if (hit) return hit;
674
675
  }
675
- path47.pop();
676
+ path48.pop();
676
677
  done.add(id2);
677
678
  return null;
678
679
  };
@@ -1737,7 +1738,7 @@ function lastServedModel(events) {
1737
1738
  return void 0;
1738
1739
  }
1739
1740
  function taskLogForSurface(events) {
1740
- return events.map((event) => event.type === "task_skills" ? { type: "task_skills", skills: event.skills.map(({ name, source, path: path47 }) => ({ name, source, path: path47 })) } : event);
1741
+ return events.map((event) => event.type === "task_skills" ? { type: "task_skills", skills: event.skills.map(({ name, source, path: path48 }) => ({ name, source, path: path48 })) } : event);
1741
1742
  }
1742
1743
  function coalesceTaskLog(events) {
1743
1744
  const out = [];
@@ -2780,9 +2781,9 @@ function literalCdTarget(seg) {
2780
2781
  return target === "" || target.startsWith("-") || target.startsWith("~") || /[*?[]/.test(target) ? void 0 : target;
2781
2782
  }
2782
2783
  function followCd(segments) {
2783
- if (segments.some((s) => s.joinedBy === "other" || SHELL_STATE_COMMANDS.has(s.binary))) return;
2784
+ if (segments.some((s) => s.joinedBy === "other" || movesShell(s))) return;
2784
2785
  const inPipeline = (i) => segments[i].joinedBy === "pipe" || segments[i + 1]?.joinedBy === "pipe";
2785
- if (segments.some((s, i) => s.binary === "cd" && !inPipeline(i) && literalCdTarget(s) === void 0)) return;
2786
+ if (segments.some((s, i) => s.binary.toLowerCase() === "cd" && !inPipeline(i) && literalCdTarget(s) === void 0)) return;
2786
2787
  let chain = [];
2787
2788
  segments.forEach((seg, i) => {
2788
2789
  seg.cwd = chain;
@@ -2791,7 +2792,7 @@ function followCd(segments) {
2791
2792
  });
2792
2793
  }
2793
2794
  function markShellState(segments, bareSegment) {
2794
- if (!bareSegment && !segments.some((s) => SHELL_STATE_COMMANDS.has(s.binary))) return;
2795
+ if (!bareSegment && !segments.some(movesShell)) return;
2795
2796
  for (const seg of segments) if (seg.binary === "cd") seg.cdSteered = true;
2796
2797
  }
2797
2798
  var SHELL_STATE_COMMANDS = /* @__PURE__ */ new Set([
@@ -2810,6 +2811,7 @@ var SHELL_STATE_COMMANDS = /* @__PURE__ */ new Set([
2810
2811
  "exec",
2811
2812
  "pushd",
2812
2813
  "popd",
2814
+ "chdir",
2813
2815
  "alias",
2814
2816
  "unalias",
2815
2817
  "builtin",
@@ -2818,6 +2820,9 @@ var SHELL_STATE_COMMANDS = /* @__PURE__ */ new Set([
2818
2820
  "hash",
2819
2821
  "trap"
2820
2822
  ]);
2823
+ function movesShell(seg) {
2824
+ return SHELL_STATE_COMMANDS.has(seg.binary.toLowerCase());
2825
+ }
2821
2826
 
2822
2827
  // src/services/commandRunners.ts
2823
2828
  var HELP_FLAGS = ["--help", "--version"];
@@ -3673,8 +3678,9 @@ var FLAG_POLICY = {
3673
3678
  sort: {
3674
3679
  cluster: true,
3675
3680
  // Absent on purpose: `-o`/`--output` writes a file with no redirect for the
3676
- // redirect check to see, and `--compress-program` names a program that sort
3677
- // runs over its own temporary files — a permitted binary as an interpreter.
3681
+ // redirect check to see, `-T`/`--temporary-directory` names where sort writes
3682
+ // its run files, and `--compress-program` names a program that sort runs
3683
+ // over its own temporary files — a permitted binary as an interpreter.
3678
3684
  booleans: [
3679
3685
  "-b",
3680
3686
  "-c",
@@ -3713,11 +3719,9 @@ var FLAG_POLICY = {
3713
3719
  "-k",
3714
3720
  "-t",
3715
3721
  "-S",
3716
- "-T",
3717
3722
  "--key",
3718
3723
  "--field-separator",
3719
3724
  "--buffer-size",
3720
- "--temporary-directory",
3721
3725
  "--random-source",
3722
3726
  "--batch-size",
3723
3727
  "--parallel",
@@ -4344,23 +4348,28 @@ function scanFlags(seg) {
4344
4348
  const booleans = /* @__PURE__ */ new Set([...spec.booleans ?? [], ...UNIVERSAL_FLAGS]);
4345
4349
  const values = new Set(spec.values ?? []);
4346
4350
  const operands = [];
4351
+ const operandAt = [];
4352
+ const operand = (at) => {
4353
+ operands.push(seg.args[at]);
4354
+ operandAt.push(at);
4355
+ };
4347
4356
  let i = 0;
4348
4357
  while (i < seg.args.length) {
4349
4358
  const token = seg.args[i];
4350
4359
  if (token === "--" && !spec.expressionArgs) {
4351
- operands.push(...seg.args.slice(i + 1));
4360
+ for (let j = i + 1; j < seg.args.length; j++) operand(j);
4352
4361
  break;
4353
4362
  }
4354
4363
  if (!/^-./.test(token)) {
4355
- operands.push(token);
4364
+ operand(i);
4356
4365
  i++;
4357
4366
  continue;
4358
4367
  }
4359
4368
  const shape = matchFlag(spec, booleans, values, token);
4360
- if (shape === void 0) return { unknown: flagLabel(token), operands };
4369
+ if (shape === void 0) return { unknown: flagLabel(token), operands, operandAt };
4361
4370
  i += shape === "value" && takesNextToken(seg.args[i + 1]) ? 2 : 1;
4362
4371
  }
4363
- return { operands };
4372
+ return { operands, operandAt };
4364
4373
  }
4365
4374
  function subcommandOf(seg) {
4366
4375
  const scan = scanFlags(seg);
@@ -4513,6 +4522,8 @@ function sedRefusalFor(binary, scripts) {
4513
4522
  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}`;
4514
4523
  case "write":
4515
4524
  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.`;
4525
+ case "read":
4526
+ return `The "${binary}" script reads a file it names itself (the "r"/"R" command), which path confinement cannot see. Read that file directly instead.`;
4516
4527
  default:
4517
4528
  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.`;
4518
4529
  }
@@ -4633,6 +4644,7 @@ function sedScriptHazard(script) {
4633
4644
  if (cmd === void 0) return "unreadable";
4634
4645
  if (cmd === "e") return "exec";
4635
4646
  if (cmd === "w" || cmd === "W") return "write";
4647
+ if (cmd === "r" || cmd === "R") return "read";
4636
4648
  if (cmd === "{" || cmd === "}") continue;
4637
4649
  if (cmd === "#") {
4638
4650
  while (i < n && script[i] !== "\n") i++;
@@ -4654,7 +4666,7 @@ function sedScriptHazard(script) {
4654
4666
  if (!commandEnds()) return "unreadable";
4655
4667
  continue;
4656
4668
  }
4657
- if ("aicrR".includes(cmd)) {
4669
+ if ("aic".includes(cmd)) {
4658
4670
  while (i < n && script[i] !== "\n") i += script[i] === "\\" ? 2 : 1;
4659
4671
  continue;
4660
4672
  }
@@ -4754,6 +4766,12 @@ function awkRefusalFor(binary, programs) {
4754
4766
  return `The "${binary}" program pipes to or from a command ("|"), which runs it through a shell this classifier cannot inspect. ${INSPECT_OR_TASK}`;
4755
4767
  case "write":
4756
4768
  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.`;
4769
+ case "read":
4770
+ 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.`;
4771
+ case "environment":
4772
+ return `The "${binary}" program reads the environment ("ENVIRON"), which holds credentials. Run printenv for the one variable you need, which asks first.`;
4773
+ case "symbol-table":
4774
+ return `The "${binary}" program uses "SYMTAB", which gawk reads as every global by name, "ENVIRON" and "ARGV" included. Name the variable you need directly.`;
4757
4775
  case "extension":
4758
4776
  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}`;
4759
4777
  default:
@@ -4795,6 +4813,7 @@ function awkProgramHazard(program) {
4795
4813
  let slash = "regex";
4796
4814
  let depth = 0;
4797
4815
  let printDepth;
4816
+ let getlineDepth;
4798
4817
  let continues = false;
4799
4818
  const readRegex = () => {
4800
4819
  i++;
@@ -4861,7 +4880,10 @@ function awkProgramHazard(program) {
4861
4880
  continue;
4862
4881
  }
4863
4882
  if (c === "\n" || c === ";") {
4864
- if (c === ";" || !continues) printDepth = void 0;
4883
+ if (c === ";" || !continues) {
4884
+ printDepth = void 0;
4885
+ getlineDepth = void 0;
4886
+ }
4865
4887
  slash = "regex";
4866
4888
  i++;
4867
4889
  continue;
@@ -4912,6 +4934,12 @@ function awkProgramHazard(program) {
4912
4934
  slash = "unclear";
4913
4935
  continue;
4914
4936
  }
4937
+ if (c === "<") {
4938
+ if (getlineDepth !== void 0 && depth >= getlineDepth) return "read";
4939
+ i++;
4940
+ slash = "unclear";
4941
+ continue;
4942
+ }
4915
4943
  if (c === "(" || c === "[") {
4916
4944
  depth++;
4917
4945
  i++;
@@ -4920,12 +4948,16 @@ function awkProgramHazard(program) {
4920
4948
  }
4921
4949
  if (c === ")" || c === "]") {
4922
4950
  depth--;
4951
+ if (getlineDepth !== void 0 && depth < getlineDepth) getlineDepth = void 0;
4923
4952
  i++;
4924
4953
  slash = "divide";
4925
4954
  continue;
4926
4955
  }
4927
4956
  if (c === "{" || c === "}") {
4928
- if (c === "}") printDepth = void 0;
4957
+ if (c === "}") {
4958
+ printDepth = void 0;
4959
+ getlineDepth = void 0;
4960
+ }
4929
4961
  i++;
4930
4962
  slash = "regex";
4931
4963
  continue;
@@ -4946,6 +4978,10 @@ function awkProgramHazard(program) {
4946
4978
  while (i < n && /[A-Za-z0-9_.]/.test(program[i])) i++;
4947
4979
  const word = program.slice(start, i);
4948
4980
  if (word === "system") return "exec";
4981
+ if (word === "ENVIRON") return "environment";
4982
+ if (word === "ARGV") return "read";
4983
+ if (word === "SYMTAB") return "symbol-table";
4984
+ if (word === "getline") getlineDepth = depth;
4949
4985
  if (word === "print" || word === "printf") printDepth = depth;
4950
4986
  slash = AWK_KEYWORDS.includes(word) ? "unclear" : "divide";
4951
4987
  continue;
@@ -5832,6 +5868,36 @@ var GIT_READONLY_SUBCOMMANDS = [
5832
5868
  "whatchanged",
5833
5869
  "cat-file"
5834
5870
  ];
5871
+ var onlyAction = (...reads) => (after) => after[0] === void 0 || reads.includes(after[0]);
5872
+ var GIT_PROMPTABLE = {
5873
+ "ls-remote": () => true,
5874
+ "for-each-ref": () => true,
5875
+ "show-ref": () => true,
5876
+ "name-rev": () => true,
5877
+ "merge-base": () => true,
5878
+ "count-objects": () => true,
5879
+ "cherry": () => true,
5880
+ "range-diff": () => true,
5881
+ "diff-tree": () => true,
5882
+ "diff-files": () => true,
5883
+ "diff-index": () => true,
5884
+ "show-branch": () => true,
5885
+ "annotate": () => true,
5886
+ "check-ignore": () => true,
5887
+ "check-attr": () => true,
5888
+ "check-ref-format": () => true,
5889
+ "check-mailmap": () => true,
5890
+ "var": () => true,
5891
+ "version": () => true,
5892
+ "worktree": onlyAction("list"),
5893
+ "notes": onlyAction("list", "show"),
5894
+ "bisect": onlyAction("log"),
5895
+ "sparse-checkout": onlyAction("list"),
5896
+ "reflog": onlyAction("show", "exists"),
5897
+ "symbolic-ref": (after) => after.filter((a) => !a.startsWith("-")).length <= 1,
5898
+ // `-w` writes the object; git's shared flag set keeps it for `git diff -w`.
5899
+ "hash-object": (after) => !after.some((a) => /^-[^-]*w/.test(a))
5900
+ };
5835
5901
  var REFUSED_COMMANDS = [
5836
5902
  "rm",
5837
5903
  "rmdir",
@@ -5923,7 +5989,28 @@ var REFUSED_SUBCOMMANDS = {
5923
5989
  "clone",
5924
5990
  "fetch",
5925
5991
  "pull",
5926
- "submodule"
5992
+ "submodule",
5993
+ "add",
5994
+ "rm",
5995
+ "mv",
5996
+ "format-patch",
5997
+ "replace",
5998
+ "mergetool",
5999
+ "repack",
6000
+ "update-index",
6001
+ "read-tree",
6002
+ "checkout-index",
6003
+ "merge-file",
6004
+ "bundle",
6005
+ "commit-tree",
6006
+ "write-tree",
6007
+ "mktree",
6008
+ "mktag",
6009
+ "unpack-objects",
6010
+ "fast-import",
6011
+ "pack-refs",
6012
+ "prune-packed",
6013
+ "maintenance"
5927
6014
  ],
5928
6015
  npm: ["publish", "unpublish", "deprecate", "owner", "access", "token", "login", "logout"],
5929
6016
  yarn: ["publish", "npm"],
@@ -6133,7 +6220,7 @@ function pathRefs(command, opts = {}) {
6133
6220
  return lexAll(command.trim(), dialect, opts.cdpathSet).segments.flatMap((seg) => {
6134
6221
  const patterns = patternArgs(seg);
6135
6222
  const cwd = seg.cwd ?? [];
6136
- return [...seg.args.filter((_, i) => !patterns.has(i)), ...seg.inputs].flatMap(pathIn).map((path47) => ({ path: path47, cwd }));
6223
+ return [...seg.args.filter((_, i) => !patterns.has(i)), ...seg.inputs].flatMap(pathIn).map((path48) => ({ path: path48, cwd }));
6137
6224
  });
6138
6225
  }
6139
6226
  function excludedBy(command, allow, opts = {}) {
@@ -6240,7 +6327,9 @@ function patternArgs(seg) {
6240
6327
  var SCOPE_LEAD_ARGS = 2;
6241
6328
  function scopeFor(seg) {
6242
6329
  const name = seg.binaryPath ?? seg.binary;
6243
- if (!MULTIPLEXERS.includes(seg.binary.toLowerCase())) return name;
6330
+ const program = programName(seg.binary);
6331
+ if (!MULTIPLEXERS.includes(program)) return name;
6332
+ if (program === "git") return gitScope(seg, name);
6244
6333
  const lead = [];
6245
6334
  for (const arg of seg.args) {
6246
6335
  if (arg.startsWith("-")) break;
@@ -6249,6 +6338,14 @@ function scopeFor(seg) {
6249
6338
  }
6250
6339
  return [name, ...lead].join(" ");
6251
6340
  }
6341
+ function gitScope(seg, name) {
6342
+ const scan = scanFlags({ ...seg, binary: "git" });
6343
+ if (!scan || scan.unknown || scan.operands[0] === "ls-remote") return [name, ...seg.args].map(scopeWord).join(" ");
6344
+ return [name, ...scan.operands.slice(0, SCOPE_LEAD_ARGS)].join(" ");
6345
+ }
6346
+ function scopeWord(word) {
6347
+ return word === "" || /[\s"'\\]/.test(word) ? JSON.stringify(word) : word;
6348
+ }
6252
6349
  function climbsByGlob(seg) {
6253
6350
  if (!seg.globbed) return false;
6254
6351
  const patterns = patternArgs(seg);
@@ -6270,6 +6367,27 @@ function swallowsRefName(arg) {
6270
6367
  const values = FLAG_POLICY.git.values ?? [];
6271
6368
  return [...arg.slice(1)].some((c) => c !== "n" && values.includes(`-${c}`));
6272
6369
  }
6370
+ function gitArgsAfterSubcommand(seg) {
6371
+ const at = scanFlags(seg)?.operandAt[0];
6372
+ return at === void 0 ? [] : seg.args.slice(at + 1);
6373
+ }
6374
+ var REMOTE_NAME = /^[A-Za-z0-9_][A-Za-z0-9_.-]*$/;
6375
+ function namesConfiguredRemote(seg, after) {
6376
+ const destination2 = scanFlags({ ...seg, args: after })?.operands[0];
6377
+ return destination2 === void 0 || REMOTE_NAME.test(destination2);
6378
+ }
6379
+ function programName(binary) {
6380
+ return binary.toLowerCase().replace(/\.exe$/, "");
6381
+ }
6382
+ function isRemoteListing(seg) {
6383
+ const git2 = { ...seg, binary: programName(seg.binary) };
6384
+ return git2.binary === "git" && subcommandOf(git2) === "ls-remote";
6385
+ }
6386
+ function remoteListingTexts(seg) {
6387
+ if (!isRemoteListing(seg)) return [];
6388
+ const git2 = { ...seg, binary: "git" };
6389
+ return scanFlags({ ...git2, args: gitArgsAfterSubcommand(git2) })?.operands ?? [];
6390
+ }
6273
6391
  function refusalFor(seg, trustedResolves) {
6274
6392
  const web = webCommand(seg, trustedResolves);
6275
6393
  if (web?.reason) return web.reason;
@@ -6307,8 +6425,24 @@ function refusalFor(seg, trustedResolves) {
6307
6425
  }
6308
6426
  if (seg.binary === "git") {
6309
6427
  const sub = subcommandOf(seg);
6428
+ const after = gitArgsAfterSubcommand(seg);
6429
+ if (sub !== void 0 && !GIT_READONLY_SUBCOMMANDS.includes(sub)) {
6430
+ const reads = GIT_PROMPTABLE[sub];
6431
+ if (!reads) {
6432
+ 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}`;
6433
+ }
6434
+ if (!reads(after)) {
6435
+ return `"git ${[sub, ...after].join(" ")}" is not a read-only form of "git ${sub}". You are a read-only planner: ${EFFECT_ROUTE}`;
6436
+ }
6437
+ }
6438
+ if (sub === "ls-remote" && !namesConfiguredRemote(seg, after)) {
6439
+ 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.`;
6440
+ }
6441
+ if (sub === "ls-remote" && (seg.expandable || seg.globbed || after.some((a) => a.includes("~")))) {
6442
+ 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.`;
6443
+ }
6310
6444
  if (sub === "branch" || sub === "tag") {
6311
- const flag2 = seg.args.find((a) => ["-D", "-d", "-m", "-M", "--delete", "--move"].includes(a)) ?? seg.args.slice(seg.args.indexOf(sub) + 1).find(swallowsRefName);
6445
+ const flag2 = seg.args.find((a) => ["-D", "-d", "-m", "-M", "--delete", "--move"].includes(a)) ?? after.find(swallowsRefName);
6312
6446
  if (flag2) {
6313
6447
  return `"git ${sub} ${flag2}" mutates refs. You are a read-only planner: ${EFFECT_ROUTE}`;
6314
6448
  }
@@ -6447,10 +6581,10 @@ function classifyCommand(command, opts = {}) {
6447
6581
  return { tier: "refuse", scope: "", reason: `curl and wget are refused in Full and allowlist mode. ${web.effect ? EFFECT_REFUSAL_HINT : READ_REFUSAL_HINT}` };
6448
6582
  }
6449
6583
  const allow = opts.allow;
6450
- if (web && allow && web.urls.some((url) => scopeExcluded(allow, `${seg.binary.toLowerCase().replace(/\.exe$/, "")} ${url.origin}/*`))) {
6584
+ if (web && allow && web.urls.some((url) => scopeExcluded(allow, `${programName(seg.binary)} ${url.origin}/*`))) {
6451
6585
  return { tier: "refuse", scope: "", reason: `The web origin is excluded by the planner allowlist. ${READ_REFUSAL_HINT}` };
6452
6586
  }
6453
- const reason = refusalFor({ ...seg, binary: seg.binary.toLowerCase() }, opts.trustedWebResolves);
6587
+ const reason = refusalFor({ ...seg, binary: programName(seg.binary) }, opts.trustedWebResolves);
6454
6588
  if (reason) return { tier: "refuse", scope: "", reason: withWrapperNote(reason, wrappers) };
6455
6589
  if (runner && !XARGS_TARGETS.includes(seg.binary)) {
6456
6590
  return {
@@ -6460,23 +6594,24 @@ function classifyCommand(command, opts = {}) {
6460
6594
  };
6461
6595
  }
6462
6596
  }
6463
- if (unwrapped.some(({ seg }) => seg.binary.toLowerCase().replace(/\.exe$/, "") === "wget") && wgetVersionCommands(command, opts.dialect).includes(void 0)) {
6597
+ if (unwrapped.some(({ seg }) => programName(seg.binary) === "wget") && wgetVersionCommands(command, opts.dialect).includes(void 0)) {
6464
6598
  return { tier: "refuse", scope: "", reason: `The wget executable cannot be checked safely in this working directory. ${READ_REFUSAL_HINT}` };
6465
6599
  }
6466
6600
  const nonAuto = unwrapped.filter((u) => u.runner !== void 0 || u.wrappers.some((w) => w !== w.toLowerCase()) || !isAuto(u.seg, opts));
6467
6601
  if (nonAuto.length === 0) return { tier: "auto", scope: "" };
6602
+ const changesDirectory = unwrapped.some(({ seg }) => seg.binary.toLowerCase() === "cd");
6468
6603
  const lineScoped = nonAuto.some((u) => {
6469
6604
  const name = u.seg.binary.toLowerCase();
6470
- return name === "cd" || name !== "command" && SHELL_STATE_COMMANDS.has(name) || climbsByGlob(u.seg);
6605
+ return name === "cd" || name !== "command" && SHELL_STATE_COMMANDS.has(name) || climbsByGlob(u.seg) || isRemoteListing(u.seg) && (changesDirectory || u.wrappers.length > 0 || nonAuto.length > 1);
6471
6606
  });
6472
6607
  const webUrls = nonAuto.flatMap((u) => webCommand(u.seg, opts.trustedWebResolves)?.urls ?? []);
6473
- const webOutboundTexts = nonAuto.flatMap((u) => webCommand(u.seg, opts.trustedWebResolves)?.outboundTexts ?? []);
6608
+ const outboundTexts = nonAuto.flatMap((u) => webCommand(u.seg, opts.trustedWebResolves)?.outboundTexts ?? remoteListingTexts(u.seg));
6474
6609
  if (lineScoped && webUrls.length === 0) {
6475
- return { tier: "ask", scope: trimmed2 };
6610
+ return { tier: "ask", scope: trimmed2, ...outboundTexts.length ? { outboundTexts } : {} };
6476
6611
  }
6477
6612
  const scopes = [...new Set(nonAuto.flatMap((u) => {
6478
6613
  const web = webCommand(u.seg, opts.trustedWebResolves);
6479
- return web ? web.urls.map((url) => `${u.seg.binary.toLowerCase().replace(/\.exe$/, "")} ${url.origin}/*`) : [lineScoped ? trimmed2 : u.runner ? `${u.runner} ${scopeFor(u.seg)}` : scopeFor(u.seg)];
6614
+ return web ? web.urls.map((url) => `${programName(u.seg.binary)} ${url.origin}/*`) : [lineScoped ? trimmed2 : u.runner ? `${u.runner} ${scopeFor(u.seg)}` : scopeFor(u.seg)];
6480
6615
  }))].sort();
6481
6616
  const scope = scopes.join(" + ");
6482
6617
  const perInvocation = nonAuto.some((u) => webCommand(u.seg, opts.trustedWebResolves)?.perInvocation);
@@ -6484,7 +6619,8 @@ function classifyCommand(command, opts = {}) {
6484
6619
  return {
6485
6620
  tier: "ask",
6486
6621
  scope,
6487
- ...webUrls.length ? { webUrls, webOutboundTexts } : {},
6622
+ ...webUrls.length ? { webUrls } : {},
6623
+ ...outboundTexts.length ? { outboundTexts } : {},
6488
6624
  ...scopes.length > 1 && webUrls.length ? { scopes } : {},
6489
6625
  ...perInvocation ? { perInvocation } : {},
6490
6626
  ...allowedBy ? { allowedBy } : {}
@@ -6628,9 +6764,9 @@ function summarizeToolCall(tool2, argsJson, toolLabel) {
6628
6764
  const scope = args.include ? ` in ${args.include}` : "";
6629
6765
  return `grep ${ellipsize(String(args.pattern), 40)}${scope}${mode}`;
6630
6766
  }
6631
- const path47 = args.filePath || args.file_path || args.path;
6632
- if (path47) {
6633
- const basename6 = String(path47).split("/").pop() || String(path47);
6767
+ const path48 = args.filePath || args.file_path || args.path;
6768
+ if (path48) {
6769
+ const basename6 = String(path48).split("/").pop() || String(path48);
6634
6770
  return `${display} ${basename6}`;
6635
6771
  }
6636
6772
  if (args.pattern) {
@@ -8116,7 +8252,7 @@ ${TASK_CONTINUE}`);
8116
8252
  command = disableWebCommandStartup(command, this.researchShell.dialect);
8117
8253
  const { approval, root } = site;
8118
8254
  const allow = approval.allowlist?.();
8119
- const { tier, scope, reason, allowedBy, perInvocation, scopes: commandScopes, webUrls, webOutboundTexts } = classifyCommand(command, {
8255
+ const { tier, scope, reason, allowedBy, perInvocation, scopes: commandScopes, webUrls, outboundTexts } = classifyCommand(command, {
8120
8256
  ...this.policyOptions(),
8121
8257
  ...approval.commandPolicy?.(),
8122
8258
  ...allow ? { allow } : {}
@@ -8141,8 +8277,9 @@ ${TASK_CONTINUE}`);
8141
8277
  if (site.task?.sealed) return this.runSealed(site, command, request, "after", signal);
8142
8278
  if (tier === "refuse") return this.sealedInstead(site, command, request, refusal(`Command refused: ${reason}${site.task ? `
8143
8279
  ${TASK_UNSEALED} ${TASK_CONTINUE}` : ""}`), signal);
8144
- if (webUrls?.length && carriesSecret([...webOutboundTexts ?? [], ...webUrls.map((url) => url.href)], [...this.outboundSecrets(), ...sensitiveEnvValues(process.env)])) {
8145
- return refusal("Command refused: a web command contains what looks like a secret. Fetch the page with your web fetch tool, without credentials.");
8280
+ const outbound = [...outboundTexts ?? [], ...(webUrls ?? []).map((url) => url.href)];
8281
+ if (outbound.length && carriesSecret(outbound, [...this.outboundSecrets(), ...sensitiveEnvValues(process.env)])) {
8282
+ 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.");
8146
8283
  }
8147
8284
  const vettedAddresses = /* @__PURE__ */ new Map();
8148
8285
  for (const url of webUrls ?? []) {
@@ -8245,7 +8382,7 @@ ${outcome.output}${outcome.success ? "" : `
8245
8382
  }
8246
8383
  let shell;
8247
8384
  try {
8248
- const sealed2 = site.task?.gitBefore ? { ...site.sealed, gitEntries: site.task.gitBefore.entries.map(({ path: path47, directory }) => ({ path: path47, directory })) } : site.sealed;
8385
+ const sealed2 = site.task?.gitBefore ? { ...site.sealed, gitEntries: site.task.gitBefore.entries.map(({ path: path48, directory }) => ({ path: path48, directory })) } : site.sealed;
8249
8386
  shell = await site.sandbox.launchSealed(this.researchShell, sealed2);
8250
8387
  } catch (err) {
8251
8388
  return { refused: refusal(`Command not run: its sealed sandbox could not be prepared (${err instanceof Error ? err.message : String(err)}).`) };
@@ -9794,8 +9931,8 @@ function firstLineOf(command) {
9794
9931
  function text(value) {
9795
9932
  return typeof value === "string" ? value : "";
9796
9933
  }
9797
- function searchArg(pattern, path47, include = "") {
9798
- return `${pattern}${path47 ? ` in ${path47}` : ""}${include ? ` (${include})` : ""}`;
9934
+ function searchArg(pattern, path48, include = "") {
9935
+ return `${pattern}${path48 ? ` in ${path48}` : ""}${include ? ` (${include})` : ""}`;
9799
9936
  }
9800
9937
  function hintArg(args) {
9801
9938
  for (const field of HINT_FIELDS) {
@@ -9900,8 +10037,8 @@ function userMessage(id2, text6, skills, { turnId, forced } = {}) {
9900
10037
  ...forced ? { forced: true } : {}
9901
10038
  };
9902
10039
  }
9903
- function skillLoadNotice({ invokedBy, name, source, path: path47, attaches }) {
9904
- return { invokedBy, name, source, path: path47, ...attaches ? { attaches } : {} };
10040
+ function skillLoadNotice({ invokedBy, name, source, path: path48, attaches }) {
10041
+ return { invokedBy, name, source, path: path48, ...attaches ? { attaches } : {} };
9905
10042
  }
9906
10043
  var LOAD_SKILL_TOOL = /(?:^|_)load_skill$/;
9907
10044
  function surfaceStep(step) {
@@ -9918,8 +10055,8 @@ function loadedSkillName(args) {
9918
10055
  return void 0;
9919
10056
  }
9920
10057
  }
9921
- function skillLoadBlock(id2, { invokedBy, name, source, path: path47, attaches }, turnId) {
9922
- return { type: "skill_load", id: id2, invokedBy, name, source, path: path47, ...attaches ? { attaches } : {}, ...turnId ? { turnId } : {} };
10058
+ function skillLoadBlock(id2, { invokedBy, name, source, path: path48, attaches }, turnId) {
10059
+ return { type: "skill_load", id: id2, invokedBy, name, source, path: path48, ...attaches ? { attaches } : {}, ...turnId ? { turnId } : {} };
9923
10060
  }
9924
10061
  function subagentBlock(id2, { toolCallId, model, usage, turnId, ...fields }) {
9925
10062
  return {
@@ -10693,6 +10830,7 @@ function enabledRunners(cfg) {
10693
10830
  }
10694
10831
 
10695
10832
  // src/plugins/builtin/claudeModels.ts
10833
+ var path13 = __toESM(require("path"));
10696
10834
  var ANTHROPIC_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
10697
10835
  var ANTHROPIC_VARIANT_LABELS = {
10698
10836
  adaptive: "Adaptive",
@@ -10786,6 +10924,53 @@ function parseClaudeHelp(stdout) {
10786
10924
  }
10787
10925
  return models;
10788
10926
  }
10927
+ function claudeConfigDir(env) {
10928
+ return env.CLAUDE_CONFIG_DIR?.trim() || "~/.claude";
10929
+ }
10930
+ function claudeManagedSettingsDir(platform = process.platform) {
10931
+ if (platform === "darwin") return "/Library/Application Support/ClaudeCode";
10932
+ if (platform === "win32") return "C:\\Program Files\\ClaudeCode";
10933
+ return "/etc/claude-code";
10934
+ }
10935
+ function readClaudeSettingsModels(files, platform = process.platform) {
10936
+ const managed = claudeManagedSettingsDir(platform);
10937
+ const join44 = platform === "win32" ? path13.win32.join : path13.posix.join;
10938
+ const dropIns = files.list(join44(managed, "managed-settings.d")).filter((name) => name.endsWith(".json") && !name.startsWith(".")).sort().map((name) => join44(managed, "managed-settings.d", name));
10939
+ const ranked = [...dropIns.reverse(), join44(managed, "managed-settings.json"), join44(claudeConfigDir(files.env), "settings.json")];
10940
+ for (const file of ranked) {
10941
+ const content = files.read(file);
10942
+ const lineup = content === null ? null : parseClaudeSettingsModels(content);
10943
+ if (lineup) return lineup;
10944
+ }
10945
+ return [];
10946
+ }
10947
+ function parseClaudeSettingsModels(content) {
10948
+ let parsed;
10949
+ try {
10950
+ parsed = JSON.parse(content);
10951
+ } catch {
10952
+ return null;
10953
+ }
10954
+ const picker = parsed && typeof parsed === "object" ? parsed.modelPicker : void 0;
10955
+ const options = picker && typeof picker === "object" ? picker.options : void 0;
10956
+ if (!Array.isArray(options)) return null;
10957
+ const seen = /* @__PURE__ */ new Set();
10958
+ const models = [];
10959
+ for (const row of options) {
10960
+ if (!row || typeof row !== "object") continue;
10961
+ const { model: raw, label } = row;
10962
+ const model = typeof raw === "string" ? raw.trim() : "";
10963
+ if (!model || seen.has(model)) continue;
10964
+ seen.add(model);
10965
+ models.push({
10966
+ modelId: model,
10967
+ modelLabel: typeof label === "string" && label.trim() ? label.trim() : model,
10968
+ runnerProvider: model.includes("/") ? model.split("/")[0] : void 0,
10969
+ variants: []
10970
+ });
10971
+ }
10972
+ return models;
10973
+ }
10789
10974
 
10790
10975
  // src/plugins/builtin/claude-code.manifest.ts
10791
10976
  var TURN_NOT_LOGGED_IN = /not logged in\s*·\s*please run \/login/i;
@@ -10824,18 +11009,24 @@ var CLAUDE_CODE_MANIFEST = {
10824
11009
  // 3. canonicalAliases — stable --model contracts (opus/sonnet/haiku/fable)
10825
11010
  // merged into any successful result to fill gaps, and used as the full
10826
11011
  // last-resort list when both API and CLI are unavailable.
11012
+ //
11013
+ // Whichever source answers, the models the user added to Claude's own
11014
+ // picker (`modelPicker.options` in its managed or user settings, usually
11015
+ // gateway IDs) are appended (settingsModels): the CLI accepts them as
11016
+ // `--model` values though no catalog above lists them.
10827
11017
  apiDiscovery: {
10828
11018
  url: "https://api.anthropic.com/v1/models?limit=100",
10829
11019
  headers: { "anthropic-version": "2023-06-01" },
10830
11020
  auth: [
10831
11021
  { type: "env", varName: "ANTHROPIC_API_KEY", header: "X-Api-Key" },
10832
- { type: "file", path: "~/.claude/.credentials.json", jsonPath: "claudeAiOauth.accessToken", header: "Authorization", prefix: "Bearer " }
11022
+ { type: "file", path: (env) => `${claudeConfigDir(env)}/.credentials.json`, jsonPath: "claudeAiOauth.accessToken", header: "Authorization", prefix: "Bearer " }
10833
11023
  ],
10834
11024
  parser: parseAnthropicModels
10835
11025
  },
10836
11026
  discoveryCommands: [
10837
11027
  { command: "claude", args: ["--help"], parser: parseClaudeHelp }
10838
11028
  ],
11029
+ settingsModels: (files) => readClaudeSettingsModels(files),
10839
11030
  canonicalAliases: [
10840
11031
  { modelId: "opus", modelLabel: "Opus" },
10841
11032
  { modelId: "sonnet", modelLabel: "Sonnet" },
@@ -10893,7 +11084,7 @@ var import_path5 = require("path");
10893
11084
 
10894
11085
  // src/services/harness/repoConfig.ts
10895
11086
  var import_fs6 = require("fs");
10896
- var path13 = __toESM(require("path"));
11087
+ var path14 = __toESM(require("path"));
10897
11088
 
10898
11089
  // src/services/mcp/OrdewellMcpServer.ts
10899
11090
  var import_http = require("http");
@@ -11857,12 +12048,12 @@ function checkpointReply(answer2) {
11857
12048
  return { text: `The checkpoint was withdrawn: ${answer2.why}`, isError: true };
11858
12049
  }
11859
12050
  }
11860
- function fieldPath(path47) {
11861
- return path47.reduce((text6, part) => typeof part === "number" ? `${text6}[${part}]` : text6 ? `${text6}.${String(part)}` : String(part), "");
12051
+ function fieldPath(path48) {
12052
+ return path48.reduce((text6, part) => typeof part === "number" ? `${text6}[${part}]` : text6 ? `${text6}.${String(part)}` : String(part), "");
11862
12053
  }
11863
- function valueAt(value, path47) {
12054
+ function valueAt(value, path48) {
11864
12055
  let current = value;
11865
- for (const part of path47) {
12056
+ for (const part of path48) {
11866
12057
  if (typeof part === "number") {
11867
12058
  if (!Array.isArray(current)) return void 0;
11868
12059
  current = current[part];
@@ -11884,7 +12075,7 @@ function boundedRefusal(issue, args) {
11884
12075
  var LEAKED_PARAMETER = /<\/parameter>|<parameter\s+name\s*=\s*"([^"]*)">/g;
11885
12076
  function leakedMarkupIn(value) {
11886
12077
  const found = [];
11887
- const walk = (node, path47) => {
12078
+ const walk = (node, path48) => {
11888
12079
  if (typeof node === "string") {
11889
12080
  const names = [];
11890
12081
  let closing = false;
@@ -11892,25 +12083,25 @@ function leakedMarkupIn(value) {
11892
12083
  if (match[1] !== void 0) names.push(match[1]);
11893
12084
  else closing = true;
11894
12085
  }
11895
- if (names.length > 0 || closing) found.push({ path: path47, names: [...new Set(names)], closing });
12086
+ if (names.length > 0 || closing) found.push({ path: path48, names: [...new Set(names)], closing });
11896
12087
  return;
11897
12088
  }
11898
12089
  if (Array.isArray(node)) {
11899
- node.forEach((item, i) => walk(item, `${path47}[${i}]`));
12090
+ node.forEach((item, i) => walk(item, `${path48}[${i}]`));
11900
12091
  return;
11901
12092
  }
11902
12093
  if (node !== null && typeof node === "object") {
11903
- for (const [key, item] of Object.entries(node)) walk(item, path47 ? `${path47}.${key}` : key);
12094
+ for (const [key, item] of Object.entries(node)) walk(item, path48 ? `${path48}.${key}` : key);
11904
12095
  }
11905
12096
  };
11906
12097
  walk(value, "");
11907
12098
  return found;
11908
12099
  }
11909
12100
  function markupRefusal(leaks) {
11910
- return leaks.map(({ path: path47, names, closing }) => {
12101
+ return leaks.map(({ path: path48, names, closing }) => {
11911
12102
  const tags = [...names.map((n) => `<parameter name="${n}">`), ...closing ? ["</parameter>"] : []];
11912
- const guide = names.length > 0 ? ` The parameters ${names.map((n) => JSON.stringify(n)).join(", ")} are inside it; pass them as separate arguments, not inside ${path47}.` : " Pass each argument separately, not inside one string.";
11913
- return `${path47} contains leaked tool-call markup: ${tags.join(", ")}.${guide}`;
12103
+ const guide = names.length > 0 ? ` The parameters ${names.map((n) => JSON.stringify(n)).join(", ")} are inside it; pass them as separate arguments, not inside ${path48}.` : " Pass each argument separately, not inside one string.";
12104
+ return `${path48} contains leaked tool-call markup: ${tags.join(", ")}.${guide}`;
11914
12105
  }).join("\n");
11915
12106
  }
11916
12107
  function invalidArguments(name, error, args) {
@@ -12375,9 +12566,9 @@ function mcpClientConfig(credential) {
12375
12566
  }
12376
12567
  function ownerOnlyConfigFile(fileName, contents) {
12377
12568
  const dir = (0, import_fs5.mkdtempSync)((0, import_path4.join)((0, import_os.tmpdir)(), "ordewell-mcp-"));
12378
- const path47 = (0, import_path4.join)(dir, fileName);
12379
- (0, import_fs5.writeFileSync)(path47, contents, { mode: 384 });
12380
- return { path: path47, remove: () => (0, import_fs5.rmSync)(dir, { recursive: true, force: true }) };
12569
+ const path48 = (0, import_path4.join)(dir, fileName);
12570
+ (0, import_fs5.writeFileSync)(path48, contents, { mode: 384 });
12571
+ return { path: path48, remove: () => (0, import_fs5.rmSync)(dir, { recursive: true, force: true }) };
12381
12572
  }
12382
12573
 
12383
12574
  // src/services/harness/repoConfig.ts
@@ -12400,19 +12591,19 @@ function onDisk(target) {
12400
12591
  try {
12401
12592
  return import_fs6.realpathSync.native(target);
12402
12593
  } catch {
12403
- return path13.resolve(target);
12594
+ return path14.resolve(target);
12404
12595
  }
12405
12596
  }
12406
12597
  function insideOnDisk(root, target) {
12407
- const rel = path13.relative(onDisk(root), onDisk(target));
12408
- return rel === "" || !rel.startsWith("..") && !path13.isAbsolute(rel);
12598
+ const rel = path14.relative(onDisk(root), onDisk(target));
12599
+ return rel === "" || !rel.startsWith("..") && !path14.isAbsolute(rel);
12409
12600
  }
12410
12601
  function repoLevels(workspace) {
12411
12602
  const levels = [];
12412
- for (let dir = path13.resolve(workspace); ; dir = path13.dirname(dir)) {
12603
+ for (let dir = path14.resolve(workspace); ; dir = path14.dirname(dir)) {
12413
12604
  levels.unshift(dir);
12414
- if ((0, import_fs6.existsSync)(path13.join(dir, ".git"))) return levels;
12415
- if (path13.dirname(dir) === dir) return [path13.resolve(workspace)];
12605
+ if ((0, import_fs6.existsSync)(path14.join(dir, ".git"))) return levels;
12606
+ if (path14.dirname(dir) === dir) return [path14.resolve(workspace)];
12416
12607
  }
12417
12608
  }
12418
12609
  function repoRoot(workspace) {
@@ -12506,33 +12697,33 @@ var TomlReader = class {
12506
12697
  if (this.i >= this.src.length) return root;
12507
12698
  if (this.src.startsWith("[[", this.i)) {
12508
12699
  this.i += 2;
12509
- const path47 = this.key();
12700
+ const path48 = this.key();
12510
12701
  this.expect("]]");
12511
- const parent = this.tableAt(root, path47.slice(0, -1));
12512
- const last = path47[path47.length - 1];
12702
+ const parent = this.tableAt(root, path48.slice(0, -1));
12703
+ const last = path48[path48.length - 1];
12513
12704
  const list = Object.hasOwn(parent, last) ? parent[last] : parent[last] = [];
12514
- if (!Array.isArray(list)) throw this.fail(`${path47.join(".")} is not an array of tables`);
12705
+ if (!Array.isArray(list)) throw this.fail(`${path48.join(".")} is not an array of tables`);
12515
12706
  table = newTable();
12516
12707
  list.push(table);
12517
12708
  } else if (this.src[this.i] === "[") {
12518
12709
  this.i += 1;
12519
- const path47 = this.key();
12710
+ const path48 = this.key();
12520
12711
  this.expect("]");
12521
- table = this.tableAt(root, path47);
12712
+ table = this.tableAt(root, path48);
12522
12713
  } else {
12523
- const path47 = this.key();
12714
+ const path48 = this.key();
12524
12715
  this.skipBlank(false);
12525
12716
  this.expect("=");
12526
12717
  this.skipBlank(false);
12527
- const owner = this.tableAt(table, path47.slice(0, -1));
12528
- owner[path47[path47.length - 1]] = this.value();
12718
+ const owner = this.tableAt(table, path48.slice(0, -1));
12719
+ owner[path48[path48.length - 1]] = this.value();
12529
12720
  }
12530
12721
  this.endOfLine();
12531
12722
  }
12532
12723
  }
12533
- tableAt(from, path47) {
12724
+ tableAt(from, path48) {
12534
12725
  let table = from;
12535
- for (const part of path47) {
12726
+ for (const part of path48) {
12536
12727
  const next = Object.hasOwn(table, part) ? table[part] : void 0;
12537
12728
  if (next === void 0) table = table[part] = newTable();
12538
12729
  else if (isRecord2(next)) table = next;
@@ -12601,10 +12792,10 @@ var TomlReader = class {
12601
12792
  this.i++;
12602
12793
  return table;
12603
12794
  }
12604
- const path47 = this.key();
12795
+ const path48 = this.key();
12605
12796
  this.expect("=");
12606
12797
  this.skipBlank(false);
12607
- this.tableAt(table, path47.slice(0, -1))[path47[path47.length - 1]] = this.value();
12798
+ this.tableAt(table, path48.slice(0, -1))[path48[path48.length - 1]] = this.value();
12608
12799
  this.skipBlank(true);
12609
12800
  if (this.src[this.i] === ",") this.i++;
12610
12801
  else if (this.src[this.i] !== "}") throw this.fail("expected , or } in an inline table");
@@ -12749,7 +12940,8 @@ var CODEX_MANIFEST = {
12749
12940
  appServer: {
12750
12941
  command: "codex",
12751
12942
  args: ["app-server"],
12752
- cacheFile: "~/.codex/models_cache.json"
12943
+ // CODEX_HOME moves all of Codex's own files from `~/.codex`.
12944
+ cacheFile: (env) => `${env.CODEX_HOME?.trim() || "~/.codex"}/models_cache.json`
12753
12945
  },
12754
12946
  canonicalAliases: [
12755
12947
  { modelId: "gpt-5.6-sol", modelLabel: "GPT-5.6-Sol" },
@@ -12978,8 +13170,7 @@ var LineBuffer = class {
12978
13170
  // src/services/harness/ClaudeCodeAdapter.ts
12979
13171
  var import_crypto6 = require("crypto");
12980
13172
  var import_fs11 = require("fs");
12981
- var os9 = __toESM(require("os"));
12982
- var path21 = __toESM(require("path"));
13173
+ var path22 = __toESM(require("path"));
12983
13174
 
12984
13175
  // src/plugins/resolveArgs.ts
12985
13176
  function claudeThinkingArgs(effort) {
@@ -13017,14 +13208,29 @@ function resolveTaskRunnerFlags(manifest, ctx) {
13017
13208
  return flags;
13018
13209
  }
13019
13210
 
13211
+ // src/utils/runnerHome.ts
13212
+ var os5 = __toESM(require("os"));
13213
+ function runnerHomeDir(env, platform = process.platform) {
13214
+ const name = platform === "win32" ? "USERPROFILE" : "HOME";
13215
+ const key = platform === "win32" ? Object.keys(env).filter((k) => k.toUpperCase() === name).pop() : name;
13216
+ const home = key === void 0 ? void 0 : env[key];
13217
+ return home ?? os5.homedir();
13218
+ }
13219
+ function expandRunnerHome(filePath, env = process.env) {
13220
+ if (!filePath.startsWith("~")) return filePath;
13221
+ const home = runnerHomeDir(env);
13222
+ return home ? home + filePath.slice(1) : null;
13223
+ }
13224
+
13020
13225
  // src/services/harness/runnerProcess.ts
13226
+ var import_net = require("net");
13021
13227
  var import_string_decoder = require("string_decoder");
13022
13228
 
13023
13229
  // src/utils/shellPath.ts
13024
13230
  var import_child_process3 = require("child_process");
13025
13231
  var import_util = require("util");
13026
- var os5 = __toESM(require("os"));
13027
- var path14 = __toESM(require("path"));
13232
+ var os6 = __toESM(require("os"));
13233
+ var path15 = __toESM(require("path"));
13028
13234
  var execAsync = (0, import_util.promisify)(import_child_process3.exec);
13029
13235
  var LOGIN_SHELL_TIMEOUT_MS = 5e3;
13030
13236
  var PATH_MARKER = "__ORDEWELL_PATH__";
@@ -13033,21 +13239,21 @@ var defaultExec = async (command, options) => {
13033
13239
  return { stdout: String(stdout) };
13034
13240
  };
13035
13241
  function posixBinDirs() {
13036
- const home = os5.homedir();
13242
+ const home = os6.homedir();
13037
13243
  return [
13038
- path14.join(home, ".local", "bin"),
13039
- path14.join(home, ".opencode", "bin"),
13040
- path14.join(home, ".claude", "bin"),
13041
- path14.join(home, ".bun", "bin"),
13042
- path14.join(home, ".volta", "bin"),
13043
- path14.join(home, ".npm-global", "bin"),
13044
- path14.join(home, "bin"),
13244
+ path15.join(home, ".local", "bin"),
13245
+ path15.join(home, ".opencode", "bin"),
13246
+ path15.join(home, ".claude", "bin"),
13247
+ path15.join(home, ".bun", "bin"),
13248
+ path15.join(home, ".volta", "bin"),
13249
+ path15.join(home, ".npm-global", "bin"),
13250
+ path15.join(home, "bin"),
13045
13251
  "/usr/local/bin",
13046
13252
  "/opt/homebrew/bin"
13047
13253
  ];
13048
13254
  }
13049
13255
  function windowsBinDirs(env, home) {
13050
- const join44 = (...parts) => path14.win32.join(...parts);
13256
+ const join44 = (...parts) => path15.win32.join(...parts);
13051
13257
  const under = (base, ...parts) => base ? [join44(base, ...parts)] : [];
13052
13258
  const appData = env.APPDATA;
13053
13259
  const localAppData = env.LOCALAPPDATA;
@@ -13084,7 +13290,7 @@ function windowsBinDirs(env, home) {
13084
13290
  function wellKnownBinDirs(deps = {}) {
13085
13291
  const platform = deps.platform ?? process.platform;
13086
13292
  if (platform !== "win32") return posixBinDirs();
13087
- return [...new Set(windowsBinDirs(deps.env ?? process.env, deps.home ?? os5.homedir()))];
13293
+ return [...new Set(windowsBinDirs(deps.env ?? process.env, deps.home ?? os6.homedir()))];
13088
13294
  }
13089
13295
  async function loginShellPath(execImpl) {
13090
13296
  if (process.platform === "win32") return null;
@@ -13107,14 +13313,14 @@ function augmentedPath(execImpl = defaultExec) {
13107
13313
  const segments = [];
13108
13314
  const push = (p) => {
13109
13315
  if (!p) return;
13110
- for (const seg of p.split(path14.delimiter)) {
13316
+ for (const seg of p.split(path15.delimiter)) {
13111
13317
  if (seg && !segments.includes(seg)) segments.push(seg);
13112
13318
  }
13113
13319
  };
13114
13320
  push(process.env.PATH);
13115
13321
  push(await loginShellPath(execImpl));
13116
13322
  for (const dir of wellKnownBinDirs()) push(dir);
13117
- return segments.join(path14.delimiter);
13323
+ return segments.join(path15.delimiter);
13118
13324
  })();
13119
13325
  }
13120
13326
  return cachedPath;
@@ -13136,7 +13342,7 @@ function withPath(base, resolvedPath, overrides = {}) {
13136
13342
 
13137
13343
  // src/utils/launch.ts
13138
13344
  var fsSync4 = __toESM(require("fs"));
13139
- var path15 = __toESM(require("path"));
13345
+ var path16 = __toESM(require("path"));
13140
13346
  var DIRECT_EXTENSIONS = [".exe", ".com"];
13141
13347
  var BATCH_EXTENSIONS = [".cmd", ".bat"];
13142
13348
  var POWERSHELL_EXTENSIONS = [".ps1"];
@@ -13144,7 +13350,7 @@ var CMD_EXE_MAX_COMMAND_LINE = 8191;
13144
13350
  var WINDOWS_MAX_COMMAND_LINE = 32767;
13145
13351
  function defaultPowerShell() {
13146
13352
  const root = process.env.SystemRoot || process.env.windir;
13147
- return root ? path15.win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe") : "powershell.exe";
13353
+ return root ? path16.win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe") : "powershell.exe";
13148
13354
  }
13149
13355
  var POWERSHELL_FLAGS = ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File"];
13150
13356
  var CommandLineTooLongError = class extends Error {
@@ -13210,7 +13416,7 @@ Searched: ${searchedPath || "(empty PATH)"}`);
13210
13416
  };
13211
13417
  function isResolvableOnPosixPath(command, PATH, exists) {
13212
13418
  if (command.includes("/")) return exists(command);
13213
- return PATH.split(path15.delimiter).map((d) => d.trim()).filter(Boolean).some((dir) => exists(path15.join(dir, command)));
13419
+ return PATH.split(path16.delimiter).map((d) => d.trim()).filter(Boolean).some((dir) => exists(path16.join(dir, command)));
13214
13420
  }
13215
13421
  function isExecutableResolved(command, plan, PATH, deps = {}) {
13216
13422
  if ((deps.platform ?? process.platform) === "win32") return plan.file !== command;
@@ -13250,9 +13456,9 @@ function cmdArgs(line) {
13250
13456
  return ["/d", "/s", "/c", `"${line}"`];
13251
13457
  }
13252
13458
  function candidateExtensions(command, pathExt) {
13253
- const declared = pathExt.split(path15.win32.delimiter).map((e) => e.trim().toLowerCase()).filter(Boolean);
13459
+ const declared = pathExt.split(path16.win32.delimiter).map((e) => e.trim().toLowerCase()).filter(Boolean);
13254
13460
  const known = /* @__PURE__ */ new Set([...DIRECT_EXTENSIONS, ...BATCH_EXTENSIONS, ...POWERSHELL_EXTENSIONS, ...declared]);
13255
- const ext = path15.win32.extname(command).toLowerCase();
13461
+ const ext = path16.win32.extname(command).toLowerCase();
13256
13462
  if (ext && known.has(ext)) {
13257
13463
  const route = BATCH_EXTENSIONS.includes(ext) ? "cmd" : POWERSHELL_EXTENSIONS.includes(ext) ? "powershell" : "direct";
13258
13464
  return { direct: [], cmd: [], powershell: [], [route]: [""] };
@@ -13273,13 +13479,13 @@ async function findWindowsExecutables(command, deps) {
13273
13479
  const exists = deps.exists ?? defaultExists3;
13274
13480
  const pathExt = (deps.pathExt ?? (() => process.env.PATHEXT ?? ""))();
13275
13481
  const byRoute = candidateExtensions(command, pathExt);
13276
- 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);
13277
- const base = path15.win32.basename(command);
13482
+ const dirs = path16.win32.basename(command) !== command ? [path16.win32.dirname(path16.win32.resolve(command))] : (await (deps.resolvePath ?? augmentedPath)() || "").split(path16.win32.delimiter).map((d) => d.trim().replace(/^"|"$/g, "")).filter(Boolean);
13483
+ const base = path16.win32.basename(command);
13278
13484
  const found = [];
13279
13485
  for (const route of ROUTES) {
13280
13486
  outer: for (const ext of byRoute[route]) {
13281
13487
  for (const dir of dirs) {
13282
- const candidate = path15.win32.join(dir, `${base}${ext}`);
13488
+ const candidate = path16.win32.join(dir, `${base}${ext}`);
13283
13489
  if (exists(candidate)) {
13284
13490
  found.push({ file: candidate, route });
13285
13491
  break outer;
@@ -13327,7 +13533,7 @@ async function planDirectLaunch(command, args, deps = {}) {
13327
13533
 
13328
13534
  // src/utils/workspace.ts
13329
13535
  var fs3 = __toESM(require("fs"));
13330
- var path16 = __toESM(require("path"));
13536
+ var path17 = __toESM(require("path"));
13331
13537
  var WorkspaceNotFoundError = class extends Error {
13332
13538
  constructor(workspace) {
13333
13539
  super(`Workspace "${workspace}" does not exist.`);
@@ -13383,7 +13589,7 @@ function assertWorkspaceIsProject(workspace, deps = {}) {
13383
13589
  throw new WorkspaceNotAProjectError(workspace);
13384
13590
  }
13385
13591
  const hasMarker = [...PROJECT_MARKER_DIRS, ...PROJECT_MARKER_FILES].some(
13386
- (marker2) => exists(path16.join(resolved, marker2))
13592
+ (marker2) => exists(path17.join(resolved, marker2))
13387
13593
  );
13388
13594
  if (!hasMarker) throw new WorkspaceNotAProjectError(workspace);
13389
13595
  }
@@ -13391,12 +13597,12 @@ function assertWorkspaceIsProject(workspace, deps = {}) {
13391
13597
  // src/services/workspaceEnv.ts
13392
13598
  var import_child_process4 = require("child_process");
13393
13599
  var fs5 = __toESM(require("fs"));
13394
- var os6 = __toESM(require("os"));
13395
- var path18 = __toESM(require("path"));
13600
+ var os7 = __toESM(require("os"));
13601
+ var path19 = __toESM(require("path"));
13396
13602
 
13397
13603
  // src/utils/fsHelpers.ts
13398
13604
  var fs4 = __toESM(require("fs"));
13399
- var path17 = __toESM(require("path"));
13605
+ var path18 = __toESM(require("path"));
13400
13606
  var import_crypto5 = require("crypto");
13401
13607
  var STATE_DIR = ".ordewell";
13402
13608
  function ensureDir(dir) {
@@ -13405,9 +13611,9 @@ function ensureDir(dir) {
13405
13611
  }
13406
13612
  }
13407
13613
  function writeFileAtomic(filePath, content) {
13408
- const dir = path17.dirname(filePath);
13614
+ const dir = path18.dirname(filePath);
13409
13615
  ensureDir(dir);
13410
- const tempPath = path17.join(dir, `.${path17.basename(filePath)}.${(0, import_crypto5.randomUUID)()}.tmp`);
13616
+ const tempPath = path18.join(dir, `.${path18.basename(filePath)}.${(0, import_crypto5.randomUUID)()}.tmp`);
13411
13617
  fs4.writeFileSync(tempPath, content, { flag: "wx" });
13412
13618
  try {
13413
13619
  fs4.renameSync(tempPath, filePath);
@@ -13420,7 +13626,7 @@ function writeFileAtomic(filePath, content) {
13420
13626
  }
13421
13627
  }
13422
13628
  function getStateDir(baseDir) {
13423
- return path17.join(baseDir ?? process.cwd(), STATE_DIR);
13629
+ return path18.join(baseDir ?? process.cwd(), STATE_DIR);
13424
13630
  }
13425
13631
  var IGNORE_FILE = ".gitignore";
13426
13632
  var STATE_DIR_IGNORE = "*\n!.gitignore\n!skills/\n!skills/**\n";
@@ -13428,7 +13634,7 @@ var LEGACY_IGNORES = /* @__PURE__ */ new Set(["*", "*\n"]);
13428
13634
  function ensureStateDirIgnored(baseDir) {
13429
13635
  const dir = getStateDir(baseDir);
13430
13636
  ensureDir(dir);
13431
- const file = path17.join(dir, IGNORE_FILE);
13637
+ const file = path18.join(dir, IGNORE_FILE);
13432
13638
  try {
13433
13639
  fs4.writeFileSync(file, STATE_DIR_IGNORE, { flag: "wx" });
13434
13640
  return;
@@ -13963,7 +14169,7 @@ function defaultDeps() {
13963
14169
  }
13964
14170
  },
13965
14171
  baseEnv: process.env,
13966
- home: os6.homedir()
14172
+ home: os7.homedir()
13967
14173
  };
13968
14174
  }
13969
14175
  var REFUSED2 = new Set(SETTINGS_ENV_REFUSED);
@@ -13975,7 +14181,7 @@ async function resolveWorkspaceEnv(cwd, overrides = {}) {
13975
14181
  const direnv = await fromDirenv(checkoutDirOf(cwd, deps), deps);
13976
14182
  result.blockedEnvrc = direnv.blocked;
13977
14183
  Object.assign(collected, direnv.env);
13978
- const file = findUp(cwd, path18.join(STATE_DIR, WORKSPACE_ENV_FILE), deps);
14184
+ const file = findUp(cwd, path19.join(STATE_DIR, WORKSPACE_ENV_FILE), deps);
13979
14185
  if (file) {
13980
14186
  const content = deps.readFile(file);
13981
14187
  if (content !== null) {
@@ -13992,7 +14198,7 @@ async function resolveWorkspaceEnv(cwd, overrides = {}) {
13992
14198
  }
13993
14199
  var evaluating = /* @__PURE__ */ new Map();
13994
14200
  async function workspaceEnvOf(cwd) {
13995
- const key = path18.resolve(cwd);
14201
+ const key = path19.resolve(cwd);
13996
14202
  let running2 = evaluating.get(key);
13997
14203
  if (!running2) {
13998
14204
  running2 = resolveWorkspaceEnv(cwd).then((result) => result.env).finally(() => evaluating.delete(key));
@@ -14001,11 +14207,11 @@ async function workspaceEnvOf(cwd) {
14001
14207
  return { ...await running2 };
14002
14208
  }
14003
14209
  function checkoutDirOf(cwd, deps) {
14004
- const parts = path18.resolve(cwd).split(path18.sep);
14210
+ const parts = path19.resolve(cwd).split(path19.sep);
14005
14211
  const at = parts.findIndex((part, i) => part === STATE_DIR && parts[i + 1] === "worktrees");
14006
14212
  if (at < 0 || parts.length < at + 4) return cwd;
14007
- const root = parts.slice(0, at).join(path18.sep) || path18.sep;
14008
- const mapped = path18.join(root, ...parts.slice(at + 4));
14213
+ const root = parts.slice(0, at).join(path19.sep) || path19.sep;
14214
+ const mapped = path19.join(root, ...parts.slice(at + 4));
14009
14215
  return deps.isDirectory(mapped) ? mapped : root;
14010
14216
  }
14011
14217
  async function fromDirenv(cwd, deps) {
@@ -14035,18 +14241,18 @@ async function fromDirenv(cwd, deps) {
14035
14241
  return { env, blocked: null };
14036
14242
  }
14037
14243
  function findUp(start, relative17, deps) {
14038
- let dir = path18.resolve(start);
14244
+ let dir = path19.resolve(start);
14039
14245
  for (; ; ) {
14040
- const candidate = path18.join(dir, relative17);
14246
+ const candidate = path19.join(dir, relative17);
14041
14247
  if (deps.readFile(candidate) !== null) return candidate;
14042
- const parent = path18.dirname(dir);
14248
+ const parent = path19.dirname(dir);
14043
14249
  if (parent === dir) return null;
14044
14250
  dir = parent;
14045
14251
  }
14046
14252
  }
14047
14253
  async function isTracked(file, deps) {
14048
14254
  try {
14049
- const out = await deps.run("git", ["ls-files", "--error-unmatch", path18.basename(file)], { cwd: path18.dirname(file), env: deps.baseEnv });
14255
+ const out = await deps.run("git", ["ls-files", "--error-unmatch", path19.basename(file)], { cwd: path19.dirname(file), env: deps.baseEnv });
14050
14256
  return out.code === 0;
14051
14257
  } catch {
14052
14258
  return false;
@@ -14056,7 +14262,7 @@ function parseEnvFile(content, context, home) {
14056
14262
  const env = {};
14057
14263
  const lookup2 = (name) => env[name] ?? context[name] ?? "";
14058
14264
  const expand = (value) => {
14059
- const tilde = value.startsWith("~/") ? path18.join(home, value.slice(2)) : value;
14265
+ const tilde = value.startsWith("~/") ? path19.join(home, value.slice(2)) : value;
14060
14266
  return tilde.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, braced, bare) => lookup2(braced ?? bare));
14061
14267
  };
14062
14268
  for (const raw of content.split(/\r?\n/)) {
@@ -14326,7 +14532,7 @@ function readOnlySessionRules(site) {
14326
14532
  real = import_fs8.realpathSync.native(root);
14327
14533
  } catch {
14328
14534
  }
14329
- for (const path47 of /* @__PURE__ */ new Set([root, real])) rules.push({ action: "external_directory", resource: `${path47.replace(/[\\/]$/, "")}/*`, effect: "allow" });
14535
+ for (const path48 of /* @__PURE__ */ new Set([root, real])) rules.push({ action: "external_directory", resource: `${path48.replace(/[\\/]$/, "")}/*`, effect: "allow" });
14330
14536
  }
14331
14537
  return rules;
14332
14538
  }
@@ -14452,6 +14658,14 @@ function runnerEnv(resolvedPath, workspace = {}, launch = {}, drop) {
14452
14658
  // src/services/harness/runnerProcess.ts
14453
14659
  var STDERR_TAIL_CHARS = 4e3;
14454
14660
  var CLOSE_GRACE_MS = 2e3;
14661
+ function releasePipes(child) {
14662
+ for (const stream of [child.stdin, child.stdout, child.stderr]) {
14663
+ if (stream instanceof import_net.Socket) stream.unref();
14664
+ }
14665
+ for (const stream of [child.stdout, child.stderr]) {
14666
+ if (stream instanceof import_net.Socket) stream.resume();
14667
+ }
14668
+ }
14455
14669
  var RunnerProcess = class {
14456
14670
  constructor(deps) {
14457
14671
  this.deps = deps;
@@ -14524,6 +14738,7 @@ var RunnerProcess = class {
14524
14738
  const grace = setTimeout(() => {
14525
14739
  for (const flush of [...this.stdoutEnds]) flush();
14526
14740
  this.end({ code, signal });
14741
+ releasePipes(child);
14527
14742
  }, CLOSE_GRACE_MS);
14528
14743
  grace.unref?.();
14529
14744
  void this.ended.then(() => clearTimeout(grace));
@@ -14622,11 +14837,11 @@ ${tail}` : ""}`;
14622
14837
 
14623
14838
  // src/services/sandbox/nativeShellLauncher.ts
14624
14839
  var import_fs9 = require("fs");
14625
- var os7 = __toESM(require("os"));
14626
- var path19 = __toESM(require("path"));
14840
+ var os8 = __toESM(require("os"));
14841
+ var path20 = __toESM(require("path"));
14627
14842
  function nativeShellInvocation(platform, env = process.env) {
14628
14843
  const shell = platform === "darwin" ? "/bin/zsh" : env.SHELL;
14629
- return { shell: shell && path19.posix.isAbsolute(shell) ? shell : "/bin/sh", args: ["-c"] };
14844
+ return { shell: shell && path20.posix.isAbsolute(shell) ? shell : "/bin/sh", args: ["-c"] };
14630
14845
  }
14631
14846
  var live = /* @__PURE__ */ new Set();
14632
14847
  var exitHook = false;
@@ -14658,10 +14873,10 @@ function launcherScript(launch) {
14658
14873
  }
14659
14874
  async function createNativeShellLauncher(opts) {
14660
14875
  const invocation = nativeShellInvocation(opts.platform ?? process.platform, opts.env);
14661
- const root = (0, import_fs9.realpathSync)((0, import_fs9.mkdtempSync)(path19.join(os7.tmpdir(), "ordewell-native-shell-")));
14876
+ const root = (0, import_fs9.realpathSync)((0, import_fs9.mkdtempSync)(path20.join(os8.tmpdir(), "ordewell-native-shell-")));
14662
14877
  let shortTmp = null;
14663
14878
  try {
14664
- shortTmp = (0, import_fs9.realpathSync)((0, import_fs9.mkdtempSync)(path19.join("/tmp", "ow-")));
14879
+ shortTmp = (0, import_fs9.realpathSync)((0, import_fs9.mkdtempSync)(path20.join("/tmp", "ow-")));
14665
14880
  const tmpdir7 = shortTmp;
14666
14881
  const launch = await opts.sandbox.launchNativeShell({
14667
14882
  workspace: opts.workspace,
@@ -14670,7 +14885,7 @@ async function createNativeShellLauncher(opts) {
14670
14885
  shellArgs: invocation.args,
14671
14886
  ...opts.env ? { env: opts.env } : {}
14672
14887
  });
14673
- const prefix = path19.join(root, "run");
14888
+ const prefix = path20.join(root, "run");
14674
14889
  (0, import_fs9.writeFileSync)(prefix, launcherScript(launch), { mode: 448 });
14675
14890
  (0, import_fs9.chmodSync)(prefix, 448);
14676
14891
  keep(root);
@@ -15186,9 +15401,8 @@ var ReplyRuns = class {
15186
15401
  };
15187
15402
 
15188
15403
  // src/services/harness/claudeRepoConfig.ts
15189
- var os8 = __toESM(require("os"));
15190
- var path20 = __toESM(require("path"));
15191
- var MEMORY_FILES = ["CLAUDE.md", path20.join(".claude", "CLAUDE.md"), "CLAUDE.local.md"];
15404
+ var path21 = __toESM(require("path"));
15405
+ var MEMORY_FILES = ["CLAUDE.md", path21.join(".claude", "CLAUDE.md"), "CLAUDE.local.md"];
15192
15406
  var MAX_IMPORT_DEPTH = 5;
15193
15407
  function importsOf(text6) {
15194
15408
  const prose = text6.replace(/```[\s\S]*?```/g, "").replace(/`[^`\n]*`/g, "");
@@ -15199,11 +15413,11 @@ function withImports(workspace, file, depth, seen, into) {
15199
15413
  seen.add(file);
15200
15414
  const text6 = readText(file);
15201
15415
  if (text6 === void 0) return;
15202
- into.push({ path: path20.relative(workspace, file), text: text6 });
15416
+ into.push({ path: path21.relative(workspace, file), text: text6 });
15203
15417
  if (depth >= MAX_IMPORT_DEPTH) return;
15204
15418
  for (const target of importsOf(text6)) {
15205
15419
  if (target.startsWith("~")) continue;
15206
- const abs = path20.resolve(path20.dirname(file), target);
15420
+ const abs = path21.resolve(path21.dirname(file), target);
15207
15421
  if (insideOnDisk(workspace, abs)) withImports(workspace, abs, depth + 1, seen, into);
15208
15422
  }
15209
15423
  }
@@ -15224,12 +15438,12 @@ var CLAUDE_REPO_CONFIG = {
15224
15438
  instructions(workspace) {
15225
15439
  const files = [];
15226
15440
  const seen = /* @__PURE__ */ new Set();
15227
- for (const name of MEMORY_FILES) withImports(workspace, path20.join(workspace, name), 0, seen, files);
15441
+ for (const name of MEMORY_FILES) withImports(workspace, path21.join(workspace, name), 0, seen, files);
15228
15442
  return files;
15229
15443
  },
15230
15444
  projectServers(workspace) {
15231
15445
  const source = ".mcp.json";
15232
- const text6 = readText(path20.join(workspace, source));
15446
+ const text6 = readText(path21.join(workspace, source));
15233
15447
  if (text6 === void 0) return NO_SERVERS;
15234
15448
  let parsed;
15235
15449
  try {
@@ -15247,13 +15461,14 @@ var CLAUDE_REPO_CONFIG = {
15247
15461
  return found;
15248
15462
  }
15249
15463
  };
15250
- function claudeLocalServers(configDir2, workspace) {
15251
- const file = configDir2 ? path20.join(configDir2, ".claude.json") : path20.join(os8.homedir(), ".claude.json");
15252
- const text6 = readText(file);
15464
+ function claudeLocalServers(env, workspace) {
15465
+ const configDir2 = env.CLAUDE_CONFIG_DIR?.trim();
15466
+ const file = expandRunnerHome(configDir2 ? path21.join(configDir2, ".claude.json") : "~/.claude.json", env);
15467
+ const text6 = file === null ? void 0 : readText(file);
15253
15468
  if (text6 === void 0) return {};
15254
15469
  try {
15255
15470
  const parsed = JSON.parse(text6);
15256
- const project = isRecord2(parsed) && isRecord2(parsed.projects) ? parsed.projects[path20.resolve(workspace)] : void 0;
15471
+ const project = isRecord2(parsed) && isRecord2(parsed.projects) ? parsed.projects[path21.resolve(workspace)] : void 0;
15257
15472
  return isRecord2(project) && isRecord2(project.mcpServers) ? project.mcpServers : {};
15258
15473
  } catch {
15259
15474
  return {};
@@ -15508,13 +15723,13 @@ var NO_AUTO_MEMORY = { CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1" };
15508
15723
  function inSavedOutput(abs, configDir2, sessionId) {
15509
15724
  let projects;
15510
15725
  try {
15511
- projects = import_fs11.realpathSync.native(path21.join(configDir2, "projects"));
15726
+ projects = import_fs11.realpathSync.native(path22.join(configDir2, "projects"));
15512
15727
  } catch {
15513
15728
  return false;
15514
15729
  }
15515
- const rel = path21.relative(projects, abs);
15516
- if (rel.startsWith("..") || path21.isAbsolute(rel)) return false;
15517
- const [project, session, folder] = rel.split(path21.sep);
15730
+ const rel = path22.relative(projects, abs);
15731
+ if (rel.startsWith("..") || path22.isAbsolute(rel)) return false;
15732
+ const [project, session, folder] = rel.split(path22.sep);
15518
15733
  return Boolean(project) && session === sessionId && folder === "tool-results";
15519
15734
  }
15520
15735
  var PATH_TOOLS = {
@@ -15752,7 +15967,7 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
15752
15967
  "--append-system-prompt",
15753
15968
  opts.systemPrompt
15754
15969
  ];
15755
- if (opts.mcp) args.push(...this.ordewellServerArgs(opts.mcp, "planner", this.plannerServers(opts, workspace)));
15970
+ if (opts.mcp) args.push(...this.ordewellServerArgs(opts.mcp, "planner", this.plannerServers(opts, { ...process.env, ...workspace })));
15756
15971
  if (opts.model) args.push("--model", opts.model);
15757
15972
  if (opts.effort && opts.effort !== "adaptive") args.push("--effort", opts.effort);
15758
15973
  if (opts.resumeSessionId) {
@@ -15760,7 +15975,8 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
15760
15975
  this.countFrom(opts.resumeSessionId, opts.resumeCost);
15761
15976
  this.pendingResume = opts.resumeSessionId;
15762
15977
  }
15763
- this.configDir = workspace.CLAUDE_CONFIG_DIR ?? process.env.CLAUDE_CONFIG_DIR ?? path21.join(os9.homedir(), ".claude");
15978
+ const env = { ...process.env, ...workspace };
15979
+ this.configDir = expandRunnerHome(claudeConfigDir(env), env);
15764
15980
  return {
15765
15981
  command: "claude",
15766
15982
  args,
@@ -15830,13 +16046,12 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
15830
16046
  /**
15831
16047
  * What user-only settings stop the CLI loading that is not the
15832
16048
  * repository's to choose (ADR-0028): the user's own local-scope servers,
15833
- * under the `CLAUDE_CONFIG_DIR` the planner runs with, and the project
15834
- * servers approved for it.
16049
+ * from the account in the environment the planner runs with, and the
16050
+ * project servers approved for it.
15835
16051
  */
15836
- plannerServers(opts, workspace) {
15837
- const configDir2 = workspace.CLAUDE_CONFIG_DIR ?? process.env.CLAUDE_CONFIG_DIR;
16052
+ plannerServers(opts, env) {
15838
16053
  return {
15839
- ...claudeLocalServers(configDir2, opts.cwd),
16054
+ ...claudeLocalServers(env, opts.cwd),
15840
16055
  ...Object.fromEntries((opts.projectServers ?? []).map((server4) => [server4.name, server4.definition]))
15841
16056
  };
15842
16057
  }
@@ -16600,8 +16815,8 @@ function runProbe(deps, cwd, env, flags) {
16600
16815
  }
16601
16816
 
16602
16817
  // src/services/harness/codexRepoConfig.ts
16603
- var path22 = __toESM(require("path"));
16604
- var CONFIG_FILE = path22.join(".codex", "config.toml");
16818
+ var path23 = __toESM(require("path"));
16819
+ var CONFIG_FILE = path23.join(".codex", "config.toml");
16605
16820
  function stringsOf(value) {
16606
16821
  return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
16607
16822
  }
@@ -16627,9 +16842,9 @@ var CODEX_REPO_CONFIG = {
16627
16842
  const files = [];
16628
16843
  for (const dir of repoLevels(workspace)) {
16629
16844
  for (const name of ["AGENTS.override.md", "AGENTS.md"]) {
16630
- const text6 = readText(path22.join(dir, name));
16845
+ const text6 = readText(path23.join(dir, name));
16631
16846
  if (text6 === void 0) continue;
16632
- files.push({ path: path22.relative(workspace, path22.join(dir, name)) || name, text: text6 });
16847
+ files.push({ path: path23.relative(workspace, path23.join(dir, name)) || name, text: text6 });
16633
16848
  break;
16634
16849
  }
16635
16850
  }
@@ -16640,10 +16855,10 @@ var CODEX_REPO_CONFIG = {
16640
16855
  const byName = /* @__PURE__ */ new Map();
16641
16856
  const unreadable = [];
16642
16857
  for (const dir of repoLevels(workspace)) {
16643
- const file = path22.join(dir, CONFIG_FILE);
16858
+ const file = path23.join(dir, CONFIG_FILE);
16644
16859
  const text6 = readText(file);
16645
16860
  if (text6 === void 0) continue;
16646
- const source = path22.relative(workspace, file);
16861
+ const source = path23.relative(workspace, file);
16647
16862
  let parsed;
16648
16863
  try {
16649
16864
  parsed = parseToml(text6);
@@ -16663,9 +16878,9 @@ var CODEX_REPO_CONFIG = {
16663
16878
  };
16664
16879
  function untrustedProjects(workspace) {
16665
16880
  const projects = {};
16666
- for (let dir = path22.resolve(workspace); ; dir = path22.dirname(dir)) {
16881
+ for (let dir = path23.resolve(workspace); ; dir = path23.dirname(dir)) {
16667
16882
  projects[dir] = { trust_level: "untrusted" };
16668
- if (path22.dirname(dir) === dir) return projects;
16883
+ if (path23.dirname(dir) === dir) return projects;
16669
16884
  }
16670
16885
  }
16671
16886
 
@@ -17786,7 +18001,7 @@ ${this.exitMessage()}`);
17786
18001
  case "webSearch":
17787
18002
  return;
17788
18003
  case "fileChange": {
17789
- const paths = (item.changes ?? []).map((change) => change.path).filter((path47) => !!path47);
18004
+ const paths = (item.changes ?? []).map((change) => change.path).filter((path48) => !!path48);
17790
18005
  this.fileChangePaths.set(item.id, paths);
17791
18006
  if (this.startOpts?.kind === "task") {
17792
18007
  for (const change of item.changes ?? []) this.announceFileChange(item.id, change, emit, subagentId);
@@ -17889,20 +18104,20 @@ ${this.exitMessage()}`);
17889
18104
  announceFileChange(itemId, change, emit, subagentId) {
17890
18105
  const rows = this.fileChangeRows.get(itemId) ?? /* @__PURE__ */ new Map();
17891
18106
  this.fileChangeRows.set(itemId, rows);
17892
- const path47 = this.shownChangePath(change);
17893
- const known = rows.get(path47);
18107
+ const path48 = this.shownChangePath(change);
18108
+ const known = rows.get(path48);
17894
18109
  if (known) return known;
17895
- const row = rows.size === 0 ? itemId : `${itemId}:${path47}`;
17896
- rows.set(path47, row);
17897
- emit({ type: "tool_call", id: row, name: changeName(change), args: { path: path47 }, ...subagentId ? { subagentId } : {} });
18110
+ const row = rows.size === 0 ? itemId : `${itemId}:${path48}`;
18111
+ rows.set(path48, row);
18112
+ emit({ type: "tool_call", id: row, name: changeName(change), args: { path: path48 }, ...subagentId ? { subagentId } : {} });
17898
18113
  return row;
17899
18114
  }
17900
18115
  /** Relative to the task's worktree, where every path but a stray one lies; a move names both ends. */
17901
18116
  shownChangePath(change) {
17902
- const shown = (path47) => {
18117
+ const shown = (path48) => {
17903
18118
  const cwd = this.startOpts?.cwd;
17904
- const rel = cwd ? (0, import_path6.relative)(cwd, path47) : path47;
17905
- return rel && rel !== ".." && !rel.startsWith(`..${import_path6.sep}`) && !(0, import_path6.isAbsolute)(rel) ? rel : path47;
18119
+ const rel = cwd ? (0, import_path6.relative)(cwd, path48) : path48;
18120
+ return rel && rel !== ".." && !rel.startsWith(`..${import_path6.sep}`) && !(0, import_path6.isAbsolute)(rel) ? rel : path48;
17906
18121
  };
17907
18122
  const from = shown(change.path ?? "");
17908
18123
  return change.kind?.move_path ? `${from} \u2192 ${shown(change.kind.move_path)}` : from;
@@ -17953,7 +18168,7 @@ var import_fs12 = require("fs");
17953
18168
 
17954
18169
  // src/services/harness/projectServers.ts
17955
18170
  var import_crypto8 = require("crypto");
17956
- var path23 = __toESM(require("path"));
18171
+ var path24 = __toESM(require("path"));
17957
18172
  function canonical2(value) {
17958
18173
  if (Array.isArray(value)) return value.map(canonical2);
17959
18174
  if (!isRecord2(value)) return value;
@@ -17964,7 +18179,7 @@ function projectServerScope(server4) {
17964
18179
  return `mcp-server:${server4.name}@${hash}`;
17965
18180
  }
17966
18181
  var scopeName = (scope) => scope.slice(0, scope.lastIndexOf("@"));
17967
- function fileProjectServerApprovals(file = path23.join(globalDataDir(), "project-mcp-servers.json")) {
18182
+ function fileProjectServerApprovals(file = path24.join(globalDataDir(), "project-mcp-servers.json")) {
17968
18183
  const load = () => {
17969
18184
  const text6 = readText(file);
17970
18185
  if (text6 === void 0) return {};
@@ -19127,8 +19342,8 @@ var OpenCodeAdapter = class extends ProcessLifecycle {
19127
19342
  this.v2 = new OpenCodeV2({
19128
19343
  version,
19129
19344
  nativeShell: this.nativeShell?.prefix ?? null,
19130
- request: (method, path47, body, signal, timeoutMs) => this.request(method, path47, body, signal, timeoutMs),
19131
- json: (method, path47, body, signal, timeoutMs) => this.json(method, path47, body, signal, timeoutMs),
19345
+ request: (method, path48, body, signal, timeoutMs) => this.request(method, path48, body, signal, timeoutMs),
19346
+ json: (method, path48, body, signal, timeoutMs) => this.json(method, path48, body, signal, timeoutMs),
19132
19347
  openEvents: (signal) => this.deps.fetch(`${this.baseUrl}/api/event`, { signal, headers: this.headers() }),
19133
19348
  processEnded: this.processEnded,
19134
19349
  isExited: () => this.exited,
@@ -19765,15 +19980,15 @@ var OpenCodeAdapter = class extends ProcessLifecycle {
19765
19980
  onActivity
19766
19981
  );
19767
19982
  }
19768
- async json(method, path47, body, signal, timeoutMs) {
19769
- const response = await this.request(method, path47, body, signal, timeoutMs);
19770
- if (!response.ok) throw new OpenCodeHttpError(`${method} ${path47} failed: ${response.status} ${response.statusText}`);
19983
+ async json(method, path48, body, signal, timeoutMs) {
19984
+ const response = await this.request(method, path48, body, signal, timeoutMs);
19985
+ if (!response.ok) throw new OpenCodeHttpError(`${method} ${path48} failed: ${response.status} ${response.statusText}`);
19771
19986
  return await response.json().catch(() => null);
19772
19987
  }
19773
19988
  /** `timeoutMs` null is for a turn's own POST, which the server answers when the turn is done. */
19774
- request(method, path47, body, signal, timeoutMs = REQUEST_TIMEOUT_MS) {
19989
+ request(method, path48, body, signal, timeoutMs = REQUEST_TIMEOUT_MS) {
19775
19990
  const deadline = withDeadline(signal, timeoutMs);
19776
- return this.deps.fetch(`${this.baseUrl}${path47}`, {
19991
+ return this.deps.fetch(`${this.baseUrl}${path48}`, {
19777
19992
  method,
19778
19993
  headers: this.headers({ "content-type": "application/json" }),
19779
19994
  ...body === void 0 ? {} : { body: JSON.stringify(body) },
@@ -19834,8 +20049,8 @@ var OpenCodeAdapter = class extends ProcessLifecycle {
19834
20049
  };
19835
20050
 
19836
20051
  // src/services/harness/openCodeRepoConfig.ts
19837
- var path24 = __toESM(require("path"));
19838
- var CONFIG_FILES = ["opencode.json", "opencode.jsonc", path24.join(".opencode", "opencode.json"), path24.join(".opencode", "opencode.jsonc")];
20052
+ var path25 = __toESM(require("path"));
20053
+ var CONFIG_FILES = ["opencode.json", "opencode.jsonc", path25.join(".opencode", "opencode.json"), path25.join(".opencode", "opencode.jsonc")];
19839
20054
  var INSTRUCTION_NAMES = ["AGENTS.md", "CLAUDE.md", "CONTEXT.md"];
19840
20055
  function server3(name, entry, source) {
19841
20056
  if (!isRecord2(entry)) return "not an object";
@@ -19854,8 +20069,8 @@ var OPENCODE_REPO_CONFIG = {
19854
20069
  const levels = repoLevels(workspace);
19855
20070
  for (const name of INSTRUCTION_NAMES) {
19856
20071
  const files = levels.flatMap((dir) => {
19857
- const text6 = readText(path24.join(dir, name));
19858
- return text6 === void 0 ? [] : [{ path: path24.relative(workspace, path24.join(dir, name)) || name, text: text6 }];
20072
+ const text6 = readText(path25.join(dir, name));
20073
+ return text6 === void 0 ? [] : [{ path: path25.relative(workspace, path25.join(dir, name)) || name, text: text6 }];
19859
20074
  });
19860
20075
  if (files.length > 0) return files;
19861
20076
  }
@@ -19867,10 +20082,10 @@ var OPENCODE_REPO_CONFIG = {
19867
20082
  const unreadable = [];
19868
20083
  for (const dir of repoLevels(workspace)) {
19869
20084
  for (const name of CONFIG_FILES) {
19870
- const file = path24.join(dir, name);
20085
+ const file = path25.join(dir, name);
19871
20086
  const text6 = readText(file);
19872
20087
  if (text6 === void 0) continue;
19873
- const source = path24.relative(workspace, file);
20088
+ const source = path25.relative(workspace, file);
19874
20089
  let parsed;
19875
20090
  try {
19876
20091
  parsed = parseJsonc(text6);
@@ -20059,7 +20274,7 @@ function knownModelId(value, optionIds, shortcuts) {
20059
20274
 
20060
20275
  // src/services/ModelCatalog.ts
20061
20276
  var fs6 = __toESM(require("fs"));
20062
- var path25 = __toESM(require("path"));
20277
+ var path26 = __toESM(require("path"));
20063
20278
  var CACHE_KEY = "model-catalog";
20064
20279
  var CACHE_TTL_MS = 60 * 60 * 1e3;
20065
20280
  function storageKey(baseUrl) {
@@ -20112,7 +20327,7 @@ var ModelCatalog = class {
20112
20327
  const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
20113
20328
  const key = storageKey(url);
20114
20329
  const cacheDir = globalCacheDir();
20115
- const cacheFile = path25.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
20330
+ const cacheFile = path26.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
20116
20331
  if (fs6.existsSync(cacheFile)) fs6.rmSync(cacheFile);
20117
20332
  } catch {
20118
20333
  }
@@ -20120,7 +20335,7 @@ var ModelCatalog = class {
20120
20335
  static readCache(key) {
20121
20336
  try {
20122
20337
  const cacheDir = globalCacheDir();
20123
- const cacheFile = path25.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
20338
+ const cacheFile = path26.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
20124
20339
  if (!fs6.existsSync(cacheFile)) return null;
20125
20340
  const raw = fs6.readFileSync(cacheFile, "utf8");
20126
20341
  const data = JSON.parse(raw);
@@ -20134,7 +20349,7 @@ var ModelCatalog = class {
20134
20349
  try {
20135
20350
  const cacheDir = globalCacheDir();
20136
20351
  fs6.mkdirSync(cacheDir, { recursive: true });
20137
- const cacheFile = path25.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
20352
+ const cacheFile = path26.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
20138
20353
  fs6.writeFileSync(
20139
20354
  cacheFile,
20140
20355
  JSON.stringify({ models, cachedAt: Date.now() }, null, 2)
@@ -20145,15 +20360,17 @@ var ModelCatalog = class {
20145
20360
  };
20146
20361
 
20147
20362
  // src/services/ModelDiscovery.ts
20148
- var path26 = __toESM(require("path"));
20363
+ var path27 = __toESM(require("path"));
20149
20364
  var import_child_process6 = require("child_process");
20150
20365
  var import_util2 = require("util");
20151
20366
  var fsSync5 = __toESM(require("fs"));
20152
- var osMod = __toESM(require("os"));
20153
20367
 
20154
20368
  // src/services/harness/appServerModels.ts
20155
20369
  var import_child_process5 = require("child_process");
20156
20370
  var DEFAULT_TIMEOUT_MS3 = 2e4;
20371
+ function definedOnly(env) {
20372
+ return Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== void 0));
20373
+ }
20157
20374
  function parseRpcLine(line) {
20158
20375
  try {
20159
20376
  const value = JSON.parse(line);
@@ -20166,10 +20383,9 @@ async function openAppServer(command, args, launch) {
20166
20383
  const runner = new RunnerProcess({
20167
20384
  spawn: import_child_process5.spawn,
20168
20385
  fetch: globalThis.fetch,
20169
- // The cwd is no workspace: discovery asks for the catalog before one is
20170
- // chosen, so it pays no direnv round trip before a spawn with a deadline.
20171
- // The account monitor passes the workspace's environment itself, since
20172
- // that can choose whose account is read.
20386
+ // The cwd is no workspace, so it pays no direnv round trip before a spawn
20387
+ // with a deadline. Discovery and the account monitor pass the workspace's
20388
+ // environment themselves, since that can choose whose account is read.
20173
20389
  workspaceEnv: async () => ({}),
20174
20390
  ...launch.deps
20175
20391
  });
@@ -20222,7 +20438,11 @@ async function openAppServer(command, args, launch) {
20222
20438
  }
20223
20439
  var APP_SERVER_CLIENT_INFO = { name: "ordewell", title: "Ordewell", version: "0.1.0" };
20224
20440
  async function discoverAppServerModels(command, args, options = {}) {
20225
- const server4 = await openAppServer(command, args, { cwd: process.cwd(), deps: options.deps });
20441
+ const env = options.env;
20442
+ const server4 = await openAppServer(command, args, {
20443
+ cwd: process.cwd(),
20444
+ deps: { ...env ? { workspaceEnv: async () => definedOnly(env) } : {}, ...options.deps }
20445
+ });
20226
20446
  if (!server4) return null;
20227
20447
  const { rpc } = server4;
20228
20448
  const asked = (async () => {
@@ -20249,10 +20469,10 @@ async function discoverAppServerModels(command, args, options = {}) {
20249
20469
  var execAsync2 = (0, import_util2.promisify)(import_child_process6.exec);
20250
20470
  var defaultExec2 = async (command, options) => {
20251
20471
  const PATH = await augmentedPath();
20252
- const { stdout } = await execAsync2(command, { ...options, maxBuffer: 32 * 1024 * 1024, env: withPath(process.env, PATH) });
20472
+ const { stdout } = await execAsync2(command, { timeout: options?.timeout, maxBuffer: 32 * 1024 * 1024, env: withPath(options?.env ?? process.env, PATH) });
20253
20473
  return { stdout: String(stdout) };
20254
20474
  };
20255
- var defaultAppServerClient = (command, args, options) => discoverAppServerModels(command, args, { timeout: options?.timeout });
20475
+ var defaultAppServerClient = (command, args, options) => discoverAppServerModels(command, args, { timeout: options?.timeout, env: options?.env });
20256
20476
  function effortVariant(effort) {
20257
20477
  return { id: effort, label: effort.charAt(0).toUpperCase() + effort.slice(1) };
20258
20478
  }
@@ -20286,7 +20506,7 @@ function geminiDiscoveryCacheKey(apiKey) {
20286
20506
  return {
20287
20507
  key: `gemini-models:${hash}`,
20288
20508
  dir,
20289
- file: path26.join(dir, `gemini_models_${hash}.json`)
20509
+ file: path27.join(dir, `gemini_models_${hash}.json`)
20290
20510
  };
20291
20511
  }
20292
20512
  function readGeminiCache(apiKey) {
@@ -20373,15 +20593,22 @@ function applyVariants(models, manifest) {
20373
20593
  if (!variants || variants.length === 0) return models;
20374
20594
  return models.map((m) => m.variants.length > 0 ? m : { ...m, variants });
20375
20595
  }
20376
- function resolveApiAuth(authMethods) {
20596
+ function listDir(dir) {
20597
+ try {
20598
+ return fsSync5.readdirSync(dir);
20599
+ } catch {
20600
+ return [];
20601
+ }
20602
+ }
20603
+ function resolveApiAuth(authMethods, env) {
20377
20604
  for (const method of authMethods) {
20378
20605
  try {
20379
20606
  let token;
20380
20607
  if (method.type === "env") {
20381
- token = process.env[method.varName];
20608
+ token = env[method.varName];
20382
20609
  } else {
20383
- const filePath = method.path.replace(/^~/, osMod.homedir());
20384
- if (!fsSync5.existsSync(filePath)) continue;
20610
+ const filePath = expandRunnerHome(typeof method.path === "function" ? method.path(env) : method.path, env);
20611
+ if (!filePath || !fsSync5.existsSync(filePath)) continue;
20385
20612
  const data = JSON.parse(fsSync5.readFileSync(filePath, "utf8"));
20386
20613
  token = method.jsonPath.split(".").reduce((obj, key) => {
20387
20614
  if (obj && typeof obj === "object") return obj[key];
@@ -20397,8 +20624,8 @@ function resolveApiAuth(authMethods) {
20397
20624
  }
20398
20625
  return null;
20399
20626
  }
20400
- async function discoverFromApi(manifest, config, fetchImpl) {
20401
- const auth = resolveApiAuth(config.auth);
20627
+ async function discoverFromApi(manifest, config, env, fetchImpl) {
20628
+ const auth = resolveApiAuth(config.auth, env);
20402
20629
  if (!auth) return null;
20403
20630
  try {
20404
20631
  const headers = { ...config.headers, [auth.header]: auth.value };
@@ -20414,7 +20641,7 @@ async function discoverFromApi(manifest, config, fetchImpl) {
20414
20641
  function wasKilled(err) {
20415
20642
  return !!err && typeof err === "object" && err.killed === true;
20416
20643
  }
20417
- async function discoverFromCommand(manifest, execImpl) {
20644
+ async function discoverFromCommand(manifest, execImpl, env) {
20418
20645
  const discovery = manifest.modelDiscovery;
20419
20646
  const commands = discovery.discoveryCommands ?? [
20420
20647
  { command: discovery.command, args: discovery.args || [], parser: discovery.parser }
@@ -20423,7 +20650,7 @@ async function discoverFromCommand(manifest, execImpl) {
20423
20650
  let timedOut2 = false;
20424
20651
  for (const cmd of commands) {
20425
20652
  try {
20426
- const { stdout } = await execImpl(`${cmd.command} ${cmd.args.join(" ")}`, { timeout: 45e3 });
20653
+ const { stdout } = await execImpl(`${cmd.command} ${cmd.args.join(" ")}`, { timeout: 45e3, env });
20427
20654
  const models = parseModelOutput(stdout, cmd.parser, manifest);
20428
20655
  if (models.length > 0) return { models: applyVariants(mergeCanonicalAliases(models, manifest), manifest), timedOut: timedOut2 };
20429
20656
  } catch (err) {
@@ -20489,21 +20716,26 @@ var ModelDiscovery = class {
20489
20716
  * same background service.
20490
20717
  */
20491
20718
  inflight = /* @__PURE__ */ new Map();
20719
+ /** Bumped by every `clear()`: a discovery begun before one answers its own lookups but fills no cache. */
20720
+ generation = 0;
20492
20721
  registry;
20493
20722
  execImpl;
20494
20723
  fetchImpl;
20495
20724
  appServerClient;
20496
20725
  readFileImpl;
20497
20726
  now;
20498
- constructor(registry, execImpl = defaultExec2, fetchImpl, appServerClient = defaultAppServerClient, readFileImpl, now = Date.now) {
20727
+ env;
20728
+ constructor(registry, execImpl = defaultExec2, fetchImpl, appServerClient = defaultAppServerClient, readFileImpl, now = Date.now, env = async () => process.env) {
20499
20729
  this.registry = registry;
20500
20730
  this.execImpl = execImpl;
20501
20731
  this.fetchImpl = fetchImpl;
20502
20732
  this.appServerClient = appServerClient;
20503
20733
  this.now = now;
20734
+ this.env = env;
20504
20735
  this.readFileImpl = readFileImpl ?? ((filePath) => {
20505
20736
  try {
20506
- return fsSync5.readFileSync(filePath.replace(/^~/, osMod.homedir()), "utf8");
20737
+ const file = expandRunnerHome(filePath);
20738
+ return file ? fsSync5.readFileSync(file, "utf8") : null;
20507
20739
  } catch {
20508
20740
  return null;
20509
20741
  }
@@ -20515,16 +20747,17 @@ var ModelDiscovery = class {
20515
20747
  * null when both fail so `discover()` falls through to the remaining
20516
20748
  * methods.
20517
20749
  */
20518
- async discoverFromAppServer(manifest) {
20750
+ async discoverFromAppServer(manifest, env) {
20519
20751
  const config = manifest.modelDiscovery.appServer;
20520
20752
  if (!config) return null;
20521
- const raw = await this.appServerClient(config.command, config.args);
20753
+ const raw = await this.appServerClient(config.command, config.args, { env });
20522
20754
  if (raw) {
20523
20755
  const models = parseAppServerModels(raw);
20524
20756
  if (models.length > 0) return models;
20525
20757
  }
20526
- if (config.cacheFile) {
20527
- const content = this.readFileImpl(config.cacheFile);
20758
+ const cacheFile = config.cacheFile && expandRunnerHome(typeof config.cacheFile === "function" ? config.cacheFile(env) : config.cacheFile, env);
20759
+ if (cacheFile) {
20760
+ const content = this.readFileImpl(cacheFile);
20528
20761
  if (content) {
20529
20762
  const models = parseCodexModelsCache(content);
20530
20763
  if (models.length > 0) return models;
@@ -20562,37 +20795,60 @@ var ModelDiscovery = class {
20562
20795
  if (!manifest) {
20563
20796
  return [];
20564
20797
  }
20565
- let models;
20798
+ const generation2 = this.generation;
20799
+ const env = await this.env().catch(() => process.env);
20800
+ const found = await this.discoverFromSources(manifest, env);
20801
+ const models = this.withSettingsModels(found.models, manifest, env);
20802
+ if (generation2 !== this.generation) return models;
20803
+ if (found.cacheable && models.length > 0) this.cache.set(runner, models);
20804
+ else this.misses.set(runner, { models, at: this.now() });
20805
+ return models;
20806
+ }
20807
+ async discoverFromSources(manifest, env) {
20566
20808
  let cacheable = true;
20567
20809
  const discovery = manifest.modelDiscovery;
20568
20810
  if (discovery.appServer) {
20569
- const appServerModels = await this.discoverFromAppServer(manifest);
20570
- if (appServerModels && appServerModels.length > 0) {
20571
- this.cache.set(runner, appServerModels);
20572
- return appServerModels;
20573
- }
20811
+ const appServerModels = await this.discoverFromAppServer(manifest, env);
20812
+ if (appServerModels && appServerModels.length > 0) return { models: appServerModels, cacheable: true };
20574
20813
  cacheable = false;
20575
20814
  }
20576
20815
  if (discovery.apiDiscovery) {
20577
- const apiModels = await discoverFromApi(manifest, discovery.apiDiscovery, this.fetchImpl);
20816
+ const apiModels = await discoverFromApi(manifest, discovery.apiDiscovery, env, this.fetchImpl);
20578
20817
  if (apiModels && apiModels.length > 0) {
20579
- models = applyVariants(mergeCanonicalAliases(apiModels, manifest), manifest);
20580
- if (cacheable && models.length > 0) this.cache.set(runner, models);
20581
- return models;
20818
+ return { models: applyVariants(mergeCanonicalAliases(apiModels, manifest), manifest), cacheable };
20582
20819
  }
20583
20820
  }
20584
20821
  if (discovery.method === "command" && (discovery.command || discovery.discoveryCommands?.length)) {
20585
- const result = await discoverFromCommand(manifest, this.execImpl);
20586
- models = result.models;
20587
- cacheable = !result.fromFallback;
20588
- } else {
20589
- models = buildFallbackModels(manifest);
20822
+ const result = await discoverFromCommand(manifest, this.execImpl, env);
20823
+ return { models: result.models, cacheable: !result.fromFallback };
20590
20824
  }
20591
- if (cacheable && models.length > 0) this.cache.set(runner, models);
20592
- else this.misses.set(runner, { models, at: this.now() });
20593
- return models;
20825
+ return { models: buildFallbackModels(manifest), cacheable };
20826
+ }
20827
+ /**
20828
+ * Append the models registered in the runner's own settings. Read on every
20829
+ * discovery rather than cached apart, so a refresh picks up an edit;
20830
+ * whatever the sources already offer keeps its entry and metadata.
20831
+ */
20832
+ withSettingsModels(models, manifest, env) {
20833
+ const read2 = manifest.modelDiscovery.settingsModels;
20834
+ if (!read2) return models;
20835
+ const files = {
20836
+ read: (filePath) => {
20837
+ const file = expandRunnerHome(filePath, env);
20838
+ return file ? this.readFileImpl(file) : null;
20839
+ },
20840
+ list: (dir) => {
20841
+ const expanded = expandRunnerHome(dir, env);
20842
+ return expanded ? listDir(expanded) : [];
20843
+ },
20844
+ env
20845
+ };
20846
+ const seen = new Set(models.map((m) => m.modelId));
20847
+ const added = read2(files).filter((m) => !seen.has(m.modelId));
20848
+ return added.length > 0 ? [...models, ...applyVariants(added, manifest)] : models;
20594
20849
  }
20595
20850
  clear() {
20851
+ this.generation++;
20596
20852
  this.cache.clear();
20597
20853
  this.misses.clear();
20598
20854
  this.inflight.clear();
@@ -20758,7 +21014,7 @@ function parseMaxParallel(value) {
20758
21014
 
20759
21015
  // src/services/SettingsService.ts
20760
21016
  var fs12 = __toESM(require("fs"));
20761
- var path33 = __toESM(require("path"));
21017
+ var path34 = __toESM(require("path"));
20762
21018
 
20763
21019
  // src/utils/fileLock.ts
20764
21020
  var fs7 = __toESM(require("fs"));
@@ -20890,6 +21146,10 @@ var RECEIPT_LABELS = {
20890
21146
  function receiptLabel(task2) {
20891
21147
  return isTaskRunning(task2) && task2.completionReceived ? RECEIPT_LABELS[task2.completionReceived] : null;
20892
21148
  }
21149
+ function attachingLabel(task2) {
21150
+ if (!isTaskRunning(task2) || !task2.attaching) return null;
21151
+ return task2.attaching === 2 ? "Connecting Ordewell's tools, retry" : "Connecting Ordewell's tools";
21152
+ }
20893
21153
  function approvalLabel(count) {
20894
21154
  if (!count) return null;
20895
21155
  return count > 1 ? `Waiting for approval (${count})` : "Waiting for approval";
@@ -22359,7 +22619,7 @@ function createWakePolicy(options) {
22359
22619
  }
22360
22620
 
22361
22621
  // src/services/runnerExit.ts
22362
- 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;
22622
+ 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;
22363
22623
  var USAGE_LIMIT_TAIL = 4096;
22364
22624
  function classifyRunnerStop(said2) {
22365
22625
  if (typeof said2 !== "string" && said2.usageLimit !== void 0) return said2.usageLimit ? "usage-limit" : "stopped";
@@ -22720,8 +22980,8 @@ function compactionCut(transcript) {
22720
22980
  }
22721
22981
 
22722
22982
  // src/services/skillInvocation.ts
22723
- var os10 = __toESM(require("os"));
22724
- function abbreviateHome(file, home = os10.homedir(), platform = process.platform) {
22983
+ var os9 = __toESM(require("os"));
22984
+ function abbreviateHome(file, home = os9.homedir(), platform = process.platform) {
22725
22985
  const win = platform === "win32";
22726
22986
  const normal = (p) => win ? p.replace(/\\/g, "/") : p;
22727
22987
  const shown = normal(file);
@@ -22736,13 +22996,13 @@ function modelInvocablePlannerSkills(skills) {
22736
22996
  function modelInvocableTaskSkills(skills) {
22737
22997
  return skills.filter((skill) => skill.appliesTo === "task" && skill.modelInvocable);
22738
22998
  }
22739
- function snapshotSkill(skill, invokedBy, home = os10.homedir()) {
22999
+ function snapshotSkill(skill, invokedBy, home = os9.homedir()) {
22740
23000
  return { invokedBy, name: skill.name, source: skill.source, path: abbreviateHome(skill.path, home), content: skill.content };
22741
23001
  }
22742
23002
  function attachSkill(skill, home) {
22743
23003
  return { ...snapshotSkill(skill, "user", home), content: "", attaches: { description: skill.description } };
22744
23004
  }
22745
- function resolveSkillInvocation(text6, skillsService, home = os10.homedir()) {
23005
+ function resolveSkillInvocation(text6, skillsService, home = os9.homedir()) {
22746
23006
  const skills = skillTokens(text6).flatMap(({ name }) => {
22747
23007
  const skill = skillsService.findSkill(name);
22748
23008
  if (!skill?.userInvocable) return [];
@@ -22806,33 +23066,33 @@ ${skillBlock(entry.skill)}`, timestamp: entry.timestamp });
22806
23066
  }
22807
23067
 
22808
23068
  // src/services/taskSkills.ts
22809
- var path32 = __toESM(require("path"));
23069
+ var path33 = __toESM(require("path"));
22810
23070
 
22811
23071
  // src/services/SkillsService.ts
22812
23072
  var import_crypto12 = require("crypto");
22813
23073
  var fs11 = __toESM(require("fs"));
22814
- var path31 = __toESM(require("path"));
23074
+ var path32 = __toESM(require("path"));
22815
23075
 
22816
23076
  // src/services/builtinSkills.ts
22817
- var path27 = __toESM(require("path"));
23077
+ var path28 = __toESM(require("path"));
22818
23078
  var import_url = require("url");
22819
23079
  function builtinSkillsDir() {
22820
- const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path27.dirname((0, import_url.fileURLToPath)(
23080
+ const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path28.dirname((0, import_url.fileURLToPath)(
22821
23081
  // @ts-expect-error - import.meta is only valid under an ESM module setting
22822
23082
  __filename
22823
23083
  ));
22824
- return path27.join(dir, "..", "skills");
23084
+ return path28.join(dir, "..", "skills");
22825
23085
  }
22826
23086
 
22827
23087
  // src/services/repoGroup.ts
22828
23088
  var fs10 = __toESM(require("fs"));
22829
- var path30 = __toESM(require("path"));
23089
+ var path31 = __toESM(require("path"));
22830
23090
 
22831
23091
  // src/services/worktreeBootstrap.ts
22832
23092
  var import_child_process8 = require("child_process");
22833
23093
  var import_util4 = require("util");
22834
23094
  var fs9 = __toESM(require("fs"));
22835
- var path29 = __toESM(require("path"));
23095
+ var path30 = __toESM(require("path"));
22836
23096
 
22837
23097
  // src/services/gitExec.ts
22838
23098
  var import_child_process7 = require("child_process");
@@ -22914,7 +23174,7 @@ function subcommandOf2(args) {
22914
23174
 
22915
23175
  // src/services/worktreeLink.ts
22916
23176
  var fs8 = __toESM(require("fs"));
22917
- var path28 = __toESM(require("path"));
23177
+ var path29 = __toESM(require("path"));
22918
23178
  function linkPath(source, target, platform, hardLink = fs8.linkSync) {
22919
23179
  const isDir = fs8.statSync(source).isDirectory();
22920
23180
  if (platform !== "win32") {
@@ -22939,8 +23199,8 @@ function mirrorDir(source, target, opts) {
22939
23199
  const mirror = (from, to, prefix, top) => {
22940
23200
  fs8.mkdirSync(to, { recursive: true });
22941
23201
  for (const entry of fs8.readdirSync(from, { withFileTypes: true })) {
22942
- const name = path28.join(prefix, entry.name);
22943
- const at = { from: path28.join(from, entry.name), to: path28.join(to, entry.name), name };
23202
+ const name = path29.join(prefix, entry.name);
23203
+ const at = { from: path29.join(from, entry.name), to: path29.join(to, entry.name), name };
22944
23204
  if (entry.isSymbolicLink()) links.push(at);
22945
23205
  else if (top && entry.isDirectory() && (entry.name.startsWith("@") || entry.name === ".bin")) mirror(at.from, at.to, name, false);
22946
23206
  else if (linkPath(at.from, at.to, opts.platform, opts.hardLink) === "copy") copied.push(name);
@@ -22960,16 +23220,16 @@ function mirrorDir(source, target, opts) {
22960
23220
  }
22961
23221
  function relink(from, to, opts) {
22962
23222
  const text6 = fs8.readlinkSync(from);
22963
- const pointee = path28.isAbsolute(text6) ? rebased(text6, opts) : text6;
23223
+ const pointee = path29.isAbsolute(text6) ? rebased(text6, opts) : text6;
22964
23224
  if (opts.platform !== "win32") {
22965
- if (!fs8.existsSync(path28.resolve(path28.dirname(to), pointee)) && fs8.existsSync(path28.resolve(path28.dirname(from), pointee))) {
22966
- fs8.symlinkSync(rebased(path28.resolve(path28.dirname(from), pointee), opts), to);
23225
+ if (!fs8.existsSync(path29.resolve(path29.dirname(to), pointee)) && fs8.existsSync(path29.resolve(path29.dirname(from), pointee))) {
23226
+ fs8.symlinkSync(rebased(path29.resolve(path29.dirname(from), pointee), opts), to);
22967
23227
  return "symlink";
22968
23228
  }
22969
23229
  fs8.symlinkSync(pointee, to);
22970
23230
  return "symlink";
22971
23231
  }
22972
- const resolved = fs8.existsSync(path28.resolve(path28.dirname(to), pointee)) ? path28.resolve(path28.dirname(to), pointee) : rebased(path28.resolve(path28.dirname(from), pointee), opts);
23232
+ const resolved = fs8.existsSync(path29.resolve(path29.dirname(to), pointee)) ? path29.resolve(path29.dirname(to), pointee) : rebased(path29.resolve(path29.dirname(from), pointee), opts);
22973
23233
  try {
22974
23234
  fs8.statSync(resolved);
22975
23235
  } catch {
@@ -22978,9 +23238,9 @@ function relink(from, to, opts) {
22978
23238
  return linkPath(resolved, to, opts.platform, opts.hardLink);
22979
23239
  }
22980
23240
  function rebased(pointee, opts) {
22981
- const rel = path28.relative(opts.from, pointee);
22982
- const inside2 = rel !== "" && !rel.startsWith("..") && !path28.isAbsolute(rel);
22983
- return inside2 && !rel.split(/[\\/]/).includes("node_modules") ? path28.join(opts.to, rel) : pointee;
23241
+ const rel = path29.relative(opts.from, pointee);
23242
+ const inside2 = rel !== "" && !rel.startsWith("..") && !path29.isAbsolute(rel);
23243
+ return inside2 && !rel.split(/[\\/]/).includes("node_modules") ? path29.join(opts.to, rel) : pointee;
22984
23244
  }
22985
23245
 
22986
23246
  // src/services/worktreeBootstrap.ts
@@ -22997,7 +23257,7 @@ function lexists(target) {
22997
23257
  return false;
22998
23258
  }
22999
23259
  }
23000
- function listDir(dir) {
23260
+ function listDir2(dir) {
23001
23261
  try {
23002
23262
  return fs9.readdirSync(dir).sort();
23003
23263
  } catch {
@@ -23017,18 +23277,18 @@ function matchLinks(root, patterns) {
23017
23277
  }
23018
23278
  function segmentMatches(root, base, segment) {
23019
23279
  const under = (name) => base ? `${base}/${name}` : name;
23020
- if (!/[*?]/.test(segment)) return lexists(path29.join(root, base, segment)) ? [under(segment)] : [];
23280
+ if (!/[*?]/.test(segment)) return lexists(path30.join(root, base, segment)) ? [under(segment)] : [];
23021
23281
  const pattern = new RegExp(`^${segment.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}$`);
23022
- return listDir(path29.join(root, base)).filter((name) => !NEVER_SCANNED.has(name) && pattern.test(name)).map(under);
23282
+ return listDir2(path30.join(root, base)).filter((name) => !NEVER_SCANNED.has(name) && pattern.test(name)).map(under);
23023
23283
  }
23024
23284
  function installDirs(root) {
23025
23285
  const packages = matchLinks(root, workspaceGlobs(root)).map((pkg) => `${pkg}/node_modules`);
23026
- return ["node_modules", ...packages].filter((dir) => lexists(path29.join(root, dir)));
23286
+ return ["node_modules", ...packages].filter((dir) => lexists(path30.join(root, dir)));
23027
23287
  }
23028
23288
  function workspaceGlobs(root) {
23029
23289
  let manifest;
23030
23290
  try {
23031
- manifest = JSON.parse(fs9.readFileSync(path29.join(root, "package.json"), "utf8"));
23291
+ manifest = JSON.parse(fs9.readFileSync(path30.join(root, "package.json"), "utf8"));
23032
23292
  } catch {
23033
23293
  return [];
23034
23294
  }
@@ -23043,19 +23303,19 @@ async function bootstrap(repo, cwd, opts) {
23043
23303
  const linked = [];
23044
23304
  const copied = [];
23045
23305
  const link = (name) => {
23046
- const target = path29.join(cwd, name);
23306
+ const target = path30.join(cwd, name);
23047
23307
  if (lexists(target)) return;
23048
- fs9.mkdirSync(path29.dirname(target), { recursive: true });
23049
- if (linkPath(path29.join(repo.root, name), target, opts.platform) === "copy") copied.push(name);
23308
+ fs9.mkdirSync(path30.dirname(target), { recursive: true });
23309
+ if (linkPath(path30.join(repo.root, name), target, opts.platform) === "copy") copied.push(name);
23050
23310
  linked.push(name);
23051
23311
  };
23052
23312
  const mirror = (name) => {
23053
- const target = path29.join(cwd, name);
23054
- if (lexists(target) || !fs9.existsSync(path29.dirname(target))) return;
23055
- const source = path29.join(repo.root, name);
23313
+ const target = path30.join(cwd, name);
23314
+ if (lexists(target) || !fs9.existsSync(path30.dirname(target))) return;
23315
+ const source = path30.join(repo.root, name);
23056
23316
  if (!fs9.lstatSync(source).isDirectory()) return link(name);
23057
23317
  const mirrored = mirrorDir(source, target, { platform: opts.platform, from: repo.root, to: cwd });
23058
- copied.push(...mirrored.map((entry) => path29.join(name, entry)));
23318
+ copied.push(...mirrored.map((entry) => path30.join(name, entry)));
23059
23319
  linked.push(name);
23060
23320
  };
23061
23321
  if (!setup) {
@@ -23090,12 +23350,12 @@ async function runSetup(command, repo, cwd, resolvePath) {
23090
23350
 
23091
23351
  // src/services/repoGroup.ts
23092
23352
  function hasGitEntry(dir) {
23093
- return fs10.existsSync(path30.join(dir, ".git"));
23353
+ return fs10.existsSync(path31.join(dir, ".git"));
23094
23354
  }
23095
23355
  function groupPathOf(listed3) {
23096
23356
  const slashed = listed3.trim().replace(/\\/g, "/");
23097
- if (path30.posix.isAbsolute(slashed) || path30.win32.isAbsolute(slashed)) return null;
23098
- const rel = path30.posix.normalize(slashed).replace(/\/+$/, "");
23357
+ if (path31.posix.isAbsolute(slashed) || path31.win32.isAbsolute(slashed)) return null;
23358
+ const rel = path31.posix.normalize(slashed).replace(/\/+$/, "");
23099
23359
  return rel === "" || rel === "." || rel === ".." || rel.startsWith("../") ? null : rel;
23100
23360
  }
23101
23361
  function childDirs(dir) {
@@ -23106,14 +23366,14 @@ function childDirs(dir) {
23106
23366
  }
23107
23367
  }
23108
23368
  function groupPaths(workspaceRoot, listed3) {
23109
- if (listed3.length === 0) return childDirs(workspaceRoot).filter((name) => hasGitEntry(path30.join(workspaceRoot, name)));
23110
- const paths = listed3.map(groupPathOf).filter((rel) => rel !== null && hasGitEntry(path30.join(workspaceRoot, rel)));
23369
+ if (listed3.length === 0) return childDirs(workspaceRoot).filter((name) => hasGitEntry(path31.join(workspaceRoot, name)));
23370
+ const paths = listed3.map(groupPathOf).filter((rel) => rel !== null && hasGitEntry(path31.join(workspaceRoot, rel)));
23111
23371
  return [...new Set(paths)].sort();
23112
23372
  }
23113
23373
  function workspaceRepoGroup(workspaceRoot, listed3) {
23114
- for (let dir = path30.resolve(workspaceRoot); ; dir = path30.dirname(dir)) {
23374
+ for (let dir = path31.resolve(workspaceRoot); ; dir = path31.dirname(dir)) {
23115
23375
  if (hasGitEntry(dir)) return [SELF_REPO];
23116
- if (path30.dirname(dir) === dir) break;
23376
+ if (path31.dirname(dir) === dir) break;
23117
23377
  }
23118
23378
  return groupPaths(workspaceRoot, listed3);
23119
23379
  }
@@ -23194,8 +23454,8 @@ function copyDirSync(src, dest) {
23194
23454
  fs11.mkdirSync(dest, { recursive: true });
23195
23455
  const entries = fs11.readdirSync(src, { withFileTypes: true });
23196
23456
  for (const entry of entries) {
23197
- const srcPath = path31.join(src, entry.name);
23198
- const destPath = path31.join(dest, entry.name);
23457
+ const srcPath = path32.join(src, entry.name);
23458
+ const destPath = path32.join(dest, entry.name);
23199
23459
  if (entry.isDirectory()) copyDirSync(srcPath, destPath);
23200
23460
  else fs11.copyFileSync(srcPath, destPath);
23201
23461
  }
@@ -23219,7 +23479,7 @@ function readDir(dir, source) {
23219
23479
  if (!fs11.existsSync(dir)) return { skills, invalid };
23220
23480
  for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
23221
23481
  if (!entry.isDirectory()) continue;
23222
- const skillFile = path31.join(dir, entry.name, "SKILL.md");
23482
+ const skillFile = path32.join(dir, entry.name, "SKILL.md");
23223
23483
  if (!fs11.existsSync(skillFile)) continue;
23224
23484
  if (!isSkillName(entry.name)) {
23225
23485
  invalid.push({ name: entry.name, path: skillFile, source, reason: INVALID_SKILL_NAME_REASON });
@@ -23238,15 +23498,15 @@ function skillFileIn(dir, name) {
23238
23498
  return void 0;
23239
23499
  }
23240
23500
  if (!entries.some((entry) => entry.isDirectory() && entry.name === name)) return void 0;
23241
- const skillFile = path31.join(dir, name, "SKILL.md");
23501
+ const skillFile = path32.join(dir, name, "SKILL.md");
23242
23502
  return fs11.existsSync(skillFile) ? skillFile : void 0;
23243
23503
  }
23244
23504
  function skillsDirOf(root) {
23245
- return path31.join(root, STATE_DIR, "skills");
23505
+ return path32.join(root, STATE_DIR, "skills");
23246
23506
  }
23247
23507
  function workspaceSkillRoots(workspaceRoot, repos, checkout = workspaceRoot) {
23248
23508
  if (repos.length === 0 || repos.includes(SELF_REPO)) return [checkout];
23249
- return [workspaceRoot, ...repos.map((repo) => path31.join(checkout, repo))];
23509
+ return [workspaceRoot, ...repos.map((repo) => path32.join(checkout, repo))];
23250
23510
  }
23251
23511
  var SkillsService = class _SkillsService {
23252
23512
  workspaceRoots;
@@ -23269,11 +23529,11 @@ var SkillsService = class _SkillsService {
23269
23529
  return this.workspaceRoots.map(skillsDirOf);
23270
23530
  }
23271
23531
  globalDir() {
23272
- return path31.join(globalDataDir(), "skills");
23532
+ return path32.join(globalDataDir(), "skills");
23273
23533
  }
23274
23534
  readSeedManifest() {
23275
23535
  try {
23276
- const parsed = JSON.parse(fs11.readFileSync(path31.join(this.globalDir(), SEED_MANIFEST), "utf8"));
23536
+ const parsed = JSON.parse(fs11.readFileSync(path32.join(this.globalDir(), SEED_MANIFEST), "utf8"));
23277
23537
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
23278
23538
  return Object.fromEntries(
23279
23539
  Object.entries(parsed).filter((entry) => typeof entry[1] === "string")
@@ -23289,7 +23549,7 @@ var SkillsService = class _SkillsService {
23289
23549
  manifest[name] = hash;
23290
23550
  try {
23291
23551
  fs11.mkdirSync(this.globalDir(), { recursive: true });
23292
- fs11.writeFileSync(path31.join(this.globalDir(), SEED_MANIFEST), JSON.stringify(manifest, null, 2));
23552
+ fs11.writeFileSync(path32.join(this.globalDir(), SEED_MANIFEST), JSON.stringify(manifest, null, 2));
23293
23553
  } catch (err) {
23294
23554
  console.warn(`Could not record seeded skill ${name}: ${String(err)}`);
23295
23555
  }
@@ -23300,11 +23560,11 @@ var SkillsService = class _SkillsService {
23300
23560
  return this.readSeedManifest()[name] === currentHash || (PRIOR_BUILTIN_HASHES[name] ?? []).includes(currentHash);
23301
23561
  }
23302
23562
  seed(name) {
23303
- const dest = path31.join(this.globalDir(), name);
23563
+ const dest = path32.join(this.globalDir(), name);
23304
23564
  const exists = fs11.existsSync(dest);
23305
23565
  let srcDir;
23306
23566
  try {
23307
- srcDir = path31.join(builtinSkillsDir(), name);
23567
+ srcDir = path32.join(builtinSkillsDir(), name);
23308
23568
  } catch (err) {
23309
23569
  if (!exists) console.warn(`Could not locate built-in skills dir: ${String(err)}`);
23310
23570
  return false;
@@ -23313,9 +23573,9 @@ var SkillsService = class _SkillsService {
23313
23573
  if (!exists) console.warn(`Built-in skill not found in package: ${name}`);
23314
23574
  return false;
23315
23575
  }
23316
- const srcHash = fileHash(path31.join(srcDir, "SKILL.md"));
23576
+ const srcHash = fileHash(path32.join(srcDir, "SKILL.md"));
23317
23577
  if (exists) {
23318
- const currentHash = fileHash(path31.join(dest, "SKILL.md"));
23578
+ const currentHash = fileHash(path32.join(dest, "SKILL.md"));
23319
23579
  if (currentHash !== void 0 && currentHash === srcHash) {
23320
23580
  this.recordSeed(name, currentHash);
23321
23581
  return false;
@@ -23359,7 +23619,7 @@ var SkillsService = class _SkillsService {
23359
23619
  if (entries.length !== 1 || entries[0].name !== "SKILL.md" || !entries[0].isFile()) return false;
23360
23620
  let raw;
23361
23621
  try {
23362
- raw = readSkillText(path31.join(dir, "SKILL.md"));
23622
+ raw = readSkillText(path32.join(dir, "SKILL.md"));
23363
23623
  } catch {
23364
23624
  return false;
23365
23625
  }
@@ -23367,7 +23627,7 @@ var SkillsService = class _SkillsService {
23367
23627
  }
23368
23628
  pruneRetired() {
23369
23629
  for (const name of RETIRED_BUILTIN_SKILL_NAMES) {
23370
- const dest = path31.join(this.globalDir(), name);
23630
+ const dest = path32.join(this.globalDir(), name);
23371
23631
  if (!fs11.existsSync(dest)) continue;
23372
23632
  if (!this.isUnmodifiedRetiredSeed(dest, name)) continue;
23373
23633
  try {
@@ -23505,12 +23765,12 @@ var runGit = execFileWithTimeout(1e4);
23505
23765
  async function committedInHead(files) {
23506
23766
  const byDir = /* @__PURE__ */ new Map();
23507
23767
  for (const file of new Set(files)) {
23508
- const dir = path32.dirname(path32.dirname(file));
23768
+ const dir = path33.dirname(path33.dirname(file));
23509
23769
  byDir.set(dir, [...byDir.get(dir) ?? [], file]);
23510
23770
  }
23511
23771
  const result = /* @__PURE__ */ new Map();
23512
23772
  await Promise.all([...byDir].map(async ([dir, inDir]) => {
23513
- const relative17 = (file) => path32.relative(dir, file).split(path32.sep).join("/");
23773
+ const relative17 = (file) => path33.relative(dir, file).split(path33.sep).join("/");
23514
23774
  try {
23515
23775
  const { stdout } = await runGit("git", ["ls-tree", "--name-only", "HEAD", "--", ...inDir.map(relative17)], { cwd: dir, env: cleanEnv() });
23516
23776
  const listed3 = new Set(stdout.split("\n").map((line) => line.trim()).filter(Boolean));
@@ -23530,9 +23790,9 @@ function plannedSkillLookup(skillsAt, workspaceRoot, layout) {
23530
23790
  searchedDirs: () => lookup2.searchedDirs(),
23531
23791
  listSkills: () => lookup2.listSkills(),
23532
23792
  uncommitted: async (skills) => {
23533
- const checked = skills.filter((skill) => skill.source === "workspace" && !(readInPlace && path32.dirname(path32.dirname(skill.path)) === readInPlace));
23793
+ const checked = skills.filter((skill) => skill.source === "workspace" && !(readInPlace && path33.dirname(path33.dirname(skill.path)) === readInPlace));
23534
23794
  const committed = await committedInHead(checked.map((skill) => skill.path));
23535
- return new Map(checked.filter((skill) => committed.get(skill.path) === false).map((skill) => [skill.path, path32.relative(workspaceRoot, path32.dirname(skill.path)).split(path32.sep).join("/")]));
23795
+ 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("/")]));
23536
23796
  }
23537
23797
  };
23538
23798
  }
@@ -23545,13 +23805,13 @@ function checkPlanSkills(tasks, lookup2) {
23545
23805
  function checkTaskSkillsEdit(task2, skills, lookup2) {
23546
23806
  return check([{ taskId: task2.id, owner: `Task "${task2.title}"`, ...attached(skills) }], lookup2);
23547
23807
  }
23548
- function subtaskEntries(op, spec, path47 = []) {
23808
+ function subtaskEntries(op, spec, path48 = []) {
23549
23809
  const subtasks = spec?.subtasks;
23550
23810
  if (!Array.isArray(subtasks)) return [];
23551
23811
  return subtasks.flatMap((sub, i) => {
23552
23812
  if (typeof sub !== "object" || sub === null) return [];
23553
23813
  const child = sub;
23554
- const at = [...path47, typeof child.title === "string" && child.title.trim() ? `"${child.title}"` : `#${i + 1}`];
23814
+ const at = [...path48, typeof child.title === "string" && child.title.trim() ? `"${child.title}"` : `#${i + 1}`];
23555
23815
  return [{ op, owner: `subtask ${at.join(" > ")}`, ...attached(child.skills) }, ...subtaskEntries(op, child, at)];
23556
23816
  });
23557
23817
  }
@@ -27421,7 +27681,7 @@ var RUN_SETTING_KEYS = Object.keys(RUN_SETTING_CHECKS);
27421
27681
  function getSettingsPath() {
27422
27682
  const override = process.env.ORDEWELL_SETTINGS_PATH;
27423
27683
  if (override && override.trim()) return override.trim();
27424
- return path33.join(globalDataDir(), "settings.json");
27684
+ return path34.join(globalDataDir(), "settings.json");
27425
27685
  }
27426
27686
  function readPlannerModels(raw) {
27427
27687
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
@@ -27501,8 +27761,8 @@ var SettingsService = class {
27501
27761
  * a watch on the old one pointing at nothing.
27502
27762
  */
27503
27763
  watch(listener) {
27504
- const dir = path33.dirname(this.filePath);
27505
- const name = path33.basename(this.filePath);
27764
+ const dir = path34.dirname(this.filePath);
27765
+ const name = path34.basename(this.filePath);
27506
27766
  let seen = this.fileStamp();
27507
27767
  let timer;
27508
27768
  let dead = false;
@@ -27511,7 +27771,7 @@ var SettingsService = class {
27511
27771
  fs12.mkdirSync(dir, { recursive: true });
27512
27772
  watcher = fs12.watch(dir, (_event, changed) => {
27513
27773
  if (dead) return;
27514
- if (changed && changed !== name && changed !== path33.basename(dir)) return;
27774
+ if (changed && changed !== name && changed !== path34.basename(dir)) return;
27515
27775
  clearTimeout(timer);
27516
27776
  timer = setTimeout(() => {
27517
27777
  const stamp2 = this.fileStamp();
@@ -27744,7 +28004,7 @@ var SettingsService = class {
27744
28004
  if (this.unreadable) {
27745
28005
  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.`);
27746
28006
  }
27747
- const dir = path33.dirname(this.filePath);
28007
+ const dir = path34.dirname(this.filePath);
27748
28008
  if (!fs12.existsSync(dir)) {
27749
28009
  fs12.mkdirSync(dir, { recursive: true });
27750
28010
  }
@@ -28188,7 +28448,7 @@ var BaseConfig = class {
28188
28448
  const { model, effort, override = "" } = configured;
28189
28449
  const named3 = isPlannerBackend(configured.setting ?? "") || isPlannerBackend(process.env.AI_PROVIDER ?? "");
28190
28450
  const pickedElsewhere = configured.pickedFor !== void 0 && configured.pickedFor !== provider;
28191
- const kept = configured.pickedFor === provider || !pickedElsewhere && (isApiProvider(provider) || named3 && !isForeignModel(provider, model, this.agentCatalog(provider)));
28451
+ const kept = configured.pickedFor === provider || !pickedElsewhere && (isApiProvider(provider) || named3 && !isForeignModel(provider, model, () => this.agentCatalog(provider)));
28192
28452
  if (kept) return { model: override || model, effort, source: override ? "request" : "configured" };
28193
28453
  const own = pickedElsewhere || named3 ? rememberedOrDefault(provider) : detectedAgentChoice(provider, configured);
28194
28454
  return !override || override === own.model ? own : { model: override, effort: "", source: "request" };
@@ -28197,7 +28457,8 @@ var BaseConfig = class {
28197
28457
  * The model ids a coding agent was last discovered serving, empty while
28198
28458
  * discovery is cold. A host that holds a runner catalog supplies it: it is
28199
28459
  * the only evidence that tells OpenCode's `provider/model` ids from an
28200
- * OpenRouter slug.
28460
+ * OpenRouter slug. Asked only when a named agent's configured model is
28461
+ * neither its alias nor its remembered pick.
28201
28462
  */
28202
28463
  agentCatalog(_provider) {
28203
28464
  return [];
@@ -28222,8 +28483,10 @@ function isVendorModelFor(agent, model) {
28222
28483
  return !discoveryOf(agent)?.providerQualifiedIds && SLUG_SHAPE.test(model);
28223
28484
  }
28224
28485
  function isForeignModel(agent, model, catalog) {
28225
- if (!model || isStableAlias(agent, model) || catalog.includes(model) || rememberedChoice(agent)?.model === model) return false;
28226
- return isVendorModelFor(agent, model) || catalog.length > 0 && SLUG_SHAPE.test(model);
28486
+ if (!model || isStableAlias(agent, model) || rememberedChoice(agent)?.model === model) return false;
28487
+ const listed3 = catalog();
28488
+ if (listed3.includes(model)) return false;
28489
+ return isVendorModelFor(agent, model) || listed3.length > 0 && SLUG_SHAPE.test(model);
28227
28490
  }
28228
28491
  function rememberedChoice(provider) {
28229
28492
  const entry = new SettingsService().getPlannerModel(provider);
@@ -28343,11 +28606,11 @@ function mergeOutcome(result, branch, group, repaired = []) {
28343
28606
  // src/services/taskDiffRead.ts
28344
28607
  var import_crypto13 = require("crypto");
28345
28608
  var fs13 = __toESM(require("fs"));
28346
- var path34 = __toESM(require("path"));
28609
+ var path35 = __toESM(require("path"));
28347
28610
  var import_os3 = require("os");
28348
28611
  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).";
28349
28612
  function allowedPath(file) {
28350
- return file.length > 0 && !path34.isAbsolute(file) && !/^[A-Za-z]:/.test(file) && !file.split(/[\\/]/).some((part) => part === ".." || part === ".git" || part === ".ordewell");
28613
+ return file.length > 0 && !path35.isAbsolute(file) && !/^[A-Za-z]:/.test(file) && !file.split(/[\\/]/).some((part) => part === ".." || part === ".git" || part === ".ordewell");
28351
28614
  }
28352
28615
  function stats(raw) {
28353
28616
  return raw.split("\0").flatMap((row) => {
@@ -28356,8 +28619,8 @@ function stats(raw) {
28356
28619
  });
28357
28620
  }
28358
28621
  function inside(root, target) {
28359
- const rel = path34.relative(root, target);
28360
- return rel === "" || !rel.startsWith(`..${path34.sep}`) && rel !== ".." && !path34.isAbsolute(rel);
28622
+ const rel = path35.relative(root, target);
28623
+ return rel === "" || !rel.startsWith(`..${path35.sep}`) && rel !== ".." && !path35.isAbsolute(rel);
28361
28624
  }
28362
28625
  function unquoted(quoted4) {
28363
28626
  const bytes = [];
@@ -28405,10 +28668,10 @@ function patchFiles(patch) {
28405
28668
  const sections = patch.toString("utf8").split(/(?<![^\n])(?=diff --git )/).filter((section) => section.startsWith("diff --git "));
28406
28669
  const files = /* @__PURE__ */ new Map();
28407
28670
  for (const section of sections) {
28408
- const path47 = sectionPath(section);
28671
+ const path48 = sectionPath(section);
28409
28672
  const counts = sectionStats(section);
28410
- const earlier = files.get(path47);
28411
- files.set(path47, earlier ? { path: path47, section: earlier.section + section, added: summed(earlier.added, counts.added), removed: summed(earlier.removed, counts.removed) } : { path: path47, section, ...counts });
28673
+ const earlier = files.get(path48);
28674
+ files.set(path48, earlier ? { path: path48, section: earlier.section + section, added: summed(earlier.added, counts.added), removed: summed(earlier.removed, counts.removed) } : { path: path48, section, ...counts });
28412
28675
  }
28413
28676
  return [...files.values()];
28414
28677
  }
@@ -28478,11 +28741,11 @@ async function readTaskDiff(invoker, run, taskId, options) {
28478
28741
  files.push({ ...file, repo: repo.path, patch: () => git(reader, cwd, [...diffArgs, "--", `:(top,literal)${prefix}${file.path}`]) });
28479
28742
  }
28480
28743
  if (live3) {
28481
- const workspace = path34.join(entry.worktree, prefix);
28744
+ const workspace = path35.join(entry.worktree, prefix);
28482
28745
  const others = await git(reader, workspace, [...common, "ls-files", "--others", "--exclude-standard", "-z", "--", "."]);
28483
28746
  for (const file of others.split("\0").filter((f) => allowed(f))) {
28484
- const full = path34.join(workspace, file);
28485
- if (!inside(fs13.realpathSync(workspace), fs13.realpathSync(path34.dirname(full)))) continue;
28747
+ const full = path35.join(workspace, file);
28748
+ if (!inside(fs13.realpathSync(workspace), fs13.realpathSync(path35.dirname(full)))) continue;
28486
28749
  const untracked = async (numstat) => {
28487
28750
  const result = await tryGit(reader, workspace, [...common, "diff", "--no-index", "--no-renames", "--no-ext-diff", "--no-textconv", ...numstat ? ["--numstat", "-z"] : [], "--", import_os3.devNull, file]);
28488
28751
  if (!result.ok && result.code !== 1) throw new Error(result.stderr.trim() || "Could not read an untracked file diff.");
@@ -28512,11 +28775,11 @@ ${await file.patch()}`);
28512
28775
  var import_crypto14 = require("crypto");
28513
28776
  var import_child_process9 = require("child_process");
28514
28777
  var fs15 = __toESM(require("fs"));
28515
- var path36 = __toESM(require("path"));
28778
+ var path37 = __toESM(require("path"));
28516
28779
 
28517
28780
  // src/utils/prdStore.ts
28518
28781
  var fs14 = __toESM(require("fs"));
28519
- var path35 = __toESM(require("path"));
28782
+ var path36 = __toESM(require("path"));
28520
28783
  var PRD_BLOCK_RE = /<!--\s*ORDEWELL_PRD_START\s+slug="([^"]+)"\s*-->\s*([\s\S]*?)\s*<!--\s*ORDEWELL_PRD_END\s*-->/;
28521
28784
  function extractPrdBlock(text6) {
28522
28785
  const match = PRD_BLOCK_RE.exec(text6);
@@ -28530,9 +28793,9 @@ function sanitizeSlug(raw) {
28530
28793
  return raw.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
28531
28794
  }
28532
28795
  function savePrdMarkdown(workspace, slug, markdown) {
28533
- const dir = path35.join(workspace, ".scratch", sanitizeSlug(slug));
28796
+ const dir = path36.join(workspace, ".scratch", sanitizeSlug(slug));
28534
28797
  fs14.mkdirSync(dir, { recursive: true });
28535
- const file = path35.join(dir, "PRD.md");
28798
+ const file = path36.join(dir, "PRD.md");
28536
28799
  fs14.writeFileSync(file, markdown.endsWith("\n") ? markdown : markdown + "\n", "utf8");
28537
28800
  return file;
28538
28801
  }
@@ -28564,12 +28827,12 @@ var NESTED_REPO_DEPTH = 2;
28564
28827
  var GITLINK_MODE = "160000";
28565
28828
  var READ_ONLY = ["--no-optional-locks", "-c", "core.fsmonitor=false"];
28566
28829
  function isInside(parent, child) {
28567
- const rel = path36.relative(parent, child);
28568
- return rel !== "" && !rel.startsWith("..") && !path36.isAbsolute(rel);
28830
+ const rel = path37.relative(parent, child);
28831
+ return rel !== "" && !rel.startsWith("..") && !path37.isAbsolute(rel);
28569
28832
  }
28570
28833
  function resolvesAsNamed(base, p) {
28571
28834
  try {
28572
- return fs15.realpathSync(p) === path36.join(fs15.realpathSync(base), path36.relative(base, p));
28835
+ return fs15.realpathSync(p) === path37.join(fs15.realpathSync(base), path37.relative(base, p));
28573
28836
  } catch {
28574
28837
  return false;
28575
28838
  }
@@ -28624,7 +28887,7 @@ function sameDir(a, b) {
28624
28887
  try {
28625
28888
  return fs15.realpathSync.native(p);
28626
28889
  } catch {
28627
- return path36.resolve(p);
28890
+ return path37.resolve(p);
28628
28891
  }
28629
28892
  };
28630
28893
  return real(a) === real(b);
@@ -28646,7 +28909,7 @@ function clearImmutableFlags(platform, dir) {
28646
28909
  (0, import_child_process9.spawnSync)("/usr/bin/chflags", ["-R", "nouchg", dir], { stdio: "ignore" });
28647
28910
  }
28648
28911
  function leftoverLinks(dir) {
28649
- const named3 = listDir(dir).filter((name) => LINKED_ARTIFACTS.has(name) || isEnvFile(name) || isLink(path36.join(dir, name)));
28912
+ const named3 = listDir2(dir).filter((name) => LINKED_ARTIFACTS.has(name) || isEnvFile(name) || isLink(path37.join(dir, name)));
28650
28913
  return [.../* @__PURE__ */ new Set([...named3, ...installDirs(dir)])];
28651
28914
  }
28652
28915
  function isLink(target) {
@@ -28719,7 +28982,7 @@ var GitWorktreeIsolation = class {
28719
28982
  const head = await this.headOf(root);
28720
28983
  const repo = { path: repoPath, head };
28721
28984
  if (scan.toplevel !== void 0) {
28722
- const top = path36.relative(workspaceRoot, scan.toplevel).split(path36.sep).join("/");
28985
+ const top = path37.relative(workspaceRoot, scan.toplevel).split(path37.sep).join("/");
28723
28986
  if (top !== "") repo.top = top;
28724
28987
  }
28725
28988
  if (enabled && (head.kind === "branch" || head.kind === "detached")) {
@@ -28776,9 +29039,9 @@ var GitWorktreeIsolation = class {
28776
29039
  async nestedRepos(workspaceRoot, toplevel) {
28777
29040
  const found = [];
28778
29041
  const walk = (rel, depth) => {
28779
- for (const name of childDirs(path36.join(workspaceRoot, rel))) {
29042
+ for (const name of childDirs(path37.join(workspaceRoot, rel))) {
28780
29043
  const child = rel ? `${rel}/${name}` : name;
28781
- if (hasGitEntry(path36.join(workspaceRoot, child))) found.push(child);
29044
+ if (hasGitEntry(path37.join(workspaceRoot, child))) found.push(child);
28782
29045
  else if (depth < NESTED_REPO_DEPTH) walk(child, depth + 1);
28783
29046
  }
28784
29047
  };
@@ -28796,7 +29059,7 @@ var GitWorktreeIsolation = class {
28796
29059
  const [meta, file] = entry.split(" ");
28797
29060
  if (file !== void 0 && meta.startsWith(`${GITLINK_MODE} `)) paths.add(file);
28798
29061
  }
28799
- const declared = await this.tryGit(workspaceRoot, ["config", "-z", "-f", path36.join(toplevel, ".gitmodules"), "--get-regexp", "^submodule\\..*\\.path$"]);
29062
+ const declared = await this.tryGit(workspaceRoot, ["config", "-z", "-f", path37.join(toplevel, ".gitmodules"), "--get-regexp", "^submodule\\..*\\.path$"]);
28800
29063
  for (const entry of declared.stdout.split("\0")) {
28801
29064
  const value = entry.split("\n")[1];
28802
29065
  if (value) paths.add(value.replace(/\/+$/, ""));
@@ -28854,7 +29117,7 @@ var GitWorktreeIsolation = class {
28854
29117
  async acceptsWorktree(run, repo) {
28855
29118
  const dir = this.integrationDir(run, repo);
28856
29119
  ensureStateDirIgnored(run.workspaceRoot);
28857
- fs15.mkdirSync(path36.dirname(dir), { recursive: true });
29120
+ fs15.mkdirSync(path37.dirname(dir), { recursive: true });
28858
29121
  const added = await this.tryGit(repo.root, ["worktree", "add", "-q", "--no-checkout", dir, repo.integrationBranch]);
28859
29122
  await this.removeWorktreeDir(run, repo, dir, []);
28860
29123
  return added.ok;
@@ -28869,12 +29132,12 @@ var GitWorktreeIsolation = class {
28869
29132
  if (isolated.includes(SELF_REPO)) return [];
28870
29133
  const shared2 = [];
28871
29134
  const walk = (rel) => {
28872
- for (const name of listDir(path36.join(workspaceRoot, rel))) {
29135
+ for (const name of listDir2(path37.join(workspaceRoot, rel))) {
28873
29136
  if (!rel && (name === STATE_DIR || name === ".git")) continue;
28874
29137
  const child = rel ? `${rel}/${name}` : name;
28875
29138
  if (isolated.includes(child)) continue;
28876
29139
  if (isolated.some((p) => p.startsWith(`${child}/`))) walk(child);
28877
- else if (resolves(path36.join(workspaceRoot, child))) shared2.push(child);
29140
+ else if (resolves(path37.join(workspaceRoot, child))) shared2.push(child);
28878
29141
  }
28879
29142
  };
28880
29143
  walk("");
@@ -28914,9 +29177,9 @@ var GitWorktreeIsolation = class {
28914
29177
  try {
28915
29178
  ensureStateDirIgnored(run.workspaceRoot);
28916
29179
  for (const repo of run.repos) {
28917
- const worktree = path36.join(dir, repo.path);
29180
+ const worktree = path37.join(dir, repo.path);
28918
29181
  const tip = (await this.git(repo.root, ["rev-parse", repo.integrationBranch])).trim();
28919
- fs15.mkdirSync(path36.dirname(worktree), { recursive: true });
29182
+ fs15.mkdirSync(path37.dirname(worktree), { recursive: true });
28920
29183
  await this.git(repo.root, ["worktree", "add", "-q", "-B", branch, worktree, tip]);
28921
29184
  record.repos[repo.path] = { worktree, linked: [], baseRef: tip };
28922
29185
  }
@@ -28941,13 +29204,13 @@ var GitWorktreeIsolation = class {
28941
29204
  setupFailed.push({ repo: repo.path, detail: err.detail });
28942
29205
  }
28943
29206
  entry.linked = boot.linked;
28944
- copied.push(...boot.copied.map((name) => path36.posix.join(repo.path, name)));
29207
+ copied.push(...boot.copied.map((name) => path37.posix.join(repo.path, name)));
28945
29208
  }
28946
29209
  for (const rel of run.shared) {
28947
- const source = path36.join(run.workspaceRoot, rel);
28948
- const target = path36.join(cwd, rel);
29210
+ const source = path37.join(run.workspaceRoot, rel);
29211
+ const target = path37.join(cwd, rel);
28949
29212
  if (!resolves(source) || lexists(target)) continue;
28950
- fs15.mkdirSync(path36.dirname(target), { recursive: true });
29213
+ fs15.mkdirSync(path37.dirname(target), { recursive: true });
28951
29214
  if (linkPath(source, target, this.platform) === "copy") copied.push(rel);
28952
29215
  const entry = self ? record.repos[SELF_REPO] : void 0;
28953
29216
  if (entry) entry.linked.push(rel);
@@ -28979,7 +29242,7 @@ var GitWorktreeIsolation = class {
28979
29242
  */
28980
29243
  async clearLeftovers(run, task2, names) {
28981
29244
  const removal = noRemoval();
28982
- const holder = Object.values(run.tasks).find((r) => r.taskId !== task2.id && (r.branch === names.branch || path36.resolve(r.workspace) === path36.resolve(names.dir) || isInside(r.workspace, names.dir)));
29245
+ 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)));
28983
29246
  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`);
28984
29247
  const found = [];
28985
29248
  for (const repo of run.repos) {
@@ -28989,14 +29252,14 @@ var GitWorktreeIsolation = class {
28989
29252
  worktree: /^worktree (.+)$/m.exec(block)?.[1],
28990
29253
  branch: /^branch refs\/heads\/(.+)$/m.exec(block)?.[1]
28991
29254
  }));
28992
- const at = /* @__PURE__ */ new Set([path36.join(names.dir, repo.path)]);
29255
+ const at = /* @__PURE__ */ new Set([path37.join(names.dir, repo.path)]);
28993
29256
  for (const entry of registered) {
28994
- if (entry.worktree && entry.branch === names.branch && isInside(this.runRoot(run), path36.resolve(entry.worktree))) at.add(entry.worktree);
29257
+ if (entry.worktree && entry.branch === names.branch && isInside(this.runRoot(run), path37.resolve(entry.worktree))) at.add(entry.worktree);
28995
29258
  }
28996
- const base = { branch: names.branch, name: path36.basename(names.dir), title: task2.title };
29259
+ const base = { branch: names.branch, name: path37.basename(names.dir), title: task2.title };
28997
29260
  for (const worktree of at) {
28998
29261
  if (!fs15.existsSync(worktree)) continue;
28999
- const linked = leftoverLinks(path36.join(worktree, await this.prefixOf(repo)));
29262
+ const linked = leftoverLinks(path37.join(worktree, await this.prefixOf(repo)));
29000
29263
  found.push({ repo, worktree, target: { ...base, worktree, linked } });
29001
29264
  }
29002
29265
  found.push({ repo, worktree: names.dir, target: { ...base, linked: [] } });
@@ -29089,7 +29352,7 @@ var GitWorktreeIsolation = class {
29089
29352
  */
29090
29353
  async inWorkspacePlace(repo, worktree) {
29091
29354
  const prefix = await this.prefixOf(repo);
29092
- return prefix ? path36.join(worktree, prefix) : worktree;
29355
+ return prefix ? path37.join(worktree, prefix) : worktree;
29093
29356
  }
29094
29357
  async prefixOf(repo) {
29095
29358
  return (await this.git(repo.root, ["rev-parse", "--show-prefix"])).trim();
@@ -29619,15 +29882,15 @@ var GitWorktreeIsolation = class {
29619
29882
  return landingMessage(kind, from, this.deps.plannedWith?.() ?? false);
29620
29883
  }
29621
29884
  integrationDir(run, repo) {
29622
- return path36.join(this.runRoot(run), INTEGRATION_DIR, repo.path);
29885
+ return path37.join(this.runRoot(run), INTEGRATION_DIR, repo.path);
29623
29886
  }
29624
29887
  async ensureIntegrationWorktree(run, repo) {
29625
29888
  return this.admin(run.workspaceRoot, async () => {
29626
29889
  const dir = this.integrationDir(run, repo);
29627
- if (fs15.existsSync(path36.join(dir, ".git"))) return dir;
29890
+ if (fs15.existsSync(path37.join(dir, ".git"))) return dir;
29628
29891
  await this.tryGit(repo.root, ["worktree", "prune"]);
29629
29892
  ensureStateDirIgnored(run.workspaceRoot);
29630
- fs15.mkdirSync(path36.dirname(dir), { recursive: true });
29893
+ fs15.mkdirSync(path37.dirname(dir), { recursive: true });
29631
29894
  await this.git(repo.root, ["worktree", "add", "-q", dir, repo.integrationBranch]);
29632
29895
  return dir;
29633
29896
  });
@@ -29650,7 +29913,7 @@ var GitWorktreeIsolation = class {
29650
29913
  const removal = noRemoval();
29651
29914
  for (const repo of opts.preserve === false || record.readOnly ? [] : run.repos) {
29652
29915
  const entry = record.repos[repo.path];
29653
- const target = { worktree: entry?.worktree, linked: entry?.linked ?? [], branch: record.branch, name: path36.basename(record.workspace), title: record.title, commit: record.commit };
29916
+ const target = { worktree: entry?.worktree, linked: entry?.linked ?? [], branch: record.branch, name: path37.basename(record.workspace), title: record.title, commit: record.commit };
29654
29917
  try {
29655
29918
  const kept = await this.preserve(run, repo, target, opts.intent);
29656
29919
  if (kept) removal.preserved.push({ task: task2, ...kept });
@@ -29731,7 +29994,7 @@ var GitWorktreeIsolation = class {
29731
29994
  return;
29732
29995
  }
29733
29996
  for (const entry of entries) {
29734
- const target = path36.join(dir, entry.name);
29997
+ const target = path37.join(dir, entry.name);
29735
29998
  if (entry.isSymbolicLink()) this.unlinkIfLink(target);
29736
29999
  else if (entry.isDirectory()) this.unlinkLinksUnder(target);
29737
30000
  }
@@ -29753,20 +30016,20 @@ var GitWorktreeIsolation = class {
29753
30016
  return false;
29754
30017
  }
29755
30018
  };
29756
- const owned = new Set(Object.values(run.tasks).map((r) => path36.resolve(r.workspace)));
30019
+ const owned = new Set(Object.values(run.tasks).map((r) => path37.resolve(r.workspace)));
29757
30020
  const root = this.runRoot(run);
29758
30021
  if (fs15.existsSync(root)) {
29759
30022
  for (const entry of fs15.readdirSync(root)) {
29760
- const dir = path36.join(root, entry);
29761
- if (entry === INTEGRATION_DIR || owned.has(path36.resolve(dir))) continue;
30023
+ const dir = path37.join(root, entry);
30024
+ if (entry === INTEGRATION_DIR || owned.has(path37.resolve(dir))) continue;
29762
30025
  let kept = true;
29763
30026
  for (const repo of run.repos) {
29764
- const worktree = path36.join(dir, repo.path);
29765
- const linked = leftoverLinks(path36.join(worktree, await this.prefixOf(repo)));
30027
+ const worktree = path37.join(dir, repo.path);
30028
+ const linked = leftoverLinks(path37.join(worktree, await this.prefixOf(repo)));
29766
30029
  if (!await keep2(repo, { worktree, linked, branch: null, name: entry, title: entry }, worktree)) kept = false;
29767
30030
  }
29768
30031
  if (!kept) continue;
29769
- for (const repo of run.repos) await this.removeWorktreeDir(run, repo, path36.join(dir, repo.path), []);
30032
+ for (const repo of run.repos) await this.removeWorktreeDir(run, repo, path37.join(dir, repo.path), []);
29770
30033
  this.removeTaskWorkspace(run, dir);
29771
30034
  }
29772
30035
  }
@@ -29784,13 +30047,13 @@ var GitWorktreeIsolation = class {
29784
30047
  }
29785
30048
  async removeWorktreeDir(run, repo, dir, linked) {
29786
30049
  for (const name of /* @__PURE__ */ new Set([...linked, ...installDirs(dir)])) {
29787
- const target = path36.join(dir, name);
29788
- if (!resolvesAsNamed(this.runRoot(run), path36.dirname(target))) continue;
30050
+ const target = path37.join(dir, name);
30051
+ if (!resolvesAsNamed(this.runRoot(run), path37.dirname(target))) continue;
29789
30052
  this.unlinkIfLink(target);
29790
30053
  this.unlinkLinksUnder(target);
29791
30054
  }
29792
30055
  for (const name of resolvesAsNamed(this.runRoot(run), dir) ? fs15.readdirSync(dir) : []) {
29793
- if (LINKED_ARTIFACTS.has(name) || isEnvFile(name)) this.unlinkIfLink(path36.join(dir, name));
30056
+ if (LINKED_ARTIFACTS.has(name) || isEnvFile(name)) this.unlinkIfLink(path37.join(dir, name));
29794
30057
  }
29795
30058
  await this.tryGit(repo.root, ["worktree", "remove", "--force", dir]);
29796
30059
  if (fs15.existsSync(dir) && isInside(this.runRoot(run), dir)) {
@@ -29798,7 +30061,7 @@ var GitWorktreeIsolation = class {
29798
30061
  fs15.rmSync(dir, { recursive: true, force: true });
29799
30062
  }
29800
30063
  await this.tryGit(repo.root, ["worktree", "prune"]);
29801
- for (let parent = path36.dirname(dir); isInside(this.runRoot(run), parent); parent = path36.dirname(parent)) this.removeIfEmpty(parent);
30064
+ for (let parent = path37.dirname(dir); isInside(this.runRoot(run), parent); parent = path37.dirname(parent)) this.removeIfEmpty(parent);
29802
30065
  }
29803
30066
  unlinkIfLink(target) {
29804
30067
  try {
@@ -29813,14 +30076,14 @@ var GitWorktreeIsolation = class {
29813
30076
  }
29814
30077
  }
29815
30078
  runRoot(run) {
29816
- return path36.join(run.workspaceRoot, STATE_DIR, "worktrees", run.id);
30079
+ return path37.join(run.workspaceRoot, STATE_DIR, "worktrees", run.id);
29817
30080
  }
29818
30081
  namesFor(run, task2) {
29819
30082
  const slug = sanitizeSlug(task2.title).slice(0, 40).replace(/-+$/, "") || "task";
29820
30083
  let name = `${task2.order}-${slug}`;
29821
30084
  const clash = Object.values(run.tasks).some((r) => r.taskId !== task2.id && r.branch === `ordewell/${run.id}/${name}`);
29822
30085
  if (clash) name = `${name}-${sanitizeSlug(task2.id) || "x"}`;
29823
- return { branch: `ordewell/${run.id}/${name}`, dir: path36.join(this.runRoot(run), name) };
30086
+ return { branch: `ordewell/${run.id}/${name}`, dir: path37.join(this.runRoot(run), name) };
29824
30087
  }
29825
30088
  async unmergedPaths(cwd) {
29826
30089
  const listed3 = await this.tryGit(cwd, ["diff", "--name-only", "-z", "--diff-filter=U"]);
@@ -29834,7 +30097,7 @@ var GitWorktreeIsolation = class {
29834
30097
  async rebaseInProgress(cwd) {
29835
30098
  for (const dir of ["rebase-merge", "rebase-apply"]) {
29836
30099
  const found = await this.tryGit(cwd, ["rev-parse", "--git-path", dir]);
29837
- if (found.ok && fs15.existsSync(path36.resolve(cwd, found.stdout.trim()))) return true;
30100
+ if (found.ok && fs15.existsSync(path37.resolve(cwd, found.stdout.trim()))) return true;
29838
30101
  }
29839
30102
  return false;
29840
30103
  }
@@ -29942,7 +30205,7 @@ function handoffBaseLine(handoff, length) {
29942
30205
  return handoff.repos.map((repo) => `${group ? `${repo.path}: ` : ""}${repoBaseLine(repo, length)}`).join(", ");
29943
30206
  }
29944
30207
  function repoResultLines(handoff) {
29945
- return handoff.repos.map(({ path: path47, landed: landed2 }) => `${path47}: ${landed2.length === 0 ? "nothing to merge" : `${landed2.length} task${landed2.length === 1 ? "" : "s"} landed`}`);
30208
+ return handoff.repos.map(({ path: path48, landed: landed2 }) => `${path48}: ${landed2.length === 0 ? "nothing to merge" : `${landed2.length} task${landed2.length === 1 ? "" : "s"} landed`}`);
29946
30209
  }
29947
30210
 
29948
30211
  // src/interfaces/ILogger.ts
@@ -33769,15 +34032,21 @@ var Landing = class {
33769
34032
  * has landed. Its branch is on the integration branch by then, so it merges
33770
34033
  * clean — and one the resolver did not really bring along conflicts again
33771
34034
  * instead of being taken at its word. Null when there is nothing to land.
34035
+ * The task is named before its landing settles, so the caller can hold
34036
+ * its worktree for as long as the merge is in it; one the caller says is
34037
+ * `leaving` the plan is not landed at all.
33772
34038
  */
33773
- async landResolved(resolverId) {
34039
+ landResolved(resolverId, leaving = () => false) {
33774
34040
  const conflictedId = this.runs.takeResolver(resolverId);
33775
- if (!conflictedId) return null;
34041
+ if (!conflictedId || leaving(conflictedId)) return null;
33776
34042
  const task2 = this.tasks.get(conflictedId);
33777
34043
  if (!task2 || this.runs.current?.tasks[conflictedId]?.status !== "conflict") return null;
34044
+ return { task: task2, outcome: this.landConflicted(task2) };
34045
+ }
34046
+ async landConflicted(task2) {
33778
34047
  const landing2 = await this.runs.integrate(task2);
33779
- if (landing2 !== "merged") return { task: task2, outcome: this.unmerged(task2, landing2) };
33780
- return { task: task2, outcome: { kind: "landed", messages: [{ level: "info", text: `Task "${task2.title}" landed through its conflict resolution.` }] } };
34048
+ if (landing2 !== "merged") return this.unmerged(task2, landing2);
34049
+ return { kind: "landed", messages: [{ level: "info", text: `Task "${task2.title}" landed through its conflict resolution.` }] };
33781
34050
  }
33782
34051
  /** 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. */
33783
34052
  nextRepair(taskId) {
@@ -34122,7 +34391,7 @@ function heldFrom(state) {
34122
34391
  }
34123
34392
 
34124
34393
  // src/services/Attempts.ts
34125
- var path39 = __toESM(require("path"));
34394
+ var path40 = __toESM(require("path"));
34126
34395
 
34127
34396
  // src/services/VerdictEngine.ts
34128
34397
  var IDLE_TIMEOUT_MS = 10 * 6e4;
@@ -34230,6 +34499,18 @@ var VerdictEngine = class {
34230
34499
  this.resumeIdle(taskId);
34231
34500
  this.toolCheckpoints.get(taskId)?.({ kind: "rejected", reason });
34232
34501
  }
34502
+ /**
34503
+ * An attempt's runner is being started, with no session to watch yet. Its
34504
+ * silence counts from here, so one whose start never resolves — stuck
34505
+ * launching, or waiting for its tools to attach — is flagged quiet as well.
34506
+ * {@link watch} starts the count again once the runner is up.
34507
+ */
34508
+ starting(taskId) {
34509
+ const gen = this.bumpGeneration(taskId);
34510
+ this.idlePaused.delete(taskId);
34511
+ this.openApprovals.delete(taskId);
34512
+ this.touchIdle(taskId, gen);
34513
+ }
34233
34514
  /**
34234
34515
  * Attach to a spawned session. The runner's `task_complete` call on the
34235
34516
  * attempt's token is the only completion evidence (ADR-0022, V2); a session
@@ -34240,6 +34521,9 @@ var VerdictEngine = class {
34240
34521
  */
34241
34522
  watch(task2, session) {
34242
34523
  const gen = this.bumpGeneration(task2.id);
34524
+ this.idlePaused.delete(task2.id);
34525
+ this.openApprovals.delete(task2.id);
34526
+ this.touchIdle(task2.id, gen);
34243
34527
  session.onOutput(() => {
34244
34528
  if (this.generations.get(task2.id) === gen) this.touchIdle(task2.id, gen);
34245
34529
  });
@@ -34498,7 +34782,7 @@ function reportedVerdict(report) {
34498
34782
  // src/services/ScriptRunner.ts
34499
34783
  var import_child_process11 = require("child_process");
34500
34784
  var fs16 = __toESM(require("fs"));
34501
- var path37 = __toESM(require("path"));
34785
+ var path38 = __toESM(require("path"));
34502
34786
  var import_events2 = require("events");
34503
34787
  var import_string_decoder2 = require("string_decoder");
34504
34788
 
@@ -34639,7 +34923,7 @@ var ScriptSession = class extends AbstractRunnerSession {
34639
34923
  async launchOf(step) {
34640
34924
  if ("argv" in step) {
34641
34925
  const [named3, ...args] = step.argv;
34642
- const program = this.setup.platform === "win32" && path37.win32.basename(named3) !== named3 && !path37.win32.isAbsolute(named3) ? path37.win32.resolve(this.setup.cwd, named3) : named3;
34926
+ const program = this.setup.platform === "win32" && path38.win32.basename(named3) !== named3 && !path38.win32.isAbsolute(named3) ? path38.win32.resolve(this.setup.cwd, named3) : named3;
34643
34927
  const plan = await planDirectLaunch(program, args, this.setup.launch);
34644
34928
  return { file: plan.file, args: plan.args, shell: false, ...plan.verbatim ? { verbatim: true } : {} };
34645
34929
  }
@@ -34874,8 +35158,8 @@ function passed(status) {
34874
35158
  // src/services/PatchCapture.ts
34875
35159
  var import_crypto15 = require("crypto");
34876
35160
  var fs17 = __toESM(require("fs"));
34877
- var os11 = __toESM(require("os"));
34878
- var path38 = __toESM(require("path"));
35161
+ var os10 = __toESM(require("os"));
35162
+ var path39 = __toESM(require("path"));
34879
35163
 
34880
35164
  // src/services/readTask.ts
34881
35165
  function readTask2(args, readerId, plan, log) {
@@ -35142,7 +35426,7 @@ function linkInPatch(patch) {
35142
35426
  function readOwn(scratch, name, max) {
35143
35427
  let fd;
35144
35428
  try {
35145
- fd = fs17.openSync(path38.join(scratch, name), fs17.constants.O_RDONLY | (fs17.constants.O_NOFOLLOW ?? 0));
35429
+ fd = fs17.openSync(path39.join(scratch, name), fs17.constants.O_RDONLY | (fs17.constants.O_NOFOLLOW ?? 0));
35146
35430
  } catch (err) {
35147
35431
  throw new Refusal(`the capture left no ${name.split(".").pop()} Ordewell could read (${err.code ?? String(err)})`);
35148
35432
  }
@@ -35180,7 +35464,7 @@ async function captureHeld(runs, opts, signal) {
35180
35464
  const [worktree] = runs.worktrees;
35181
35465
  const scratch = runs.scratch;
35182
35466
  const tag = `ordewell-patch-${(0, import_crypto15.randomBytes)(8).toString("hex")}`;
35183
- const stem = path38.join(scratch, tag);
35467
+ const stem = path39.join(scratch, tag);
35184
35468
  const own = (suffix, max = LISTING_BYTES) => readOwn(scratch, `${tag}.${suffix}`, max);
35185
35469
  try {
35186
35470
  const stage = await runs.run(stageScript(stem, opts.linked ?? []), worktree);
@@ -35272,8 +35556,8 @@ async function indexEntries(invoker, worktree) {
35272
35556
  return entries;
35273
35557
  }
35274
35558
  async function trialApply(invoker, worktree, patchFile, scratch) {
35275
- const index = path38.resolve(worktree, (await git(invoker, worktree, [...APPLY_GIT, "rev-parse", "--git-path", "index"])).trim());
35276
- const copy = path38.join(scratch, "index");
35559
+ const index = path39.resolve(worktree, (await git(invoker, worktree, [...APPLY_GIT, "rev-parse", "--git-path", "index"])).trim());
35560
+ const copy = path39.join(scratch, "index");
35277
35561
  fs17.copyFileSync(index, copy);
35278
35562
  const trial = { ...invoker, exec: (file, args, opts) => invoker.exec(file, args, { ...opts, env: { ...opts.env, GIT_INDEX_FILE: copy } }) };
35279
35563
  const tried = await tryGit(trial, worktree, [...APPLY_GIT, "apply", "--cached", "--3way", "--whitespace=nowarn", patchFile]);
@@ -35293,7 +35577,7 @@ function gitRefused(lead, stderr) {
35293
35577
  function onDisk3(worktree, names) {
35294
35578
  return names.map((name) => {
35295
35579
  try {
35296
- const stat = fs17.lstatSync(path38.join(worktree, name), { bigint: true, throwIfNoEntry: false });
35580
+ const stat = fs17.lstatSync(path39.join(worktree, name), { bigint: true, throwIfNoEntry: false });
35297
35581
  return stat ? `${stat.mode} ${stat.ino} ${stat.size} ${stat.mtimeNs} ${stat.ctimeNs}` : "none";
35298
35582
  } catch (err) {
35299
35583
  return err.code ?? "unreadable";
@@ -35333,7 +35617,7 @@ function leavesWorktree(worktree, names, linked, held2, left) {
35333
35617
  if (i < parts.length && held2.get(at)?.some((entry) => entry.mode === SUBMODULE)) {
35334
35618
  return `the patch writes ${name}, under ${at}, a submodule, whose files are another repository's.`;
35335
35619
  }
35336
- const full = path38.join(worktree, ...parts.slice(0, i));
35620
+ const full = path39.join(worktree, ...parts.slice(0, i));
35337
35621
  const stat = fs17.lstatSync(full, { throwIfNoEntry: false });
35338
35622
  if (!stat) break;
35339
35623
  const last = i === parts.length;
@@ -35361,7 +35645,7 @@ async function applyPatch(invoker, worktree, patch, saved, opts = {}) {
35361
35645
  if (link !== void 0) {
35362
35646
  return refused4(`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.`);
35363
35647
  }
35364
- const dir = fs17.mkdtempSync(path38.join(os11.tmpdir(), "ordewell-apply-"));
35648
+ const dir = fs17.mkdtempSync(path39.join(os10.tmpdir(), "ordewell-apply-"));
35365
35649
  try {
35366
35650
  const toplevel = (await git(invoker, worktree, [...APPLY_GIT, "rev-parse", "--show-toplevel"])).trim();
35367
35651
  if (fs17.realpathSync(toplevel) !== fs17.realpathSync(worktree)) return refused4(`${worktree} is not the root of a worktree of its own.`);
@@ -35371,7 +35655,7 @@ async function applyPatch(invoker, worktree, patch, saved, opts = {}) {
35371
35655
  }
35372
35656
  const before = await unmergedPaths(invoker, worktree);
35373
35657
  if (before.length > 0) return refused4(`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.`);
35374
- const file = path38.join(dir, "saved.patch");
35658
+ const file = path39.join(dir, "saved.patch");
35375
35659
  fs17.writeFileSync(file, patch, { flag: "wx", mode: 384 });
35376
35660
  const held2 = await indexEntries(invoker, worktree);
35377
35661
  const named3 = await pathsNamed(invoker, worktree, file);
@@ -35607,6 +35891,10 @@ var Attempts = class {
35607
35891
  receipt(taskId) {
35608
35892
  return this.live.get(taskId)?.receipt ?? null;
35609
35893
  }
35894
+ /** Which spawn of the live attempt's runner waits for Ordewell's tools to connect; null when none does. */
35895
+ attaching(taskId) {
35896
+ return this.live.get(taskId)?.attaching ?? null;
35897
+ }
35610
35898
  /** The kind a task's next attempt starts as, short of a repair or a continue: what is weighed before one exists. */
35611
35899
  nextKind(taskId) {
35612
35900
  const task2 = this.deps.store.get(taskId);
@@ -35647,7 +35935,9 @@ var Attempts = class {
35647
35935
  shell: null,
35648
35936
  web: null,
35649
35937
  patch: null,
35650
- receipt: null
35938
+ receipt: null,
35939
+ attaching: null,
35940
+ spawning: new AbortController()
35651
35941
  };
35652
35942
  this.spawnCounts.set(task2.id, attempt.attempt);
35653
35943
  this.live.set(task2.id, attempt);
@@ -35662,7 +35952,8 @@ var Attempts = class {
35662
35952
  /**
35663
35953
  * The one way an attempt ends. Dropping the record is itself what
35664
35954
  * invalidates a spawn still in flight ({@link run} kills the session it
35665
- * receives for an attempt that is no longer current).
35955
+ * receives for an attempt that is no longer current), and its cancel takes
35956
+ * back a runner still starting meanwhile.
35666
35957
  *
35667
35958
  * A user interruption also bumps the verifier's generation *before* stopping
35668
35959
  * the runner: a runner may fire onExit synchronously from
@@ -35678,6 +35969,7 @@ var Attempts = class {
35678
35969
  const session = attempt?.session ?? null;
35679
35970
  const nativeSessionId = session?.nativeSessionId() ?? null;
35680
35971
  if (attempt) {
35972
+ attempt.spawning.abort();
35681
35973
  store.endAttemptUsage(taskId, attempt.attempt, (/* @__PURE__ */ new Date()).toISOString(), session?.sessionCost?.() ?? void 0, nativeSessionId ?? void 0);
35682
35974
  this.deps.output.detach(taskId);
35683
35975
  attempt.shell?.end();
@@ -35854,12 +36146,14 @@ var Attempts = class {
35854
36146
  this.abandonSpawn(task2, attempt);
35855
36147
  return false;
35856
36148
  }
36149
+ this.verifier.starting(task2.id);
35857
36150
  const session = attempt.script ? await this.spawnScript(task2, attempt, cwd) : await this.spawnRunner(task2, attempt, cwd, worktree ? workspace : void 0, previousWork);
35858
36151
  if (this.live.get(task2.id) !== attempt) {
35859
36152
  this.abandonSpawn(task2, attempt, session);
35860
36153
  return false;
35861
36154
  }
35862
36155
  attempt.phase = "running";
36156
+ attempt.attaching = null;
35863
36157
  attempt.session = session;
35864
36158
  store.setTaskRunnerSessionId(task2.id, void 0);
35865
36159
  store.clearCarriedFrom(task2.id);
@@ -36005,7 +36299,13 @@ var Attempts = class {
36005
36299
  ...web ? { webReads: web } : {},
36006
36300
  applyPatch: (args) => this.applyPatch(task2, attempt, args),
36007
36301
  workspaceRoot: this.deps.workspaceRoot(),
36008
- onNotice: (message) => this.host.tell("warn", `Task "${task2.title}": ${message}`)
36302
+ onNotice: (message) => this.host.tell("warn", `Task "${task2.title}": ${message}`),
36303
+ cancelled: attempt.spawning.signal,
36304
+ onAttaching: (spawn) => {
36305
+ if (this.live.get(task2.id) !== attempt) return;
36306
+ attempt.attaching = spawn;
36307
+ this.host.emit("onTaskChanged");
36308
+ }
36009
36309
  });
36010
36310
  }
36011
36311
  /**
@@ -36058,9 +36358,9 @@ var Attempts = class {
36058
36358
  const repos = Object.values(this.deps.runs.openRecord(taskId)?.repos ?? {});
36059
36359
  if (repos.length !== 1 || cwd === null) return [];
36060
36360
  const [{ worktree, linked }] = repos;
36061
- const prefix = path39.relative(worktree, cwd).split(path39.sep).join("/");
36062
- if (prefix.startsWith("..") || path39.isAbsolute(prefix)) return [];
36063
- return linked.map((name) => (prefix ? `${prefix}/${name}` : name).split(path39.sep).join("/"));
36361
+ const prefix = path40.relative(worktree, cwd).split(path40.sep).join("/");
36362
+ if (prefix.startsWith("..") || path40.isAbsolute(prefix)) return [];
36363
+ return linked.map((name) => (prefix ? `${prefix}/${name}` : name).split(path40.sep).join("/"));
36064
36364
  }
36065
36365
  /**
36066
36366
  * A script task's start (ADR-0035): its body as the plan holds it now, run
@@ -36098,7 +36398,7 @@ var Attempts = class {
36098
36398
  this.deps.notifications.warn(message);
36099
36399
  };
36100
36400
  if (resolved.blockedEnvrc) {
36101
- warn(`blocked:${resolved.blockedEnvrc}`, `direnv has blocked ${resolved.blockedEnvrc}, so tasks start without its variables. Run \`direnv allow\` in ${path39.dirname(resolved.blockedEnvrc)} to use them.`);
36401
+ warn(`blocked:${resolved.blockedEnvrc}`, `direnv has blocked ${resolved.blockedEnvrc}, so tasks start without its variables. Run \`direnv allow\` in ${path40.dirname(resolved.blockedEnvrc)} to use them.`);
36102
36402
  }
36103
36403
  if (resolved.trackedEnvFile) {
36104
36404
  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.`);
@@ -36124,7 +36424,7 @@ var Attempts = class {
36124
36424
  uncommittedSkillFolder(name) {
36125
36425
  const root = this.deps.workspaceRoot();
36126
36426
  const skill = this.deps.skillsAt(this.skillRoots(root)).findSkill(name);
36127
- return skill?.source === "workspace" ? path39.relative(root, path39.dirname(skill.path)).split(path39.sep).join("/") : void 0;
36427
+ return skill?.source === "workspace" ? path40.relative(root, path40.dirname(skill.path)).split(path40.sep).join("/") : void 0;
36128
36428
  }
36129
36429
  /**
36130
36430
  * The roots whose `.ordewell/skills/` an attempt at `cwd` reads: in a repo
@@ -36554,12 +36854,12 @@ function ancestors(task2, store) {
36554
36854
 
36555
36855
  // src/services/sessionLease.ts
36556
36856
  var fs19 = __toESM(require("fs"));
36557
- var path41 = __toESM(require("path"));
36857
+ var path42 = __toESM(require("path"));
36558
36858
  var import_crypto17 = require("crypto");
36559
36859
 
36560
36860
  // src/utils/sessionStore.ts
36561
36861
  var fs18 = __toESM(require("fs"));
36562
- var path40 = __toESM(require("path"));
36862
+ var path41 = __toESM(require("path"));
36563
36863
 
36564
36864
  // src/utils/sessionId.ts
36565
36865
  var import_crypto16 = require("crypto");
@@ -36570,13 +36870,13 @@ function mintSessionId() {
36570
36870
  // src/utils/sessionStore.ts
36571
36871
  var SESSIONS_SUBDIR = "sessions";
36572
36872
  function getSessionsDir(baseDir) {
36573
- return path40.join(getStateDir(baseDir), SESSIONS_SUBDIR);
36873
+ return path41.join(getStateDir(baseDir), SESSIONS_SUBDIR);
36574
36874
  }
36575
36875
  function idSegment(id2) {
36576
36876
  return /^[A-Za-z0-9_-][A-Za-z0-9._-]{0,127}$/.test(id2) ? id2 : `~${Buffer.from(id2, "utf-8").toString("hex")}`;
36577
36877
  }
36578
36878
  function sessionDataDir(sessionId, baseDir) {
36579
- return path40.join(getSessionsDir(baseDir), idSegment(sessionId));
36879
+ return path41.join(getSessionsDir(baseDir), idSegment(sessionId));
36580
36880
  }
36581
36881
  var REJECTION_MESSAGE = "This session was created with an older version of Ordewell. Please re-plan.";
36582
36882
  function isSessionMeta(value) {
@@ -36603,8 +36903,8 @@ function legacySessionFilename(session) {
36603
36903
  return `${filenameStem(session)}_${session.id.slice(0, 8)}.json`;
36604
36904
  }
36605
36905
  function dropLegacyFile(sessionsDir, meta) {
36606
- const legacy = path40.join(sessionsDir, legacySessionFilename(meta));
36607
- if (legacy === path40.join(sessionsDir, sessionFilename(meta)) || !fs18.existsSync(legacy)) return;
36906
+ const legacy = path41.join(sessionsDir, legacySessionFilename(meta));
36907
+ if (legacy === path41.join(sessionsDir, sessionFilename(meta)) || !fs18.existsSync(legacy)) return;
36608
36908
  try {
36609
36909
  const held2 = JSON.parse(fs18.readFileSync(legacy, "utf-8")).meta?.id;
36610
36910
  if (held2 === meta.id) fs18.unlinkSync(legacy);
@@ -36631,7 +36931,7 @@ function saveSession(plan, goal, baseDir, id2) {
36631
36931
  };
36632
36932
  dropRetiredPlanFields(session.plan);
36633
36933
  const filename = sessionFilename(meta);
36634
- const filePath = path40.join(sessionsDir, filename);
36934
+ const filePath = path41.join(sessionsDir, filename);
36635
36935
  writeFileAtomic(filePath, JSON.stringify(session, null, 2));
36636
36936
  dropLegacyFile(sessionsDir, meta);
36637
36937
  return meta;
@@ -36643,7 +36943,7 @@ function listSessions(baseDir, logger = defaultLogger) {
36643
36943
  return files.map((f) => {
36644
36944
  let parsed;
36645
36945
  try {
36646
- parsed = JSON.parse(fs18.readFileSync(path40.join(sessionsDir, f), "utf-8"));
36946
+ parsed = JSON.parse(fs18.readFileSync(path41.join(sessionsDir, f), "utf-8"));
36647
36947
  } catch (err) {
36648
36948
  if (err instanceof SyntaxError) return moveAside(sessionsDir, f, "is not valid JSON", logger);
36649
36949
  logger.warn("sessionStore", `failed to read session file ${f}; skipping`, err);
@@ -36664,7 +36964,7 @@ function listSessions(baseDir, logger = defaultLogger) {
36664
36964
  function moveAside(sessionsDir, file, reason, logger) {
36665
36965
  const aside = `${file}.${Date.now()}.corrupt`;
36666
36966
  try {
36667
- fs18.renameSync(path40.join(sessionsDir, file), path40.join(sessionsDir, aside));
36967
+ fs18.renameSync(path41.join(sessionsDir, file), path41.join(sessionsDir, aside));
36668
36968
  logger.warn("sessionStore", `session file ${file} ${reason}; moved aside to ${aside} rather than overwritten`);
36669
36969
  } catch {
36670
36970
  logger.warn("sessionStore", `failed to read session file ${file}; skipping`);
@@ -36675,7 +36975,7 @@ function findSessionFile(sessionsDir, sessionId, logger) {
36675
36975
  const files = fs18.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
36676
36976
  for (const f of files) {
36677
36977
  try {
36678
- const raw = fs18.readFileSync(path40.join(sessionsDir, f), "utf-8");
36978
+ const raw = fs18.readFileSync(path41.join(sessionsDir, f), "utf-8");
36679
36979
  const session = JSON.parse(raw);
36680
36980
  if (session.meta.id === sessionId) return f;
36681
36981
  } catch (err) {
@@ -36690,7 +36990,7 @@ function loadSession(sessionId, baseDir, logger = defaultLogger) {
36690
36990
  try {
36691
36991
  const file = findSessionFile(sessionsDir, sessionId, logger);
36692
36992
  if (file) {
36693
- const raw = fs18.readFileSync(path40.join(sessionsDir, file), "utf-8");
36993
+ const raw = fs18.readFileSync(path41.join(sessionsDir, file), "utf-8");
36694
36994
  const parsed = JSON.parse(raw);
36695
36995
  if (hasScalarRunner(parsed)) {
36696
36996
  logger.warn("sessionStore", `session file ${file}: ${REJECTION_MESSAGE}`);
@@ -36732,7 +37032,7 @@ function deleteSession(sessionId, baseDir, logger = defaultLogger) {
36732
37032
  try {
36733
37033
  const file = findSessionFile(sessionsDir, sessionId, logger);
36734
37034
  if (file) {
36735
- fs18.unlinkSync(path40.join(sessionsDir, file));
37035
+ fs18.unlinkSync(path41.join(sessionsDir, file));
36736
37036
  return true;
36737
37037
  }
36738
37038
  } catch (err) {
@@ -36785,7 +37085,7 @@ var UNLEASED = {
36785
37085
  };
36786
37086
  var hostsHere = /* @__PURE__ */ new Set();
36787
37087
  function leaseFile(sessionId, workspace) {
36788
- return path41.join(sessionDataDir(sessionId, workspace), LEASE_FILE);
37088
+ return path42.join(sessionDataDir(sessionId, workspace), LEASE_FILE);
36789
37089
  }
36790
37090
  function isRecord9(value) {
36791
37091
  if (typeof value !== "object" || value === null) return false;
@@ -36874,7 +37174,7 @@ var LeaseHost = class {
36874
37174
  */
36875
37175
  acquire(file, workspace, holder) {
36876
37176
  ensureStateDirIgnored(workspace);
36877
- ensurePrivateDir(path41.dirname(file));
37177
+ ensurePrivateDir(path42.dirname(file));
36878
37178
  const outcome = withFileLock(file, () => {
36879
37179
  const current = readRecord(file);
36880
37180
  const taken = current !== null && current.hostId !== this.id ? current : null;
@@ -36929,7 +37229,7 @@ var LeaseHost = class {
36929
37229
  withFileLock(file, () => {
36930
37230
  if (readRecord(file)?.hostId === this.id) fs19.rmSync(file, { force: true });
36931
37231
  }, "release a session lease");
36932
- fs19.rmdirSync(path41.dirname(file));
37232
+ fs19.rmdirSync(path42.dirname(file));
36933
37233
  } catch {
36934
37234
  }
36935
37235
  }
@@ -37122,10 +37422,14 @@ var TaskOrchestrator = class _TaskOrchestrator {
37122
37422
  /** Conflict repairs owed with every slot taken and no scheduler to claim them: a freed slot starts them. */
37123
37423
  owedRepairs = /* @__PURE__ */ new Set();
37124
37424
  /**
37125
- * Tasks Mark complete is landing after it ended their attempt: in progress
37126
- * with no attempt for as long as that takes, and not orphans for it.
37425
+ * Tasks Mark complete is landing after it ended their attempt, or that
37426
+ * their conflict resolver is landing, and that landing: in progress with
37427
+ * no attempt for as long as it takes, and not orphans for it. Both can be
37428
+ * landing one task at once, so each holds it for itself (see {@link holdWhileLanding}).
37127
37429
  */
37128
- completing = /* @__PURE__ */ new Set();
37430
+ completing = /* @__PURE__ */ new Map();
37431
+ /** Tasks {@link releaseTask} is tearing down once their landings have settled: none may begin another. */
37432
+ releasing = /* @__PURE__ */ new Set();
37129
37433
  /** Tasks paused on a usage limit, the run's hold on them, and each runner's wait for its reset (ADR-0018, U1). */
37130
37434
  limitPauses;
37131
37435
  /**
@@ -37261,6 +37565,10 @@ var TaskOrchestrator = class _TaskOrchestrator {
37261
37565
  getCompletionReceipt(taskId) {
37262
37566
  return this.attempts.receipt(taskId);
37263
37567
  }
37568
+ /** Which spawn of a live attempt's runner waits for Ordewell's tools to connect (ADR-0025); null when none does. */
37569
+ getToolsAttaching(taskId) {
37570
+ return this.attempts.attaching(taskId);
37571
+ }
37264
37572
  /** Recent clean output of a task's latest attempt, running or ended; null if it never ran. */
37265
37573
  getLiveOutput(taskId, opts) {
37266
37574
  return this.attempts.liveTail(taskId, opts);
@@ -37425,12 +37733,13 @@ var TaskOrchestrator = class _TaskOrchestrator {
37425
37733
  /**
37426
37734
  * Something can still start that nobody has to ask for again: a start
37427
37735
  * admitted and not yet claimed, a force start queued behind its
37428
- * dependencies, a task that resumes on its own at its usage limit's
37429
- * reset, or a start a dirty checkout parked for Stash and continue to
37430
- * replay. A Stop is owed to it, whether or not anything runs.
37736
+ * dependencies, a conflict repair owed a slot or the end of a start hold,
37737
+ * a task that resumes on its own at its usage limit's reset, or a start a
37738
+ * dirty checkout parked for Stash and continue to replay. A Stop is owed to
37739
+ * it, whether or not anything runs.
37431
37740
  */
37432
37741
  get startsPending() {
37433
- return this.admissions > 0 || this.queuedStarts.size > 0 || this.runs.blocked || this.store.allTasks.some(waitsForReset);
37742
+ return this.admissions > 0 || this.queuedStarts.size > 0 || this.owedRepairs.size > 0 || this.runs.blocked || this.store.allTasks.some(waitsForReset);
37434
37743
  }
37435
37744
  /**
37436
37745
  * The plan's activity as one word for every surface: a start on its way,
@@ -37931,6 +38240,7 @@ var TaskOrchestrator = class _TaskOrchestrator {
37931
38240
  * Work that did not land waits on the user — it never fails the task or
37932
38241
  * blocks its dependents — unless its conflict is owed a repair, which takes
37933
38242
  * the slot the ending attempt freed and never one more than the run allows.
38243
+ * A planner turn's start hold holds it as it holds any new spawn (ADR-0029).
37934
38244
  * The repair follows on from the start whose work it repairs: a Stop since
37935
38245
  * that was admitted withdraws it.
37936
38246
  */
@@ -37938,20 +38248,22 @@ var TaskOrchestrator = class _TaskOrchestrator {
37938
38248
  this.store.markAwaitingUser(task2.id, "conflict");
37939
38249
  this.say(outcome.messages);
37940
38250
  if (outcome.kind !== "repair-needed") return;
37941
- if (this.attempts.size < this.config.maxParallelSessions) {
38251
+ const waitsFor = this.startHolds > 0 ? "the planner's turn ends" : this.slotsFull() ? "a slot is free" : null;
38252
+ if (!waitsFor) {
37942
38253
  const claimed = this.claim(task2.id, admission);
37943
38254
  if ("refused" in claimed) this.tell("warn", `The conflict of task "${task2.title}" is not repaired: ${claimed.refused}`);
37944
38255
  else this.unawaited(claimed.spawned, `Could not start the repair of task "${task2.title}"`);
37945
38256
  } else {
37946
38257
  this.store.markPending(task2.id);
37947
38258
  if (!this.isScheduling) this.owedRepairs.add(task2.id);
37948
- this.tell("info", `Task "${task2.title}" is repaired once a slot is free.`);
38259
+ this.tell("info", `Task "${task2.title}" is repaired once ${waitsFor}.`);
37949
38260
  }
37950
38261
  }
37951
- /** Start the repairs {@link settleUnlanded} left owed, as slots allow. */
38262
+ /** Start the repairs {@link settleUnlanded} left owed, as slots and any start hold allow. */
37952
38263
  startOwedRepairs(admission) {
38264
+ if (this.startHolds > 0) return;
37953
38265
  for (const id2 of [...this.owedRepairs]) {
37954
- if (this.attempts.size >= this.config.maxParallelSessions) return;
38266
+ if (this.slotsFull()) return;
37955
38267
  this.owedRepairs.delete(id2);
37956
38268
  const task2 = this.store.get(id2);
37957
38269
  if (!task2 || task2.status !== "pending" || this.attempts.has(id2) || !this.landing.nextRepair(id2)) continue;
@@ -37961,13 +38273,31 @@ var TaskOrchestrator = class _TaskOrchestrator {
37961
38273
  }
37962
38274
  }
37963
38275
  async landResolved(resolverId, admission) {
37964
- const resolved = await this.landing.landResolved(resolverId);
38276
+ const resolved = this.landing.landResolved(resolverId, (id2) => this.releasing.has(id2));
37965
38277
  if (!resolved) return;
37966
- const { task: task2, outcome } = resolved;
38278
+ const { task: task2 } = resolved;
38279
+ const outcome = await this.holdWhileLanding(task2.id, resolved.outcome);
38280
+ if (this.store.get(task2.id)?.status === "completed") return;
37967
38281
  await this.applyLanding(task2, outcome, admission, () => {
37968
38282
  if (task2.verdict) this.logAndArchive(task2, task2.verdict);
37969
38283
  });
37970
38284
  }
38285
+ /**
38286
+ * Hold a task for as long as `landing` is merging its worktree. Its end lets
38287
+ * go of its own hold only: another landing of the task begun meanwhile
38288
+ * still holds it.
38289
+ */
38290
+ async holdWhileLanding(taskId, landing2) {
38291
+ const holds2 = this.completing.get(taskId) ?? /* @__PURE__ */ new Set();
38292
+ holds2.add(landing2);
38293
+ this.completing.set(taskId, holds2);
38294
+ try {
38295
+ return await landing2;
38296
+ } finally {
38297
+ holds2.delete(landing2);
38298
+ if (holds2.size === 0 && this.completing.get(taskId) === holds2) this.completing.delete(taskId);
38299
+ }
38300
+ }
37971
38301
  getReadyTasks() {
37972
38302
  return this.readiness().ready;
37973
38303
  }
@@ -38075,17 +38405,27 @@ var TaskOrchestrator = class _TaskOrchestrator {
38075
38405
  * as {@link cancelTask} does, but its worktree goes: no task is left to land
38076
38406
  * it into. A spawn still in flight just loses its attempt,
38077
38407
  * which is what makes {@link Attempts} kill the session it is about to
38078
- * receive. The id's cross-attempt bookkeeping goes too — a hold or retry
38079
- * count kept for a task that no longer exists would be inherited by nothing.
38408
+ * receive. Every landing of it in flight — Mark complete's, its resolver's —
38409
+ * settles first, so its worktree is not torn down under a merge, and none
38410
+ * begins while it goes; a repair a landing claims is cancelled as any
38411
+ * running attempt is. The id's cross-attempt bookkeeping
38412
+ * goes too — a hold or retry count kept for a task that no longer exists
38413
+ * would be inherited by nothing.
38080
38414
  */
38081
38415
  async releaseTask(taskId) {
38082
38416
  this.limitPauses.cancel(taskId);
38083
- const phase = this.attempts.phaseOf(taskId);
38084
- if (phase === "running" || phase === "integrating") await this.cancelAttempt(taskId, { keep: false });
38085
- else {
38086
- this.attempts.end(taskId, "release");
38087
- if (this.store.get(taskId)?.status === "in_progress") this.store.markPending(taskId);
38088
- await this.runs.release(taskId, { keep: false });
38417
+ for (let holds2 = this.completing.get(taskId); holds2; holds2 = this.completing.get(taskId)) await Promise.allSettled(holds2);
38418
+ this.releasing.add(taskId);
38419
+ try {
38420
+ const phase = this.attempts.phaseOf(taskId);
38421
+ if (phase === "running" || phase === "integrating") await this.cancelAttempt(taskId, { keep: false });
38422
+ else {
38423
+ this.attempts.end(taskId, "release");
38424
+ if (this.store.get(taskId)?.status === "in_progress") this.store.markPending(taskId);
38425
+ await this.runs.release(taskId, { keep: false });
38426
+ }
38427
+ } finally {
38428
+ this.releasing.delete(taskId);
38089
38429
  }
38090
38430
  this.onHold.delete(taskId);
38091
38431
  this.retryCounts.delete(taskId);
@@ -38094,17 +38434,12 @@ var TaskOrchestrator = class _TaskOrchestrator {
38094
38434
  async markTaskComplete(taskId) {
38095
38435
  const task2 = this.store.get(taskId);
38096
38436
  if (!task2 || task2.status === "completed") return;
38097
- if (this.completing.has(taskId)) return;
38437
+ if (this.completing.has(taskId) || this.releasing.has(taskId)) return;
38098
38438
  const admission = this.admittedNow();
38099
38439
  const ended = this.attempts.end(taskId, "complete");
38100
38440
  const verdict = this.attempts.markComplete(task2);
38101
- this.completing.add(taskId);
38102
- let landing2;
38103
- try {
38104
- landing2 = await (ended?.integration ?? this.landing.landVouched(task2));
38105
- } finally {
38106
- this.completing.delete(taskId);
38107
- }
38441
+ const landed2 = ended?.integration ?? this.landing.landVouched(task2);
38442
+ const landing2 = await this.holdWhileLanding(taskId, landed2);
38108
38443
  this.store.setTaskVerdict(taskId, verdict);
38109
38444
  const handoff = task2.type === "user" ? userTaskHandoff(task2.userSteps) : void 0;
38110
38445
  this.attempts.handOver(taskId, summarizeOutput(verdict.reason, ""), handoff);
@@ -38393,7 +38728,7 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
38393
38728
  if (!await this.openForAdmitted(taskId, origin, resume, admission)) return;
38394
38729
  if (this.attempts.has(taskId)) return;
38395
38730
  this.refuseOpsDuringMerge(taskId, forced);
38396
- if (forced.queued && this.holdsQueued(taskId, forced)) {
38731
+ if (forced.queued && (this.holdsQueued(taskId, forced) || this.slotsFull())) {
38397
38732
  this.queuedStarts.set(taskId, { pastGate: forced.pastGate });
38398
38733
  return;
38399
38734
  }
@@ -38452,8 +38787,9 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
38452
38787
  this.queuedStarts.delete(id2);
38453
38788
  continue;
38454
38789
  }
38790
+ if (this.slotsFull()) continue;
38455
38791
  const waits = this.startWaits(task2, task2.research ? void 0 : { queued: true, pastGate: queued.pastGate });
38456
- if (task2.research && (waits.length > 0 || this.attempts.size >= this.config.maxParallelSessions)) continue;
38792
+ if (task2.research && waits.length > 0) continue;
38457
38793
  if (waits.some((w) => QUEUED_WAITS.has(w.kind))) continue;
38458
38794
  if (this.leftToScheduler(task2) && queued.pastGate.length === 0) {
38459
38795
  this.queuedStarts.delete(id2);
@@ -38482,6 +38818,9 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
38482
38818
  const task2 = this.store.get(taskId);
38483
38819
  return !!task2 && this.startWaits(task2, forced).some((w) => QUEUED_WAITS.has(w.kind) || w.kind === "dependencies" || w.kind === "merge-gate");
38484
38820
  }
38821
+ slotsFull() {
38822
+ return this.attempts.size >= this.config.maxParallelSessions;
38823
+ }
38485
38824
  sayQueuedAtGate(task2, queued, unmerged) {
38486
38825
  const titles = unmerged.map((id2) => `"${this.store.get(id2)?.title ?? id2}"`).join(", ");
38487
38826
  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.`;
@@ -38582,7 +38921,7 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
38582
38921
  */
38583
38922
  async settleRun(after) {
38584
38923
  if (!this.store.isFinished()) this.transition({ type: "reopen" });
38585
- if (this.attempts.size > 0) return;
38924
+ if (this.attempts.size > 0 || this.owedRepairs.size > 0) return;
38586
38925
  if (this.runState.kind === "held") return;
38587
38926
  if (this.runState.kind !== "scheduling") {
38588
38927
  if (after === "tick") {
@@ -38876,21 +39215,45 @@ var Planner = class {
38876
39215
  };
38877
39216
 
38878
39217
  // src/services/ModelResolver.ts
38879
- var ModelResolver = class {
39218
+ var ModelResolver = class _ModelResolver {
38880
39219
  constructor(registry, config, deps = {}) {
38881
39220
  this.registry = registry;
38882
39221
  this.config = config;
38883
- this.discovery = new ModelDiscovery(registry, deps.execImpl, deps.fetchImpl);
39222
+ this.discovery = new ModelDiscovery(registry, deps.execImpl, deps.fetchImpl, void 0, void 0, void 0, deps.env);
38884
39223
  this.fetchImpl = deps.fetchImpl;
39224
+ this.execImpl = deps.execImpl;
39225
+ this.shared = { vendor: { options: null, load: null, errors: {}, generation: 0 }, root: this, views: /* @__PURE__ */ new Map() };
38885
39226
  }
38886
39227
  registry;
38887
39228
  config;
38888
39229
  discovery;
38889
39230
  fetchImpl;
38890
- cachedPickerOptions = null;
38891
- /** A catalog load under way: a second ask, or a warm-up, shares it rather than fetching again. */
38892
- pickerLoad = null;
38893
- lastDiscoveryErrors = {};
39231
+ execImpl;
39232
+ shared;
39233
+ get vendor() {
39234
+ return this.shared.vendor;
39235
+ }
39236
+ /**
39237
+ * This resolver for runners launched with another environment, a
39238
+ * workspace's own: their catalogs are discovered and cached under `env`, as
39239
+ * `CLAUDE_CONFIG_DIR` there can name another account, while the vendor
39240
+ * catalogs stay this resolver's, fetched once. One view per `key`, made on
39241
+ * its first ask: a later ask with the same key gets that view, whatever
39242
+ * `env` it passes.
39243
+ */
39244
+ withRunnerEnv(key, env) {
39245
+ let view = this.shared.views.get(key);
39246
+ if (!view) {
39247
+ view = new _ModelResolver(this.registry, this.config, { fetchImpl: this.fetchImpl, execImpl: this.execImpl, env });
39248
+ view.shared = this.shared;
39249
+ this.shared.views.set(key, view);
39250
+ }
39251
+ return view;
39252
+ }
39253
+ /** Every runner-environment catalog this resolver's family holds: its root's and each view's. */
39254
+ runnerCatalogs() {
39255
+ return [this.shared.root, ...this.shared.views.values()].map((resolver) => resolver.discovery);
39256
+ }
38894
39257
  static builtinOptions() {
38895
39258
  return toOrchestratorOptions({}, ORCHESTRATOR_SHORTCUTS);
38896
39259
  }
@@ -38906,16 +39269,16 @@ var ModelResolver = class {
38906
39269
  return Object.fromEntries(discovered);
38907
39270
  }
38908
39271
  async pickerOptions() {
38909
- if (this.cachedPickerOptions) return this.cachedPickerOptions;
38910
- if (!this.pickerLoad) {
39272
+ if (this.vendor.options) return this.vendor.options;
39273
+ if (!this.vendor.load) {
38911
39274
  const load = this.loadPickerOptions();
38912
- this.pickerLoad = load;
39275
+ this.vendor.load = load;
38913
39276
  void load.finally(() => {
38914
- if (this.pickerLoad === load) this.pickerLoad = null;
39277
+ if (this.vendor.load === load) this.vendor.load = null;
38915
39278
  }).catch(() => {
38916
39279
  });
38917
39280
  }
38918
- return this.pickerLoad;
39281
+ return this.vendor.load;
38919
39282
  }
38920
39283
  /**
38921
39284
  * Start loading the vendor catalogs without waiting for them, unless they
@@ -38923,20 +39286,25 @@ var ModelResolver = class {
38923
39286
  * known yet reads it from them on a later call, never waiting on the fetch.
38924
39287
  */
38925
39288
  warmPickerOptions() {
38926
- if (this.cachedPickerOptions || this.pickerLoad) return;
39289
+ if (this.vendor.options || this.vendor.load) return;
38927
39290
  this.pickerOptions().catch(() => {
38928
39291
  });
38929
39292
  }
38930
39293
  async loadPickerOptions() {
39294
+ const vendor = this.vendor;
39295
+ const generation2 = vendor.generation;
38931
39296
  const { apiKeys, baseUrls } = collectProviderCredentials(this.config);
38932
39297
  const { models, errors } = await fetchAllProviderModels({
38933
39298
  apiKeys,
38934
39299
  baseUrls,
38935
39300
  fetchImpl: this.fetchImpl
38936
39301
  });
38937
- this.lastDiscoveryErrors = errors;
38938
- this.cachedPickerOptions = toOrchestratorOptions(models, ORCHESTRATOR_SHORTCUTS);
38939
- return this.cachedPickerOptions;
39302
+ const options = toOrchestratorOptions(models, ORCHESTRATOR_SHORTCUTS);
39303
+ if (vendor.generation === generation2) {
39304
+ vendor.errors = errors;
39305
+ vendor.options = options;
39306
+ }
39307
+ return options;
38940
39308
  }
38941
39309
  /**
38942
39310
  * Per-provider failures from the most recent `pickerOptions()` fetch, keyed
@@ -38945,7 +39313,7 @@ var ModelResolver = class {
38945
39313
  * key/endpoint is set but whose catalog could not be reached.
38946
39314
  */
38947
39315
  getDiscoveryErrors() {
38948
- return this.lastDiscoveryErrors;
39316
+ return this.vendor.errors;
38949
39317
  }
38950
39318
  async refresh() {
38951
39319
  const options = await this.pickerOptions();
@@ -38956,8 +39324,25 @@ var ModelResolver = class {
38956
39324
  this.config.setProviderModelLists(lists);
38957
39325
  return lists;
38958
39326
  }
39327
+ /** Forget every runner catalog, each workspace's included; the vendor catalogs stay. */
38959
39328
  refreshRunnerModels() {
38960
- this.discovery.clear();
39329
+ for (const discovery of this.runnerCatalogs()) discovery.clear();
39330
+ }
39331
+ /**
39332
+ * The models a pick for `provider` is judged against: a harness planner's
39333
+ * own runner catalog, or the vendor picker catalog (ADR-0009). Discovered
39334
+ * if not held. Never throws: a catalog that cannot be had is empty.
39335
+ */
39336
+ async plannerCatalog(provider) {
39337
+ const runner = runnerForProvider(provider);
39338
+ if (runner) return candidatesOf((await this.modelsForRunners([runner]))[runner] ?? []);
39339
+ return (await this.pickerOptions().catch(() => [])).map((o) => ({ id: o.id }));
39340
+ }
39341
+ /** {@link plannerCatalog} from what is already held, so a cold catalog is empty; never discovers. */
39342
+ cachedPlannerCatalog(provider) {
39343
+ const runner = runnerForProvider(provider);
39344
+ if (runner) return candidatesOf(this.getCachedRunnerModels(runner));
39345
+ return this.getCachedPickerOptions().map((o) => ({ id: o.id }));
38961
39346
  }
38962
39347
  /**
38963
39348
  * Synchronous reads of whatever the last `modelsForRunners`/`pickerOptions`
@@ -38968,7 +39353,7 @@ var ModelResolver = class {
38968
39353
  return this.discovery.getCached(runner) ?? [];
38969
39354
  }
38970
39355
  getCachedPickerOptions() {
38971
- return this.cachedPickerOptions ?? [];
39356
+ return this.vendor.options ?? [];
38972
39357
  }
38973
39358
  /**
38974
39359
  * A model's context window from whatever catalog is already cached (#49),
@@ -38981,7 +39366,7 @@ var ModelResolver = class {
38981
39366
  */
38982
39367
  contextWindowFor(source, model) {
38983
39368
  if (!model) return void 0;
38984
- const window2 = isApiProvider(source) ? this.cachedPickerOptions?.find((o) => o.apiProvider === source && o.id === prefixModelId(source, stripModelPrefix(model, source)))?.contextWindow : this.discovery.getCached(source)?.find((m) => m.modelId === model)?.contextWindow;
39369
+ 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;
38985
39370
  return window2 && window2 > 0 ? window2 : void 0;
38986
39371
  }
38987
39372
  /**
@@ -38991,14 +39376,16 @@ var ModelResolver = class {
38991
39376
  */
38992
39377
  maxOutputTokensFor(source, model) {
38993
39378
  if (!model || !isApiProvider(source)) return void 0;
38994
- const cap = this.cachedPickerOptions?.find((o) => o.apiProvider === source && o.id === prefixModelId(source, stripModelPrefix(model, source)))?.maxOutputTokens;
39379
+ const cap = this.vendor.options?.find((o) => o.apiProvider === source && o.id === prefixModelId(source, stripModelPrefix(model, source)))?.maxOutputTokens;
38995
39380
  return cap && cap > 0 ? cap : void 0;
38996
39381
  }
39382
+ /** Forget every catalog: the vendor ones, and each runner environment's. */
38997
39383
  invalidate() {
38998
- this.discovery.clear();
38999
- this.cachedPickerOptions = null;
39000
- this.pickerLoad = null;
39001
- this.lastDiscoveryErrors = {};
39384
+ this.refreshRunnerModels();
39385
+ this.vendor.generation++;
39386
+ this.vendor.options = null;
39387
+ this.vendor.load = null;
39388
+ this.vendor.errors = {};
39002
39389
  if (!this.fetchImpl) {
39003
39390
  for (const provider of PROVIDER_PRIORITY) {
39004
39391
  const baseUrl = this.config.getProviderBaseUrl(provider);
@@ -39008,6 +39395,9 @@ var ModelResolver = class {
39008
39395
  }
39009
39396
  }
39010
39397
  };
39398
+ function candidatesOf(models) {
39399
+ return models.map((m) => ({ id: m.modelId, variants: m.variants }));
39400
+ }
39011
39401
 
39012
39402
  // src/services/RunnerInstallation.ts
39013
39403
  var import_child_process12 = require("child_process");
@@ -39785,12 +40175,12 @@ function openCodeStorePaths(env, home) {
39785
40175
  return { db: file === ":memory:" ? null : (0, import_path9.resolve)(data, file), auth: (0, import_path9.join)(data, "auth.json") };
39786
40176
  }
39787
40177
  function openCodeGoLoginReader(deps = {}) {
39788
- const homedir12 = deps.homedir ?? import_os6.homedir;
40178
+ const homedir10 = deps.homedir ?? import_os6.homedir;
39789
40179
  let last;
39790
40180
  return {
39791
40181
  async read(env, signal) {
39792
40182
  if (signal.aborted) return { kind: "unreadable" };
39793
- const paths = openCodeStorePaths(env, homedir12());
40183
+ const paths = openCodeStorePaths(env, homedir10());
39794
40184
  const [db, wal, auth] = await Promise.all([
39795
40185
  paths.db ? stamp(paths.db) : null,
39796
40186
  paths.db ? stamp(`${paths.db}-wal`) : null,
@@ -39844,8 +40234,8 @@ function v2Login(db, row) {
39844
40234
  login: { route: "console", owner: digest("console", db, row.id, account, orgID ?? ""), access: value.access, ...orgID ? { orgID } : {}, expires: value.expires }
39845
40235
  };
39846
40236
  }
39847
- async function legacyLogin(path47, env) {
39848
- const content = env.OPENCODE_AUTH_CONTENT ?? await readSmall(path47);
40237
+ async function legacyLogin(path48, env) {
40238
+ const content = env.OPENCODE_AUTH_CONTENT ?? await readSmall(path48);
39849
40239
  if (content === void 0) return env[ENV_KEY] ? { kind: "unsupported" } : { kind: "none" };
39850
40240
  if (content === null) return { kind: "unreadable" };
39851
40241
  const auth = parseJson(content);
@@ -39854,15 +40244,15 @@ async function legacyLogin(path47, env) {
39854
40244
  if (entry === void 0) return env[ENV_KEY] ? { kind: "unsupported" } : { kind: "none" };
39855
40245
  if (!isRecord12(entry) || entry.type !== "api") return { kind: "unsupported" };
39856
40246
  if (typeof entry.key !== "string" || entry.key.length === 0) return { kind: "unreadable" };
39857
- return { kind: "login", login: { route: "key", owner: digest("v1", path47, entry.key), key: entry.key } };
40247
+ return { kind: "login", login: { route: "key", owner: digest("v1", path48, entry.key), key: entry.key } };
39858
40248
  }
39859
- function queryStore(path47, signal, deps) {
40249
+ function queryStore(path48, signal, deps) {
39860
40250
  return new Promise((settle) => {
39861
40251
  let worker;
39862
40252
  try {
39863
40253
  worker = (deps.spawnWorker ?? ((source, options) => new import_worker_threads.Worker(source, options)))(WORKER_SOURCE, {
39864
40254
  eval: true,
39865
- workerData: { path: path47, integrations: GO_INTEGRATIONS, busyMs: BUSY_MS, maxValueBytes: MAX_VALUE_BYTES },
40255
+ workerData: { path: path48, integrations: GO_INTEGRATIONS, busyMs: BUSY_MS, maxValueBytes: MAX_VALUE_BYTES },
39866
40256
  execArgv: deps.workerExecArgv ?? [],
39867
40257
  resourceLimits: { maxOldGenerationSizeMb: 64 }
39868
40258
  });
@@ -39889,19 +40279,19 @@ function queryStore(path47, signal, deps) {
39889
40279
  worker.once("exit", aborted);
39890
40280
  });
39891
40281
  }
39892
- async function stamp(path47) {
40282
+ async function stamp(path48) {
39893
40283
  try {
39894
- const stat = await import_fs14.promises.stat(path47);
40284
+ const stat = await import_fs14.promises.stat(path48);
39895
40285
  return [stat.ino, stat.size, stat.mtimeMs];
39896
40286
  } catch {
39897
40287
  return null;
39898
40288
  }
39899
40289
  }
39900
- async function readSmall(path47) {
40290
+ async function readSmall(path48) {
39901
40291
  try {
39902
- const stat = await import_fs14.promises.stat(path47);
40292
+ const stat = await import_fs14.promises.stat(path48);
39903
40293
  if (!stat.isFile() || stat.size > MAX_AUTH_FILE_BYTES) return null;
39904
- return await import_fs14.promises.readFile(path47, "utf8");
40294
+ return await import_fs14.promises.readFile(path48, "utf8");
39905
40295
  } catch (err) {
39906
40296
  return err.code === "ENOENT" ? void 0 : null;
39907
40297
  }
@@ -41030,20 +41420,20 @@ function createAiService(config, deps) {
41030
41420
  }
41031
41421
 
41032
41422
  // src/services/createSession.ts
41033
- var path44 = __toESM(require("path"));
41423
+ var path45 = __toESM(require("path"));
41034
41424
 
41035
41425
  // src/services/handoffReferences.ts
41036
41426
  var fs21 = __toESM(require("fs"));
41037
- var path42 = __toESM(require("path"));
41427
+ var path43 = __toESM(require("path"));
41038
41428
  var import_child_process15 = require("child_process");
41039
41429
  function resolveHandoffReferences(handoff, workspace) {
41040
41430
  const checked = /* @__PURE__ */ new Map();
41041
41431
  const contained = (candidate) => {
41042
41432
  try {
41043
41433
  const root = fs21.realpathSync(workspace);
41044
- const resolved = fs21.realpathSync(path42.resolve(root, candidate));
41045
- const relative17 = path42.relative(root, resolved);
41046
- return relative17 !== ".." && !relative17.startsWith(`..${path42.sep}`) && !path42.isAbsolute(relative17) ? resolved : void 0;
41434
+ const resolved = fs21.realpathSync(path43.resolve(root, candidate));
41435
+ const relative17 = path43.relative(root, resolved);
41436
+ return relative17 !== ".." && !relative17.startsWith(`..${path43.sep}`) && !path43.isAbsolute(relative17) ? resolved : void 0;
41047
41437
  } catch {
41048
41438
  return void 0;
41049
41439
  }
@@ -41064,7 +41454,7 @@ function resolveHandoffReferences(handoff, workspace) {
41064
41454
  }
41065
41455
  } else if (/^[0-9a-fA-F]{4,64}$/.test(ref.sha)) {
41066
41456
  const repo = contained(ref.repo ?? ".");
41067
- if (repo && fs21.existsSync(path42.join(repo, ".git"))) {
41457
+ if (repo && fs21.existsSync(path43.join(repo, ".git"))) {
41068
41458
  const result2 = (0, import_child_process15.spawnSync)("git", ["--no-optional-locks", "--no-replace-objects", "cat-file", "-t", ref.sha], {
41069
41459
  cwd: repo,
41070
41460
  encoding: "utf8",
@@ -42022,7 +42412,7 @@ function serializeTask(t) {
42022
42412
  ...t.loop && t.loopRound !== void 0 ? { loopRound: t.loopRound } : {}
42023
42413
  };
42024
42414
  }
42025
- function serializeTaskStatus(t, idleSince = null, isolation = null, queued = [], awaitingApproval = 0, mergeGate2 = [], checkpoint = "", waitsForApproval2 = false, holdReason = null, completionReceived = null) {
42415
+ function serializeTaskStatus(t, idleSince = null, isolation = null, queued = [], awaitingApproval = 0, mergeGate2 = [], checkpoint = "", waitsForApproval2 = false, holdReason = null, completionReceived = null, attaching = null) {
42026
42416
  return {
42027
42417
  id: t.id,
42028
42418
  type: t.type,
@@ -42030,6 +42420,7 @@ function serializeTaskStatus(t, idleSince = null, isolation = null, queued = [],
42030
42420
  verdict: t.verdict ? { outcome: t.verdict.outcome, reason: t.verdict.reason, checks: t.verdict.checks || [], ...t.verdict.reported ? { reported: t.verdict.reported } : {} } : null,
42031
42421
  idleSince,
42032
42422
  ...completionReceived ? { completionReceived } : {},
42423
+ ...attaching ? { attaching } : {},
42033
42424
  ...t.attempted ? { attempted: true } : {},
42034
42425
  ...t.attemptUsage ? { attemptUsage: structuredClone(t.attemptUsage) } : {},
42035
42426
  ...isolation ? { isolation } : {},
@@ -42059,7 +42450,7 @@ function surfacePendingTurns(pending) {
42059
42450
  function surfaceTask({ attemptSkills, subtasks, runnerSessionId: _runnerSessionId, ...task2 }) {
42060
42451
  return {
42061
42452
  ...task2,
42062
- ...attemptSkills ? { attemptSkills: attemptSkills.map(({ name, source, path: path47 }) => ({ name, source, path: path47 })) } : {},
42453
+ ...attemptSkills ? { attemptSkills: attemptSkills.map(({ name, source, path: path48 }) => ({ name, source, path: path48 })) } : {},
42063
42454
  subtasks: (subtasks ?? []).map(surfaceTask)
42064
42455
  };
42065
42456
  }
@@ -42479,8 +42870,8 @@ var RunSupervision = class {
42479
42870
 
42480
42871
  // src/services/runView.ts
42481
42872
  var EMPTY_RUN = { tasks: [], runMerge: null, handoff: null, mergeResult: null, activity: IDLE_ACTIVITY };
42482
- function split({ verdict, idleSince, completionReceived, isolation, checkpoint, queued, continuable, awaitingApproval, mergeGate: mergeGate2, forcedPastGate, waitsForApproval: waitsForApproval2, holdReason, handoff, attempted, ...plan }) {
42483
- return { plan, status: { verdict, idleSince, completionReceived, isolation, checkpoint, queued, continuable, awaitingApproval, mergeGate: mergeGate2, forcedPastGate, waitsForApproval: waitsForApproval2, holdReason, handoff, attempted } };
42873
+ function split({ verdict, idleSince, completionReceived, attaching, isolation, checkpoint, queued, continuable, awaitingApproval, mergeGate: mergeGate2, forcedPastGate, waitsForApproval: waitsForApproval2, holdReason, handoff, attempted, ...plan }) {
42874
+ return { plan, status: { verdict, idleSince, completionReceived, attaching, isolation, checkpoint, queued, continuable, awaitingApproval, mergeGate: mergeGate2, forcedPastGate, waitsForApproval: waitsForApproval2, holdReason, handoff, attempted } };
42484
42875
  }
42485
42876
  function refresh(previous, tasks) {
42486
42877
  const held2 = new Map([...walkPlan(previous)].map(({ task: task2 }) => [task2.id, task2]));
@@ -42716,7 +43107,8 @@ var SessionEventRelay = class {
42716
43107
  this.checkpointQuestion(t.id),
42717
43108
  this.waitsForApproval(t.id),
42718
43109
  this.orchestrator.holdReason(t.id),
42719
- this.orchestrator.getCompletionReceipt(t.id)
43110
+ this.orchestrator.getCompletionReceipt(t.id),
43111
+ this.orchestrator.getToolsAttaching(t.id)
42720
43112
  )),
42721
43113
  ...runMerge ? { runMerge } : {},
42722
43114
  activity: this.orchestrator.activity
@@ -42838,10 +43230,10 @@ var SessionEventRelay = class {
42838
43230
 
42839
43231
  // src/utils/taskLogStore.ts
42840
43232
  var fs22 = __toESM(require("fs"));
42841
- var path43 = __toESM(require("path"));
43233
+ var path44 = __toESM(require("path"));
42842
43234
  var EXTENSION = ".jsonl";
42843
43235
  function taskDir({ baseDir, sessionId }, taskId) {
42844
- return path43.join(sessionDataDir(sessionId, baseDir), "tasks", idSegment(taskId));
43236
+ return path44.join(sessionDataDir(sessionId, baseDir), "tasks", idSegment(taskId));
42845
43237
  }
42846
43238
  function listTaskLogAttempts(location, taskId) {
42847
43239
  let names;
@@ -42859,7 +43251,7 @@ function openTaskLog(location, taskId, attempt) {
42859
43251
  const next = (listTaskLogAttempts(location, taskId).pop() ?? 0) + 1;
42860
43252
  let number = attempt !== void 0 && Number.isInteger(attempt) && attempt >= 1 ? attempt : next;
42861
43253
  for (; ; ) {
42862
- const file = path43.join(dir, `${number}${EXTENSION}`);
43254
+ const file = path44.join(dir, `${number}${EXTENSION}`);
42863
43255
  try {
42864
43256
  fs22.writeFileSync(file, "", { flag: "wx" });
42865
43257
  } catch (err) {
@@ -42883,15 +43275,15 @@ function prepareTaskOutputFile(location, taskId, attempt) {
42883
43275
  ensureStateDirIgnored(location.baseDir);
42884
43276
  const dir = taskDir(location, taskId);
42885
43277
  fs22.mkdirSync(dir, { recursive: true });
42886
- return path43.join(dir, `${attempt}.output.txt`);
43278
+ return path44.join(dir, `${attempt}.output.txt`);
42887
43279
  }
42888
43280
  function taskPatchFile(location, taskId, attempt) {
42889
- return path43.join(taskDir(location, taskId), `${attempt}.patch`);
43281
+ return path44.join(taskDir(location, taskId), `${attempt}.patch`);
42890
43282
  }
42891
43283
  function saveTaskPatch(location, taskId, attempt, patch) {
42892
43284
  ensureStateDirIgnored(location.baseDir);
42893
43285
  const file = taskPatchFile(location, taskId, attempt);
42894
- fs22.mkdirSync(path43.dirname(file), { recursive: true });
43286
+ fs22.mkdirSync(path44.dirname(file), { recursive: true });
42895
43287
  const partial = `${file}.${process.pid}-${Date.now()}.partial`;
42896
43288
  fs22.writeFileSync(partial, patch, { flag: "wx" });
42897
43289
  fs22.renameSync(partial, file);
@@ -42912,7 +43304,7 @@ function readTaskLog(location, taskId, attempt) {
42912
43304
  if (!Number.isInteger(attempt) || attempt < 1) return [];
42913
43305
  let raw;
42914
43306
  try {
42915
- raw = fs22.readFileSync(path43.join(taskDir(location, taskId), `${attempt}${EXTENSION}`), "utf-8");
43307
+ raw = fs22.readFileSync(path44.join(taskDir(location, taskId), `${attempt}${EXTENSION}`), "utf-8");
42916
43308
  } catch {
42917
43309
  return [];
42918
43310
  }
@@ -44974,7 +45366,7 @@ function createSession(deps) {
44974
45366
  ...deps.openTaskLog ? {} : {
44975
45367
  keepPatch: (taskId, attempt, patch) => {
44976
45368
  const location = session.taskLogLocation;
44977
- return path44.relative(location.baseDir, saveTaskPatch(location, taskId, attempt, patch)).split(path44.sep).join("/");
45369
+ return path45.relative(location.baseDir, saveTaskPatch(location, taskId, attempt, patch)).split(path45.sep).join("/");
44978
45370
  }
44979
45371
  },
44980
45372
  recordHandoff: (sessionId, taskId, handoff) => taskLogs.note(sessionId, taskId, { type: "handoff", handoff }),
@@ -47842,6 +48234,7 @@ function taskRowView(task2, context = {}) {
47842
48234
  resumes: resumeLabel(task2, context.now),
47843
48235
  approvals: approvalLabel(task2.awaitingApproval),
47844
48236
  receipt: receiptLabel(task2),
48237
+ attaching: attachingLabel(task2),
47845
48238
  waitsForApproval: task2.waitsForApproval && NOT_STARTED.has(task2.status) ? WAITS_FOR_APPROVAL_LABEL : null,
47846
48239
  holdReason: task2.holdReason?.trim() || null,
47847
48240
  research: task2.type === "ai" && task2.research === true,
@@ -48155,6 +48548,7 @@ var StructuredSession = class extends AbstractRunnerSession {
48155
48548
  */
48156
48549
  async startAttached(opts) {
48157
48550
  const { server: server4, scope, shell, savePatch, webReads } = this.launch.tools;
48551
+ this.launch.onAttaching?.(1);
48158
48552
  const spawned = await spawnAttached({
48159
48553
  runner: this.launch.runner,
48160
48554
  create: () => this.launch.createAdapter(this.launch.runner, this.launch.deps),
@@ -48191,7 +48585,10 @@ var StructuredSession = class extends AbstractRunnerSession {
48191
48585
  },
48192
48586
  cancelled: this.ended.signal,
48193
48587
  startupExit: () => this.startupExit,
48194
- onRespawn: (failure2) => this.launch.onNotice?.(`${this.launch.runner} started without Ordewell's tools (${failure2}); respawning once.`),
48588
+ onRespawn: (failure2) => {
48589
+ this.launch.onNotice?.(`${this.launch.runner} started without Ordewell's tools (${failure2}); respawning once.`);
48590
+ this.launch.onAttaching?.(2);
48591
+ },
48195
48592
  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}.`
48196
48593
  });
48197
48594
  if (!spawned) return;
@@ -48774,16 +49171,22 @@ var StructuredRunner = class extends AbstractRunner {
48774
49171
  checkHandoff: opts.checkHandoff ?? ((completion) => checkHandoff(completion, opts.taskId, [])),
48775
49172
  ...opts.readTask ? { readTask: opts.readTask } : {},
48776
49173
  ...opts.applyPatch ? { applyPatch: opts.applyPatch } : {},
48777
- onNotice: opts.onNotice
49174
+ onNotice: opts.onNotice,
49175
+ onAttaching: opts.onAttaching
48778
49176
  });
48779
49177
  console.error(`[structured] Starting ${opts.runner} [${opts.mode ?? "default"}] (${opts.modelId || "default"}) for task ${opts.taskId.slice(0, 8)}`);
48780
49178
  this.registerSession(id2, session);
49179
+ const cancel = () => void session.stop();
49180
+ opts.cancelled?.addEventListener("abort", cancel, { once: true });
48781
49181
  try {
49182
+ if (opts.cancelled?.aborted) cancel();
48782
49183
  await session.start(opts.prompt);
48783
49184
  } catch (err) {
48784
49185
  session.kill();
48785
49186
  this.sessions.delete(id2);
48786
49187
  throw err;
49188
+ } finally {
49189
+ opts.cancelled?.removeEventListener("abort", cancel);
48787
49190
  }
48788
49191
  return session;
48789
49192
  }
@@ -48857,7 +49260,7 @@ var RunnerRegistry = class {
48857
49260
 
48858
49261
  // src/plugins/removedPluginNotice.ts
48859
49262
  var fs23 = __toESM(require("fs"));
48860
- var path45 = __toESM(require("path"));
49263
+ var path46 = __toESM(require("path"));
48861
49264
  function isManifestFile(file) {
48862
49265
  try {
48863
49266
  return fs23.statSync(file).isFile();
@@ -48866,10 +49269,10 @@ function isManifestFile(file) {
48866
49269
  }
48867
49270
  }
48868
49271
  function removedPluginNotice(dataDir = globalDataDir()) {
48869
- const directory = path45.join(dataDir, "plugins");
49272
+ const directory = path46.join(dataDir, "plugins");
48870
49273
  try {
48871
- const hasManifest = isManifestFile(path45.join(directory, "manifest.json")) || fs23.readdirSync(directory, { withFileTypes: true }).some(
48872
- (entry) => (entry.isDirectory() || entry.isSymbolicLink()) && isManifestFile(path45.join(directory, entry.name, "manifest.json"))
49274
+ const hasManifest = isManifestFile(path46.join(directory, "manifest.json")) || fs23.readdirSync(directory, { withFileTypes: true }).some(
49275
+ (entry) => (entry.isDirectory() || entry.isSymbolicLink()) && isManifestFile(path46.join(directory, entry.name, "manifest.json"))
48873
49276
  );
48874
49277
  if (hasManifest) return "Plugin runners were removed; manifests in ~/.ordewell/plugins/ are ignored. See the CHANGELOG.";
48875
49278
  } catch {
@@ -48880,15 +49283,15 @@ function removedPluginNotice(dataDir = globalDataDir()) {
48880
49283
 
48881
49284
  // src/utils/stateStore.ts
48882
49285
  var fs24 = __toESM(require("fs"));
48883
- var path46 = __toESM(require("path"));
49286
+ var path47 = __toESM(require("path"));
48884
49287
  var STATE_FILE2 = "state.json";
48885
49288
  function getStatePath(baseDir) {
48886
- return path46.join(getStateDir(baseDir), STATE_FILE2);
49289
+ return path47.join(getStateDir(baseDir), STATE_FILE2);
48887
49290
  }
48888
49291
  var SESSION_ID_KEY = "sessionId";
48889
49292
  function saveState(plan, baseDir, sessionId) {
48890
49293
  const statePath = getStatePath(baseDir ?? process.cwd());
48891
- ensureDir(path46.dirname(statePath));
49294
+ ensureDir(path47.dirname(statePath));
48892
49295
  plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
48893
49296
  const file = sessionId ? { ...plan, [SESSION_ID_KEY]: sessionId } : plan;
48894
49297
  dropRetiredPlanFields(file);
@@ -49175,6 +49578,7 @@ var PlannerModelMemory = class {
49175
49578
  assignedRunnerOf,
49176
49579
  assignmentIssue,
49177
49580
  assignmentIssueText,
49581
+ attachingLabel,
49178
49582
  augmentedPath,
49179
49583
  automergeSummary,
49180
49584
  autonomyLevelLabel,