@ordewell/core 0.5.1 → 0.5.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
@@ -2637,33 +2637,33 @@ function grepOptions(args) {
2637
2637
  };
2638
2638
  }
2639
2639
  var STOPPED_TOOL_RESULT = "Stopped by the user \u2014 this call was not executed, and no further tool calls will run this turn.";
2640
- async function executeTool(name, args, fs14, fetcher, signal) {
2641
- const outcome = await runTool(name, args, fs14, fetcher, signal);
2640
+ async function executeTool(name, args, fs15, fetcher, signal) {
2641
+ const outcome = await runTool(name, args, fs15, fetcher, signal);
2642
2642
  return { ...outcome, output: redactSecrets(outcome.output) };
2643
2643
  }
2644
- async function runTool(name, args, fs14, fetcher, signal) {
2644
+ async function runTool(name, args, fs15, fetcher, signal) {
2645
2645
  if (signal?.aborted) return { success: false, output: STOPPED_TOOL_RESULT, truncated: false };
2646
2646
  switch (name) {
2647
2647
  case "read_file": {
2648
2648
  const offset = num(args.offset);
2649
2649
  const limit = num(args.limit);
2650
2650
  const maxBytes = num(args.maxBytes);
2651
- return fs14.readFile(String(args.path ?? ""), { offset, limit, maxBytes });
2651
+ return fs15.readFile(String(args.path ?? ""), { offset, limit, maxBytes });
2652
2652
  }
2653
2653
  case "read_files": {
2654
2654
  const paths = Array.isArray(args.paths) ? args.paths.map(String) : [];
2655
- return fs14.readFiles(paths);
2655
+ return fs15.readFiles(paths);
2656
2656
  }
2657
2657
  case "glob":
2658
- return fs14.glob(String(args.pattern ?? ""), { path: str(args.path), headLimit: num(args.head_limit ?? args.headLimit) });
2658
+ return fs15.glob(String(args.pattern ?? ""), { path: str(args.path), headLimit: num(args.head_limit ?? args.headLimit) });
2659
2659
  case "grep":
2660
- return fs14.grep(String(args.pattern ?? ""), grepOptions(args));
2660
+ return fs15.grep(String(args.pattern ?? ""), grepOptions(args));
2661
2661
  case "find_symbol":
2662
- return fs14.findSymbol(String(args.symbol ?? ""), { language: str(args.language), path: str(args.path) });
2662
+ return fs15.findSymbol(String(args.symbol ?? ""), { language: str(args.language), path: str(args.path) });
2663
2663
  case "list_dir":
2664
- return fs14.listDir(String(args.path ?? ""), num(args.depth));
2664
+ return fs15.listDir(String(args.path ?? ""), num(args.depth));
2665
2665
  case "bash":
2666
- return fs14.bash(String(args.command ?? ""), signal);
2666
+ return fs15.bash(String(args.command ?? ""), signal);
2667
2667
  case "fetch": {
2668
2668
  if (!fetcher) return { success: false, output: "Web fetching is not available in this environment.", truncated: false };
2669
2669
  const url = String(args.url ?? "");
@@ -2771,14 +2771,14 @@ var PendingApprovals = class {
2771
2771
  ask(request) {
2772
2772
  const id = `ap-${Date.now()}-${counter++}`;
2773
2773
  const pending = { id, request, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
2774
- return new Promise((resolve6) => {
2774
+ return new Promise((resolve7) => {
2775
2775
  const settle = (granted) => {
2776
2776
  const entry = this.entries.get(id);
2777
2777
  if (!entry) return;
2778
2778
  clearTimeout(entry.timer);
2779
2779
  this.entries.delete(id);
2780
2780
  this.opts.onSettled?.(id, granted);
2781
- resolve6(granted);
2781
+ resolve7(granted);
2782
2782
  };
2783
2783
  const timer = setTimeout(() => settle(false), this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
2784
2784
  timer.unref?.();
@@ -2876,9 +2876,9 @@ function applyHeadLimit(stdout, headLimit) {
2876
2876
  }
2877
2877
  function formatSearchOutput(capped, root, opts) {
2878
2878
  if (capped.rows.length === 0) return opts.emptyMessage;
2879
- const sep = opts.sep ?? path3.sep;
2880
- const prefix = root === sep || root.endsWith(sep) ? root : `${root}${sep}`;
2881
- const body = prefix === sep ? capped.rows.join("\n") : capped.rows.map((row) => row.startsWith(prefix) ? row.slice(prefix.length) : row).join("\n");
2879
+ const sep2 = opts.sep ?? path3.sep;
2880
+ const prefix = root === sep2 || root.endsWith(sep2) ? root : `${root}${sep2}`;
2881
+ const body = prefix === sep2 ? capped.rows.join("\n") : capped.rows.map((row) => row.startsWith(prefix) ? row.slice(prefix.length) : row).join("\n");
2882
2882
  if (!capped.truncated) return body;
2883
2883
  const hint = opts.hint ? ` ${opts.hint}` : "";
2884
2884
  return `${body}
@@ -3469,6 +3469,15 @@ function configuredProviders(config) {
3469
3469
  return out;
3470
3470
  }
3471
3471
 
3472
+ // src/utils/maxParallel.ts
3473
+ var DEFAULT_MAX_PARALLEL = 3;
3474
+ function parseMaxParallel(value) {
3475
+ const text = String(value ?? "").trim();
3476
+ const n = typeof value === "number" ? value : Number(text);
3477
+ if (text === "" || !Number.isInteger(n) || n < 1) return null;
3478
+ return n;
3479
+ }
3480
+
3472
3481
  // src/interfaces/BaseConfig.ts
3473
3482
  function normalizeGeminiModel(id) {
3474
3483
  return id.replace(/^gemini:/, "").replace(/^google\//, "");
@@ -3673,7 +3682,7 @@ var BaseConfig = class {
3673
3682
  return "";
3674
3683
  }
3675
3684
  get maxParallelSessions() {
3676
- return parseInt(process.env.ORDEWELL_MAX_PARALLEL || "3", 10);
3685
+ return parseMaxParallel(process.env.ORDEWELL_MAX_PARALLEL) ?? DEFAULT_MAX_PARALLEL;
3677
3686
  }
3678
3687
  get researchEnabled() {
3679
3688
  return process.env.ORDEWELL_RESEARCH_ENABLED !== "false";
@@ -3824,28 +3833,28 @@ function posixBinDirs() {
3824
3833
  ];
3825
3834
  }
3826
3835
  function windowsBinDirs(env, home) {
3827
- const join22 = (...parts) => path4.win32.join(...parts);
3828
- const under = (base, ...parts) => base ? [join22(base, ...parts)] : [];
3836
+ const join23 = (...parts) => path4.win32.join(...parts);
3837
+ const under = (base, ...parts) => base ? [join23(base, ...parts)] : [];
3829
3838
  const appData = env.APPDATA;
3830
3839
  const localAppData = env.LOCALAPPDATA;
3831
3840
  const programData = env.ProgramData;
3832
3841
  return [
3833
3842
  // Native per-user installers, PowerShell-driven and otherwise.
3834
- join22(home, ".local", "bin"),
3835
- join22(home, ".opencode", "bin"),
3836
- join22(home, ".claude", "bin"),
3837
- join22(home, ".codex", "bin"),
3843
+ join23(home, ".local", "bin"),
3844
+ join23(home, ".opencode", "bin"),
3845
+ join23(home, ".claude", "bin"),
3846
+ join23(home, ".codex", "bin"),
3838
3847
  ...under(localAppData, "Programs", "claude"),
3839
3848
  // Node package managers. npm's global prefix is `%APPDATA%\npm`; the
3840
3849
  // literal spelling is kept as a fallback for a host that does not export
3841
3850
  // APPDATA (a service account, a stripped CI container).
3842
3851
  ...under(appData, "npm"),
3843
- join22(home, "AppData", "Roaming", "npm"),
3852
+ join23(home, "AppData", "Roaming", "npm"),
3844
3853
  ...under(localAppData, "pnpm"),
3845
3854
  ...under(localAppData, "Yarn", "bin"),
3846
- join22(home, ".bun", "bin"),
3855
+ join23(home, ".bun", "bin"),
3847
3856
  // Windows package managers.
3848
- join22(home, "scoop", "shims"),
3857
+ join23(home, "scoop", "shims"),
3849
3858
  ...under(env.SCOOP, "shims"),
3850
3859
  ...under(env.SCOOP_GLOBAL ?? programData, "scoop", "shims"),
3851
3860
  ...under(programData, "chocolatey", "bin"),
@@ -3855,7 +3864,7 @@ function windowsBinDirs(env, home) {
3855
3864
  ...under(localAppData, "Microsoft", "WindowsApps"),
3856
3865
  // Version managers. Volta's Windows home is not the POSIX `~/.volta`.
3857
3866
  ...under(localAppData, "Volta", "bin"),
3858
- join22(home, ".volta", "bin")
3867
+ join23(home, ".volta", "bin")
3859
3868
  ];
3860
3869
  }
3861
3870
  function wellKnownBinDirs(deps = {}) {
@@ -5836,12 +5845,12 @@ function buildConflictRepairPrompt(task, conflict, integrationBranch) {
5836
5845
  const files = conflict.conflictFiles ?? [];
5837
5846
  const asked = `What the task was asked to do:
5838
5847
  ${task.prompt ?? task.description}`;
5839
- const resolve6 = "resolve every conflict so that both sides' intent survives: keep the work already integrated and add what the task contributed.";
5848
+ const resolve7 = "resolve every conflict so that both sides' intent survives: keep the work already integrated and add what the task contributed.";
5840
5849
  const repos = conflict.repos.filter((repo) => repo !== SELF_REPO);
5841
5850
  if (repos.length === 0) {
5842
5851
  return [
5843
5852
  `Task #${task.order} "${task.title}" passed, but merging its branch \`${branch}\` into \`${integrationBranch}\` conflicted${files.length > 0 ? ` in ${files.join(", ")}` : ""}. None of its work has landed yet.`,
5844
- `This is the task's own worktree, on \`${branch}\`, with its work committed. Run \`git merge --no-edit ${integrationBranch}\` here and ${resolve6} Never drop either side wholesale, and never abort the merge or reset it away.`,
5853
+ `This is the task's own worktree, on \`${branch}\`, with its work committed. Run \`git merge --no-edit ${integrationBranch}\` here and ${resolve7} Never drop either side wholesale, and never abort the merge or reset it away.`,
5845
5854
  "Build and test the result the way this project does, commit the merge, and only then print the completion marker.",
5846
5855
  "",
5847
5856
  asked
@@ -5850,7 +5859,7 @@ ${task.prompt ?? task.description}`;
5850
5859
  const stoppedIn = conflict.conflictRepo ?? repos[0];
5851
5860
  return [
5852
5861
  `Task #${task.order} "${task.title}" passed, but landing its branch \`${branch}\` on \`${integrationBranch}\` conflicted in ${stoppedIn}${files.length > 0 ? ` (${files.join(", ")})` : ""}. A task lands in every repository it changed or in none, so none of its work has landed yet.`,
5853
- `This is the task's own workspace, with each repository at its usual path on \`${branch}\` and the task's work committed. In each repository the task changed \u2014 ${repos.join(", ")} \u2014 run \`git merge --no-edit ${integrationBranch}\` inside that repository's directory, and ${resolve6} Never drop either side wholesale, and never abort a merge or reset it away.`,
5862
+ `This is the task's own workspace, with each repository at its usual path on \`${branch}\` and the task's work committed. In each repository the task changed \u2014 ${repos.join(", ")} \u2014 run \`git merge --no-edit ${integrationBranch}\` inside that repository's directory, and ${resolve7} Never drop either side wholesale, and never abort a merge or reset it away.`,
5854
5863
  "Build and test the result the way this project does, commit the merge in each repository, and only then print the completion marker.",
5855
5864
  "",
5856
5865
  asked
@@ -6186,8 +6195,8 @@ function compactResearchResults(resultsText, maxChars = COMPACTION_LIMITS.resear
6186
6195
 
6187
6196
  // src/services/ContextCollector.ts
6188
6197
  var ORDEWELL_CONTEXT_MAX = 8e3;
6189
- async function collectResearchContext(fs14, runners) {
6190
- const collector = new ContextCollector(fs14);
6198
+ async function collectResearchContext(fs15, runners) {
6199
+ const collector = new ContextCollector(fs15);
6191
6200
  let contextStr = "";
6192
6201
  let firstContext = null;
6193
6202
  for (const r of runners) {
@@ -6214,8 +6223,8 @@ ${firstContext.dirStructure}
6214
6223
  return contextStr;
6215
6224
  }
6216
6225
  var ContextCollector = class {
6217
- constructor(fs14, registry) {
6218
- this.fs = fs14;
6226
+ constructor(fs15, registry) {
6227
+ this.fs = fs15;
6219
6228
  this.registry = registry;
6220
6229
  }
6221
6230
  fs;
@@ -6263,22 +6272,22 @@ var ALLOWED_TOOL_NAMES = new Set(subagentToolSpecs().map((t) => t.name));
6263
6272
  function refused(output) {
6264
6273
  return { success: false, output, truncated: false };
6265
6274
  }
6266
- function nonPromptingFs(fs14) {
6267
- const withinWorkspace = (p) => resolveWithin(fs14.getWorkspaceRoot(), p ?? ".").inside;
6275
+ function nonPromptingFs(fs15) {
6276
+ const withinWorkspace = (p) => resolveWithin(fs15.getWorkspaceRoot(), p ?? ".").inside;
6268
6277
  const outsideRefusal = (p) => refused(
6269
6278
  `Path "${p}" is outside the workspace. Research agents are confined to the workspace root \u2014 note the gap in your digest and let the planner request it directly.`
6270
6279
  );
6271
6280
  return {
6272
- getWorkspaceRoot: () => fs14.getWorkspaceRoot(),
6273
- readFile: (p, opts) => withinWorkspace(p) ? fs14.readFile(p, opts) : Promise.resolve(outsideRefusal(p)),
6281
+ getWorkspaceRoot: () => fs15.getWorkspaceRoot(),
6282
+ readFile: (p, opts) => withinWorkspace(p) ? fs15.readFile(p, opts) : Promise.resolve(outsideRefusal(p)),
6274
6283
  readFiles: (paths) => {
6275
6284
  const escaping = paths.find((p) => !withinWorkspace(p));
6276
- return escaping ? Promise.resolve(outsideRefusal(escaping)) : fs14.readFiles(paths);
6285
+ return escaping ? Promise.resolve(outsideRefusal(escaping)) : fs15.readFiles(paths);
6277
6286
  },
6278
- glob: (pattern, opts) => withinWorkspace(opts?.path) ? fs14.glob(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
6279
- grep: (pattern, opts) => withinWorkspace(opts?.path) ? fs14.grep(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
6280
- findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs14.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
6281
- listDir: (p, depth) => withinWorkspace(p) ? fs14.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
6287
+ glob: (pattern, opts) => withinWorkspace(opts?.path) ? fs15.glob(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
6288
+ grep: (pattern, opts) => withinWorkspace(opts?.path) ? fs15.grep(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
6289
+ findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs15.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
6290
+ listDir: (p, depth) => withinWorkspace(p) ? fs15.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
6282
6291
  bash: async (command, signal) => {
6283
6292
  const dialect = resolveResearchShell().dialect;
6284
6293
  const { tier, reason } = classifyCommand(command, { dialect });
@@ -6289,13 +6298,13 @@ function nonPromptingFs(fs14) {
6289
6298
  }
6290
6299
  const escaping = pathLikeArgs(command, { dialect }).find((p) => !withinWorkspace(p));
6291
6300
  if (escaping) return outsideRefusal(escaping);
6292
- return fs14.bash(command, signal);
6301
+ return fs15.bash(command, signal);
6293
6302
  }
6294
6303
  };
6295
6304
  }
6296
6305
  async function runLoop(prompt, deps) {
6297
6306
  if (deps.signal?.aborted) return "[research agent aborted before starting]";
6298
- const fs14 = nonPromptingFs(deps.fs);
6307
+ const fs15 = nonPromptingFs(deps.fs);
6299
6308
  const chat = deps.createChat((delta) => deps.onProgress?.({ type: "thinking", text: delta }));
6300
6309
  let turn = await chat.sendMessage(prompt, deps.signal);
6301
6310
  for (let step = 0; turn.hasToolCalls && step < SUBAGENT_LIMITS.maxSteps; step++) {
@@ -6312,7 +6321,7 @@ ${turn.text}`;
6312
6321
  if (tc.name === "read_file" && !("maxBytes" in args) && !("limit" in args)) args.limit = 2e3;
6313
6322
  const toolArgs = JSON.stringify(tc.args);
6314
6323
  deps.onProgress?.({ type: "tool_call", tool: tc.name, toolArgs, toolCallId: tc.id });
6315
- const res = await executeTool(tc.name, args, fs14, void 0, deps.signal);
6324
+ const res = await executeTool(tc.name, args, fs15, void 0, deps.signal);
6316
6325
  const output = res.output.length > SUBAGENT_LIMITS.toolOutputMaxChars ? res.output.slice(0, SUBAGENT_LIMITS.toolOutputMaxChars) + `
6317
6326
  [... truncated to ${SUBAGENT_LIMITS.toolOutputMaxChars} chars, total ${res.output.length}]` : res.output;
6318
6327
  const stepEntry = {
@@ -6428,7 +6437,7 @@ var BaseAiService = class _BaseAiService {
6428
6437
  * steering/failed result and the loop continues sequentially: a subagent can
6429
6438
  * never fail a plan or a turn.
6430
6439
  */
6431
- async executeSpawnAgent(args, fs14, onProgress, signal, subagentId) {
6440
+ async executeSpawnAgent(args, fs15, onProgress, signal, subagentId) {
6432
6441
  const prompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
6433
6442
  if (!prompt) {
6434
6443
  return { success: false, output: 'spawn_research_agent requires a non-empty "prompt" string: a self-contained task for the agent, including what its digest must report back.', truncated: false };
@@ -6439,20 +6448,20 @@ var BaseAiService = class _BaseAiService {
6439
6448
  if (!chat) throw new Error("subagent chats are not available for this provider");
6440
6449
  return chat;
6441
6450
  },
6442
- fs: fs14,
6451
+ fs: fs15,
6443
6452
  signal,
6444
6453
  onProgress: (progress) => onProgress({ ...progress, subagentId })
6445
6454
  });
6446
6455
  }
6447
6456
  /** Collect project context for the planning phase. Shared with the harness backend. */
6448
- static async collectResearchContext(fs14, runners) {
6449
- return collectResearchContext(fs14, runners);
6457
+ static async collectResearchContext(fs15, runners) {
6458
+ return collectResearchContext(fs15, runners);
6450
6459
  }
6451
6460
  /**
6452
6461
  * Execute one round of tool calls and report each through onProgress.
6453
6462
  * Returns the results to feed back plus the log entries produced.
6454
6463
  */
6455
- async executeToolCalls(toolCalls, fs14, onProgress, fetcher, stepIndex, thinking, signal) {
6464
+ async executeToolCalls(toolCalls, fs15, onProgress, fetcher, stepIndex, thinking, signal) {
6456
6465
  const toolResults = [];
6457
6466
  const logEntries = [];
6458
6467
  let resultsText = "";
@@ -6471,18 +6480,18 @@ var BaseAiService = class _BaseAiService {
6471
6480
  const subagentId = uuidv4();
6472
6481
  spawnSubagentIds.set(i, subagentId);
6473
6482
  onProgress({ type: "tool_call", tool: toolCalls[i].name, toolArgs: JSON.stringify(toolCalls[i].args), subagentId, toolCallId: toolCalls[i].id });
6474
- precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args }, fs14, onProgress, signal, subagentId));
6483
+ precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args }, fs15, onProgress, signal, subagentId));
6475
6484
  }) : Promise.resolve(),
6476
6485
  parallelIndexes.length > 1 ? mapWithConcurrency(parallelIndexes, TOOL_ROUND_CONCURRENCY, async (i) => {
6477
6486
  onProgress({ type: "tool_call", tool: toolCalls[i].name, toolArgs: JSON.stringify(toolCalls[i].args), toolCallId: toolCalls[i].id });
6478
- precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i], fs14, fetcher, signal));
6487
+ precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i], fs15, fetcher, signal));
6479
6488
  }) : Promise.resolve()
6480
6489
  ]);
6481
6490
  for (const [index, tc] of toolCalls.entries()) {
6482
6491
  const toolArgs = normalizedArgs[index];
6483
6492
  const preResult = precomputed.get(index);
6484
6493
  if (!preResult) onProgress({ type: "tool_call", tool: tc.name, toolArgs: JSON.stringify(tc.args), toolCallId: tc.id });
6485
- const toolResult = preResult ?? await executeTool(tc.name, toolArgs, fs14, fetcher, signal);
6494
+ const toolResult = preResult ?? await executeTool(tc.name, toolArgs, fs15, fetcher, signal);
6486
6495
  const MAX_RESPONSE_CHARS = 5e4;
6487
6496
  const truncatedResponse = toolResult.success && (toolResult.truncated || toolResult.output.length > MAX_RESPONSE_CHARS);
6488
6497
  const llmOutput = toolResult.output.length > MAX_RESPONSE_CHARS ? toolResult.output.slice(0, MAX_RESPONSE_CHARS) + `
@@ -6668,7 +6677,7 @@ ${llmOutput}
6668
6677
  * parsed tasks if a plan was emitted mid-loop, or null if the loop
6669
6678
  * exhausted without a valid plan.
6670
6679
  */
6671
- async runResearchLoop(chat, firstMessage, fs14, onProgress, runners, initialLog, fetcher, userGoal, runnerModes, autonomousDefault, signal) {
6680
+ async runResearchLoop(chat, firstMessage, fs15, onProgress, runners, initialLog, fetcher, userGoal, runnerModes, autonomousDefault, signal) {
6672
6681
  const researchLog = initialLog ? [...initialLog] : [];
6673
6682
  if (!initialLog && userGoal) {
6674
6683
  researchLog.push({
@@ -6695,7 +6704,7 @@ ${llmOutput}
6695
6704
  if (!turn.hasToolCalls) break;
6696
6705
  const { toolResults, logEntries, resultsText } = await this.executeToolCalls(
6697
6706
  turn.toolCalls,
6698
- fs14,
6707
+ fs15,
6699
6708
  onProgress,
6700
6709
  fetcher,
6701
6710
  step,
@@ -6930,11 +6939,11 @@ ${repairHint}` }] : parts;
6930
6939
  return this.consumePlanStream(result, onToken);
6931
6940
  }
6932
6941
  // --- IAiService implementation ---
6933
- async researchAndPlan(userDescription, runners, modelsByRunner, fs14, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
6942
+ async researchAndPlan(userDescription, runners, modelsByRunner, fs15, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
6934
6943
  this.ensureInit();
6935
6944
  const { autonomousDefault } = modes;
6936
6945
  const genModel = this.getPlanningModel();
6937
- const contextStr = await BaseAiService.collectResearchContext(fs14, runners);
6946
+ const contextStr = await BaseAiService.collectResearchContext(fs15, runners);
6938
6947
  const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
6939
6948
  const firstMessage = `${buildResearchToolsPrompt()}
6940
6949
 
@@ -6945,17 +6954,17 @@ Explore the workspace to understand the codebase, then generate the plan.`;
6945
6954
  generationConfig: { temperature: 0.3, topP: 0.95, maxOutputTokens: 16384 }
6946
6955
  });
6947
6956
  const researchChat = new GeminiResearchChat(chat);
6948
- const result = await this.runResearchLoop(researchChat, firstMessage, fs14, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
6957
+ const result = await this.runResearchLoop(researchChat, firstMessage, fs15, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
6949
6958
  if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
6950
6959
  const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
6951
6960
  return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
6952
6961
  }
6953
- async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs14, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
6962
+ async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs15, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
6954
6963
  this.ensureInit();
6955
6964
  const { autonomousDefault } = modes;
6956
6965
  let ctx = "";
6957
- if (fs14) {
6958
- const collected = await new ContextCollector(fs14).collect(runners[0] ?? "claude-code");
6966
+ if (fs15) {
6967
+ const collected = await new ContextCollector(fs15).collect(runners[0] ?? "claude-code");
6959
6968
  if (collected.aiflowContext) ctx = `
6960
6969
  <aiflow_context>
6961
6970
  ${collected.aiflowContext}
@@ -6971,12 +6980,12 @@ ${collected.aiflowContext}
6971
6980
  autonomousDefault
6972
6981
  );
6973
6982
  }
6974
- async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs14, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
6983
+ async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs15, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
6975
6984
  this.ensureInit();
6976
6985
  const { autonomousDefault } = modes;
6977
6986
  let aiflowContext;
6978
- if (fs14) {
6979
- const collected = await new ContextCollector(fs14).collect(existingPlan.runners[0] ?? "claude-code");
6987
+ if (fs15) {
6988
+ const collected = await new ContextCollector(fs15).collect(existingPlan.runners[0] ?? "claude-code");
6980
6989
  if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
6981
6990
  }
6982
6991
  const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
@@ -7212,11 +7221,11 @@ ${buildResearchToolsPrompt()}` }
7212
7221
  }
7213
7222
  }
7214
7223
  // --- IAiService implementation ---
7215
- async researchAndPlan(userDescription, runners, modelsByRunner, fs14, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
7224
+ async researchAndPlan(userDescription, runners, modelsByRunner, fs15, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
7216
7225
  this.ensureInit();
7217
7226
  const client = this.getClient();
7218
7227
  const { autonomousDefault } = modes;
7219
- const contextStr = await BaseAiService.collectResearchContext(fs14, runners);
7228
+ const contextStr = await BaseAiService.collectResearchContext(fs15, runners);
7220
7229
  const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
7221
7230
  const userText = `User goal: ${userDescription}`;
7222
7231
  const firstMessage = `${buildResearchToolsPrompt()}
@@ -7234,17 +7243,17 @@ ${userText}`;
7234
7243
  toOpenAiTools(),
7235
7244
  (delta) => onProgress({ type: "thinking", text: delta })
7236
7245
  );
7237
- const result = await this.runResearchLoop(researchChat, firstMessage, fs14, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
7246
+ const result = await this.runResearchLoop(researchChat, firstMessage, fs15, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
7238
7247
  if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
7239
7248
  const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
7240
7249
  return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
7241
7250
  }
7242
- async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs14, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
7251
+ async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs15, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
7243
7252
  this.ensureInit();
7244
7253
  const { autonomousDefault } = modes;
7245
7254
  let ctx = "";
7246
- if (fs14) {
7247
- const collected = await new ContextCollector(fs14).collect(runners[0] ?? "claude-code");
7255
+ if (fs15) {
7256
+ const collected = await new ContextCollector(fs15).collect(runners[0] ?? "claude-code");
7248
7257
  if (collected.aiflowContext) ctx = `
7249
7258
  <aiflow_context>
7250
7259
  ${collected.aiflowContext}
@@ -7260,12 +7269,12 @@ ${collected.aiflowContext}
7260
7269
  autonomousDefault
7261
7270
  );
7262
7271
  }
7263
- async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs14, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
7272
+ async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs15, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
7264
7273
  this.ensureInit();
7265
7274
  const { autonomousDefault } = modes;
7266
7275
  let aiflowContext;
7267
- if (fs14) {
7268
- const collected = await new ContextCollector(fs14).collect(existingPlan.runners[0] ?? "claude-code");
7276
+ if (fs15) {
7277
+ const collected = await new ContextCollector(fs15).collect(existingPlan.runners[0] ?? "claude-code");
7269
7278
  if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
7270
7279
  }
7271
7280
  const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
@@ -7284,6 +7293,9 @@ ${collected.aiflowContext}
7284
7293
  }
7285
7294
  };
7286
7295
 
7296
+ // src/services/TaskOrchestrator.ts
7297
+ import * as path11 from "path";
7298
+
7287
7299
  // src/services/promptAugment.ts
7288
7300
  var MAX_TAIL_CHARS = 500;
7289
7301
  var DEFAULT_PLAN_MAP_MAX_ENTRIES = 30;
@@ -7869,18 +7881,230 @@ ${line}
7869
7881
  };
7870
7882
 
7871
7883
  // src/services/transcriptCapture.ts
7872
- import { readFileSync as readFileSync2, readdirSync as readdirSync3, statSync as statSync4, existsSync as existsSync4 } from "fs";
7884
+ import { readFileSync as readFileSync3, readdirSync as readdirSync3, statSync as statSync5, existsSync as existsSync4 } from "fs";
7885
+ import * as os4 from "os";
7886
+ import * as path10 from "path";
7887
+
7888
+ // src/services/workspaceEnv.ts
7889
+ import { execFile as execFile2 } from "child_process";
7890
+ import * as fs5 from "fs";
7873
7891
  import * as os3 from "os";
7874
7892
  import * as path9 from "path";
7893
+
7894
+ // src/services/settingsEnvAllowlist.ts
7895
+ var ORDEWELL_SETTABLE_ENV = [
7896
+ "AI_PROVIDER",
7897
+ "ORCHESTRATOR_MODEL",
7898
+ "ORDEWELL_SUBAGENT_MODEL",
7899
+ "ORDEWELL_PLANNER_EFFORT",
7900
+ "ORDEWELL_RESEARCH_ENABLED",
7901
+ "ORDEWELL_MAX_PARALLEL",
7902
+ "ORDEWELL_CONFLICT_REPAIR_ATTEMPTS"
7903
+ ];
7904
+ var SETTINGS_ENV_REFUSED = [
7905
+ // Approval posture and pre-approved scope.
7906
+ "ORDEWELL_AUTONOMOUS_MODE",
7907
+ "ORDEWELL_APPROVAL_MODE",
7908
+ "ORDEWELL_APPROVAL_ALLOW",
7909
+ // Where settings are persisted.
7910
+ "ORDEWELL_SETTINGS_PATH",
7911
+ // Runtime options and dynamic loaders.
7912
+ "NODE_OPTIONS",
7913
+ "NODE_REPL_EXTERNAL_MODULE",
7914
+ "NODE_PATH",
7915
+ "ELECTRON_RUN_AS_NODE",
7916
+ "LD_PRELOAD",
7917
+ "LD_LIBRARY_PATH",
7918
+ "LD_AUDIT",
7919
+ "DYLD_INSERT_LIBRARIES",
7920
+ "DYLD_LIBRARY_PATH",
7921
+ "DYLD_FRAMEWORK_PATH",
7922
+ // Executable resolution.
7923
+ "PATH",
7924
+ "PATHEXT",
7925
+ "NODE"
7926
+ ];
7927
+ var REFUSED = new Set(SETTINGS_ENV_REFUSED);
7928
+ function providerEnvKeys() {
7929
+ const keys = [];
7930
+ for (const meta of Object.values(ALL_PROVIDERS)) {
7931
+ if (meta.apiKeyEnvVar) keys.push(meta.apiKeyEnvVar);
7932
+ if (meta.baseUrlEnvVar) keys.push(meta.baseUrlEnvVar);
7933
+ for (const detect of meta.detectEnvVars) keys.push(detect);
7934
+ }
7935
+ return keys;
7936
+ }
7937
+ var PROVIDER_CREDENTIAL_ENV = new Set(providerEnvKeys());
7938
+ var SETTINGS_ENV_ALLOWLIST = new Set(
7939
+ [...providerEnvKeys(), ...ORDEWELL_SETTABLE_ENV].filter((key) => !REFUSED.has(key))
7940
+ );
7941
+ function admitSettingsEnv(env) {
7942
+ const accepted = {};
7943
+ const rejected = [];
7944
+ for (const [key, value] of Object.entries(env)) {
7945
+ if (REFUSED.has(key) || !SETTINGS_ENV_ALLOWLIST.has(key) || typeof value !== "string") {
7946
+ rejected.push(key);
7947
+ continue;
7948
+ }
7949
+ accepted[key] = value;
7950
+ }
7951
+ return { accepted, rejected };
7952
+ }
7953
+
7954
+ // src/services/workspaceEnv.ts
7955
+ var WORKSPACE_ENV_FILE = "env";
7956
+ var DIRENV_TIMEOUT_MS = 5e3;
7957
+ var KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
7958
+ function defaultDeps() {
7959
+ return {
7960
+ run: (command, args, opts) => new Promise((resolve7) => {
7961
+ execFile2(command, args, { ...opts, timeout: DIRENV_TIMEOUT_MS, encoding: "utf8" }, (err, stdout, stderr) => {
7962
+ const code = err ? typeof err.code === "number" ? Number(err.code) : -1 : 0;
7963
+ resolve7({ code, stdout: String(stdout ?? ""), stderr: String(stderr ?? "") });
7964
+ });
7965
+ }),
7966
+ readFile: (file) => {
7967
+ try {
7968
+ return fs5.readFileSync(file, "utf8");
7969
+ } catch {
7970
+ return null;
7971
+ }
7972
+ },
7973
+ isDirectory: (dir) => {
7974
+ try {
7975
+ return fs5.statSync(dir).isDirectory();
7976
+ } catch {
7977
+ return false;
7978
+ }
7979
+ },
7980
+ baseEnv: process.env,
7981
+ home: os3.homedir()
7982
+ };
7983
+ }
7984
+ var REFUSED2 = new Set(SETTINGS_ENV_REFUSED);
7985
+ async function resolveWorkspaceEnv(cwd, overrides = {}) {
7986
+ const deps = { ...defaultDeps(), ...overrides };
7987
+ const result = { env: {}, blockedEnvrc: null, refused: [], trackedEnvFile: null };
7988
+ if (!deps.isDirectory(cwd)) return result;
7989
+ const collected = {};
7990
+ const direnv = await fromDirenv(checkoutDirOf(cwd, deps), deps);
7991
+ result.blockedEnvrc = direnv.blocked;
7992
+ Object.assign(collected, direnv.env);
7993
+ const file = findUp(cwd, path9.join(STATE_DIR, WORKSPACE_ENV_FILE), deps);
7994
+ if (file) {
7995
+ const content = deps.readFile(file);
7996
+ if (content !== null) {
7997
+ if (await isTracked(file, deps)) result.trackedEnvFile = file;
7998
+ else Object.assign(collected, parseEnvFile(content, { ...deps.baseEnv, ...collected }, deps.home));
7999
+ }
8000
+ }
8001
+ for (const [key, value] of Object.entries(collected)) {
8002
+ if (!KEY.test(key)) continue;
8003
+ if (REFUSED2.has(key) || key.toUpperCase() === "PATH") result.refused.push(key);
8004
+ else result.env[key] = value;
8005
+ }
8006
+ return result;
8007
+ }
8008
+ async function workspaceEnvOf(cwd) {
8009
+ return (await resolveWorkspaceEnv(cwd)).env;
8010
+ }
8011
+ function checkoutDirOf(cwd, deps) {
8012
+ const parts = path9.resolve(cwd).split(path9.sep);
8013
+ const at = parts.findIndex((part, i) => part === STATE_DIR && parts[i + 1] === "worktrees");
8014
+ if (at < 0 || parts.length < at + 4) return cwd;
8015
+ const root = parts.slice(0, at).join(path9.sep) || path9.sep;
8016
+ const mapped = path9.join(root, ...parts.slice(at + 4));
8017
+ return deps.isDirectory(mapped) ? mapped : root;
8018
+ }
8019
+ async function fromDirenv(cwd, deps) {
8020
+ const setting = deps.baseEnv.ORDEWELL_DIRENV;
8021
+ if (setting === "false" || setting === "0") return { env: {}, blocked: null };
8022
+ const baseEnv = Object.fromEntries(Object.entries(deps.baseEnv).filter(([key]) => !key.startsWith("DIRENV_")));
8023
+ let out;
8024
+ try {
8025
+ out = await deps.run("direnv", ["export", "json"], { cwd, env: baseEnv });
8026
+ } catch {
8027
+ return { env: {}, blocked: null };
8028
+ }
8029
+ const blocked = out.stderr.match(/error (\S*\.envrc) is blocked/);
8030
+ if (blocked) return { env: {}, blocked: blocked[1] };
8031
+ if (out.code !== 0 || !out.stdout.trim()) return { env: {}, blocked: null };
8032
+ let parsed;
8033
+ try {
8034
+ parsed = JSON.parse(out.stdout);
8035
+ } catch {
8036
+ return { env: {}, blocked: null };
8037
+ }
8038
+ if (!parsed || typeof parsed !== "object") return { env: {}, blocked: null };
8039
+ const env = {};
8040
+ for (const [key, value] of Object.entries(parsed)) {
8041
+ if (typeof value === "string" && !key.startsWith("DIRENV_")) env[key] = value;
8042
+ }
8043
+ return { env, blocked: null };
8044
+ }
8045
+ function findUp(start, relative5, deps) {
8046
+ let dir = path9.resolve(start);
8047
+ for (; ; ) {
8048
+ const candidate = path9.join(dir, relative5);
8049
+ if (deps.readFile(candidate) !== null) return candidate;
8050
+ const parent = path9.dirname(dir);
8051
+ if (parent === dir) return null;
8052
+ dir = parent;
8053
+ }
8054
+ }
8055
+ async function isTracked(file, deps) {
8056
+ try {
8057
+ const out = await deps.run("git", ["ls-files", "--error-unmatch", path9.basename(file)], { cwd: path9.dirname(file), env: deps.baseEnv });
8058
+ return out.code === 0;
8059
+ } catch {
8060
+ return false;
8061
+ }
8062
+ }
8063
+ function parseEnvFile(content, context, home) {
8064
+ const env = {};
8065
+ const lookup2 = (name) => env[name] ?? context[name] ?? "";
8066
+ const expand = (value) => {
8067
+ const tilde = value.startsWith("~/") ? path9.join(home, value.slice(2)) : value;
8068
+ return tilde.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, braced, bare) => lookup2(braced ?? bare));
8069
+ };
8070
+ for (const raw of content.split(/\r?\n/)) {
8071
+ const line = raw.trim().replace(/^export\s+/, "");
8072
+ if (!line || line.startsWith("#")) continue;
8073
+ const eq = line.indexOf("=");
8074
+ if (eq <= 0) continue;
8075
+ const key = line.slice(0, eq).trim();
8076
+ if (!KEY.test(key)) continue;
8077
+ const rest = line.slice(eq + 1).trim();
8078
+ if (rest.startsWith("'")) {
8079
+ const end = rest.indexOf("'", 1);
8080
+ env[key] = end > 0 ? rest.slice(1, end) : rest.slice(1);
8081
+ } else if (rest.startsWith('"')) {
8082
+ const end = rest.lastIndexOf('"');
8083
+ env[key] = expand((end > 0 ? rest.slice(1, end) : rest.slice(1)).replace(/\\n/g, "\n").replace(/\\"/g, '"'));
8084
+ } else {
8085
+ env[key] = expand(rest.replace(/\s+#.*$/, ""));
8086
+ }
8087
+ }
8088
+ return env;
8089
+ }
8090
+
8091
+ // src/services/transcriptCapture.ts
7875
8092
  var HomeTranscriptReader = class {
7876
8093
  home;
8094
+ claudeConfigDirs;
7877
8095
  constructor(opts = {}) {
7878
8096
  const { homeDir } = opts;
7879
- this.home = homeDir ? () => homeDir : () => process.env.HOME || os3.homedir();
8097
+ this.home = homeDir ? () => homeDir : () => process.env.HOME || os4.homedir();
8098
+ const workspaceEnv = opts.workspaceEnv ?? workspaceEnvOf;
8099
+ this.claudeConfigDirs = async (cwd) => [...new Set([
8100
+ (await workspaceEnv(cwd)).CLAUDE_CONFIG_DIR,
8101
+ homeDir ? void 0 : process.env.CLAUDE_CONFIG_DIR,
8102
+ path10.join(this.home(), ".claude")
8103
+ ].filter((dir) => Boolean(dir)))];
7880
8104
  }
7881
8105
  async finalAssistantText(query, maxChars = 4e3) {
7882
8106
  try {
7883
- if (query.runner === "claude-code") return claudeFinal(this.home(), query, maxChars);
8107
+ if (query.runner === "claude-code") return claudeFinal(await this.claudeConfigDirs(query.cwd), query, maxChars);
7884
8108
  if (query.runner === "opencode") return await opencodeFinal(this.home(), query, maxChars);
7885
8109
  if (query.runner === "codex") return codexFinal(this.home(), query, maxChars);
7886
8110
  } catch {
@@ -7889,22 +8113,22 @@ var HomeTranscriptReader = class {
7889
8113
  }
7890
8114
  };
7891
8115
  var CLAUDE_PROJECT_NAME_MAX = 200;
7892
- function claudeProjectDirs(home, cwd) {
7893
- const projects = path9.join(home, ".claude", "projects");
8116
+ function claudeProjectDirs(configDir2, cwd) {
8117
+ const projects = path10.join(configDir2, "projects");
7894
8118
  const munged = cwd.replace(/[^a-zA-Z0-9]/g, "-");
7895
8119
  if (munged.length <= CLAUDE_PROJECT_NAME_MAX) {
7896
- const dir = path9.join(projects, munged);
8120
+ const dir = path10.join(projects, munged);
7897
8121
  return existsSync4(dir) ? [dir] : [];
7898
8122
  }
7899
8123
  if (!existsSync4(projects)) return [];
7900
8124
  const prefix = `${munged.slice(0, CLAUDE_PROJECT_NAME_MAX)}-`;
7901
- return readdirSync3(projects).filter((name) => name.startsWith(prefix)).map((name) => path9.join(projects, name));
8125
+ return readdirSync3(projects).filter((name) => name.startsWith(prefix)).map((name) => path10.join(projects, name));
7902
8126
  }
7903
- function claudeFinal(home, query, maxChars) {
8127
+ function claudeFinal(configDirs, query, maxChars) {
7904
8128
  const cutoff = query.startedAt ? Date.parse(query.startedAt) : 0;
7905
- const candidates = claudeProjectDirs(home, query.cwd).flatMap((dir) => readdirSync3(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path9.join(dir, f))).filter((f) => cutoff ? statSync4(f).mtimeMs >= cutoff - 5e3 : true).sort((a, b) => statSync4(b).mtimeMs - statSync4(a).mtimeMs);
8129
+ const candidates = configDirs.flatMap((dir) => claudeProjectDirs(dir, query.cwd)).flatMap((dir) => readdirSync3(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path10.join(dir, f))).filter((f) => cutoff ? statSync5(f).mtimeMs >= cutoff - 5e3 : true).sort((a, b) => statSync5(b).mtimeMs - statSync5(a).mtimeMs);
7906
8130
  for (const file of candidates) {
7907
- const raw = readFileSync2(file, "utf8");
8131
+ const raw = readFileSync3(file, "utf8");
7908
8132
  if (!raw.includes(query.marker)) continue;
7909
8133
  const text = claudeLastAssistant(raw);
7910
8134
  if (text) return clamp(text, maxChars);
@@ -7938,7 +8162,7 @@ async function opencodeFinal(home, query, maxChars) {
7938
8162
  } catch {
7939
8163
  return null;
7940
8164
  }
7941
- const dbPath = path9.join(home, ".local", "share", "opencode", "opencode.db");
8165
+ const dbPath = path10.join(home, ".local", "share", "opencode", "opencode.db");
7942
8166
  if (!existsSync4(dbPath)) return null;
7943
8167
  let db;
7944
8168
  try {
@@ -7991,7 +8215,7 @@ async function opencodeFinal(home, query, maxChars) {
7991
8215
  }
7992
8216
  }
7993
8217
  function codexFinal(home, query, maxChars) {
7994
- const root = path9.join(home, ".codex", "sessions");
8218
+ const root = path10.join(home, ".codex", "sessions");
7995
8219
  if (!existsSync4(root)) return null;
7996
8220
  const cutoff = query.startedAt ? Date.parse(query.startedAt) : 0;
7997
8221
  const files = [];
@@ -8005,10 +8229,10 @@ function codexFinal(home, query, maxChars) {
8005
8229
  continue;
8006
8230
  }
8007
8231
  for (const e of entries) {
8008
- const full = path9.join(dir, e);
8232
+ const full = path10.join(dir, e);
8009
8233
  let st;
8010
8234
  try {
8011
- st = statSync4(full);
8235
+ st = statSync5(full);
8012
8236
  } catch {
8013
8237
  continue;
8014
8238
  }
@@ -8016,7 +8240,7 @@ function codexFinal(home, query, maxChars) {
8016
8240
  else if (e.startsWith("rollout-") && e.endsWith(".jsonl") && (!cutoff || st.mtimeMs >= cutoff - 5e3)) files.push(full);
8017
8241
  }
8018
8242
  }
8019
- files.sort((a, b) => statSync4(b).mtimeMs - statSync4(a).mtimeMs);
8243
+ files.sort((a, b) => statSync5(b).mtimeMs - statSync5(a).mtimeMs);
8020
8244
  for (const file of cutoff ? files : files.slice(0, 10)) {
8021
8245
  const text = codexLastAssistant(file, query);
8022
8246
  if (text) return clamp(text, maxChars);
@@ -8024,7 +8248,7 @@ function codexFinal(home, query, maxChars) {
8024
8248
  return null;
8025
8249
  }
8026
8250
  function codexLastAssistant(file, query) {
8027
- const raw = readFileSync2(file, "utf8");
8251
+ const raw = readFileSync3(file, "utf8");
8028
8252
  if (!raw.includes(query.marker)) return null;
8029
8253
  const lines = raw.split("\n").filter((l) => l.trim());
8030
8254
  let sawCwd = false;
@@ -8456,6 +8680,43 @@ ${taskB.description}`,
8456
8680
  }
8457
8681
  };
8458
8682
 
8683
+ // src/utils/shell.ts
8684
+ var ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]|\x1b\][^\x07]*\x07|\x1b[()][AB012]|\r/g;
8685
+ function stripAnsi(text) {
8686
+ return text.replace(ANSI_RE, "");
8687
+ }
8688
+ function posixShellQuote(s) {
8689
+ return `'${s.replace(/'/g, `'\\''`)}'`;
8690
+ }
8691
+ function buildShellInvocation(command, args) {
8692
+ const inner = [command, ...args].map(posixShellQuote).join(" ");
8693
+ return { shellPath: "/bin/bash", shellArgs: ["-lc", inner] };
8694
+ }
8695
+ function wrapWithPty(command, args, opts = {}) {
8696
+ const inner = [command, ...args].map(posixShellQuote).join(" ");
8697
+ const watcher = opts.controlChannel ? 'exec 4<&0; ( while read -r C R <&3; do stty cols "$C" rows "$R" <&4; done ) & ' : "";
8698
+ const setup = opts.size ? `stty cols ${opts.size.cols} rows ${opts.size.rows}; ` : "";
8699
+ return { command: "script", args: ["-q", "-e", "-f", "-c", `${watcher}${setup}${inner}`, "/dev/null"] };
8700
+ }
8701
+
8702
+ // src/services/blockingPrompts.ts
8703
+ var TAIL = 4096;
8704
+ var squash = (text) => stripAnsi(text).replace(/\s+/g, "").toLowerCase();
8705
+ function watchBlockingPrompts(session, prompts, onPrompt) {
8706
+ if (prompts.length === 0) return;
8707
+ const pending = prompts.map((prompt) => ({ prompt, phrase: squash(prompt.phrase) }));
8708
+ let tail = "";
8709
+ session.onOutput((text) => {
8710
+ if (pending.length === 0) return;
8711
+ tail = (tail + squash(text)).slice(-TAIL);
8712
+ for (let i = pending.length - 1; i >= 0; i--) {
8713
+ if (!tail.includes(pending[i].phrase)) continue;
8714
+ onPrompt(pending[i].prompt);
8715
+ pending.splice(i, 1);
8716
+ }
8717
+ });
8718
+ }
8719
+
8459
8720
  // src/services/TaskOrchestrator.ts
8460
8721
  var SHARED_ROOT_TAIL = "tasks run in the workspace root without worktree isolation.";
8461
8722
  function sharedRootNotice(reason, repos) {
@@ -8519,6 +8780,25 @@ var TaskOrchestrator = class {
8519
8780
  */
8520
8781
  retryCounts = /* @__PURE__ */ new Map();
8521
8782
  spawnCounts = /* @__PURE__ */ new Map();
8783
+ /**
8784
+ * The terminal a verdict left open, by task. It stays so the user can read
8785
+ * the agent's output or keep talking to it — but only while its worktree
8786
+ * does: once that is removed the agent sits in a deleted directory, and a
8787
+ * newer attempt in the same worktree would share it with a second agent.
8788
+ * Without this, every task of every run left one agent process running
8789
+ * until the daemon stopped.
8790
+ */
8791
+ lingering = /* @__PURE__ */ new Map();
8792
+ /**
8793
+ * A full-plan run a failure paused. Retrying the failed task is the explicit
8794
+ * resume the pause waits for — without this a retry only reset the task to
8795
+ * pending and nothing ran until the user also re-ran the whole plan.
8796
+ */
8797
+ haltedByFailure = false;
8798
+ /** The workspace's own variables for a task's cwd (ADR-0016); swapped out in tests. */
8799
+ workspaceEnv = (cwd) => resolveWorkspaceEnv(cwd);
8800
+ /** What the workspace env has already warned about, so a run says it once, not per task. */
8801
+ envWarnings = /* @__PURE__ */ new Set();
8522
8802
  /**
8523
8803
  * Tasks pulled out of auto-scheduling (user-cancelled or failed to spawn).
8524
8804
  * They stay 'pending' — "not executed" — but the scheduler skips them until
@@ -8562,6 +8842,32 @@ var TaskOrchestrator = class {
8562
8842
  setWorkspaceRoot(fn) {
8563
8843
  this.workspaceRootFn = fn;
8564
8844
  }
8845
+ setWorkspaceEnvResolver(resolve7) {
8846
+ this.workspaceEnv = resolve7;
8847
+ }
8848
+ /**
8849
+ * The variables a task's agent gets from its workspace. What cannot be
8850
+ * applied is said once — silently starting without them is how agents ran
8851
+ * under the wrong account when an edited `.envrc` was left unallowed.
8852
+ */
8853
+ async envForTask(cwd) {
8854
+ const resolved = await this.workspaceEnv(cwd);
8855
+ const warn = (key, message) => {
8856
+ if (this.envWarnings.has(key)) return;
8857
+ this.envWarnings.add(key);
8858
+ this.notifications.warn(message);
8859
+ };
8860
+ if (resolved.blockedEnvrc) {
8861
+ warn(`blocked:${resolved.blockedEnvrc}`, `direnv has blocked ${resolved.blockedEnvrc}, so tasks start without its variables. Run \`direnv allow\` in ${path11.dirname(resolved.blockedEnvrc)} to use them.`);
8862
+ }
8863
+ if (resolved.trackedEnvFile) {
8864
+ 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.`);
8865
+ }
8866
+ if (resolved.refused.length > 0) {
8867
+ warn(`refused:${resolved.refused.join(",")}`, `Ignored ${resolved.refused.join(", ")} from the workspace environment: Ordewell never passes these to agents.`);
8868
+ }
8869
+ return resolved.env;
8870
+ }
8565
8871
  setRegistry(registry) {
8566
8872
  this.registry = registry;
8567
8873
  }
@@ -8706,6 +9012,7 @@ var TaskOrchestrator = class {
8706
9012
  return result;
8707
9013
  }
8708
9014
  async clearMergedRun(run) {
9015
+ this.closeLingeringOf(run);
8709
9016
  try {
8710
9017
  await this.isolation.discard(run, { integration: "delete-merged" });
8711
9018
  } catch (err) {
@@ -8716,14 +9023,32 @@ var TaskOrchestrator = class {
8716
9023
  }
8717
9024
  /** Worktrees and task branches go; the integration branch and the record stay for review and merge. */
8718
9025
  async cleanupRun() {
9026
+ this.closeLingeringOf(this.requireRun());
8719
9027
  await this.isolation.discard(this.requireRun(), { integration: "keep" });
8720
9028
  this.emit("onIsolationChanged");
8721
9029
  }
8722
9030
  /** The run and everything it made go, and the plan forgets it; the next run starts afresh. */
8723
9031
  async discardRun() {
9032
+ this.closeLingeringOf(this.requireRun());
8724
9033
  await this.isolation.discard(this.requireRun(), { integration: "delete" });
8725
9034
  this.forgetRun();
8726
9035
  }
9036
+ watchBlockingPrompts(task, attempt, session) {
9037
+ const manifest = this.registry?.get(attempt.runner)?.manifest;
9038
+ watchBlockingPrompts(session, manifest?.runner.blockingPrompts ?? [], (prompt) => {
9039
+ if (this.attempts.get(task.id) !== attempt) return;
9040
+ this.notifications.warn(`Task "${task.title}" is waiting for you: ${manifest?.displayName ?? attempt.runner} is asking ${prompt.asks}. Answer it in the task's terminal.`);
9041
+ });
9042
+ }
9043
+ closeLingering(taskId) {
9044
+ const sessionId = this.lingering.get(taskId);
9045
+ if (sessionId === void 0) return;
9046
+ this.lingering.delete(taskId);
9047
+ this.terminalRunner.stop(sessionId);
9048
+ }
9049
+ closeLingeringOf(run) {
9050
+ for (const taskId of Object.keys(run.tasks)) this.closeLingering(taskId);
9051
+ }
8727
9052
  forgetRun() {
8728
9053
  this.isolationRun = null;
8729
9054
  this.resolvers = {};
@@ -8811,11 +9136,18 @@ var TaskOrchestrator = class {
8811
9136
  if (!await this.openRun(() => this.start()) || this.running) return;
8812
9137
  console.log(`[TaskOrchestrator] Starting with ${this.store.allTasks.length} tasks (${this.store.allTasks.filter((t) => t.type === "ai" && t.prompt).length} AI ready)`);
8813
9138
  this.running = true;
9139
+ this.haltedByFailure = false;
8814
9140
  this.planStatus = "running";
8815
9141
  this.emit("onTaskChanged");
8816
9142
  await this.tick();
8817
9143
  }
9144
+ haltOnFailure() {
9145
+ if (this.running) this.haltedByFailure = true;
9146
+ this.running = false;
9147
+ this.planStatus = "approved";
9148
+ }
8818
9149
  stop() {
9150
+ this.haltedByFailure = false;
8819
9151
  this.running = false;
8820
9152
  this.planStatus = "approved";
8821
9153
  this.terminalRunner.stopAll();
@@ -8852,8 +9184,7 @@ ${summary || "(empty \u2014 no output captured)"}`);
8852
9184
  await this.landPassed(task, landing);
8853
9185
  } else {
8854
9186
  this.store.markFailed(taskId);
8855
- this.running = false;
8856
- this.planStatus = "approved";
9187
+ this.haltOnFailure();
8857
9188
  this.notifications.error(`Task "${task.title}" failed verification: ${verdict.reason}`);
8858
9189
  if (attempt.worktree) await this.releaseWorktree(taskId, { keep: true });
8859
9190
  }
@@ -8993,8 +9324,7 @@ ${summary || "(empty \u2014 no output captured)"}`);
8993
9324
  if (whyNot) this.tell("info", whyNot);
8994
9325
  } else {
8995
9326
  this.store.markFailed(task.id);
8996
- this.running = false;
8997
- this.planStatus = "approved";
9327
+ this.haltOnFailure();
8998
9328
  this.notifications.error(inRepo ? `Task "${task.title}" passed, but git could not integrate its work in ${inRepo}, so none of it landed. Its worktrees are kept for inspection.` : `Task "${task.title}" passed, but git could not integrate its work. Its worktree is kept for inspection.`);
8999
9329
  }
9000
9330
  }
@@ -9045,6 +9375,7 @@ ${summary || "(empty \u2014 no output captured)"}`);
9045
9375
  await integration;
9046
9376
  const run = this.isolationRun;
9047
9377
  if (!run?.tasks[taskId]) return;
9378
+ if (!opts.keep) this.closeLingering(taskId);
9048
9379
  try {
9049
9380
  await this.isolation.release(run, taskId, opts);
9050
9381
  } catch (err) {
@@ -9193,7 +9524,8 @@ ${summary || "(empty \u2014 no output captured)"}`);
9193
9524
  this.onHold.delete(taskId);
9194
9525
  this.emit("onTaskChanged");
9195
9526
  await this.releaseWorktree(taskId, { keep: false }, ended?.integration);
9196
- await this.tick();
9527
+ if (this.haltedByFailure && !this.running) await this.start();
9528
+ else await this.tick();
9197
9529
  }
9198
9530
  /**
9199
9531
  * Manually start a single AI task right now, bypassing dependency/readiness
@@ -9319,6 +9651,8 @@ ${summary || "(empty \u2014 no output captured)"}`);
9319
9651
  // A merge to resolve is not new behaviour to drive test-first.
9320
9652
  tddEnabled: !attempt.repair && this.tddEnabled()
9321
9653
  });
9654
+ this.closeLingering(task.id);
9655
+ const env = await this.envForTask(cwd);
9322
9656
  const session = await this.terminalRunner.spawn({
9323
9657
  taskId: task.id,
9324
9658
  runner: attempt.runner,
@@ -9330,13 +9664,15 @@ ${summary || "(empty \u2014 no output captured)"}`);
9330
9664
  cwd,
9331
9665
  registry: this.registry ?? void 0,
9332
9666
  order: task.order,
9333
- title: task.title
9667
+ title: task.title,
9668
+ env
9334
9669
  });
9335
9670
  if (this.attempts.get(task.id) !== attempt) return this.abandonSpawn(task, attempt, session);
9336
9671
  attempt.phase = "running";
9337
9672
  attempt.session = session;
9338
9673
  this.output.attach(task.id, session);
9339
9674
  this.verifier.watch(task, session);
9675
+ this.watchBlockingPrompts(task, attempt, session);
9340
9676
  this.emit("onTaskChanged");
9341
9677
  if (attempt.repair) {
9342
9678
  const group = this.isolationRun?.repos.some((r) => r.path !== SELF_REPO);
@@ -9569,6 +9905,7 @@ ${summary || "(empty \u2014 no output captured)"}`);
9569
9905
  const attempt = this.attempts.get(taskId);
9570
9906
  this.attempts.delete(taskId);
9571
9907
  if (attempt) this.output.detach(taskId);
9908
+ if (reason === "verdict" && attempt?.session) this.lingering.set(taskId, attempt.session.id);
9572
9909
  if (reason === "cancel" || reason === "release" || reason === "complete" || reason === "retry") {
9573
9910
  const task = this.store.get(taskId);
9574
9911
  if (task) this.verifier.clear(task);
@@ -9711,29 +10048,29 @@ ${errors.map((e) => `- ${e}`).join("\n")}`
9711
10048
  };
9712
10049
 
9713
10050
  // src/services/ModelDiscovery.ts
9714
- import * as path12 from "path";
10051
+ import * as path14 from "path";
9715
10052
  import { exec as exec3, spawn } from "child_process";
9716
10053
  import { promisify as promisify3 } from "util";
9717
10054
  import * as fsSync3 from "fs";
9718
10055
  import * as osMod from "os";
9719
10056
 
9720
10057
  // src/utils/globalDataDir.ts
9721
- import * as fs5 from "fs";
9722
- import * as path10 from "path";
9723
- import * as os4 from "os";
10058
+ import * as fs6 from "fs";
10059
+ import * as path12 from "path";
10060
+ import * as os5 from "os";
9724
10061
  function globalDataDir() {
9725
- return path10.join(os4.homedir(), ".ordewell");
10062
+ return path12.join(os5.homedir(), ".ordewell");
9726
10063
  }
9727
10064
  function copyDirSync(src, dest) {
9728
- fs5.mkdirSync(dest, { recursive: true });
9729
- const entries = fs5.readdirSync(src, { withFileTypes: true });
10065
+ fs6.mkdirSync(dest, { recursive: true });
10066
+ const entries = fs6.readdirSync(src, { withFileTypes: true });
9730
10067
  for (const entry of entries) {
9731
- const srcPath = path10.join(src, entry.name);
9732
- const destPath = path10.join(dest, entry.name);
10068
+ const srcPath = path12.join(src, entry.name);
10069
+ const destPath = path12.join(dest, entry.name);
9733
10070
  if (entry.isDirectory()) {
9734
10071
  copyDirSync(srcPath, destPath);
9735
10072
  } else {
9736
- fs5.copyFileSync(srcPath, destPath);
10073
+ fs6.copyFileSync(srcPath, destPath);
9737
10074
  }
9738
10075
  }
9739
10076
  }
@@ -9741,50 +10078,29 @@ var migrated = false;
9741
10078
  function migrateOldConfigDir() {
9742
10079
  if (migrated) return;
9743
10080
  migrated = true;
9744
- const oldDir = path10.join(os4.homedir(), ".config", "ordewell");
9745
- if (!fs5.existsSync(oldDir)) return;
10081
+ const oldDir = path12.join(os5.homedir(), ".config", "ordewell");
10082
+ if (!fs6.existsSync(oldDir)) return;
9746
10083
  const newDir = globalDataDir();
9747
10084
  const liftFile = (name) => {
9748
- const oldFile = path10.join(oldDir, name);
9749
- const newFile = path10.join(newDir, name);
9750
- if (fs5.existsSync(oldFile) && !fs5.existsSync(newFile)) {
9751
- fs5.mkdirSync(newDir, { recursive: true });
9752
- fs5.copyFileSync(oldFile, newFile);
10085
+ const oldFile = path12.join(oldDir, name);
10086
+ const newFile = path12.join(newDir, name);
10087
+ if (fs6.existsSync(oldFile) && !fs6.existsSync(newFile)) {
10088
+ fs6.mkdirSync(newDir, { recursive: true });
10089
+ fs6.copyFileSync(oldFile, newFile);
9753
10090
  }
9754
10091
  };
9755
10092
  liftFile("settings.json");
9756
10093
  liftFile(".env");
9757
- const oldPlugins = path10.join(oldDir, "plugins");
9758
- const newPlugins = path10.join(newDir, "plugins");
9759
- if (fs5.existsSync(oldPlugins) && !fs5.existsSync(newPlugins)) {
10094
+ const oldPlugins = path12.join(oldDir, "plugins");
10095
+ const newPlugins = path12.join(newDir, "plugins");
10096
+ if (fs6.existsSync(oldPlugins) && !fs6.existsSync(newPlugins)) {
9760
10097
  copyDirSync(oldPlugins, newPlugins);
9761
10098
  }
9762
10099
  }
9763
10100
 
9764
10101
  // src/utils/launch.ts
9765
10102
  import * as fsSync2 from "fs";
9766
- import * as path11 from "path";
9767
-
9768
- // src/utils/shell.ts
9769
- var ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]|\x1b\][^\x07]*\x07|\x1b[()][AB012]|\r/g;
9770
- function stripAnsi(text) {
9771
- return text.replace(ANSI_RE, "");
9772
- }
9773
- function posixShellQuote(s) {
9774
- return `'${s.replace(/'/g, `'\\''`)}'`;
9775
- }
9776
- function buildShellInvocation(command, args) {
9777
- const inner = [command, ...args].map(posixShellQuote).join(" ");
9778
- return { shellPath: "/bin/bash", shellArgs: ["-lc", inner] };
9779
- }
9780
- function wrapWithPty(command, args, opts = {}) {
9781
- const inner = [command, ...args].map(posixShellQuote).join(" ");
9782
- const watcher = opts.controlChannel ? 'exec 4<&0; ( while read -r C R <&3; do stty cols "$C" rows "$R" <&4; done ) & ' : "";
9783
- const setup = opts.size ? `stty cols ${opts.size.cols} rows ${opts.size.rows}; ` : "";
9784
- return { command: "script", args: ["-q", "-e", "-f", "-c", `${watcher}${setup}${inner}`, "/dev/null"] };
9785
- }
9786
-
9787
- // src/utils/launch.ts
10103
+ import * as path13 from "path";
9788
10104
  var DIRECT_EXTENSIONS = [".exe", ".com"];
9789
10105
  var BATCH_EXTENSIONS = [".cmd", ".bat"];
9790
10106
  var POWERSHELL_EXTENSIONS = [".ps1"];
@@ -9792,7 +10108,7 @@ var CMD_EXE_MAX_COMMAND_LINE = 8191;
9792
10108
  var WINDOWS_MAX_COMMAND_LINE = 32767;
9793
10109
  function defaultPowerShell() {
9794
10110
  const root = process.env.SystemRoot || process.env.windir;
9795
- return root ? path11.win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe") : "powershell.exe";
10111
+ return root ? path13.win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe") : "powershell.exe";
9796
10112
  }
9797
10113
  var POWERSHELL_FLAGS = ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File"];
9798
10114
  var CommandLineTooLongError = class extends Error {
@@ -9844,7 +10160,7 @@ Searched: ${searchedPath || "(empty PATH)"}`);
9844
10160
  };
9845
10161
  function isResolvableOnPosixPath(command, PATH, exists) {
9846
10162
  if (command.includes("/")) return exists(command);
9847
- return PATH.split(path11.delimiter).map((d) => d.trim()).filter(Boolean).some((dir) => exists(path11.join(dir, command)));
10163
+ return PATH.split(path13.delimiter).map((d) => d.trim()).filter(Boolean).some((dir) => exists(path13.join(dir, command)));
9848
10164
  }
9849
10165
  function isExecutableResolved(command, plan, PATH, deps = {}) {
9850
10166
  if ((deps.platform ?? process.platform) === "win32") return plan.file !== command;
@@ -9884,9 +10200,9 @@ function cmdArgs(line) {
9884
10200
  return ["/d", "/s", "/c", `"${line}"`];
9885
10201
  }
9886
10202
  function candidateExtensions(command, pathExt) {
9887
- const declared = pathExt.split(path11.win32.delimiter).map((e) => e.trim().toLowerCase()).filter(Boolean);
10203
+ const declared = pathExt.split(path13.win32.delimiter).map((e) => e.trim().toLowerCase()).filter(Boolean);
9888
10204
  const known = /* @__PURE__ */ new Set([...DIRECT_EXTENSIONS, ...BATCH_EXTENSIONS, ...POWERSHELL_EXTENSIONS, ...declared]);
9889
- const ext = path11.win32.extname(command).toLowerCase();
10205
+ const ext = path13.win32.extname(command).toLowerCase();
9890
10206
  if (ext && known.has(ext)) {
9891
10207
  const route = BATCH_EXTENSIONS.includes(ext) ? "cmd" : POWERSHELL_EXTENSIONS.includes(ext) ? "powershell" : "direct";
9892
10208
  return { direct: [], cmd: [], powershell: [], [route]: [""] };
@@ -9907,13 +10223,13 @@ async function findWindowsExecutables(command, deps) {
9907
10223
  const exists = deps.exists ?? defaultExists2;
9908
10224
  const pathExt = (deps.pathExt ?? (() => process.env.PATHEXT ?? ""))();
9909
10225
  const byRoute = candidateExtensions(command, pathExt);
9910
- const dirs = path11.win32.basename(command) !== command ? [path11.win32.dirname(path11.win32.resolve(command))] : (await (deps.resolvePath ?? augmentedPath)() || "").split(path11.win32.delimiter).map((d) => d.trim().replace(/^"|"$/g, "")).filter(Boolean);
9911
- const base = path11.win32.basename(command);
10226
+ const dirs = path13.win32.basename(command) !== command ? [path13.win32.dirname(path13.win32.resolve(command))] : (await (deps.resolvePath ?? augmentedPath)() || "").split(path13.win32.delimiter).map((d) => d.trim().replace(/^"|"$/g, "")).filter(Boolean);
10227
+ const base = path13.win32.basename(command);
9912
10228
  const found = [];
9913
10229
  for (const route of ROUTES) {
9914
10230
  outer: for (const ext of byRoute[route]) {
9915
10231
  for (const dir of dirs) {
9916
- const candidate = path11.win32.join(dir, `${base}${ext}`);
10232
+ const candidate = path13.win32.join(dir, `${base}${ext}`);
9917
10233
  if (exists(candidate)) {
9918
10234
  found.push({ file: candidate, route });
9919
10235
  break outer;
@@ -9959,10 +10275,10 @@ async function planShellLaunch(command, args, deps = {}) {
9959
10275
  }
9960
10276
 
9961
10277
  // src/utils/processTree.ts
9962
- import { execFile as execFile2 } from "child_process";
10278
+ import { execFile as execFile3 } from "child_process";
9963
10279
  var HARD_KILL_DELAY_MS = 5e3;
9964
10280
  function defaultExecFile2(file, args, cb) {
9965
- execFile2(file, args, (err) => cb(err ?? null));
10281
+ execFile3(file, args, (err) => cb(err ?? null));
9966
10282
  }
9967
10283
  function killTree(proc, deps = {}) {
9968
10284
  if (!proc || proc.killed || typeof proc.exitCode === "number") return;
@@ -10008,7 +10324,7 @@ var defaultAppServerClient = async (command, args, options) => {
10008
10324
  const PATH = await augmentedPath();
10009
10325
  const launch = await planDirectLaunch(command, args).catch(() => null);
10010
10326
  if (!launch) return null;
10011
- return new Promise((resolve6) => {
10327
+ return new Promise((resolve7) => {
10012
10328
  let child;
10013
10329
  try {
10014
10330
  child = spawn(launch.file, launch.args, {
@@ -10017,7 +10333,7 @@ var defaultAppServerClient = async (command, args, options) => {
10017
10333
  windowsVerbatimArguments: launch.verbatim
10018
10334
  });
10019
10335
  } catch {
10020
- resolve6(null);
10336
+ resolve7(null);
10021
10337
  return;
10022
10338
  }
10023
10339
  let settled = false;
@@ -10026,7 +10342,7 @@ var defaultAppServerClient = async (command, args, options) => {
10026
10342
  settled = true;
10027
10343
  clearTimeout(timer);
10028
10344
  killTree(child);
10029
- resolve6(value);
10345
+ resolve7(value);
10030
10346
  };
10031
10347
  const timer = setTimeout(() => finish(null), options?.timeout ?? 2e4);
10032
10348
  child.on("error", () => finish(null));
@@ -10093,7 +10409,7 @@ function geminiDiscoveryCacheKey(apiKey) {
10093
10409
  return {
10094
10410
  key: `gemini-models:${hash}`,
10095
10411
  dir,
10096
- file: path12.join(dir, `gemini_models_${hash}.json`)
10412
+ file: path14.join(dir, `gemini_models_${hash}.json`)
10097
10413
  };
10098
10414
  }
10099
10415
  function readGeminiCache(apiKey) {
@@ -10616,8 +10932,8 @@ var ModelDiscovery = class {
10616
10932
  };
10617
10933
 
10618
10934
  // src/services/ModelCatalog.ts
10619
- import * as fs6 from "fs";
10620
- import * as path13 from "path";
10935
+ import * as fs7 from "fs";
10936
+ import * as path15 from "path";
10621
10937
  var CACHE_KEY = "model-catalog";
10622
10938
  var CACHE_TTL_MS = 60 * 60 * 1e3;
10623
10939
  function storageKey(baseUrl) {
@@ -10669,17 +10985,17 @@ var ModelCatalog = class {
10669
10985
  const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
10670
10986
  const key = storageKey(url);
10671
10987
  const cacheDir = globalCacheDir();
10672
- const cacheFile = path13.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
10673
- if (fs6.existsSync(cacheFile)) fs6.rmSync(cacheFile);
10988
+ const cacheFile = path15.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
10989
+ if (fs7.existsSync(cacheFile)) fs7.rmSync(cacheFile);
10674
10990
  } catch {
10675
10991
  }
10676
10992
  }
10677
10993
  static readCache(key) {
10678
10994
  try {
10679
10995
  const cacheDir = globalCacheDir();
10680
- const cacheFile = path13.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
10681
- if (!fs6.existsSync(cacheFile)) return null;
10682
- const raw = fs6.readFileSync(cacheFile, "utf8");
10996
+ const cacheFile = path15.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
10997
+ if (!fs7.existsSync(cacheFile)) return null;
10998
+ const raw = fs7.readFileSync(cacheFile, "utf8");
10683
10999
  const data = JSON.parse(raw);
10684
11000
  if (Date.now() - data.cachedAt > CACHE_TTL_MS) return null;
10685
11001
  return JSON.stringify(data.models);
@@ -10690,9 +11006,9 @@ var ModelCatalog = class {
10690
11006
  static writeCache(key, models) {
10691
11007
  try {
10692
11008
  const cacheDir = globalCacheDir();
10693
- fs6.mkdirSync(cacheDir, { recursive: true });
10694
- const cacheFile = path13.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
10695
- fs6.writeFileSync(
11009
+ fs7.mkdirSync(cacheDir, { recursive: true });
11010
+ const cacheFile = path15.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
11011
+ fs7.writeFileSync(
10696
11012
  cacheFile,
10697
11013
  JSON.stringify({ models, cachedAt: Date.now() }, null, 2)
10698
11014
  );
@@ -11068,72 +11384,12 @@ var RunnerInstallation = class {
11068
11384
  }
11069
11385
  };
11070
11386
 
11071
- // src/services/settingsEnvAllowlist.ts
11072
- var ORDEWELL_SETTABLE_ENV = [
11073
- "AI_PROVIDER",
11074
- "ORCHESTRATOR_MODEL",
11075
- "ORDEWELL_SUBAGENT_MODEL",
11076
- "ORDEWELL_PLANNER_EFFORT",
11077
- "ORDEWELL_RESEARCH_ENABLED",
11078
- "ORDEWELL_MAX_PARALLEL",
11079
- "ORDEWELL_CONFLICT_REPAIR_ATTEMPTS"
11080
- ];
11081
- var SETTINGS_ENV_REFUSED = [
11082
- // Approval posture and pre-approved scope.
11083
- "ORDEWELL_AUTONOMOUS_MODE",
11084
- "ORDEWELL_APPROVAL_MODE",
11085
- "ORDEWELL_APPROVAL_ALLOW",
11086
- // Where settings are persisted.
11087
- "ORDEWELL_SETTINGS_PATH",
11088
- // Runtime options and dynamic loaders.
11089
- "NODE_OPTIONS",
11090
- "NODE_REPL_EXTERNAL_MODULE",
11091
- "NODE_PATH",
11092
- "ELECTRON_RUN_AS_NODE",
11093
- "LD_PRELOAD",
11094
- "LD_LIBRARY_PATH",
11095
- "LD_AUDIT",
11096
- "DYLD_INSERT_LIBRARIES",
11097
- "DYLD_LIBRARY_PATH",
11098
- "DYLD_FRAMEWORK_PATH",
11099
- // Executable resolution.
11100
- "PATH",
11101
- "PATHEXT",
11102
- "NODE"
11103
- ];
11104
- var REFUSED = new Set(SETTINGS_ENV_REFUSED);
11105
- function providerEnvKeys() {
11106
- const keys = [];
11107
- for (const meta of Object.values(ALL_PROVIDERS)) {
11108
- if (meta.apiKeyEnvVar) keys.push(meta.apiKeyEnvVar);
11109
- if (meta.baseUrlEnvVar) keys.push(meta.baseUrlEnvVar);
11110
- for (const detect of meta.detectEnvVars) keys.push(detect);
11111
- }
11112
- return keys;
11113
- }
11114
- var PROVIDER_CREDENTIAL_ENV = new Set(providerEnvKeys());
11115
- var SETTINGS_ENV_ALLOWLIST = new Set(
11116
- [...providerEnvKeys(), ...ORDEWELL_SETTABLE_ENV].filter((key) => !REFUSED.has(key))
11117
- );
11118
- function admitSettingsEnv(env) {
11119
- const accepted = {};
11120
- const rejected = [];
11121
- for (const [key, value] of Object.entries(env)) {
11122
- if (REFUSED.has(key) || !SETTINGS_ENV_ALLOWLIST.has(key) || typeof value !== "string") {
11123
- rejected.push(key);
11124
- continue;
11125
- }
11126
- accepted[key] = value;
11127
- }
11128
- return { accepted, rejected };
11129
- }
11130
-
11131
11387
  // src/services/harness/CliAgentAiService.ts
11132
11388
  import { spawn as nodeSpawn } from "child_process";
11133
11389
 
11134
11390
  // src/utils/workspace.ts
11135
- import * as fs7 from "fs";
11136
- import * as path14 from "path";
11391
+ import * as fs8 from "fs";
11392
+ import * as path16 from "path";
11137
11393
  var WorkspaceNotFoundError = class extends Error {
11138
11394
  constructor(workspace) {
11139
11395
  super(`Workspace "${workspace}" does not exist.`);
@@ -11144,7 +11400,7 @@ var WorkspaceNotFoundError = class extends Error {
11144
11400
  };
11145
11401
  function defaultIsDirectory(candidate) {
11146
11402
  try {
11147
- return fs7.statSync(candidate).isDirectory();
11403
+ return fs8.statSync(candidate).isDirectory();
11148
11404
  } catch {
11149
11405
  return false;
11150
11406
  }
@@ -11180,8 +11436,8 @@ var PROJECT_MARKER_FILES = [
11180
11436
  "CMakeLists.txt"
11181
11437
  ];
11182
11438
  function assertWorkspaceIsProject(workspace, deps = {}) {
11183
- const realpath = deps.realpath ?? ((candidate) => fs7.realpathSync(candidate));
11184
- const exists = deps.exists ?? ((candidate) => fs7.existsSync(candidate));
11439
+ const realpath = deps.realpath ?? ((candidate) => fs8.realpathSync(candidate));
11440
+ const exists = deps.exists ?? ((candidate) => fs8.existsSync(candidate));
11185
11441
  let resolved;
11186
11442
  try {
11187
11443
  resolved = realpath(workspace);
@@ -11189,7 +11445,7 @@ function assertWorkspaceIsProject(workspace, deps = {}) {
11189
11445
  throw new WorkspaceNotAProjectError(workspace);
11190
11446
  }
11191
11447
  const hasMarker = [...PROJECT_MARKER_DIRS, ...PROJECT_MARKER_FILES].some(
11192
- (marker2) => exists(path14.join(resolved, marker2))
11448
+ (marker2) => exists(path16.join(resolved, marker2))
11193
11449
  );
11194
11450
  if (!hasMarker) throw new WorkspaceNotAProjectError(workspace);
11195
11451
  }
@@ -11257,13 +11513,14 @@ var StdioAgentAdapter = class {
11257
11513
  }
11258
11514
  async start(opts) {
11259
11515
  assertWorkspaceExists(opts.cwd, { isDirectory: this.deps.isDirectory });
11260
- this.processEnded = new Promise((resolve6) => {
11261
- this.markEnded = resolve6;
11516
+ this.processEnded = new Promise((resolve7) => {
11517
+ this.markEnded = resolve7;
11262
11518
  });
11263
11519
  const spec = this.spawnSpec(opts);
11264
11520
  const resolvePath = this.deps.resolvePath ?? augmentedPath;
11265
11521
  const PATH = await resolvePath();
11266
- this.spawnEnv = withPath(process.env, PATH, spec.env);
11522
+ const workspace = await (this.deps.workspaceEnv ?? workspaceEnvOf)(opts.cwd);
11523
+ this.spawnEnv = withPath(process.env, PATH, { ...workspace, ...spec.env });
11267
11524
  const launch = await planDirectLaunch(spec.command, spec.args, {
11268
11525
  platform: this.deps.platform,
11269
11526
  resolvePath
@@ -11309,7 +11566,7 @@ ${err.message}`).slice(-STDERR_TAIL_CHARS);
11309
11566
  onEvent({ type: "error", message: this.exitMessage() });
11310
11567
  return;
11311
11568
  }
11312
- await new Promise((resolve6) => {
11569
+ await new Promise((resolve7) => {
11313
11570
  let settled = false;
11314
11571
  const finish = () => {
11315
11572
  if (settled) return;
@@ -11320,7 +11577,7 @@ ${err.message}`).slice(-STDERR_TAIL_CHARS);
11320
11577
  signal?.removeEventListener("abort", onAbort);
11321
11578
  this.process?.removeListener("exit", onExit);
11322
11579
  this.process?.removeListener("error", onExit);
11323
- resolve6();
11580
+ resolve7();
11324
11581
  };
11325
11582
  const emit = (event) => {
11326
11583
  onEvent(event);
@@ -11535,14 +11792,14 @@ function codexSandboxUnavailableMessage() {
11535
11792
  ].join("\n");
11536
11793
  }
11537
11794
  function runProbe(deps, cwd, env, flags) {
11538
- return new Promise((resolve6) => {
11795
+ return new Promise((resolve7) => {
11539
11796
  let output = "";
11540
11797
  let settled = false;
11541
11798
  const finish = (code) => {
11542
11799
  if (settled) return;
11543
11800
  settled = true;
11544
11801
  clearTimeout(timer);
11545
- resolve6({ code, output });
11802
+ resolve7({ code, output });
11546
11803
  };
11547
11804
  const timer = setTimeout(() => finish(null), PROBE_TIMEOUT_MS);
11548
11805
  timer.unref?.();
@@ -11625,8 +11882,8 @@ var CodexAdapter = class extends StdioAgentAdapter {
11625
11882
  async handshake(opts) {
11626
11883
  this.sandbox = await probeCodexSandbox(this.deps, opts.cwd, this.spawnEnv);
11627
11884
  if (this.sandbox === "unavailable") throw new Error(codexSandboxUnavailableMessage());
11628
- const ready = new Promise((resolve6) => {
11629
- this.settleHandshake = resolve6;
11885
+ const ready = new Promise((resolve7) => {
11886
+ this.settleHandshake = resolve7;
11630
11887
  });
11631
11888
  this.writeLine({
11632
11889
  jsonrpc: "2.0",
@@ -11639,8 +11896,8 @@ var CodexAdapter = class extends StdioAgentAdapter {
11639
11896
  // A binary that rejects `app-server` outright dies immediately; waiting
11640
11897
  // out the timeout would turn a one-line diagnostic into a 30s stall.
11641
11898
  this.processEnded.then(() => false),
11642
- new Promise((resolve6) => {
11643
- const t = setTimeout(() => resolve6(false), HANDSHAKE_TIMEOUT_MS);
11899
+ new Promise((resolve7) => {
11900
+ const t = setTimeout(() => resolve7(false), HANDSHAKE_TIMEOUT_MS);
11644
11901
  t.unref?.();
11645
11902
  })
11646
11903
  ]);
@@ -11917,12 +12174,12 @@ function describeError(err) {
11917
12174
  return lines.join(": ");
11918
12175
  }
11919
12176
  function delay(ms, signal) {
11920
- return new Promise((resolve6) => {
11921
- const timer = setTimeout(resolve6, ms);
12177
+ return new Promise((resolve7) => {
12178
+ const timer = setTimeout(resolve7, ms);
11922
12179
  timer.unref?.();
11923
12180
  signal?.addEventListener("abort", () => {
11924
12181
  clearTimeout(timer);
11925
- resolve6();
12182
+ resolve7();
11926
12183
  }, { once: true });
11927
12184
  });
11928
12185
  }
@@ -11956,17 +12213,17 @@ var OpenCodeAdapter = class {
11956
12213
  throw new ExecutableNotFoundError("opencode", PATH);
11957
12214
  }
11958
12215
  this.process = this.deps.spawn(launch.file, launch.args, {
11959
- env: withPath(process.env, PATH),
12216
+ env: withPath(process.env, PATH, await (this.deps.workspaceEnv ?? workspaceEnvOf)(opts.cwd)),
11960
12217
  stdio: ["pipe", "pipe", "pipe"],
11961
12218
  cwd: opts.cwd,
11962
12219
  windowsVerbatimArguments: launch.verbatim
11963
12220
  });
11964
- const banner = new Promise((resolve6) => {
12221
+ const banner = new Promise((resolve7) => {
11965
12222
  let seen = "";
11966
12223
  const scan = (chunk) => {
11967
12224
  seen += chunk.toString();
11968
12225
  const match = seen.match(/https?:\/\/[^\s]+/);
11969
- if (match) resolve6(match[0].replace(/[.,)]$/, ""));
12226
+ if (match) resolve7(match[0].replace(/[.,)]$/, ""));
11970
12227
  };
11971
12228
  this.process.stdout?.on("data", scan);
11972
12229
  this.process.stderr?.on("data", (chunk) => {
@@ -11975,15 +12232,15 @@ var OpenCodeAdapter = class {
11975
12232
  });
11976
12233
  this.process.on("exit", () => {
11977
12234
  this.exited = true;
11978
- resolve6(null);
12235
+ resolve7(null);
11979
12236
  });
11980
12237
  this.process.on("error", (err) => {
11981
12238
  this.exited = true;
11982
12239
  this.stderrTail = (this.stderrTail + `
11983
12240
  ${err.message}`).slice(-STDERR_TAIL_CHARS2);
11984
- resolve6(null);
12241
+ resolve7(null);
11985
12242
  });
11986
- const timer = setTimeout(() => resolve6(null), SERVER_READY_TIMEOUT_MS);
12243
+ const timer = setTimeout(() => resolve7(null), SERVER_READY_TIMEOUT_MS);
11987
12244
  timer.unref?.();
11988
12245
  });
11989
12246
  this.baseUrl = await banner;
@@ -12016,8 +12273,8 @@ ${this.stderrTail.trim()}` : ""}`);
12016
12273
  const streamAbort = new AbortController();
12017
12274
  let connected = () => {
12018
12275
  };
12019
- const streamReady = new Promise((resolve6) => {
12020
- connected = resolve6;
12276
+ const streamReady = new Promise((resolve7) => {
12277
+ connected = resolve7;
12021
12278
  });
12022
12279
  const live = this.streamEvents(
12023
12280
  streamAbort.signal,
@@ -12221,14 +12478,14 @@ ${part.text}` : part.text });
12221
12478
  }
12222
12479
  }
12223
12480
  }
12224
- async json(method, path23, body, signal) {
12225
- const response = await this.deps.fetch(`${this.baseUrl}${path23}`, {
12481
+ async json(method, path25, body, signal) {
12482
+ const response = await this.deps.fetch(`${this.baseUrl}${path25}`, {
12226
12483
  method,
12227
12484
  headers: { "content-type": "application/json" },
12228
12485
  ...body === void 0 ? {} : { body: JSON.stringify(body) },
12229
12486
  ...signal ? { signal } : {}
12230
12487
  });
12231
- if (!response.ok) throw new Error(`${method} ${path23} failed: ${response.status} ${response.statusText}`);
12488
+ if (!response.ok) throw new Error(`${method} ${path25} failed: ${response.status} ${response.statusText}`);
12232
12489
  return await response.json().catch(() => null);
12233
12490
  }
12234
12491
  nativeSessionId() {
@@ -12682,8 +12939,8 @@ var CliAgentAiService = class {
12682
12939
  this.lastNativeSessionId = previousSessionId;
12683
12940
  }
12684
12941
  }
12685
- async researchAndPlan(userDescription, runners, modelsByRunner, fs14, onProgress, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
12686
- const contextStr = await collectResearchContext(fs14, runners);
12942
+ async researchAndPlan(userDescription, runners, modelsByRunner, fs15, onProgress, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
12943
+ const contextStr = await collectResearchContext(fs15, runners);
12687
12944
  const researchLog = [
12688
12945
  { id: `up-${Date.now()}`, type: "user_prompt", content: userDescription, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
12689
12946
  ];
@@ -13582,25 +13839,25 @@ function retargetTaskRunner(task, runner, catalog) {
13582
13839
  }
13583
13840
 
13584
13841
  // src/services/SkillsService.ts
13585
- import * as fs8 from "fs";
13586
- import * as path16 from "path";
13842
+ import * as fs9 from "fs";
13843
+ import * as path18 from "path";
13587
13844
 
13588
13845
  // src/services/builtinSkills.ts
13589
- import * as path15 from "path";
13846
+ import * as path17 from "path";
13590
13847
  import { fileURLToPath } from "url";
13591
13848
  function builtinSkillsDir() {
13592
- const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path15.dirname(fileURLToPath(
13849
+ const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path17.dirname(fileURLToPath(
13593
13850
  // @ts-expect-error - import.meta is only valid under an ESM module setting
13594
13851
  import.meta.url
13595
13852
  ));
13596
- return path15.join(dir, "..", "skills");
13853
+ return path17.join(dir, "..", "skills");
13597
13854
  }
13598
13855
 
13599
13856
  // src/services/SkillsService.ts
13600
13857
  var BUILTIN_SKILL_NAMES = ["grilling", "to-spec", "improve-codebase-architecture"];
13601
13858
  var RETIRED_BUILTIN_SKILL_NAMES = ["grill-me"];
13602
13859
  function parseSkillFile(filePath, name, source) {
13603
- const raw = fs8.readFileSync(filePath, "utf8");
13860
+ const raw = fs9.readFileSync(filePath, "utf8");
13604
13861
  const frontmatter = {};
13605
13862
  let content = raw;
13606
13863
  if (raw.startsWith("---\n")) {
@@ -13629,23 +13886,23 @@ function parseSkillFile(filePath, name, source) {
13629
13886
  return { name, description: metadata.description, metadata, content, path: filePath, source };
13630
13887
  }
13631
13888
  function copyDirSync2(src, dest) {
13632
- fs8.mkdirSync(dest, { recursive: true });
13633
- const entries = fs8.readdirSync(src, { withFileTypes: true });
13889
+ fs9.mkdirSync(dest, { recursive: true });
13890
+ const entries = fs9.readdirSync(src, { withFileTypes: true });
13634
13891
  for (const entry of entries) {
13635
- const srcPath = path16.join(src, entry.name);
13636
- const destPath = path16.join(dest, entry.name);
13892
+ const srcPath = path18.join(src, entry.name);
13893
+ const destPath = path18.join(dest, entry.name);
13637
13894
  if (entry.isDirectory()) copyDirSync2(srcPath, destPath);
13638
- else fs8.copyFileSync(srcPath, destPath);
13895
+ else fs9.copyFileSync(srcPath, destPath);
13639
13896
  }
13640
13897
  }
13641
13898
  function readDir(dir, source) {
13642
- if (!fs8.existsSync(dir)) return [];
13643
- const entries = fs8.readdirSync(dir, { withFileTypes: true });
13899
+ if (!fs9.existsSync(dir)) return [];
13900
+ const entries = fs9.readdirSync(dir, { withFileTypes: true });
13644
13901
  const skills = [];
13645
13902
  for (const entry of entries) {
13646
13903
  if (!entry.isDirectory()) continue;
13647
- const skillFile = path16.join(dir, entry.name, "SKILL.md");
13648
- if (!fs8.existsSync(skillFile)) continue;
13904
+ const skillFile = path18.join(dir, entry.name, "SKILL.md");
13905
+ if (!fs9.existsSync(skillFile)) continue;
13649
13906
  const parsed = parseSkillFile(skillFile, entry.name, source);
13650
13907
  if (parsed) skills.push(parsed);
13651
13908
  }
@@ -13657,22 +13914,22 @@ var SkillsService = class {
13657
13914
  }
13658
13915
  workspaceRoot;
13659
13916
  localDir() {
13660
- return this.workspaceRoot ? path16.join(this.workspaceRoot, ".ordewell", "skills") : void 0;
13917
+ return this.workspaceRoot ? path18.join(this.workspaceRoot, ".ordewell", "skills") : void 0;
13661
13918
  }
13662
13919
  globalDir() {
13663
- return path16.join(globalDataDir(), "skills");
13920
+ return path18.join(globalDataDir(), "skills");
13664
13921
  }
13665
13922
  seed(name) {
13666
- const dest = path16.join(this.globalDir(), name);
13667
- if (fs8.existsSync(dest)) return false;
13923
+ const dest = path18.join(this.globalDir(), name);
13924
+ if (fs9.existsSync(dest)) return false;
13668
13925
  let srcDir;
13669
13926
  try {
13670
- srcDir = path16.join(builtinSkillsDir(), name);
13927
+ srcDir = path18.join(builtinSkillsDir(), name);
13671
13928
  } catch (err) {
13672
13929
  console.warn(`Could not locate built-in skills dir: ${String(err)}`);
13673
13930
  return false;
13674
13931
  }
13675
- if (!fs8.existsSync(srcDir)) {
13932
+ if (!fs9.existsSync(srcDir)) {
13676
13933
  console.warn(`Built-in skill not found in package: ${name}`);
13677
13934
  return false;
13678
13935
  }
@@ -13686,14 +13943,14 @@ var SkillsService = class {
13686
13943
  isUnmodifiedRetiredSeed(dir, expectedName) {
13687
13944
  let entries;
13688
13945
  try {
13689
- entries = fs8.readdirSync(dir, { withFileTypes: true });
13946
+ entries = fs9.readdirSync(dir, { withFileTypes: true });
13690
13947
  } catch {
13691
13948
  return false;
13692
13949
  }
13693
13950
  if (entries.length !== 1 || entries[0].name !== "SKILL.md" || !entries[0].isFile()) return false;
13694
13951
  let raw;
13695
13952
  try {
13696
- raw = fs8.readFileSync(path16.join(dir, "SKILL.md"), "utf8");
13953
+ raw = fs9.readFileSync(path18.join(dir, "SKILL.md"), "utf8");
13697
13954
  } catch {
13698
13955
  return false;
13699
13956
  }
@@ -13710,11 +13967,11 @@ var SkillsService = class {
13710
13967
  }
13711
13968
  pruneRetired() {
13712
13969
  for (const name of RETIRED_BUILTIN_SKILL_NAMES) {
13713
- const dest = path16.join(this.globalDir(), name);
13714
- if (!fs8.existsSync(dest)) continue;
13970
+ const dest = path18.join(this.globalDir(), name);
13971
+ if (!fs9.existsSync(dest)) continue;
13715
13972
  if (!this.isUnmodifiedRetiredSeed(dest, name)) continue;
13716
13973
  try {
13717
- fs8.rmSync(dest, { recursive: true, force: true });
13974
+ fs9.rmSync(dest, { recursive: true, force: true });
13718
13975
  } catch (err) {
13719
13976
  console.warn(`Could not prune retired skill ${name}: ${String(err)}`);
13720
13977
  }
@@ -13725,11 +13982,11 @@ var SkillsService = class {
13725
13982
  if (BUILTIN_SKILL_NAMES.includes(name)) this.seed(name);
13726
13983
  const localDir = this.localDir();
13727
13984
  if (localDir) {
13728
- const localFile = path16.join(localDir, name, "SKILL.md");
13729
- if (fs8.existsSync(localFile)) return parseSkillFile(localFile, name, "local");
13985
+ const localFile = path18.join(localDir, name, "SKILL.md");
13986
+ if (fs9.existsSync(localFile)) return parseSkillFile(localFile, name, "local");
13730
13987
  }
13731
- const globalFile = path16.join(this.globalDir(), name, "SKILL.md");
13732
- if (fs8.existsSync(globalFile)) return parseSkillFile(globalFile, name, "global");
13988
+ const globalFile = path18.join(this.globalDir(), name, "SKILL.md");
13989
+ if (fs9.existsSync(globalFile)) return parseSkillFile(globalFile, name, "global");
13733
13990
  return void 0;
13734
13991
  }
13735
13992
  listSkills() {
@@ -13756,8 +14013,8 @@ function createSkillsService(workspaceRoot) {
13756
14013
  }
13757
14014
 
13758
14015
  // src/utils/sessionStore.ts
13759
- import * as fs9 from "fs";
13760
- import * as path17 from "path";
14016
+ import * as fs10 from "fs";
14017
+ import * as path19 from "path";
13761
14018
 
13762
14019
  // src/utils/sessionId.ts
13763
14020
  import { randomBytes as randomBytes2 } from "crypto";
@@ -13768,7 +14025,7 @@ function mintSessionId() {
13768
14025
  // src/utils/sessionStore.ts
13769
14026
  var SESSIONS_SUBDIR = "sessions";
13770
14027
  function getSessionsDir(baseDir) {
13771
- return path17.join(getStateDir(baseDir), SESSIONS_SUBDIR);
14028
+ return path19.join(getStateDir(baseDir), SESSIONS_SUBDIR);
13772
14029
  }
13773
14030
  var REJECTION_MESSAGE = "This session was created with an older version of Ordewell. Please re-plan.";
13774
14031
  function hasScalarRunner(parsed) {
@@ -13790,11 +14047,11 @@ function legacySessionFilename(session) {
13790
14047
  return `${filenameStem(session)}_${session.id.slice(0, 8)}.json`;
13791
14048
  }
13792
14049
  function dropLegacyFile(sessionsDir, meta) {
13793
- const legacy = path17.join(sessionsDir, legacySessionFilename(meta));
13794
- if (legacy === path17.join(sessionsDir, sessionFilename(meta)) || !fs9.existsSync(legacy)) return;
14050
+ const legacy = path19.join(sessionsDir, legacySessionFilename(meta));
14051
+ if (legacy === path19.join(sessionsDir, sessionFilename(meta)) || !fs10.existsSync(legacy)) return;
13795
14052
  try {
13796
- const held = JSON.parse(fs9.readFileSync(legacy, "utf-8")).meta?.id;
13797
- if (held === meta.id) fs9.unlinkSync(legacy);
14053
+ const held = JSON.parse(fs10.readFileSync(legacy, "utf-8")).meta?.id;
14054
+ if (held === meta.id) fs10.unlinkSync(legacy);
13798
14055
  } catch {
13799
14056
  }
13800
14057
  }
@@ -13817,18 +14074,18 @@ function saveSession(plan, goal, baseDir, id) {
13817
14074
  plan: JSON.parse(JSON.stringify(plan))
13818
14075
  };
13819
14076
  const filename = sessionFilename(meta);
13820
- const filePath = path17.join(sessionsDir, filename);
13821
- fs9.writeFileSync(filePath, JSON.stringify(session, null, 2));
14077
+ const filePath = path19.join(sessionsDir, filename);
14078
+ fs10.writeFileSync(filePath, JSON.stringify(session, null, 2));
13822
14079
  dropLegacyFile(sessionsDir, meta);
13823
14080
  return meta;
13824
14081
  }
13825
14082
  function listSessions(baseDir, logger = defaultLogger) {
13826
14083
  const sessionsDir = getSessionsDir(baseDir);
13827
- if (!fs9.existsSync(sessionsDir)) return [];
13828
- const files = fs9.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
14084
+ if (!fs10.existsSync(sessionsDir)) return [];
14085
+ const files = fs10.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
13829
14086
  return files.map((f) => {
13830
14087
  try {
13831
- const raw = fs9.readFileSync(path17.join(sessionsDir, f), "utf-8");
14088
+ const raw = fs10.readFileSync(path19.join(sessionsDir, f), "utf-8");
13832
14089
  const parsed = JSON.parse(raw);
13833
14090
  if (hasScalarRunner(parsed)) {
13834
14091
  logger.warn("sessionStore", `session file ${f}: ${REJECTION_MESSAGE}`);
@@ -13843,10 +14100,10 @@ function listSessions(baseDir, logger = defaultLogger) {
13843
14100
  }).filter((m) => m !== null).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
13844
14101
  }
13845
14102
  function findSessionFile(sessionsDir, sessionId, logger) {
13846
- const files = fs9.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
14103
+ const files = fs10.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
13847
14104
  for (const f of files) {
13848
14105
  try {
13849
- const raw = fs9.readFileSync(path17.join(sessionsDir, f), "utf-8");
14106
+ const raw = fs10.readFileSync(path19.join(sessionsDir, f), "utf-8");
13850
14107
  const session = JSON.parse(raw);
13851
14108
  if (session.meta.id === sessionId) return f;
13852
14109
  } catch (err) {
@@ -13857,11 +14114,11 @@ function findSessionFile(sessionsDir, sessionId, logger) {
13857
14114
  }
13858
14115
  function loadSession(sessionId, baseDir, logger = defaultLogger) {
13859
14116
  const sessionsDir = getSessionsDir(baseDir);
13860
- if (!fs9.existsSync(sessionsDir)) return null;
14117
+ if (!fs10.existsSync(sessionsDir)) return null;
13861
14118
  try {
13862
14119
  const file = findSessionFile(sessionsDir, sessionId, logger);
13863
14120
  if (file) {
13864
- const raw = fs9.readFileSync(path17.join(sessionsDir, file), "utf-8");
14121
+ const raw = fs10.readFileSync(path19.join(sessionsDir, file), "utf-8");
13865
14122
  const parsed = JSON.parse(raw);
13866
14123
  if (hasScalarRunner(parsed)) {
13867
14124
  logger.warn("sessionStore", `session file ${file}: ${REJECTION_MESSAGE}`);
@@ -13892,11 +14149,11 @@ function getLatestSession(baseDir, logger = defaultLogger) {
13892
14149
  }
13893
14150
  function deleteSession(sessionId, baseDir, logger = defaultLogger) {
13894
14151
  const sessionsDir = getSessionsDir(baseDir);
13895
- if (!fs9.existsSync(sessionsDir)) return false;
14152
+ if (!fs10.existsSync(sessionsDir)) return false;
13896
14153
  try {
13897
14154
  const file = findSessionFile(sessionsDir, sessionId, logger);
13898
14155
  if (file) {
13899
- fs9.unlinkSync(path17.join(sessionsDir, file));
14156
+ fs10.unlinkSync(path19.join(sessionsDir, file));
13900
14157
  return true;
13901
14158
  }
13902
14159
  } catch (err) {
@@ -14577,6 +14834,10 @@ var Session = class {
14577
14834
  }
14578
14835
  await this.orchestrator.runTask(taskId);
14579
14836
  }
14837
+ /** Start whatever the scheduler can now fit — after the parallel limit was raised mid-run, say. */
14838
+ async reschedule() {
14839
+ await this.orchestrator.tick();
14840
+ }
14580
14841
  async retryTask(taskId) {
14581
14842
  await this.orchestrator.retryTask(taskId);
14582
14843
  this.persist();
@@ -15457,7 +15718,7 @@ var HeadlessRunner = class extends AbstractRunner {
15457
15718
  return {
15458
15719
  launch: await planDirectLaunch(command, args, this.launchDeps),
15459
15720
  resolvedPath,
15460
- env: invocation.env,
15721
+ env: { ...opts.env, ...invocation.env },
15461
15722
  pty,
15462
15723
  submitPromptKey: invocation.submitPromptKey
15463
15724
  };
@@ -15476,11 +15737,11 @@ var HeadlessRunner = class extends AbstractRunner {
15476
15737
  };
15477
15738
 
15478
15739
  // src/services/TmuxRunner.ts
15479
- import { execFile as execFile3 } from "child_process";
15740
+ import { execFile as execFile4 } from "child_process";
15480
15741
  import { promisify as promisify5 } from "util";
15481
15742
  import { tmpdir } from "os";
15482
- import { join as join16 } from "path";
15483
- import { existsSync as existsSync11, readFileSync as readFileSync7, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
15743
+ import { join as join17 } from "path";
15744
+ import { existsSync as existsSync11, readFileSync as readFileSync8, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
15484
15745
 
15485
15746
  // src/utils/tmux.ts
15486
15747
  import { execSync as execSync2 } from "child_process";
@@ -15531,7 +15792,7 @@ function clipboardCopyCommand(hasBin = defaultHasBin, platform = process.platfor
15531
15792
 
15532
15793
  // src/services/TmuxRunner.ts
15533
15794
  var EXIT_RE = /<<<ORDEWELL_TMUX_EXIT:(\d+)>>>/;
15534
- var execFileAsync2 = promisify5(execFile3);
15795
+ var execFileAsync2 = promisify5(execFile4);
15535
15796
  var defaultExecFile3 = async (command, args) => {
15536
15797
  const { stdout, stderr } = await execFileAsync2(command, args);
15537
15798
  return { stdout: String(stdout), stderr: String(stderr) };
@@ -15587,7 +15848,7 @@ var TmuxSession = class extends AbstractTerminalSession {
15587
15848
  if (this.exited) return;
15588
15849
  let content;
15589
15850
  try {
15590
- content = existsSync11(this.logPath) ? readFileSync7(this.logPath, "utf8") : "";
15851
+ content = existsSync11(this.logPath) ? readFileSync8(this.logPath, "utf8") : "";
15591
15852
  } catch {
15592
15853
  return;
15593
15854
  }
@@ -15766,10 +16027,10 @@ var TmuxRunner = class extends AbstractRunner {
15766
16027
  windowName,
15767
16028
  this.execFileImpl,
15768
16029
  this.pollIntervalMs,
15769
- join16(this.logDir, `${id}.log`),
16030
+ join17(this.logDir, `${id}.log`),
15770
16031
  this.socket
15771
16032
  );
15772
- invocation.env = { ...invocation.env, PATH: await this.resolvePath() };
16033
+ invocation.env = { ...opts.env, ...invocation.env, PATH: await this.resolvePath() };
15773
16034
  await session.start(invocation.command, invocation.args, opts.cwd, invocation.env);
15774
16035
  if (invocation.submitPromptKey) session.write("\r");
15775
16036
  this.registerSession(id, session);
@@ -15779,8 +16040,8 @@ var TmuxRunner = class extends AbstractRunner {
15779
16040
 
15780
16041
  // src/utils/daemonToken.ts
15781
16042
  import { randomBytes as randomBytes3, timingSafeEqual } from "crypto";
15782
- import { closeSync, mkdirSync as mkdirSync9, openSync, readFileSync as readFileSync8, unlinkSync as unlinkSync3, writeSync } from "fs";
15783
- import { join as join17 } from "path";
16043
+ import { closeSync, mkdirSync as mkdirSync9, openSync, readFileSync as readFileSync9, unlinkSync as unlinkSync3, writeSync } from "fs";
16044
+ import { join as join18 } from "path";
15784
16045
  var BEARER_PREFIX = "bearer ";
15785
16046
  var DAEMON_TOKEN_SUBPROTOCOL_PREFIX = "ordewell.token.";
15786
16047
  var DAEMON_SUBPROTOCOL = "ordewell.v1";
@@ -15788,7 +16049,7 @@ function configDir() {
15788
16049
  return globalDataDir();
15789
16050
  }
15790
16051
  function daemonTokenPath(port) {
15791
- return join17(configDir(), `server-${port}.token`);
16052
+ return join18(configDir(), `server-${port}.token`);
15792
16053
  }
15793
16054
  function mintDaemonToken(port) {
15794
16055
  const token = randomBytes3(32).toString("base64url");
@@ -15808,7 +16069,7 @@ function mintDaemonToken(port) {
15808
16069
  }
15809
16070
  function readDaemonToken(port) {
15810
16071
  try {
15811
- const token = readFileSync8(daemonTokenPath(port), "utf8").trim();
16072
+ const token = readFileSync9(daemonTokenPath(port), "utf8").trim();
15812
16073
  return token === "" ? void 0 : token;
15813
16074
  } catch {
15814
16075
  return void 0;
@@ -15849,18 +16110,18 @@ function tokensMatch(presented, expected) {
15849
16110
  }
15850
16111
 
15851
16112
  // src/plugins/RunnerRegistry.ts
15852
- import * as fs11 from "fs";
15853
- import * as path20 from "path";
15854
- import * as os5 from "os";
16113
+ import * as fs12 from "fs";
16114
+ import * as path22 from "path";
16115
+ import * as os6 from "os";
15855
16116
  import { execFileSync } from "child_process";
15856
16117
  import { randomBytes as randomBytes4 } from "crypto";
15857
16118
 
15858
16119
  // src/plugins/FsPluginStore.ts
15859
- import * as fs10 from "fs";
15860
- import * as path19 from "path";
16120
+ import * as fs11 from "fs";
16121
+ import * as path21 from "path";
15861
16122
 
15862
16123
  // src/plugins/pluginNames.ts
15863
- import * as path18 from "path";
16124
+ import * as path20 from "path";
15864
16125
  var PLUGIN_NAME_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
15865
16126
  var MAX_PLUGIN_NAME_LENGTH = 64;
15866
16127
  function isPlainPluginName(name) {
@@ -15875,10 +16136,10 @@ function assertPlainPluginName(name) {
15875
16136
  }
15876
16137
  function resolvePluginInstallDir(pluginsDir, name) {
15877
16138
  assertPlainPluginName(name);
15878
- const root = path18.resolve(pluginsDir);
15879
- const dest = path18.resolve(root, name);
15880
- const relative5 = path18.relative(root, dest);
15881
- if (relative5 !== name || relative5 === "" || path18.isAbsolute(relative5)) {
16139
+ const root = path20.resolve(pluginsDir);
16140
+ const dest = path20.resolve(root, name);
16141
+ const relative5 = path20.relative(root, dest);
16142
+ if (relative5 !== name || relative5 === "" || path20.isAbsolute(relative5)) {
15882
16143
  throw new Error(`Plugin install path escapes the plugins directory: ${JSON.stringify(name)}`);
15883
16144
  }
15884
16145
  return dest;
@@ -15893,13 +16154,13 @@ function isValidManifest(obj) {
15893
16154
 
15894
16155
  // src/plugins/FsPluginStore.ts
15895
16156
  function userPluginsDir() {
15896
- return path19.join(globalDataDir(), "plugins");
16157
+ return path21.join(globalDataDir(), "plugins");
15897
16158
  }
15898
16159
  function loadManifestFromDir(dir) {
15899
- const manifestPath = path19.join(dir, "manifest.json");
15900
- if (!fs10.existsSync(manifestPath)) return null;
16160
+ const manifestPath = path21.join(dir, "manifest.json");
16161
+ if (!fs11.existsSync(manifestPath)) return null;
15901
16162
  try {
15902
- const raw = fs10.readFileSync(manifestPath, "utf8");
16163
+ const raw = fs11.readFileSync(manifestPath, "utf8");
15903
16164
  const parsed = JSON.parse(raw);
15904
16165
  if (!isValidManifest(parsed)) return null;
15905
16166
  return parsed;
@@ -15909,29 +16170,29 @@ function loadManifestFromDir(dir) {
15909
16170
  }
15910
16171
  function copyDirSync3(src, dest) {
15911
16172
  ensureDir(dest);
15912
- const entries = fs10.readdirSync(src, { withFileTypes: true });
16173
+ const entries = fs11.readdirSync(src, { withFileTypes: true });
15913
16174
  for (const entry of entries) {
15914
- const srcPath = path19.join(src, entry.name);
15915
- const destPath = path19.join(dest, entry.name);
16175
+ const srcPath = path21.join(src, entry.name);
16176
+ const destPath = path21.join(dest, entry.name);
15916
16177
  if (entry.isDirectory()) {
15917
16178
  copyDirSync3(srcPath, destPath);
15918
16179
  } else {
15919
- fs10.copyFileSync(srcPath, destPath);
16180
+ fs11.copyFileSync(srcPath, destPath);
15920
16181
  }
15921
16182
  }
15922
16183
  }
15923
16184
  function rmDirSync(dir) {
15924
- if (!fs10.existsSync(dir)) return;
15925
- const entries = fs10.readdirSync(dir, { withFileTypes: true });
16185
+ if (!fs11.existsSync(dir)) return;
16186
+ const entries = fs11.readdirSync(dir, { withFileTypes: true });
15926
16187
  for (const entry of entries) {
15927
- const p = path19.join(dir, entry.name);
16188
+ const p = path21.join(dir, entry.name);
15928
16189
  if (entry.isDirectory()) {
15929
16190
  rmDirSync(p);
15930
16191
  } else {
15931
- fs10.unlinkSync(p);
16192
+ fs11.unlinkSync(p);
15932
16193
  }
15933
16194
  }
15934
- fs10.rmdirSync(dir);
16195
+ fs11.rmdirSync(dir);
15935
16196
  }
15936
16197
  var FsPluginStore = class {
15937
16198
  getUserPluginsDir() {
@@ -15939,10 +16200,10 @@ var FsPluginStore = class {
15939
16200
  }
15940
16201
  listUserPluginDirs() {
15941
16202
  const dir = userPluginsDir();
15942
- if (!fs10.existsSync(dir)) return [];
16203
+ if (!fs11.existsSync(dir)) return [];
15943
16204
  let entries;
15944
16205
  try {
15945
- entries = fs10.readdirSync(dir, { withFileTypes: true });
16206
+ entries = fs11.readdirSync(dir, { withFileTypes: true });
15946
16207
  } catch {
15947
16208
  return [];
15948
16209
  }
@@ -15950,7 +16211,7 @@ var FsPluginStore = class {
15950
16211
  }
15951
16212
  listDir(dir) {
15952
16213
  try {
15953
- return fs10.readdirSync(dir);
16214
+ return fs11.readdirSync(dir);
15954
16215
  } catch {
15955
16216
  return [];
15956
16217
  }
@@ -15968,20 +16229,20 @@ var FsPluginStore = class {
15968
16229
  ensureDir(dir);
15969
16230
  }
15970
16231
  writeFile(filePath, content) {
15971
- fs10.writeFileSync(filePath, content);
16232
+ fs11.writeFileSync(filePath, content);
15972
16233
  }
15973
16234
  readFile(filePath) {
15974
16235
  try {
15975
- return fs10.readFileSync(filePath, "utf8");
16236
+ return fs11.readFileSync(filePath, "utf8");
15976
16237
  } catch {
15977
16238
  return null;
15978
16239
  }
15979
16240
  }
15980
- dirExists(path23) {
15981
- return fs10.existsSync(path23) && fs10.statSync(path23).isDirectory();
16241
+ dirExists(path25) {
16242
+ return fs11.existsSync(path25) && fs11.statSync(path25).isDirectory();
15982
16243
  }
15983
- exists(path23) {
15984
- return fs10.existsSync(path23);
16244
+ exists(path25) {
16245
+ return fs11.existsSync(path25);
15985
16246
  }
15986
16247
  };
15987
16248
 
@@ -16008,7 +16269,15 @@ var CLAUDE_CODE_MANIFEST = {
16008
16269
  "{{/if}}",
16009
16270
  "{{prompt}}"
16010
16271
  ],
16011
- promptInArgs: true
16272
+ promptInArgs: true,
16273
+ // Interactive sessions show these even with --dangerously-skip-permissions:
16274
+ // a folder Claude Code has never been trusted in (a fresh clone, or a repo
16275
+ // only ever planned in headlessly), and the first Bypass Permissions run.
16276
+ blockingPrompts: [
16277
+ { phrase: "Is this a project you created or one you trust", asks: "whether to trust the task's folder" },
16278
+ { phrase: "Do you trust the files in this folder", asks: "whether to trust the task's folder" },
16279
+ { phrase: "running in Bypass Permissions mode", asks: "you to accept Bypass Permissions mode" }
16280
+ ]
16012
16281
  },
16013
16282
  features: {
16014
16283
  modelSelection: true,
@@ -16413,7 +16682,7 @@ var RunnerRegistry = class {
16413
16682
  return entry?.source === "builtin";
16414
16683
  }
16415
16684
  installFromPath(sourcePath) {
16416
- const absPath = path20.resolve(sourcePath);
16685
+ const absPath = path22.resolve(sourcePath);
16417
16686
  if (!this.store.dirExists(absPath)) {
16418
16687
  throw new Error(`Plugin source must be a directory: ${absPath}`);
16419
16688
  }
@@ -16425,7 +16694,7 @@ var RunnerRegistry = class {
16425
16694
  }
16426
16695
  installFromGit(url) {
16427
16696
  const validated = assertInstallablePluginUrl(url);
16428
- const tmpDir = path20.join(os5.tmpdir(), `ordewell-plugin-${randomBytes4(12).toString("hex")}`);
16697
+ const tmpDir = path22.join(os6.tmpdir(), `ordewell-plugin-${randomBytes4(12).toString("hex")}`);
16429
16698
  this.store.ensureDir(tmpDir);
16430
16699
  try {
16431
16700
  try {
@@ -16438,7 +16707,7 @@ var RunnerRegistry = class {
16438
16707
  const hasManifest = contents.some((f) => f === "manifest.json");
16439
16708
  let sourceDir = tmpDir;
16440
16709
  if (!hasManifest) {
16441
- const onlyEntry = contents.length === 1 ? path20.join(tmpDir, contents[0]) : void 0;
16710
+ const onlyEntry = contents.length === 1 ? path22.join(tmpDir, contents[0]) : void 0;
16442
16711
  if (!onlyEntry || !this.store.dirExists(onlyEntry)) {
16443
16712
  throw new Error(`No manifest.json found in repository: ${validated.toString()}`);
16444
16713
  }
@@ -16486,7 +16755,7 @@ var RunnerRegistry = class {
16486
16755
  }
16487
16756
  createSkeleton(name, outputDir) {
16488
16757
  assertPlainPluginName(name);
16489
- const dir = path20.resolve(outputDir, name);
16758
+ const dir = path22.resolve(outputDir, name);
16490
16759
  this.store.ensureDir(dir);
16491
16760
  const manifest = {
16492
16761
  name,
@@ -16518,8 +16787,8 @@ var RunnerRegistry = class {
16518
16787
  ]
16519
16788
  }
16520
16789
  };
16521
- this.store.writeFile(path20.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
16522
- const runSh = path20.join(dir, "run.sh");
16790
+ this.store.writeFile(path22.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
16791
+ const runSh = path22.join(dir, "run.sh");
16523
16792
  const scriptContent = `#!/usr/bin/env bash
16524
16793
  # Custom runner script for ${name}
16525
16794
  # Receives:
@@ -16545,30 +16814,30 @@ echo "Running ${name} with prompt: $prompt"
16545
16814
  };
16546
16815
  function fsChmodSync(fpath, mode) {
16547
16816
  try {
16548
- fs11.chmodSync(fpath, mode);
16817
+ fs12.chmodSync(fpath, mode);
16549
16818
  } catch {
16550
16819
  }
16551
16820
  }
16552
16821
 
16553
16822
  // src/utils/stateStore.ts
16554
- import * as fs12 from "fs";
16555
- import * as path21 from "path";
16823
+ import * as fs13 from "fs";
16824
+ import * as path23 from "path";
16556
16825
  var STATE_FILE = "state.json";
16557
16826
  function getStatePath(baseDir) {
16558
- return path21.join(getStateDir(baseDir), STATE_FILE);
16827
+ return path23.join(getStateDir(baseDir), STATE_FILE);
16559
16828
  }
16560
16829
  function saveState(plan, baseDir) {
16561
16830
  const statePath = getStatePath(baseDir ?? process.cwd());
16562
- ensureDir(path21.dirname(statePath));
16831
+ ensureDir(path23.dirname(statePath));
16563
16832
  plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
16564
- fs12.writeFileSync(statePath, JSON.stringify(plan, null, 2));
16833
+ fs13.writeFileSync(statePath, JSON.stringify(plan, null, 2));
16565
16834
  }
16566
16835
  function loadState(baseDir, logger = defaultLogger) {
16567
16836
  const dir = baseDir ?? process.cwd();
16568
16837
  const statePath = getStatePath(dir);
16569
- if (!fs12.existsSync(statePath)) return null;
16838
+ if (!fs13.existsSync(statePath)) return null;
16570
16839
  try {
16571
- const raw = fs12.readFileSync(statePath, "utf-8");
16840
+ const raw = fs13.readFileSync(statePath, "utf-8");
16572
16841
  const parsed = JSON.parse(raw);
16573
16842
  if (!Array.isArray(parsed.runners)) {
16574
16843
  logger.warn("stateStore", `rejecting state from ${statePath}: old format (scalar "runner" instead of "runners" array). Please re-plan.`);
@@ -16585,16 +16854,16 @@ function loadState(baseDir, logger = defaultLogger) {
16585
16854
  function clearState(baseDir) {
16586
16855
  const dir = baseDir ?? process.cwd();
16587
16856
  const statePath = getStatePath(dir);
16588
- if (fs12.existsSync(statePath)) fs12.unlinkSync(statePath);
16857
+ if (fs13.existsSync(statePath)) fs13.unlinkSync(statePath);
16589
16858
  }
16590
16859
  function stateExists(baseDir) {
16591
16860
  const dir = baseDir ?? process.cwd();
16592
- return fs12.existsSync(getStatePath(dir));
16861
+ return fs13.existsSync(getStatePath(dir));
16593
16862
  }
16594
16863
 
16595
16864
  // src/services/SettingsService.ts
16596
- import * as fs13 from "fs";
16597
- import * as path22 from "path";
16865
+ import * as fs14 from "fs";
16866
+ import * as path24 from "path";
16598
16867
  var DEFAULTS = {
16599
16868
  tdd: { enabled: true },
16600
16869
  verification: { enabled: false }
@@ -16602,7 +16871,7 @@ var DEFAULTS = {
16602
16871
  function getSettingsPath() {
16603
16872
  const override = process.env.ORDEWELL_SETTINGS_PATH;
16604
16873
  if (override && override.trim()) return override.trim();
16605
- return path22.join(globalDataDir(), "settings.json");
16874
+ return path24.join(globalDataDir(), "settings.json");
16606
16875
  }
16607
16876
  function readPlannerModels(raw) {
16608
16877
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
@@ -16635,7 +16904,7 @@ var SettingsService = class {
16635
16904
  }
16636
16905
  fileMtimeMs() {
16637
16906
  try {
16638
- return fs13.statSync(this.filePath).mtimeMs;
16907
+ return fs14.statSync(this.filePath).mtimeMs;
16639
16908
  } catch {
16640
16909
  return null;
16641
16910
  }
@@ -16707,8 +16976,8 @@ var SettingsService = class {
16707
16976
  load() {
16708
16977
  migrateOldConfigDir();
16709
16978
  try {
16710
- if (fs13.existsSync(this.filePath)) {
16711
- const raw = JSON.parse(fs13.readFileSync(this.filePath, "utf-8"));
16979
+ if (fs14.existsSync(this.filePath)) {
16980
+ const raw = JSON.parse(fs14.readFileSync(this.filePath, "utf-8"));
16712
16981
  const settings = {
16713
16982
  tdd: { enabled: raw.tdd?.enabled ?? DEFAULTS.tdd.enabled },
16714
16983
  verification: { enabled: raw.verification?.enabled ?? DEFAULTS.verification.enabled }
@@ -16730,11 +16999,11 @@ var SettingsService = class {
16730
16999
  return { ...DEFAULTS, tdd: { ...DEFAULTS.tdd }, verification: { ...DEFAULTS.verification } };
16731
17000
  }
16732
17001
  persist() {
16733
- const dir = path22.dirname(this.filePath);
16734
- if (!fs13.existsSync(dir)) {
16735
- fs13.mkdirSync(dir, { recursive: true });
17002
+ const dir = path24.dirname(this.filePath);
17003
+ if (!fs14.existsSync(dir)) {
17004
+ fs14.mkdirSync(dir, { recursive: true });
16736
17005
  }
16737
- fs13.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2));
17006
+ fs14.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2));
16738
17007
  this.cachedMtimeMs = this.fileMtimeMs();
16739
17008
  }
16740
17009
  };
@@ -16800,6 +17069,7 @@ export {
16800
17069
  ConversationEditError,
16801
17070
  DAEMON_SUBPROTOCOL,
16802
17071
  DAEMON_TOKEN_SUBPROTOCOL_PREFIX,
17072
+ DEFAULT_MAX_PARALLEL,
16803
17073
  DENY_ALL,
16804
17074
  EmbeddedNewlineError,
16805
17075
  EnvConfig,
@@ -16982,6 +17252,7 @@ export {
16982
17252
  modifyValidationFeedback,
16983
17253
  normalizeAgentArgs,
16984
17254
  normalizeGeminiModel,
17255
+ parseMaxParallel,
16985
17256
  parsePartialPlan,
16986
17257
  parsePlanJson,
16987
17258
  parseTaskOpsJson,