@ordewell/core 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2477,6 +2477,8 @@ function lex(command, nested, dialect) {
2477
2477
  let unbalanced = false;
2478
2478
  let grouped = false;
2479
2479
  let joinedBy;
2480
+ let followable = true;
2481
+ let bareSegment = false;
2480
2482
  let tokens = [];
2481
2483
  let expandable = false;
2482
2484
  let current = "";
@@ -2506,9 +2508,20 @@ function lex(command, nested, dialect) {
2506
2508
  current = "";
2507
2509
  started = false;
2508
2510
  };
2509
- const endSegment = (nextPiped, next = "other") => {
2511
+ const endSegment = (nextPiped, next = "other", last = false) => {
2510
2512
  endToken();
2511
- if (tokens.length > 0) segments.push({ ...toSegment(tokens, piped, dialect), expandable, inputs, joinedBy });
2513
+ if (!last && next === "other") followable = false;
2514
+ if (tokens.length > 0) {
2515
+ const seg = toSegment(tokens, piped, dialect);
2516
+ if (!seg.binary) {
2517
+ followable = false;
2518
+ bareSegment = true;
2519
+ }
2520
+ segments.push({ ...seg, expandable, inputs, joinedBy });
2521
+ } else if (!last || segments.length > 0 || joinedBy !== void 0) {
2522
+ followable = false;
2523
+ bareSegment = true;
2524
+ }
2512
2525
  tokens = [];
2513
2526
  inputs = [];
2514
2527
  expandable = false;
@@ -2663,8 +2676,8 @@ function lex(command, nested, dialect) {
2663
2676
  i++;
2664
2677
  }
2665
2678
  if (quote !== "") unbalanced = true;
2666
- endSegment(false);
2667
- return { segments, unsafeRedirect, processSubstitution, unbalanced, grouped };
2679
+ endSegment(false, "other", true);
2680
+ return { segments, unsafeRedirect, processSubstitution, unbalanced, grouped, followable, bareSegment };
2668
2681
  }
2669
2682
  function binaryName(token, dialect) {
2670
2683
  const base = token.split(/[/\\]/).pop() ?? "";
@@ -2808,7 +2821,8 @@ function lexAll(command, dialect, cdpathSet = false) {
2808
2821
  const queue = [];
2809
2822
  const top = lex(command, queue, dialect);
2810
2823
  const all = { ...top, segments: top.segments.filter((s) => s.binary) };
2811
- if (!top.grouped && queue.length === 0 && !cdpathSet) followCd(all.segments);
2824
+ markShellState(all.segments, top.bareSegment);
2825
+ if (top.followable && !top.grouped && queue.length === 0 && !cdpathSet) followCd(all.segments);
2812
2826
  for (let depth = 0; depth < 32 && queue.length > 0; depth++) {
2813
2827
  const inner = lex(queue.shift(), queue, dialect);
2814
2828
  all.segments.push(...inner.segments.filter((s) => s.binary));
@@ -2825,22 +2839,51 @@ function literalCdTarget(seg) {
2825
2839
  return target === "" || target.startsWith("-") || target.startsWith("~") || /[*?[]/.test(target) ? void 0 : target;
2826
2840
  }
2827
2841
  function followCd(segments) {
2828
- if (segments.some((s) => s.joinedBy === "other")) return;
2842
+ if (segments.some((s) => s.joinedBy === "other" || SHELL_STATE_COMMANDS.has(s.binary))) return;
2843
+ const inPipeline = (i) => segments[i].joinedBy === "pipe" || segments[i + 1]?.joinedBy === "pipe";
2844
+ if (segments.some((s, i) => s.binary === "cd" && !inPipeline(i) && literalCdTarget(s) === void 0)) return;
2829
2845
  let chain = [];
2830
2846
  segments.forEach((seg, i) => {
2831
2847
  seg.cwd = chain;
2832
- const inPipeline = seg.joinedBy === "pipe" || segments[i + 1]?.joinedBy === "pipe";
2833
- const target = inPipeline ? void 0 : literalCdTarget(seg);
2848
+ const target = inPipeline(i) ? void 0 : literalCdTarget(seg);
2834
2849
  if (target !== void 0) chain = [...chain, target];
2835
2850
  });
2836
2851
  }
2852
+ function markShellState(segments, bareSegment) {
2853
+ if (!bareSegment && !segments.some((s) => SHELL_STATE_COMMANDS.has(s.binary))) return;
2854
+ for (const seg of segments) if (seg.binary === "cd") seg.cdSteered = true;
2855
+ }
2856
+ var SHELL_STATE_COMMANDS = /* @__PURE__ */ new Set([
2857
+ "export",
2858
+ "set",
2859
+ "shopt",
2860
+ "declare",
2861
+ "typeset",
2862
+ "readonly",
2863
+ "local",
2864
+ "unset",
2865
+ "let",
2866
+ "source",
2867
+ ".",
2868
+ "eval",
2869
+ "exec",
2870
+ "pushd",
2871
+ "popd",
2872
+ "alias",
2873
+ "unalias",
2874
+ "builtin",
2875
+ "command",
2876
+ "enable",
2877
+ "hash",
2878
+ "trap"
2879
+ ]);
2837
2880
  function looksLikePath(arg) {
2838
2881
  if (arg.startsWith("--") && arg.includes("=")) return looksLikePath(arg.slice(arg.indexOf("=") + 1));
2839
2882
  return arg.startsWith("/") || arg.startsWith("~") || arg.startsWith("../") || arg === ".." || arg.startsWith("./") || /^[A-Za-z]:/.test(arg) || arg.startsWith("\\") || arg.startsWith("..\\") || arg.startsWith(".\\") || /(^|[\\/])\.\.([\\/]|$)/.test(arg);
2840
2883
  }
2841
2884
  function pathRefs(command, opts = {}) {
2842
2885
  const dialect = dialectFor(opts.dialect);
2843
- return lexAll(command, dialect, opts.cdpathSet).segments.flatMap((seg) => {
2886
+ return lexAll(command.trim(), dialect, opts.cdpathSet).segments.flatMap((seg) => {
2844
2887
  const patterns = patternArgs(seg);
2845
2888
  const cwd = seg.cwd ?? [];
2846
2889
  return [...seg.args.filter((_, i) => !patterns.has(i)), ...seg.inputs].flatMap(pathIn).map((path26) => ({ path: path26, cwd }));
@@ -3008,7 +3051,7 @@ function scopeFor(seg) {
3008
3051
  return [seg.binary, ...lead].join(" ");
3009
3052
  }
3010
3053
  function isAuto(seg, opts) {
3011
- if (seg.binary === "cd") return !opts.cdpathSet && literalCdTarget(seg) !== void 0;
3054
+ if (seg.binary === "cd") return !opts.cdpathSet && !seg.cdSteered && literalCdTarget(seg) !== void 0;
3012
3055
  if (!AUTO_COMMANDS.includes(seg.binary)) return false;
3013
3056
  if (seg.expandable) return false;
3014
3057
  if (seg.binary !== "git") return true;
@@ -3210,6 +3253,9 @@ function researchShellWarning(shell) {
3210
3253
  if (shell.dialect !== "cmd") return null;
3211
3254
  return "No POSIX shell was found on this machine, so research commands run through cmd.exe, where most read-only tools (ls, cat, wc, head, grep, find) do not exist. Installing Git for Windows provides one and restores the full research toolset.";
3212
3255
  }
3256
+ function researchPolicyOptions(shell) {
3257
+ return { dialect: shell.dialect, cdpathSet: !!process.env.CDPATH };
3258
+ }
3213
3259
 
3214
3260
  // src/services/pathScope.ts
3215
3261
  import * as os from "os";
@@ -3443,7 +3489,7 @@ ${refs.output.trim()}`);
3443
3489
  return [...found].map(([scope, abs]) => ({ abs, scope }));
3444
3490
  }
3445
3491
  policyOptions() {
3446
- return { dialect: this.researchShell.dialect, cdpathSet: !!process.env.CDPATH };
3492
+ return researchPolicyOptions(this.researchShell);
3447
3493
  }
3448
3494
  /**
3449
3495
  * Three tiers (see `commandPolicy.ts`): read-only inspection runs silently,
@@ -7726,16 +7772,15 @@ function nonPromptingFs(fs16) {
7726
7772
  findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs16.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
7727
7773
  listDir: (p, depth) => withinWorkspace(p) ? fs16.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
7728
7774
  bash: async (command, signal) => {
7729
- const dialect = resolveResearchShell().dialect;
7730
- const policy = { dialect, cdpathSet: !!process.env.CDPATH };
7775
+ const policy = researchPolicyOptions(resolveResearchShell());
7731
7776
  const { tier, reason } = classifyCommand(command, policy);
7732
7777
  if (tier !== "auto") {
7733
7778
  return refused2(
7734
7779
  tier === "refuse" ? `Command refused: ${reason}` : `"${command}" needs user approval, and research agents run in the background where nothing can be approved. Use the read-only research tools, and note in your digest that this command would answer the question.`
7735
7780
  );
7736
7781
  }
7737
- const escaping = pathRefs(command, policy).find((ref) => !resolveRef(fs16.getWorkspaceRoot(), ref).inside);
7738
- if (escaping) return outsideRefusal(escaping.path);
7782
+ const escaping = pathRefs(command, policy).map((ref) => resolveRef(fs16.getWorkspaceRoot(), ref)).find((r) => !r.inside);
7783
+ if (escaping) return outsideRefusal(escaping.abs);
7739
7784
  return fs16.bash(command, signal);
7740
7785
  }
7741
7786
  };
@@ -10774,11 +10819,17 @@ function buildShellInvocation(command, args) {
10774
10819
  const inner = [command, ...args].map(posixShellQuote).join(" ");
10775
10820
  return { shellPath: "/bin/bash", shellArgs: ["-lc", inner] };
10776
10821
  }
10822
+ var BSD_SCRIPT_PLATFORMS = /* @__PURE__ */ new Set(["darwin", "freebsd", "openbsd", "netbsd"]);
10823
+ var BSD_SCRIPT_FEED = 'exec 5<&0; p=$(mktemp) || exit 1; { cat <&5 & echo $! >"$p"; wait; } 2>/dev/null | { script -q -e -F /dev/null /bin/sh -c "$0"; s=$?; kill "$(cat "$p")" 2>/dev/null; rm -f "$p"; exit $s; }';
10777
10824
  function wrapWithPty(command, args, opts = {}) {
10778
10825
  const inner = [command, ...args].map(posixShellQuote).join(" ");
10779
10826
  const watcher = opts.controlChannel ? 'exec 4<&0; ( while read -r C R <&3; do stty cols "$C" rows "$R" <&4; done ) & ' : "";
10780
10827
  const setup = opts.size ? `stty cols ${opts.size.cols} rows ${opts.size.rows}; ` : "";
10781
- return { command: "script", args: ["-q", "-e", "-f", "-c", `${watcher}${setup}${inner}`, "/dev/null"] };
10828
+ const line = `${watcher}${setup}${inner}`;
10829
+ if (BSD_SCRIPT_PLATFORMS.has(opts.platform ?? process.platform)) {
10830
+ return { command: "/bin/sh", args: ["-c", BSD_SCRIPT_FEED, line] };
10831
+ }
10832
+ return { command: "script", args: ["-q", "-e", "-f", "-c", line, "/dev/null"] };
10782
10833
  }
10783
10834
 
10784
10835
  // src/services/blockingPrompts.ts
@@ -10973,24 +11024,37 @@ var Block = /* @__PURE__ */ ((Block2) => {
10973
11024
  Block2[Block2["IfHeadlessSession"] = 8] = "IfHeadlessSession";
10974
11025
  Block2[Block2["IfInteractiveVariant"] = 9] = "IfInteractiveVariant";
10975
11026
  Block2[Block2["IfProjectTrust"] = 10] = "IfProjectTrust";
11027
+ Block2[Block2["IfHeadlessModel"] = 11] = "IfHeadlessModel";
10976
11028
  return Block2;
10977
11029
  })(Block || {});
10978
11030
  function isInteractive(ctx) {
10979
11031
  return ctx.interactive ?? !ctx.headless;
10980
11032
  }
11033
+ function majorVersion(version) {
11034
+ const match = version?.match(/(\d+)\.\d+/);
11035
+ return match ? Number(match[1]) : void 0;
11036
+ }
11037
+ function versionedInvocation(manifest, ctx) {
11038
+ const major = majorVersion(ctx.runnerVersion);
11039
+ if (major === void 0) return void 0;
11040
+ return (manifest.runner.versioned ?? []).filter((v) => major >= v.minMajor).sort((a, b) => b.minMajor - a.minMajor)[0];
11041
+ }
10981
11042
  function resolveArgs(manifest, ctx) {
10982
11043
  const args = [];
10983
11044
  const env = {};
10984
- if (manifest.runner.env) {
10985
- for (const [key, value] of Object.entries(manifest.runner.env)) {
11045
+ const versioned = versionedInvocation(manifest, ctx);
11046
+ const argsTemplate = versioned?.argsTemplate ?? manifest.runner.argsTemplate;
11047
+ const envTemplate = versioned ? versioned.env : manifest.runner.env;
11048
+ if (envTemplate) {
11049
+ for (const [key, value] of Object.entries(envTemplate)) {
10986
11050
  const resolved = substituteSimple(value, ctx);
10987
11051
  if (resolved.length > 0) env[key] = resolved;
10988
11052
  }
10989
11053
  }
10990
11054
  let block = 0 /* None */;
10991
11055
  let include = true;
10992
- for (let i = 0; i < manifest.runner.argsTemplate.length; i++) {
10993
- const raw = manifest.runner.argsTemplate[i];
11056
+ for (let i = 0; i < argsTemplate.length; i++) {
11057
+ const raw = argsTemplate[i];
10994
11058
  const blockOpen = parseBlockOpen(raw);
10995
11059
  if (blockOpen !== null) {
10996
11060
  if (block !== 0 /* None */) throw new ResolveError(`Nested conditional block at position ${i}: "${raw}"`);
@@ -11049,6 +11113,8 @@ function parseBlockOpen(raw) {
11049
11113
  return 9 /* IfInteractiveVariant */;
11050
11114
  case "{{if projectTrust}}":
11051
11115
  return 10 /* IfProjectTrust */;
11116
+ case "{{if headlessModel}}":
11117
+ return 11 /* IfHeadlessModel */;
11052
11118
  default:
11053
11119
  return null;
11054
11120
  }
@@ -11088,6 +11154,9 @@ function shouldIncludeBlock(block, ctx) {
11088
11154
  // there is no cwd to name.
11089
11155
  case 10 /* IfProjectTrust */:
11090
11156
  return isInteractive(ctx) && !!ctx.cwd;
11157
+ // A model flag that only the non-interactive subcommand takes.
11158
+ case 11 /* IfHeadlessModel */:
11159
+ return !!ctx.model && !isInteractive(ctx);
11091
11160
  default:
11092
11161
  return true;
11093
11162
  }
@@ -11133,6 +11202,10 @@ function resolveToken(token, manifest, ctx) {
11133
11202
  if (token === "{{model}}") return ctx.model || "";
11134
11203
  if (token === "{{thinkingEffort}}") return ctx.thinkingEffort || "";
11135
11204
  if (token === "{{mode}}") return ctx.mode || "build";
11205
+ if (token === "{{modelWithVariant}}") {
11206
+ if (!ctx.model) return "";
11207
+ return ctx.thinkingEffort ? `${ctx.model}#${ctx.thinkingEffort}` : ctx.model;
11208
+ }
11136
11209
  if (token === "{{projectTrust}}") {
11137
11210
  if (!ctx.cwd) return "";
11138
11211
  return `projects.${JSON.stringify(ctx.cwd)}.trust_level="trusted"`;
@@ -11207,13 +11280,21 @@ function resolveOpencodeVariantConfig(ctx) {
11207
11280
  }
11208
11281
  return JSON.stringify(config);
11209
11282
  }
11283
+ function resolveOpencodeSessionConfig(ctx) {
11284
+ const mode = ctx.mode || "build";
11285
+ const pinned = resolveOpencodeVariantConfig(ctx);
11286
+ const config = pinned ? JSON.parse(pinned) : {};
11287
+ config.default_agent = mode;
11288
+ if (ctx.model && !pinned) config.agent = { [mode]: { model: ctx.model } };
11289
+ return JSON.stringify(config);
11290
+ }
11210
11291
  function substituteSimple(value, ctx) {
11211
11292
  const conditioned = value.replace(/\{\{if (\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (match, name, body) => {
11212
11293
  const block = parseBlockOpen(`{{if ${name}}}`);
11213
11294
  if (block === null) return match;
11214
11295
  return shouldIncludeBlock(block, ctx) ? body : "";
11215
11296
  });
11216
- return conditioned.replace(/\{\{opencodeVariantConfig\}\}/g, () => resolveOpencodeVariantConfig(ctx)).replace(/\{\{prompt\}\}/g, ctx.prompt).replace(/\{\{model\}\}/g, ctx.model || "").replace(/\{\{thinkingEffort\}\}/g, ctx.thinkingEffort || "").replace(/\{\{mode\}\}/g, ctx.mode || "build");
11297
+ return conditioned.replace(/\{\{opencodeVariantConfig\}\}/g, () => resolveOpencodeVariantConfig(ctx)).replace(/\{\{opencodeSessionConfig\}\}/g, () => resolveOpencodeSessionConfig(ctx)).replace(/\{\{prompt\}\}/g, ctx.prompt).replace(/\{\{model\}\}/g, ctx.model || "").replace(/\{\{thinkingEffort\}\}/g, ctx.thinkingEffort || "").replace(/\{\{mode\}\}/g, ctx.mode || "build");
11217
11298
  }
11218
11299
 
11219
11300
  // src/utils/launch.ts
@@ -20138,13 +20219,33 @@ function buildRunnerInvocation(opts) {
20138
20219
  mode: opts.mode ?? "build",
20139
20220
  headless: opts.headless,
20140
20221
  interactive: opts.interactive,
20141
- cwd: opts.cwd
20222
+ cwd: opts.cwd,
20223
+ runnerVersion: opts.runnerVersion
20142
20224
  });
20143
20225
  }
20144
20226
 
20145
20227
  // src/services/HeadlessRunner.ts
20146
20228
  import { spawn as nodeSpawn2, execSync } from "child_process";
20147
20229
 
20230
+ // src/services/runnerVersion.ts
20231
+ import { execFile as execFile4 } from "child_process";
20232
+ var VERSION_TIMEOUT_MS = 5e3;
20233
+ var cache = /* @__PURE__ */ new Map();
20234
+ var probeRunnerVersion = (command, PATH) => {
20235
+ const key = `${command}\0${PATH}`;
20236
+ let pending = cache.get(key);
20237
+ if (!pending) {
20238
+ pending = new Promise((resolve7) => {
20239
+ execFile4(command, ["--version"], { env: { ...process.env, PATH }, timeout: VERSION_TIMEOUT_MS }, (err, stdout) => {
20240
+ const line = String(stdout ?? "").trim();
20241
+ resolve7(err || !line ? void 0 : line);
20242
+ });
20243
+ });
20244
+ cache.set(key, pending);
20245
+ }
20246
+ return pending;
20247
+ };
20248
+
20148
20249
  // src/services/AbstractRunner.ts
20149
20250
  import { EventEmitter } from "events";
20150
20251
  var AbstractTerminalSession = class {
@@ -20265,6 +20366,10 @@ Process error: ${err.message}
20265
20366
  this.process.stdin.write(text);
20266
20367
  }
20267
20368
  }
20369
+ /** Closes the runner's stdin: what it has read so far is all it will get. */
20370
+ endInput() {
20371
+ this.process?.stdin?.end();
20372
+ }
20268
20373
  /** PTY resize requests from a surface that owns the terminal rendering the wrapper. */
20269
20374
  writeControl(text) {
20270
20375
  if (this.controlStream && !this.controlStream.destroyed) {
@@ -20277,6 +20382,7 @@ var HeadlessRunner = class extends AbstractRunner {
20277
20382
  hasScriptCmd;
20278
20383
  resolvePath;
20279
20384
  launchDeps;
20385
+ runnerVersion;
20280
20386
  spawnCount = 0;
20281
20387
  /**
20282
20388
  * Session shape: a piped subprocess is not a terminal, so runners get their
@@ -20292,6 +20398,7 @@ var HeadlessRunner = class extends AbstractRunner {
20292
20398
  this.hasScriptCmd = deps.hasScriptCmd ?? defaultHasScriptCmd;
20293
20399
  this.resolvePath = deps.resolvePath ?? augmentedPath;
20294
20400
  this.launchDeps = { resolvePath: this.resolvePath, ...deps.launchDeps };
20401
+ this.runnerVersion = deps.runnerVersion ?? probeRunnerVersion;
20295
20402
  }
20296
20403
  /**
20297
20404
  * Unique per spawn, not per task: a retry reuses its task id and ids often
@@ -20309,6 +20416,9 @@ var HeadlessRunner = class extends AbstractRunner {
20309
20416
  /** Everything up to, but not including, spawning — so a surface that owns its own child reaches the same decisions. */
20310
20417
  async prepareLaunch(opts, ptyOptions) {
20311
20418
  const interactive = this.defaultInteractive;
20419
+ const resolvedPath = await this.resolvePath();
20420
+ const manifest = opts.registry?.get(opts.runner)?.manifest;
20421
+ const runnerVersion = manifest?.runner.versioned?.length ? await this.runnerVersion(manifest.runner.command, resolvedPath) : void 0;
20312
20422
  const invocation = buildRunnerInvocation({
20313
20423
  runner: opts.runner,
20314
20424
  prompt: opts.prompt,
@@ -20319,18 +20429,18 @@ var HeadlessRunner = class extends AbstractRunner {
20319
20429
  headless: opts.headless ?? true,
20320
20430
  interactive,
20321
20431
  cwd: opts.cwd,
20432
+ runnerVersion,
20322
20433
  registry: opts.registry
20323
20434
  });
20324
- const manifest = opts.registry?.get(opts.runner)?.manifest;
20325
20435
  const pty = (manifest?.runner.requiresTty === true || interactive) && this.hasScriptCmd();
20326
- const resolvedPath = await this.resolvePath();
20327
- const { command, args } = pty ? wrapWithPty(invocation.command, invocation.args, ptyOptions) : { command: invocation.command, args: invocation.args };
20436
+ const { command, args } = pty ? wrapWithPty(invocation.command, invocation.args, { platform: this.launchDeps.platform, ...ptyOptions }) : { command: invocation.command, args: invocation.args };
20328
20437
  return {
20329
20438
  launch: await planDirectLaunch(command, args, this.launchDeps),
20330
20439
  resolvedPath,
20331
20440
  env: { ...opts.env, ...invocation.env },
20332
20441
  pty,
20333
- submitPromptKey: invocation.submitPromptKey
20442
+ submitPromptKey: invocation.submitPromptKey,
20443
+ closeStdin: !pty && !interactive && invocation.promptInArgs
20334
20444
  };
20335
20445
  }
20336
20446
  async spawn(opts) {
@@ -20341,13 +20451,14 @@ var HeadlessRunner = class extends AbstractRunner {
20341
20451
  console.error(`[headless] Starting ${opts.runner} [${modeStr}] (${opts.modelId || "default"}) for task ${opts.taskId.slice(0, 8)}${prepared.pty ? " (PTY)" : ""}`);
20342
20452
  session.start(prepared.launch, opts.cwd, prepared.resolvedPath, prepared.env);
20343
20453
  if (prepared.submitPromptKey) session.write("\r");
20454
+ if (prepared.closeStdin) session.endInput();
20344
20455
  this.registerSession(id, session);
20345
20456
  return session;
20346
20457
  }
20347
20458
  };
20348
20459
 
20349
20460
  // src/services/TmuxRunner.ts
20350
- import { execFile as execFile4 } from "child_process";
20461
+ import { execFile as execFile5 } from "child_process";
20351
20462
  import { promisify as promisify5 } from "util";
20352
20463
  import { tmpdir } from "os";
20353
20464
  import { join as join19 } from "path";
@@ -20404,7 +20515,7 @@ function clipboardCopyCommand(hasBin = defaultHasBin, platform = process.platfor
20404
20515
  var EXIT_RE = /<<<ORDEWELL_TMUX_EXIT:(\d+)>>>/;
20405
20516
  var LIVENESS_INTERVAL_MS = 5e3;
20406
20517
  var LIVENESS_MISSES = 2;
20407
- var execFileAsync2 = promisify5(execFile4);
20518
+ var execFileAsync2 = promisify5(execFile5);
20408
20519
  var defaultExecFile3 = async (command, args) => {
20409
20520
  const { stdout, stderr } = await execFileAsync2(command, args);
20410
20521
  return { stdout: String(stdout), stderr: String(stderr) };
@@ -20554,6 +20665,7 @@ var TmuxRunner = class extends AbstractRunner {
20554
20665
  pollIntervalMs;
20555
20666
  logDir;
20556
20667
  clipboardCommand;
20668
+ runnerVersion;
20557
20669
  /** Retries get a freshly named window so a failed attempt's output stays inspectable. */
20558
20670
  attempts = /* @__PURE__ */ new Map();
20559
20671
  ready = null;
@@ -20566,6 +20678,7 @@ var TmuxRunner = class extends AbstractRunner {
20566
20678
  this.pollIntervalMs = deps.pollIntervalMs ?? 500;
20567
20679
  this.logDir = deps.logDir ?? tmpdir();
20568
20680
  this.clipboardCommand = deps.clipboardCommand ?? (() => clipboardCopyCommand());
20681
+ this.runnerVersion = deps.runnerVersion ?? probeRunnerVersion;
20569
20682
  }
20570
20683
  /**
20571
20684
  * Reaps a session orphaned by a crashed prior daemon on the same port, then
@@ -20649,6 +20762,8 @@ var TmuxRunner = class extends AbstractRunner {
20649
20762
  }
20650
20763
  }
20651
20764
  async spawn(opts) {
20765
+ const manifest = opts.registry?.get(opts.runner)?.manifest;
20766
+ const runnerVersion = manifest?.runner.versioned?.length ? await this.runnerVersion(manifest.runner.command, await this.resolvePath()) : void 0;
20652
20767
  const invocation = buildRunnerInvocation({
20653
20768
  runner: opts.runner,
20654
20769
  prompt: opts.prompt,
@@ -20664,6 +20779,7 @@ var TmuxRunner = class extends AbstractRunner {
20664
20779
  headless: opts.headless ?? true,
20665
20780
  interactive: true,
20666
20781
  cwd: opts.cwd,
20782
+ runnerVersion,
20667
20783
  registry: opts.registry
20668
20784
  });
20669
20785
  await this.ensureSession();
@@ -21580,6 +21696,37 @@ var OPENCODE_MANIFEST = {
21580
21696
  env: {
21581
21697
  OPENCODE_CONFIG_CONTENT: "{{if interactiveVariant}}{{opencodeVariantConfig}}{{/if}}"
21582
21698
  },
21699
+ // OpenCode 2.x changed both shapes. Its TUI takes no `--model`/`--agent`,
21700
+ // so they ride on OPENCODE_CONFIG_CONTENT instead, and its `run` has no
21701
+ // `--variant` — the variant is a `#suffix` on the model id. Both run
21702
+ // `--standalone`: without it the TUI and `run` attach to the user's
21703
+ // background service, which neither reads this process's config nor
21704
+ // starts in the task's directory and environment.
21705
+ versioned: [{
21706
+ minMajor: 2,
21707
+ argsTemplate: [
21708
+ "{{if headlessSession}}",
21709
+ "run",
21710
+ "--agent",
21711
+ "{{mode}}",
21712
+ "{{/if}}",
21713
+ "{{if headlessModel}}",
21714
+ "--model",
21715
+ "{{modelWithVariant}}",
21716
+ "{{/if}}",
21717
+ "--standalone",
21718
+ "{{if buildMode}}",
21719
+ "--auto",
21720
+ "{{/if}}",
21721
+ "{{if interactive}}",
21722
+ "--prompt",
21723
+ "{{/if}}",
21724
+ "{{prompt}}"
21725
+ ],
21726
+ env: {
21727
+ OPENCODE_CONFIG_CONTENT: "{{if interactive}}{{opencodeSessionConfig}}{{/if}}"
21728
+ }
21729
+ }],
21583
21730
  promptInArgs: true,
21584
21731
  requiresTty: true,
21585
21732
  // `--prompt` only pre-fills the TUI's composer; it does not submit it.