@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.js CHANGED
@@ -56,6 +56,7 @@ __export(index_exports, {
56
56
  ConversationEditError: () => ConversationEditError,
57
57
  DAEMON_SUBPROTOCOL: () => DAEMON_SUBPROTOCOL,
58
58
  DAEMON_TOKEN_SUBPROTOCOL_PREFIX: () => DAEMON_TOKEN_SUBPROTOCOL_PREFIX,
59
+ DEFAULT_MAX_PARALLEL: () => DEFAULT_MAX_PARALLEL,
59
60
  DENY_ALL: () => DENY_ALL,
60
61
  EmbeddedNewlineError: () => EmbeddedNewlineError,
61
62
  EnvConfig: () => EnvConfig,
@@ -238,6 +239,7 @@ __export(index_exports, {
238
239
  modifyValidationFeedback: () => modifyValidationFeedback,
239
240
  normalizeAgentArgs: () => normalizeAgentArgs,
240
241
  normalizeGeminiModel: () => normalizeGeminiModel,
242
+ parseMaxParallel: () => parseMaxParallel,
241
243
  parsePartialPlan: () => parsePartialPlan,
242
244
  parsePlanJson: () => parsePlanJson,
243
245
  parseTaskOpsJson: () => parseTaskOpsJson,
@@ -3191,33 +3193,33 @@ function grepOptions(args) {
3191
3193
  };
3192
3194
  }
3193
3195
  var STOPPED_TOOL_RESULT = "Stopped by the user \u2014 this call was not executed, and no further tool calls will run this turn.";
3194
- async function executeTool(name, args, fs14, fetcher, signal) {
3195
- const outcome = await runTool(name, args, fs14, fetcher, signal);
3196
+ async function executeTool(name, args, fs15, fetcher, signal) {
3197
+ const outcome = await runTool(name, args, fs15, fetcher, signal);
3196
3198
  return { ...outcome, output: redactSecrets(outcome.output) };
3197
3199
  }
3198
- async function runTool(name, args, fs14, fetcher, signal) {
3200
+ async function runTool(name, args, fs15, fetcher, signal) {
3199
3201
  if (signal?.aborted) return { success: false, output: STOPPED_TOOL_RESULT, truncated: false };
3200
3202
  switch (name) {
3201
3203
  case "read_file": {
3202
3204
  const offset = num(args.offset);
3203
3205
  const limit = num(args.limit);
3204
3206
  const maxBytes = num(args.maxBytes);
3205
- return fs14.readFile(String(args.path ?? ""), { offset, limit, maxBytes });
3207
+ return fs15.readFile(String(args.path ?? ""), { offset, limit, maxBytes });
3206
3208
  }
3207
3209
  case "read_files": {
3208
3210
  const paths = Array.isArray(args.paths) ? args.paths.map(String) : [];
3209
- return fs14.readFiles(paths);
3211
+ return fs15.readFiles(paths);
3210
3212
  }
3211
3213
  case "glob":
3212
- return fs14.glob(String(args.pattern ?? ""), { path: str(args.path), headLimit: num(args.head_limit ?? args.headLimit) });
3214
+ return fs15.glob(String(args.pattern ?? ""), { path: str(args.path), headLimit: num(args.head_limit ?? args.headLimit) });
3213
3215
  case "grep":
3214
- return fs14.grep(String(args.pattern ?? ""), grepOptions(args));
3216
+ return fs15.grep(String(args.pattern ?? ""), grepOptions(args));
3215
3217
  case "find_symbol":
3216
- return fs14.findSymbol(String(args.symbol ?? ""), { language: str(args.language), path: str(args.path) });
3218
+ return fs15.findSymbol(String(args.symbol ?? ""), { language: str(args.language), path: str(args.path) });
3217
3219
  case "list_dir":
3218
- return fs14.listDir(String(args.path ?? ""), num(args.depth));
3220
+ return fs15.listDir(String(args.path ?? ""), num(args.depth));
3219
3221
  case "bash":
3220
- return fs14.bash(String(args.command ?? ""), signal);
3222
+ return fs15.bash(String(args.command ?? ""), signal);
3221
3223
  case "fetch": {
3222
3224
  if (!fetcher) return { success: false, output: "Web fetching is not available in this environment.", truncated: false };
3223
3225
  const url = String(args.url ?? "");
@@ -3325,14 +3327,14 @@ var PendingApprovals = class {
3325
3327
  ask(request) {
3326
3328
  const id = `ap-${Date.now()}-${counter++}`;
3327
3329
  const pending = { id, request, createdAt: (/* @__PURE__ */ new Date()).toISOString() };
3328
- return new Promise((resolve6) => {
3330
+ return new Promise((resolve7) => {
3329
3331
  const settle = (granted) => {
3330
3332
  const entry = this.entries.get(id);
3331
3333
  if (!entry) return;
3332
3334
  clearTimeout(entry.timer);
3333
3335
  this.entries.delete(id);
3334
3336
  this.opts.onSettled?.(id, granted);
3335
- resolve6(granted);
3337
+ resolve7(granted);
3336
3338
  };
3337
3339
  const timer = setTimeout(() => settle(false), this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS);
3338
3340
  timer.unref?.();
@@ -3430,9 +3432,9 @@ function applyHeadLimit(stdout, headLimit) {
3430
3432
  }
3431
3433
  function formatSearchOutput(capped, root, opts) {
3432
3434
  if (capped.rows.length === 0) return opts.emptyMessage;
3433
- const sep = opts.sep ?? import_node_path.default.sep;
3434
- const prefix = root === sep || root.endsWith(sep) ? root : `${root}${sep}`;
3435
- const body = prefix === sep ? capped.rows.join("\n") : capped.rows.map((row) => row.startsWith(prefix) ? row.slice(prefix.length) : row).join("\n");
3435
+ const sep2 = opts.sep ?? import_node_path.default.sep;
3436
+ const prefix = root === sep2 || root.endsWith(sep2) ? root : `${root}${sep2}`;
3437
+ const body = prefix === sep2 ? capped.rows.join("\n") : capped.rows.map((row) => row.startsWith(prefix) ? row.slice(prefix.length) : row).join("\n");
3436
3438
  if (!capped.truncated) return body;
3437
3439
  const hint = opts.hint ? ` ${opts.hint}` : "";
3438
3440
  return `${body}
@@ -4023,6 +4025,15 @@ function configuredProviders(config) {
4023
4025
  return out;
4024
4026
  }
4025
4027
 
4028
+ // src/utils/maxParallel.ts
4029
+ var DEFAULT_MAX_PARALLEL = 3;
4030
+ function parseMaxParallel(value) {
4031
+ const text = String(value ?? "").trim();
4032
+ const n = typeof value === "number" ? value : Number(text);
4033
+ if (text === "" || !Number.isInteger(n) || n < 1) return null;
4034
+ return n;
4035
+ }
4036
+
4026
4037
  // src/interfaces/BaseConfig.ts
4027
4038
  function normalizeGeminiModel(id) {
4028
4039
  return id.replace(/^gemini:/, "").replace(/^google\//, "");
@@ -4227,7 +4238,7 @@ var BaseConfig = class {
4227
4238
  return "";
4228
4239
  }
4229
4240
  get maxParallelSessions() {
4230
- return parseInt(process.env.ORDEWELL_MAX_PARALLEL || "3", 10);
4241
+ return parseMaxParallel(process.env.ORDEWELL_MAX_PARALLEL) ?? DEFAULT_MAX_PARALLEL;
4231
4242
  }
4232
4243
  get researchEnabled() {
4233
4244
  return process.env.ORDEWELL_RESEARCH_ENABLED !== "false";
@@ -4468,28 +4479,28 @@ function posixBinDirs() {
4468
4479
  ];
4469
4480
  }
4470
4481
  function windowsBinDirs(env, home) {
4471
- const join23 = (...parts) => path5.win32.join(...parts);
4472
- const under = (base, ...parts) => base ? [join23(base, ...parts)] : [];
4482
+ const join24 = (...parts) => path5.win32.join(...parts);
4483
+ const under = (base, ...parts) => base ? [join24(base, ...parts)] : [];
4473
4484
  const appData = env.APPDATA;
4474
4485
  const localAppData = env.LOCALAPPDATA;
4475
4486
  const programData = env.ProgramData;
4476
4487
  return [
4477
4488
  // Native per-user installers, PowerShell-driven and otherwise.
4478
- join23(home, ".local", "bin"),
4479
- join23(home, ".opencode", "bin"),
4480
- join23(home, ".claude", "bin"),
4481
- join23(home, ".codex", "bin"),
4489
+ join24(home, ".local", "bin"),
4490
+ join24(home, ".opencode", "bin"),
4491
+ join24(home, ".claude", "bin"),
4492
+ join24(home, ".codex", "bin"),
4482
4493
  ...under(localAppData, "Programs", "claude"),
4483
4494
  // Node package managers. npm's global prefix is `%APPDATA%\npm`; the
4484
4495
  // literal spelling is kept as a fallback for a host that does not export
4485
4496
  // APPDATA (a service account, a stripped CI container).
4486
4497
  ...under(appData, "npm"),
4487
- join23(home, "AppData", "Roaming", "npm"),
4498
+ join24(home, "AppData", "Roaming", "npm"),
4488
4499
  ...under(localAppData, "pnpm"),
4489
4500
  ...under(localAppData, "Yarn", "bin"),
4490
- join23(home, ".bun", "bin"),
4501
+ join24(home, ".bun", "bin"),
4491
4502
  // Windows package managers.
4492
- join23(home, "scoop", "shims"),
4503
+ join24(home, "scoop", "shims"),
4493
4504
  ...under(env.SCOOP, "shims"),
4494
4505
  ...under(env.SCOOP_GLOBAL ?? programData, "scoop", "shims"),
4495
4506
  ...under(programData, "chocolatey", "bin"),
@@ -4499,7 +4510,7 @@ function windowsBinDirs(env, home) {
4499
4510
  ...under(localAppData, "Microsoft", "WindowsApps"),
4500
4511
  // Version managers. Volta's Windows home is not the POSIX `~/.volta`.
4501
4512
  ...under(localAppData, "Volta", "bin"),
4502
- join23(home, ".volta", "bin")
4513
+ join24(home, ".volta", "bin")
4503
4514
  ];
4504
4515
  }
4505
4516
  function wellKnownBinDirs(deps = {}) {
@@ -6694,12 +6705,12 @@ function buildConflictRepairPrompt(task, conflict, integrationBranch) {
6694
6705
  const files = conflict.conflictFiles ?? [];
6695
6706
  const asked = `What the task was asked to do:
6696
6707
  ${task.prompt ?? task.description}`;
6697
- const resolve6 = "resolve every conflict so that both sides' intent survives: keep the work already integrated and add what the task contributed.";
6708
+ const resolve7 = "resolve every conflict so that both sides' intent survives: keep the work already integrated and add what the task contributed.";
6698
6709
  const repos = conflict.repos.filter((repo) => repo !== SELF_REPO);
6699
6710
  if (repos.length === 0) {
6700
6711
  return [
6701
6712
  `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.`,
6702
- `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.`,
6713
+ `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.`,
6703
6714
  "Build and test the result the way this project does, commit the merge, and only then print the completion marker.",
6704
6715
  "",
6705
6716
  asked
@@ -6708,7 +6719,7 @@ ${task.prompt ?? task.description}`;
6708
6719
  const stoppedIn = conflict.conflictRepo ?? repos[0];
6709
6720
  return [
6710
6721
  `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.`,
6711
- `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.`,
6722
+ `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.`,
6712
6723
  "Build and test the result the way this project does, commit the merge in each repository, and only then print the completion marker.",
6713
6724
  "",
6714
6725
  asked
@@ -8067,8 +8078,8 @@ function compactResearchResults(resultsText, maxChars = COMPACTION_LIMITS.resear
8067
8078
 
8068
8079
  // src/services/ContextCollector.ts
8069
8080
  var ORDEWELL_CONTEXT_MAX = 8e3;
8070
- async function collectResearchContext(fs14, runners) {
8071
- const collector = new ContextCollector(fs14);
8081
+ async function collectResearchContext(fs15, runners) {
8082
+ const collector = new ContextCollector(fs15);
8072
8083
  let contextStr = "";
8073
8084
  let firstContext = null;
8074
8085
  for (const r of runners) {
@@ -8095,8 +8106,8 @@ ${firstContext.dirStructure}
8095
8106
  return contextStr;
8096
8107
  }
8097
8108
  var ContextCollector = class {
8098
- constructor(fs14, registry) {
8099
- this.fs = fs14;
8109
+ constructor(fs15, registry) {
8110
+ this.fs = fs15;
8100
8111
  this.registry = registry;
8101
8112
  }
8102
8113
  fs;
@@ -8177,10 +8188,10 @@ function summarizeToolCall(tool, argsJson, toolLabel) {
8177
8188
  const scope = args.include ? ` in ${args.include}` : "";
8178
8189
  return `grep ${ellipsize(String(args.pattern), 40)}${scope}${mode}`;
8179
8190
  }
8180
- const path24 = args.filePath || args.file_path || args.path;
8181
- if (path24) {
8182
- const basename = String(path24).split("/").pop() || String(path24);
8183
- return `${display} ${basename}`;
8191
+ const path26 = args.filePath || args.file_path || args.path;
8192
+ if (path26) {
8193
+ const basename2 = String(path26).split("/").pop() || String(path26);
8194
+ return `${display} ${basename2}`;
8184
8195
  }
8185
8196
  if (args.pattern) {
8186
8197
  return `${display} ${args.pattern}`;
@@ -8213,22 +8224,22 @@ var ALLOWED_TOOL_NAMES = new Set(subagentToolSpecs().map((t) => t.name));
8213
8224
  function refused(output) {
8214
8225
  return { success: false, output, truncated: false };
8215
8226
  }
8216
- function nonPromptingFs(fs14) {
8217
- const withinWorkspace = (p) => resolveWithin(fs14.getWorkspaceRoot(), p ?? ".").inside;
8227
+ function nonPromptingFs(fs15) {
8228
+ const withinWorkspace = (p) => resolveWithin(fs15.getWorkspaceRoot(), p ?? ".").inside;
8218
8229
  const outsideRefusal = (p) => refused(
8219
8230
  `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.`
8220
8231
  );
8221
8232
  return {
8222
- getWorkspaceRoot: () => fs14.getWorkspaceRoot(),
8223
- readFile: (p, opts) => withinWorkspace(p) ? fs14.readFile(p, opts) : Promise.resolve(outsideRefusal(p)),
8233
+ getWorkspaceRoot: () => fs15.getWorkspaceRoot(),
8234
+ readFile: (p, opts) => withinWorkspace(p) ? fs15.readFile(p, opts) : Promise.resolve(outsideRefusal(p)),
8224
8235
  readFiles: (paths) => {
8225
8236
  const escaping = paths.find((p) => !withinWorkspace(p));
8226
- return escaping ? Promise.resolve(outsideRefusal(escaping)) : fs14.readFiles(paths);
8237
+ return escaping ? Promise.resolve(outsideRefusal(escaping)) : fs15.readFiles(paths);
8227
8238
  },
8228
- glob: (pattern, opts) => withinWorkspace(opts?.path) ? fs14.glob(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
8229
- grep: (pattern, opts) => withinWorkspace(opts?.path) ? fs14.grep(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
8230
- findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs14.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
8231
- listDir: (p, depth) => withinWorkspace(p) ? fs14.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
8239
+ glob: (pattern, opts) => withinWorkspace(opts?.path) ? fs15.glob(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
8240
+ grep: (pattern, opts) => withinWorkspace(opts?.path) ? fs15.grep(pattern, opts) : Promise.resolve(outsideRefusal(opts.path)),
8241
+ findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs15.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
8242
+ listDir: (p, depth) => withinWorkspace(p) ? fs15.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
8232
8243
  bash: async (command, signal) => {
8233
8244
  const dialect = resolveResearchShell().dialect;
8234
8245
  const { tier, reason } = classifyCommand(command, { dialect });
@@ -8239,13 +8250,13 @@ function nonPromptingFs(fs14) {
8239
8250
  }
8240
8251
  const escaping = pathLikeArgs(command, { dialect }).find((p) => !withinWorkspace(p));
8241
8252
  if (escaping) return outsideRefusal(escaping);
8242
- return fs14.bash(command, signal);
8253
+ return fs15.bash(command, signal);
8243
8254
  }
8244
8255
  };
8245
8256
  }
8246
8257
  async function runLoop(prompt, deps) {
8247
8258
  if (deps.signal?.aborted) return "[research agent aborted before starting]";
8248
- const fs14 = nonPromptingFs(deps.fs);
8259
+ const fs15 = nonPromptingFs(deps.fs);
8249
8260
  const chat = deps.createChat((delta) => deps.onProgress?.({ type: "thinking", text: delta }));
8250
8261
  let turn = await chat.sendMessage(prompt, deps.signal);
8251
8262
  for (let step = 0; turn.hasToolCalls && step < SUBAGENT_LIMITS.maxSteps; step++) {
@@ -8262,7 +8273,7 @@ ${turn.text}`;
8262
8273
  if (tc.name === "read_file" && !("maxBytes" in args) && !("limit" in args)) args.limit = 2e3;
8263
8274
  const toolArgs = JSON.stringify(tc.args);
8264
8275
  deps.onProgress?.({ type: "tool_call", tool: tc.name, toolArgs, toolCallId: tc.id });
8265
- const res = await executeTool(tc.name, args, fs14, void 0, deps.signal);
8276
+ const res = await executeTool(tc.name, args, fs15, void 0, deps.signal);
8266
8277
  const output = res.output.length > SUBAGENT_LIMITS.toolOutputMaxChars ? res.output.slice(0, SUBAGENT_LIMITS.toolOutputMaxChars) + `
8267
8278
  [... truncated to ${SUBAGENT_LIMITS.toolOutputMaxChars} chars, total ${res.output.length}]` : res.output;
8268
8279
  const stepEntry = {
@@ -8378,7 +8389,7 @@ var BaseAiService = class _BaseAiService {
8378
8389
  * steering/failed result and the loop continues sequentially: a subagent can
8379
8390
  * never fail a plan or a turn.
8380
8391
  */
8381
- async executeSpawnAgent(args, fs14, onProgress, signal, subagentId) {
8392
+ async executeSpawnAgent(args, fs15, onProgress, signal, subagentId) {
8382
8393
  const prompt = typeof args.prompt === "string" ? args.prompt.trim() : "";
8383
8394
  if (!prompt) {
8384
8395
  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 };
@@ -8389,20 +8400,20 @@ var BaseAiService = class _BaseAiService {
8389
8400
  if (!chat) throw new Error("subagent chats are not available for this provider");
8390
8401
  return chat;
8391
8402
  },
8392
- fs: fs14,
8403
+ fs: fs15,
8393
8404
  signal,
8394
8405
  onProgress: (progress) => onProgress({ ...progress, subagentId })
8395
8406
  });
8396
8407
  }
8397
8408
  /** Collect project context for the planning phase. Shared with the harness backend. */
8398
- static async collectResearchContext(fs14, runners) {
8399
- return collectResearchContext(fs14, runners);
8409
+ static async collectResearchContext(fs15, runners) {
8410
+ return collectResearchContext(fs15, runners);
8400
8411
  }
8401
8412
  /**
8402
8413
  * Execute one round of tool calls and report each through onProgress.
8403
8414
  * Returns the results to feed back plus the log entries produced.
8404
8415
  */
8405
- async executeToolCalls(toolCalls, fs14, onProgress, fetcher, stepIndex, thinking, signal) {
8416
+ async executeToolCalls(toolCalls, fs15, onProgress, fetcher, stepIndex, thinking, signal) {
8406
8417
  const toolResults = [];
8407
8418
  const logEntries = [];
8408
8419
  let resultsText = "";
@@ -8421,18 +8432,18 @@ var BaseAiService = class _BaseAiService {
8421
8432
  const subagentId = (0, import_uuid3.v4)();
8422
8433
  spawnSubagentIds.set(i, subagentId);
8423
8434
  onProgress({ type: "tool_call", tool: toolCalls[i].name, toolArgs: JSON.stringify(toolCalls[i].args), subagentId, toolCallId: toolCalls[i].id });
8424
- precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args }, fs14, onProgress, signal, subagentId));
8435
+ precomputed.set(i, await this.executeSpawnAgent({ ...toolCalls[i].args }, fs15, onProgress, signal, subagentId));
8425
8436
  }) : Promise.resolve(),
8426
8437
  parallelIndexes.length > 1 ? mapWithConcurrency(parallelIndexes, TOOL_ROUND_CONCURRENCY, async (i) => {
8427
8438
  onProgress({ type: "tool_call", tool: toolCalls[i].name, toolArgs: JSON.stringify(toolCalls[i].args), toolCallId: toolCalls[i].id });
8428
- precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i], fs14, fetcher, signal));
8439
+ precomputed.set(i, await executeTool(toolCalls[i].name, normalizedArgs[i], fs15, fetcher, signal));
8429
8440
  }) : Promise.resolve()
8430
8441
  ]);
8431
8442
  for (const [index, tc] of toolCalls.entries()) {
8432
8443
  const toolArgs = normalizedArgs[index];
8433
8444
  const preResult = precomputed.get(index);
8434
8445
  if (!preResult) onProgress({ type: "tool_call", tool: tc.name, toolArgs: JSON.stringify(tc.args), toolCallId: tc.id });
8435
- const toolResult = preResult ?? await executeTool(tc.name, toolArgs, fs14, fetcher, signal);
8446
+ const toolResult = preResult ?? await executeTool(tc.name, toolArgs, fs15, fetcher, signal);
8436
8447
  const MAX_RESPONSE_CHARS = 5e4;
8437
8448
  const truncatedResponse = toolResult.success && (toolResult.truncated || toolResult.output.length > MAX_RESPONSE_CHARS);
8438
8449
  const llmOutput = toolResult.output.length > MAX_RESPONSE_CHARS ? toolResult.output.slice(0, MAX_RESPONSE_CHARS) + `
@@ -8618,7 +8629,7 @@ ${llmOutput}
8618
8629
  * parsed tasks if a plan was emitted mid-loop, or null if the loop
8619
8630
  * exhausted without a valid plan.
8620
8631
  */
8621
- async runResearchLoop(chat, firstMessage, fs14, onProgress, runners, initialLog, fetcher, userGoal, runnerModes, autonomousDefault, signal) {
8632
+ async runResearchLoop(chat, firstMessage, fs15, onProgress, runners, initialLog, fetcher, userGoal, runnerModes, autonomousDefault, signal) {
8622
8633
  const researchLog = initialLog ? [...initialLog] : [];
8623
8634
  if (!initialLog && userGoal) {
8624
8635
  researchLog.push({
@@ -8645,7 +8656,7 @@ ${llmOutput}
8645
8656
  if (!turn.hasToolCalls) break;
8646
8657
  const { toolResults, logEntries, resultsText } = await this.executeToolCalls(
8647
8658
  turn.toolCalls,
8648
- fs14,
8659
+ fs15,
8649
8660
  onProgress,
8650
8661
  fetcher,
8651
8662
  step,
@@ -8878,11 +8889,11 @@ ${repairHint}` }] : parts;
8878
8889
  return this.consumePlanStream(result, onToken);
8879
8890
  }
8880
8891
  // --- IAiService implementation ---
8881
- async researchAndPlan(userDescription, runners, modelsByRunner, fs14, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
8892
+ async researchAndPlan(userDescription, runners, modelsByRunner, fs15, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
8882
8893
  this.ensureInit();
8883
8894
  const { autonomousDefault } = modes;
8884
8895
  const genModel = this.getPlanningModel();
8885
- const contextStr = await BaseAiService.collectResearchContext(fs14, runners);
8896
+ const contextStr = await BaseAiService.collectResearchContext(fs15, runners);
8886
8897
  const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
8887
8898
  const firstMessage = `${buildResearchToolsPrompt()}
8888
8899
 
@@ -8893,17 +8904,17 @@ Explore the workspace to understand the codebase, then generate the plan.`;
8893
8904
  generationConfig: { temperature: 0.3, topP: 0.95, maxOutputTokens: 16384 }
8894
8905
  });
8895
8906
  const researchChat = new GeminiResearchChat(chat);
8896
- const result = await this.runResearchLoop(researchChat, firstMessage, fs14, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
8907
+ const result = await this.runResearchLoop(researchChat, firstMessage, fs15, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
8897
8908
  if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
8898
8909
  const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
8899
8910
  return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
8900
8911
  }
8901
- async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs14, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
8912
+ async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs15, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
8902
8913
  this.ensureInit();
8903
8914
  const { autonomousDefault } = modes;
8904
8915
  let ctx = "";
8905
- if (fs14) {
8906
- const collected = await new ContextCollector(fs14).collect(runners[0] ?? "claude-code");
8916
+ if (fs15) {
8917
+ const collected = await new ContextCollector(fs15).collect(runners[0] ?? "claude-code");
8907
8918
  if (collected.aiflowContext) ctx = `
8908
8919
  <aiflow_context>
8909
8920
  ${collected.aiflowContext}
@@ -8919,12 +8930,12 @@ ${collected.aiflowContext}
8919
8930
  autonomousDefault
8920
8931
  );
8921
8932
  }
8922
- async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs14, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
8933
+ async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs15, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, _signal) {
8923
8934
  this.ensureInit();
8924
8935
  const { autonomousDefault } = modes;
8925
8936
  let aiflowContext;
8926
- if (fs14) {
8927
- const collected = await new ContextCollector(fs14).collect(existingPlan.runners[0] ?? "claude-code");
8937
+ if (fs15) {
8938
+ const collected = await new ContextCollector(fs15).collect(existingPlan.runners[0] ?? "claude-code");
8928
8939
  if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
8929
8940
  }
8930
8941
  const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
@@ -9160,11 +9171,11 @@ ${buildResearchToolsPrompt()}` }
9160
9171
  }
9161
9172
  }
9162
9173
  // --- IAiService implementation ---
9163
- async researchAndPlan(userDescription, runners, modelsByRunner, fs14, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
9174
+ async researchAndPlan(userDescription, runners, modelsByRunner, fs15, onProgress, fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
9164
9175
  this.ensureInit();
9165
9176
  const client = this.getClient();
9166
9177
  const { autonomousDefault } = modes;
9167
- const contextStr = await BaseAiService.collectResearchContext(fs14, runners);
9178
+ const contextStr = await BaseAiService.collectResearchContext(fs15, runners);
9168
9179
  const systemPrompt = buildResearchPrompt(userDescription, contextStr, modelsByRunner, runners, runnerModes, modes);
9169
9180
  const userText = `User goal: ${userDescription}`;
9170
9181
  const firstMessage = `${buildResearchToolsPrompt()}
@@ -9182,17 +9193,17 @@ ${userText}`;
9182
9193
  toOpenAiTools(),
9183
9194
  (delta) => onProgress({ type: "thinking", text: delta })
9184
9195
  );
9185
- const result = await this.runResearchLoop(researchChat, firstMessage, fs14, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
9196
+ const result = await this.runResearchLoop(researchChat, firstMessage, fs15, onProgress, runners, void 0, fetcher, userDescription, runnerModes, autonomousDefault, signal);
9186
9197
  if (result.tasks) return { tasks: result.tasks, researchLog: result.researchLog, researchResults: result.researchResults };
9187
9198
  const fallback = await this.generatePlanFallback(userDescription, contextStr, result.researchResults, modelsByRunner, runners, onProgress, result.researchLog, runnerModes, autonomousDefault, signal, modes);
9188
9199
  return { tasks: fallback.tasks, researchLog: fallback.researchLog, researchResults: result.researchResults };
9189
9200
  }
9190
- async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs14, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
9201
+ async generatePlanDirect(userDescription, runners = ["claude-code"], modelsByRunner = {}, onToken, fs15, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
9191
9202
  this.ensureInit();
9192
9203
  const { autonomousDefault } = modes;
9193
9204
  let ctx = "";
9194
- if (fs14) {
9195
- const collected = await new ContextCollector(fs14).collect(runners[0] ?? "claude-code");
9205
+ if (fs15) {
9206
+ const collected = await new ContextCollector(fs15).collect(runners[0] ?? "claude-code");
9196
9207
  if (collected.aiflowContext) ctx = `
9197
9208
  <aiflow_context>
9198
9209
  ${collected.aiflowContext}
@@ -9208,12 +9219,12 @@ ${collected.aiflowContext}
9208
9219
  autonomousDefault
9209
9220
  );
9210
9221
  }
9211
- async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs14, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
9222
+ async modifyPlan(existingPlan, userRequest, modelsByRunner, onProgress, fs15, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
9212
9223
  this.ensureInit();
9213
9224
  const { autonomousDefault } = modes;
9214
9225
  let aiflowContext;
9215
- if (fs14) {
9216
- const collected = await new ContextCollector(fs14).collect(existingPlan.runners[0] ?? "claude-code");
9226
+ if (fs15) {
9227
+ const collected = await new ContextCollector(fs15).collect(existingPlan.runners[0] ?? "claude-code");
9217
9228
  if (collected.aiflowContext) aiflowContext = collected.aiflowContext;
9218
9229
  }
9219
9230
  const prompt = buildModifyPlanPrompt(existingPlan, userRequest, modelsByRunner, aiflowContext, runnerModes, autonomousDefault);
@@ -9232,6 +9243,9 @@ ${collected.aiflowContext}
9232
9243
  }
9233
9244
  };
9234
9245
 
9246
+ // src/services/TaskOrchestrator.ts
9247
+ var path12 = __toESM(require("path"));
9248
+
9235
9249
  // src/services/promptAugment.ts
9236
9250
  var MAX_TAIL_CHARS = 500;
9237
9251
  var DEFAULT_PLAN_MAP_MAX_ENTRIES = 30;
@@ -9818,17 +9832,229 @@ ${line}
9818
9832
 
9819
9833
  // src/services/transcriptCapture.ts
9820
9834
  var import_fs = require("fs");
9835
+ var os4 = __toESM(require("os"));
9836
+ var path11 = __toESM(require("path"));
9837
+
9838
+ // src/services/workspaceEnv.ts
9839
+ var import_child_process3 = require("child_process");
9840
+ var fs5 = __toESM(require("fs"));
9821
9841
  var os3 = __toESM(require("os"));
9822
9842
  var path10 = __toESM(require("path"));
9843
+
9844
+ // src/services/settingsEnvAllowlist.ts
9845
+ var ORDEWELL_SETTABLE_ENV = [
9846
+ "AI_PROVIDER",
9847
+ "ORCHESTRATOR_MODEL",
9848
+ "ORDEWELL_SUBAGENT_MODEL",
9849
+ "ORDEWELL_PLANNER_EFFORT",
9850
+ "ORDEWELL_RESEARCH_ENABLED",
9851
+ "ORDEWELL_MAX_PARALLEL",
9852
+ "ORDEWELL_CONFLICT_REPAIR_ATTEMPTS"
9853
+ ];
9854
+ var SETTINGS_ENV_REFUSED = [
9855
+ // Approval posture and pre-approved scope.
9856
+ "ORDEWELL_AUTONOMOUS_MODE",
9857
+ "ORDEWELL_APPROVAL_MODE",
9858
+ "ORDEWELL_APPROVAL_ALLOW",
9859
+ // Where settings are persisted.
9860
+ "ORDEWELL_SETTINGS_PATH",
9861
+ // Runtime options and dynamic loaders.
9862
+ "NODE_OPTIONS",
9863
+ "NODE_REPL_EXTERNAL_MODULE",
9864
+ "NODE_PATH",
9865
+ "ELECTRON_RUN_AS_NODE",
9866
+ "LD_PRELOAD",
9867
+ "LD_LIBRARY_PATH",
9868
+ "LD_AUDIT",
9869
+ "DYLD_INSERT_LIBRARIES",
9870
+ "DYLD_LIBRARY_PATH",
9871
+ "DYLD_FRAMEWORK_PATH",
9872
+ // Executable resolution.
9873
+ "PATH",
9874
+ "PATHEXT",
9875
+ "NODE"
9876
+ ];
9877
+ var REFUSED = new Set(SETTINGS_ENV_REFUSED);
9878
+ function providerEnvKeys() {
9879
+ const keys = [];
9880
+ for (const meta of Object.values(ALL_PROVIDERS)) {
9881
+ if (meta.apiKeyEnvVar) keys.push(meta.apiKeyEnvVar);
9882
+ if (meta.baseUrlEnvVar) keys.push(meta.baseUrlEnvVar);
9883
+ for (const detect of meta.detectEnvVars) keys.push(detect);
9884
+ }
9885
+ return keys;
9886
+ }
9887
+ var PROVIDER_CREDENTIAL_ENV = new Set(providerEnvKeys());
9888
+ var SETTINGS_ENV_ALLOWLIST = new Set(
9889
+ [...providerEnvKeys(), ...ORDEWELL_SETTABLE_ENV].filter((key) => !REFUSED.has(key))
9890
+ );
9891
+ function admitSettingsEnv(env) {
9892
+ const accepted = {};
9893
+ const rejected = [];
9894
+ for (const [key, value] of Object.entries(env)) {
9895
+ if (REFUSED.has(key) || !SETTINGS_ENV_ALLOWLIST.has(key) || typeof value !== "string") {
9896
+ rejected.push(key);
9897
+ continue;
9898
+ }
9899
+ accepted[key] = value;
9900
+ }
9901
+ return { accepted, rejected };
9902
+ }
9903
+
9904
+ // src/services/workspaceEnv.ts
9905
+ var WORKSPACE_ENV_FILE = "env";
9906
+ var DIRENV_TIMEOUT_MS = 5e3;
9907
+ var KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
9908
+ function defaultDeps() {
9909
+ return {
9910
+ run: (command, args, opts) => new Promise((resolve7) => {
9911
+ (0, import_child_process3.execFile)(command, args, { ...opts, timeout: DIRENV_TIMEOUT_MS, encoding: "utf8" }, (err, stdout, stderr) => {
9912
+ const code = err ? typeof err.code === "number" ? Number(err.code) : -1 : 0;
9913
+ resolve7({ code, stdout: String(stdout ?? ""), stderr: String(stderr ?? "") });
9914
+ });
9915
+ }),
9916
+ readFile: (file) => {
9917
+ try {
9918
+ return fs5.readFileSync(file, "utf8");
9919
+ } catch {
9920
+ return null;
9921
+ }
9922
+ },
9923
+ isDirectory: (dir) => {
9924
+ try {
9925
+ return fs5.statSync(dir).isDirectory();
9926
+ } catch {
9927
+ return false;
9928
+ }
9929
+ },
9930
+ baseEnv: process.env,
9931
+ home: os3.homedir()
9932
+ };
9933
+ }
9934
+ var REFUSED2 = new Set(SETTINGS_ENV_REFUSED);
9935
+ async function resolveWorkspaceEnv(cwd, overrides = {}) {
9936
+ const deps = { ...defaultDeps(), ...overrides };
9937
+ const result = { env: {}, blockedEnvrc: null, refused: [], trackedEnvFile: null };
9938
+ if (!deps.isDirectory(cwd)) return result;
9939
+ const collected = {};
9940
+ const direnv = await fromDirenv(checkoutDirOf(cwd, deps), deps);
9941
+ result.blockedEnvrc = direnv.blocked;
9942
+ Object.assign(collected, direnv.env);
9943
+ const file = findUp(cwd, path10.join(STATE_DIR, WORKSPACE_ENV_FILE), deps);
9944
+ if (file) {
9945
+ const content = deps.readFile(file);
9946
+ if (content !== null) {
9947
+ if (await isTracked(file, deps)) result.trackedEnvFile = file;
9948
+ else Object.assign(collected, parseEnvFile(content, { ...deps.baseEnv, ...collected }, deps.home));
9949
+ }
9950
+ }
9951
+ for (const [key, value] of Object.entries(collected)) {
9952
+ if (!KEY.test(key)) continue;
9953
+ if (REFUSED2.has(key) || key.toUpperCase() === "PATH") result.refused.push(key);
9954
+ else result.env[key] = value;
9955
+ }
9956
+ return result;
9957
+ }
9958
+ async function workspaceEnvOf(cwd) {
9959
+ return (await resolveWorkspaceEnv(cwd)).env;
9960
+ }
9961
+ function checkoutDirOf(cwd, deps) {
9962
+ const parts = path10.resolve(cwd).split(path10.sep);
9963
+ const at = parts.findIndex((part, i) => part === STATE_DIR && parts[i + 1] === "worktrees");
9964
+ if (at < 0 || parts.length < at + 4) return cwd;
9965
+ const root = parts.slice(0, at).join(path10.sep) || path10.sep;
9966
+ const mapped = path10.join(root, ...parts.slice(at + 4));
9967
+ return deps.isDirectory(mapped) ? mapped : root;
9968
+ }
9969
+ async function fromDirenv(cwd, deps) {
9970
+ const setting = deps.baseEnv.ORDEWELL_DIRENV;
9971
+ if (setting === "false" || setting === "0") return { env: {}, blocked: null };
9972
+ const baseEnv = Object.fromEntries(Object.entries(deps.baseEnv).filter(([key]) => !key.startsWith("DIRENV_")));
9973
+ let out;
9974
+ try {
9975
+ out = await deps.run("direnv", ["export", "json"], { cwd, env: baseEnv });
9976
+ } catch {
9977
+ return { env: {}, blocked: null };
9978
+ }
9979
+ const blocked = out.stderr.match(/error (\S*\.envrc) is blocked/);
9980
+ if (blocked) return { env: {}, blocked: blocked[1] };
9981
+ if (out.code !== 0 || !out.stdout.trim()) return { env: {}, blocked: null };
9982
+ let parsed;
9983
+ try {
9984
+ parsed = JSON.parse(out.stdout);
9985
+ } catch {
9986
+ return { env: {}, blocked: null };
9987
+ }
9988
+ if (!parsed || typeof parsed !== "object") return { env: {}, blocked: null };
9989
+ const env = {};
9990
+ for (const [key, value] of Object.entries(parsed)) {
9991
+ if (typeof value === "string" && !key.startsWith("DIRENV_")) env[key] = value;
9992
+ }
9993
+ return { env, blocked: null };
9994
+ }
9995
+ function findUp(start, relative5, deps) {
9996
+ let dir = path10.resolve(start);
9997
+ for (; ; ) {
9998
+ const candidate = path10.join(dir, relative5);
9999
+ if (deps.readFile(candidate) !== null) return candidate;
10000
+ const parent = path10.dirname(dir);
10001
+ if (parent === dir) return null;
10002
+ dir = parent;
10003
+ }
10004
+ }
10005
+ async function isTracked(file, deps) {
10006
+ try {
10007
+ const out = await deps.run("git", ["ls-files", "--error-unmatch", path10.basename(file)], { cwd: path10.dirname(file), env: deps.baseEnv });
10008
+ return out.code === 0;
10009
+ } catch {
10010
+ return false;
10011
+ }
10012
+ }
10013
+ function parseEnvFile(content, context, home) {
10014
+ const env = {};
10015
+ const lookup2 = (name) => env[name] ?? context[name] ?? "";
10016
+ const expand = (value) => {
10017
+ const tilde = value.startsWith("~/") ? path10.join(home, value.slice(2)) : value;
10018
+ return tilde.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (_, braced, bare) => lookup2(braced ?? bare));
10019
+ };
10020
+ for (const raw of content.split(/\r?\n/)) {
10021
+ const line = raw.trim().replace(/^export\s+/, "");
10022
+ if (!line || line.startsWith("#")) continue;
10023
+ const eq = line.indexOf("=");
10024
+ if (eq <= 0) continue;
10025
+ const key = line.slice(0, eq).trim();
10026
+ if (!KEY.test(key)) continue;
10027
+ const rest = line.slice(eq + 1).trim();
10028
+ if (rest.startsWith("'")) {
10029
+ const end = rest.indexOf("'", 1);
10030
+ env[key] = end > 0 ? rest.slice(1, end) : rest.slice(1);
10031
+ } else if (rest.startsWith('"')) {
10032
+ const end = rest.lastIndexOf('"');
10033
+ env[key] = expand((end > 0 ? rest.slice(1, end) : rest.slice(1)).replace(/\\n/g, "\n").replace(/\\"/g, '"'));
10034
+ } else {
10035
+ env[key] = expand(rest.replace(/\s+#.*$/, ""));
10036
+ }
10037
+ }
10038
+ return env;
10039
+ }
10040
+
10041
+ // src/services/transcriptCapture.ts
9823
10042
  var HomeTranscriptReader = class {
9824
10043
  home;
10044
+ claudeConfigDirs;
9825
10045
  constructor(opts = {}) {
9826
10046
  const { homeDir } = opts;
9827
- this.home = homeDir ? () => homeDir : () => process.env.HOME || os3.homedir();
10047
+ this.home = homeDir ? () => homeDir : () => process.env.HOME || os4.homedir();
10048
+ const workspaceEnv = opts.workspaceEnv ?? workspaceEnvOf;
10049
+ this.claudeConfigDirs = async (cwd) => [...new Set([
10050
+ (await workspaceEnv(cwd)).CLAUDE_CONFIG_DIR,
10051
+ homeDir ? void 0 : process.env.CLAUDE_CONFIG_DIR,
10052
+ path11.join(this.home(), ".claude")
10053
+ ].filter((dir) => Boolean(dir)))];
9828
10054
  }
9829
10055
  async finalAssistantText(query, maxChars = 4e3) {
9830
10056
  try {
9831
- if (query.runner === "claude-code") return claudeFinal(this.home(), query, maxChars);
10057
+ if (query.runner === "claude-code") return claudeFinal(await this.claudeConfigDirs(query.cwd), query, maxChars);
9832
10058
  if (query.runner === "opencode") return await opencodeFinal(this.home(), query, maxChars);
9833
10059
  if (query.runner === "codex") return codexFinal(this.home(), query, maxChars);
9834
10060
  } catch {
@@ -9837,20 +10063,20 @@ var HomeTranscriptReader = class {
9837
10063
  }
9838
10064
  };
9839
10065
  var CLAUDE_PROJECT_NAME_MAX = 200;
9840
- function claudeProjectDirs(home, cwd) {
9841
- const projects = path10.join(home, ".claude", "projects");
10066
+ function claudeProjectDirs(configDir2, cwd) {
10067
+ const projects = path11.join(configDir2, "projects");
9842
10068
  const munged = cwd.replace(/[^a-zA-Z0-9]/g, "-");
9843
10069
  if (munged.length <= CLAUDE_PROJECT_NAME_MAX) {
9844
- const dir = path10.join(projects, munged);
10070
+ const dir = path11.join(projects, munged);
9845
10071
  return (0, import_fs.existsSync)(dir) ? [dir] : [];
9846
10072
  }
9847
10073
  if (!(0, import_fs.existsSync)(projects)) return [];
9848
10074
  const prefix = `${munged.slice(0, CLAUDE_PROJECT_NAME_MAX)}-`;
9849
- return (0, import_fs.readdirSync)(projects).filter((name) => name.startsWith(prefix)).map((name) => path10.join(projects, name));
10075
+ return (0, import_fs.readdirSync)(projects).filter((name) => name.startsWith(prefix)).map((name) => path11.join(projects, name));
9850
10076
  }
9851
- function claudeFinal(home, query, maxChars) {
10077
+ function claudeFinal(configDirs, query, maxChars) {
9852
10078
  const cutoff = query.startedAt ? Date.parse(query.startedAt) : 0;
9853
- const candidates = claudeProjectDirs(home, query.cwd).flatMap((dir) => (0, import_fs.readdirSync)(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path10.join(dir, f))).filter((f) => cutoff ? (0, import_fs.statSync)(f).mtimeMs >= cutoff - 5e3 : true).sort((a, b) => (0, import_fs.statSync)(b).mtimeMs - (0, import_fs.statSync)(a).mtimeMs);
10079
+ const candidates = configDirs.flatMap((dir) => claudeProjectDirs(dir, query.cwd)).flatMap((dir) => (0, import_fs.readdirSync)(dir).filter((f) => f.endsWith(".jsonl")).map((f) => path11.join(dir, f))).filter((f) => cutoff ? (0, import_fs.statSync)(f).mtimeMs >= cutoff - 5e3 : true).sort((a, b) => (0, import_fs.statSync)(b).mtimeMs - (0, import_fs.statSync)(a).mtimeMs);
9854
10080
  for (const file of candidates) {
9855
10081
  const raw = (0, import_fs.readFileSync)(file, "utf8");
9856
10082
  if (!raw.includes(query.marker)) continue;
@@ -9886,7 +10112,7 @@ async function opencodeFinal(home, query, maxChars) {
9886
10112
  } catch {
9887
10113
  return null;
9888
10114
  }
9889
- const dbPath = path10.join(home, ".local", "share", "opencode", "opencode.db");
10115
+ const dbPath = path11.join(home, ".local", "share", "opencode", "opencode.db");
9890
10116
  if (!(0, import_fs.existsSync)(dbPath)) return null;
9891
10117
  let db;
9892
10118
  try {
@@ -9939,7 +10165,7 @@ async function opencodeFinal(home, query, maxChars) {
9939
10165
  }
9940
10166
  }
9941
10167
  function codexFinal(home, query, maxChars) {
9942
- const root = path10.join(home, ".codex", "sessions");
10168
+ const root = path11.join(home, ".codex", "sessions");
9943
10169
  if (!(0, import_fs.existsSync)(root)) return null;
9944
10170
  const cutoff = query.startedAt ? Date.parse(query.startedAt) : 0;
9945
10171
  const files = [];
@@ -9953,7 +10179,7 @@ function codexFinal(home, query, maxChars) {
9953
10179
  continue;
9954
10180
  }
9955
10181
  for (const e of entries) {
9956
- const full = path10.join(dir, e);
10182
+ const full = path11.join(dir, e);
9957
10183
  let st;
9958
10184
  try {
9959
10185
  st = (0, import_fs.statSync)(full);
@@ -10404,6 +10630,43 @@ ${taskB.description}`,
10404
10630
  }
10405
10631
  };
10406
10632
 
10633
+ // src/utils/shell.ts
10634
+ var ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]|\x1b\][^\x07]*\x07|\x1b[()][AB012]|\r/g;
10635
+ function stripAnsi(text) {
10636
+ return text.replace(ANSI_RE, "");
10637
+ }
10638
+ function posixShellQuote(s) {
10639
+ return `'${s.replace(/'/g, `'\\''`)}'`;
10640
+ }
10641
+ function buildShellInvocation(command, args) {
10642
+ const inner = [command, ...args].map(posixShellQuote).join(" ");
10643
+ return { shellPath: "/bin/bash", shellArgs: ["-lc", inner] };
10644
+ }
10645
+ function wrapWithPty(command, args, opts = {}) {
10646
+ const inner = [command, ...args].map(posixShellQuote).join(" ");
10647
+ const watcher = opts.controlChannel ? 'exec 4<&0; ( while read -r C R <&3; do stty cols "$C" rows "$R" <&4; done ) & ' : "";
10648
+ const setup = opts.size ? `stty cols ${opts.size.cols} rows ${opts.size.rows}; ` : "";
10649
+ return { command: "script", args: ["-q", "-e", "-f", "-c", `${watcher}${setup}${inner}`, "/dev/null"] };
10650
+ }
10651
+
10652
+ // src/services/blockingPrompts.ts
10653
+ var TAIL = 4096;
10654
+ var squash = (text) => stripAnsi(text).replace(/\s+/g, "").toLowerCase();
10655
+ function watchBlockingPrompts(session, prompts, onPrompt) {
10656
+ if (prompts.length === 0) return;
10657
+ const pending = prompts.map((prompt) => ({ prompt, phrase: squash(prompt.phrase) }));
10658
+ let tail = "";
10659
+ session.onOutput((text) => {
10660
+ if (pending.length === 0) return;
10661
+ tail = (tail + squash(text)).slice(-TAIL);
10662
+ for (let i = pending.length - 1; i >= 0; i--) {
10663
+ if (!tail.includes(pending[i].phrase)) continue;
10664
+ onPrompt(pending[i].prompt);
10665
+ pending.splice(i, 1);
10666
+ }
10667
+ });
10668
+ }
10669
+
10407
10670
  // src/services/TaskOrchestrator.ts
10408
10671
  var SHARED_ROOT_TAIL = "tasks run in the workspace root without worktree isolation.";
10409
10672
  function sharedRootNotice(reason, repos) {
@@ -10467,6 +10730,25 @@ var TaskOrchestrator = class {
10467
10730
  */
10468
10731
  retryCounts = /* @__PURE__ */ new Map();
10469
10732
  spawnCounts = /* @__PURE__ */ new Map();
10733
+ /**
10734
+ * The terminal a verdict left open, by task. It stays so the user can read
10735
+ * the agent's output or keep talking to it — but only while its worktree
10736
+ * does: once that is removed the agent sits in a deleted directory, and a
10737
+ * newer attempt in the same worktree would share it with a second agent.
10738
+ * Without this, every task of every run left one agent process running
10739
+ * until the daemon stopped.
10740
+ */
10741
+ lingering = /* @__PURE__ */ new Map();
10742
+ /**
10743
+ * A full-plan run a failure paused. Retrying the failed task is the explicit
10744
+ * resume the pause waits for — without this a retry only reset the task to
10745
+ * pending and nothing ran until the user also re-ran the whole plan.
10746
+ */
10747
+ haltedByFailure = false;
10748
+ /** The workspace's own variables for a task's cwd (ADR-0016); swapped out in tests. */
10749
+ workspaceEnv = (cwd) => resolveWorkspaceEnv(cwd);
10750
+ /** What the workspace env has already warned about, so a run says it once, not per task. */
10751
+ envWarnings = /* @__PURE__ */ new Set();
10470
10752
  /**
10471
10753
  * Tasks pulled out of auto-scheduling (user-cancelled or failed to spawn).
10472
10754
  * They stay 'pending' — "not executed" — but the scheduler skips them until
@@ -10510,6 +10792,32 @@ var TaskOrchestrator = class {
10510
10792
  setWorkspaceRoot(fn) {
10511
10793
  this.workspaceRootFn = fn;
10512
10794
  }
10795
+ setWorkspaceEnvResolver(resolve7) {
10796
+ this.workspaceEnv = resolve7;
10797
+ }
10798
+ /**
10799
+ * The variables a task's agent gets from its workspace. What cannot be
10800
+ * applied is said once — silently starting without them is how agents ran
10801
+ * under the wrong account when an edited `.envrc` was left unallowed.
10802
+ */
10803
+ async envForTask(cwd) {
10804
+ const resolved = await this.workspaceEnv(cwd);
10805
+ const warn = (key, message) => {
10806
+ if (this.envWarnings.has(key)) return;
10807
+ this.envWarnings.add(key);
10808
+ this.notifications.warn(message);
10809
+ };
10810
+ if (resolved.blockedEnvrc) {
10811
+ warn(`blocked:${resolved.blockedEnvrc}`, `direnv has blocked ${resolved.blockedEnvrc}, so tasks start without its variables. Run \`direnv allow\` in ${path12.dirname(resolved.blockedEnvrc)} to use them.`);
10812
+ }
10813
+ if (resolved.trackedEnvFile) {
10814
+ 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.`);
10815
+ }
10816
+ if (resolved.refused.length > 0) {
10817
+ warn(`refused:${resolved.refused.join(",")}`, `Ignored ${resolved.refused.join(", ")} from the workspace environment: Ordewell never passes these to agents.`);
10818
+ }
10819
+ return resolved.env;
10820
+ }
10513
10821
  setRegistry(registry) {
10514
10822
  this.registry = registry;
10515
10823
  }
@@ -10654,6 +10962,7 @@ var TaskOrchestrator = class {
10654
10962
  return result;
10655
10963
  }
10656
10964
  async clearMergedRun(run) {
10965
+ this.closeLingeringOf(run);
10657
10966
  try {
10658
10967
  await this.isolation.discard(run, { integration: "delete-merged" });
10659
10968
  } catch (err) {
@@ -10664,14 +10973,32 @@ var TaskOrchestrator = class {
10664
10973
  }
10665
10974
  /** Worktrees and task branches go; the integration branch and the record stay for review and merge. */
10666
10975
  async cleanupRun() {
10976
+ this.closeLingeringOf(this.requireRun());
10667
10977
  await this.isolation.discard(this.requireRun(), { integration: "keep" });
10668
10978
  this.emit("onIsolationChanged");
10669
10979
  }
10670
10980
  /** The run and everything it made go, and the plan forgets it; the next run starts afresh. */
10671
10981
  async discardRun() {
10982
+ this.closeLingeringOf(this.requireRun());
10672
10983
  await this.isolation.discard(this.requireRun(), { integration: "delete" });
10673
10984
  this.forgetRun();
10674
10985
  }
10986
+ watchBlockingPrompts(task, attempt, session) {
10987
+ const manifest = this.registry?.get(attempt.runner)?.manifest;
10988
+ watchBlockingPrompts(session, manifest?.runner.blockingPrompts ?? [], (prompt) => {
10989
+ if (this.attempts.get(task.id) !== attempt) return;
10990
+ 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.`);
10991
+ });
10992
+ }
10993
+ closeLingering(taskId) {
10994
+ const sessionId = this.lingering.get(taskId);
10995
+ if (sessionId === void 0) return;
10996
+ this.lingering.delete(taskId);
10997
+ this.terminalRunner.stop(sessionId);
10998
+ }
10999
+ closeLingeringOf(run) {
11000
+ for (const taskId of Object.keys(run.tasks)) this.closeLingering(taskId);
11001
+ }
10675
11002
  forgetRun() {
10676
11003
  this.isolationRun = null;
10677
11004
  this.resolvers = {};
@@ -10759,11 +11086,18 @@ var TaskOrchestrator = class {
10759
11086
  if (!await this.openRun(() => this.start()) || this.running) return;
10760
11087
  console.log(`[TaskOrchestrator] Starting with ${this.store.allTasks.length} tasks (${this.store.allTasks.filter((t) => t.type === "ai" && t.prompt).length} AI ready)`);
10761
11088
  this.running = true;
11089
+ this.haltedByFailure = false;
10762
11090
  this.planStatus = "running";
10763
11091
  this.emit("onTaskChanged");
10764
11092
  await this.tick();
10765
11093
  }
11094
+ haltOnFailure() {
11095
+ if (this.running) this.haltedByFailure = true;
11096
+ this.running = false;
11097
+ this.planStatus = "approved";
11098
+ }
10766
11099
  stop() {
11100
+ this.haltedByFailure = false;
10767
11101
  this.running = false;
10768
11102
  this.planStatus = "approved";
10769
11103
  this.terminalRunner.stopAll();
@@ -10800,8 +11134,7 @@ ${summary || "(empty \u2014 no output captured)"}`);
10800
11134
  await this.landPassed(task, landing);
10801
11135
  } else {
10802
11136
  this.store.markFailed(taskId);
10803
- this.running = false;
10804
- this.planStatus = "approved";
11137
+ this.haltOnFailure();
10805
11138
  this.notifications.error(`Task "${task.title}" failed verification: ${verdict.reason}`);
10806
11139
  if (attempt.worktree) await this.releaseWorktree(taskId, { keep: true });
10807
11140
  }
@@ -10941,8 +11274,7 @@ ${summary || "(empty \u2014 no output captured)"}`);
10941
11274
  if (whyNot) this.tell("info", whyNot);
10942
11275
  } else {
10943
11276
  this.store.markFailed(task.id);
10944
- this.running = false;
10945
- this.planStatus = "approved";
11277
+ this.haltOnFailure();
10946
11278
  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.`);
10947
11279
  }
10948
11280
  }
@@ -10993,6 +11325,7 @@ ${summary || "(empty \u2014 no output captured)"}`);
10993
11325
  await integration;
10994
11326
  const run = this.isolationRun;
10995
11327
  if (!run?.tasks[taskId]) return;
11328
+ if (!opts.keep) this.closeLingering(taskId);
10996
11329
  try {
10997
11330
  await this.isolation.release(run, taskId, opts);
10998
11331
  } catch (err) {
@@ -11141,7 +11474,8 @@ ${summary || "(empty \u2014 no output captured)"}`);
11141
11474
  this.onHold.delete(taskId);
11142
11475
  this.emit("onTaskChanged");
11143
11476
  await this.releaseWorktree(taskId, { keep: false }, ended?.integration);
11144
- await this.tick();
11477
+ if (this.haltedByFailure && !this.running) await this.start();
11478
+ else await this.tick();
11145
11479
  }
11146
11480
  /**
11147
11481
  * Manually start a single AI task right now, bypassing dependency/readiness
@@ -11267,6 +11601,8 @@ ${summary || "(empty \u2014 no output captured)"}`);
11267
11601
  // A merge to resolve is not new behaviour to drive test-first.
11268
11602
  tddEnabled: !attempt.repair && this.tddEnabled()
11269
11603
  });
11604
+ this.closeLingering(task.id);
11605
+ const env = await this.envForTask(cwd);
11270
11606
  const session = await this.terminalRunner.spawn({
11271
11607
  taskId: task.id,
11272
11608
  runner: attempt.runner,
@@ -11278,13 +11614,15 @@ ${summary || "(empty \u2014 no output captured)"}`);
11278
11614
  cwd,
11279
11615
  registry: this.registry ?? void 0,
11280
11616
  order: task.order,
11281
- title: task.title
11617
+ title: task.title,
11618
+ env
11282
11619
  });
11283
11620
  if (this.attempts.get(task.id) !== attempt) return this.abandonSpawn(task, attempt, session);
11284
11621
  attempt.phase = "running";
11285
11622
  attempt.session = session;
11286
11623
  this.output.attach(task.id, session);
11287
11624
  this.verifier.watch(task, session);
11625
+ this.watchBlockingPrompts(task, attempt, session);
11288
11626
  this.emit("onTaskChanged");
11289
11627
  if (attempt.repair) {
11290
11628
  const group = this.isolationRun?.repos.some((r) => r.path !== SELF_REPO);
@@ -11517,6 +11855,7 @@ ${summary || "(empty \u2014 no output captured)"}`);
11517
11855
  const attempt = this.attempts.get(taskId);
11518
11856
  this.attempts.delete(taskId);
11519
11857
  if (attempt) this.output.detach(taskId);
11858
+ if (reason === "verdict" && attempt?.session) this.lingering.set(taskId, attempt.session.id);
11520
11859
  if (reason === "cancel" || reason === "release" || reason === "complete" || reason === "retry") {
11521
11860
  const task = this.store.get(taskId);
11522
11861
  if (task) this.verifier.clear(task);
@@ -11659,29 +11998,29 @@ ${errors.map((e) => `- ${e}`).join("\n")}`
11659
11998
  };
11660
11999
 
11661
12000
  // src/services/ModelDiscovery.ts
11662
- var path13 = __toESM(require("path"));
11663
- var import_child_process4 = require("child_process");
12001
+ var path15 = __toESM(require("path"));
12002
+ var import_child_process5 = require("child_process");
11664
12003
  var import_util3 = require("util");
11665
12004
  var fsSync3 = __toESM(require("fs"));
11666
12005
  var osMod = __toESM(require("os"));
11667
12006
 
11668
12007
  // src/utils/globalDataDir.ts
11669
- var fs5 = __toESM(require("fs"));
11670
- var path11 = __toESM(require("path"));
11671
- var os4 = __toESM(require("os"));
12008
+ var fs6 = __toESM(require("fs"));
12009
+ var path13 = __toESM(require("path"));
12010
+ var os5 = __toESM(require("os"));
11672
12011
  function globalDataDir() {
11673
- return path11.join(os4.homedir(), ".ordewell");
12012
+ return path13.join(os5.homedir(), ".ordewell");
11674
12013
  }
11675
12014
  function copyDirSync(src, dest) {
11676
- fs5.mkdirSync(dest, { recursive: true });
11677
- const entries = fs5.readdirSync(src, { withFileTypes: true });
12015
+ fs6.mkdirSync(dest, { recursive: true });
12016
+ const entries = fs6.readdirSync(src, { withFileTypes: true });
11678
12017
  for (const entry of entries) {
11679
- const srcPath = path11.join(src, entry.name);
11680
- const destPath = path11.join(dest, entry.name);
12018
+ const srcPath = path13.join(src, entry.name);
12019
+ const destPath = path13.join(dest, entry.name);
11681
12020
  if (entry.isDirectory()) {
11682
12021
  copyDirSync(srcPath, destPath);
11683
12022
  } else {
11684
- fs5.copyFileSync(srcPath, destPath);
12023
+ fs6.copyFileSync(srcPath, destPath);
11685
12024
  }
11686
12025
  }
11687
12026
  }
@@ -11689,50 +12028,29 @@ var migrated = false;
11689
12028
  function migrateOldConfigDir() {
11690
12029
  if (migrated) return;
11691
12030
  migrated = true;
11692
- const oldDir = path11.join(os4.homedir(), ".config", "ordewell");
11693
- if (!fs5.existsSync(oldDir)) return;
12031
+ const oldDir = path13.join(os5.homedir(), ".config", "ordewell");
12032
+ if (!fs6.existsSync(oldDir)) return;
11694
12033
  const newDir = globalDataDir();
11695
12034
  const liftFile = (name) => {
11696
- const oldFile = path11.join(oldDir, name);
11697
- const newFile = path11.join(newDir, name);
11698
- if (fs5.existsSync(oldFile) && !fs5.existsSync(newFile)) {
11699
- fs5.mkdirSync(newDir, { recursive: true });
11700
- fs5.copyFileSync(oldFile, newFile);
12035
+ const oldFile = path13.join(oldDir, name);
12036
+ const newFile = path13.join(newDir, name);
12037
+ if (fs6.existsSync(oldFile) && !fs6.existsSync(newFile)) {
12038
+ fs6.mkdirSync(newDir, { recursive: true });
12039
+ fs6.copyFileSync(oldFile, newFile);
11701
12040
  }
11702
12041
  };
11703
12042
  liftFile("settings.json");
11704
12043
  liftFile(".env");
11705
- const oldPlugins = path11.join(oldDir, "plugins");
11706
- const newPlugins = path11.join(newDir, "plugins");
11707
- if (fs5.existsSync(oldPlugins) && !fs5.existsSync(newPlugins)) {
12044
+ const oldPlugins = path13.join(oldDir, "plugins");
12045
+ const newPlugins = path13.join(newDir, "plugins");
12046
+ if (fs6.existsSync(oldPlugins) && !fs6.existsSync(newPlugins)) {
11708
12047
  copyDirSync(oldPlugins, newPlugins);
11709
12048
  }
11710
12049
  }
11711
12050
 
11712
12051
  // src/utils/launch.ts
11713
12052
  var fsSync2 = __toESM(require("fs"));
11714
- var path12 = __toESM(require("path"));
11715
-
11716
- // src/utils/shell.ts
11717
- var ANSI_RE = /\x1b\[[0-9;]*[A-Za-z]|\x1b\][^\x07]*\x07|\x1b[()][AB012]|\r/g;
11718
- function stripAnsi(text) {
11719
- return text.replace(ANSI_RE, "");
11720
- }
11721
- function posixShellQuote(s) {
11722
- return `'${s.replace(/'/g, `'\\''`)}'`;
11723
- }
11724
- function buildShellInvocation(command, args) {
11725
- const inner = [command, ...args].map(posixShellQuote).join(" ");
11726
- return { shellPath: "/bin/bash", shellArgs: ["-lc", inner] };
11727
- }
11728
- function wrapWithPty(command, args, opts = {}) {
11729
- const inner = [command, ...args].map(posixShellQuote).join(" ");
11730
- const watcher = opts.controlChannel ? 'exec 4<&0; ( while read -r C R <&3; do stty cols "$C" rows "$R" <&4; done ) & ' : "";
11731
- const setup = opts.size ? `stty cols ${opts.size.cols} rows ${opts.size.rows}; ` : "";
11732
- return { command: "script", args: ["-q", "-e", "-f", "-c", `${watcher}${setup}${inner}`, "/dev/null"] };
11733
- }
11734
-
11735
- // src/utils/launch.ts
12053
+ var path14 = __toESM(require("path"));
11736
12054
  var DIRECT_EXTENSIONS = [".exe", ".com"];
11737
12055
  var BATCH_EXTENSIONS = [".cmd", ".bat"];
11738
12056
  var POWERSHELL_EXTENSIONS = [".ps1"];
@@ -11740,7 +12058,7 @@ var CMD_EXE_MAX_COMMAND_LINE = 8191;
11740
12058
  var WINDOWS_MAX_COMMAND_LINE = 32767;
11741
12059
  function defaultPowerShell() {
11742
12060
  const root = process.env.SystemRoot || process.env.windir;
11743
- return root ? path12.win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe") : "powershell.exe";
12061
+ return root ? path14.win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe") : "powershell.exe";
11744
12062
  }
11745
12063
  var POWERSHELL_FLAGS = ["-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File"];
11746
12064
  var CommandLineTooLongError = class extends Error {
@@ -11792,7 +12110,7 @@ Searched: ${searchedPath || "(empty PATH)"}`);
11792
12110
  };
11793
12111
  function isResolvableOnPosixPath(command, PATH, exists) {
11794
12112
  if (command.includes("/")) return exists(command);
11795
- return PATH.split(path12.delimiter).map((d) => d.trim()).filter(Boolean).some((dir) => exists(path12.join(dir, command)));
12113
+ return PATH.split(path14.delimiter).map((d) => d.trim()).filter(Boolean).some((dir) => exists(path14.join(dir, command)));
11796
12114
  }
11797
12115
  function isExecutableResolved(command, plan, PATH, deps = {}) {
11798
12116
  if ((deps.platform ?? process.platform) === "win32") return plan.file !== command;
@@ -11832,9 +12150,9 @@ function cmdArgs(line) {
11832
12150
  return ["/d", "/s", "/c", `"${line}"`];
11833
12151
  }
11834
12152
  function candidateExtensions(command, pathExt) {
11835
- const declared = pathExt.split(path12.win32.delimiter).map((e) => e.trim().toLowerCase()).filter(Boolean);
12153
+ const declared = pathExt.split(path14.win32.delimiter).map((e) => e.trim().toLowerCase()).filter(Boolean);
11836
12154
  const known = /* @__PURE__ */ new Set([...DIRECT_EXTENSIONS, ...BATCH_EXTENSIONS, ...POWERSHELL_EXTENSIONS, ...declared]);
11837
- const ext = path12.win32.extname(command).toLowerCase();
12155
+ const ext = path14.win32.extname(command).toLowerCase();
11838
12156
  if (ext && known.has(ext)) {
11839
12157
  const route = BATCH_EXTENSIONS.includes(ext) ? "cmd" : POWERSHELL_EXTENSIONS.includes(ext) ? "powershell" : "direct";
11840
12158
  return { direct: [], cmd: [], powershell: [], [route]: [""] };
@@ -11855,13 +12173,13 @@ async function findWindowsExecutables(command, deps) {
11855
12173
  const exists = deps.exists ?? defaultExists2;
11856
12174
  const pathExt = (deps.pathExt ?? (() => process.env.PATHEXT ?? ""))();
11857
12175
  const byRoute = candidateExtensions(command, pathExt);
11858
- const dirs = path12.win32.basename(command) !== command ? [path12.win32.dirname(path12.win32.resolve(command))] : (await (deps.resolvePath ?? augmentedPath)() || "").split(path12.win32.delimiter).map((d) => d.trim().replace(/^"|"$/g, "")).filter(Boolean);
11859
- const base = path12.win32.basename(command);
12176
+ const dirs = path14.win32.basename(command) !== command ? [path14.win32.dirname(path14.win32.resolve(command))] : (await (deps.resolvePath ?? augmentedPath)() || "").split(path14.win32.delimiter).map((d) => d.trim().replace(/^"|"$/g, "")).filter(Boolean);
12177
+ const base = path14.win32.basename(command);
11860
12178
  const found = [];
11861
12179
  for (const route of ROUTES) {
11862
12180
  outer: for (const ext of byRoute[route]) {
11863
12181
  for (const dir of dirs) {
11864
- const candidate = path12.win32.join(dir, `${base}${ext}`);
12182
+ const candidate = path14.win32.join(dir, `${base}${ext}`);
11865
12183
  if (exists(candidate)) {
11866
12184
  found.push({ file: candidate, route });
11867
12185
  break outer;
@@ -11907,10 +12225,10 @@ async function planShellLaunch(command, args, deps = {}) {
11907
12225
  }
11908
12226
 
11909
12227
  // src/utils/processTree.ts
11910
- var import_child_process3 = require("child_process");
12228
+ var import_child_process4 = require("child_process");
11911
12229
  var HARD_KILL_DELAY_MS = 5e3;
11912
12230
  function defaultExecFile2(file, args, cb) {
11913
- (0, import_child_process3.execFile)(file, args, (err) => cb(err ?? null));
12231
+ (0, import_child_process4.execFile)(file, args, (err) => cb(err ?? null));
11914
12232
  }
11915
12233
  function killTree(proc, deps = {}) {
11916
12234
  if (!proc || proc.killed || typeof proc.exitCode === "number") return;
@@ -11946,7 +12264,7 @@ function killTree(proc, deps = {}) {
11946
12264
  }
11947
12265
 
11948
12266
  // src/services/ModelDiscovery.ts
11949
- var execAsync3 = (0, import_util3.promisify)(import_child_process4.exec);
12267
+ var execAsync3 = (0, import_util3.promisify)(import_child_process5.exec);
11950
12268
  var defaultExec2 = async (command, options) => {
11951
12269
  const PATH = await augmentedPath();
11952
12270
  const { stdout } = await execAsync3(command, { ...options, maxBuffer: 32 * 1024 * 1024, env: withPath(process.env, PATH) });
@@ -11956,16 +12274,16 @@ var defaultAppServerClient = async (command, args, options) => {
11956
12274
  const PATH = await augmentedPath();
11957
12275
  const launch = await planDirectLaunch(command, args).catch(() => null);
11958
12276
  if (!launch) return null;
11959
- return new Promise((resolve6) => {
12277
+ return new Promise((resolve7) => {
11960
12278
  let child;
11961
12279
  try {
11962
- child = (0, import_child_process4.spawn)(launch.file, launch.args, {
12280
+ child = (0, import_child_process5.spawn)(launch.file, launch.args, {
11963
12281
  env: withPath(process.env, PATH),
11964
12282
  stdio: ["pipe", "pipe", "ignore"],
11965
12283
  windowsVerbatimArguments: launch.verbatim
11966
12284
  });
11967
12285
  } catch {
11968
- resolve6(null);
12286
+ resolve7(null);
11969
12287
  return;
11970
12288
  }
11971
12289
  let settled = false;
@@ -11974,7 +12292,7 @@ var defaultAppServerClient = async (command, args, options) => {
11974
12292
  settled = true;
11975
12293
  clearTimeout(timer);
11976
12294
  killTree(child);
11977
- resolve6(value);
12295
+ resolve7(value);
11978
12296
  };
11979
12297
  const timer = setTimeout(() => finish(null), options?.timeout ?? 2e4);
11980
12298
  child.on("error", () => finish(null));
@@ -12041,7 +12359,7 @@ function geminiDiscoveryCacheKey(apiKey) {
12041
12359
  return {
12042
12360
  key: `gemini-models:${hash}`,
12043
12361
  dir,
12044
- file: path13.join(dir, `gemini_models_${hash}.json`)
12362
+ file: path15.join(dir, `gemini_models_${hash}.json`)
12045
12363
  };
12046
12364
  }
12047
12365
  function readGeminiCache(apiKey) {
@@ -12564,8 +12882,8 @@ var ModelDiscovery = class {
12564
12882
  };
12565
12883
 
12566
12884
  // src/services/ModelCatalog.ts
12567
- var fs6 = __toESM(require("fs"));
12568
- var path14 = __toESM(require("path"));
12885
+ var fs7 = __toESM(require("fs"));
12886
+ var path16 = __toESM(require("path"));
12569
12887
  var CACHE_KEY = "model-catalog";
12570
12888
  var CACHE_TTL_MS = 60 * 60 * 1e3;
12571
12889
  function storageKey(baseUrl) {
@@ -12617,17 +12935,17 @@ var ModelCatalog = class {
12617
12935
  const url = (baseUrl || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
12618
12936
  const key = storageKey(url);
12619
12937
  const cacheDir = globalCacheDir();
12620
- const cacheFile = path14.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
12621
- if (fs6.existsSync(cacheFile)) fs6.rmSync(cacheFile);
12938
+ const cacheFile = path16.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
12939
+ if (fs7.existsSync(cacheFile)) fs7.rmSync(cacheFile);
12622
12940
  } catch {
12623
12941
  }
12624
12942
  }
12625
12943
  static readCache(key) {
12626
12944
  try {
12627
12945
  const cacheDir = globalCacheDir();
12628
- const cacheFile = path14.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
12629
- if (!fs6.existsSync(cacheFile)) return null;
12630
- const raw = fs6.readFileSync(cacheFile, "utf8");
12946
+ const cacheFile = path16.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
12947
+ if (!fs7.existsSync(cacheFile)) return null;
12948
+ const raw = fs7.readFileSync(cacheFile, "utf8");
12631
12949
  const data = JSON.parse(raw);
12632
12950
  if (Date.now() - data.cachedAt > CACHE_TTL_MS) return null;
12633
12951
  return JSON.stringify(data.models);
@@ -12638,9 +12956,9 @@ var ModelCatalog = class {
12638
12956
  static writeCache(key, models) {
12639
12957
  try {
12640
12958
  const cacheDir = globalCacheDir();
12641
- fs6.mkdirSync(cacheDir, { recursive: true });
12642
- const cacheFile = path14.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
12643
- fs6.writeFileSync(
12959
+ fs7.mkdirSync(cacheDir, { recursive: true });
12960
+ const cacheFile = path16.join(cacheDir, `${key.replace(/[:/]/g, "_")}.json`);
12961
+ fs7.writeFileSync(
12644
12962
  cacheFile,
12645
12963
  JSON.stringify({ models, cachedAt: Date.now() }, null, 2)
12646
12964
  );
@@ -12907,9 +13225,9 @@ var ModelResolver = class {
12907
13225
  };
12908
13226
 
12909
13227
  // src/services/RunnerInstallation.ts
12910
- var import_child_process5 = require("child_process");
13228
+ var import_child_process6 = require("child_process");
12911
13229
  var import_util4 = require("util");
12912
- var execAsync4 = (0, import_util4.promisify)(import_child_process5.exec);
13230
+ var execAsync4 = (0, import_util4.promisify)(import_child_process6.exec);
12913
13231
  var defaultExec3 = async (command, options) => {
12914
13232
  const PATH = await augmentedPath();
12915
13233
  const { stdout } = await execAsync4(command, { ...options, env: withPath(process.env, PATH) });
@@ -13016,72 +13334,12 @@ var RunnerInstallation = class {
13016
13334
  }
13017
13335
  };
13018
13336
 
13019
- // src/services/settingsEnvAllowlist.ts
13020
- var ORDEWELL_SETTABLE_ENV = [
13021
- "AI_PROVIDER",
13022
- "ORCHESTRATOR_MODEL",
13023
- "ORDEWELL_SUBAGENT_MODEL",
13024
- "ORDEWELL_PLANNER_EFFORT",
13025
- "ORDEWELL_RESEARCH_ENABLED",
13026
- "ORDEWELL_MAX_PARALLEL",
13027
- "ORDEWELL_CONFLICT_REPAIR_ATTEMPTS"
13028
- ];
13029
- var SETTINGS_ENV_REFUSED = [
13030
- // Approval posture and pre-approved scope.
13031
- "ORDEWELL_AUTONOMOUS_MODE",
13032
- "ORDEWELL_APPROVAL_MODE",
13033
- "ORDEWELL_APPROVAL_ALLOW",
13034
- // Where settings are persisted.
13035
- "ORDEWELL_SETTINGS_PATH",
13036
- // Runtime options and dynamic loaders.
13037
- "NODE_OPTIONS",
13038
- "NODE_REPL_EXTERNAL_MODULE",
13039
- "NODE_PATH",
13040
- "ELECTRON_RUN_AS_NODE",
13041
- "LD_PRELOAD",
13042
- "LD_LIBRARY_PATH",
13043
- "LD_AUDIT",
13044
- "DYLD_INSERT_LIBRARIES",
13045
- "DYLD_LIBRARY_PATH",
13046
- "DYLD_FRAMEWORK_PATH",
13047
- // Executable resolution.
13048
- "PATH",
13049
- "PATHEXT",
13050
- "NODE"
13051
- ];
13052
- var REFUSED = new Set(SETTINGS_ENV_REFUSED);
13053
- function providerEnvKeys() {
13054
- const keys = [];
13055
- for (const meta of Object.values(ALL_PROVIDERS)) {
13056
- if (meta.apiKeyEnvVar) keys.push(meta.apiKeyEnvVar);
13057
- if (meta.baseUrlEnvVar) keys.push(meta.baseUrlEnvVar);
13058
- for (const detect of meta.detectEnvVars) keys.push(detect);
13059
- }
13060
- return keys;
13061
- }
13062
- var PROVIDER_CREDENTIAL_ENV = new Set(providerEnvKeys());
13063
- var SETTINGS_ENV_ALLOWLIST = new Set(
13064
- [...providerEnvKeys(), ...ORDEWELL_SETTABLE_ENV].filter((key) => !REFUSED.has(key))
13065
- );
13066
- function admitSettingsEnv(env) {
13067
- const accepted = {};
13068
- const rejected = [];
13069
- for (const [key, value] of Object.entries(env)) {
13070
- if (REFUSED.has(key) || !SETTINGS_ENV_ALLOWLIST.has(key) || typeof value !== "string") {
13071
- rejected.push(key);
13072
- continue;
13073
- }
13074
- accepted[key] = value;
13075
- }
13076
- return { accepted, rejected };
13077
- }
13078
-
13079
13337
  // src/services/harness/CliAgentAiService.ts
13080
- var import_child_process6 = require("child_process");
13338
+ var import_child_process7 = require("child_process");
13081
13339
 
13082
13340
  // src/utils/workspace.ts
13083
- var fs7 = __toESM(require("fs"));
13084
- var path15 = __toESM(require("path"));
13341
+ var fs8 = __toESM(require("fs"));
13342
+ var path17 = __toESM(require("path"));
13085
13343
  var WorkspaceNotFoundError = class extends Error {
13086
13344
  constructor(workspace) {
13087
13345
  super(`Workspace "${workspace}" does not exist.`);
@@ -13092,7 +13350,7 @@ var WorkspaceNotFoundError = class extends Error {
13092
13350
  };
13093
13351
  function defaultIsDirectory(candidate) {
13094
13352
  try {
13095
- return fs7.statSync(candidate).isDirectory();
13353
+ return fs8.statSync(candidate).isDirectory();
13096
13354
  } catch {
13097
13355
  return false;
13098
13356
  }
@@ -13128,8 +13386,8 @@ var PROJECT_MARKER_FILES = [
13128
13386
  "CMakeLists.txt"
13129
13387
  ];
13130
13388
  function assertWorkspaceIsProject(workspace, deps = {}) {
13131
- const realpath = deps.realpath ?? ((candidate) => fs7.realpathSync(candidate));
13132
- const exists = deps.exists ?? ((candidate) => fs7.existsSync(candidate));
13389
+ const realpath = deps.realpath ?? ((candidate) => fs8.realpathSync(candidate));
13390
+ const exists = deps.exists ?? ((candidate) => fs8.existsSync(candidate));
13133
13391
  let resolved;
13134
13392
  try {
13135
13393
  resolved = realpath(workspace);
@@ -13137,7 +13395,7 @@ function assertWorkspaceIsProject(workspace, deps = {}) {
13137
13395
  throw new WorkspaceNotAProjectError(workspace);
13138
13396
  }
13139
13397
  const hasMarker = [...PROJECT_MARKER_DIRS, ...PROJECT_MARKER_FILES].some(
13140
- (marker2) => exists(path15.join(resolved, marker2))
13398
+ (marker2) => exists(path17.join(resolved, marker2))
13141
13399
  );
13142
13400
  if (!hasMarker) throw new WorkspaceNotAProjectError(workspace);
13143
13401
  }
@@ -13205,13 +13463,14 @@ var StdioAgentAdapter = class {
13205
13463
  }
13206
13464
  async start(opts) {
13207
13465
  assertWorkspaceExists(opts.cwd, { isDirectory: this.deps.isDirectory });
13208
- this.processEnded = new Promise((resolve6) => {
13209
- this.markEnded = resolve6;
13466
+ this.processEnded = new Promise((resolve7) => {
13467
+ this.markEnded = resolve7;
13210
13468
  });
13211
13469
  const spec = this.spawnSpec(opts);
13212
13470
  const resolvePath = this.deps.resolvePath ?? augmentedPath;
13213
13471
  const PATH = await resolvePath();
13214
- this.spawnEnv = withPath(process.env, PATH, spec.env);
13472
+ const workspace = await (this.deps.workspaceEnv ?? workspaceEnvOf)(opts.cwd);
13473
+ this.spawnEnv = withPath(process.env, PATH, { ...workspace, ...spec.env });
13215
13474
  const launch = await planDirectLaunch(spec.command, spec.args, {
13216
13475
  platform: this.deps.platform,
13217
13476
  resolvePath
@@ -13257,7 +13516,7 @@ ${err.message}`).slice(-STDERR_TAIL_CHARS);
13257
13516
  onEvent({ type: "error", message: this.exitMessage() });
13258
13517
  return;
13259
13518
  }
13260
- await new Promise((resolve6) => {
13519
+ await new Promise((resolve7) => {
13261
13520
  let settled = false;
13262
13521
  const finish = () => {
13263
13522
  if (settled) return;
@@ -13268,7 +13527,7 @@ ${err.message}`).slice(-STDERR_TAIL_CHARS);
13268
13527
  signal?.removeEventListener("abort", onAbort);
13269
13528
  this.process?.removeListener("exit", onExit);
13270
13529
  this.process?.removeListener("error", onExit);
13271
- resolve6();
13530
+ resolve7();
13272
13531
  };
13273
13532
  const emit = (event) => {
13274
13533
  onEvent(event);
@@ -13483,14 +13742,14 @@ function codexSandboxUnavailableMessage() {
13483
13742
  ].join("\n");
13484
13743
  }
13485
13744
  function runProbe(deps, cwd, env, flags) {
13486
- return new Promise((resolve6) => {
13745
+ return new Promise((resolve7) => {
13487
13746
  let output = "";
13488
13747
  let settled = false;
13489
13748
  const finish = (code) => {
13490
13749
  if (settled) return;
13491
13750
  settled = true;
13492
13751
  clearTimeout(timer);
13493
- resolve6({ code, output });
13752
+ resolve7({ code, output });
13494
13753
  };
13495
13754
  const timer = setTimeout(() => finish(null), PROBE_TIMEOUT_MS);
13496
13755
  timer.unref?.();
@@ -13573,8 +13832,8 @@ var CodexAdapter = class extends StdioAgentAdapter {
13573
13832
  async handshake(opts) {
13574
13833
  this.sandbox = await probeCodexSandbox(this.deps, opts.cwd, this.spawnEnv);
13575
13834
  if (this.sandbox === "unavailable") throw new Error(codexSandboxUnavailableMessage());
13576
- const ready = new Promise((resolve6) => {
13577
- this.settleHandshake = resolve6;
13835
+ const ready = new Promise((resolve7) => {
13836
+ this.settleHandshake = resolve7;
13578
13837
  });
13579
13838
  this.writeLine({
13580
13839
  jsonrpc: "2.0",
@@ -13587,8 +13846,8 @@ var CodexAdapter = class extends StdioAgentAdapter {
13587
13846
  // A binary that rejects `app-server` outright dies immediately; waiting
13588
13847
  // out the timeout would turn a one-line diagnostic into a 30s stall.
13589
13848
  this.processEnded.then(() => false),
13590
- new Promise((resolve6) => {
13591
- const t = setTimeout(() => resolve6(false), HANDSHAKE_TIMEOUT_MS);
13849
+ new Promise((resolve7) => {
13850
+ const t = setTimeout(() => resolve7(false), HANDSHAKE_TIMEOUT_MS);
13592
13851
  t.unref?.();
13593
13852
  })
13594
13853
  ]);
@@ -13865,12 +14124,12 @@ function describeError(err) {
13865
14124
  return lines.join(": ");
13866
14125
  }
13867
14126
  function delay(ms, signal) {
13868
- return new Promise((resolve6) => {
13869
- const timer = setTimeout(resolve6, ms);
14127
+ return new Promise((resolve7) => {
14128
+ const timer = setTimeout(resolve7, ms);
13870
14129
  timer.unref?.();
13871
14130
  signal?.addEventListener("abort", () => {
13872
14131
  clearTimeout(timer);
13873
- resolve6();
14132
+ resolve7();
13874
14133
  }, { once: true });
13875
14134
  });
13876
14135
  }
@@ -13904,17 +14163,17 @@ var OpenCodeAdapter = class {
13904
14163
  throw new ExecutableNotFoundError("opencode", PATH);
13905
14164
  }
13906
14165
  this.process = this.deps.spawn(launch.file, launch.args, {
13907
- env: withPath(process.env, PATH),
14166
+ env: withPath(process.env, PATH, await (this.deps.workspaceEnv ?? workspaceEnvOf)(opts.cwd)),
13908
14167
  stdio: ["pipe", "pipe", "pipe"],
13909
14168
  cwd: opts.cwd,
13910
14169
  windowsVerbatimArguments: launch.verbatim
13911
14170
  });
13912
- const banner = new Promise((resolve6) => {
14171
+ const banner = new Promise((resolve7) => {
13913
14172
  let seen = "";
13914
14173
  const scan = (chunk) => {
13915
14174
  seen += chunk.toString();
13916
14175
  const match = seen.match(/https?:\/\/[^\s]+/);
13917
- if (match) resolve6(match[0].replace(/[.,)]$/, ""));
14176
+ if (match) resolve7(match[0].replace(/[.,)]$/, ""));
13918
14177
  };
13919
14178
  this.process.stdout?.on("data", scan);
13920
14179
  this.process.stderr?.on("data", (chunk) => {
@@ -13923,15 +14182,15 @@ var OpenCodeAdapter = class {
13923
14182
  });
13924
14183
  this.process.on("exit", () => {
13925
14184
  this.exited = true;
13926
- resolve6(null);
14185
+ resolve7(null);
13927
14186
  });
13928
14187
  this.process.on("error", (err) => {
13929
14188
  this.exited = true;
13930
14189
  this.stderrTail = (this.stderrTail + `
13931
14190
  ${err.message}`).slice(-STDERR_TAIL_CHARS2);
13932
- resolve6(null);
14191
+ resolve7(null);
13933
14192
  });
13934
- const timer = setTimeout(() => resolve6(null), SERVER_READY_TIMEOUT_MS);
14193
+ const timer = setTimeout(() => resolve7(null), SERVER_READY_TIMEOUT_MS);
13935
14194
  timer.unref?.();
13936
14195
  });
13937
14196
  this.baseUrl = await banner;
@@ -13964,8 +14223,8 @@ ${this.stderrTail.trim()}` : ""}`);
13964
14223
  const streamAbort = new AbortController();
13965
14224
  let connected = () => {
13966
14225
  };
13967
- const streamReady = new Promise((resolve6) => {
13968
- connected = resolve6;
14226
+ const streamReady = new Promise((resolve7) => {
14227
+ connected = resolve7;
13969
14228
  });
13970
14229
  const live = this.streamEvents(
13971
14230
  streamAbort.signal,
@@ -14169,14 +14428,14 @@ ${part.text}` : part.text });
14169
14428
  }
14170
14429
  }
14171
14430
  }
14172
- async json(method, path24, body, signal) {
14173
- const response = await this.deps.fetch(`${this.baseUrl}${path24}`, {
14431
+ async json(method, path26, body, signal) {
14432
+ const response = await this.deps.fetch(`${this.baseUrl}${path26}`, {
14174
14433
  method,
14175
14434
  headers: { "content-type": "application/json" },
14176
14435
  ...body === void 0 ? {} : { body: JSON.stringify(body) },
14177
14436
  ...signal ? { signal } : {}
14178
14437
  });
14179
- if (!response.ok) throw new Error(`${method} ${path24} failed: ${response.status} ${response.statusText}`);
14438
+ if (!response.ok) throw new Error(`${method} ${path26} failed: ${response.status} ${response.statusText}`);
14180
14439
  return await response.json().catch(() => null);
14181
14440
  }
14182
14441
  nativeSessionId() {
@@ -14269,7 +14528,7 @@ var CliAgentAiService = class {
14269
14528
  if (!runner) throw new Error(`${config.aiProvider} is not a coding-agent planner.`);
14270
14529
  this.runner = runner;
14271
14530
  this.processDeps = {
14272
- spawn: deps.spawn ?? import_child_process6.spawn,
14531
+ spawn: deps.spawn ?? import_child_process7.spawn,
14273
14532
  fetch: deps.fetch ?? globalThis.fetch,
14274
14533
  resolvePath: deps.resolvePath,
14275
14534
  platform: deps.platform,
@@ -14630,8 +14889,8 @@ var CliAgentAiService = class {
14630
14889
  this.lastNativeSessionId = previousSessionId;
14631
14890
  }
14632
14891
  }
14633
- async researchAndPlan(userDescription, runners, modelsByRunner, fs14, onProgress, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
14634
- const contextStr = await collectResearchContext(fs14, runners);
14892
+ async researchAndPlan(userDescription, runners, modelsByRunner, fs15, onProgress, _fetcher, runnerModes, modes = DEFAULT_PLANNER_MODES, signal) {
14893
+ const contextStr = await collectResearchContext(fs15, runners);
14635
14894
  const researchLog = [
14636
14895
  { id: `up-${Date.now()}`, type: "user_prompt", content: userDescription, timestamp: (/* @__PURE__ */ new Date()).toISOString() }
14637
14896
  ];
@@ -15530,26 +15789,26 @@ function retargetTaskRunner(task, runner, catalog) {
15530
15789
  }
15531
15790
 
15532
15791
  // src/services/SkillsService.ts
15533
- var fs8 = __toESM(require("fs"));
15534
- var path17 = __toESM(require("path"));
15792
+ var fs9 = __toESM(require("fs"));
15793
+ var path19 = __toESM(require("path"));
15535
15794
 
15536
15795
  // src/services/builtinSkills.ts
15537
- var path16 = __toESM(require("path"));
15796
+ var path18 = __toESM(require("path"));
15538
15797
  var import_url = require("url");
15539
15798
  var import_meta = {};
15540
15799
  function builtinSkillsDir() {
15541
- const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path16.dirname((0, import_url.fileURLToPath)(
15800
+ const dir = typeof __dirname !== "undefined" && __dirname ? __dirname : path18.dirname((0, import_url.fileURLToPath)(
15542
15801
  // @ts-expect-error - import.meta is only valid under an ESM module setting
15543
15802
  import_meta.url
15544
15803
  ));
15545
- return path16.join(dir, "..", "skills");
15804
+ return path18.join(dir, "..", "skills");
15546
15805
  }
15547
15806
 
15548
15807
  // src/services/SkillsService.ts
15549
15808
  var BUILTIN_SKILL_NAMES = ["grilling", "to-spec", "improve-codebase-architecture"];
15550
15809
  var RETIRED_BUILTIN_SKILL_NAMES = ["grill-me"];
15551
15810
  function parseSkillFile(filePath, name, source) {
15552
- const raw = fs8.readFileSync(filePath, "utf8");
15811
+ const raw = fs9.readFileSync(filePath, "utf8");
15553
15812
  const frontmatter = {};
15554
15813
  let content = raw;
15555
15814
  if (raw.startsWith("---\n")) {
@@ -15578,23 +15837,23 @@ function parseSkillFile(filePath, name, source) {
15578
15837
  return { name, description: metadata.description, metadata, content, path: filePath, source };
15579
15838
  }
15580
15839
  function copyDirSync2(src, dest) {
15581
- fs8.mkdirSync(dest, { recursive: true });
15582
- const entries = fs8.readdirSync(src, { withFileTypes: true });
15840
+ fs9.mkdirSync(dest, { recursive: true });
15841
+ const entries = fs9.readdirSync(src, { withFileTypes: true });
15583
15842
  for (const entry of entries) {
15584
- const srcPath = path17.join(src, entry.name);
15585
- const destPath = path17.join(dest, entry.name);
15843
+ const srcPath = path19.join(src, entry.name);
15844
+ const destPath = path19.join(dest, entry.name);
15586
15845
  if (entry.isDirectory()) copyDirSync2(srcPath, destPath);
15587
- else fs8.copyFileSync(srcPath, destPath);
15846
+ else fs9.copyFileSync(srcPath, destPath);
15588
15847
  }
15589
15848
  }
15590
15849
  function readDir(dir, source) {
15591
- if (!fs8.existsSync(dir)) return [];
15592
- const entries = fs8.readdirSync(dir, { withFileTypes: true });
15850
+ if (!fs9.existsSync(dir)) return [];
15851
+ const entries = fs9.readdirSync(dir, { withFileTypes: true });
15593
15852
  const skills = [];
15594
15853
  for (const entry of entries) {
15595
15854
  if (!entry.isDirectory()) continue;
15596
- const skillFile = path17.join(dir, entry.name, "SKILL.md");
15597
- if (!fs8.existsSync(skillFile)) continue;
15855
+ const skillFile = path19.join(dir, entry.name, "SKILL.md");
15856
+ if (!fs9.existsSync(skillFile)) continue;
15598
15857
  const parsed = parseSkillFile(skillFile, entry.name, source);
15599
15858
  if (parsed) skills.push(parsed);
15600
15859
  }
@@ -15606,22 +15865,22 @@ var SkillsService = class {
15606
15865
  }
15607
15866
  workspaceRoot;
15608
15867
  localDir() {
15609
- return this.workspaceRoot ? path17.join(this.workspaceRoot, ".ordewell", "skills") : void 0;
15868
+ return this.workspaceRoot ? path19.join(this.workspaceRoot, ".ordewell", "skills") : void 0;
15610
15869
  }
15611
15870
  globalDir() {
15612
- return path17.join(globalDataDir(), "skills");
15871
+ return path19.join(globalDataDir(), "skills");
15613
15872
  }
15614
15873
  seed(name) {
15615
- const dest = path17.join(this.globalDir(), name);
15616
- if (fs8.existsSync(dest)) return false;
15874
+ const dest = path19.join(this.globalDir(), name);
15875
+ if (fs9.existsSync(dest)) return false;
15617
15876
  let srcDir;
15618
15877
  try {
15619
- srcDir = path17.join(builtinSkillsDir(), name);
15878
+ srcDir = path19.join(builtinSkillsDir(), name);
15620
15879
  } catch (err) {
15621
15880
  console.warn(`Could not locate built-in skills dir: ${String(err)}`);
15622
15881
  return false;
15623
15882
  }
15624
- if (!fs8.existsSync(srcDir)) {
15883
+ if (!fs9.existsSync(srcDir)) {
15625
15884
  console.warn(`Built-in skill not found in package: ${name}`);
15626
15885
  return false;
15627
15886
  }
@@ -15635,14 +15894,14 @@ var SkillsService = class {
15635
15894
  isUnmodifiedRetiredSeed(dir, expectedName) {
15636
15895
  let entries;
15637
15896
  try {
15638
- entries = fs8.readdirSync(dir, { withFileTypes: true });
15897
+ entries = fs9.readdirSync(dir, { withFileTypes: true });
15639
15898
  } catch {
15640
15899
  return false;
15641
15900
  }
15642
15901
  if (entries.length !== 1 || entries[0].name !== "SKILL.md" || !entries[0].isFile()) return false;
15643
15902
  let raw;
15644
15903
  try {
15645
- raw = fs8.readFileSync(path17.join(dir, "SKILL.md"), "utf8");
15904
+ raw = fs9.readFileSync(path19.join(dir, "SKILL.md"), "utf8");
15646
15905
  } catch {
15647
15906
  return false;
15648
15907
  }
@@ -15659,11 +15918,11 @@ var SkillsService = class {
15659
15918
  }
15660
15919
  pruneRetired() {
15661
15920
  for (const name of RETIRED_BUILTIN_SKILL_NAMES) {
15662
- const dest = path17.join(this.globalDir(), name);
15663
- if (!fs8.existsSync(dest)) continue;
15921
+ const dest = path19.join(this.globalDir(), name);
15922
+ if (!fs9.existsSync(dest)) continue;
15664
15923
  if (!this.isUnmodifiedRetiredSeed(dest, name)) continue;
15665
15924
  try {
15666
- fs8.rmSync(dest, { recursive: true, force: true });
15925
+ fs9.rmSync(dest, { recursive: true, force: true });
15667
15926
  } catch (err) {
15668
15927
  console.warn(`Could not prune retired skill ${name}: ${String(err)}`);
15669
15928
  }
@@ -15674,11 +15933,11 @@ var SkillsService = class {
15674
15933
  if (BUILTIN_SKILL_NAMES.includes(name)) this.seed(name);
15675
15934
  const localDir = this.localDir();
15676
15935
  if (localDir) {
15677
- const localFile = path17.join(localDir, name, "SKILL.md");
15678
- if (fs8.existsSync(localFile)) return parseSkillFile(localFile, name, "local");
15936
+ const localFile = path19.join(localDir, name, "SKILL.md");
15937
+ if (fs9.existsSync(localFile)) return parseSkillFile(localFile, name, "local");
15679
15938
  }
15680
- const globalFile = path17.join(this.globalDir(), name, "SKILL.md");
15681
- if (fs8.existsSync(globalFile)) return parseSkillFile(globalFile, name, "global");
15939
+ const globalFile = path19.join(this.globalDir(), name, "SKILL.md");
15940
+ if (fs9.existsSync(globalFile)) return parseSkillFile(globalFile, name, "global");
15682
15941
  return void 0;
15683
15942
  }
15684
15943
  listSkills() {
@@ -15759,8 +16018,8 @@ function truncateCheckpointSummary(summary) {
15759
16018
  }
15760
16019
 
15761
16020
  // src/utils/sessionStore.ts
15762
- var fs9 = __toESM(require("fs"));
15763
- var path18 = __toESM(require("path"));
16021
+ var fs10 = __toESM(require("fs"));
16022
+ var path20 = __toESM(require("path"));
15764
16023
 
15765
16024
  // src/utils/sessionId.ts
15766
16025
  var import_crypto2 = require("crypto");
@@ -15771,7 +16030,7 @@ function mintSessionId() {
15771
16030
  // src/utils/sessionStore.ts
15772
16031
  var SESSIONS_SUBDIR = "sessions";
15773
16032
  function getSessionsDir(baseDir) {
15774
- return path18.join(getStateDir(baseDir), SESSIONS_SUBDIR);
16033
+ return path20.join(getStateDir(baseDir), SESSIONS_SUBDIR);
15775
16034
  }
15776
16035
  var REJECTION_MESSAGE = "This session was created with an older version of Ordewell. Please re-plan.";
15777
16036
  function hasScalarRunner(parsed) {
@@ -15793,11 +16052,11 @@ function legacySessionFilename(session) {
15793
16052
  return `${filenameStem(session)}_${session.id.slice(0, 8)}.json`;
15794
16053
  }
15795
16054
  function dropLegacyFile(sessionsDir, meta) {
15796
- const legacy = path18.join(sessionsDir, legacySessionFilename(meta));
15797
- if (legacy === path18.join(sessionsDir, sessionFilename(meta)) || !fs9.existsSync(legacy)) return;
16055
+ const legacy = path20.join(sessionsDir, legacySessionFilename(meta));
16056
+ if (legacy === path20.join(sessionsDir, sessionFilename(meta)) || !fs10.existsSync(legacy)) return;
15798
16057
  try {
15799
- const held = JSON.parse(fs9.readFileSync(legacy, "utf-8")).meta?.id;
15800
- if (held === meta.id) fs9.unlinkSync(legacy);
16058
+ const held = JSON.parse(fs10.readFileSync(legacy, "utf-8")).meta?.id;
16059
+ if (held === meta.id) fs10.unlinkSync(legacy);
15801
16060
  } catch {
15802
16061
  }
15803
16062
  }
@@ -15820,18 +16079,18 @@ function saveSession(plan, goal, baseDir, id) {
15820
16079
  plan: JSON.parse(JSON.stringify(plan))
15821
16080
  };
15822
16081
  const filename = sessionFilename(meta);
15823
- const filePath = path18.join(sessionsDir, filename);
15824
- fs9.writeFileSync(filePath, JSON.stringify(session, null, 2));
16082
+ const filePath = path20.join(sessionsDir, filename);
16083
+ fs10.writeFileSync(filePath, JSON.stringify(session, null, 2));
15825
16084
  dropLegacyFile(sessionsDir, meta);
15826
16085
  return meta;
15827
16086
  }
15828
16087
  function listSessions(baseDir, logger = defaultLogger) {
15829
16088
  const sessionsDir = getSessionsDir(baseDir);
15830
- if (!fs9.existsSync(sessionsDir)) return [];
15831
- const files = fs9.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
16089
+ if (!fs10.existsSync(sessionsDir)) return [];
16090
+ const files = fs10.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
15832
16091
  return files.map((f) => {
15833
16092
  try {
15834
- const raw = fs9.readFileSync(path18.join(sessionsDir, f), "utf-8");
16093
+ const raw = fs10.readFileSync(path20.join(sessionsDir, f), "utf-8");
15835
16094
  const parsed = JSON.parse(raw);
15836
16095
  if (hasScalarRunner(parsed)) {
15837
16096
  logger.warn("sessionStore", `session file ${f}: ${REJECTION_MESSAGE}`);
@@ -15846,10 +16105,10 @@ function listSessions(baseDir, logger = defaultLogger) {
15846
16105
  }).filter((m) => m !== null).sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
15847
16106
  }
15848
16107
  function findSessionFile(sessionsDir, sessionId, logger) {
15849
- const files = fs9.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
16108
+ const files = fs10.readdirSync(sessionsDir).filter((f) => f.endsWith(".json"));
15850
16109
  for (const f of files) {
15851
16110
  try {
15852
- const raw = fs9.readFileSync(path18.join(sessionsDir, f), "utf-8");
16111
+ const raw = fs10.readFileSync(path20.join(sessionsDir, f), "utf-8");
15853
16112
  const session = JSON.parse(raw);
15854
16113
  if (session.meta.id === sessionId) return f;
15855
16114
  } catch (err) {
@@ -15860,11 +16119,11 @@ function findSessionFile(sessionsDir, sessionId, logger) {
15860
16119
  }
15861
16120
  function loadSession(sessionId, baseDir, logger = defaultLogger) {
15862
16121
  const sessionsDir = getSessionsDir(baseDir);
15863
- if (!fs9.existsSync(sessionsDir)) return null;
16122
+ if (!fs10.existsSync(sessionsDir)) return null;
15864
16123
  try {
15865
16124
  const file = findSessionFile(sessionsDir, sessionId, logger);
15866
16125
  if (file) {
15867
- const raw = fs9.readFileSync(path18.join(sessionsDir, file), "utf-8");
16126
+ const raw = fs10.readFileSync(path20.join(sessionsDir, file), "utf-8");
15868
16127
  const parsed = JSON.parse(raw);
15869
16128
  if (hasScalarRunner(parsed)) {
15870
16129
  logger.warn("sessionStore", `session file ${file}: ${REJECTION_MESSAGE}`);
@@ -15895,11 +16154,11 @@ function getLatestSession(baseDir, logger = defaultLogger) {
15895
16154
  }
15896
16155
  function deleteSession(sessionId, baseDir, logger = defaultLogger) {
15897
16156
  const sessionsDir = getSessionsDir(baseDir);
15898
- if (!fs9.existsSync(sessionsDir)) return false;
16157
+ if (!fs10.existsSync(sessionsDir)) return false;
15899
16158
  try {
15900
16159
  const file = findSessionFile(sessionsDir, sessionId, logger);
15901
16160
  if (file) {
15902
- fs9.unlinkSync(path18.join(sessionsDir, file));
16161
+ fs10.unlinkSync(path20.join(sessionsDir, file));
15903
16162
  return true;
15904
16163
  }
15905
16164
  } catch (err) {
@@ -16580,6 +16839,10 @@ var Session = class {
16580
16839
  }
16581
16840
  await this.orchestrator.runTask(taskId);
16582
16841
  }
16842
+ /** Start whatever the scheduler can now fit — after the parallel limit was raised mid-run, say. */
16843
+ async reschedule() {
16844
+ await this.orchestrator.tick();
16845
+ }
16583
16846
  async retryTask(taskId) {
16584
16847
  await this.orchestrator.retryTask(taskId);
16585
16848
  this.persist();
@@ -17337,7 +17600,7 @@ function buildRunnerInvocation(opts) {
17337
17600
  }
17338
17601
 
17339
17602
  // src/services/HeadlessRunner.ts
17340
- var import_child_process7 = require("child_process");
17603
+ var import_child_process8 = require("child_process");
17341
17604
 
17342
17605
  // src/services/AbstractRunner.ts
17343
17606
  var import_events = require("events");
@@ -17392,7 +17655,7 @@ var AbstractRunner = class {
17392
17655
  function defaultHasScriptCmd() {
17393
17656
  if (process.platform === "win32") return false;
17394
17657
  try {
17395
- (0, import_child_process7.execSync)("which script", { stdio: "ignore" });
17658
+ (0, import_child_process8.execSync)("which script", { stdio: "ignore" });
17396
17659
  return true;
17397
17660
  } catch {
17398
17661
  return false;
@@ -17482,7 +17745,7 @@ var HeadlessRunner = class extends AbstractRunner {
17482
17745
  defaultInteractive = false;
17483
17746
  constructor(deps = {}) {
17484
17747
  super();
17485
- this.spawnImpl = deps.spawnImpl ?? import_child_process7.spawn;
17748
+ this.spawnImpl = deps.spawnImpl ?? import_child_process8.spawn;
17486
17749
  this.hasScriptCmd = deps.hasScriptCmd ?? defaultHasScriptCmd;
17487
17750
  this.resolvePath = deps.resolvePath ?? augmentedPath;
17488
17751
  this.launchDeps = { resolvePath: this.resolvePath, ...deps.launchDeps };
@@ -17522,7 +17785,7 @@ var HeadlessRunner = class extends AbstractRunner {
17522
17785
  return {
17523
17786
  launch: await planDirectLaunch(command, args, this.launchDeps),
17524
17787
  resolvedPath,
17525
- env: invocation.env,
17788
+ env: { ...opts.env, ...invocation.env },
17526
17789
  pty,
17527
17790
  submitPromptKey: invocation.submitPromptKey
17528
17791
  };
@@ -17541,14 +17804,14 @@ var HeadlessRunner = class extends AbstractRunner {
17541
17804
  };
17542
17805
 
17543
17806
  // src/services/TmuxRunner.ts
17544
- var import_child_process9 = require("child_process");
17807
+ var import_child_process10 = require("child_process");
17545
17808
  var import_util5 = require("util");
17546
17809
  var import_os = require("os");
17547
17810
  var import_path = require("path");
17548
17811
  var import_fs2 = require("fs");
17549
17812
 
17550
17813
  // src/utils/tmux.ts
17551
- var import_child_process8 = require("child_process");
17814
+ var import_child_process9 = require("child_process");
17552
17815
  function tmuxSessionName(port) {
17553
17816
  return `ordewell-${port}`;
17554
17817
  }
@@ -17561,7 +17824,7 @@ function tmuxWindowName(taskId, planSessionId) {
17561
17824
  return `t-${scope}${slug(taskId).slice(0, 16)}`;
17562
17825
  }
17563
17826
  var defaultProbe = () => {
17564
- (0, import_child_process8.execSync)(process.platform === "win32" ? "where tmux" : "which tmux", { stdio: "ignore" });
17827
+ (0, import_child_process9.execSync)(process.platform === "win32" ? "where tmux" : "which tmux", { stdio: "ignore" });
17565
17828
  };
17566
17829
  function hasTmux(probe = defaultProbe) {
17567
17830
  try {
@@ -17573,7 +17836,7 @@ function hasTmux(probe = defaultProbe) {
17573
17836
  }
17574
17837
  var defaultHasBin = (bin) => {
17575
17838
  try {
17576
- (0, import_child_process8.execSync)(`${process.platform === "win32" ? "where" : "which"} ${bin}`, { stdio: "ignore" });
17839
+ (0, import_child_process9.execSync)(`${process.platform === "win32" ? "where" : "which"} ${bin}`, { stdio: "ignore" });
17577
17840
  return true;
17578
17841
  } catch {
17579
17842
  return false;
@@ -17596,7 +17859,7 @@ function clipboardCopyCommand(hasBin = defaultHasBin, platform = process.platfor
17596
17859
 
17597
17860
  // src/services/TmuxRunner.ts
17598
17861
  var EXIT_RE = /<<<ORDEWELL_TMUX_EXIT:(\d+)>>>/;
17599
- var execFileAsync2 = (0, import_util5.promisify)(import_child_process9.execFile);
17862
+ var execFileAsync2 = (0, import_util5.promisify)(import_child_process10.execFile);
17600
17863
  var defaultExecFile3 = async (command, args) => {
17601
17864
  const { stdout, stderr } = await execFileAsync2(command, args);
17602
17865
  return { stdout: String(stdout), stderr: String(stderr) };
@@ -17834,7 +18097,7 @@ var TmuxRunner = class extends AbstractRunner {
17834
18097
  (0, import_path.join)(this.logDir, `${id}.log`),
17835
18098
  this.socket
17836
18099
  );
17837
- invocation.env = { ...invocation.env, PATH: await this.resolvePath() };
18100
+ invocation.env = { ...opts.env, ...invocation.env, PATH: await this.resolvePath() };
17838
18101
  await session.start(invocation.command, invocation.args, opts.cwd, invocation.env);
17839
18102
  if (invocation.submitPromptKey) session.write("\r");
17840
18103
  this.registerSession(id, session);
@@ -17914,18 +18177,18 @@ function tokensMatch(presented, expected) {
17914
18177
  }
17915
18178
 
17916
18179
  // src/plugins/RunnerRegistry.ts
17917
- var fs11 = __toESM(require("fs"));
17918
- var path21 = __toESM(require("path"));
17919
- var os5 = __toESM(require("os"));
17920
- var import_child_process10 = require("child_process");
18180
+ var fs12 = __toESM(require("fs"));
18181
+ var path23 = __toESM(require("path"));
18182
+ var os6 = __toESM(require("os"));
18183
+ var import_child_process11 = require("child_process");
17921
18184
  var import_crypto4 = require("crypto");
17922
18185
 
17923
18186
  // src/plugins/FsPluginStore.ts
17924
- var fs10 = __toESM(require("fs"));
17925
- var path20 = __toESM(require("path"));
18187
+ var fs11 = __toESM(require("fs"));
18188
+ var path22 = __toESM(require("path"));
17926
18189
 
17927
18190
  // src/plugins/pluginNames.ts
17928
- var path19 = __toESM(require("path"));
18191
+ var path21 = __toESM(require("path"));
17929
18192
  var PLUGIN_NAME_PATTERN = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
17930
18193
  var MAX_PLUGIN_NAME_LENGTH = 64;
17931
18194
  function isPlainPluginName(name) {
@@ -17940,10 +18203,10 @@ function assertPlainPluginName(name) {
17940
18203
  }
17941
18204
  function resolvePluginInstallDir(pluginsDir, name) {
17942
18205
  assertPlainPluginName(name);
17943
- const root = path19.resolve(pluginsDir);
17944
- const dest = path19.resolve(root, name);
17945
- const relative5 = path19.relative(root, dest);
17946
- if (relative5 !== name || relative5 === "" || path19.isAbsolute(relative5)) {
18206
+ const root = path21.resolve(pluginsDir);
18207
+ const dest = path21.resolve(root, name);
18208
+ const relative5 = path21.relative(root, dest);
18209
+ if (relative5 !== name || relative5 === "" || path21.isAbsolute(relative5)) {
17947
18210
  throw new Error(`Plugin install path escapes the plugins directory: ${JSON.stringify(name)}`);
17948
18211
  }
17949
18212
  return dest;
@@ -17958,13 +18221,13 @@ function isValidManifest(obj) {
17958
18221
 
17959
18222
  // src/plugins/FsPluginStore.ts
17960
18223
  function userPluginsDir() {
17961
- return path20.join(globalDataDir(), "plugins");
18224
+ return path22.join(globalDataDir(), "plugins");
17962
18225
  }
17963
18226
  function loadManifestFromDir(dir) {
17964
- const manifestPath = path20.join(dir, "manifest.json");
17965
- if (!fs10.existsSync(manifestPath)) return null;
18227
+ const manifestPath = path22.join(dir, "manifest.json");
18228
+ if (!fs11.existsSync(manifestPath)) return null;
17966
18229
  try {
17967
- const raw = fs10.readFileSync(manifestPath, "utf8");
18230
+ const raw = fs11.readFileSync(manifestPath, "utf8");
17968
18231
  const parsed = JSON.parse(raw);
17969
18232
  if (!isValidManifest(parsed)) return null;
17970
18233
  return parsed;
@@ -17974,29 +18237,29 @@ function loadManifestFromDir(dir) {
17974
18237
  }
17975
18238
  function copyDirSync3(src, dest) {
17976
18239
  ensureDir(dest);
17977
- const entries = fs10.readdirSync(src, { withFileTypes: true });
18240
+ const entries = fs11.readdirSync(src, { withFileTypes: true });
17978
18241
  for (const entry of entries) {
17979
- const srcPath = path20.join(src, entry.name);
17980
- const destPath = path20.join(dest, entry.name);
18242
+ const srcPath = path22.join(src, entry.name);
18243
+ const destPath = path22.join(dest, entry.name);
17981
18244
  if (entry.isDirectory()) {
17982
18245
  copyDirSync3(srcPath, destPath);
17983
18246
  } else {
17984
- fs10.copyFileSync(srcPath, destPath);
18247
+ fs11.copyFileSync(srcPath, destPath);
17985
18248
  }
17986
18249
  }
17987
18250
  }
17988
18251
  function rmDirSync(dir) {
17989
- if (!fs10.existsSync(dir)) return;
17990
- const entries = fs10.readdirSync(dir, { withFileTypes: true });
18252
+ if (!fs11.existsSync(dir)) return;
18253
+ const entries = fs11.readdirSync(dir, { withFileTypes: true });
17991
18254
  for (const entry of entries) {
17992
- const p = path20.join(dir, entry.name);
18255
+ const p = path22.join(dir, entry.name);
17993
18256
  if (entry.isDirectory()) {
17994
18257
  rmDirSync(p);
17995
18258
  } else {
17996
- fs10.unlinkSync(p);
18259
+ fs11.unlinkSync(p);
17997
18260
  }
17998
18261
  }
17999
- fs10.rmdirSync(dir);
18262
+ fs11.rmdirSync(dir);
18000
18263
  }
18001
18264
  var FsPluginStore = class {
18002
18265
  getUserPluginsDir() {
@@ -18004,10 +18267,10 @@ var FsPluginStore = class {
18004
18267
  }
18005
18268
  listUserPluginDirs() {
18006
18269
  const dir = userPluginsDir();
18007
- if (!fs10.existsSync(dir)) return [];
18270
+ if (!fs11.existsSync(dir)) return [];
18008
18271
  let entries;
18009
18272
  try {
18010
- entries = fs10.readdirSync(dir, { withFileTypes: true });
18273
+ entries = fs11.readdirSync(dir, { withFileTypes: true });
18011
18274
  } catch {
18012
18275
  return [];
18013
18276
  }
@@ -18015,7 +18278,7 @@ var FsPluginStore = class {
18015
18278
  }
18016
18279
  listDir(dir) {
18017
18280
  try {
18018
- return fs10.readdirSync(dir);
18281
+ return fs11.readdirSync(dir);
18019
18282
  } catch {
18020
18283
  return [];
18021
18284
  }
@@ -18033,20 +18296,20 @@ var FsPluginStore = class {
18033
18296
  ensureDir(dir);
18034
18297
  }
18035
18298
  writeFile(filePath, content) {
18036
- fs10.writeFileSync(filePath, content);
18299
+ fs11.writeFileSync(filePath, content);
18037
18300
  }
18038
18301
  readFile(filePath) {
18039
18302
  try {
18040
- return fs10.readFileSync(filePath, "utf8");
18303
+ return fs11.readFileSync(filePath, "utf8");
18041
18304
  } catch {
18042
18305
  return null;
18043
18306
  }
18044
18307
  }
18045
- dirExists(path24) {
18046
- return fs10.existsSync(path24) && fs10.statSync(path24).isDirectory();
18308
+ dirExists(path26) {
18309
+ return fs11.existsSync(path26) && fs11.statSync(path26).isDirectory();
18047
18310
  }
18048
- exists(path24) {
18049
- return fs10.existsSync(path24);
18311
+ exists(path26) {
18312
+ return fs11.existsSync(path26);
18050
18313
  }
18051
18314
  };
18052
18315
 
@@ -18073,7 +18336,15 @@ var CLAUDE_CODE_MANIFEST = {
18073
18336
  "{{/if}}",
18074
18337
  "{{prompt}}"
18075
18338
  ],
18076
- promptInArgs: true
18339
+ promptInArgs: true,
18340
+ // Interactive sessions show these even with --dangerously-skip-permissions:
18341
+ // a folder Claude Code has never been trusted in (a fresh clone, or a repo
18342
+ // only ever planned in headlessly), and the first Bypass Permissions run.
18343
+ blockingPrompts: [
18344
+ { phrase: "Is this a project you created or one you trust", asks: "whether to trust the task's folder" },
18345
+ { phrase: "Do you trust the files in this folder", asks: "whether to trust the task's folder" },
18346
+ { phrase: "running in Bypass Permissions mode", asks: "you to accept Bypass Permissions mode" }
18347
+ ]
18077
18348
  },
18078
18349
  features: {
18079
18350
  modelSelection: true,
@@ -18417,7 +18688,7 @@ function isReservedRunnerName(name) {
18417
18688
  return RESERVED_RUNNER_NAMES.has(name.toLowerCase());
18418
18689
  }
18419
18690
  function gitClone(url, destDir) {
18420
- (0, import_child_process10.execFileSync)("git", ["clone", "--depth", "1", "--", url, destDir], {
18691
+ (0, import_child_process11.execFileSync)("git", ["clone", "--depth", "1", "--", url, destDir], {
18421
18692
  stdio: "pipe",
18422
18693
  timeout: 3e4,
18423
18694
  // A repository that demands credentials should fail, not hang on a prompt.
@@ -18478,7 +18749,7 @@ var RunnerRegistry = class {
18478
18749
  return entry?.source === "builtin";
18479
18750
  }
18480
18751
  installFromPath(sourcePath) {
18481
- const absPath = path21.resolve(sourcePath);
18752
+ const absPath = path23.resolve(sourcePath);
18482
18753
  if (!this.store.dirExists(absPath)) {
18483
18754
  throw new Error(`Plugin source must be a directory: ${absPath}`);
18484
18755
  }
@@ -18490,7 +18761,7 @@ var RunnerRegistry = class {
18490
18761
  }
18491
18762
  installFromGit(url) {
18492
18763
  const validated = assertInstallablePluginUrl(url);
18493
- const tmpDir = path21.join(os5.tmpdir(), `ordewell-plugin-${(0, import_crypto4.randomBytes)(12).toString("hex")}`);
18764
+ const tmpDir = path23.join(os6.tmpdir(), `ordewell-plugin-${(0, import_crypto4.randomBytes)(12).toString("hex")}`);
18494
18765
  this.store.ensureDir(tmpDir);
18495
18766
  try {
18496
18767
  try {
@@ -18503,7 +18774,7 @@ var RunnerRegistry = class {
18503
18774
  const hasManifest = contents.some((f) => f === "manifest.json");
18504
18775
  let sourceDir = tmpDir;
18505
18776
  if (!hasManifest) {
18506
- const onlyEntry = contents.length === 1 ? path21.join(tmpDir, contents[0]) : void 0;
18777
+ const onlyEntry = contents.length === 1 ? path23.join(tmpDir, contents[0]) : void 0;
18507
18778
  if (!onlyEntry || !this.store.dirExists(onlyEntry)) {
18508
18779
  throw new Error(`No manifest.json found in repository: ${validated.toString()}`);
18509
18780
  }
@@ -18551,7 +18822,7 @@ var RunnerRegistry = class {
18551
18822
  }
18552
18823
  createSkeleton(name, outputDir) {
18553
18824
  assertPlainPluginName(name);
18554
- const dir = path21.resolve(outputDir, name);
18825
+ const dir = path23.resolve(outputDir, name);
18555
18826
  this.store.ensureDir(dir);
18556
18827
  const manifest = {
18557
18828
  name,
@@ -18583,8 +18854,8 @@ var RunnerRegistry = class {
18583
18854
  ]
18584
18855
  }
18585
18856
  };
18586
- this.store.writeFile(path21.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
18587
- const runSh = path21.join(dir, "run.sh");
18857
+ this.store.writeFile(path23.join(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
18858
+ const runSh = path23.join(dir, "run.sh");
18588
18859
  const scriptContent = `#!/usr/bin/env bash
18589
18860
  # Custom runner script for ${name}
18590
18861
  # Receives:
@@ -18610,30 +18881,30 @@ echo "Running ${name} with prompt: $prompt"
18610
18881
  };
18611
18882
  function fsChmodSync(fpath, mode) {
18612
18883
  try {
18613
- fs11.chmodSync(fpath, mode);
18884
+ fs12.chmodSync(fpath, mode);
18614
18885
  } catch {
18615
18886
  }
18616
18887
  }
18617
18888
 
18618
18889
  // src/utils/stateStore.ts
18619
- var fs12 = __toESM(require("fs"));
18620
- var path22 = __toESM(require("path"));
18890
+ var fs13 = __toESM(require("fs"));
18891
+ var path24 = __toESM(require("path"));
18621
18892
  var STATE_FILE = "state.json";
18622
18893
  function getStatePath(baseDir) {
18623
- return path22.join(getStateDir(baseDir), STATE_FILE);
18894
+ return path24.join(getStateDir(baseDir), STATE_FILE);
18624
18895
  }
18625
18896
  function saveState(plan, baseDir) {
18626
18897
  const statePath = getStatePath(baseDir ?? process.cwd());
18627
- ensureDir(path22.dirname(statePath));
18898
+ ensureDir(path24.dirname(statePath));
18628
18899
  plan.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
18629
- fs12.writeFileSync(statePath, JSON.stringify(plan, null, 2));
18900
+ fs13.writeFileSync(statePath, JSON.stringify(plan, null, 2));
18630
18901
  }
18631
18902
  function loadState(baseDir, logger = defaultLogger) {
18632
18903
  const dir = baseDir ?? process.cwd();
18633
18904
  const statePath = getStatePath(dir);
18634
- if (!fs12.existsSync(statePath)) return null;
18905
+ if (!fs13.existsSync(statePath)) return null;
18635
18906
  try {
18636
- const raw = fs12.readFileSync(statePath, "utf-8");
18907
+ const raw = fs13.readFileSync(statePath, "utf-8");
18637
18908
  const parsed = JSON.parse(raw);
18638
18909
  if (!Array.isArray(parsed.runners)) {
18639
18910
  logger.warn("stateStore", `rejecting state from ${statePath}: old format (scalar "runner" instead of "runners" array). Please re-plan.`);
@@ -18650,16 +18921,16 @@ function loadState(baseDir, logger = defaultLogger) {
18650
18921
  function clearState(baseDir) {
18651
18922
  const dir = baseDir ?? process.cwd();
18652
18923
  const statePath = getStatePath(dir);
18653
- if (fs12.existsSync(statePath)) fs12.unlinkSync(statePath);
18924
+ if (fs13.existsSync(statePath)) fs13.unlinkSync(statePath);
18654
18925
  }
18655
18926
  function stateExists(baseDir) {
18656
18927
  const dir = baseDir ?? process.cwd();
18657
- return fs12.existsSync(getStatePath(dir));
18928
+ return fs13.existsSync(getStatePath(dir));
18658
18929
  }
18659
18930
 
18660
18931
  // src/services/SettingsService.ts
18661
- var fs13 = __toESM(require("fs"));
18662
- var path23 = __toESM(require("path"));
18932
+ var fs14 = __toESM(require("fs"));
18933
+ var path25 = __toESM(require("path"));
18663
18934
  var DEFAULTS = {
18664
18935
  tdd: { enabled: true },
18665
18936
  verification: { enabled: false }
@@ -18667,7 +18938,7 @@ var DEFAULTS = {
18667
18938
  function getSettingsPath() {
18668
18939
  const override = process.env.ORDEWELL_SETTINGS_PATH;
18669
18940
  if (override && override.trim()) return override.trim();
18670
- return path23.join(globalDataDir(), "settings.json");
18941
+ return path25.join(globalDataDir(), "settings.json");
18671
18942
  }
18672
18943
  function readPlannerModels(raw) {
18673
18944
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
@@ -18700,7 +18971,7 @@ var SettingsService = class {
18700
18971
  }
18701
18972
  fileMtimeMs() {
18702
18973
  try {
18703
- return fs13.statSync(this.filePath).mtimeMs;
18974
+ return fs14.statSync(this.filePath).mtimeMs;
18704
18975
  } catch {
18705
18976
  return null;
18706
18977
  }
@@ -18772,8 +19043,8 @@ var SettingsService = class {
18772
19043
  load() {
18773
19044
  migrateOldConfigDir();
18774
19045
  try {
18775
- if (fs13.existsSync(this.filePath)) {
18776
- const raw = JSON.parse(fs13.readFileSync(this.filePath, "utf-8"));
19046
+ if (fs14.existsSync(this.filePath)) {
19047
+ const raw = JSON.parse(fs14.readFileSync(this.filePath, "utf-8"));
18777
19048
  const settings = {
18778
19049
  tdd: { enabled: raw.tdd?.enabled ?? DEFAULTS.tdd.enabled },
18779
19050
  verification: { enabled: raw.verification?.enabled ?? DEFAULTS.verification.enabled }
@@ -18795,11 +19066,11 @@ var SettingsService = class {
18795
19066
  return { ...DEFAULTS, tdd: { ...DEFAULTS.tdd }, verification: { ...DEFAULTS.verification } };
18796
19067
  }
18797
19068
  persist() {
18798
- const dir = path23.dirname(this.filePath);
18799
- if (!fs13.existsSync(dir)) {
18800
- fs13.mkdirSync(dir, { recursive: true });
19069
+ const dir = path25.dirname(this.filePath);
19070
+ if (!fs14.existsSync(dir)) {
19071
+ fs14.mkdirSync(dir, { recursive: true });
18801
19072
  }
18802
- fs13.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2));
19073
+ fs14.writeFileSync(this.filePath, JSON.stringify(this.cache, null, 2));
18803
19074
  this.cachedMtimeMs = this.fileMtimeMs();
18804
19075
  }
18805
19076
  };
@@ -18866,6 +19137,7 @@ var PlannerModelMemory = class {
18866
19137
  ConversationEditError,
18867
19138
  DAEMON_SUBPROTOCOL,
18868
19139
  DAEMON_TOKEN_SUBPROTOCOL_PREFIX,
19140
+ DEFAULT_MAX_PARALLEL,
18869
19141
  DENY_ALL,
18870
19142
  EmbeddedNewlineError,
18871
19143
  EnvConfig,
@@ -19048,6 +19320,7 @@ var PlannerModelMemory = class {
19048
19320
  modifyValidationFeedback,
19049
19321
  normalizeAgentArgs,
19050
19322
  normalizeGeminiModel,
19323
+ parseMaxParallel,
19051
19324
  parsePartialPlan,
19052
19325
  parsePlanJson,
19053
19326
  parseTaskOpsJson,