@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.mjs CHANGED
@@ -47,6 +47,7 @@ import {
47
47
  assignmentConsequence,
48
48
  assignmentIssue,
49
49
  assignmentIssueText,
50
+ attachingLabel,
50
51
  automergeSummary,
51
52
  autorunSummary,
52
53
  awaitingLabel,
@@ -231,7 +232,7 @@ import {
231
232
  withPatch,
232
233
  withoutSealedMark,
233
234
  worstSeverity
234
- } from "./chunk-4QXOWEGC.mjs";
235
+ } from "./chunk-7WNLN63J.mjs";
235
236
  import {
236
237
  DEFAULT_LOOP_ROUNDS,
237
238
  DEFAULT_SCRIPT_TIMEOUT_MS,
@@ -918,9 +919,9 @@ function literalCdTarget(seg) {
918
919
  return target === "" || target.startsWith("-") || target.startsWith("~") || /[*?[]/.test(target) ? void 0 : target;
919
920
  }
920
921
  function followCd(segments) {
921
- if (segments.some((s) => s.joinedBy === "other" || SHELL_STATE_COMMANDS.has(s.binary))) return;
922
+ if (segments.some((s) => s.joinedBy === "other" || movesShell(s))) return;
922
923
  const inPipeline = (i) => segments[i].joinedBy === "pipe" || segments[i + 1]?.joinedBy === "pipe";
923
- if (segments.some((s, i) => s.binary === "cd" && !inPipeline(i) && literalCdTarget(s) === void 0)) return;
924
+ if (segments.some((s, i) => s.binary.toLowerCase() === "cd" && !inPipeline(i) && literalCdTarget(s) === void 0)) return;
924
925
  let chain = [];
925
926
  segments.forEach((seg, i) => {
926
927
  seg.cwd = chain;
@@ -929,7 +930,7 @@ function followCd(segments) {
929
930
  });
930
931
  }
931
932
  function markShellState(segments, bareSegment) {
932
- if (!bareSegment && !segments.some((s) => SHELL_STATE_COMMANDS.has(s.binary))) return;
933
+ if (!bareSegment && !segments.some(movesShell)) return;
933
934
  for (const seg of segments) if (seg.binary === "cd") seg.cdSteered = true;
934
935
  }
935
936
  var SHELL_STATE_COMMANDS = /* @__PURE__ */ new Set([
@@ -948,6 +949,7 @@ var SHELL_STATE_COMMANDS = /* @__PURE__ */ new Set([
948
949
  "exec",
949
950
  "pushd",
950
951
  "popd",
952
+ "chdir",
951
953
  "alias",
952
954
  "unalias",
953
955
  "builtin",
@@ -956,6 +958,9 @@ var SHELL_STATE_COMMANDS = /* @__PURE__ */ new Set([
956
958
  "hash",
957
959
  "trap"
958
960
  ]);
961
+ function movesShell(seg) {
962
+ return SHELL_STATE_COMMANDS.has(seg.binary.toLowerCase());
963
+ }
959
964
 
960
965
  // src/services/commandRunners.ts
961
966
  var HELP_FLAGS = ["--help", "--version"];
@@ -1811,8 +1816,9 @@ var FLAG_POLICY = {
1811
1816
  sort: {
1812
1817
  cluster: true,
1813
1818
  // Absent on purpose: `-o`/`--output` writes a file with no redirect for the
1814
- // redirect check to see, and `--compress-program` names a program that sort
1815
- // runs over its own temporary files — a permitted binary as an interpreter.
1819
+ // redirect check to see, `-T`/`--temporary-directory` names where sort writes
1820
+ // its run files, and `--compress-program` names a program that sort runs
1821
+ // over its own temporary files — a permitted binary as an interpreter.
1816
1822
  booleans: [
1817
1823
  "-b",
1818
1824
  "-c",
@@ -1851,11 +1857,9 @@ var FLAG_POLICY = {
1851
1857
  "-k",
1852
1858
  "-t",
1853
1859
  "-S",
1854
- "-T",
1855
1860
  "--key",
1856
1861
  "--field-separator",
1857
1862
  "--buffer-size",
1858
- "--temporary-directory",
1859
1863
  "--random-source",
1860
1864
  "--batch-size",
1861
1865
  "--parallel",
@@ -2482,23 +2486,28 @@ function scanFlags(seg) {
2482
2486
  const booleans = /* @__PURE__ */ new Set([...spec.booleans ?? [], ...UNIVERSAL_FLAGS]);
2483
2487
  const values = new Set(spec.values ?? []);
2484
2488
  const operands = [];
2489
+ const operandAt = [];
2490
+ const operand = (at) => {
2491
+ operands.push(seg.args[at]);
2492
+ operandAt.push(at);
2493
+ };
2485
2494
  let i = 0;
2486
2495
  while (i < seg.args.length) {
2487
2496
  const token = seg.args[i];
2488
2497
  if (token === "--" && !spec.expressionArgs) {
2489
- operands.push(...seg.args.slice(i + 1));
2498
+ for (let j = i + 1; j < seg.args.length; j++) operand(j);
2490
2499
  break;
2491
2500
  }
2492
2501
  if (!/^-./.test(token)) {
2493
- operands.push(token);
2502
+ operand(i);
2494
2503
  i++;
2495
2504
  continue;
2496
2505
  }
2497
2506
  const shape = matchFlag(spec, booleans, values, token);
2498
- if (shape === void 0) return { unknown: flagLabel(token), operands };
2507
+ if (shape === void 0) return { unknown: flagLabel(token), operands, operandAt };
2499
2508
  i += shape === "value" && takesNextToken(seg.args[i + 1]) ? 2 : 1;
2500
2509
  }
2501
- return { operands };
2510
+ return { operands, operandAt };
2502
2511
  }
2503
2512
  function subcommandOf(seg) {
2504
2513
  const scan = scanFlags(seg);
@@ -2651,6 +2660,8 @@ function sedRefusalFor(binary, scripts) {
2651
2660
  return `The "${binary}" script runs a shell command (the "e" command or the "e" flag of "s"), which this classifier cannot inspect. ${INSPECT_OR_TASK}`;
2652
2661
  case "write":
2653
2662
  return `The "${binary}" script writes a file (the "w"/"W" command or the "w" flag of "s"). You are a read-only planner \u2014 print the output instead, or describe the write as a task.`;
2663
+ case "read":
2664
+ return `The "${binary}" script reads a file it names itself (the "r"/"R" command), which path confinement cannot see. Read that file directly instead.`;
2654
2665
  default:
2655
2666
  return `This classifier could not read the "${binary}" script well enough to rule out a shell command or a file write in it. Rewrite it with plain print, delete and substitute commands, or describe the work as a task.`;
2656
2667
  }
@@ -2771,6 +2782,7 @@ function sedScriptHazard(script) {
2771
2782
  if (cmd === void 0) return "unreadable";
2772
2783
  if (cmd === "e") return "exec";
2773
2784
  if (cmd === "w" || cmd === "W") return "write";
2785
+ if (cmd === "r" || cmd === "R") return "read";
2774
2786
  if (cmd === "{" || cmd === "}") continue;
2775
2787
  if (cmd === "#") {
2776
2788
  while (i < n && script[i] !== "\n") i++;
@@ -2792,7 +2804,7 @@ function sedScriptHazard(script) {
2792
2804
  if (!commandEnds()) return "unreadable";
2793
2805
  continue;
2794
2806
  }
2795
- if ("aicrR".includes(cmd)) {
2807
+ if ("aic".includes(cmd)) {
2796
2808
  while (i < n && script[i] !== "\n") i += script[i] === "\\" ? 2 : 1;
2797
2809
  continue;
2798
2810
  }
@@ -2892,6 +2904,12 @@ function awkRefusalFor(binary, programs) {
2892
2904
  return `The "${binary}" program pipes to or from a command ("|"), which runs it through a shell this classifier cannot inspect. ${INSPECT_OR_TASK}`;
2893
2905
  case "write":
2894
2906
  return `The "${binary}" program redirects its output to a file (">"). You are a read-only planner \u2014 print to standard output instead, or describe the write as a task.`;
2907
+ case "read":
2908
+ return `The "${binary}" program reads a file it names itself ("getline <" or an "ARGV" entry), which path confinement cannot see. Name the file as an operand instead.`;
2909
+ case "environment":
2910
+ return `The "${binary}" program reads the environment ("ENVIRON"), which holds credentials. Run printenv for the one variable you need, which asks first.`;
2911
+ case "symbol-table":
2912
+ return `The "${binary}" program uses "SYMTAB", which gawk reads as every global by name, "ENVIRON" and "ARGV" included. Name the variable you need directly.`;
2895
2913
  case "extension":
2896
2914
  return `The "${binary}" program uses an "@" form, which loads an extension, includes a source file or calls a function by name \u2014 none of which this classifier can inspect. ${INSPECT_OR_TASK}`;
2897
2915
  default:
@@ -2933,6 +2951,7 @@ function awkProgramHazard(program) {
2933
2951
  let slash = "regex";
2934
2952
  let depth = 0;
2935
2953
  let printDepth;
2954
+ let getlineDepth;
2936
2955
  let continues = false;
2937
2956
  const readRegex = () => {
2938
2957
  i++;
@@ -2999,7 +3018,10 @@ function awkProgramHazard(program) {
2999
3018
  continue;
3000
3019
  }
3001
3020
  if (c === "\n" || c === ";") {
3002
- if (c === ";" || !continues) printDepth = void 0;
3021
+ if (c === ";" || !continues) {
3022
+ printDepth = void 0;
3023
+ getlineDepth = void 0;
3024
+ }
3003
3025
  slash = "regex";
3004
3026
  i++;
3005
3027
  continue;
@@ -3050,6 +3072,12 @@ function awkProgramHazard(program) {
3050
3072
  slash = "unclear";
3051
3073
  continue;
3052
3074
  }
3075
+ if (c === "<") {
3076
+ if (getlineDepth !== void 0 && depth >= getlineDepth) return "read";
3077
+ i++;
3078
+ slash = "unclear";
3079
+ continue;
3080
+ }
3053
3081
  if (c === "(" || c === "[") {
3054
3082
  depth++;
3055
3083
  i++;
@@ -3058,12 +3086,16 @@ function awkProgramHazard(program) {
3058
3086
  }
3059
3087
  if (c === ")" || c === "]") {
3060
3088
  depth--;
3089
+ if (getlineDepth !== void 0 && depth < getlineDepth) getlineDepth = void 0;
3061
3090
  i++;
3062
3091
  slash = "divide";
3063
3092
  continue;
3064
3093
  }
3065
3094
  if (c === "{" || c === "}") {
3066
- if (c === "}") printDepth = void 0;
3095
+ if (c === "}") {
3096
+ printDepth = void 0;
3097
+ getlineDepth = void 0;
3098
+ }
3067
3099
  i++;
3068
3100
  slash = "regex";
3069
3101
  continue;
@@ -3084,6 +3116,10 @@ function awkProgramHazard(program) {
3084
3116
  while (i < n && /[A-Za-z0-9_.]/.test(program[i])) i++;
3085
3117
  const word = program.slice(start, i);
3086
3118
  if (word === "system") return "exec";
3119
+ if (word === "ENVIRON") return "environment";
3120
+ if (word === "ARGV") return "read";
3121
+ if (word === "SYMTAB") return "symbol-table";
3122
+ if (word === "getline") getlineDepth = depth;
3087
3123
  if (word === "print" || word === "printf") printDepth = depth;
3088
3124
  slash = AWK_KEYWORDS.includes(word) ? "unclear" : "divide";
3089
3125
  continue;
@@ -3970,6 +4006,36 @@ var GIT_READONLY_SUBCOMMANDS = [
3970
4006
  "whatchanged",
3971
4007
  "cat-file"
3972
4008
  ];
4009
+ var onlyAction = (...reads) => (after) => after[0] === void 0 || reads.includes(after[0]);
4010
+ var GIT_PROMPTABLE = {
4011
+ "ls-remote": () => true,
4012
+ "for-each-ref": () => true,
4013
+ "show-ref": () => true,
4014
+ "name-rev": () => true,
4015
+ "merge-base": () => true,
4016
+ "count-objects": () => true,
4017
+ "cherry": () => true,
4018
+ "range-diff": () => true,
4019
+ "diff-tree": () => true,
4020
+ "diff-files": () => true,
4021
+ "diff-index": () => true,
4022
+ "show-branch": () => true,
4023
+ "annotate": () => true,
4024
+ "check-ignore": () => true,
4025
+ "check-attr": () => true,
4026
+ "check-ref-format": () => true,
4027
+ "check-mailmap": () => true,
4028
+ "var": () => true,
4029
+ "version": () => true,
4030
+ "worktree": onlyAction("list"),
4031
+ "notes": onlyAction("list", "show"),
4032
+ "bisect": onlyAction("log"),
4033
+ "sparse-checkout": onlyAction("list"),
4034
+ "reflog": onlyAction("show", "exists"),
4035
+ "symbolic-ref": (after) => after.filter((a) => !a.startsWith("-")).length <= 1,
4036
+ // `-w` writes the object; git's shared flag set keeps it for `git diff -w`.
4037
+ "hash-object": (after) => !after.some((a) => /^-[^-]*w/.test(a))
4038
+ };
3973
4039
  var REFUSED_COMMANDS = [
3974
4040
  "rm",
3975
4041
  "rmdir",
@@ -4061,7 +4127,28 @@ var REFUSED_SUBCOMMANDS = {
4061
4127
  "clone",
4062
4128
  "fetch",
4063
4129
  "pull",
4064
- "submodule"
4130
+ "submodule",
4131
+ "add",
4132
+ "rm",
4133
+ "mv",
4134
+ "format-patch",
4135
+ "replace",
4136
+ "mergetool",
4137
+ "repack",
4138
+ "update-index",
4139
+ "read-tree",
4140
+ "checkout-index",
4141
+ "merge-file",
4142
+ "bundle",
4143
+ "commit-tree",
4144
+ "write-tree",
4145
+ "mktree",
4146
+ "mktag",
4147
+ "unpack-objects",
4148
+ "fast-import",
4149
+ "pack-refs",
4150
+ "prune-packed",
4151
+ "maintenance"
4065
4152
  ],
4066
4153
  npm: ["publish", "unpublish", "deprecate", "owner", "access", "token", "login", "logout"],
4067
4154
  yarn: ["publish", "npm"],
@@ -4271,7 +4358,7 @@ function pathRefs(command, opts = {}) {
4271
4358
  return lexAll(command.trim(), dialect, opts.cdpathSet).segments.flatMap((seg) => {
4272
4359
  const patterns = patternArgs(seg);
4273
4360
  const cwd = seg.cwd ?? [];
4274
- return [...seg.args.filter((_, i) => !patterns.has(i)), ...seg.inputs].flatMap(pathIn).map((path46) => ({ path: path46, cwd }));
4361
+ return [...seg.args.filter((_, i) => !patterns.has(i)), ...seg.inputs].flatMap(pathIn).map((path47) => ({ path: path47, cwd }));
4275
4362
  });
4276
4363
  }
4277
4364
  function excludedBy(command, allow, opts = {}) {
@@ -4378,7 +4465,9 @@ function patternArgs(seg) {
4378
4465
  var SCOPE_LEAD_ARGS = 2;
4379
4466
  function scopeFor(seg) {
4380
4467
  const name = seg.binaryPath ?? seg.binary;
4381
- if (!MULTIPLEXERS.includes(seg.binary.toLowerCase())) return name;
4468
+ const program = programName(seg.binary);
4469
+ if (!MULTIPLEXERS.includes(program)) return name;
4470
+ if (program === "git") return gitScope(seg, name);
4382
4471
  const lead = [];
4383
4472
  for (const arg of seg.args) {
4384
4473
  if (arg.startsWith("-")) break;
@@ -4387,6 +4476,14 @@ function scopeFor(seg) {
4387
4476
  }
4388
4477
  return [name, ...lead].join(" ");
4389
4478
  }
4479
+ function gitScope(seg, name) {
4480
+ const scan = scanFlags({ ...seg, binary: "git" });
4481
+ if (!scan || scan.unknown || scan.operands[0] === "ls-remote") return [name, ...seg.args].map(scopeWord).join(" ");
4482
+ return [name, ...scan.operands.slice(0, SCOPE_LEAD_ARGS)].join(" ");
4483
+ }
4484
+ function scopeWord(word) {
4485
+ return word === "" || /[\s"'\\]/.test(word) ? JSON.stringify(word) : word;
4486
+ }
4390
4487
  function climbsByGlob(seg) {
4391
4488
  if (!seg.globbed) return false;
4392
4489
  const patterns = patternArgs(seg);
@@ -4408,6 +4505,27 @@ function swallowsRefName(arg) {
4408
4505
  const values = FLAG_POLICY.git.values ?? [];
4409
4506
  return [...arg.slice(1)].some((c) => c !== "n" && values.includes(`-${c}`));
4410
4507
  }
4508
+ function gitArgsAfterSubcommand(seg) {
4509
+ const at = scanFlags(seg)?.operandAt[0];
4510
+ return at === void 0 ? [] : seg.args.slice(at + 1);
4511
+ }
4512
+ var REMOTE_NAME = /^[A-Za-z0-9_][A-Za-z0-9_.-]*$/;
4513
+ function namesConfiguredRemote(seg, after) {
4514
+ const destination2 = scanFlags({ ...seg, args: after })?.operands[0];
4515
+ return destination2 === void 0 || REMOTE_NAME.test(destination2);
4516
+ }
4517
+ function programName(binary) {
4518
+ return binary.toLowerCase().replace(/\.exe$/, "");
4519
+ }
4520
+ function isRemoteListing(seg) {
4521
+ const git2 = { ...seg, binary: programName(seg.binary) };
4522
+ return git2.binary === "git" && subcommandOf(git2) === "ls-remote";
4523
+ }
4524
+ function remoteListingTexts(seg) {
4525
+ if (!isRemoteListing(seg)) return [];
4526
+ const git2 = { ...seg, binary: "git" };
4527
+ return scanFlags({ ...git2, args: gitArgsAfterSubcommand(git2) })?.operands ?? [];
4528
+ }
4411
4529
  function refusalFor(seg, trustedResolves) {
4412
4530
  const web = webCommand(seg, trustedResolves);
4413
4531
  if (web?.reason) return web.reason;
@@ -4445,8 +4563,24 @@ function refusalFor(seg, trustedResolves) {
4445
4563
  }
4446
4564
  if (seg.binary === "git") {
4447
4565
  const sub = subcommandOf(seg);
4566
+ const after = gitArgsAfterSubcommand(seg);
4567
+ if (sub !== void 0 && !GIT_READONLY_SUBCOMMANDS.includes(sub)) {
4568
+ const reads = GIT_PROMPTABLE[sub];
4569
+ if (!reads) {
4570
+ return `"git ${sub}" is not a git subcommand known only to read, so it may write the repository. You are a read-only planner: ${EFFECT_ROUTE}`;
4571
+ }
4572
+ if (!reads(after)) {
4573
+ return `"git ${[sub, ...after].join(" ")}" is not a read-only form of "git ${sub}". You are a read-only planner: ${EFFECT_ROUTE}`;
4574
+ }
4575
+ }
4576
+ if (sub === "ls-remote" && !namesConfiguredRemote(seg, after)) {
4577
+ return `"git ls-remote" given a URL, an address or a path reaches it through git's own transport, which resolves the host itself and follows a redirect, so the origin approval, address guard and pinned connection a web read gets (ADR-0027) cannot hold for it. Name a remote the repository configures, such as "git ls-remote origin", or fetch the page with your web fetch tool.`;
4578
+ }
4579
+ if (sub === "ls-remote" && (seg.expandable || seg.globbed || after.some((a) => a.includes("~")))) {
4580
+ return `The shell rewrites part of this "git ls-remote" before git sees it (a variable, substitution, glob or "~"), so what it sends the remote, and which repository it lists, cannot be checked for a secret or approved. Write the remote and patterns literally, in single quotes where they hold a "*", and re-run.`;
4581
+ }
4448
4582
  if (sub === "branch" || sub === "tag") {
4449
- const flag2 = seg.args.find((a) => ["-D", "-d", "-m", "-M", "--delete", "--move"].includes(a)) ?? seg.args.slice(seg.args.indexOf(sub) + 1).find(swallowsRefName);
4583
+ const flag2 = seg.args.find((a) => ["-D", "-d", "-m", "-M", "--delete", "--move"].includes(a)) ?? after.find(swallowsRefName);
4450
4584
  if (flag2) {
4451
4585
  return `"git ${sub} ${flag2}" mutates refs. You are a read-only planner: ${EFFECT_ROUTE}`;
4452
4586
  }
@@ -4585,10 +4719,10 @@ function classifyCommand(command, opts = {}) {
4585
4719
  return { tier: "refuse", scope: "", reason: `curl and wget are refused in Full and allowlist mode. ${web.effect ? EFFECT_REFUSAL_HINT : READ_REFUSAL_HINT}` };
4586
4720
  }
4587
4721
  const allow = opts.allow;
4588
- if (web && allow && web.urls.some((url) => scopeExcluded(allow, `${seg.binary.toLowerCase().replace(/\.exe$/, "")} ${url.origin}/*`))) {
4722
+ if (web && allow && web.urls.some((url) => scopeExcluded(allow, `${programName(seg.binary)} ${url.origin}/*`))) {
4589
4723
  return { tier: "refuse", scope: "", reason: `The web origin is excluded by the planner allowlist. ${READ_REFUSAL_HINT}` };
4590
4724
  }
4591
- const reason = refusalFor({ ...seg, binary: seg.binary.toLowerCase() }, opts.trustedWebResolves);
4725
+ const reason = refusalFor({ ...seg, binary: programName(seg.binary) }, opts.trustedWebResolves);
4592
4726
  if (reason) return { tier: "refuse", scope: "", reason: withWrapperNote(reason, wrappers) };
4593
4727
  if (runner && !XARGS_TARGETS.includes(seg.binary)) {
4594
4728
  return {
@@ -4598,23 +4732,24 @@ function classifyCommand(command, opts = {}) {
4598
4732
  };
4599
4733
  }
4600
4734
  }
4601
- if (unwrapped.some(({ seg }) => seg.binary.toLowerCase().replace(/\.exe$/, "") === "wget") && wgetVersionCommands(command, opts.dialect).includes(void 0)) {
4735
+ if (unwrapped.some(({ seg }) => programName(seg.binary) === "wget") && wgetVersionCommands(command, opts.dialect).includes(void 0)) {
4602
4736
  return { tier: "refuse", scope: "", reason: `The wget executable cannot be checked safely in this working directory. ${READ_REFUSAL_HINT}` };
4603
4737
  }
4604
4738
  const nonAuto = unwrapped.filter((u) => u.runner !== void 0 || u.wrappers.some((w) => w !== w.toLowerCase()) || !isAuto(u.seg, opts));
4605
4739
  if (nonAuto.length === 0) return { tier: "auto", scope: "" };
4740
+ const changesDirectory = unwrapped.some(({ seg }) => seg.binary.toLowerCase() === "cd");
4606
4741
  const lineScoped = nonAuto.some((u) => {
4607
4742
  const name = u.seg.binary.toLowerCase();
4608
- return name === "cd" || name !== "command" && SHELL_STATE_COMMANDS.has(name) || climbsByGlob(u.seg);
4743
+ return name === "cd" || name !== "command" && SHELL_STATE_COMMANDS.has(name) || climbsByGlob(u.seg) || isRemoteListing(u.seg) && (changesDirectory || u.wrappers.length > 0 || nonAuto.length > 1);
4609
4744
  });
4610
4745
  const webUrls = nonAuto.flatMap((u) => webCommand(u.seg, opts.trustedWebResolves)?.urls ?? []);
4611
- const webOutboundTexts = nonAuto.flatMap((u) => webCommand(u.seg, opts.trustedWebResolves)?.outboundTexts ?? []);
4746
+ const outboundTexts = nonAuto.flatMap((u) => webCommand(u.seg, opts.trustedWebResolves)?.outboundTexts ?? remoteListingTexts(u.seg));
4612
4747
  if (lineScoped && webUrls.length === 0) {
4613
- return { tier: "ask", scope: trimmed };
4748
+ return { tier: "ask", scope: trimmed, ...outboundTexts.length ? { outboundTexts } : {} };
4614
4749
  }
4615
4750
  const scopes = [...new Set(nonAuto.flatMap((u) => {
4616
4751
  const web = webCommand(u.seg, opts.trustedWebResolves);
4617
- return web ? web.urls.map((url) => `${u.seg.binary.toLowerCase().replace(/\.exe$/, "")} ${url.origin}/*`) : [lineScoped ? trimmed : u.runner ? `${u.runner} ${scopeFor(u.seg)}` : scopeFor(u.seg)];
4752
+ return web ? web.urls.map((url) => `${programName(u.seg.binary)} ${url.origin}/*`) : [lineScoped ? trimmed : u.runner ? `${u.runner} ${scopeFor(u.seg)}` : scopeFor(u.seg)];
4618
4753
  }))].sort();
4619
4754
  const scope = scopes.join(" + ");
4620
4755
  const perInvocation = nonAuto.some((u) => webCommand(u.seg, opts.trustedWebResolves)?.perInvocation);
@@ -4622,7 +4757,8 @@ function classifyCommand(command, opts = {}) {
4622
4757
  return {
4623
4758
  tier: "ask",
4624
4759
  scope,
4625
- ...webUrls.length ? { webUrls, webOutboundTexts } : {},
4760
+ ...webUrls.length ? { webUrls } : {},
4761
+ ...outboundTexts.length ? { outboundTexts } : {},
4626
4762
  ...scopes.length > 1 && webUrls.length ? { scopes } : {},
4627
4763
  ...perInvocation ? { perInvocation } : {},
4628
4764
  ...allowedBy ? { allowedBy } : {}
@@ -6176,7 +6312,7 @@ ${TASK_CONTINUE}`);
6176
6312
  command = disableWebCommandStartup(command, this.researchShell.dialect);
6177
6313
  const { approval, root } = site;
6178
6314
  const allow = approval.allowlist?.();
6179
- const { tier, scope, reason, allowedBy, perInvocation, scopes: commandScopes, webUrls, webOutboundTexts } = classifyCommand(command, {
6315
+ const { tier, scope, reason, allowedBy, perInvocation, scopes: commandScopes, webUrls, outboundTexts } = classifyCommand(command, {
6180
6316
  ...this.policyOptions(),
6181
6317
  ...approval.commandPolicy?.(),
6182
6318
  ...allow ? { allow } : {}
@@ -6201,8 +6337,9 @@ ${TASK_CONTINUE}`);
6201
6337
  if (site.task?.sealed) return this.runSealed(site, command, request, "after", signal);
6202
6338
  if (tier === "refuse") return this.sealedInstead(site, command, request, refusal(`Command refused: ${reason}${site.task ? `
6203
6339
  ${TASK_UNSEALED} ${TASK_CONTINUE}` : ""}`), signal);
6204
- if (webUrls?.length && carriesSecret([...webOutboundTexts ?? [], ...webUrls.map((url) => url.href)], [...this.outboundSecrets(), ...sensitiveEnvValues(process.env)])) {
6205
- return refusal("Command refused: a web command contains what looks like a secret. Fetch the page with your web fetch tool, without credentials.");
6340
+ const outbound = [...outboundTexts ?? [], ...(webUrls ?? []).map((url) => url.href)];
6341
+ if (outbound.length && carriesSecret(outbound, [...this.outboundSecrets(), ...sensitiveEnvValues(process.env)])) {
6342
+ return refusal(webUrls?.length ? "Command refused: a web command contains what looks like a secret. Fetch the page with your web fetch tool, without credentials." : "Command refused: what this command sends over the network contains what looks like a secret. Run it without the value.");
6206
6343
  }
6207
6344
  const vettedAddresses = /* @__PURE__ */ new Map();
6208
6345
  for (const url of webUrls ?? []) {
@@ -6305,7 +6442,7 @@ ${outcome.output}${outcome.success ? "" : `
6305
6442
  }
6306
6443
  let shell;
6307
6444
  try {
6308
- const sealed = site.task?.gitBefore ? { ...site.sealed, gitEntries: site.task.gitBefore.entries.map(({ path: path46, directory }) => ({ path: path46, directory })) } : site.sealed;
6445
+ const sealed = site.task?.gitBefore ? { ...site.sealed, gitEntries: site.task.gitBefore.entries.map(({ path: path47, directory }) => ({ path: path47, directory })) } : site.sealed;
6309
6446
  shell = await site.sandbox.launchSealed(this.researchShell, sealed);
6310
6447
  } catch (err) {
6311
6448
  return { refused: refusal(`Command not run: its sealed sandbox could not be prepared (${err instanceof Error ? err.message : String(err)}).`) };
@@ -7984,6 +8121,7 @@ function enabledRunners(cfg) {
7984
8121
  }
7985
8122
 
7986
8123
  // src/plugins/builtin/claudeModels.ts
8124
+ import * as path12 from "path";
7987
8125
  var ANTHROPIC_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
7988
8126
  var ANTHROPIC_VARIANT_LABELS = {
7989
8127
  adaptive: "Adaptive",
@@ -8077,6 +8215,53 @@ function parseClaudeHelp(stdout) {
8077
8215
  }
8078
8216
  return models;
8079
8217
  }
8218
+ function claudeConfigDir(env) {
8219
+ return env.CLAUDE_CONFIG_DIR?.trim() || "~/.claude";
8220
+ }
8221
+ function claudeManagedSettingsDir(platform = process.platform) {
8222
+ if (platform === "darwin") return "/Library/Application Support/ClaudeCode";
8223
+ if (platform === "win32") return "C:\\Program Files\\ClaudeCode";
8224
+ return "/etc/claude-code";
8225
+ }
8226
+ function readClaudeSettingsModels(files, platform = process.platform) {
8227
+ const managed = claudeManagedSettingsDir(platform);
8228
+ const join43 = platform === "win32" ? path12.win32.join : path12.posix.join;
8229
+ const dropIns = files.list(join43(managed, "managed-settings.d")).filter((name) => name.endsWith(".json") && !name.startsWith(".")).sort().map((name) => join43(managed, "managed-settings.d", name));
8230
+ const ranked = [...dropIns.reverse(), join43(managed, "managed-settings.json"), join43(claudeConfigDir(files.env), "settings.json")];
8231
+ for (const file of ranked) {
8232
+ const content = files.read(file);
8233
+ const lineup = content === null ? null : parseClaudeSettingsModels(content);
8234
+ if (lineup) return lineup;
8235
+ }
8236
+ return [];
8237
+ }
8238
+ function parseClaudeSettingsModels(content) {
8239
+ let parsed;
8240
+ try {
8241
+ parsed = JSON.parse(content);
8242
+ } catch {
8243
+ return null;
8244
+ }
8245
+ const picker = parsed && typeof parsed === "object" ? parsed.modelPicker : void 0;
8246
+ const options = picker && typeof picker === "object" ? picker.options : void 0;
8247
+ if (!Array.isArray(options)) return null;
8248
+ const seen = /* @__PURE__ */ new Set();
8249
+ const models = [];
8250
+ for (const row of options) {
8251
+ if (!row || typeof row !== "object") continue;
8252
+ const { model: raw, label } = row;
8253
+ const model = typeof raw === "string" ? raw.trim() : "";
8254
+ if (!model || seen.has(model)) continue;
8255
+ seen.add(model);
8256
+ models.push({
8257
+ modelId: model,
8258
+ modelLabel: typeof label === "string" && label.trim() ? label.trim() : model,
8259
+ runnerProvider: model.includes("/") ? model.split("/")[0] : void 0,
8260
+ variants: []
8261
+ });
8262
+ }
8263
+ return models;
8264
+ }
8080
8265
 
8081
8266
  // src/plugins/builtin/claude-code.manifest.ts
8082
8267
  var TURN_NOT_LOGGED_IN = /not logged in\s*·\s*please run \/login/i;
@@ -8115,18 +8300,24 @@ var CLAUDE_CODE_MANIFEST = {
8115
8300
  // 3. canonicalAliases — stable --model contracts (opus/sonnet/haiku/fable)
8116
8301
  // merged into any successful result to fill gaps, and used as the full
8117
8302
  // last-resort list when both API and CLI are unavailable.
8303
+ //
8304
+ // Whichever source answers, the models the user added to Claude's own
8305
+ // picker (`modelPicker.options` in its managed or user settings, usually
8306
+ // gateway IDs) are appended (settingsModels): the CLI accepts them as
8307
+ // `--model` values though no catalog above lists them.
8118
8308
  apiDiscovery: {
8119
8309
  url: "https://api.anthropic.com/v1/models?limit=100",
8120
8310
  headers: { "anthropic-version": "2023-06-01" },
8121
8311
  auth: [
8122
8312
  { type: "env", varName: "ANTHROPIC_API_KEY", header: "X-Api-Key" },
8123
- { type: "file", path: "~/.claude/.credentials.json", jsonPath: "claudeAiOauth.accessToken", header: "Authorization", prefix: "Bearer " }
8313
+ { type: "file", path: (env) => `${claudeConfigDir(env)}/.credentials.json`, jsonPath: "claudeAiOauth.accessToken", header: "Authorization", prefix: "Bearer " }
8124
8314
  ],
8125
8315
  parser: parseAnthropicModels
8126
8316
  },
8127
8317
  discoveryCommands: [
8128
8318
  { command: "claude", args: ["--help"], parser: parseClaudeHelp }
8129
8319
  ],
8320
+ settingsModels: (files) => readClaudeSettingsModels(files),
8130
8321
  canonicalAliases: [
8131
8322
  { modelId: "opus", modelLabel: "Opus" },
8132
8323
  { modelId: "sonnet", modelLabel: "Sonnet" },
@@ -8184,7 +8375,7 @@ import { join as join11, resolve as resolve4 } from "path";
8184
8375
 
8185
8376
  // src/services/harness/repoConfig.ts
8186
8377
  import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
8187
- import * as path12 from "path";
8378
+ import * as path13 from "path";
8188
8379
 
8189
8380
  // src/services/mcp/OrdewellMcpServer.ts
8190
8381
  import { createServer } from "http";
@@ -8897,12 +9088,12 @@ function checkpointReply(answer2) {
8897
9088
  return { text: `The checkpoint was withdrawn: ${answer2.why}`, isError: true };
8898
9089
  }
8899
9090
  }
8900
- function fieldPath(path46) {
8901
- return path46.reduce((text4, part) => typeof part === "number" ? `${text4}[${part}]` : text4 ? `${text4}.${String(part)}` : String(part), "");
9091
+ function fieldPath(path47) {
9092
+ return path47.reduce((text4, part) => typeof part === "number" ? `${text4}[${part}]` : text4 ? `${text4}.${String(part)}` : String(part), "");
8902
9093
  }
8903
- function valueAt(value, path46) {
9094
+ function valueAt(value, path47) {
8904
9095
  let current = value;
8905
- for (const part of path46) {
9096
+ for (const part of path47) {
8906
9097
  if (typeof part === "number") {
8907
9098
  if (!Array.isArray(current)) return void 0;
8908
9099
  current = current[part];
@@ -8924,7 +9115,7 @@ function boundedRefusal(issue, args) {
8924
9115
  var LEAKED_PARAMETER = /<\/parameter>|<parameter\s+name\s*=\s*"([^"]*)">/g;
8925
9116
  function leakedMarkupIn(value) {
8926
9117
  const found = [];
8927
- const walk = (node, path46) => {
9118
+ const walk = (node, path47) => {
8928
9119
  if (typeof node === "string") {
8929
9120
  const names = [];
8930
9121
  let closing = false;
@@ -8932,25 +9123,25 @@ function leakedMarkupIn(value) {
8932
9123
  if (match[1] !== void 0) names.push(match[1]);
8933
9124
  else closing = true;
8934
9125
  }
8935
- if (names.length > 0 || closing) found.push({ path: path46, names: [...new Set(names)], closing });
9126
+ if (names.length > 0 || closing) found.push({ path: path47, names: [...new Set(names)], closing });
8936
9127
  return;
8937
9128
  }
8938
9129
  if (Array.isArray(node)) {
8939
- node.forEach((item, i) => walk(item, `${path46}[${i}]`));
9130
+ node.forEach((item, i) => walk(item, `${path47}[${i}]`));
8940
9131
  return;
8941
9132
  }
8942
9133
  if (node !== null && typeof node === "object") {
8943
- for (const [key, item] of Object.entries(node)) walk(item, path46 ? `${path46}.${key}` : key);
9134
+ for (const [key, item] of Object.entries(node)) walk(item, path47 ? `${path47}.${key}` : key);
8944
9135
  }
8945
9136
  };
8946
9137
  walk(value, "");
8947
9138
  return found;
8948
9139
  }
8949
9140
  function markupRefusal(leaks) {
8950
- return leaks.map(({ path: path46, names, closing }) => {
9141
+ return leaks.map(({ path: path47, names, closing }) => {
8951
9142
  const tags = [...names.map((n) => `<parameter name="${n}">`), ...closing ? ["</parameter>"] : []];
8952
- const guide = names.length > 0 ? ` The parameters ${names.map((n) => JSON.stringify(n)).join(", ")} are inside it; pass them as separate arguments, not inside ${path46}.` : " Pass each argument separately, not inside one string.";
8953
- return `${path46} contains leaked tool-call markup: ${tags.join(", ")}.${guide}`;
9143
+ const guide = names.length > 0 ? ` The parameters ${names.map((n) => JSON.stringify(n)).join(", ")} are inside it; pass them as separate arguments, not inside ${path47}.` : " Pass each argument separately, not inside one string.";
9144
+ return `${path47} contains leaked tool-call markup: ${tags.join(", ")}.${guide}`;
8954
9145
  }).join("\n");
8955
9146
  }
8956
9147
  function invalidArguments(name, error, args) {
@@ -9415,9 +9606,9 @@ function mcpClientConfig(credential) {
9415
9606
  }
9416
9607
  function ownerOnlyConfigFile(fileName, contents) {
9417
9608
  const dir = mkdtempSync2(join9(tmpdir2(), "ordewell-mcp-"));
9418
- const path46 = join9(dir, fileName);
9419
- writeFileSync(path46, contents, { mode: 384 });
9420
- return { path: path46, remove: () => rmSync2(dir, { recursive: true, force: true }) };
9609
+ const path47 = join9(dir, fileName);
9610
+ writeFileSync(path47, contents, { mode: 384 });
9611
+ return { path: path47, remove: () => rmSync2(dir, { recursive: true, force: true }) };
9421
9612
  }
9422
9613
 
9423
9614
  // src/services/harness/repoConfig.ts
@@ -9440,19 +9631,19 @@ function onDisk(target) {
9440
9631
  try {
9441
9632
  return realpathSync2.native(target);
9442
9633
  } catch {
9443
- return path12.resolve(target);
9634
+ return path13.resolve(target);
9444
9635
  }
9445
9636
  }
9446
9637
  function insideOnDisk(root, target) {
9447
- const rel = path12.relative(onDisk(root), onDisk(target));
9448
- return rel === "" || !rel.startsWith("..") && !path12.isAbsolute(rel);
9638
+ const rel = path13.relative(onDisk(root), onDisk(target));
9639
+ return rel === "" || !rel.startsWith("..") && !path13.isAbsolute(rel);
9449
9640
  }
9450
9641
  function repoLevels(workspace) {
9451
9642
  const levels = [];
9452
- for (let dir = path12.resolve(workspace); ; dir = path12.dirname(dir)) {
9643
+ for (let dir = path13.resolve(workspace); ; dir = path13.dirname(dir)) {
9453
9644
  levels.unshift(dir);
9454
- if (existsSync3(path12.join(dir, ".git"))) return levels;
9455
- if (path12.dirname(dir) === dir) return [path12.resolve(workspace)];
9645
+ if (existsSync3(path13.join(dir, ".git"))) return levels;
9646
+ if (path13.dirname(dir) === dir) return [path13.resolve(workspace)];
9456
9647
  }
9457
9648
  }
9458
9649
  function repoRoot(workspace) {
@@ -9546,33 +9737,33 @@ var TomlReader = class {
9546
9737
  if (this.i >= this.src.length) return root;
9547
9738
  if (this.src.startsWith("[[", this.i)) {
9548
9739
  this.i += 2;
9549
- const path46 = this.key();
9740
+ const path47 = this.key();
9550
9741
  this.expect("]]");
9551
- const parent = this.tableAt(root, path46.slice(0, -1));
9552
- const last = path46[path46.length - 1];
9742
+ const parent = this.tableAt(root, path47.slice(0, -1));
9743
+ const last = path47[path47.length - 1];
9553
9744
  const list = Object.hasOwn(parent, last) ? parent[last] : parent[last] = [];
9554
- if (!Array.isArray(list)) throw this.fail(`${path46.join(".")} is not an array of tables`);
9745
+ if (!Array.isArray(list)) throw this.fail(`${path47.join(".")} is not an array of tables`);
9555
9746
  table = newTable();
9556
9747
  list.push(table);
9557
9748
  } else if (this.src[this.i] === "[") {
9558
9749
  this.i += 1;
9559
- const path46 = this.key();
9750
+ const path47 = this.key();
9560
9751
  this.expect("]");
9561
- table = this.tableAt(root, path46);
9752
+ table = this.tableAt(root, path47);
9562
9753
  } else {
9563
- const path46 = this.key();
9754
+ const path47 = this.key();
9564
9755
  this.skipBlank(false);
9565
9756
  this.expect("=");
9566
9757
  this.skipBlank(false);
9567
- const owner = this.tableAt(table, path46.slice(0, -1));
9568
- owner[path46[path46.length - 1]] = this.value();
9758
+ const owner = this.tableAt(table, path47.slice(0, -1));
9759
+ owner[path47[path47.length - 1]] = this.value();
9569
9760
  }
9570
9761
  this.endOfLine();
9571
9762
  }
9572
9763
  }
9573
- tableAt(from, path46) {
9764
+ tableAt(from, path47) {
9574
9765
  let table = from;
9575
- for (const part of path46) {
9766
+ for (const part of path47) {
9576
9767
  const next = Object.hasOwn(table, part) ? table[part] : void 0;
9577
9768
  if (next === void 0) table = table[part] = newTable();
9578
9769
  else if (isRecord(next)) table = next;
@@ -9641,10 +9832,10 @@ var TomlReader = class {
9641
9832
  this.i++;
9642
9833
  return table;
9643
9834
  }
9644
- const path46 = this.key();
9835
+ const path47 = this.key();
9645
9836
  this.expect("=");
9646
9837
  this.skipBlank(false);
9647
- this.tableAt(table, path46.slice(0, -1))[path46[path46.length - 1]] = this.value();
9838
+ this.tableAt(table, path47.slice(0, -1))[path47[path47.length - 1]] = this.value();
9648
9839
  this.skipBlank(true);
9649
9840
  if (this.src[this.i] === ",") this.i++;
9650
9841
  else if (this.src[this.i] !== "}") throw this.fail("expected , or } in an inline table");
@@ -9789,7 +9980,8 @@ var CODEX_MANIFEST = {
9789
9980
  appServer: {
9790
9981
  command: "codex",
9791
9982
  args: ["app-server"],
9792
- cacheFile: "~/.codex/models_cache.json"
9983
+ // CODEX_HOME moves all of Codex's own files from `~/.codex`.
9984
+ cacheFile: (env) => `${env.CODEX_HOME?.trim() || "~/.codex"}/models_cache.json`
9793
9985
  },
9794
9986
  canonicalAliases: [
9795
9987
  { modelId: "gpt-5.6-sol", modelLabel: "GPT-5.6-Sol" },
@@ -10018,8 +10210,7 @@ var LineBuffer = class {
10018
10210
  // src/services/harness/ClaudeCodeAdapter.ts
10019
10211
  import { randomUUID as randomUUID3 } from "crypto";
10020
10212
  import { realpathSync as realpathSync7 } from "fs";
10021
- import * as os9 from "os";
10022
- import * as path20 from "path";
10213
+ import * as path21 from "path";
10023
10214
 
10024
10215
  // src/plugins/resolveArgs.ts
10025
10216
  function claudeThinkingArgs(effort) {
@@ -10057,14 +10248,29 @@ function resolveTaskRunnerFlags(manifest, ctx) {
10057
10248
  return flags;
10058
10249
  }
10059
10250
 
10251
+ // src/utils/runnerHome.ts
10252
+ import * as os5 from "os";
10253
+ function runnerHomeDir(env, platform = process.platform) {
10254
+ const name = platform === "win32" ? "USERPROFILE" : "HOME";
10255
+ const key = platform === "win32" ? Object.keys(env).filter((k) => k.toUpperCase() === name).pop() : name;
10256
+ const home = key === void 0 ? void 0 : env[key];
10257
+ return home ?? os5.homedir();
10258
+ }
10259
+ function expandRunnerHome(filePath, env = process.env) {
10260
+ if (!filePath.startsWith("~")) return filePath;
10261
+ const home = runnerHomeDir(env);
10262
+ return home ? home + filePath.slice(1) : null;
10263
+ }
10264
+
10060
10265
  // src/services/harness/runnerProcess.ts
10266
+ import { Socket } from "net";
10061
10267
  import { StringDecoder } from "string_decoder";
10062
10268
 
10063
10269
  // src/utils/shellPath.ts
10064
10270
  import { exec } from "child_process";
10065
10271
  import { promisify } from "util";
10066
- import * as os5 from "os";
10067
- import * as path13 from "path";
10272
+ import * as os6 from "os";
10273
+ import * as path14 from "path";
10068
10274
  var execAsync = promisify(exec);
10069
10275
  var LOGIN_SHELL_TIMEOUT_MS = 5e3;
10070
10276
  var PATH_MARKER = "__ORDEWELL_PATH__";
@@ -10073,21 +10279,21 @@ var defaultExec = async (command, options) => {
10073
10279
  return { stdout: String(stdout) };
10074
10280
  };
10075
10281
  function posixBinDirs() {
10076
- const home = os5.homedir();
10282
+ const home = os6.homedir();
10077
10283
  return [
10078
- path13.join(home, ".local", "bin"),
10079
- path13.join(home, ".opencode", "bin"),
10080
- path13.join(home, ".claude", "bin"),
10081
- path13.join(home, ".bun", "bin"),
10082
- path13.join(home, ".volta", "bin"),
10083
- path13.join(home, ".npm-global", "bin"),
10084
- path13.join(home, "bin"),
10284
+ path14.join(home, ".local", "bin"),
10285
+ path14.join(home, ".opencode", "bin"),
10286
+ path14.join(home, ".claude", "bin"),
10287
+ path14.join(home, ".bun", "bin"),
10288
+ path14.join(home, ".volta", "bin"),
10289
+ path14.join(home, ".npm-global", "bin"),
10290
+ path14.join(home, "bin"),
10085
10291
  "/usr/local/bin",
10086
10292
  "/opt/homebrew/bin"
10087
10293
  ];
10088
10294
  }
10089
10295
  function windowsBinDirs(env, home) {
10090
- const join43 = (...parts) => path13.win32.join(...parts);
10296
+ const join43 = (...parts) => path14.win32.join(...parts);
10091
10297
  const under = (base, ...parts) => base ? [join43(base, ...parts)] : [];
10092
10298
  const appData = env.APPDATA;
10093
10299
  const localAppData = env.LOCALAPPDATA;
@@ -10124,7 +10330,7 @@ function windowsBinDirs(env, home) {
10124
10330
  function wellKnownBinDirs(deps = {}) {
10125
10331
  const platform = deps.platform ?? process.platform;
10126
10332
  if (platform !== "win32") return posixBinDirs();
10127
- return [...new Set(windowsBinDirs(deps.env ?? process.env, deps.home ?? os5.homedir()))];
10333
+ return [...new Set(windowsBinDirs(deps.env ?? process.env, deps.home ?? os6.homedir()))];
10128
10334
  }
10129
10335
  async function loginShellPath(execImpl) {
10130
10336
  if (process.platform === "win32") return null;
@@ -10147,14 +10353,14 @@ function augmentedPath(execImpl = defaultExec) {
10147
10353
  const segments = [];
10148
10354
  const push = (p) => {
10149
10355
  if (!p) return;
10150
- for (const seg of p.split(path13.delimiter)) {
10356
+ for (const seg of p.split(path14.delimiter)) {
10151
10357
  if (seg && !segments.includes(seg)) segments.push(seg);
10152
10358
  }
10153
10359
  };
10154
10360
  push(process.env.PATH);
10155
10361
  push(await loginShellPath(execImpl));
10156
10362
  for (const dir of wellKnownBinDirs()) push(dir);
10157
- return segments.join(path13.delimiter);
10363
+ return segments.join(path14.delimiter);
10158
10364
  })();
10159
10365
  }
10160
10366
  return cachedPath;
@@ -10176,7 +10382,7 @@ function withPath(base, resolvedPath, overrides = {}) {
10176
10382
 
10177
10383
  // src/utils/launch.ts
10178
10384
  import * as fsSync4 from "fs";
10179
- import * as path14 from "path";
10385
+ import * as path15 from "path";
10180
10386
  var DIRECT_EXTENSIONS = [".exe", ".com"];
10181
10387
  var BATCH_EXTENSIONS = [".cmd", ".bat"];
10182
10388
  var POWERSHELL_EXTENSIONS = [".ps1"];
@@ -10184,7 +10390,7 @@ var CMD_EXE_MAX_COMMAND_LINE = 8191;
10184
10390
  var WINDOWS_MAX_COMMAND_LINE = 32767;
10185
10391
  function defaultPowerShell() {
10186
10392
  const root = process.env.SystemRoot || process.env.windir;
10187
- return root ? path14.win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe") : "powershell.exe";
10393
+ return root ? path15.win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe") : "powershell.exe";
10188
10394
  }
10189
10395
  var POWERSHELL_FLAGS = ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File"];
10190
10396
  var CommandLineTooLongError = class extends Error {
@@ -10250,7 +10456,7 @@ Searched: ${searchedPath || "(empty PATH)"}`);
10250
10456
  };
10251
10457
  function isResolvableOnPosixPath(command, PATH, exists) {
10252
10458
  if (command.includes("/")) return exists(command);
10253
- return PATH.split(path14.delimiter).map((d) => d.trim()).filter(Boolean).some((dir) => exists(path14.join(dir, command)));
10459
+ return PATH.split(path15.delimiter).map((d) => d.trim()).filter(Boolean).some((dir) => exists(path15.join(dir, command)));
10254
10460
  }
10255
10461
  function isExecutableResolved(command, plan, PATH, deps = {}) {
10256
10462
  if ((deps.platform ?? process.platform) === "win32") return plan.file !== command;
@@ -10290,9 +10496,9 @@ function cmdArgs(line) {
10290
10496
  return ["/d", "/s", "/c", `"${line}"`];
10291
10497
  }
10292
10498
  function candidateExtensions(command, pathExt) {
10293
- const declared = pathExt.split(path14.win32.delimiter).map((e) => e.trim().toLowerCase()).filter(Boolean);
10499
+ const declared = pathExt.split(path15.win32.delimiter).map((e) => e.trim().toLowerCase()).filter(Boolean);
10294
10500
  const known = /* @__PURE__ */ new Set([...DIRECT_EXTENSIONS, ...BATCH_EXTENSIONS, ...POWERSHELL_EXTENSIONS, ...declared]);
10295
- const ext = path14.win32.extname(command).toLowerCase();
10501
+ const ext = path15.win32.extname(command).toLowerCase();
10296
10502
  if (ext && known.has(ext)) {
10297
10503
  const route = BATCH_EXTENSIONS.includes(ext) ? "cmd" : POWERSHELL_EXTENSIONS.includes(ext) ? "powershell" : "direct";
10298
10504
  return { direct: [], cmd: [], powershell: [], [route]: [""] };
@@ -10313,13 +10519,13 @@ async function findWindowsExecutables(command, deps) {
10313
10519
  const exists = deps.exists ?? defaultExists3;
10314
10520
  const pathExt = (deps.pathExt ?? (() => process.env.PATHEXT ?? ""))();
10315
10521
  const byRoute = candidateExtensions(command, pathExt);
10316
- const dirs = path14.win32.basename(command) !== command ? [path14.win32.dirname(path14.win32.resolve(command))] : (await (deps.resolvePath ?? augmentedPath)() || "").split(path14.win32.delimiter).map((d) => d.trim().replace(/^"|"$/g, "")).filter(Boolean);
10317
- const base = path14.win32.basename(command);
10522
+ const dirs = path15.win32.basename(command) !== command ? [path15.win32.dirname(path15.win32.resolve(command))] : (await (deps.resolvePath ?? augmentedPath)() || "").split(path15.win32.delimiter).map((d) => d.trim().replace(/^"|"$/g, "")).filter(Boolean);
10523
+ const base = path15.win32.basename(command);
10318
10524
  const found = [];
10319
10525
  for (const route of ROUTES) {
10320
10526
  outer: for (const ext of byRoute[route]) {
10321
10527
  for (const dir of dirs) {
10322
- const candidate = path14.win32.join(dir, `${base}${ext}`);
10528
+ const candidate = path15.win32.join(dir, `${base}${ext}`);
10323
10529
  if (exists(candidate)) {
10324
10530
  found.push({ file: candidate, route });
10325
10531
  break outer;
@@ -10367,7 +10573,7 @@ async function planDirectLaunch(command, args, deps = {}) {
10367
10573
 
10368
10574
  // src/utils/workspace.ts
10369
10575
  import * as fs3 from "fs";
10370
- import * as path15 from "path";
10576
+ import * as path16 from "path";
10371
10577
  var WorkspaceNotFoundError = class extends Error {
10372
10578
  constructor(workspace) {
10373
10579
  super(`Workspace "${workspace}" does not exist.`);
@@ -10423,7 +10629,7 @@ function assertWorkspaceIsProject(workspace, deps = {}) {
10423
10629
  throw new WorkspaceNotAProjectError(workspace);
10424
10630
  }
10425
10631
  const hasMarker = [...PROJECT_MARKER_DIRS, ...PROJECT_MARKER_FILES].some(
10426
- (marker2) => exists(path15.join(resolved, marker2))
10632
+ (marker2) => exists(path16.join(resolved, marker2))
10427
10633
  );
10428
10634
  if (!hasMarker) throw new WorkspaceNotAProjectError(workspace);
10429
10635
  }
@@ -10431,12 +10637,12 @@ function assertWorkspaceIsProject(workspace, deps = {}) {
10431
10637
  // src/services/workspaceEnv.ts
10432
10638
  import { execFile as execFile3 } from "child_process";
10433
10639
  import * as fs5 from "fs";
10434
- import * as os6 from "os";
10435
- import * as path17 from "path";
10640
+ import * as os7 from "os";
10641
+ import * as path18 from "path";
10436
10642
 
10437
10643
  // src/utils/fsHelpers.ts
10438
10644
  import * as fs4 from "fs";
10439
- import * as path16 from "path";
10645
+ import * as path17 from "path";
10440
10646
  import { randomUUID as randomUUID2 } from "crypto";
10441
10647
  var STATE_DIR = ".ordewell";
10442
10648
  function ensureDir(dir) {
@@ -10445,9 +10651,9 @@ function ensureDir(dir) {
10445
10651
  }
10446
10652
  }
10447
10653
  function writeFileAtomic(filePath, content) {
10448
- const dir = path16.dirname(filePath);
10654
+ const dir = path17.dirname(filePath);
10449
10655
  ensureDir(dir);
10450
- const tempPath = path16.join(dir, `.${path16.basename(filePath)}.${randomUUID2()}.tmp`);
10656
+ const tempPath = path17.join(dir, `.${path17.basename(filePath)}.${randomUUID2()}.tmp`);
10451
10657
  fs4.writeFileSync(tempPath, content, { flag: "wx" });
10452
10658
  try {
10453
10659
  fs4.renameSync(tempPath, filePath);
@@ -10460,7 +10666,7 @@ function writeFileAtomic(filePath, content) {
10460
10666
  }
10461
10667
  }
10462
10668
  function getStateDir(baseDir) {
10463
- return path16.join(baseDir ?? process.cwd(), STATE_DIR);
10669
+ return path17.join(baseDir ?? process.cwd(), STATE_DIR);
10464
10670
  }
10465
10671
  var IGNORE_FILE = ".gitignore";
10466
10672
  var STATE_DIR_IGNORE = "*\n!.gitignore\n!skills/\n!skills/**\n";
@@ -10468,7 +10674,7 @@ var LEGACY_IGNORES = /* @__PURE__ */ new Set(["*", "*\n"]);
10468
10674
  function ensureStateDirIgnored(baseDir) {
10469
10675
  const dir = getStateDir(baseDir);
10470
10676
  ensureDir(dir);
10471
- const file = path16.join(dir, IGNORE_FILE);
10677
+ const file = path17.join(dir, IGNORE_FILE);
10472
10678
  try {
10473
10679
  fs4.writeFileSync(file, STATE_DIR_IGNORE, { flag: "wx" });
10474
10680
  return;
@@ -11003,7 +11209,7 @@ function defaultDeps() {
11003
11209
  }
11004
11210
  },
11005
11211
  baseEnv: process.env,
11006
- home: os6.homedir()
11212
+ home: os7.homedir()
11007
11213
  };
11008
11214
  }
11009
11215
  var REFUSED2 = new Set(SETTINGS_ENV_REFUSED);
@@ -11015,7 +11221,7 @@ async function resolveWorkspaceEnv(cwd, overrides = {}) {
11015
11221
  const direnv = await fromDirenv(checkoutDirOf(cwd, deps), deps);
11016
11222
  result.blockedEnvrc = direnv.blocked;
11017
11223
  Object.assign(collected, direnv.env);
11018
- const file = findUp(cwd, path17.join(STATE_DIR, WORKSPACE_ENV_FILE), deps);
11224
+ const file = findUp(cwd, path18.join(STATE_DIR, WORKSPACE_ENV_FILE), deps);
11019
11225
  if (file) {
11020
11226
  const content = deps.readFile(file);
11021
11227
  if (content !== null) {
@@ -11032,7 +11238,7 @@ async function resolveWorkspaceEnv(cwd, overrides = {}) {
11032
11238
  }
11033
11239
  var evaluating = /* @__PURE__ */ new Map();
11034
11240
  async function workspaceEnvOf(cwd) {
11035
- const key = path17.resolve(cwd);
11241
+ const key = path18.resolve(cwd);
11036
11242
  let running = evaluating.get(key);
11037
11243
  if (!running) {
11038
11244
  running = resolveWorkspaceEnv(cwd).then((result) => result.env).finally(() => evaluating.delete(key));
@@ -11041,11 +11247,11 @@ async function workspaceEnvOf(cwd) {
11041
11247
  return { ...await running };
11042
11248
  }
11043
11249
  function checkoutDirOf(cwd, deps) {
11044
- const parts = path17.resolve(cwd).split(path17.sep);
11250
+ const parts = path18.resolve(cwd).split(path18.sep);
11045
11251
  const at = parts.findIndex((part, i) => part === STATE_DIR && parts[i + 1] === "worktrees");
11046
11252
  if (at < 0 || parts.length < at + 4) return cwd;
11047
- const root = parts.slice(0, at).join(path17.sep) || path17.sep;
11048
- const mapped = path17.join(root, ...parts.slice(at + 4));
11253
+ const root = parts.slice(0, at).join(path18.sep) || path18.sep;
11254
+ const mapped = path18.join(root, ...parts.slice(at + 4));
11049
11255
  return deps.isDirectory(mapped) ? mapped : root;
11050
11256
  }
11051
11257
  async function fromDirenv(cwd, deps) {
@@ -11075,18 +11281,18 @@ async function fromDirenv(cwd, deps) {
11075
11281
  return { env, blocked: null };
11076
11282
  }
11077
11283
  function findUp(start, relative17, deps) {
11078
- let dir = path17.resolve(start);
11284
+ let dir = path18.resolve(start);
11079
11285
  for (; ; ) {
11080
- const candidate = path17.join(dir, relative17);
11286
+ const candidate = path18.join(dir, relative17);
11081
11287
  if (deps.readFile(candidate) !== null) return candidate;
11082
- const parent = path17.dirname(dir);
11288
+ const parent = path18.dirname(dir);
11083
11289
  if (parent === dir) return null;
11084
11290
  dir = parent;
11085
11291
  }
11086
11292
  }
11087
11293
  async function isTracked(file, deps) {
11088
11294
  try {
11089
- const out = await deps.run("git", ["ls-files", "--error-unmatch", path17.basename(file)], { cwd: path17.dirname(file), env: deps.baseEnv });
11295
+ const out = await deps.run("git", ["ls-files", "--error-unmatch", path18.basename(file)], { cwd: path18.dirname(file), env: deps.baseEnv });
11090
11296
  return out.code === 0;
11091
11297
  } catch {
11092
11298
  return false;
@@ -11096,7 +11302,7 @@ function parseEnvFile(content, context, home) {
11096
11302
  const env = {};
11097
11303
  const lookup2 = (name) => env[name] ?? context[name] ?? "";
11098
11304
  const expand = (value) => {
11099
- const tilde = value.startsWith("~/") ? path17.join(home, value.slice(2)) : value;
11305
+ const tilde = value.startsWith("~/") ? path18.join(home, value.slice(2)) : value;
11100
11306
  return tilde.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, braced, bare) => lookup2(braced ?? bare));
11101
11307
  };
11102
11308
  for (const raw of content.split(/\r?\n/)) {
@@ -11366,7 +11572,7 @@ function readOnlySessionRules(site) {
11366
11572
  real = realpathSync4.native(root);
11367
11573
  } catch {
11368
11574
  }
11369
- for (const path46 of /* @__PURE__ */ new Set([root, real])) rules.push({ action: "external_directory", resource: `${path46.replace(/[\\/]$/, "")}/*`, effect: "allow" });
11575
+ for (const path47 of /* @__PURE__ */ new Set([root, real])) rules.push({ action: "external_directory", resource: `${path47.replace(/[\\/]$/, "")}/*`, effect: "allow" });
11370
11576
  }
11371
11577
  return rules;
11372
11578
  }
@@ -11492,6 +11698,14 @@ function runnerEnv(resolvedPath, workspace = {}, launch = {}, drop) {
11492
11698
  // src/services/harness/runnerProcess.ts
11493
11699
  var STDERR_TAIL_CHARS = 4e3;
11494
11700
  var CLOSE_GRACE_MS = 2e3;
11701
+ function releasePipes(child) {
11702
+ for (const stream of [child.stdin, child.stdout, child.stderr]) {
11703
+ if (stream instanceof Socket) stream.unref();
11704
+ }
11705
+ for (const stream of [child.stdout, child.stderr]) {
11706
+ if (stream instanceof Socket) stream.resume();
11707
+ }
11708
+ }
11495
11709
  var RunnerProcess = class {
11496
11710
  constructor(deps) {
11497
11711
  this.deps = deps;
@@ -11564,6 +11778,7 @@ var RunnerProcess = class {
11564
11778
  const grace = setTimeout(() => {
11565
11779
  for (const flush of [...this.stdoutEnds]) flush();
11566
11780
  this.end({ code, signal });
11781
+ releasePipes(child);
11567
11782
  }, CLOSE_GRACE_MS);
11568
11783
  grace.unref?.();
11569
11784
  void this.ended.then(() => clearTimeout(grace));
@@ -11662,11 +11877,11 @@ ${tail}` : ""}`;
11662
11877
 
11663
11878
  // src/services/sandbox/nativeShellLauncher.ts
11664
11879
  import { chmodSync, mkdtempSync as mkdtempSync3, realpathSync as realpathSync5, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
11665
- import * as os7 from "os";
11666
- import * as path18 from "path";
11880
+ import * as os8 from "os";
11881
+ import * as path19 from "path";
11667
11882
  function nativeShellInvocation(platform, env = process.env) {
11668
11883
  const shell = platform === "darwin" ? "/bin/zsh" : env.SHELL;
11669
- return { shell: shell && path18.posix.isAbsolute(shell) ? shell : "/bin/sh", args: ["-c"] };
11884
+ return { shell: shell && path19.posix.isAbsolute(shell) ? shell : "/bin/sh", args: ["-c"] };
11670
11885
  }
11671
11886
  var live = /* @__PURE__ */ new Set();
11672
11887
  var exitHook = false;
@@ -11698,10 +11913,10 @@ function launcherScript(launch) {
11698
11913
  }
11699
11914
  async function createNativeShellLauncher(opts) {
11700
11915
  const invocation = nativeShellInvocation(opts.platform ?? process.platform, opts.env);
11701
- const root = realpathSync5(mkdtempSync3(path18.join(os7.tmpdir(), "ordewell-native-shell-")));
11916
+ const root = realpathSync5(mkdtempSync3(path19.join(os8.tmpdir(), "ordewell-native-shell-")));
11702
11917
  let shortTmp = null;
11703
11918
  try {
11704
- shortTmp = realpathSync5(mkdtempSync3(path18.join("/tmp", "ow-")));
11919
+ shortTmp = realpathSync5(mkdtempSync3(path19.join("/tmp", "ow-")));
11705
11920
  const tmpdir7 = shortTmp;
11706
11921
  const launch = await opts.sandbox.launchNativeShell({
11707
11922
  workspace: opts.workspace,
@@ -11710,7 +11925,7 @@ async function createNativeShellLauncher(opts) {
11710
11925
  shellArgs: invocation.args,
11711
11926
  ...opts.env ? { env: opts.env } : {}
11712
11927
  });
11713
- const prefix = path18.join(root, "run");
11928
+ const prefix = path19.join(root, "run");
11714
11929
  writeFileSync3(prefix, launcherScript(launch), { mode: 448 });
11715
11930
  chmodSync(prefix, 448);
11716
11931
  keep(root);
@@ -12226,9 +12441,8 @@ var ReplyRuns = class {
12226
12441
  };
12227
12442
 
12228
12443
  // src/services/harness/claudeRepoConfig.ts
12229
- import * as os8 from "os";
12230
- import * as path19 from "path";
12231
- var MEMORY_FILES = ["CLAUDE.md", path19.join(".claude", "CLAUDE.md"), "CLAUDE.local.md"];
12444
+ import * as path20 from "path";
12445
+ var MEMORY_FILES = ["CLAUDE.md", path20.join(".claude", "CLAUDE.md"), "CLAUDE.local.md"];
12232
12446
  var MAX_IMPORT_DEPTH = 5;
12233
12447
  function importsOf(text4) {
12234
12448
  const prose = text4.replace(/```[\s\S]*?```/g, "").replace(/`[^`\n]*`/g, "");
@@ -12239,11 +12453,11 @@ function withImports(workspace, file, depth, seen, into) {
12239
12453
  seen.add(file);
12240
12454
  const text4 = readText(file);
12241
12455
  if (text4 === void 0) return;
12242
- into.push({ path: path19.relative(workspace, file), text: text4 });
12456
+ into.push({ path: path20.relative(workspace, file), text: text4 });
12243
12457
  if (depth >= MAX_IMPORT_DEPTH) return;
12244
12458
  for (const target of importsOf(text4)) {
12245
12459
  if (target.startsWith("~")) continue;
12246
- const abs = path19.resolve(path19.dirname(file), target);
12460
+ const abs = path20.resolve(path20.dirname(file), target);
12247
12461
  if (insideOnDisk(workspace, abs)) withImports(workspace, abs, depth + 1, seen, into);
12248
12462
  }
12249
12463
  }
@@ -12264,12 +12478,12 @@ var CLAUDE_REPO_CONFIG = {
12264
12478
  instructions(workspace) {
12265
12479
  const files = [];
12266
12480
  const seen = /* @__PURE__ */ new Set();
12267
- for (const name of MEMORY_FILES) withImports(workspace, path19.join(workspace, name), 0, seen, files);
12481
+ for (const name of MEMORY_FILES) withImports(workspace, path20.join(workspace, name), 0, seen, files);
12268
12482
  return files;
12269
12483
  },
12270
12484
  projectServers(workspace) {
12271
12485
  const source = ".mcp.json";
12272
- const text4 = readText(path19.join(workspace, source));
12486
+ const text4 = readText(path20.join(workspace, source));
12273
12487
  if (text4 === void 0) return NO_SERVERS;
12274
12488
  let parsed;
12275
12489
  try {
@@ -12287,13 +12501,14 @@ var CLAUDE_REPO_CONFIG = {
12287
12501
  return found;
12288
12502
  }
12289
12503
  };
12290
- function claudeLocalServers(configDir2, workspace) {
12291
- const file = configDir2 ? path19.join(configDir2, ".claude.json") : path19.join(os8.homedir(), ".claude.json");
12292
- const text4 = readText(file);
12504
+ function claudeLocalServers(env, workspace) {
12505
+ const configDir2 = env.CLAUDE_CONFIG_DIR?.trim();
12506
+ const file = expandRunnerHome(configDir2 ? path20.join(configDir2, ".claude.json") : "~/.claude.json", env);
12507
+ const text4 = file === null ? void 0 : readText(file);
12293
12508
  if (text4 === void 0) return {};
12294
12509
  try {
12295
12510
  const parsed = JSON.parse(text4);
12296
- const project = isRecord(parsed) && isRecord(parsed.projects) ? parsed.projects[path19.resolve(workspace)] : void 0;
12511
+ const project = isRecord(parsed) && isRecord(parsed.projects) ? parsed.projects[path20.resolve(workspace)] : void 0;
12297
12512
  return isRecord(project) && isRecord(project.mcpServers) ? project.mcpServers : {};
12298
12513
  } catch {
12299
12514
  return {};
@@ -12548,13 +12763,13 @@ var NO_AUTO_MEMORY = { CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1" };
12548
12763
  function inSavedOutput(abs, configDir2, sessionId) {
12549
12764
  let projects;
12550
12765
  try {
12551
- projects = realpathSync7.native(path20.join(configDir2, "projects"));
12766
+ projects = realpathSync7.native(path21.join(configDir2, "projects"));
12552
12767
  } catch {
12553
12768
  return false;
12554
12769
  }
12555
- const rel = path20.relative(projects, abs);
12556
- if (rel.startsWith("..") || path20.isAbsolute(rel)) return false;
12557
- const [project, session, folder] = rel.split(path20.sep);
12770
+ const rel = path21.relative(projects, abs);
12771
+ if (rel.startsWith("..") || path21.isAbsolute(rel)) return false;
12772
+ const [project, session, folder] = rel.split(path21.sep);
12558
12773
  return Boolean(project) && session === sessionId && folder === "tool-results";
12559
12774
  }
12560
12775
  var PATH_TOOLS = {
@@ -12792,7 +13007,7 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
12792
13007
  "--append-system-prompt",
12793
13008
  opts.systemPrompt
12794
13009
  ];
12795
- if (opts.mcp) args.push(...this.ordewellServerArgs(opts.mcp, "planner", this.plannerServers(opts, workspace)));
13010
+ if (opts.mcp) args.push(...this.ordewellServerArgs(opts.mcp, "planner", this.plannerServers(opts, { ...process.env, ...workspace })));
12796
13011
  if (opts.model) args.push("--model", opts.model);
12797
13012
  if (opts.effort && opts.effort !== "adaptive") args.push("--effort", opts.effort);
12798
13013
  if (opts.resumeSessionId) {
@@ -12800,7 +13015,8 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
12800
13015
  this.countFrom(opts.resumeSessionId, opts.resumeCost);
12801
13016
  this.pendingResume = opts.resumeSessionId;
12802
13017
  }
12803
- this.configDir = workspace.CLAUDE_CONFIG_DIR ?? process.env.CLAUDE_CONFIG_DIR ?? path20.join(os9.homedir(), ".claude");
13018
+ const env = { ...process.env, ...workspace };
13019
+ this.configDir = expandRunnerHome(claudeConfigDir(env), env);
12804
13020
  return {
12805
13021
  command: "claude",
12806
13022
  args,
@@ -12870,13 +13086,12 @@ var ClaudeCodeAdapter = class extends StdioAgentAdapter {
12870
13086
  /**
12871
13087
  * What user-only settings stop the CLI loading that is not the
12872
13088
  * repository's to choose (ADR-0028): the user's own local-scope servers,
12873
- * under the `CLAUDE_CONFIG_DIR` the planner runs with, and the project
12874
- * servers approved for it.
13089
+ * from the account in the environment the planner runs with, and the
13090
+ * project servers approved for it.
12875
13091
  */
12876
- plannerServers(opts, workspace) {
12877
- const configDir2 = workspace.CLAUDE_CONFIG_DIR ?? process.env.CLAUDE_CONFIG_DIR;
13092
+ plannerServers(opts, env) {
12878
13093
  return {
12879
- ...claudeLocalServers(configDir2, opts.cwd),
13094
+ ...claudeLocalServers(env, opts.cwd),
12880
13095
  ...Object.fromEntries((opts.projectServers ?? []).map((server4) => [server4.name, server4.definition]))
12881
13096
  };
12882
13097
  }
@@ -13640,8 +13855,8 @@ function runProbe(deps, cwd, env, flags) {
13640
13855
  }
13641
13856
 
13642
13857
  // src/services/harness/codexRepoConfig.ts
13643
- import * as path21 from "path";
13644
- var CONFIG_FILE = path21.join(".codex", "config.toml");
13858
+ import * as path22 from "path";
13859
+ var CONFIG_FILE = path22.join(".codex", "config.toml");
13645
13860
  function stringsOf(value) {
13646
13861
  return Array.isArray(value) ? value.filter((v) => typeof v === "string") : [];
13647
13862
  }
@@ -13667,9 +13882,9 @@ var CODEX_REPO_CONFIG = {
13667
13882
  const files = [];
13668
13883
  for (const dir of repoLevels(workspace)) {
13669
13884
  for (const name of ["AGENTS.override.md", "AGENTS.md"]) {
13670
- const text4 = readText(path21.join(dir, name));
13885
+ const text4 = readText(path22.join(dir, name));
13671
13886
  if (text4 === void 0) continue;
13672
- files.push({ path: path21.relative(workspace, path21.join(dir, name)) || name, text: text4 });
13887
+ files.push({ path: path22.relative(workspace, path22.join(dir, name)) || name, text: text4 });
13673
13888
  break;
13674
13889
  }
13675
13890
  }
@@ -13680,10 +13895,10 @@ var CODEX_REPO_CONFIG = {
13680
13895
  const byName = /* @__PURE__ */ new Map();
13681
13896
  const unreadable = [];
13682
13897
  for (const dir of repoLevels(workspace)) {
13683
- const file = path21.join(dir, CONFIG_FILE);
13898
+ const file = path22.join(dir, CONFIG_FILE);
13684
13899
  const text4 = readText(file);
13685
13900
  if (text4 === void 0) continue;
13686
- const source = path21.relative(workspace, file);
13901
+ const source = path22.relative(workspace, file);
13687
13902
  let parsed;
13688
13903
  try {
13689
13904
  parsed = parseToml(text4);
@@ -13703,9 +13918,9 @@ var CODEX_REPO_CONFIG = {
13703
13918
  };
13704
13919
  function untrustedProjects(workspace) {
13705
13920
  const projects = {};
13706
- for (let dir = path21.resolve(workspace); ; dir = path21.dirname(dir)) {
13921
+ for (let dir = path22.resolve(workspace); ; dir = path22.dirname(dir)) {
13707
13922
  projects[dir] = { trust_level: "untrusted" };
13708
- if (path21.dirname(dir) === dir) return projects;
13923
+ if (path22.dirname(dir) === dir) return projects;
13709
13924
  }
13710
13925
  }
13711
13926
 
@@ -14826,7 +15041,7 @@ ${this.exitMessage()}`);
14826
15041
  case "webSearch":
14827
15042
  return;
14828
15043
  case "fileChange": {
14829
- const paths = (item.changes ?? []).map((change) => change.path).filter((path46) => !!path46);
15044
+ const paths = (item.changes ?? []).map((change) => change.path).filter((path47) => !!path47);
14830
15045
  this.fileChangePaths.set(item.id, paths);
14831
15046
  if (this.startOpts?.kind === "task") {
14832
15047
  for (const change of item.changes ?? []) this.announceFileChange(item.id, change, emit, subagentId);
@@ -14929,20 +15144,20 @@ ${this.exitMessage()}`);
14929
15144
  announceFileChange(itemId, change, emit, subagentId) {
14930
15145
  const rows = this.fileChangeRows.get(itemId) ?? /* @__PURE__ */ new Map();
14931
15146
  this.fileChangeRows.set(itemId, rows);
14932
- const path46 = this.shownChangePath(change);
14933
- const known = rows.get(path46);
15147
+ const path47 = this.shownChangePath(change);
15148
+ const known = rows.get(path47);
14934
15149
  if (known) return known;
14935
- const row = rows.size === 0 ? itemId : `${itemId}:${path46}`;
14936
- rows.set(path46, row);
14937
- emit({ type: "tool_call", id: row, name: changeName(change), args: { path: path46 }, ...subagentId ? { subagentId } : {} });
15150
+ const row = rows.size === 0 ? itemId : `${itemId}:${path47}`;
15151
+ rows.set(path47, row);
15152
+ emit({ type: "tool_call", id: row, name: changeName(change), args: { path: path47 }, ...subagentId ? { subagentId } : {} });
14938
15153
  return row;
14939
15154
  }
14940
15155
  /** Relative to the task's worktree, where every path but a stray one lies; a move names both ends. */
14941
15156
  shownChangePath(change) {
14942
- const shown = (path46) => {
15157
+ const shown = (path47) => {
14943
15158
  const cwd = this.startOpts?.cwd;
14944
- const rel = cwd ? relative8(cwd, path46) : path46;
14945
- return rel && rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute6(rel) ? rel : path46;
15159
+ const rel = cwd ? relative8(cwd, path47) : path47;
15160
+ return rel && rel !== ".." && !rel.startsWith(`..${sep3}`) && !isAbsolute6(rel) ? rel : path47;
14946
15161
  };
14947
15162
  const from = shown(change.path ?? "");
14948
15163
  return change.kind?.move_path ? `${from} \u2192 ${shown(change.kind.move_path)}` : from;
@@ -14993,7 +15208,7 @@ import { realpathSync as realpathSync8 } from "fs";
14993
15208
 
14994
15209
  // src/services/harness/projectServers.ts
14995
15210
  import { createHash } from "crypto";
14996
- import * as path22 from "path";
15211
+ import * as path23 from "path";
14997
15212
  function canonical2(value) {
14998
15213
  if (Array.isArray(value)) return value.map(canonical2);
14999
15214
  if (!isRecord(value)) return value;
@@ -15004,7 +15219,7 @@ function projectServerScope(server4) {
15004
15219
  return `mcp-server:${server4.name}@${hash}`;
15005
15220
  }
15006
15221
  var scopeName = (scope) => scope.slice(0, scope.lastIndexOf("@"));
15007
- function fileProjectServerApprovals(file = path22.join(globalDataDir(), "project-mcp-servers.json")) {
15222
+ function fileProjectServerApprovals(file = path23.join(globalDataDir(), "project-mcp-servers.json")) {
15008
15223
  const load = () => {
15009
15224
  const text4 = readText(file);
15010
15225
  if (text4 === void 0) return {};
@@ -16167,8 +16382,8 @@ var OpenCodeAdapter = class extends ProcessLifecycle {
16167
16382
  this.v2 = new OpenCodeV2({
16168
16383
  version,
16169
16384
  nativeShell: this.nativeShell?.prefix ?? null,
16170
- request: (method, path46, body, signal, timeoutMs) => this.request(method, path46, body, signal, timeoutMs),
16171
- json: (method, path46, body, signal, timeoutMs) => this.json(method, path46, body, signal, timeoutMs),
16385
+ request: (method, path47, body, signal, timeoutMs) => this.request(method, path47, body, signal, timeoutMs),
16386
+ json: (method, path47, body, signal, timeoutMs) => this.json(method, path47, body, signal, timeoutMs),
16172
16387
  openEvents: (signal) => this.deps.fetch(`${this.baseUrl}/api/event`, { signal, headers: this.headers() }),
16173
16388
  processEnded: this.processEnded,
16174
16389
  isExited: () => this.exited,
@@ -16805,15 +17020,15 @@ var OpenCodeAdapter = class extends ProcessLifecycle {
16805
17020
  onActivity
16806
17021
  );
16807
17022
  }
16808
- async json(method, path46, body, signal, timeoutMs) {
16809
- const response = await this.request(method, path46, body, signal, timeoutMs);
16810
- if (!response.ok) throw new OpenCodeHttpError(`${method} ${path46} failed: ${response.status} ${response.statusText}`);
17023
+ async json(method, path47, body, signal, timeoutMs) {
17024
+ const response = await this.request(method, path47, body, signal, timeoutMs);
17025
+ if (!response.ok) throw new OpenCodeHttpError(`${method} ${path47} failed: ${response.status} ${response.statusText}`);
16811
17026
  return await response.json().catch(() => null);
16812
17027
  }
16813
17028
  /** `timeoutMs` null is for a turn's own POST, which the server answers when the turn is done. */
16814
- request(method, path46, body, signal, timeoutMs = REQUEST_TIMEOUT_MS) {
17029
+ request(method, path47, body, signal, timeoutMs = REQUEST_TIMEOUT_MS) {
16815
17030
  const deadline = withDeadline(signal, timeoutMs);
16816
- return this.deps.fetch(`${this.baseUrl}${path46}`, {
17031
+ return this.deps.fetch(`${this.baseUrl}${path47}`, {
16817
17032
  method,
16818
17033
  headers: this.headers({ "content-type": "application/json" }),
16819
17034
  ...body === void 0 ? {} : { body: JSON.stringify(body) },
@@ -16874,8 +17089,8 @@ var OpenCodeAdapter = class extends ProcessLifecycle {
16874
17089
  };
16875
17090
 
16876
17091
  // src/services/harness/openCodeRepoConfig.ts
16877
- import * as path23 from "path";
16878
- var CONFIG_FILES = ["opencode.json", "opencode.jsonc", path23.join(".opencode", "opencode.json"), path23.join(".opencode", "opencode.jsonc")];
17092
+ import * as path24 from "path";
17093
+ var CONFIG_FILES = ["opencode.json", "opencode.jsonc", path24.join(".opencode", "opencode.json"), path24.join(".opencode", "opencode.jsonc")];
16879
17094
  var INSTRUCTION_NAMES = ["AGENTS.md", "CLAUDE.md", "CONTEXT.md"];
16880
17095
  function server3(name, entry, source) {
16881
17096
  if (!isRecord(entry)) return "not an object";
@@ -16894,8 +17109,8 @@ var OPENCODE_REPO_CONFIG = {
16894
17109
  const levels = repoLevels(workspace);
16895
17110
  for (const name of INSTRUCTION_NAMES) {
16896
17111
  const files = levels.flatMap((dir) => {
16897
- const text4 = readText(path23.join(dir, name));
16898
- return text4 === void 0 ? [] : [{ path: path23.relative(workspace, path23.join(dir, name)) || name, text: text4 }];
17112
+ const text4 = readText(path24.join(dir, name));
17113
+ return text4 === void 0 ? [] : [{ path: path24.relative(workspace, path24.join(dir, name)) || name, text: text4 }];
16899
17114
  });
16900
17115
  if (files.length > 0) return files;
16901
17116
  }
@@ -16907,10 +17122,10 @@ var OPENCODE_REPO_CONFIG = {
16907
17122
  const unreadable = [];
16908
17123
  for (const dir of repoLevels(workspace)) {
16909
17124
  for (const name of CONFIG_FILES) {
16910
- const file = path23.join(dir, name);
17125
+ const file = path24.join(dir, name);
16911
17126
  const text4 = readText(file);
16912
17127
  if (text4 === void 0) continue;
16913
- const source = path23.relative(workspace, file);
17128
+ const source = path24.relative(workspace, file);
16914
17129
  let parsed;
16915
17130
  try {
16916
17131
  parsed = parseJsonc(text4);
@@ -17099,7 +17314,7 @@ function knownModelId(value, optionIds, shortcuts) {
17099
17314
 
17100
17315
  // src/services/ModelCatalog.ts
17101
17316
  import * as fs6 from "fs";
17102
- import * as path24 from "path";
17317
+ import * as path25 from "path";
17103
17318
  var CACHE_KEY = "model-catalog";
17104
17319
  var CACHE_TTL_MS = 60 * 60 * 1e3;
17105
17320
  function storageKey(baseUrl) {
@@ -17152,7 +17367,7 @@ var ModelCatalog = class {
17152
17367
  const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
17153
17368
  const key = storageKey(url);
17154
17369
  const cacheDir = globalCacheDir();
17155
- const cacheFile = path24.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
17370
+ const cacheFile = path25.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
17156
17371
  if (fs6.existsSync(cacheFile)) fs6.rmSync(cacheFile);
17157
17372
  } catch {
17158
17373
  }
@@ -17160,7 +17375,7 @@ var ModelCatalog = class {
17160
17375
  static readCache(key) {
17161
17376
  try {
17162
17377
  const cacheDir = globalCacheDir();
17163
- const cacheFile = path24.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
17378
+ const cacheFile = path25.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
17164
17379
  if (!fs6.existsSync(cacheFile)) return null;
17165
17380
  const raw = fs6.readFileSync(cacheFile, "utf8");
17166
17381
  const data = JSON.parse(raw);
@@ -17174,7 +17389,7 @@ var ModelCatalog = class {
17174
17389
  try {
17175
17390
  const cacheDir = globalCacheDir();
17176
17391
  fs6.mkdirSync(cacheDir, { recursive: true });
17177
- const cacheFile = path24.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
17392
+ const cacheFile = path25.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
17178
17393
  fs6.writeFileSync(
17179
17394
  cacheFile,
17180
17395
  JSON.stringify({ models, cachedAt: Date.now() }, null, 2)
@@ -17185,15 +17400,17 @@ var ModelCatalog = class {
17185
17400
  };
17186
17401
 
17187
17402
  // src/services/ModelDiscovery.ts
17188
- import * as path25 from "path";
17403
+ import * as path26 from "path";
17189
17404
  import { exec as exec2 } from "child_process";
17190
17405
  import { promisify as promisify2 } from "util";
17191
17406
  import * as fsSync5 from "fs";
17192
- import * as osMod from "os";
17193
17407
 
17194
17408
  // src/services/harness/appServerModels.ts
17195
17409
  import { spawn as nodeSpawn } from "child_process";
17196
17410
  var DEFAULT_TIMEOUT_MS3 = 2e4;
17411
+ function definedOnly(env) {
17412
+ return Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== void 0));
17413
+ }
17197
17414
  function parseRpcLine(line) {
17198
17415
  try {
17199
17416
  const value = JSON.parse(line);
@@ -17206,10 +17423,9 @@ async function openAppServer(command, args, launch) {
17206
17423
  const runner = new RunnerProcess({
17207
17424
  spawn: nodeSpawn,
17208
17425
  fetch: globalThis.fetch,
17209
- // The cwd is no workspace: discovery asks for the catalog before one is
17210
- // chosen, so it pays no direnv round trip before a spawn with a deadline.
17211
- // The account monitor passes the workspace's environment itself, since
17212
- // that can choose whose account is read.
17426
+ // The cwd is no workspace, so it pays no direnv round trip before a spawn
17427
+ // with a deadline. Discovery and the account monitor pass the workspace's
17428
+ // environment themselves, since that can choose whose account is read.
17213
17429
  workspaceEnv: async () => ({}),
17214
17430
  ...launch.deps
17215
17431
  });
@@ -17262,7 +17478,11 @@ async function openAppServer(command, args, launch) {
17262
17478
  }
17263
17479
  var APP_SERVER_CLIENT_INFO = { name: "ordewell", title: "Ordewell", version: "0.1.0" };
17264
17480
  async function discoverAppServerModels(command, args, options = {}) {
17265
- const server4 = await openAppServer(command, args, { cwd: process.cwd(), deps: options.deps });
17481
+ const env = options.env;
17482
+ const server4 = await openAppServer(command, args, {
17483
+ cwd: process.cwd(),
17484
+ deps: { ...env ? { workspaceEnv: async () => definedOnly(env) } : {}, ...options.deps }
17485
+ });
17266
17486
  if (!server4) return null;
17267
17487
  const { rpc } = server4;
17268
17488
  const asked = (async () => {
@@ -17289,10 +17509,10 @@ async function discoverAppServerModels(command, args, options = {}) {
17289
17509
  var execAsync2 = promisify2(exec2);
17290
17510
  var defaultExec2 = async (command, options) => {
17291
17511
  const PATH = await augmentedPath();
17292
- const { stdout } = await execAsync2(command, { ...options, maxBuffer: 32 * 1024 * 1024, env: withPath(process.env, PATH) });
17512
+ const { stdout } = await execAsync2(command, { timeout: options?.timeout, maxBuffer: 32 * 1024 * 1024, env: withPath(options?.env ?? process.env, PATH) });
17293
17513
  return { stdout: String(stdout) };
17294
17514
  };
17295
- var defaultAppServerClient = (command, args, options) => discoverAppServerModels(command, args, { timeout: options?.timeout });
17515
+ var defaultAppServerClient = (command, args, options) => discoverAppServerModels(command, args, { timeout: options?.timeout, env: options?.env });
17296
17516
  function effortVariant(effort) {
17297
17517
  return { id: effort, label: effort.charAt(0).toUpperCase() + effort.slice(1) };
17298
17518
  }
@@ -17326,7 +17546,7 @@ function geminiDiscoveryCacheKey(apiKey) {
17326
17546
  return {
17327
17547
  key: `gemini-models:${hash}`,
17328
17548
  dir,
17329
- file: path25.join(dir, `gemini_models_${hash}.json`)
17549
+ file: path26.join(dir, `gemini_models_${hash}.json`)
17330
17550
  };
17331
17551
  }
17332
17552
  function readGeminiCache(apiKey) {
@@ -17413,15 +17633,22 @@ function applyVariants(models, manifest) {
17413
17633
  if (!variants || variants.length === 0) return models;
17414
17634
  return models.map((m) => m.variants.length > 0 ? m : { ...m, variants });
17415
17635
  }
17416
- function resolveApiAuth(authMethods) {
17636
+ function listDir(dir) {
17637
+ try {
17638
+ return fsSync5.readdirSync(dir);
17639
+ } catch {
17640
+ return [];
17641
+ }
17642
+ }
17643
+ function resolveApiAuth(authMethods, env) {
17417
17644
  for (const method of authMethods) {
17418
17645
  try {
17419
17646
  let token;
17420
17647
  if (method.type === "env") {
17421
- token = process.env[method.varName];
17648
+ token = env[method.varName];
17422
17649
  } else {
17423
- const filePath = method.path.replace(/^~/, osMod.homedir());
17424
- if (!fsSync5.existsSync(filePath)) continue;
17650
+ const filePath = expandRunnerHome(typeof method.path === "function" ? method.path(env) : method.path, env);
17651
+ if (!filePath || !fsSync5.existsSync(filePath)) continue;
17425
17652
  const data = JSON.parse(fsSync5.readFileSync(filePath, "utf8"));
17426
17653
  token = method.jsonPath.split(".").reduce((obj, key) => {
17427
17654
  if (obj && typeof obj === "object") return obj[key];
@@ -17437,8 +17664,8 @@ function resolveApiAuth(authMethods) {
17437
17664
  }
17438
17665
  return null;
17439
17666
  }
17440
- async function discoverFromApi(manifest, config, fetchImpl) {
17441
- const auth = resolveApiAuth(config.auth);
17667
+ async function discoverFromApi(manifest, config, env, fetchImpl) {
17668
+ const auth = resolveApiAuth(config.auth, env);
17442
17669
  if (!auth) return null;
17443
17670
  try {
17444
17671
  const headers = { ...config.headers, [auth.header]: auth.value };
@@ -17454,7 +17681,7 @@ async function discoverFromApi(manifest, config, fetchImpl) {
17454
17681
  function wasKilled(err) {
17455
17682
  return !!err && typeof err === "object" && err.killed === true;
17456
17683
  }
17457
- async function discoverFromCommand(manifest, execImpl) {
17684
+ async function discoverFromCommand(manifest, execImpl, env) {
17458
17685
  const discovery = manifest.modelDiscovery;
17459
17686
  const commands = discovery.discoveryCommands ?? [
17460
17687
  { command: discovery.command, args: discovery.args || [], parser: discovery.parser }
@@ -17463,7 +17690,7 @@ async function discoverFromCommand(manifest, execImpl) {
17463
17690
  let timedOut2 = false;
17464
17691
  for (const cmd of commands) {
17465
17692
  try {
17466
- const { stdout } = await execImpl(`${cmd.command} ${cmd.args.join(" ")}`, { timeout: 45e3 });
17693
+ const { stdout } = await execImpl(`${cmd.command} ${cmd.args.join(" ")}`, { timeout: 45e3, env });
17467
17694
  const models = parseModelOutput(stdout, cmd.parser, manifest);
17468
17695
  if (models.length > 0) return { models: applyVariants(mergeCanonicalAliases(models, manifest), manifest), timedOut: timedOut2 };
17469
17696
  } catch (err) {
@@ -17529,21 +17756,26 @@ var ModelDiscovery = class {
17529
17756
  * same background service.
17530
17757
  */
17531
17758
  inflight = /* @__PURE__ */ new Map();
17759
+ /** Bumped by every `clear()`: a discovery begun before one answers its own lookups but fills no cache. */
17760
+ generation = 0;
17532
17761
  registry;
17533
17762
  execImpl;
17534
17763
  fetchImpl;
17535
17764
  appServerClient;
17536
17765
  readFileImpl;
17537
17766
  now;
17538
- constructor(registry, execImpl = defaultExec2, fetchImpl, appServerClient = defaultAppServerClient, readFileImpl, now = Date.now) {
17767
+ env;
17768
+ constructor(registry, execImpl = defaultExec2, fetchImpl, appServerClient = defaultAppServerClient, readFileImpl, now = Date.now, env = async () => process.env) {
17539
17769
  this.registry = registry;
17540
17770
  this.execImpl = execImpl;
17541
17771
  this.fetchImpl = fetchImpl;
17542
17772
  this.appServerClient = appServerClient;
17543
17773
  this.now = now;
17774
+ this.env = env;
17544
17775
  this.readFileImpl = readFileImpl ?? ((filePath) => {
17545
17776
  try {
17546
- return fsSync5.readFileSync(filePath.replace(/^~/, osMod.homedir()), "utf8");
17777
+ const file = expandRunnerHome(filePath);
17778
+ return file ? fsSync5.readFileSync(file, "utf8") : null;
17547
17779
  } catch {
17548
17780
  return null;
17549
17781
  }
@@ -17555,16 +17787,17 @@ var ModelDiscovery = class {
17555
17787
  * null when both fail so `discover()` falls through to the remaining
17556
17788
  * methods.
17557
17789
  */
17558
- async discoverFromAppServer(manifest) {
17790
+ async discoverFromAppServer(manifest, env) {
17559
17791
  const config = manifest.modelDiscovery.appServer;
17560
17792
  if (!config) return null;
17561
- const raw = await this.appServerClient(config.command, config.args);
17793
+ const raw = await this.appServerClient(config.command, config.args, { env });
17562
17794
  if (raw) {
17563
17795
  const models = parseAppServerModels(raw);
17564
17796
  if (models.length > 0) return models;
17565
17797
  }
17566
- if (config.cacheFile) {
17567
- const content = this.readFileImpl(config.cacheFile);
17798
+ const cacheFile = config.cacheFile && expandRunnerHome(typeof config.cacheFile === "function" ? config.cacheFile(env) : config.cacheFile, env);
17799
+ if (cacheFile) {
17800
+ const content = this.readFileImpl(cacheFile);
17568
17801
  if (content) {
17569
17802
  const models = parseCodexModelsCache(content);
17570
17803
  if (models.length > 0) return models;
@@ -17602,37 +17835,60 @@ var ModelDiscovery = class {
17602
17835
  if (!manifest) {
17603
17836
  return [];
17604
17837
  }
17605
- let models;
17838
+ const generation2 = this.generation;
17839
+ const env = await this.env().catch(() => process.env);
17840
+ const found = await this.discoverFromSources(manifest, env);
17841
+ const models = this.withSettingsModels(found.models, manifest, env);
17842
+ if (generation2 !== this.generation) return models;
17843
+ if (found.cacheable && models.length > 0) this.cache.set(runner, models);
17844
+ else this.misses.set(runner, { models, at: this.now() });
17845
+ return models;
17846
+ }
17847
+ async discoverFromSources(manifest, env) {
17606
17848
  let cacheable = true;
17607
17849
  const discovery = manifest.modelDiscovery;
17608
17850
  if (discovery.appServer) {
17609
- const appServerModels = await this.discoverFromAppServer(manifest);
17610
- if (appServerModels && appServerModels.length > 0) {
17611
- this.cache.set(runner, appServerModels);
17612
- return appServerModels;
17613
- }
17851
+ const appServerModels = await this.discoverFromAppServer(manifest, env);
17852
+ if (appServerModels && appServerModels.length > 0) return { models: appServerModels, cacheable: true };
17614
17853
  cacheable = false;
17615
17854
  }
17616
17855
  if (discovery.apiDiscovery) {
17617
- const apiModels = await discoverFromApi(manifest, discovery.apiDiscovery, this.fetchImpl);
17856
+ const apiModels = await discoverFromApi(manifest, discovery.apiDiscovery, env, this.fetchImpl);
17618
17857
  if (apiModels && apiModels.length > 0) {
17619
- models = applyVariants(mergeCanonicalAliases(apiModels, manifest), manifest);
17620
- if (cacheable && models.length > 0) this.cache.set(runner, models);
17621
- return models;
17858
+ return { models: applyVariants(mergeCanonicalAliases(apiModels, manifest), manifest), cacheable };
17622
17859
  }
17623
17860
  }
17624
17861
  if (discovery.method === "command" && (discovery.command || discovery.discoveryCommands?.length)) {
17625
- const result = await discoverFromCommand(manifest, this.execImpl);
17626
- models = result.models;
17627
- cacheable = !result.fromFallback;
17628
- } else {
17629
- models = buildFallbackModels(manifest);
17862
+ const result = await discoverFromCommand(manifest, this.execImpl, env);
17863
+ return { models: result.models, cacheable: !result.fromFallback };
17630
17864
  }
17631
- if (cacheable && models.length > 0) this.cache.set(runner, models);
17632
- else this.misses.set(runner, { models, at: this.now() });
17633
- return models;
17865
+ return { models: buildFallbackModels(manifest), cacheable };
17866
+ }
17867
+ /**
17868
+ * Append the models registered in the runner's own settings. Read on every
17869
+ * discovery rather than cached apart, so a refresh picks up an edit;
17870
+ * whatever the sources already offer keeps its entry and metadata.
17871
+ */
17872
+ withSettingsModels(models, manifest, env) {
17873
+ const read2 = manifest.modelDiscovery.settingsModels;
17874
+ if (!read2) return models;
17875
+ const files = {
17876
+ read: (filePath) => {
17877
+ const file = expandRunnerHome(filePath, env);
17878
+ return file ? this.readFileImpl(file) : null;
17879
+ },
17880
+ list: (dir) => {
17881
+ const expanded = expandRunnerHome(dir, env);
17882
+ return expanded ? listDir(expanded) : [];
17883
+ },
17884
+ env
17885
+ };
17886
+ const seen = new Set(models.map((m) => m.modelId));
17887
+ const added = read2(files).filter((m) => !seen.has(m.modelId));
17888
+ return added.length > 0 ? [...models, ...applyVariants(added, manifest)] : models;
17634
17889
  }
17635
17890
  clear() {
17891
+ this.generation++;
17636
17892
  this.cache.clear();
17637
17893
  this.misses.clear();
17638
17894
  this.inflight.clear();
@@ -17798,7 +18054,7 @@ function parseMaxParallel(value) {
17798
18054
 
17799
18055
  // src/services/SettingsService.ts
17800
18056
  import * as fs12 from "fs";
17801
- import * as path32 from "path";
18057
+ import * as path33 from "path";
17802
18058
 
17803
18059
  // src/utils/fileLock.ts
17804
18060
  import * as fs7 from "fs";
@@ -18088,7 +18344,7 @@ function stable(value) {
18088
18344
  }
18089
18345
 
18090
18346
  // src/services/runnerExit.ts
18091
- var USAGE_LIMIT_RE = /\b(?:usage|session|weekly|daily|monthly) limit\b|\brate limit (?:exceeded|reached)\b|\brate[- ]limited\b|\blimit (?:will )?reset\b|\bquota (?:exceeded|reached)\b|\btoo many requests\b/i;
18347
+ var USAGE_LIMIT_RE = /\b(?:usage|session|weekly|daily|monthly) limit\b|\byou['’]ve hit your (?:[\w'’-]+ ){0,3}?limit\b|\brate limit (?:exceeded|reached)\b|\brate[- ]limited\b|\blimit (?:will )?reset\b|\bquota (?:exceeded|reached)\b|\btoo many requests\b/i;
18092
18348
  var USAGE_LIMIT_TAIL = 4096;
18093
18349
  function classifyRunnerStop(said2) {
18094
18350
  if (typeof said2 !== "string" && said2.usageLimit !== void 0) return said2.usageLimit ? "usage-limit" : "stopped";
@@ -18316,8 +18572,8 @@ var TurnStream = class {
18316
18572
  };
18317
18573
 
18318
18574
  // src/services/skillInvocation.ts
18319
- import * as os10 from "os";
18320
- function abbreviateHome(file, home = os10.homedir(), platform = process.platform) {
18575
+ import * as os9 from "os";
18576
+ function abbreviateHome(file, home = os9.homedir(), platform = process.platform) {
18321
18577
  const win = platform === "win32";
18322
18578
  const normal = (p) => win ? p.replace(/\\/g, "/") : p;
18323
18579
  const shown = normal(file);
@@ -18332,13 +18588,13 @@ function modelInvocablePlannerSkills(skills) {
18332
18588
  function modelInvocableTaskSkills(skills) {
18333
18589
  return skills.filter((skill) => skill.appliesTo === "task" && skill.modelInvocable);
18334
18590
  }
18335
- function snapshotSkill(skill, invokedBy, home = os10.homedir()) {
18591
+ function snapshotSkill(skill, invokedBy, home = os9.homedir()) {
18336
18592
  return { invokedBy, name: skill.name, source: skill.source, path: abbreviateHome(skill.path, home), content: skill.content };
18337
18593
  }
18338
18594
  function attachSkill(skill, home) {
18339
18595
  return { ...snapshotSkill(skill, "user", home), content: "", attaches: { description: skill.description } };
18340
18596
  }
18341
- function resolveSkillInvocation(text4, skillsService, home = os10.homedir()) {
18597
+ function resolveSkillInvocation(text4, skillsService, home = os9.homedir()) {
18342
18598
  const skills = skillTokens(text4).flatMap(({ name }) => {
18343
18599
  const skill = skillsService.findSkill(name);
18344
18600
  if (!skill?.userInvocable) return [];
@@ -18402,33 +18658,33 @@ ${skillBlock(entry.skill)}`, timestamp: entry.timestamp });
18402
18658
  }
18403
18659
 
18404
18660
  // src/services/taskSkills.ts
18405
- import * as path31 from "path";
18661
+ import * as path32 from "path";
18406
18662
 
18407
18663
  // src/services/SkillsService.ts
18408
18664
  import { createHash as createHash2 } from "crypto";
18409
18665
  import * as fs11 from "fs";
18410
- import * as path30 from "path";
18666
+ import * as path31 from "path";
18411
18667
 
18412
18668
  // src/services/builtinSkills.ts
18413
- import * as path26 from "path";
18669
+ import * as path27 from "path";
18414
18670
  import { fileURLToPath } from "url";
18415
18671
  function builtinSkillsDir() {
18416
- const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path26.dirname(fileURLToPath(
18672
+ const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path27.dirname(fileURLToPath(
18417
18673
  // @ts-expect-error - import.meta is only valid under an ESM module setting
18418
18674
  import.meta.url
18419
18675
  ));
18420
- return path26.join(dir, "..", "skills");
18676
+ return path27.join(dir, "..", "skills");
18421
18677
  }
18422
18678
 
18423
18679
  // src/services/repoGroup.ts
18424
18680
  import * as fs10 from "fs";
18425
- import * as path29 from "path";
18681
+ import * as path30 from "path";
18426
18682
 
18427
18683
  // src/services/worktreeBootstrap.ts
18428
18684
  import { exec as exec3 } from "child_process";
18429
18685
  import { promisify as promisify4 } from "util";
18430
18686
  import * as fs9 from "fs";
18431
- import * as path28 from "path";
18687
+ import * as path29 from "path";
18432
18688
 
18433
18689
  // src/services/gitExec.ts
18434
18690
  import { execFile as execFile4 } from "child_process";
@@ -18510,7 +18766,7 @@ function subcommandOf2(args) {
18510
18766
 
18511
18767
  // src/services/worktreeLink.ts
18512
18768
  import * as fs8 from "fs";
18513
- import * as path27 from "path";
18769
+ import * as path28 from "path";
18514
18770
  function linkPath(source, target, platform, hardLink = fs8.linkSync) {
18515
18771
  const isDir = fs8.statSync(source).isDirectory();
18516
18772
  if (platform !== "win32") {
@@ -18535,8 +18791,8 @@ function mirrorDir(source, target, opts) {
18535
18791
  const mirror = (from, to, prefix, top) => {
18536
18792
  fs8.mkdirSync(to, { recursive: true });
18537
18793
  for (const entry of fs8.readdirSync(from, { withFileTypes: true })) {
18538
- const name = path27.join(prefix, entry.name);
18539
- const at = { from: path27.join(from, entry.name), to: path27.join(to, entry.name), name };
18794
+ const name = path28.join(prefix, entry.name);
18795
+ const at = { from: path28.join(from, entry.name), to: path28.join(to, entry.name), name };
18540
18796
  if (entry.isSymbolicLink()) links.push(at);
18541
18797
  else if (top && entry.isDirectory() && (entry.name.startsWith("@") || entry.name === ".bin")) mirror(at.from, at.to, name, false);
18542
18798
  else if (linkPath(at.from, at.to, opts.platform, opts.hardLink) === "copy") copied.push(name);
@@ -18556,16 +18812,16 @@ function mirrorDir(source, target, opts) {
18556
18812
  }
18557
18813
  function relink(from, to, opts) {
18558
18814
  const text4 = fs8.readlinkSync(from);
18559
- const pointee = path27.isAbsolute(text4) ? rebased(text4, opts) : text4;
18815
+ const pointee = path28.isAbsolute(text4) ? rebased(text4, opts) : text4;
18560
18816
  if (opts.platform !== "win32") {
18561
- if (!fs8.existsSync(path27.resolve(path27.dirname(to), pointee)) && fs8.existsSync(path27.resolve(path27.dirname(from), pointee))) {
18562
- fs8.symlinkSync(rebased(path27.resolve(path27.dirname(from), pointee), opts), to);
18817
+ if (!fs8.existsSync(path28.resolve(path28.dirname(to), pointee)) && fs8.existsSync(path28.resolve(path28.dirname(from), pointee))) {
18818
+ fs8.symlinkSync(rebased(path28.resolve(path28.dirname(from), pointee), opts), to);
18563
18819
  return "symlink";
18564
18820
  }
18565
18821
  fs8.symlinkSync(pointee, to);
18566
18822
  return "symlink";
18567
18823
  }
18568
- const resolved = fs8.existsSync(path27.resolve(path27.dirname(to), pointee)) ? path27.resolve(path27.dirname(to), pointee) : rebased(path27.resolve(path27.dirname(from), pointee), opts);
18824
+ const resolved = fs8.existsSync(path28.resolve(path28.dirname(to), pointee)) ? path28.resolve(path28.dirname(to), pointee) : rebased(path28.resolve(path28.dirname(from), pointee), opts);
18569
18825
  try {
18570
18826
  fs8.statSync(resolved);
18571
18827
  } catch {
@@ -18574,9 +18830,9 @@ function relink(from, to, opts) {
18574
18830
  return linkPath(resolved, to, opts.platform, opts.hardLink);
18575
18831
  }
18576
18832
  function rebased(pointee, opts) {
18577
- const rel = path27.relative(opts.from, pointee);
18578
- const inside2 = rel !== "" && !rel.startsWith("..") && !path27.isAbsolute(rel);
18579
- return inside2 && !rel.split(/[\\/]/).includes("node_modules") ? path27.join(opts.to, rel) : pointee;
18833
+ const rel = path28.relative(opts.from, pointee);
18834
+ const inside2 = rel !== "" && !rel.startsWith("..") && !path28.isAbsolute(rel);
18835
+ return inside2 && !rel.split(/[\\/]/).includes("node_modules") ? path28.join(opts.to, rel) : pointee;
18580
18836
  }
18581
18837
 
18582
18838
  // src/services/worktreeBootstrap.ts
@@ -18593,7 +18849,7 @@ function lexists(target) {
18593
18849
  return false;
18594
18850
  }
18595
18851
  }
18596
- function listDir(dir) {
18852
+ function listDir2(dir) {
18597
18853
  try {
18598
18854
  return fs9.readdirSync(dir).sort();
18599
18855
  } catch {
@@ -18613,18 +18869,18 @@ function matchLinks(root, patterns) {
18613
18869
  }
18614
18870
  function segmentMatches(root, base, segment) {
18615
18871
  const under = (name) => base ? `${base}/${name}` : name;
18616
- if (!/[*?]/.test(segment)) return lexists(path28.join(root, base, segment)) ? [under(segment)] : [];
18872
+ if (!/[*?]/.test(segment)) return lexists(path29.join(root, base, segment)) ? [under(segment)] : [];
18617
18873
  const pattern = new RegExp(`^${segment.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".")}$`);
18618
- return listDir(path28.join(root, base)).filter((name) => !NEVER_SCANNED.has(name) && pattern.test(name)).map(under);
18874
+ return listDir2(path29.join(root, base)).filter((name) => !NEVER_SCANNED.has(name) && pattern.test(name)).map(under);
18619
18875
  }
18620
18876
  function installDirs(root) {
18621
18877
  const packages = matchLinks(root, workspaceGlobs(root)).map((pkg) => `${pkg}/node_modules`);
18622
- return ["node_modules", ...packages].filter((dir) => lexists(path28.join(root, dir)));
18878
+ return ["node_modules", ...packages].filter((dir) => lexists(path29.join(root, dir)));
18623
18879
  }
18624
18880
  function workspaceGlobs(root) {
18625
18881
  let manifest;
18626
18882
  try {
18627
- manifest = JSON.parse(fs9.readFileSync(path28.join(root, "package.json"), "utf8"));
18883
+ manifest = JSON.parse(fs9.readFileSync(path29.join(root, "package.json"), "utf8"));
18628
18884
  } catch {
18629
18885
  return [];
18630
18886
  }
@@ -18639,19 +18895,19 @@ async function bootstrap(repo, cwd, opts) {
18639
18895
  const linked = [];
18640
18896
  const copied = [];
18641
18897
  const link = (name) => {
18642
- const target = path28.join(cwd, name);
18898
+ const target = path29.join(cwd, name);
18643
18899
  if (lexists(target)) return;
18644
- fs9.mkdirSync(path28.dirname(target), { recursive: true });
18645
- if (linkPath(path28.join(repo.root, name), target, opts.platform) === "copy") copied.push(name);
18900
+ fs9.mkdirSync(path29.dirname(target), { recursive: true });
18901
+ if (linkPath(path29.join(repo.root, name), target, opts.platform) === "copy") copied.push(name);
18646
18902
  linked.push(name);
18647
18903
  };
18648
18904
  const mirror = (name) => {
18649
- const target = path28.join(cwd, name);
18650
- if (lexists(target) || !fs9.existsSync(path28.dirname(target))) return;
18651
- const source = path28.join(repo.root, name);
18905
+ const target = path29.join(cwd, name);
18906
+ if (lexists(target) || !fs9.existsSync(path29.dirname(target))) return;
18907
+ const source = path29.join(repo.root, name);
18652
18908
  if (!fs9.lstatSync(source).isDirectory()) return link(name);
18653
18909
  const mirrored = mirrorDir(source, target, { platform: opts.platform, from: repo.root, to: cwd });
18654
- copied.push(...mirrored.map((entry) => path28.join(name, entry)));
18910
+ copied.push(...mirrored.map((entry) => path29.join(name, entry)));
18655
18911
  linked.push(name);
18656
18912
  };
18657
18913
  if (!setup) {
@@ -18686,12 +18942,12 @@ async function runSetup(command, repo, cwd, resolvePath) {
18686
18942
 
18687
18943
  // src/services/repoGroup.ts
18688
18944
  function hasGitEntry(dir) {
18689
- return fs10.existsSync(path29.join(dir, ".git"));
18945
+ return fs10.existsSync(path30.join(dir, ".git"));
18690
18946
  }
18691
18947
  function groupPathOf(listed3) {
18692
18948
  const slashed = listed3.trim().replace(/\\/g, "/");
18693
- if (path29.posix.isAbsolute(slashed) || path29.win32.isAbsolute(slashed)) return null;
18694
- const rel = path29.posix.normalize(slashed).replace(/\/+$/, "");
18949
+ if (path30.posix.isAbsolute(slashed) || path30.win32.isAbsolute(slashed)) return null;
18950
+ const rel = path30.posix.normalize(slashed).replace(/\/+$/, "");
18695
18951
  return rel === "" || rel === "." || rel === ".." || rel.startsWith("../") ? null : rel;
18696
18952
  }
18697
18953
  function childDirs(dir) {
@@ -18702,14 +18958,14 @@ function childDirs(dir) {
18702
18958
  }
18703
18959
  }
18704
18960
  function groupPaths(workspaceRoot, listed3) {
18705
- if (listed3.length === 0) return childDirs(workspaceRoot).filter((name) => hasGitEntry(path29.join(workspaceRoot, name)));
18706
- const paths = listed3.map(groupPathOf).filter((rel) => rel !== null && hasGitEntry(path29.join(workspaceRoot, rel)));
18961
+ if (listed3.length === 0) return childDirs(workspaceRoot).filter((name) => hasGitEntry(path30.join(workspaceRoot, name)));
18962
+ const paths = listed3.map(groupPathOf).filter((rel) => rel !== null && hasGitEntry(path30.join(workspaceRoot, rel)));
18707
18963
  return [...new Set(paths)].sort();
18708
18964
  }
18709
18965
  function workspaceRepoGroup(workspaceRoot, listed3) {
18710
- for (let dir = path29.resolve(workspaceRoot); ; dir = path29.dirname(dir)) {
18966
+ for (let dir = path30.resolve(workspaceRoot); ; dir = path30.dirname(dir)) {
18711
18967
  if (hasGitEntry(dir)) return [SELF_REPO];
18712
- if (path29.dirname(dir) === dir) break;
18968
+ if (path30.dirname(dir) === dir) break;
18713
18969
  }
18714
18970
  return groupPaths(workspaceRoot, listed3);
18715
18971
  }
@@ -18790,8 +19046,8 @@ function copyDirSync(src, dest) {
18790
19046
  fs11.mkdirSync(dest, { recursive: true });
18791
19047
  const entries = fs11.readdirSync(src, { withFileTypes: true });
18792
19048
  for (const entry of entries) {
18793
- const srcPath = path30.join(src, entry.name);
18794
- const destPath = path30.join(dest, entry.name);
19049
+ const srcPath = path31.join(src, entry.name);
19050
+ const destPath = path31.join(dest, entry.name);
18795
19051
  if (entry.isDirectory()) copyDirSync(srcPath, destPath);
18796
19052
  else fs11.copyFileSync(srcPath, destPath);
18797
19053
  }
@@ -18815,7 +19071,7 @@ function readDir(dir, source) {
18815
19071
  if (!fs11.existsSync(dir)) return { skills, invalid };
18816
19072
  for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
18817
19073
  if (!entry.isDirectory()) continue;
18818
- const skillFile = path30.join(dir, entry.name, "SKILL.md");
19074
+ const skillFile = path31.join(dir, entry.name, "SKILL.md");
18819
19075
  if (!fs11.existsSync(skillFile)) continue;
18820
19076
  if (!isSkillName(entry.name)) {
18821
19077
  invalid.push({ name: entry.name, path: skillFile, source, reason: INVALID_SKILL_NAME_REASON });
@@ -18834,15 +19090,15 @@ function skillFileIn(dir, name) {
18834
19090
  return void 0;
18835
19091
  }
18836
19092
  if (!entries.some((entry) => entry.isDirectory() && entry.name === name)) return void 0;
18837
- const skillFile = path30.join(dir, name, "SKILL.md");
19093
+ const skillFile = path31.join(dir, name, "SKILL.md");
18838
19094
  return fs11.existsSync(skillFile) ? skillFile : void 0;
18839
19095
  }
18840
19096
  function skillsDirOf(root) {
18841
- return path30.join(root, STATE_DIR, "skills");
19097
+ return path31.join(root, STATE_DIR, "skills");
18842
19098
  }
18843
19099
  function workspaceSkillRoots(workspaceRoot, repos, checkout = workspaceRoot) {
18844
19100
  if (repos.length === 0 || repos.includes(SELF_REPO)) return [checkout];
18845
- return [workspaceRoot, ...repos.map((repo) => path30.join(checkout, repo))];
19101
+ return [workspaceRoot, ...repos.map((repo) => path31.join(checkout, repo))];
18846
19102
  }
18847
19103
  var SkillsService = class _SkillsService {
18848
19104
  workspaceRoots;
@@ -18865,11 +19121,11 @@ var SkillsService = class _SkillsService {
18865
19121
  return this.workspaceRoots.map(skillsDirOf);
18866
19122
  }
18867
19123
  globalDir() {
18868
- return path30.join(globalDataDir(), "skills");
19124
+ return path31.join(globalDataDir(), "skills");
18869
19125
  }
18870
19126
  readSeedManifest() {
18871
19127
  try {
18872
- const parsed = JSON.parse(fs11.readFileSync(path30.join(this.globalDir(), SEED_MANIFEST), "utf8"));
19128
+ const parsed = JSON.parse(fs11.readFileSync(path31.join(this.globalDir(), SEED_MANIFEST), "utf8"));
18873
19129
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
18874
19130
  return Object.fromEntries(
18875
19131
  Object.entries(parsed).filter((entry) => typeof entry[1] === "string")
@@ -18885,7 +19141,7 @@ var SkillsService = class _SkillsService {
18885
19141
  manifest[name] = hash;
18886
19142
  try {
18887
19143
  fs11.mkdirSync(this.globalDir(), { recursive: true });
18888
- fs11.writeFileSync(path30.join(this.globalDir(), SEED_MANIFEST), JSON.stringify(manifest, null, 2));
19144
+ fs11.writeFileSync(path31.join(this.globalDir(), SEED_MANIFEST), JSON.stringify(manifest, null, 2));
18889
19145
  } catch (err) {
18890
19146
  console.warn(`Could not record seeded skill ${name}: ${String(err)}`);
18891
19147
  }
@@ -18896,11 +19152,11 @@ var SkillsService = class _SkillsService {
18896
19152
  return this.readSeedManifest()[name] === currentHash || (PRIOR_BUILTIN_HASHES[name] ?? []).includes(currentHash);
18897
19153
  }
18898
19154
  seed(name) {
18899
- const dest = path30.join(this.globalDir(), name);
19155
+ const dest = path31.join(this.globalDir(), name);
18900
19156
  const exists = fs11.existsSync(dest);
18901
19157
  let srcDir;
18902
19158
  try {
18903
- srcDir = path30.join(builtinSkillsDir(), name);
19159
+ srcDir = path31.join(builtinSkillsDir(), name);
18904
19160
  } catch (err) {
18905
19161
  if (!exists) console.warn(`Could not locate built-in skills dir: ${String(err)}`);
18906
19162
  return false;
@@ -18909,9 +19165,9 @@ var SkillsService = class _SkillsService {
18909
19165
  if (!exists) console.warn(`Built-in skill not found in package: ${name}`);
18910
19166
  return false;
18911
19167
  }
18912
- const srcHash = fileHash(path30.join(srcDir, "SKILL.md"));
19168
+ const srcHash = fileHash(path31.join(srcDir, "SKILL.md"));
18913
19169
  if (exists) {
18914
- const currentHash = fileHash(path30.join(dest, "SKILL.md"));
19170
+ const currentHash = fileHash(path31.join(dest, "SKILL.md"));
18915
19171
  if (currentHash !== void 0 && currentHash === srcHash) {
18916
19172
  this.recordSeed(name, currentHash);
18917
19173
  return false;
@@ -18955,7 +19211,7 @@ var SkillsService = class _SkillsService {
18955
19211
  if (entries.length !== 1 || entries[0].name !== "SKILL.md" || !entries[0].isFile()) return false;
18956
19212
  let raw;
18957
19213
  try {
18958
- raw = readSkillText(path30.join(dir, "SKILL.md"));
19214
+ raw = readSkillText(path31.join(dir, "SKILL.md"));
18959
19215
  } catch {
18960
19216
  return false;
18961
19217
  }
@@ -18963,7 +19219,7 @@ var SkillsService = class _SkillsService {
18963
19219
  }
18964
19220
  pruneRetired() {
18965
19221
  for (const name of RETIRED_BUILTIN_SKILL_NAMES) {
18966
- const dest = path30.join(this.globalDir(), name);
19222
+ const dest = path31.join(this.globalDir(), name);
18967
19223
  if (!fs11.existsSync(dest)) continue;
18968
19224
  if (!this.isUnmodifiedRetiredSeed(dest, name)) continue;
18969
19225
  try {
@@ -19101,12 +19357,12 @@ var runGit = execFileWithTimeout(1e4);
19101
19357
  async function committedInHead(files) {
19102
19358
  const byDir = /* @__PURE__ */ new Map();
19103
19359
  for (const file of new Set(files)) {
19104
- const dir = path31.dirname(path31.dirname(file));
19360
+ const dir = path32.dirname(path32.dirname(file));
19105
19361
  byDir.set(dir, [...byDir.get(dir) ?? [], file]);
19106
19362
  }
19107
19363
  const result = /* @__PURE__ */ new Map();
19108
19364
  await Promise.all([...byDir].map(async ([dir, inDir]) => {
19109
- const relative17 = (file) => path31.relative(dir, file).split(path31.sep).join("/");
19365
+ const relative17 = (file) => path32.relative(dir, file).split(path32.sep).join("/");
19110
19366
  try {
19111
19367
  const { stdout } = await runGit("git", ["ls-tree", "--name-only", "HEAD", "--", ...inDir.map(relative17)], { cwd: dir, env: cleanEnv() });
19112
19368
  const listed3 = new Set(stdout.split("\n").map((line) => line.trim()).filter(Boolean));
@@ -19126,9 +19382,9 @@ function plannedSkillLookup(skillsAt, workspaceRoot, layout) {
19126
19382
  searchedDirs: () => lookup2.searchedDirs(),
19127
19383
  listSkills: () => lookup2.listSkills(),
19128
19384
  uncommitted: async (skills) => {
19129
- const checked = skills.filter((skill) => skill.source === "workspace" && !(readInPlace && path31.dirname(path31.dirname(skill.path)) === readInPlace));
19385
+ const checked = skills.filter((skill) => skill.source === "workspace" && !(readInPlace && path32.dirname(path32.dirname(skill.path)) === readInPlace));
19130
19386
  const committed = await committedInHead(checked.map((skill) => skill.path));
19131
- return new Map(checked.filter((skill) => committed.get(skill.path) === false).map((skill) => [skill.path, path31.relative(workspaceRoot, path31.dirname(skill.path)).split(path31.sep).join("/")]));
19387
+ 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("/")]));
19132
19388
  }
19133
19389
  };
19134
19390
  }
@@ -19141,13 +19397,13 @@ function checkPlanSkills(tasks, lookup2) {
19141
19397
  function checkTaskSkillsEdit(task2, skills, lookup2) {
19142
19398
  return check([{ taskId: task2.id, owner: `Task "${task2.title}"`, ...attached(skills) }], lookup2);
19143
19399
  }
19144
- function subtaskEntries(op, spec, path46 = []) {
19400
+ function subtaskEntries(op, spec, path47 = []) {
19145
19401
  const subtasks = spec?.subtasks;
19146
19402
  if (!Array.isArray(subtasks)) return [];
19147
19403
  return subtasks.flatMap((sub, i) => {
19148
19404
  if (typeof sub !== "object" || sub === null) return [];
19149
19405
  const child = sub;
19150
- const at = [...path46, typeof child.title === "string" && child.title.trim() ? `"${child.title}"` : `#${i + 1}`];
19406
+ const at = [...path47, typeof child.title === "string" && child.title.trim() ? `"${child.title}"` : `#${i + 1}`];
19151
19407
  return [{ op, owner: `subtask ${at.join(" > ")}`, ...attached(child.skills) }, ...subtaskEntries(op, child, at)];
19152
19408
  });
19153
19409
  }
@@ -22989,7 +23245,7 @@ var RUN_SETTING_KEYS = Object.keys(RUN_SETTING_CHECKS);
22989
23245
  function getSettingsPath() {
22990
23246
  const override = process.env.ORDEWELL_SETTINGS_PATH;
22991
23247
  if (override && override.trim()) return override.trim();
22992
- return path32.join(globalDataDir(), "settings.json");
23248
+ return path33.join(globalDataDir(), "settings.json");
22993
23249
  }
22994
23250
  function readPlannerModels(raw) {
22995
23251
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
@@ -23069,8 +23325,8 @@ var SettingsService = class {
23069
23325
  * a watch on the old one pointing at nothing.
23070
23326
  */
23071
23327
  watch(listener) {
23072
- const dir = path32.dirname(this.filePath);
23073
- const name = path32.basename(this.filePath);
23328
+ const dir = path33.dirname(this.filePath);
23329
+ const name = path33.basename(this.filePath);
23074
23330
  let seen = this.fileStamp();
23075
23331
  let timer;
23076
23332
  let dead = false;
@@ -23079,7 +23335,7 @@ var SettingsService = class {
23079
23335
  fs12.mkdirSync(dir, { recursive: true });
23080
23336
  watcher = fs12.watch(dir, (_event, changed) => {
23081
23337
  if (dead) return;
23082
- if (changed && changed !== name && changed !== path32.basename(dir)) return;
23338
+ if (changed && changed !== name && changed !== path33.basename(dir)) return;
23083
23339
  clearTimeout(timer);
23084
23340
  timer = setTimeout(() => {
23085
23341
  const stamp2 = this.fileStamp();
@@ -23312,7 +23568,7 @@ var SettingsService = class {
23312
23568
  if (this.unreadable) {
23313
23569
  throw new Error(`Cannot save settings: ${this.filePath} could not be read, so it will not be overwritten. Fix or remove it and try again.`);
23314
23570
  }
23315
- const dir = path32.dirname(this.filePath);
23571
+ const dir = path33.dirname(this.filePath);
23316
23572
  if (!fs12.existsSync(dir)) {
23317
23573
  fs12.mkdirSync(dir, { recursive: true });
23318
23574
  }
@@ -23756,7 +24012,7 @@ var BaseConfig = class {
23756
24012
  const { model, effort, override = "" } = configured;
23757
24013
  const named3 = isPlannerBackend(configured.setting ?? "") || isPlannerBackend(process.env.AI_PROVIDER ?? "");
23758
24014
  const pickedElsewhere = configured.pickedFor !== void 0 && configured.pickedFor !== provider;
23759
- const kept = configured.pickedFor === provider || !pickedElsewhere && (isApiProvider(provider) || named3 && !isForeignModel(provider, model, this.agentCatalog(provider)));
24015
+ const kept = configured.pickedFor === provider || !pickedElsewhere && (isApiProvider(provider) || named3 && !isForeignModel(provider, model, () => this.agentCatalog(provider)));
23760
24016
  if (kept) return { model: override || model, effort, source: override ? "request" : "configured" };
23761
24017
  const own = pickedElsewhere || named3 ? rememberedOrDefault(provider) : detectedAgentChoice(provider, configured);
23762
24018
  return !override || override === own.model ? own : { model: override, effort: "", source: "request" };
@@ -23765,7 +24021,8 @@ var BaseConfig = class {
23765
24021
  * The model ids a coding agent was last discovered serving, empty while
23766
24022
  * discovery is cold. A host that holds a runner catalog supplies it: it is
23767
24023
  * the only evidence that tells OpenCode's `provider/model` ids from an
23768
- * OpenRouter slug.
24024
+ * OpenRouter slug. Asked only when a named agent's configured model is
24025
+ * neither its alias nor its remembered pick.
23769
24026
  */
23770
24027
  agentCatalog(_provider) {
23771
24028
  return [];
@@ -23790,8 +24047,10 @@ function isVendorModelFor(agent, model) {
23790
24047
  return !discoveryOf(agent)?.providerQualifiedIds && SLUG_SHAPE.test(model);
23791
24048
  }
23792
24049
  function isForeignModel(agent, model, catalog) {
23793
- if (!model || isStableAlias(agent, model) || catalog.includes(model) || rememberedChoice(agent)?.model === model) return false;
23794
- return isVendorModelFor(agent, model) || catalog.length > 0 && SLUG_SHAPE.test(model);
24050
+ if (!model || isStableAlias(agent, model) || rememberedChoice(agent)?.model === model) return false;
24051
+ const listed3 = catalog();
24052
+ if (listed3.includes(model)) return false;
24053
+ return isVendorModelFor(agent, model) || listed3.length > 0 && SLUG_SHAPE.test(model);
23795
24054
  }
23796
24055
  function rememberedChoice(provider) {
23797
24056
  const entry = new SettingsService().getPlannerModel(provider);
@@ -23826,11 +24085,11 @@ var EnvConfig = class extends BaseConfig {
23826
24085
  // src/services/taskDiffRead.ts
23827
24086
  import { createHash as createHash3 } from "crypto";
23828
24087
  import * as fs13 from "fs";
23829
- import * as path33 from "path";
24088
+ import * as path34 from "path";
23830
24089
  import { devNull } from "os";
23831
24090
  var TASK_DIFF_UNAVAILABLE = "Task diff is not available for this run: no task-specific changes were recorded (shared runs cannot attribute workspace changes to one task).";
23832
24091
  function allowedPath(file) {
23833
- return file.length > 0 && !path33.isAbsolute(file) && !/^[A-Za-z]:/.test(file) && !file.split(/[\\/]/).some((part) => part === ".." || part === ".git" || part === ".ordewell");
24092
+ return file.length > 0 && !path34.isAbsolute(file) && !/^[A-Za-z]:/.test(file) && !file.split(/[\\/]/).some((part) => part === ".." || part === ".git" || part === ".ordewell");
23834
24093
  }
23835
24094
  function stats(raw) {
23836
24095
  return raw.split("\0").flatMap((row) => {
@@ -23839,8 +24098,8 @@ function stats(raw) {
23839
24098
  });
23840
24099
  }
23841
24100
  function inside(root, target) {
23842
- const rel = path33.relative(root, target);
23843
- return rel === "" || !rel.startsWith(`..${path33.sep}`) && rel !== ".." && !path33.isAbsolute(rel);
24101
+ const rel = path34.relative(root, target);
24102
+ return rel === "" || !rel.startsWith(`..${path34.sep}`) && rel !== ".." && !path34.isAbsolute(rel);
23844
24103
  }
23845
24104
  function unquoted(quoted4) {
23846
24105
  const bytes = [];
@@ -23888,10 +24147,10 @@ function patchFiles(patch) {
23888
24147
  const sections = patch.toString("utf8").split(/(?<![^\n])(?=diff --git )/).filter((section) => section.startsWith("diff --git "));
23889
24148
  const files = /* @__PURE__ */ new Map();
23890
24149
  for (const section of sections) {
23891
- const path46 = sectionPath(section);
24150
+ const path47 = sectionPath(section);
23892
24151
  const counts = sectionStats(section);
23893
- const earlier = files.get(path46);
23894
- files.set(path46, earlier ? { path: path46, section: earlier.section + section, added: summed(earlier.added, counts.added), removed: summed(earlier.removed, counts.removed) } : { path: path46, section, ...counts });
24152
+ const earlier = files.get(path47);
24153
+ 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 });
23895
24154
  }
23896
24155
  return [...files.values()];
23897
24156
  }
@@ -23961,11 +24220,11 @@ async function readTaskDiff(invoker, run, taskId, options) {
23961
24220
  files.push({ ...file, repo: repo.path, patch: () => git(reader, cwd, [...diffArgs, "--", `:(top,literal)${prefix}${file.path}`]) });
23962
24221
  }
23963
24222
  if (live2) {
23964
- const workspace = path33.join(entry.worktree, prefix);
24223
+ const workspace = path34.join(entry.worktree, prefix);
23965
24224
  const others = await git(reader, workspace, [...common, "ls-files", "--others", "--exclude-standard", "-z", "--", "."]);
23966
24225
  for (const file of others.split("\0").filter((f) => allowed(f))) {
23967
- const full = path33.join(workspace, file);
23968
- if (!inside(fs13.realpathSync(workspace), fs13.realpathSync(path33.dirname(full)))) continue;
24226
+ const full = path34.join(workspace, file);
24227
+ if (!inside(fs13.realpathSync(workspace), fs13.realpathSync(path34.dirname(full)))) continue;
23969
24228
  const untracked = async (numstat) => {
23970
24229
  const result = await tryGit(reader, workspace, [...common, "diff", "--no-index", "--no-renames", "--no-ext-diff", "--no-textconv", ...numstat ? ["--numstat", "-z"] : [], "--", devNull, file]);
23971
24230
  if (!result.ok && result.code !== 1) throw new Error(result.stderr.trim() || "Could not read an untracked file diff.");
@@ -23995,11 +24254,11 @@ ${await file.patch()}`);
23995
24254
  import { randomBytes as randomBytes6 } from "crypto";
23996
24255
  import { spawnSync } from "child_process";
23997
24256
  import * as fs15 from "fs";
23998
- import * as path35 from "path";
24257
+ import * as path36 from "path";
23999
24258
 
24000
24259
  // src/utils/prdStore.ts
24001
24260
  import * as fs14 from "fs";
24002
- import * as path34 from "path";
24261
+ import * as path35 from "path";
24003
24262
  var PRD_BLOCK_RE = /<!--\s*ORDEWELL_PRD_START\s+slug="([^"]+)"\s*-->\s*([\s\S]*?)\s*<!--\s*ORDEWELL_PRD_END\s*-->/;
24004
24263
  function extractPrdBlock(text4) {
24005
24264
  const match = PRD_BLOCK_RE.exec(text4);
@@ -24013,9 +24272,9 @@ function sanitizeSlug(raw) {
24013
24272
  return raw.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
24014
24273
  }
24015
24274
  function savePrdMarkdown(workspace, slug, markdown) {
24016
- const dir = path34.join(workspace, ".scratch", sanitizeSlug(slug));
24275
+ const dir = path35.join(workspace, ".scratch", sanitizeSlug(slug));
24017
24276
  fs14.mkdirSync(dir, { recursive: true });
24018
- const file = path34.join(dir, "PRD.md");
24277
+ const file = path35.join(dir, "PRD.md");
24019
24278
  fs14.writeFileSync(file, markdown.endsWith("\n") ? markdown : markdown + "\n", "utf8");
24020
24279
  return file;
24021
24280
  }
@@ -24047,12 +24306,12 @@ var NESTED_REPO_DEPTH = 2;
24047
24306
  var GITLINK_MODE = "160000";
24048
24307
  var READ_ONLY = ["--no-optional-locks", "-c", "core.fsmonitor=false"];
24049
24308
  function isInside(parent, child) {
24050
- const rel = path35.relative(parent, child);
24051
- return rel !== "" && !rel.startsWith("..") && !path35.isAbsolute(rel);
24309
+ const rel = path36.relative(parent, child);
24310
+ return rel !== "" && !rel.startsWith("..") && !path36.isAbsolute(rel);
24052
24311
  }
24053
24312
  function resolvesAsNamed(base, p) {
24054
24313
  try {
24055
- return fs15.realpathSync(p) === path35.join(fs15.realpathSync(base), path35.relative(base, p));
24314
+ return fs15.realpathSync(p) === path36.join(fs15.realpathSync(base), path36.relative(base, p));
24056
24315
  } catch {
24057
24316
  return false;
24058
24317
  }
@@ -24107,7 +24366,7 @@ function sameDir(a, b) {
24107
24366
  try {
24108
24367
  return fs15.realpathSync.native(p);
24109
24368
  } catch {
24110
- return path35.resolve(p);
24369
+ return path36.resolve(p);
24111
24370
  }
24112
24371
  };
24113
24372
  return real(a) === real(b);
@@ -24129,7 +24388,7 @@ function clearImmutableFlags(platform, dir) {
24129
24388
  spawnSync("/usr/bin/chflags", ["-R", "nouchg", dir], { stdio: "ignore" });
24130
24389
  }
24131
24390
  function leftoverLinks(dir) {
24132
- const named3 = listDir(dir).filter((name) => LINKED_ARTIFACTS.has(name) || isEnvFile(name) || isLink(path35.join(dir, name)));
24391
+ const named3 = listDir2(dir).filter((name) => LINKED_ARTIFACTS.has(name) || isEnvFile(name) || isLink(path36.join(dir, name)));
24133
24392
  return [.../* @__PURE__ */ new Set([...named3, ...installDirs(dir)])];
24134
24393
  }
24135
24394
  function isLink(target) {
@@ -24202,7 +24461,7 @@ var GitWorktreeIsolation = class {
24202
24461
  const head = await this.headOf(root);
24203
24462
  const repo = { path: repoPath, head };
24204
24463
  if (scan.toplevel !== void 0) {
24205
- const top = path35.relative(workspaceRoot, scan.toplevel).split(path35.sep).join("/");
24464
+ const top = path36.relative(workspaceRoot, scan.toplevel).split(path36.sep).join("/");
24206
24465
  if (top !== "") repo.top = top;
24207
24466
  }
24208
24467
  if (enabled && (head.kind === "branch" || head.kind === "detached")) {
@@ -24259,9 +24518,9 @@ var GitWorktreeIsolation = class {
24259
24518
  async nestedRepos(workspaceRoot, toplevel) {
24260
24519
  const found = [];
24261
24520
  const walk = (rel, depth) => {
24262
- for (const name of childDirs(path35.join(workspaceRoot, rel))) {
24521
+ for (const name of childDirs(path36.join(workspaceRoot, rel))) {
24263
24522
  const child = rel ? `${rel}/${name}` : name;
24264
- if (hasGitEntry(path35.join(workspaceRoot, child))) found.push(child);
24523
+ if (hasGitEntry(path36.join(workspaceRoot, child))) found.push(child);
24265
24524
  else if (depth < NESTED_REPO_DEPTH) walk(child, depth + 1);
24266
24525
  }
24267
24526
  };
@@ -24279,7 +24538,7 @@ var GitWorktreeIsolation = class {
24279
24538
  const [meta, file] = entry.split(" ");
24280
24539
  if (file !== void 0 && meta.startsWith(`${GITLINK_MODE} `)) paths.add(file);
24281
24540
  }
24282
- const declared = await this.tryGit(workspaceRoot, ["config", "-z", "-f", path35.join(toplevel, ".gitmodules"), "--get-regexp", "^submodule\\..*\\.path$"]);
24541
+ const declared = await this.tryGit(workspaceRoot, ["config", "-z", "-f", path36.join(toplevel, ".gitmodules"), "--get-regexp", "^submodule\\..*\\.path$"]);
24283
24542
  for (const entry of declared.stdout.split("\0")) {
24284
24543
  const value = entry.split("\n")[1];
24285
24544
  if (value) paths.add(value.replace(/\/+$/, ""));
@@ -24337,7 +24596,7 @@ var GitWorktreeIsolation = class {
24337
24596
  async acceptsWorktree(run, repo) {
24338
24597
  const dir = this.integrationDir(run, repo);
24339
24598
  ensureStateDirIgnored(run.workspaceRoot);
24340
- fs15.mkdirSync(path35.dirname(dir), { recursive: true });
24599
+ fs15.mkdirSync(path36.dirname(dir), { recursive: true });
24341
24600
  const added = await this.tryGit(repo.root, ["worktree", "add", "-q", "--no-checkout", dir, repo.integrationBranch]);
24342
24601
  await this.removeWorktreeDir(run, repo, dir, []);
24343
24602
  return added.ok;
@@ -24352,12 +24611,12 @@ var GitWorktreeIsolation = class {
24352
24611
  if (isolated.includes(SELF_REPO)) return [];
24353
24612
  const shared2 = [];
24354
24613
  const walk = (rel) => {
24355
- for (const name of listDir(path35.join(workspaceRoot, rel))) {
24614
+ for (const name of listDir2(path36.join(workspaceRoot, rel))) {
24356
24615
  if (!rel && (name === STATE_DIR || name === ".git")) continue;
24357
24616
  const child = rel ? `${rel}/${name}` : name;
24358
24617
  if (isolated.includes(child)) continue;
24359
24618
  if (isolated.some((p) => p.startsWith(`${child}/`))) walk(child);
24360
- else if (resolves(path35.join(workspaceRoot, child))) shared2.push(child);
24619
+ else if (resolves(path36.join(workspaceRoot, child))) shared2.push(child);
24361
24620
  }
24362
24621
  };
24363
24622
  walk("");
@@ -24397,9 +24656,9 @@ var GitWorktreeIsolation = class {
24397
24656
  try {
24398
24657
  ensureStateDirIgnored(run.workspaceRoot);
24399
24658
  for (const repo of run.repos) {
24400
- const worktree = path35.join(dir, repo.path);
24659
+ const worktree = path36.join(dir, repo.path);
24401
24660
  const tip = (await this.git(repo.root, ["rev-parse", repo.integrationBranch])).trim();
24402
- fs15.mkdirSync(path35.dirname(worktree), { recursive: true });
24661
+ fs15.mkdirSync(path36.dirname(worktree), { recursive: true });
24403
24662
  await this.git(repo.root, ["worktree", "add", "-q", "-B", branch, worktree, tip]);
24404
24663
  record.repos[repo.path] = { worktree, linked: [], baseRef: tip };
24405
24664
  }
@@ -24424,13 +24683,13 @@ var GitWorktreeIsolation = class {
24424
24683
  setupFailed.push({ repo: repo.path, detail: err.detail });
24425
24684
  }
24426
24685
  entry.linked = boot.linked;
24427
- copied.push(...boot.copied.map((name) => path35.posix.join(repo.path, name)));
24686
+ copied.push(...boot.copied.map((name) => path36.posix.join(repo.path, name)));
24428
24687
  }
24429
24688
  for (const rel of run.shared) {
24430
- const source = path35.join(run.workspaceRoot, rel);
24431
- const target = path35.join(cwd, rel);
24689
+ const source = path36.join(run.workspaceRoot, rel);
24690
+ const target = path36.join(cwd, rel);
24432
24691
  if (!resolves(source) || lexists(target)) continue;
24433
- fs15.mkdirSync(path35.dirname(target), { recursive: true });
24692
+ fs15.mkdirSync(path36.dirname(target), { recursive: true });
24434
24693
  if (linkPath(source, target, this.platform) === "copy") copied.push(rel);
24435
24694
  const entry = self ? record.repos[SELF_REPO] : void 0;
24436
24695
  if (entry) entry.linked.push(rel);
@@ -24462,7 +24721,7 @@ var GitWorktreeIsolation = class {
24462
24721
  */
24463
24722
  async clearLeftovers(run, task2, names) {
24464
24723
  const removal = noRemoval();
24465
- const holder = Object.values(run.tasks).find((r) => r.taskId !== task2.id && (r.branch === names.branch || path35.resolve(r.workspace) === path35.resolve(names.dir) || isInside(r.workspace, names.dir)));
24724
+ 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)));
24466
24725
  if (holder) throw new Error(`Its worktree path or branch is held by the record of task ${holder.order} (${holder.title}); Ordewell will not remove it`);
24467
24726
  const found = [];
24468
24727
  for (const repo of run.repos) {
@@ -24472,14 +24731,14 @@ var GitWorktreeIsolation = class {
24472
24731
  worktree: /^worktree (.+)$/m.exec(block)?.[1],
24473
24732
  branch: /^branch refs\/heads\/(.+)$/m.exec(block)?.[1]
24474
24733
  }));
24475
- const at = /* @__PURE__ */ new Set([path35.join(names.dir, repo.path)]);
24734
+ const at = /* @__PURE__ */ new Set([path36.join(names.dir, repo.path)]);
24476
24735
  for (const entry of registered) {
24477
- if (entry.worktree && entry.branch === names.branch && isInside(this.runRoot(run), path35.resolve(entry.worktree))) at.add(entry.worktree);
24736
+ if (entry.worktree && entry.branch === names.branch && isInside(this.runRoot(run), path36.resolve(entry.worktree))) at.add(entry.worktree);
24478
24737
  }
24479
- const base = { branch: names.branch, name: path35.basename(names.dir), title: task2.title };
24738
+ const base = { branch: names.branch, name: path36.basename(names.dir), title: task2.title };
24480
24739
  for (const worktree of at) {
24481
24740
  if (!fs15.existsSync(worktree)) continue;
24482
- const linked = leftoverLinks(path35.join(worktree, await this.prefixOf(repo)));
24741
+ const linked = leftoverLinks(path36.join(worktree, await this.prefixOf(repo)));
24483
24742
  found.push({ repo, worktree, target: { ...base, worktree, linked } });
24484
24743
  }
24485
24744
  found.push({ repo, worktree: names.dir, target: { ...base, linked: [] } });
@@ -24572,7 +24831,7 @@ var GitWorktreeIsolation = class {
24572
24831
  */
24573
24832
  async inWorkspacePlace(repo, worktree) {
24574
24833
  const prefix = await this.prefixOf(repo);
24575
- return prefix ? path35.join(worktree, prefix) : worktree;
24834
+ return prefix ? path36.join(worktree, prefix) : worktree;
24576
24835
  }
24577
24836
  async prefixOf(repo) {
24578
24837
  return (await this.git(repo.root, ["rev-parse", "--show-prefix"])).trim();
@@ -25102,15 +25361,15 @@ var GitWorktreeIsolation = class {
25102
25361
  return landingMessage(kind, from, this.deps.plannedWith?.() ?? false);
25103
25362
  }
25104
25363
  integrationDir(run, repo) {
25105
- return path35.join(this.runRoot(run), INTEGRATION_DIR, repo.path);
25364
+ return path36.join(this.runRoot(run), INTEGRATION_DIR, repo.path);
25106
25365
  }
25107
25366
  async ensureIntegrationWorktree(run, repo) {
25108
25367
  return this.admin(run.workspaceRoot, async () => {
25109
25368
  const dir = this.integrationDir(run, repo);
25110
- if (fs15.existsSync(path35.join(dir, ".git"))) return dir;
25369
+ if (fs15.existsSync(path36.join(dir, ".git"))) return dir;
25111
25370
  await this.tryGit(repo.root, ["worktree", "prune"]);
25112
25371
  ensureStateDirIgnored(run.workspaceRoot);
25113
- fs15.mkdirSync(path35.dirname(dir), { recursive: true });
25372
+ fs15.mkdirSync(path36.dirname(dir), { recursive: true });
25114
25373
  await this.git(repo.root, ["worktree", "add", "-q", dir, repo.integrationBranch]);
25115
25374
  return dir;
25116
25375
  });
@@ -25133,7 +25392,7 @@ var GitWorktreeIsolation = class {
25133
25392
  const removal = noRemoval();
25134
25393
  for (const repo of opts.preserve === false || record.readOnly ? [] : run.repos) {
25135
25394
  const entry = record.repos[repo.path];
25136
- const target = { worktree: entry?.worktree, linked: entry?.linked ?? [], branch: record.branch, name: path35.basename(record.workspace), title: record.title, commit: record.commit };
25395
+ const target = { worktree: entry?.worktree, linked: entry?.linked ?? [], branch: record.branch, name: path36.basename(record.workspace), title: record.title, commit: record.commit };
25137
25396
  try {
25138
25397
  const kept = await this.preserve(run, repo, target, opts.intent);
25139
25398
  if (kept) removal.preserved.push({ task: task2, ...kept });
@@ -25214,7 +25473,7 @@ var GitWorktreeIsolation = class {
25214
25473
  return;
25215
25474
  }
25216
25475
  for (const entry of entries) {
25217
- const target = path35.join(dir, entry.name);
25476
+ const target = path36.join(dir, entry.name);
25218
25477
  if (entry.isSymbolicLink()) this.unlinkIfLink(target);
25219
25478
  else if (entry.isDirectory()) this.unlinkLinksUnder(target);
25220
25479
  }
@@ -25236,20 +25495,20 @@ var GitWorktreeIsolation = class {
25236
25495
  return false;
25237
25496
  }
25238
25497
  };
25239
- const owned = new Set(Object.values(run.tasks).map((r) => path35.resolve(r.workspace)));
25498
+ const owned = new Set(Object.values(run.tasks).map((r) => path36.resolve(r.workspace)));
25240
25499
  const root = this.runRoot(run);
25241
25500
  if (fs15.existsSync(root)) {
25242
25501
  for (const entry of fs15.readdirSync(root)) {
25243
- const dir = path35.join(root, entry);
25244
- if (entry === INTEGRATION_DIR || owned.has(path35.resolve(dir))) continue;
25502
+ const dir = path36.join(root, entry);
25503
+ if (entry === INTEGRATION_DIR || owned.has(path36.resolve(dir))) continue;
25245
25504
  let kept = true;
25246
25505
  for (const repo of run.repos) {
25247
- const worktree = path35.join(dir, repo.path);
25248
- const linked = leftoverLinks(path35.join(worktree, await this.prefixOf(repo)));
25506
+ const worktree = path36.join(dir, repo.path);
25507
+ const linked = leftoverLinks(path36.join(worktree, await this.prefixOf(repo)));
25249
25508
  if (!await keep2(repo, { worktree, linked, branch: null, name: entry, title: entry }, worktree)) kept = false;
25250
25509
  }
25251
25510
  if (!kept) continue;
25252
- for (const repo of run.repos) await this.removeWorktreeDir(run, repo, path35.join(dir, repo.path), []);
25511
+ for (const repo of run.repos) await this.removeWorktreeDir(run, repo, path36.join(dir, repo.path), []);
25253
25512
  this.removeTaskWorkspace(run, dir);
25254
25513
  }
25255
25514
  }
@@ -25267,13 +25526,13 @@ var GitWorktreeIsolation = class {
25267
25526
  }
25268
25527
  async removeWorktreeDir(run, repo, dir, linked) {
25269
25528
  for (const name of /* @__PURE__ */ new Set([...linked, ...installDirs(dir)])) {
25270
- const target = path35.join(dir, name);
25271
- if (!resolvesAsNamed(this.runRoot(run), path35.dirname(target))) continue;
25529
+ const target = path36.join(dir, name);
25530
+ if (!resolvesAsNamed(this.runRoot(run), path36.dirname(target))) continue;
25272
25531
  this.unlinkIfLink(target);
25273
25532
  this.unlinkLinksUnder(target);
25274
25533
  }
25275
25534
  for (const name of resolvesAsNamed(this.runRoot(run), dir) ? fs15.readdirSync(dir) : []) {
25276
- if (LINKED_ARTIFACTS.has(name) || isEnvFile(name)) this.unlinkIfLink(path35.join(dir, name));
25535
+ if (LINKED_ARTIFACTS.has(name) || isEnvFile(name)) this.unlinkIfLink(path36.join(dir, name));
25277
25536
  }
25278
25537
  await this.tryGit(repo.root, ["worktree", "remove", "--force", dir]);
25279
25538
  if (fs15.existsSync(dir) && isInside(this.runRoot(run), dir)) {
@@ -25281,7 +25540,7 @@ var GitWorktreeIsolation = class {
25281
25540
  fs15.rmSync(dir, { recursive: true, force: true });
25282
25541
  }
25283
25542
  await this.tryGit(repo.root, ["worktree", "prune"]);
25284
- for (let parent = path35.dirname(dir); isInside(this.runRoot(run), parent); parent = path35.dirname(parent)) this.removeIfEmpty(parent);
25543
+ for (let parent = path36.dirname(dir); isInside(this.runRoot(run), parent); parent = path36.dirname(parent)) this.removeIfEmpty(parent);
25285
25544
  }
25286
25545
  unlinkIfLink(target) {
25287
25546
  try {
@@ -25296,14 +25555,14 @@ var GitWorktreeIsolation = class {
25296
25555
  }
25297
25556
  }
25298
25557
  runRoot(run) {
25299
- return path35.join(run.workspaceRoot, STATE_DIR, "worktrees", run.id);
25558
+ return path36.join(run.workspaceRoot, STATE_DIR, "worktrees", run.id);
25300
25559
  }
25301
25560
  namesFor(run, task2) {
25302
25561
  const slug = sanitizeSlug(task2.title).slice(0, 40).replace(/-+$/, "") || "task";
25303
25562
  let name = `${task2.order}-${slug}`;
25304
25563
  const clash = Object.values(run.tasks).some((r) => r.taskId !== task2.id && r.branch === `ordewell/${run.id}/${name}`);
25305
25564
  if (clash) name = `${name}-${sanitizeSlug(task2.id) || "x"}`;
25306
- return { branch: `ordewell/${run.id}/${name}`, dir: path35.join(this.runRoot(run), name) };
25565
+ return { branch: `ordewell/${run.id}/${name}`, dir: path36.join(this.runRoot(run), name) };
25307
25566
  }
25308
25567
  async unmergedPaths(cwd) {
25309
25568
  const listed3 = await this.tryGit(cwd, ["diff", "--name-only", "-z", "--diff-filter=U"]);
@@ -25317,7 +25576,7 @@ var GitWorktreeIsolation = class {
25317
25576
  async rebaseInProgress(cwd) {
25318
25577
  for (const dir of ["rebase-merge", "rebase-apply"]) {
25319
25578
  const found = await this.tryGit(cwd, ["rev-parse", "--git-path", dir]);
25320
- if (found.ok && fs15.existsSync(path35.resolve(cwd, found.stdout.trim()))) return true;
25579
+ if (found.ok && fs15.existsSync(path36.resolve(cwd, found.stdout.trim()))) return true;
25321
25580
  }
25322
25581
  return false;
25323
25582
  }
@@ -29147,15 +29406,21 @@ var Landing = class {
29147
29406
  * has landed. Its branch is on the integration branch by then, so it merges
29148
29407
  * clean — and one the resolver did not really bring along conflicts again
29149
29408
  * instead of being taken at its word. Null when there is nothing to land.
29409
+ * The task is named before its landing settles, so the caller can hold
29410
+ * its worktree for as long as the merge is in it; one the caller says is
29411
+ * `leaving` the plan is not landed at all.
29150
29412
  */
29151
- async landResolved(resolverId) {
29413
+ landResolved(resolverId, leaving = () => false) {
29152
29414
  const conflictedId = this.runs.takeResolver(resolverId);
29153
- if (!conflictedId) return null;
29415
+ if (!conflictedId || leaving(conflictedId)) return null;
29154
29416
  const task2 = this.tasks.get(conflictedId);
29155
29417
  if (!task2 || this.runs.current?.tasks[conflictedId]?.status !== "conflict") return null;
29418
+ return { task: task2, outcome: this.landConflicted(task2) };
29419
+ }
29420
+ async landConflicted(task2) {
29156
29421
  const landing2 = await this.runs.integrate(task2);
29157
- if (landing2 !== "merged") return { task: task2, outcome: this.unmerged(task2, landing2) };
29158
- return { task: task2, outcome: { kind: "landed", messages: [{ level: "info", text: `Task "${task2.title}" landed through its conflict resolution.` }] } };
29422
+ if (landing2 !== "merged") return this.unmerged(task2, landing2);
29423
+ return { kind: "landed", messages: [{ level: "info", text: `Task "${task2.title}" landed through its conflict resolution.` }] };
29159
29424
  }
29160
29425
  /** The repair a conflicted task is owed next; null when repair is off, used up, or there is no isolated run to repair it in. */
29161
29426
  nextRepair(taskId) {
@@ -29500,7 +29765,7 @@ function heldFrom(state) {
29500
29765
  }
29501
29766
 
29502
29767
  // src/services/Attempts.ts
29503
- import * as path38 from "path";
29768
+ import * as path39 from "path";
29504
29769
 
29505
29770
  // src/services/VerdictEngine.ts
29506
29771
  var IDLE_TIMEOUT_MS = 10 * 6e4;
@@ -29608,6 +29873,18 @@ var VerdictEngine = class {
29608
29873
  this.resumeIdle(taskId);
29609
29874
  this.toolCheckpoints.get(taskId)?.({ kind: "rejected", reason });
29610
29875
  }
29876
+ /**
29877
+ * An attempt's runner is being started, with no session to watch yet. Its
29878
+ * silence counts from here, so one whose start never resolves — stuck
29879
+ * launching, or waiting for its tools to attach — is flagged quiet as well.
29880
+ * {@link watch} starts the count again once the runner is up.
29881
+ */
29882
+ starting(taskId) {
29883
+ const gen = this.bumpGeneration(taskId);
29884
+ this.idlePaused.delete(taskId);
29885
+ this.openApprovals.delete(taskId);
29886
+ this.touchIdle(taskId, gen);
29887
+ }
29611
29888
  /**
29612
29889
  * Attach to a spawned session. The runner's `task_complete` call on the
29613
29890
  * attempt's token is the only completion evidence (ADR-0022, V2); a session
@@ -29618,6 +29895,9 @@ var VerdictEngine = class {
29618
29895
  */
29619
29896
  watch(task2, session) {
29620
29897
  const gen = this.bumpGeneration(task2.id);
29898
+ this.idlePaused.delete(task2.id);
29899
+ this.openApprovals.delete(task2.id);
29900
+ this.touchIdle(task2.id, gen);
29621
29901
  session.onOutput(() => {
29622
29902
  if (this.generations.get(task2.id) === gen) this.touchIdle(task2.id, gen);
29623
29903
  });
@@ -29876,7 +30156,7 @@ function reportedVerdict(report) {
29876
30156
  // src/services/ScriptRunner.ts
29877
30157
  import { spawn as nodeSpawn2 } from "child_process";
29878
30158
  import * as fs16 from "fs";
29879
- import * as path36 from "path";
30159
+ import * as path37 from "path";
29880
30160
  import { EventEmitter as EventEmitter2 } from "events";
29881
30161
  import { StringDecoder as StringDecoder2 } from "string_decoder";
29882
30162
 
@@ -30017,7 +30297,7 @@ var ScriptSession = class extends AbstractRunnerSession {
30017
30297
  async launchOf(step) {
30018
30298
  if ("argv" in step) {
30019
30299
  const [named3, ...args] = step.argv;
30020
- const program = this.setup.platform === "win32" && path36.win32.basename(named3) !== named3 && !path36.win32.isAbsolute(named3) ? path36.win32.resolve(this.setup.cwd, named3) : named3;
30300
+ const program = this.setup.platform === "win32" && path37.win32.basename(named3) !== named3 && !path37.win32.isAbsolute(named3) ? path37.win32.resolve(this.setup.cwd, named3) : named3;
30021
30301
  const plan = await planDirectLaunch(program, args, this.setup.launch);
30022
30302
  return { file: plan.file, args: plan.args, shell: false, ...plan.verbatim ? { verbatim: true } : {} };
30023
30303
  }
@@ -30252,8 +30532,8 @@ function passed(status) {
30252
30532
  // src/services/PatchCapture.ts
30253
30533
  import { createHash as createHash4, randomBytes as randomBytes7 } from "crypto";
30254
30534
  import * as fs17 from "fs";
30255
- import * as os11 from "os";
30256
- import * as path37 from "path";
30535
+ import * as os10 from "os";
30536
+ import * as path38 from "path";
30257
30537
 
30258
30538
  // src/services/readTask.ts
30259
30539
  function readTask2(args, readerId, plan, log) {
@@ -30520,7 +30800,7 @@ function linkInPatch(patch) {
30520
30800
  function readOwn(scratch, name, max) {
30521
30801
  let fd;
30522
30802
  try {
30523
- fd = fs17.openSync(path37.join(scratch, name), fs17.constants.O_RDONLY | (fs17.constants.O_NOFOLLOW ?? 0));
30803
+ fd = fs17.openSync(path38.join(scratch, name), fs17.constants.O_RDONLY | (fs17.constants.O_NOFOLLOW ?? 0));
30524
30804
  } catch (err) {
30525
30805
  throw new Refusal(`the capture left no ${name.split(".").pop()} Ordewell could read (${err.code ?? String(err)})`);
30526
30806
  }
@@ -30558,7 +30838,7 @@ async function captureHeld(runs, opts, signal) {
30558
30838
  const [worktree] = runs.worktrees;
30559
30839
  const scratch = runs.scratch;
30560
30840
  const tag = `ordewell-patch-${randomBytes7(8).toString("hex")}`;
30561
- const stem = path37.join(scratch, tag);
30841
+ const stem = path38.join(scratch, tag);
30562
30842
  const own = (suffix, max = LISTING_BYTES) => readOwn(scratch, `${tag}.${suffix}`, max);
30563
30843
  try {
30564
30844
  const stage = await runs.run(stageScript(stem, opts.linked ?? []), worktree);
@@ -30650,8 +30930,8 @@ async function indexEntries(invoker, worktree) {
30650
30930
  return entries;
30651
30931
  }
30652
30932
  async function trialApply(invoker, worktree, patchFile, scratch) {
30653
- const index = path37.resolve(worktree, (await git(invoker, worktree, [...APPLY_GIT, "rev-parse", "--git-path", "index"])).trim());
30654
- const copy = path37.join(scratch, "index");
30933
+ const index = path38.resolve(worktree, (await git(invoker, worktree, [...APPLY_GIT, "rev-parse", "--git-path", "index"])).trim());
30934
+ const copy = path38.join(scratch, "index");
30655
30935
  fs17.copyFileSync(index, copy);
30656
30936
  const trial = { ...invoker, exec: (file, args, opts) => invoker.exec(file, args, { ...opts, env: { ...opts.env, GIT_INDEX_FILE: copy } }) };
30657
30937
  const tried = await tryGit(trial, worktree, [...APPLY_GIT, "apply", "--cached", "--3way", "--whitespace=nowarn", patchFile]);
@@ -30671,7 +30951,7 @@ function gitRefused(lead, stderr) {
30671
30951
  function onDisk3(worktree, names) {
30672
30952
  return names.map((name) => {
30673
30953
  try {
30674
- const stat = fs17.lstatSync(path37.join(worktree, name), { bigint: true, throwIfNoEntry: false });
30954
+ const stat = fs17.lstatSync(path38.join(worktree, name), { bigint: true, throwIfNoEntry: false });
30675
30955
  return stat ? `${stat.mode} ${stat.ino} ${stat.size} ${stat.mtimeNs} ${stat.ctimeNs}` : "none";
30676
30956
  } catch (err) {
30677
30957
  return err.code ?? "unreadable";
@@ -30711,7 +30991,7 @@ function leavesWorktree(worktree, names, linked, held2, left) {
30711
30991
  if (i < parts.length && held2.get(at)?.some((entry) => entry.mode === SUBMODULE)) {
30712
30992
  return `the patch writes ${name}, under ${at}, a submodule, whose files are another repository's.`;
30713
30993
  }
30714
- const full = path37.join(worktree, ...parts.slice(0, i));
30994
+ const full = path38.join(worktree, ...parts.slice(0, i));
30715
30995
  const stat = fs17.lstatSync(full, { throwIfNoEntry: false });
30716
30996
  if (!stat) break;
30717
30997
  const last = i === parts.length;
@@ -30739,7 +31019,7 @@ async function applyPatch(invoker, worktree, patch, saved, opts = {}) {
30739
31019
  if (link !== void 0) {
30740
31020
  return refused3(`the patch holds a link or a submodule (${JSON.stringify(link.slice(0, 80))}), which a saved patch never does, so it is not one that was saved.`);
30741
31021
  }
30742
- const dir = fs17.mkdtempSync(path37.join(os11.tmpdir(), "ordewell-apply-"));
31022
+ const dir = fs17.mkdtempSync(path38.join(os10.tmpdir(), "ordewell-apply-"));
30743
31023
  try {
30744
31024
  const toplevel = (await git(invoker, worktree, [...APPLY_GIT, "rev-parse", "--show-toplevel"])).trim();
30745
31025
  if (fs17.realpathSync(toplevel) !== fs17.realpathSync(worktree)) return refused3(`${worktree} is not the root of a worktree of its own.`);
@@ -30749,7 +31029,7 @@ async function applyPatch(invoker, worktree, patch, saved, opts = {}) {
30749
31029
  }
30750
31030
  const before = await unmergedPaths(invoker, worktree);
30751
31031
  if (before.length > 0) return refused3(`your worktree still has unresolved conflicts (${listed(before.slice(0, 10))}${before.length > 10 ? `, +${before.length - 10} more` : ""}). Resolve them and git add them, or undo them, then apply the patch.`);
30752
- const file = path37.join(dir, "saved.patch");
31032
+ const file = path38.join(dir, "saved.patch");
30753
31033
  fs17.writeFileSync(file, patch, { flag: "wx", mode: 384 });
30754
31034
  const held2 = await indexEntries(invoker, worktree);
30755
31035
  const named3 = await pathsNamed(invoker, worktree, file);
@@ -30985,6 +31265,10 @@ var Attempts = class {
30985
31265
  receipt(taskId) {
30986
31266
  return this.live.get(taskId)?.receipt ?? null;
30987
31267
  }
31268
+ /** Which spawn of the live attempt's runner waits for Ordewell's tools to connect; null when none does. */
31269
+ attaching(taskId) {
31270
+ return this.live.get(taskId)?.attaching ?? null;
31271
+ }
30988
31272
  /** The kind a task's next attempt starts as, short of a repair or a continue: what is weighed before one exists. */
30989
31273
  nextKind(taskId) {
30990
31274
  const task2 = this.deps.store.get(taskId);
@@ -31025,7 +31309,9 @@ var Attempts = class {
31025
31309
  shell: null,
31026
31310
  web: null,
31027
31311
  patch: null,
31028
- receipt: null
31312
+ receipt: null,
31313
+ attaching: null,
31314
+ spawning: new AbortController()
31029
31315
  };
31030
31316
  this.spawnCounts.set(task2.id, attempt.attempt);
31031
31317
  this.live.set(task2.id, attempt);
@@ -31040,7 +31326,8 @@ var Attempts = class {
31040
31326
  /**
31041
31327
  * The one way an attempt ends. Dropping the record is itself what
31042
31328
  * invalidates a spawn still in flight ({@link run} kills the session it
31043
- * receives for an attempt that is no longer current).
31329
+ * receives for an attempt that is no longer current), and its cancel takes
31330
+ * back a runner still starting meanwhile.
31044
31331
  *
31045
31332
  * A user interruption also bumps the verifier's generation *before* stopping
31046
31333
  * the runner: a runner may fire onExit synchronously from
@@ -31056,6 +31343,7 @@ var Attempts = class {
31056
31343
  const session = attempt?.session ?? null;
31057
31344
  const nativeSessionId = session?.nativeSessionId() ?? null;
31058
31345
  if (attempt) {
31346
+ attempt.spawning.abort();
31059
31347
  store.endAttemptUsage(taskId, attempt.attempt, (/* @__PURE__ */ new Date()).toISOString(), session?.sessionCost?.() ?? void 0, nativeSessionId ?? void 0);
31060
31348
  this.deps.output.detach(taskId);
31061
31349
  attempt.shell?.end();
@@ -31232,12 +31520,14 @@ var Attempts = class {
31232
31520
  this.abandonSpawn(task2, attempt);
31233
31521
  return false;
31234
31522
  }
31523
+ this.verifier.starting(task2.id);
31235
31524
  const session = attempt.script ? await this.spawnScript(task2, attempt, cwd) : await this.spawnRunner(task2, attempt, cwd, worktree ? workspace : void 0, previousWork);
31236
31525
  if (this.live.get(task2.id) !== attempt) {
31237
31526
  this.abandonSpawn(task2, attempt, session);
31238
31527
  return false;
31239
31528
  }
31240
31529
  attempt.phase = "running";
31530
+ attempt.attaching = null;
31241
31531
  attempt.session = session;
31242
31532
  store.setTaskRunnerSessionId(task2.id, void 0);
31243
31533
  store.clearCarriedFrom(task2.id);
@@ -31383,7 +31673,13 @@ var Attempts = class {
31383
31673
  ...web ? { webReads: web } : {},
31384
31674
  applyPatch: (args) => this.applyPatch(task2, attempt, args),
31385
31675
  workspaceRoot: this.deps.workspaceRoot(),
31386
- onNotice: (message) => this.host.tell("warn", `Task "${task2.title}": ${message}`)
31676
+ onNotice: (message) => this.host.tell("warn", `Task "${task2.title}": ${message}`),
31677
+ cancelled: attempt.spawning.signal,
31678
+ onAttaching: (spawn) => {
31679
+ if (this.live.get(task2.id) !== attempt) return;
31680
+ attempt.attaching = spawn;
31681
+ this.host.emit("onTaskChanged");
31682
+ }
31387
31683
  });
31388
31684
  }
31389
31685
  /**
@@ -31436,9 +31732,9 @@ var Attempts = class {
31436
31732
  const repos = Object.values(this.deps.runs.openRecord(taskId)?.repos ?? {});
31437
31733
  if (repos.length !== 1 || cwd === null) return [];
31438
31734
  const [{ worktree, linked }] = repos;
31439
- const prefix = path38.relative(worktree, cwd).split(path38.sep).join("/");
31440
- if (prefix.startsWith("..") || path38.isAbsolute(prefix)) return [];
31441
- return linked.map((name) => (prefix ? `${prefix}/${name}` : name).split(path38.sep).join("/"));
31735
+ const prefix = path39.relative(worktree, cwd).split(path39.sep).join("/");
31736
+ if (prefix.startsWith("..") || path39.isAbsolute(prefix)) return [];
31737
+ return linked.map((name) => (prefix ? `${prefix}/${name}` : name).split(path39.sep).join("/"));
31442
31738
  }
31443
31739
  /**
31444
31740
  * A script task's start (ADR-0035): its body as the plan holds it now, run
@@ -31476,7 +31772,7 @@ var Attempts = class {
31476
31772
  this.deps.notifications.warn(message);
31477
31773
  };
31478
31774
  if (resolved.blockedEnvrc) {
31479
- warn(`blocked:${resolved.blockedEnvrc}`, `direnv has blocked ${resolved.blockedEnvrc}, so tasks start without its variables. Run \`direnv allow\` in ${path38.dirname(resolved.blockedEnvrc)} to use them.`);
31775
+ 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.`);
31480
31776
  }
31481
31777
  if (resolved.trackedEnvFile) {
31482
31778
  warn(`tracked:${resolved.trackedEnvFile}`, `Ignored ${resolved.trackedEnvFile}: git tracks it, and a committed file must not choose the environment agents run in. Untrack it to use it.`);
@@ -31502,7 +31798,7 @@ var Attempts = class {
31502
31798
  uncommittedSkillFolder(name) {
31503
31799
  const root = this.deps.workspaceRoot();
31504
31800
  const skill = this.deps.skillsAt(this.skillRoots(root)).findSkill(name);
31505
- return skill?.source === "workspace" ? path38.relative(root, path38.dirname(skill.path)).split(path38.sep).join("/") : void 0;
31801
+ return skill?.source === "workspace" ? path39.relative(root, path39.dirname(skill.path)).split(path39.sep).join("/") : void 0;
31506
31802
  }
31507
31803
  /**
31508
31804
  * The roots whose `.ordewell/skills/` an attempt at `cwd` reads: in a repo
@@ -31871,12 +32167,12 @@ function ancestors(task2, store) {
31871
32167
 
31872
32168
  // src/services/sessionLease.ts
31873
32169
  import * as fs19 from "fs";
31874
- import * as path40 from "path";
32170
+ import * as path41 from "path";
31875
32171
  import { randomUUID as randomUUID6 } from "crypto";
31876
32172
 
31877
32173
  // src/utils/sessionStore.ts
31878
32174
  import * as fs18 from "fs";
31879
- import * as path39 from "path";
32175
+ import * as path40 from "path";
31880
32176
 
31881
32177
  // src/utils/sessionId.ts
31882
32178
  import { randomBytes as randomBytes8 } from "crypto";
@@ -31887,13 +32183,13 @@ function mintSessionId() {
31887
32183
  // src/utils/sessionStore.ts
31888
32184
  var SESSIONS_SUBDIR = "sessions";
31889
32185
  function getSessionsDir(baseDir) {
31890
- return path39.join(getStateDir(baseDir), SESSIONS_SUBDIR);
32186
+ return path40.join(getStateDir(baseDir), SESSIONS_SUBDIR);
31891
32187
  }
31892
32188
  function idSegment(id2) {
31893
32189
  return /^[A-Za-z0-9_-][A-Za-z0-9._-]{0,127}$/.test(id2) ? id2 : `~${Buffer.from(id2, "utf-8").toString("hex")}`;
31894
32190
  }
31895
32191
  function sessionDataDir(sessionId, baseDir) {
31896
- return path39.join(getSessionsDir(baseDir), idSegment(sessionId));
32192
+ return path40.join(getSessionsDir(baseDir), idSegment(sessionId));
31897
32193
  }
31898
32194
  var REJECTION_MESSAGE = "This session was created with an older version of Ordewell. Please re-plan.";
31899
32195
  function isSessionMeta(value) {
@@ -31920,8 +32216,8 @@ function legacySessionFilename(session) {
31920
32216
  return `${filenameStem(session)}_${session.id.slice(0, 8)}.json`;
31921
32217
  }
31922
32218
  function dropLegacyFile(sessionsDir, meta) {
31923
- const legacy = path39.join(sessionsDir, legacySessionFilename(meta));
31924
- if (legacy === path39.join(sessionsDir, sessionFilename(meta)) || !fs18.existsSync(legacy)) return;
32219
+ const legacy = path40.join(sessionsDir, legacySessionFilename(meta));
32220
+ if (legacy === path40.join(sessionsDir, sessionFilename(meta)) || !fs18.existsSync(legacy)) return;
31925
32221
  try {
31926
32222
  const held2 = JSON.parse(fs18.readFileSync(legacy, "utf-8")).meta?.id;
31927
32223
  if (held2 === meta.id) fs18.unlinkSync(legacy);
@@ -31948,7 +32244,7 @@ function saveSession(plan, goal, baseDir, id2) {
31948
32244
  };
31949
32245
  dropRetiredPlanFields(session.plan);
31950
32246
  const filename = sessionFilename(meta);
31951
- const filePath = path39.join(sessionsDir, filename);
32247
+ const filePath = path40.join(sessionsDir, filename);
31952
32248
  writeFileAtomic(filePath, JSON.stringify(session, null, 2));
31953
32249
  dropLegacyFile(sessionsDir, meta);
31954
32250
  return meta;
@@ -31960,7 +32256,7 @@ function listSessions(baseDir, logger = defaultLogger) {
31960
32256
  return files.map((f) => {
31961
32257
  let parsed;
31962
32258
  try {
31963
- parsed = JSON.parse(fs18.readFileSync(path39.join(sessionsDir, f), "utf-8"));
32259
+ parsed = JSON.parse(fs18.readFileSync(path40.join(sessionsDir, f), "utf-8"));
31964
32260
  } catch (err) {
31965
32261
  if (err instanceof SyntaxError) return moveAside(sessionsDir, f, "is not valid JSON", logger);
31966
32262
  logger.warn("sessionStore", `failed to read session file ${f}; skipping`, err);
@@ -31981,7 +32277,7 @@ function listSessions(baseDir, logger = defaultLogger) {
31981
32277
  function moveAside(sessionsDir, file, reason, logger) {
31982
32278
  const aside = `${file}.${Date.now()}.corrupt`;
31983
32279
  try {
31984
- fs18.renameSync(path39.join(sessionsDir, file), path39.join(sessionsDir, aside));
32280
+ fs18.renameSync(path40.join(sessionsDir, file), path40.join(sessionsDir, aside));
31985
32281
  logger.warn("sessionStore", `session file ${file} ${reason}; moved aside to ${aside} rather than overwritten`);
31986
32282
  } catch {
31987
32283
  logger.warn("sessionStore", `failed to read session file ${file}; skipping`);
@@ -31992,7 +32288,7 @@ function findSessionFile(sessionsDir, sessionId, logger) {
31992
32288
  const files = fs18.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
31993
32289
  for (const f of files) {
31994
32290
  try {
31995
- const raw = fs18.readFileSync(path39.join(sessionsDir, f), "utf-8");
32291
+ const raw = fs18.readFileSync(path40.join(sessionsDir, f), "utf-8");
31996
32292
  const session = JSON.parse(raw);
31997
32293
  if (session.meta.id === sessionId) return f;
31998
32294
  } catch (err) {
@@ -32007,7 +32303,7 @@ function loadSession(sessionId, baseDir, logger = defaultLogger) {
32007
32303
  try {
32008
32304
  const file = findSessionFile(sessionsDir, sessionId, logger);
32009
32305
  if (file) {
32010
- const raw = fs18.readFileSync(path39.join(sessionsDir, file), "utf-8");
32306
+ const raw = fs18.readFileSync(path40.join(sessionsDir, file), "utf-8");
32011
32307
  const parsed = JSON.parse(raw);
32012
32308
  if (hasScalarRunner(parsed)) {
32013
32309
  logger.warn("sessionStore", `session file ${file}: ${REJECTION_MESSAGE}`);
@@ -32049,7 +32345,7 @@ function deleteSession(sessionId, baseDir, logger = defaultLogger) {
32049
32345
  try {
32050
32346
  const file = findSessionFile(sessionsDir, sessionId, logger);
32051
32347
  if (file) {
32052
- fs18.unlinkSync(path39.join(sessionsDir, file));
32348
+ fs18.unlinkSync(path40.join(sessionsDir, file));
32053
32349
  return true;
32054
32350
  }
32055
32351
  } catch (err) {
@@ -32102,7 +32398,7 @@ var UNLEASED = {
32102
32398
  };
32103
32399
  var hostsHere = /* @__PURE__ */ new Set();
32104
32400
  function leaseFile(sessionId, workspace) {
32105
- return path40.join(sessionDataDir(sessionId, workspace), LEASE_FILE);
32401
+ return path41.join(sessionDataDir(sessionId, workspace), LEASE_FILE);
32106
32402
  }
32107
32403
  function isRecord6(value) {
32108
32404
  if (typeof value !== "object" || value === null) return false;
@@ -32191,7 +32487,7 @@ var LeaseHost = class {
32191
32487
  */
32192
32488
  acquire(file, workspace, holder) {
32193
32489
  ensureStateDirIgnored(workspace);
32194
- ensurePrivateDir(path40.dirname(file));
32490
+ ensurePrivateDir(path41.dirname(file));
32195
32491
  const outcome = withFileLock(file, () => {
32196
32492
  const current = readRecord(file);
32197
32493
  const taken = current !== null && current.hostId !== this.id ? current : null;
@@ -32246,7 +32542,7 @@ var LeaseHost = class {
32246
32542
  withFileLock(file, () => {
32247
32543
  if (readRecord(file)?.hostId === this.id) fs19.rmSync(file, { force: true });
32248
32544
  }, "release a session lease");
32249
- fs19.rmdirSync(path40.dirname(file));
32545
+ fs19.rmdirSync(path41.dirname(file));
32250
32546
  } catch {
32251
32547
  }
32252
32548
  }
@@ -32439,10 +32735,14 @@ var TaskOrchestrator = class _TaskOrchestrator {
32439
32735
  /** Conflict repairs owed with every slot taken and no scheduler to claim them: a freed slot starts them. */
32440
32736
  owedRepairs = /* @__PURE__ */ new Set();
32441
32737
  /**
32442
- * Tasks Mark complete is landing after it ended their attempt: in progress
32443
- * with no attempt for as long as that takes, and not orphans for it.
32738
+ * Tasks Mark complete is landing after it ended their attempt, or that
32739
+ * their conflict resolver is landing, and that landing: in progress with
32740
+ * no attempt for as long as it takes, and not orphans for it. Both can be
32741
+ * landing one task at once, so each holds it for itself (see {@link holdWhileLanding}).
32444
32742
  */
32445
- completing = /* @__PURE__ */ new Set();
32743
+ completing = /* @__PURE__ */ new Map();
32744
+ /** Tasks {@link releaseTask} is tearing down once their landings have settled: none may begin another. */
32745
+ releasing = /* @__PURE__ */ new Set();
32446
32746
  /** Tasks paused on a usage limit, the run's hold on them, and each runner's wait for its reset (ADR-0018, U1). */
32447
32747
  limitPauses;
32448
32748
  /**
@@ -32578,6 +32878,10 @@ var TaskOrchestrator = class _TaskOrchestrator {
32578
32878
  getCompletionReceipt(taskId) {
32579
32879
  return this.attempts.receipt(taskId);
32580
32880
  }
32881
+ /** Which spawn of a live attempt's runner waits for Ordewell's tools to connect (ADR-0025); null when none does. */
32882
+ getToolsAttaching(taskId) {
32883
+ return this.attempts.attaching(taskId);
32884
+ }
32581
32885
  /** Recent clean output of a task's latest attempt, running or ended; null if it never ran. */
32582
32886
  getLiveOutput(taskId, opts) {
32583
32887
  return this.attempts.liveTail(taskId, opts);
@@ -32742,12 +33046,13 @@ var TaskOrchestrator = class _TaskOrchestrator {
32742
33046
  /**
32743
33047
  * Something can still start that nobody has to ask for again: a start
32744
33048
  * admitted and not yet claimed, a force start queued behind its
32745
- * dependencies, a task that resumes on its own at its usage limit's
32746
- * reset, or a start a dirty checkout parked for Stash and continue to
32747
- * replay. A Stop is owed to it, whether or not anything runs.
33049
+ * dependencies, a conflict repair owed a slot or the end of a start hold,
33050
+ * a task that resumes on its own at its usage limit's reset, or a start a
33051
+ * dirty checkout parked for Stash and continue to replay. A Stop is owed to
33052
+ * it, whether or not anything runs.
32748
33053
  */
32749
33054
  get startsPending() {
32750
- return this.admissions > 0 || this.queuedStarts.size > 0 || this.runs.blocked || this.store.allTasks.some(waitsForReset);
33055
+ return this.admissions > 0 || this.queuedStarts.size > 0 || this.owedRepairs.size > 0 || this.runs.blocked || this.store.allTasks.some(waitsForReset);
32751
33056
  }
32752
33057
  /**
32753
33058
  * The plan's activity as one word for every surface: a start on its way,
@@ -33248,6 +33553,7 @@ var TaskOrchestrator = class _TaskOrchestrator {
33248
33553
  * Work that did not land waits on the user — it never fails the task or
33249
33554
  * blocks its dependents — unless its conflict is owed a repair, which takes
33250
33555
  * the slot the ending attempt freed and never one more than the run allows.
33556
+ * A planner turn's start hold holds it as it holds any new spawn (ADR-0029).
33251
33557
  * The repair follows on from the start whose work it repairs: a Stop since
33252
33558
  * that was admitted withdraws it.
33253
33559
  */
@@ -33255,20 +33561,22 @@ var TaskOrchestrator = class _TaskOrchestrator {
33255
33561
  this.store.markAwaitingUser(task2.id, "conflict");
33256
33562
  this.say(outcome.messages);
33257
33563
  if (outcome.kind !== "repair-needed") return;
33258
- if (this.attempts.size < this.config.maxParallelSessions) {
33564
+ const waitsFor = this.startHolds > 0 ? "the planner's turn ends" : this.slotsFull() ? "a slot is free" : null;
33565
+ if (!waitsFor) {
33259
33566
  const claimed = this.claim(task2.id, admission);
33260
33567
  if ("refused" in claimed) this.tell("warn", `The conflict of task "${task2.title}" is not repaired: ${claimed.refused}`);
33261
33568
  else this.unawaited(claimed.spawned, `Could not start the repair of task "${task2.title}"`);
33262
33569
  } else {
33263
33570
  this.store.markPending(task2.id);
33264
33571
  if (!this.isScheduling) this.owedRepairs.add(task2.id);
33265
- this.tell("info", `Task "${task2.title}" is repaired once a slot is free.`);
33572
+ this.tell("info", `Task "${task2.title}" is repaired once ${waitsFor}.`);
33266
33573
  }
33267
33574
  }
33268
- /** Start the repairs {@link settleUnlanded} left owed, as slots allow. */
33575
+ /** Start the repairs {@link settleUnlanded} left owed, as slots and any start hold allow. */
33269
33576
  startOwedRepairs(admission) {
33577
+ if (this.startHolds > 0) return;
33270
33578
  for (const id2 of [...this.owedRepairs]) {
33271
- if (this.attempts.size >= this.config.maxParallelSessions) return;
33579
+ if (this.slotsFull()) return;
33272
33580
  this.owedRepairs.delete(id2);
33273
33581
  const task2 = this.store.get(id2);
33274
33582
  if (!task2 || task2.status !== "pending" || this.attempts.has(id2) || !this.landing.nextRepair(id2)) continue;
@@ -33278,13 +33586,31 @@ var TaskOrchestrator = class _TaskOrchestrator {
33278
33586
  }
33279
33587
  }
33280
33588
  async landResolved(resolverId, admission) {
33281
- const resolved = await this.landing.landResolved(resolverId);
33589
+ const resolved = this.landing.landResolved(resolverId, (id2) => this.releasing.has(id2));
33282
33590
  if (!resolved) return;
33283
- const { task: task2, outcome } = resolved;
33591
+ const { task: task2 } = resolved;
33592
+ const outcome = await this.holdWhileLanding(task2.id, resolved.outcome);
33593
+ if (this.store.get(task2.id)?.status === "completed") return;
33284
33594
  await this.applyLanding(task2, outcome, admission, () => {
33285
33595
  if (task2.verdict) this.logAndArchive(task2, task2.verdict);
33286
33596
  });
33287
33597
  }
33598
+ /**
33599
+ * Hold a task for as long as `landing` is merging its worktree. Its end lets
33600
+ * go of its own hold only: another landing of the task begun meanwhile
33601
+ * still holds it.
33602
+ */
33603
+ async holdWhileLanding(taskId, landing2) {
33604
+ const holds2 = this.completing.get(taskId) ?? /* @__PURE__ */ new Set();
33605
+ holds2.add(landing2);
33606
+ this.completing.set(taskId, holds2);
33607
+ try {
33608
+ return await landing2;
33609
+ } finally {
33610
+ holds2.delete(landing2);
33611
+ if (holds2.size === 0 && this.completing.get(taskId) === holds2) this.completing.delete(taskId);
33612
+ }
33613
+ }
33288
33614
  getReadyTasks() {
33289
33615
  return this.readiness().ready;
33290
33616
  }
@@ -33392,17 +33718,27 @@ var TaskOrchestrator = class _TaskOrchestrator {
33392
33718
  * as {@link cancelTask} does, but its worktree goes: no task is left to land
33393
33719
  * it into. A spawn still in flight just loses its attempt,
33394
33720
  * which is what makes {@link Attempts} kill the session it is about to
33395
- * receive. The id's cross-attempt bookkeeping goes too — a hold or retry
33396
- * count kept for a task that no longer exists would be inherited by nothing.
33721
+ * receive. Every landing of it in flight — Mark complete's, its resolver's —
33722
+ * settles first, so its worktree is not torn down under a merge, and none
33723
+ * begins while it goes; a repair a landing claims is cancelled as any
33724
+ * running attempt is. The id's cross-attempt bookkeeping
33725
+ * goes too — a hold or retry count kept for a task that no longer exists
33726
+ * would be inherited by nothing.
33397
33727
  */
33398
33728
  async releaseTask(taskId) {
33399
33729
  this.limitPauses.cancel(taskId);
33400
- const phase = this.attempts.phaseOf(taskId);
33401
- if (phase === "running" || phase === "integrating") await this.cancelAttempt(taskId, { keep: false });
33402
- else {
33403
- this.attempts.end(taskId, "release");
33404
- if (this.store.get(taskId)?.status === "in_progress") this.store.markPending(taskId);
33405
- await this.runs.release(taskId, { keep: false });
33730
+ for (let holds2 = this.completing.get(taskId); holds2; holds2 = this.completing.get(taskId)) await Promise.allSettled(holds2);
33731
+ this.releasing.add(taskId);
33732
+ try {
33733
+ const phase = this.attempts.phaseOf(taskId);
33734
+ if (phase === "running" || phase === "integrating") await this.cancelAttempt(taskId, { keep: false });
33735
+ else {
33736
+ this.attempts.end(taskId, "release");
33737
+ if (this.store.get(taskId)?.status === "in_progress") this.store.markPending(taskId);
33738
+ await this.runs.release(taskId, { keep: false });
33739
+ }
33740
+ } finally {
33741
+ this.releasing.delete(taskId);
33406
33742
  }
33407
33743
  this.onHold.delete(taskId);
33408
33744
  this.retryCounts.delete(taskId);
@@ -33411,17 +33747,12 @@ var TaskOrchestrator = class _TaskOrchestrator {
33411
33747
  async markTaskComplete(taskId) {
33412
33748
  const task2 = this.store.get(taskId);
33413
33749
  if (!task2 || task2.status === "completed") return;
33414
- if (this.completing.has(taskId)) return;
33750
+ if (this.completing.has(taskId) || this.releasing.has(taskId)) return;
33415
33751
  const admission = this.admittedNow();
33416
33752
  const ended = this.attempts.end(taskId, "complete");
33417
33753
  const verdict = this.attempts.markComplete(task2);
33418
- this.completing.add(taskId);
33419
- let landing2;
33420
- try {
33421
- landing2 = await (ended?.integration ?? this.landing.landVouched(task2));
33422
- } finally {
33423
- this.completing.delete(taskId);
33424
- }
33754
+ const landed2 = ended?.integration ?? this.landing.landVouched(task2);
33755
+ const landing2 = await this.holdWhileLanding(taskId, landed2);
33425
33756
  this.store.setTaskVerdict(taskId, verdict);
33426
33757
  const handoff = task2.type === "user" ? userTaskHandoff(task2.userSteps) : void 0;
33427
33758
  this.attempts.handOver(taskId, summarizeOutput(verdict.reason, ""), handoff);
@@ -33710,7 +34041,7 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
33710
34041
  if (!await this.openForAdmitted(taskId, origin, resume, admission)) return;
33711
34042
  if (this.attempts.has(taskId)) return;
33712
34043
  this.refuseOpsDuringMerge(taskId, forced);
33713
- if (forced.queued && this.holdsQueued(taskId, forced)) {
34044
+ if (forced.queued && (this.holdsQueued(taskId, forced) || this.slotsFull())) {
33714
34045
  this.queuedStarts.set(taskId, { pastGate: forced.pastGate });
33715
34046
  return;
33716
34047
  }
@@ -33769,8 +34100,9 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
33769
34100
  this.queuedStarts.delete(id2);
33770
34101
  continue;
33771
34102
  }
34103
+ if (this.slotsFull()) continue;
33772
34104
  const waits = this.startWaits(task2, task2.research ? void 0 : { queued: true, pastGate: queued.pastGate });
33773
- if (task2.research && (waits.length > 0 || this.attempts.size >= this.config.maxParallelSessions)) continue;
34105
+ if (task2.research && waits.length > 0) continue;
33774
34106
  if (waits.some((w) => QUEUED_WAITS.has(w.kind))) continue;
33775
34107
  if (this.leftToScheduler(task2) && queued.pastGate.length === 0) {
33776
34108
  this.queuedStarts.delete(id2);
@@ -33799,6 +34131,9 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
33799
34131
  const task2 = this.store.get(taskId);
33800
34132
  return !!task2 && this.startWaits(task2, forced).some((w) => QUEUED_WAITS.has(w.kind) || w.kind === "dependencies" || w.kind === "merge-gate");
33801
34133
  }
34134
+ slotsFull() {
34135
+ return this.attempts.size >= this.config.maxParallelSessions;
34136
+ }
33802
34137
  sayQueuedAtGate(task2, queued, unmerged) {
33803
34138
  const titles = unmerged.map((id2) => `"${this.store.get(id2)?.title ?? id2}"`).join(", ");
33804
34139
  const notice = `Task "${task2.title}" waits for Merge all: the work of ${titles} is not merged into your branch yet. Merge all to start it, or force start it again to start it without that work.`;
@@ -33899,7 +34234,7 @@ ${now.message}` : LIMIT_RESET_MESSAGE;
33899
34234
  */
33900
34235
  async settleRun(after) {
33901
34236
  if (!this.store.isFinished()) this.transition({ type: "reopen" });
33902
- if (this.attempts.size > 0) return;
34237
+ if (this.attempts.size > 0 || this.owedRepairs.size > 0) return;
33903
34238
  if (this.runState.kind === "held") return;
33904
34239
  if (this.runState.kind !== "scheduling") {
33905
34240
  if (after === "tick") {
@@ -34193,21 +34528,45 @@ var Planner = class {
34193
34528
  };
34194
34529
 
34195
34530
  // src/services/ModelResolver.ts
34196
- var ModelResolver = class {
34531
+ var ModelResolver = class _ModelResolver {
34197
34532
  constructor(registry, config, deps = {}) {
34198
34533
  this.registry = registry;
34199
34534
  this.config = config;
34200
- this.discovery = new ModelDiscovery(registry, deps.execImpl, deps.fetchImpl);
34535
+ this.discovery = new ModelDiscovery(registry, deps.execImpl, deps.fetchImpl, void 0, void 0, void 0, deps.env);
34201
34536
  this.fetchImpl = deps.fetchImpl;
34537
+ this.execImpl = deps.execImpl;
34538
+ this.shared = { vendor: { options: null, load: null, errors: {}, generation: 0 }, root: this, views: /* @__PURE__ */ new Map() };
34202
34539
  }
34203
34540
  registry;
34204
34541
  config;
34205
34542
  discovery;
34206
34543
  fetchImpl;
34207
- cachedPickerOptions = null;
34208
- /** A catalog load under way: a second ask, or a warm-up, shares it rather than fetching again. */
34209
- pickerLoad = null;
34210
- lastDiscoveryErrors = {};
34544
+ execImpl;
34545
+ shared;
34546
+ get vendor() {
34547
+ return this.shared.vendor;
34548
+ }
34549
+ /**
34550
+ * This resolver for runners launched with another environment, a
34551
+ * workspace's own: their catalogs are discovered and cached under `env`, as
34552
+ * `CLAUDE_CONFIG_DIR` there can name another account, while the vendor
34553
+ * catalogs stay this resolver's, fetched once. One view per `key`, made on
34554
+ * its first ask: a later ask with the same key gets that view, whatever
34555
+ * `env` it passes.
34556
+ */
34557
+ withRunnerEnv(key, env) {
34558
+ let view = this.shared.views.get(key);
34559
+ if (!view) {
34560
+ view = new _ModelResolver(this.registry, this.config, { fetchImpl: this.fetchImpl, execImpl: this.execImpl, env });
34561
+ view.shared = this.shared;
34562
+ this.shared.views.set(key, view);
34563
+ }
34564
+ return view;
34565
+ }
34566
+ /** Every runner-environment catalog this resolver's family holds: its root's and each view's. */
34567
+ runnerCatalogs() {
34568
+ return [this.shared.root, ...this.shared.views.values()].map((resolver) => resolver.discovery);
34569
+ }
34211
34570
  static builtinOptions() {
34212
34571
  return toOrchestratorOptions({}, ORCHESTRATOR_SHORTCUTS);
34213
34572
  }
@@ -34223,16 +34582,16 @@ var ModelResolver = class {
34223
34582
  return Object.fromEntries(discovered);
34224
34583
  }
34225
34584
  async pickerOptions() {
34226
- if (this.cachedPickerOptions) return this.cachedPickerOptions;
34227
- if (!this.pickerLoad) {
34585
+ if (this.vendor.options) return this.vendor.options;
34586
+ if (!this.vendor.load) {
34228
34587
  const load = this.loadPickerOptions();
34229
- this.pickerLoad = load;
34588
+ this.vendor.load = load;
34230
34589
  void load.finally(() => {
34231
- if (this.pickerLoad === load) this.pickerLoad = null;
34590
+ if (this.vendor.load === load) this.vendor.load = null;
34232
34591
  }).catch(() => {
34233
34592
  });
34234
34593
  }
34235
- return this.pickerLoad;
34594
+ return this.vendor.load;
34236
34595
  }
34237
34596
  /**
34238
34597
  * Start loading the vendor catalogs without waiting for them, unless they
@@ -34240,20 +34599,25 @@ var ModelResolver = class {
34240
34599
  * known yet reads it from them on a later call, never waiting on the fetch.
34241
34600
  */
34242
34601
  warmPickerOptions() {
34243
- if (this.cachedPickerOptions || this.pickerLoad) return;
34602
+ if (this.vendor.options || this.vendor.load) return;
34244
34603
  this.pickerOptions().catch(() => {
34245
34604
  });
34246
34605
  }
34247
34606
  async loadPickerOptions() {
34607
+ const vendor = this.vendor;
34608
+ const generation2 = vendor.generation;
34248
34609
  const { apiKeys, baseUrls } = collectProviderCredentials(this.config);
34249
34610
  const { models, errors } = await fetchAllProviderModels({
34250
34611
  apiKeys,
34251
34612
  baseUrls,
34252
34613
  fetchImpl: this.fetchImpl
34253
34614
  });
34254
- this.lastDiscoveryErrors = errors;
34255
- this.cachedPickerOptions = toOrchestratorOptions(models, ORCHESTRATOR_SHORTCUTS);
34256
- return this.cachedPickerOptions;
34615
+ const options = toOrchestratorOptions(models, ORCHESTRATOR_SHORTCUTS);
34616
+ if (vendor.generation === generation2) {
34617
+ vendor.errors = errors;
34618
+ vendor.options = options;
34619
+ }
34620
+ return options;
34257
34621
  }
34258
34622
  /**
34259
34623
  * Per-provider failures from the most recent `pickerOptions()` fetch, keyed
@@ -34262,7 +34626,7 @@ var ModelResolver = class {
34262
34626
  * key/endpoint is set but whose catalog could not be reached.
34263
34627
  */
34264
34628
  getDiscoveryErrors() {
34265
- return this.lastDiscoveryErrors;
34629
+ return this.vendor.errors;
34266
34630
  }
34267
34631
  async refresh() {
34268
34632
  const options = await this.pickerOptions();
@@ -34273,8 +34637,25 @@ var ModelResolver = class {
34273
34637
  this.config.setProviderModelLists(lists);
34274
34638
  return lists;
34275
34639
  }
34640
+ /** Forget every runner catalog, each workspace's included; the vendor catalogs stay. */
34276
34641
  refreshRunnerModels() {
34277
- this.discovery.clear();
34642
+ for (const discovery of this.runnerCatalogs()) discovery.clear();
34643
+ }
34644
+ /**
34645
+ * The models a pick for `provider` is judged against: a harness planner's
34646
+ * own runner catalog, or the vendor picker catalog (ADR-0009). Discovered
34647
+ * if not held. Never throws: a catalog that cannot be had is empty.
34648
+ */
34649
+ async plannerCatalog(provider) {
34650
+ const runner = runnerForProvider(provider);
34651
+ if (runner) return candidatesOf((await this.modelsForRunners([runner]))[runner] ?? []);
34652
+ return (await this.pickerOptions().catch(() => [])).map((o) => ({ id: o.id }));
34653
+ }
34654
+ /** {@link plannerCatalog} from what is already held, so a cold catalog is empty; never discovers. */
34655
+ cachedPlannerCatalog(provider) {
34656
+ const runner = runnerForProvider(provider);
34657
+ if (runner) return candidatesOf(this.getCachedRunnerModels(runner));
34658
+ return this.getCachedPickerOptions().map((o) => ({ id: o.id }));
34278
34659
  }
34279
34660
  /**
34280
34661
  * Synchronous reads of whatever the last `modelsForRunners`/`pickerOptions`
@@ -34285,7 +34666,7 @@ var ModelResolver = class {
34285
34666
  return this.discovery.getCached(runner) ?? [];
34286
34667
  }
34287
34668
  getCachedPickerOptions() {
34288
- return this.cachedPickerOptions ?? [];
34669
+ return this.vendor.options ?? [];
34289
34670
  }
34290
34671
  /**
34291
34672
  * A model's context window from whatever catalog is already cached (#49),
@@ -34298,7 +34679,7 @@ var ModelResolver = class {
34298
34679
  */
34299
34680
  contextWindowFor(source, model) {
34300
34681
  if (!model) return void 0;
34301
- 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;
34682
+ const window2 = isApiProvider(source) ? this.vendor.options?.find((o) => o.apiProvider === source && o.id === prefixModelId(source, stripModelPrefix(model, source)))?.contextWindow : this.discovery.getCached(source)?.find((m) => m.modelId === model)?.contextWindow;
34302
34683
  return window2 && window2 > 0 ? window2 : void 0;
34303
34684
  }
34304
34685
  /**
@@ -34308,14 +34689,16 @@ var ModelResolver = class {
34308
34689
  */
34309
34690
  maxOutputTokensFor(source, model) {
34310
34691
  if (!model || !isApiProvider(source)) return void 0;
34311
- const cap = this.cachedPickerOptions?.find((o) => o.apiProvider === source && o.id === prefixModelId(source, stripModelPrefix(model, source)))?.maxOutputTokens;
34692
+ const cap = this.vendor.options?.find((o) => o.apiProvider === source && o.id === prefixModelId(source, stripModelPrefix(model, source)))?.maxOutputTokens;
34312
34693
  return cap && cap > 0 ? cap : void 0;
34313
34694
  }
34695
+ /** Forget every catalog: the vendor ones, and each runner environment's. */
34314
34696
  invalidate() {
34315
- this.discovery.clear();
34316
- this.cachedPickerOptions = null;
34317
- this.pickerLoad = null;
34318
- this.lastDiscoveryErrors = {};
34697
+ this.refreshRunnerModels();
34698
+ this.vendor.generation++;
34699
+ this.vendor.options = null;
34700
+ this.vendor.load = null;
34701
+ this.vendor.errors = {};
34319
34702
  if (!this.fetchImpl) {
34320
34703
  for (const provider of PROVIDER_PRIORITY) {
34321
34704
  const baseUrl = this.config.getProviderBaseUrl(provider);
@@ -34325,6 +34708,9 @@ var ModelResolver = class {
34325
34708
  }
34326
34709
  }
34327
34710
  };
34711
+ function candidatesOf(models) {
34712
+ return models.map((m) => ({ id: m.modelId, variants: m.variants }));
34713
+ }
34328
34714
 
34329
34715
  // src/services/RunnerInstallation.ts
34330
34716
  import { exec as exec4 } from "child_process";
@@ -35034,7 +35420,7 @@ function isoInstant(value) {
35034
35420
  }
35035
35421
 
35036
35422
  // src/services/harness/openCodeGoLimits.ts
35037
- import { homedir as homedir11 } from "os";
35423
+ import { homedir as homedir9 } from "os";
35038
35424
 
35039
35425
  // src/services/harness/openCodeCredentials.ts
35040
35426
  import { createHash as createHash5 } from "crypto";
@@ -35102,12 +35488,12 @@ function openCodeStorePaths(env, home) {
35102
35488
  return { db: file === ":memory:" ? null : resolve13(data, file), auth: join38(data, "auth.json") };
35103
35489
  }
35104
35490
  function openCodeGoLoginReader(deps = {}) {
35105
- const homedir12 = deps.homedir ?? osHomedir;
35491
+ const homedir10 = deps.homedir ?? osHomedir;
35106
35492
  let last;
35107
35493
  return {
35108
35494
  async read(env, signal) {
35109
35495
  if (signal.aborted) return { kind: "unreadable" };
35110
- const paths = openCodeStorePaths(env, homedir12());
35496
+ const paths = openCodeStorePaths(env, homedir10());
35111
35497
  const [db, wal, auth] = await Promise.all([
35112
35498
  paths.db ? stamp(paths.db) : null,
35113
35499
  paths.db ? stamp(`${paths.db}-wal`) : null,
@@ -35161,8 +35547,8 @@ function v2Login(db, row) {
35161
35547
  login: { route: "console", owner: digest("console", db, row.id, account, orgID ?? ""), access: value.access, ...orgID ? { orgID } : {}, expires: value.expires }
35162
35548
  };
35163
35549
  }
35164
- async function legacyLogin(path46, env) {
35165
- const content = env.OPENCODE_AUTH_CONTENT ?? await readSmall(path46);
35550
+ async function legacyLogin(path47, env) {
35551
+ const content = env.OPENCODE_AUTH_CONTENT ?? await readSmall(path47);
35166
35552
  if (content === void 0) return env[ENV_KEY] ? { kind: "unsupported" } : { kind: "none" };
35167
35553
  if (content === null) return { kind: "unreadable" };
35168
35554
  const auth = parseJson(content);
@@ -35171,15 +35557,15 @@ async function legacyLogin(path46, env) {
35171
35557
  if (entry === void 0) return env[ENV_KEY] ? { kind: "unsupported" } : { kind: "none" };
35172
35558
  if (!isRecord9(entry) || entry.type !== "api") return { kind: "unsupported" };
35173
35559
  if (typeof entry.key !== "string" || entry.key.length === 0) return { kind: "unreadable" };
35174
- return { kind: "login", login: { route: "key", owner: digest("v1", path46, entry.key), key: entry.key } };
35560
+ return { kind: "login", login: { route: "key", owner: digest("v1", path47, entry.key), key: entry.key } };
35175
35561
  }
35176
- function queryStore(path46, signal, deps) {
35562
+ function queryStore(path47, signal, deps) {
35177
35563
  return new Promise((settle) => {
35178
35564
  let worker;
35179
35565
  try {
35180
35566
  worker = (deps.spawnWorker ?? ((source, options) => new Worker(source, options)))(WORKER_SOURCE, {
35181
35567
  eval: true,
35182
- workerData: { path: path46, integrations: GO_INTEGRATIONS, busyMs: BUSY_MS, maxValueBytes: MAX_VALUE_BYTES },
35568
+ workerData: { path: path47, integrations: GO_INTEGRATIONS, busyMs: BUSY_MS, maxValueBytes: MAX_VALUE_BYTES },
35183
35569
  execArgv: deps.workerExecArgv ?? [],
35184
35570
  resourceLimits: { maxOldGenerationSizeMb: 64 }
35185
35571
  });
@@ -35206,19 +35592,19 @@ function queryStore(path46, signal, deps) {
35206
35592
  worker.once("exit", aborted);
35207
35593
  });
35208
35594
  }
35209
- async function stamp(path46) {
35595
+ async function stamp(path47) {
35210
35596
  try {
35211
- const stat = await fs20.stat(path46);
35597
+ const stat = await fs20.stat(path47);
35212
35598
  return [stat.ino, stat.size, stat.mtimeMs];
35213
35599
  } catch {
35214
35600
  return null;
35215
35601
  }
35216
35602
  }
35217
- async function readSmall(path46) {
35603
+ async function readSmall(path47) {
35218
35604
  try {
35219
- const stat = await fs20.stat(path46);
35605
+ const stat = await fs20.stat(path47);
35220
35606
  if (!stat.isFile() || stat.size > MAX_AUTH_FILE_BYTES) return null;
35221
- return await fs20.readFile(path46, "utf8");
35607
+ return await fs20.readFile(path47, "utf8");
35222
35608
  } catch (err) {
35223
35609
  return err.code === "ENOENT" ? void 0 : null;
35224
35610
  }
@@ -35251,7 +35637,7 @@ function openCodeGoAccountLimitCollector(options) {
35251
35637
  label: "OpenCode Go",
35252
35638
  provider: "opencode-go",
35253
35639
  scope: () => {
35254
- const paths = openCodeStorePaths(env(), (deps.reader?.homedir ?? homedir11)());
35640
+ const paths = openCodeStorePaths(env(), (deps.reader?.homedir ?? homedir9)());
35255
35641
  return ["opencode", paths.db ?? ":memory:", paths.auth].join("\0");
35256
35642
  },
35257
35643
  acquire: async (signal) => {
@@ -36347,20 +36733,20 @@ function createAiService(config, deps) {
36347
36733
  }
36348
36734
 
36349
36735
  // src/services/createSession.ts
36350
- import * as path43 from "path";
36736
+ import * as path44 from "path";
36351
36737
 
36352
36738
  // src/services/handoffReferences.ts
36353
36739
  import * as fs21 from "fs";
36354
- import * as path41 from "path";
36740
+ import * as path42 from "path";
36355
36741
  import { spawnSync as spawnSync2 } from "child_process";
36356
36742
  function resolveHandoffReferences(handoff, workspace) {
36357
36743
  const checked = /* @__PURE__ */ new Map();
36358
36744
  const contained = (candidate) => {
36359
36745
  try {
36360
36746
  const root = fs21.realpathSync(workspace);
36361
- const resolved = fs21.realpathSync(path41.resolve(root, candidate));
36362
- const relative17 = path41.relative(root, resolved);
36363
- return relative17 !== ".." && !relative17.startsWith(`..${path41.sep}`) && !path41.isAbsolute(relative17) ? resolved : void 0;
36747
+ const resolved = fs21.realpathSync(path42.resolve(root, candidate));
36748
+ const relative17 = path42.relative(root, resolved);
36749
+ return relative17 !== ".." && !relative17.startsWith(`..${path42.sep}`) && !path42.isAbsolute(relative17) ? resolved : void 0;
36364
36750
  } catch {
36365
36751
  return void 0;
36366
36752
  }
@@ -36381,7 +36767,7 @@ function resolveHandoffReferences(handoff, workspace) {
36381
36767
  }
36382
36768
  } else if (/^[0-9a-fA-F]{4,64}$/.test(ref.sha)) {
36383
36769
  const repo = contained(ref.repo ?? ".");
36384
- if (repo && fs21.existsSync(path41.join(repo, ".git"))) {
36770
+ if (repo && fs21.existsSync(path42.join(repo, ".git"))) {
36385
36771
  const result2 = spawnSync2("git", ["--no-optional-locks", "--no-replace-objects", "cat-file", "-t", ref.sha], {
36386
36772
  cwd: repo,
36387
36773
  encoding: "utf8",
@@ -37733,7 +38119,8 @@ var SessionEventRelay = class {
37733
38119
  this.checkpointQuestion(t.id),
37734
38120
  this.waitsForApproval(t.id),
37735
38121
  this.orchestrator.holdReason(t.id),
37736
- this.orchestrator.getCompletionReceipt(t.id)
38122
+ this.orchestrator.getCompletionReceipt(t.id),
38123
+ this.orchestrator.getToolsAttaching(t.id)
37737
38124
  )),
37738
38125
  ...runMerge ? { runMerge } : {},
37739
38126
  activity: this.orchestrator.activity
@@ -37855,10 +38242,10 @@ var SessionEventRelay = class {
37855
38242
 
37856
38243
  // src/utils/taskLogStore.ts
37857
38244
  import * as fs22 from "fs";
37858
- import * as path42 from "path";
38245
+ import * as path43 from "path";
37859
38246
  var EXTENSION = ".jsonl";
37860
38247
  function taskDir({ baseDir, sessionId }, taskId) {
37861
- return path42.join(sessionDataDir(sessionId, baseDir), "tasks", idSegment(taskId));
38248
+ return path43.join(sessionDataDir(sessionId, baseDir), "tasks", idSegment(taskId));
37862
38249
  }
37863
38250
  function listTaskLogAttempts(location, taskId) {
37864
38251
  let names;
@@ -37876,7 +38263,7 @@ function openTaskLog(location, taskId, attempt) {
37876
38263
  const next = (listTaskLogAttempts(location, taskId).pop() ?? 0) + 1;
37877
38264
  let number = attempt !== void 0 && Number.isInteger(attempt) && attempt >= 1 ? attempt : next;
37878
38265
  for (; ; ) {
37879
- const file = path42.join(dir, `${number}${EXTENSION}`);
38266
+ const file = path43.join(dir, `${number}${EXTENSION}`);
37880
38267
  try {
37881
38268
  fs22.writeFileSync(file, "", { flag: "wx" });
37882
38269
  } catch (err) {
@@ -37900,15 +38287,15 @@ function prepareTaskOutputFile(location, taskId, attempt) {
37900
38287
  ensureStateDirIgnored(location.baseDir);
37901
38288
  const dir = taskDir(location, taskId);
37902
38289
  fs22.mkdirSync(dir, { recursive: true });
37903
- return path42.join(dir, `${attempt}.output.txt`);
38290
+ return path43.join(dir, `${attempt}.output.txt`);
37904
38291
  }
37905
38292
  function taskPatchFile(location, taskId, attempt) {
37906
- return path42.join(taskDir(location, taskId), `${attempt}.patch`);
38293
+ return path43.join(taskDir(location, taskId), `${attempt}.patch`);
37907
38294
  }
37908
38295
  function saveTaskPatch(location, taskId, attempt, patch) {
37909
38296
  ensureStateDirIgnored(location.baseDir);
37910
38297
  const file = taskPatchFile(location, taskId, attempt);
37911
- fs22.mkdirSync(path42.dirname(file), { recursive: true });
38298
+ fs22.mkdirSync(path43.dirname(file), { recursive: true });
37912
38299
  const partial = `${file}.${process.pid}-${Date.now()}.partial`;
37913
38300
  fs22.writeFileSync(partial, patch, { flag: "wx" });
37914
38301
  fs22.renameSync(partial, file);
@@ -37929,7 +38316,7 @@ function readTaskLog(location, taskId, attempt) {
37929
38316
  if (!Number.isInteger(attempt) || attempt < 1) return [];
37930
38317
  let raw;
37931
38318
  try {
37932
- raw = fs22.readFileSync(path42.join(taskDir(location, taskId), `${attempt}${EXTENSION}`), "utf-8");
38319
+ raw = fs22.readFileSync(path43.join(taskDir(location, taskId), `${attempt}${EXTENSION}`), "utf-8");
37933
38320
  } catch {
37934
38321
  return [];
37935
38322
  }
@@ -39958,7 +40345,7 @@ function createSession(deps) {
39958
40345
  ...deps.openTaskLog ? {} : {
39959
40346
  keepPatch: (taskId, attempt, patch) => {
39960
40347
  const location = session.taskLogLocation;
39961
- return path43.relative(location.baseDir, saveTaskPatch(location, taskId, attempt, patch)).split(path43.sep).join("/");
40348
+ return path44.relative(location.baseDir, saveTaskPatch(location, taskId, attempt, patch)).split(path44.sep).join("/");
39962
40349
  }
39963
40350
  },
39964
40351
  recordHandoff: (sessionId, taskId, handoff) => taskLogs.note(sessionId, taskId, { type: "handoff", handoff }),
@@ -42087,6 +42474,7 @@ var StructuredSession = class extends AbstractRunnerSession {
42087
42474
  */
42088
42475
  async startAttached(opts) {
42089
42476
  const { server: server4, scope, shell, savePatch, webReads } = this.launch.tools;
42477
+ this.launch.onAttaching?.(1);
42090
42478
  const spawned = await spawnAttached({
42091
42479
  runner: this.launch.runner,
42092
42480
  create: () => this.launch.createAdapter(this.launch.runner, this.launch.deps),
@@ -42123,7 +42511,10 @@ var StructuredSession = class extends AbstractRunnerSession {
42123
42511
  },
42124
42512
  cancelled: this.ended.signal,
42125
42513
  startupExit: () => this.startupExit,
42126
- onRespawn: (failure2) => this.launch.onNotice?.(`${this.launch.runner} started without Ordewell's tools (${failure2}); respawning once.`),
42514
+ onRespawn: (failure2) => {
42515
+ this.launch.onNotice?.(`${this.launch.runner} started without Ordewell's tools (${failure2}); respawning once.`);
42516
+ this.launch.onAttaching?.(2);
42517
+ },
42127
42518
  failed: (first, second) => `Could not start ${this.launch.runner} with Ordewell's tools, so the task was not sent to it. First spawn: ${first}. Respawn: ${second}.`
42128
42519
  });
42129
42520
  if (!spawned) return;
@@ -42706,16 +43097,22 @@ var StructuredRunner = class extends AbstractRunner {
42706
43097
  checkHandoff: opts.checkHandoff ?? ((completion) => checkHandoff(completion, opts.taskId, [])),
42707
43098
  ...opts.readTask ? { readTask: opts.readTask } : {},
42708
43099
  ...opts.applyPatch ? { applyPatch: opts.applyPatch } : {},
42709
- onNotice: opts.onNotice
43100
+ onNotice: opts.onNotice,
43101
+ onAttaching: opts.onAttaching
42710
43102
  });
42711
43103
  console.error(`[structured] Starting ${opts.runner} [${opts.mode ?? "default"}] (${opts.modelId || "default"}) for task ${opts.taskId.slice(0, 8)}`);
42712
43104
  this.registerSession(id2, session);
43105
+ const cancel = () => void session.stop();
43106
+ opts.cancelled?.addEventListener("abort", cancel, { once: true });
42713
43107
  try {
43108
+ if (opts.cancelled?.aborted) cancel();
42714
43109
  await session.start(opts.prompt);
42715
43110
  } catch (err) {
42716
43111
  session.kill();
42717
43112
  this.sessions.delete(id2);
42718
43113
  throw err;
43114
+ } finally {
43115
+ opts.cancelled?.removeEventListener("abort", cancel);
42719
43116
  }
42720
43117
  return session;
42721
43118
  }
@@ -42789,7 +43186,7 @@ var RunnerRegistry = class {
42789
43186
 
42790
43187
  // src/plugins/removedPluginNotice.ts
42791
43188
  import * as fs23 from "fs";
42792
- import * as path44 from "path";
43189
+ import * as path45 from "path";
42793
43190
  function isManifestFile(file) {
42794
43191
  try {
42795
43192
  return fs23.statSync(file).isFile();
@@ -42798,10 +43195,10 @@ function isManifestFile(file) {
42798
43195
  }
42799
43196
  }
42800
43197
  function removedPluginNotice(dataDir = globalDataDir()) {
42801
- const directory = path44.join(dataDir, "plugins");
43198
+ const directory = path45.join(dataDir, "plugins");
42802
43199
  try {
42803
- const hasManifest = isManifestFile(path44.join(directory, "manifest.json")) || fs23.readdirSync(directory, { withFileTypes: true }).some(
42804
- (entry) => (entry.isDirectory() || entry.isSymbolicLink()) && isManifestFile(path44.join(directory, entry.name, "manifest.json"))
43200
+ const hasManifest = isManifestFile(path45.join(directory, "manifest.json")) || fs23.readdirSync(directory, { withFileTypes: true }).some(
43201
+ (entry) => (entry.isDirectory() || entry.isSymbolicLink()) && isManifestFile(path45.join(directory, entry.name, "manifest.json"))
42805
43202
  );
42806
43203
  if (hasManifest) return "Plugin runners were removed; manifests in ~/.ordewell/plugins/ are ignored. See the CHANGELOG.";
42807
43204
  } catch {
@@ -42812,15 +43209,15 @@ function removedPluginNotice(dataDir = globalDataDir()) {
42812
43209
 
42813
43210
  // src/utils/stateStore.ts
42814
43211
  import * as fs24 from "fs";
42815
- import * as path45 from "path";
43212
+ import * as path46 from "path";
42816
43213
  var STATE_FILE2 = "state.json";
42817
43214
  function getStatePath(baseDir) {
42818
- return path45.join(getStateDir(baseDir), STATE_FILE2);
43215
+ return path46.join(getStateDir(baseDir), STATE_FILE2);
42819
43216
  }
42820
43217
  var SESSION_ID_KEY = "sessionId";
42821
43218
  function saveState(plan, baseDir, sessionId) {
42822
43219
  const statePath = getStatePath(baseDir ?? process.cwd());
42823
- ensureDir(path45.dirname(statePath));
43220
+ ensureDir(path46.dirname(statePath));
42824
43221
  plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
42825
43222
  const file = sessionId ? { ...plan, [SESSION_ID_KEY]: sessionId } : plan;
42826
43223
  dropRetiredPlanFields(file);
@@ -43106,6 +43503,7 @@ export {
43106
43503
  assignedRunnerOf,
43107
43504
  assignmentIssue,
43108
43505
  assignmentIssueText,
43506
+ attachingLabel,
43109
43507
  augmentedPath,
43110
43508
  automergeSummary,
43111
43509
  autonomyLevelLabel,