@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.js CHANGED
@@ -3119,6 +3119,8 @@ function lex(command, nested, dialect) {
3119
3119
  let unbalanced = false;
3120
3120
  let grouped = false;
3121
3121
  let joinedBy;
3122
+ let followable = true;
3123
+ let bareSegment = false;
3122
3124
  let tokens = [];
3123
3125
  let expandable = false;
3124
3126
  let current = "";
@@ -3148,9 +3150,20 @@ function lex(command, nested, dialect) {
3148
3150
  current = "";
3149
3151
  started = false;
3150
3152
  };
3151
- const endSegment = (nextPiped, next = "other") => {
3153
+ const endSegment = (nextPiped, next = "other", last = false) => {
3152
3154
  endToken();
3153
- if (tokens.length > 0) segments.push({ ...toSegment(tokens, piped, dialect), expandable, inputs, joinedBy });
3155
+ if (!last && next === "other") followable = false;
3156
+ if (tokens.length > 0) {
3157
+ const seg = toSegment(tokens, piped, dialect);
3158
+ if (!seg.binary) {
3159
+ followable = false;
3160
+ bareSegment = true;
3161
+ }
3162
+ segments.push({ ...seg, expandable, inputs, joinedBy });
3163
+ } else if (!last || segments.length > 0 || joinedBy !== void 0) {
3164
+ followable = false;
3165
+ bareSegment = true;
3166
+ }
3154
3167
  tokens = [];
3155
3168
  inputs = [];
3156
3169
  expandable = false;
@@ -3305,8 +3318,8 @@ function lex(command, nested, dialect) {
3305
3318
  i++;
3306
3319
  }
3307
3320
  if (quote !== "") unbalanced = true;
3308
- endSegment(false);
3309
- return { segments, unsafeRedirect, processSubstitution, unbalanced, grouped };
3321
+ endSegment(false, "other", true);
3322
+ return { segments, unsafeRedirect, processSubstitution, unbalanced, grouped, followable, bareSegment };
3310
3323
  }
3311
3324
  function binaryName(token, dialect) {
3312
3325
  const base = token.split(/[/\\]/).pop() ?? "";
@@ -3450,7 +3463,8 @@ function lexAll(command, dialect, cdpathSet = false) {
3450
3463
  const queue = [];
3451
3464
  const top = lex(command, queue, dialect);
3452
3465
  const all = { ...top, segments: top.segments.filter((s) => s.binary) };
3453
- if (!top.grouped && queue.length === 0 && !cdpathSet) followCd(all.segments);
3466
+ markShellState(all.segments, top.bareSegment);
3467
+ if (top.followable && !top.grouped && queue.length === 0 && !cdpathSet) followCd(all.segments);
3454
3468
  for (let depth = 0; depth < 32 && queue.length > 0; depth++) {
3455
3469
  const inner = lex(queue.shift(), queue, dialect);
3456
3470
  all.segments.push(...inner.segments.filter((s) => s.binary));
@@ -3467,22 +3481,51 @@ function literalCdTarget(seg) {
3467
3481
  return target === "" || target.startsWith("-") || target.startsWith("~") || /[*?[]/.test(target) ? void 0 : target;
3468
3482
  }
3469
3483
  function followCd(segments) {
3470
- if (segments.some((s) => s.joinedBy === "other")) return;
3484
+ if (segments.some((s) => s.joinedBy === "other" || SHELL_STATE_COMMANDS.has(s.binary))) return;
3485
+ const inPipeline = (i) => segments[i].joinedBy === "pipe" || segments[i + 1]?.joinedBy === "pipe";
3486
+ if (segments.some((s, i) => s.binary === "cd" && !inPipeline(i) && literalCdTarget(s) === void 0)) return;
3471
3487
  let chain = [];
3472
3488
  segments.forEach((seg, i) => {
3473
3489
  seg.cwd = chain;
3474
- const inPipeline = seg.joinedBy === "pipe" || segments[i + 1]?.joinedBy === "pipe";
3475
- const target = inPipeline ? void 0 : literalCdTarget(seg);
3490
+ const target = inPipeline(i) ? void 0 : literalCdTarget(seg);
3476
3491
  if (target !== void 0) chain = [...chain, target];
3477
3492
  });
3478
3493
  }
3494
+ function markShellState(segments, bareSegment) {
3495
+ if (!bareSegment && !segments.some((s) => SHELL_STATE_COMMANDS.has(s.binary))) return;
3496
+ for (const seg of segments) if (seg.binary === "cd") seg.cdSteered = true;
3497
+ }
3498
+ var SHELL_STATE_COMMANDS = /* @__PURE__ */ new Set([
3499
+ "export",
3500
+ "set",
3501
+ "shopt",
3502
+ "declare",
3503
+ "typeset",
3504
+ "readonly",
3505
+ "local",
3506
+ "unset",
3507
+ "let",
3508
+ "source",
3509
+ ".",
3510
+ "eval",
3511
+ "exec",
3512
+ "pushd",
3513
+ "popd",
3514
+ "alias",
3515
+ "unalias",
3516
+ "builtin",
3517
+ "command",
3518
+ "enable",
3519
+ "hash",
3520
+ "trap"
3521
+ ]);
3479
3522
  function looksLikePath(arg) {
3480
3523
  if (arg.startsWith("--") && arg.includes("=")) return looksLikePath(arg.slice(arg.indexOf("=") + 1));
3481
3524
  return arg.startsWith("/") || arg.startsWith("~") || arg.startsWith("../") || arg === ".." || arg.startsWith("./") || /^[A-Za-z]:/.test(arg) || arg.startsWith("\\") || arg.startsWith("..\\") || arg.startsWith(".\\") || /(^|[\\/])\.\.([\\/]|$)/.test(arg);
3482
3525
  }
3483
3526
  function pathRefs(command, opts = {}) {
3484
3527
  const dialect = dialectFor(opts.dialect);
3485
- return lexAll(command, dialect, opts.cdpathSet).segments.flatMap((seg) => {
3528
+ return lexAll(command.trim(), dialect, opts.cdpathSet).segments.flatMap((seg) => {
3486
3529
  const patterns = patternArgs(seg);
3487
3530
  const cwd = seg.cwd ?? [];
3488
3531
  return [...seg.args.filter((_, i) => !patterns.has(i)), ...seg.inputs].flatMap(pathIn).map((path27) => ({ path: path27, cwd }));
@@ -3650,7 +3693,7 @@ function scopeFor(seg) {
3650
3693
  return [seg.binary, ...lead].join(" ");
3651
3694
  }
3652
3695
  function isAuto(seg, opts) {
3653
- if (seg.binary === "cd") return !opts.cdpathSet && literalCdTarget(seg) !== void 0;
3696
+ if (seg.binary === "cd") return !opts.cdpathSet && !seg.cdSteered && literalCdTarget(seg) !== void 0;
3654
3697
  if (!AUTO_COMMANDS.includes(seg.binary)) return false;
3655
3698
  if (seg.expandable) return false;
3656
3699
  if (seg.binary !== "git") return true;
@@ -3852,6 +3895,9 @@ function researchShellWarning(shell) {
3852
3895
  if (shell.dialect !== "cmd") return null;
3853
3896
  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.";
3854
3897
  }
3898
+ function researchPolicyOptions(shell) {
3899
+ return { dialect: shell.dialect, cdpathSet: !!process.env.CDPATH };
3900
+ }
3855
3901
 
3856
3902
  // src/services/pathScope.ts
3857
3903
  var os = __toESM(require("os"));
@@ -4085,7 +4131,7 @@ ${refs.output.trim()}`);
4085
4131
  return [...found].map(([scope, abs]) => ({ abs, scope }));
4086
4132
  }
4087
4133
  policyOptions() {
4088
- return { dialect: this.researchShell.dialect, cdpathSet: !!process.env.CDPATH };
4134
+ return researchPolicyOptions(this.researchShell);
4089
4135
  }
4090
4136
  /**
4091
4137
  * Three tiers (see `commandPolicy.ts`): read-only inspection runs silently,
@@ -10532,16 +10578,15 @@ function nonPromptingFs(fs16) {
10532
10578
  findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs16.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
10533
10579
  listDir: (p, depth) => withinWorkspace(p) ? fs16.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
10534
10580
  bash: async (command, signal) => {
10535
- const dialect = resolveResearchShell().dialect;
10536
- const policy = { dialect, cdpathSet: !!process.env.CDPATH };
10581
+ const policy = researchPolicyOptions(resolveResearchShell());
10537
10582
  const { tier, reason } = classifyCommand(command, policy);
10538
10583
  if (tier !== "auto") {
10539
10584
  return refused2(
10540
10585
  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.`
10541
10586
  );
10542
10587
  }
10543
- const escaping = pathRefs(command, policy).find((ref) => !resolveRef(fs16.getWorkspaceRoot(), ref).inside);
10544
- if (escaping) return outsideRefusal(escaping.path);
10588
+ const escaping = pathRefs(command, policy).map((ref) => resolveRef(fs16.getWorkspaceRoot(), ref)).find((r) => !r.inside);
10589
+ if (escaping) return outsideRefusal(escaping.abs);
10545
10590
  return fs16.bash(command, signal);
10546
10591
  }
10547
10592
  };
@@ -13578,11 +13623,17 @@ function buildShellInvocation(command, args) {
13578
13623
  const inner = [command, ...args].map(posixShellQuote).join(" ");
13579
13624
  return { shellPath: "/bin/bash", shellArgs: ["-lc", inner] };
13580
13625
  }
13626
+ var BSD_SCRIPT_PLATFORMS = /* @__PURE__ */ new Set(["darwin", "freebsd", "openbsd", "netbsd"]);
13627
+ 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; }';
13581
13628
  function wrapWithPty(command, args, opts = {}) {
13582
13629
  const inner = [command, ...args].map(posixShellQuote).join(" ");
13583
13630
  const watcher = opts.controlChannel ? 'exec 4<&0; ( while read -r C R <&3; do stty cols "$C" rows "$R" <&4; done ) & ' : "";
13584
13631
  const setup = opts.size ? `stty cols ${opts.size.cols} rows ${opts.size.rows}; ` : "";
13585
- return { command: "script", args: ["-q", "-e", "-f", "-c", `${watcher}${setup}${inner}`, "/dev/null"] };
13632
+ const line = `${watcher}${setup}${inner}`;
13633
+ if (BSD_SCRIPT_PLATFORMS.has(opts.platform ?? process.platform)) {
13634
+ return { command: "/bin/sh", args: ["-c", BSD_SCRIPT_FEED, line] };
13635
+ }
13636
+ return { command: "script", args: ["-q", "-e", "-f", "-c", line, "/dev/null"] };
13586
13637
  }
13587
13638
 
13588
13639
  // src/services/blockingPrompts.ts
@@ -13777,24 +13828,37 @@ var Block = /* @__PURE__ */ ((Block2) => {
13777
13828
  Block2[Block2["IfHeadlessSession"] = 8] = "IfHeadlessSession";
13778
13829
  Block2[Block2["IfInteractiveVariant"] = 9] = "IfInteractiveVariant";
13779
13830
  Block2[Block2["IfProjectTrust"] = 10] = "IfProjectTrust";
13831
+ Block2[Block2["IfHeadlessModel"] = 11] = "IfHeadlessModel";
13780
13832
  return Block2;
13781
13833
  })(Block || {});
13782
13834
  function isInteractive(ctx) {
13783
13835
  return ctx.interactive ?? !ctx.headless;
13784
13836
  }
13837
+ function majorVersion(version) {
13838
+ const match = version?.match(/(\d+)\.\d+/);
13839
+ return match ? Number(match[1]) : void 0;
13840
+ }
13841
+ function versionedInvocation(manifest, ctx) {
13842
+ const major = majorVersion(ctx.runnerVersion);
13843
+ if (major === void 0) return void 0;
13844
+ return (manifest.runner.versioned ?? []).filter((v) => major >= v.minMajor).sort((a, b) => b.minMajor - a.minMajor)[0];
13845
+ }
13785
13846
  function resolveArgs(manifest, ctx) {
13786
13847
  const args = [];
13787
13848
  const env = {};
13788
- if (manifest.runner.env) {
13789
- for (const [key, value] of Object.entries(manifest.runner.env)) {
13849
+ const versioned = versionedInvocation(manifest, ctx);
13850
+ const argsTemplate = versioned?.argsTemplate ?? manifest.runner.argsTemplate;
13851
+ const envTemplate = versioned ? versioned.env : manifest.runner.env;
13852
+ if (envTemplate) {
13853
+ for (const [key, value] of Object.entries(envTemplate)) {
13790
13854
  const resolved = substituteSimple(value, ctx);
13791
13855
  if (resolved.length > 0) env[key] = resolved;
13792
13856
  }
13793
13857
  }
13794
13858
  let block = 0 /* None */;
13795
13859
  let include = true;
13796
- for (let i = 0; i < manifest.runner.argsTemplate.length; i++) {
13797
- const raw = manifest.runner.argsTemplate[i];
13860
+ for (let i = 0; i < argsTemplate.length; i++) {
13861
+ const raw = argsTemplate[i];
13798
13862
  const blockOpen = parseBlockOpen(raw);
13799
13863
  if (blockOpen !== null) {
13800
13864
  if (block !== 0 /* None */) throw new ResolveError(`Nested conditional block at position ${i}: "${raw}"`);
@@ -13853,6 +13917,8 @@ function parseBlockOpen(raw) {
13853
13917
  return 9 /* IfInteractiveVariant */;
13854
13918
  case "{{if projectTrust}}":
13855
13919
  return 10 /* IfProjectTrust */;
13920
+ case "{{if headlessModel}}":
13921
+ return 11 /* IfHeadlessModel */;
13856
13922
  default:
13857
13923
  return null;
13858
13924
  }
@@ -13892,6 +13958,9 @@ function shouldIncludeBlock(block, ctx) {
13892
13958
  // there is no cwd to name.
13893
13959
  case 10 /* IfProjectTrust */:
13894
13960
  return isInteractive(ctx) && !!ctx.cwd;
13961
+ // A model flag that only the non-interactive subcommand takes.
13962
+ case 11 /* IfHeadlessModel */:
13963
+ return !!ctx.model && !isInteractive(ctx);
13895
13964
  default:
13896
13965
  return true;
13897
13966
  }
@@ -13937,6 +14006,10 @@ function resolveToken(token, manifest, ctx) {
13937
14006
  if (token === "{{model}}") return ctx.model || "";
13938
14007
  if (token === "{{thinkingEffort}}") return ctx.thinkingEffort || "";
13939
14008
  if (token === "{{mode}}") return ctx.mode || "build";
14009
+ if (token === "{{modelWithVariant}}") {
14010
+ if (!ctx.model) return "";
14011
+ return ctx.thinkingEffort ? `${ctx.model}#${ctx.thinkingEffort}` : ctx.model;
14012
+ }
13940
14013
  if (token === "{{projectTrust}}") {
13941
14014
  if (!ctx.cwd) return "";
13942
14015
  return `projects.${JSON.stringify(ctx.cwd)}.trust_level="trusted"`;
@@ -14011,13 +14084,21 @@ function resolveOpencodeVariantConfig(ctx) {
14011
14084
  }
14012
14085
  return JSON.stringify(config);
14013
14086
  }
14087
+ function resolveOpencodeSessionConfig(ctx) {
14088
+ const mode = ctx.mode || "build";
14089
+ const pinned = resolveOpencodeVariantConfig(ctx);
14090
+ const config = pinned ? JSON.parse(pinned) : {};
14091
+ config.default_agent = mode;
14092
+ if (ctx.model && !pinned) config.agent = { [mode]: { model: ctx.model } };
14093
+ return JSON.stringify(config);
14094
+ }
14014
14095
  function substituteSimple(value, ctx) {
14015
14096
  const conditioned = value.replace(/\{\{if (\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (match, name, body) => {
14016
14097
  const block = parseBlockOpen(`{{if ${name}}}`);
14017
14098
  if (block === null) return match;
14018
14099
  return shouldIncludeBlock(block, ctx) ? body : "";
14019
14100
  });
14020
- 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");
14101
+ 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");
14021
14102
  }
14022
14103
 
14023
14104
  // src/utils/launch.ts
@@ -23445,12 +23526,32 @@ function buildRunnerInvocation(opts) {
23445
23526
  mode: opts.mode ?? "build",
23446
23527
  headless: opts.headless,
23447
23528
  interactive: opts.interactive,
23448
- cwd: opts.cwd
23529
+ cwd: opts.cwd,
23530
+ runnerVersion: opts.runnerVersion
23449
23531
  });
23450
23532
  }
23451
23533
 
23452
23534
  // src/services/HeadlessRunner.ts
23535
+ var import_child_process9 = require("child_process");
23536
+
23537
+ // src/services/runnerVersion.ts
23453
23538
  var import_child_process8 = require("child_process");
23539
+ var VERSION_TIMEOUT_MS = 5e3;
23540
+ var cache = /* @__PURE__ */ new Map();
23541
+ var probeRunnerVersion = (command, PATH) => {
23542
+ const key = `${command}\0${PATH}`;
23543
+ let pending = cache.get(key);
23544
+ if (!pending) {
23545
+ pending = new Promise((resolve7) => {
23546
+ (0, import_child_process8.execFile)(command, ["--version"], { env: { ...process.env, PATH }, timeout: VERSION_TIMEOUT_MS }, (err, stdout) => {
23547
+ const line = String(stdout ?? "").trim();
23548
+ resolve7(err || !line ? void 0 : line);
23549
+ });
23550
+ });
23551
+ cache.set(key, pending);
23552
+ }
23553
+ return pending;
23554
+ };
23454
23555
 
23455
23556
  // src/services/AbstractRunner.ts
23456
23557
  var import_events = require("events");
@@ -23505,7 +23606,7 @@ var AbstractRunner = class {
23505
23606
  function defaultHasScriptCmd() {
23506
23607
  if (process.platform === "win32") return false;
23507
23608
  try {
23508
- (0, import_child_process8.execSync)("which script", { stdio: "ignore" });
23609
+ (0, import_child_process9.execSync)("which script", { stdio: "ignore" });
23509
23610
  return true;
23510
23611
  } catch {
23511
23612
  return false;
@@ -23572,6 +23673,10 @@ Process error: ${err.message}
23572
23673
  this.process.stdin.write(text2);
23573
23674
  }
23574
23675
  }
23676
+ /** Closes the runner's stdin: what it has read so far is all it will get. */
23677
+ endInput() {
23678
+ this.process?.stdin?.end();
23679
+ }
23575
23680
  /** PTY resize requests from a surface that owns the terminal rendering the wrapper. */
23576
23681
  writeControl(text2) {
23577
23682
  if (this.controlStream && !this.controlStream.destroyed) {
@@ -23584,6 +23689,7 @@ var HeadlessRunner = class extends AbstractRunner {
23584
23689
  hasScriptCmd;
23585
23690
  resolvePath;
23586
23691
  launchDeps;
23692
+ runnerVersion;
23587
23693
  spawnCount = 0;
23588
23694
  /**
23589
23695
  * Session shape: a piped subprocess is not a terminal, so runners get their
@@ -23595,10 +23701,11 @@ var HeadlessRunner = class extends AbstractRunner {
23595
23701
  defaultInteractive = false;
23596
23702
  constructor(deps = {}) {
23597
23703
  super();
23598
- this.spawnImpl = deps.spawnImpl ?? import_child_process8.spawn;
23704
+ this.spawnImpl = deps.spawnImpl ?? import_child_process9.spawn;
23599
23705
  this.hasScriptCmd = deps.hasScriptCmd ?? defaultHasScriptCmd;
23600
23706
  this.resolvePath = deps.resolvePath ?? augmentedPath;
23601
23707
  this.launchDeps = { resolvePath: this.resolvePath, ...deps.launchDeps };
23708
+ this.runnerVersion = deps.runnerVersion ?? probeRunnerVersion;
23602
23709
  }
23603
23710
  /**
23604
23711
  * Unique per spawn, not per task: a retry reuses its task id and ids often
@@ -23616,6 +23723,9 @@ var HeadlessRunner = class extends AbstractRunner {
23616
23723
  /** Everything up to, but not including, spawning — so a surface that owns its own child reaches the same decisions. */
23617
23724
  async prepareLaunch(opts, ptyOptions) {
23618
23725
  const interactive = this.defaultInteractive;
23726
+ const resolvedPath = await this.resolvePath();
23727
+ const manifest = opts.registry?.get(opts.runner)?.manifest;
23728
+ const runnerVersion = manifest?.runner.versioned?.length ? await this.runnerVersion(manifest.runner.command, resolvedPath) : void 0;
23619
23729
  const invocation = buildRunnerInvocation({
23620
23730
  runner: opts.runner,
23621
23731
  prompt: opts.prompt,
@@ -23626,18 +23736,18 @@ var HeadlessRunner = class extends AbstractRunner {
23626
23736
  headless: opts.headless ?? true,
23627
23737
  interactive,
23628
23738
  cwd: opts.cwd,
23739
+ runnerVersion,
23629
23740
  registry: opts.registry
23630
23741
  });
23631
- const manifest = opts.registry?.get(opts.runner)?.manifest;
23632
23742
  const pty = (manifest?.runner.requiresTty === true || interactive) && this.hasScriptCmd();
23633
- const resolvedPath = await this.resolvePath();
23634
- const { command, args } = pty ? wrapWithPty(invocation.command, invocation.args, ptyOptions) : { command: invocation.command, args: invocation.args };
23743
+ const { command, args } = pty ? wrapWithPty(invocation.command, invocation.args, { platform: this.launchDeps.platform, ...ptyOptions }) : { command: invocation.command, args: invocation.args };
23635
23744
  return {
23636
23745
  launch: await planDirectLaunch(command, args, this.launchDeps),
23637
23746
  resolvedPath,
23638
23747
  env: { ...opts.env, ...invocation.env },
23639
23748
  pty,
23640
- submitPromptKey: invocation.submitPromptKey
23749
+ submitPromptKey: invocation.submitPromptKey,
23750
+ closeStdin: !pty && !interactive && invocation.promptInArgs
23641
23751
  };
23642
23752
  }
23643
23753
  async spawn(opts) {
@@ -23648,20 +23758,21 @@ var HeadlessRunner = class extends AbstractRunner {
23648
23758
  console.error(`[headless] Starting ${opts.runner} [${modeStr}] (${opts.modelId || "default"}) for task ${opts.taskId.slice(0, 8)}${prepared.pty ? " (PTY)" : ""}`);
23649
23759
  session.start(prepared.launch, opts.cwd, prepared.resolvedPath, prepared.env);
23650
23760
  if (prepared.submitPromptKey) session.write("\r");
23761
+ if (prepared.closeStdin) session.endInput();
23651
23762
  this.registerSession(id, session);
23652
23763
  return session;
23653
23764
  }
23654
23765
  };
23655
23766
 
23656
23767
  // src/services/TmuxRunner.ts
23657
- var import_child_process10 = require("child_process");
23768
+ var import_child_process11 = require("child_process");
23658
23769
  var import_util5 = require("util");
23659
23770
  var import_os = require("os");
23660
23771
  var import_path2 = require("path");
23661
23772
  var import_fs3 = require("fs");
23662
23773
 
23663
23774
  // src/utils/tmux.ts
23664
- var import_child_process9 = require("child_process");
23775
+ var import_child_process10 = require("child_process");
23665
23776
  function tmuxSessionName(port) {
23666
23777
  return `ordewell-${port}`;
23667
23778
  }
@@ -23674,7 +23785,7 @@ function tmuxWindowName(taskId, planSessionId) {
23674
23785
  return `t-${scope}${slug(taskId).slice(0, 16)}`;
23675
23786
  }
23676
23787
  var defaultProbe = () => {
23677
- (0, import_child_process9.execSync)(process.platform === "win32" ? "where tmux" : "which tmux", { stdio: "ignore" });
23788
+ (0, import_child_process10.execSync)(process.platform === "win32" ? "where tmux" : "which tmux", { stdio: "ignore" });
23678
23789
  };
23679
23790
  function hasTmux(probe = defaultProbe) {
23680
23791
  try {
@@ -23686,7 +23797,7 @@ function hasTmux(probe = defaultProbe) {
23686
23797
  }
23687
23798
  var defaultHasBin = (bin) => {
23688
23799
  try {
23689
- (0, import_child_process9.execSync)(`${process.platform === "win32" ? "where" : "which"} ${bin}`, { stdio: "ignore" });
23800
+ (0, import_child_process10.execSync)(`${process.platform === "win32" ? "where" : "which"} ${bin}`, { stdio: "ignore" });
23690
23801
  return true;
23691
23802
  } catch {
23692
23803
  return false;
@@ -23711,7 +23822,7 @@ function clipboardCopyCommand(hasBin = defaultHasBin, platform = process.platfor
23711
23822
  var EXIT_RE = /<<<ORDEWELL_TMUX_EXIT:(\d+)>>>/;
23712
23823
  var LIVENESS_INTERVAL_MS = 5e3;
23713
23824
  var LIVENESS_MISSES = 2;
23714
- var execFileAsync2 = (0, import_util5.promisify)(import_child_process10.execFile);
23825
+ var execFileAsync2 = (0, import_util5.promisify)(import_child_process11.execFile);
23715
23826
  var defaultExecFile3 = async (command, args) => {
23716
23827
  const { stdout, stderr } = await execFileAsync2(command, args);
23717
23828
  return { stdout: String(stdout), stderr: String(stderr) };
@@ -23861,6 +23972,7 @@ var TmuxRunner = class extends AbstractRunner {
23861
23972
  pollIntervalMs;
23862
23973
  logDir;
23863
23974
  clipboardCommand;
23975
+ runnerVersion;
23864
23976
  /** Retries get a freshly named window so a failed attempt's output stays inspectable. */
23865
23977
  attempts = /* @__PURE__ */ new Map();
23866
23978
  ready = null;
@@ -23873,6 +23985,7 @@ var TmuxRunner = class extends AbstractRunner {
23873
23985
  this.pollIntervalMs = deps.pollIntervalMs ?? 500;
23874
23986
  this.logDir = deps.logDir ?? (0, import_os.tmpdir)();
23875
23987
  this.clipboardCommand = deps.clipboardCommand ?? (() => clipboardCopyCommand());
23988
+ this.runnerVersion = deps.runnerVersion ?? probeRunnerVersion;
23876
23989
  }
23877
23990
  /**
23878
23991
  * Reaps a session orphaned by a crashed prior daemon on the same port, then
@@ -23956,6 +24069,8 @@ var TmuxRunner = class extends AbstractRunner {
23956
24069
  }
23957
24070
  }
23958
24071
  async spawn(opts) {
24072
+ const manifest = opts.registry?.get(opts.runner)?.manifest;
24073
+ const runnerVersion = manifest?.runner.versioned?.length ? await this.runnerVersion(manifest.runner.command, await this.resolvePath()) : void 0;
23959
24074
  const invocation = buildRunnerInvocation({
23960
24075
  runner: opts.runner,
23961
24076
  prompt: opts.prompt,
@@ -23971,6 +24086,7 @@ var TmuxRunner = class extends AbstractRunner {
23971
24086
  headless: opts.headless ?? true,
23972
24087
  interactive: true,
23973
24088
  cwd: opts.cwd,
24089
+ runnerVersion,
23974
24090
  registry: opts.registry
23975
24091
  });
23976
24092
  await this.ensureSession();
@@ -24000,7 +24116,7 @@ var TmuxRunner = class extends AbstractRunner {
24000
24116
 
24001
24117
  // src/services/StructuredRunner.ts
24002
24118
  var import_events2 = require("events");
24003
- var import_child_process11 = require("child_process");
24119
+ var import_child_process12 = require("child_process");
24004
24120
  var DEFAULT_INTERRUPT_GRACE_MS = 5e3;
24005
24121
  var TOOL_ARG_MAX_CHARS = 120;
24006
24122
  var TURN_OPENING_EVENTS = /* @__PURE__ */ new Set(["assistant_text", "assistant_text_delta", "thinking", "thinking_delta", "tool_call"]);
@@ -24374,7 +24490,7 @@ var StructuredRunner = class extends AbstractRunner {
24374
24490
  const session = new StructuredSession(id, opts.taskId, {
24375
24491
  runner: opts.runner,
24376
24492
  deps: {
24377
- spawn: import_child_process11.spawn,
24493
+ spawn: import_child_process12.spawn,
24378
24494
  fetch: globalThis.fetch,
24379
24495
  ...this.processDeps,
24380
24496
  // Already resolved by the caller (ADR-0016); resolving it again here
@@ -24469,7 +24585,7 @@ function tokensMatch(presented, expected) {
24469
24585
  var fs13 = __toESM(require("fs"));
24470
24586
  var path24 = __toESM(require("path"));
24471
24587
  var os6 = __toESM(require("os"));
24472
- var import_child_process12 = require("child_process");
24588
+ var import_child_process13 = require("child_process");
24473
24589
  var import_crypto6 = require("crypto");
24474
24590
 
24475
24591
  // src/plugins/FsPluginStore.ts
@@ -24887,6 +25003,37 @@ var OPENCODE_MANIFEST = {
24887
25003
  env: {
24888
25004
  OPENCODE_CONFIG_CONTENT: "{{if interactiveVariant}}{{opencodeVariantConfig}}{{/if}}"
24889
25005
  },
25006
+ // OpenCode 2.x changed both shapes. Its TUI takes no `--model`/`--agent`,
25007
+ // so they ride on OPENCODE_CONFIG_CONTENT instead, and its `run` has no
25008
+ // `--variant` — the variant is a `#suffix` on the model id. Both run
25009
+ // `--standalone`: without it the TUI and `run` attach to the user's
25010
+ // background service, which neither reads this process's config nor
25011
+ // starts in the task's directory and environment.
25012
+ versioned: [{
25013
+ minMajor: 2,
25014
+ argsTemplate: [
25015
+ "{{if headlessSession}}",
25016
+ "run",
25017
+ "--agent",
25018
+ "{{mode}}",
25019
+ "{{/if}}",
25020
+ "{{if headlessModel}}",
25021
+ "--model",
25022
+ "{{modelWithVariant}}",
25023
+ "{{/if}}",
25024
+ "--standalone",
25025
+ "{{if buildMode}}",
25026
+ "--auto",
25027
+ "{{/if}}",
25028
+ "{{if interactive}}",
25029
+ "--prompt",
25030
+ "{{/if}}",
25031
+ "{{prompt}}"
25032
+ ],
25033
+ env: {
25034
+ OPENCODE_CONFIG_CONTENT: "{{if interactive}}{{opencodeSessionConfig}}{{/if}}"
25035
+ }
25036
+ }],
24890
25037
  promptInArgs: true,
24891
25038
  requiresTty: true,
24892
25039
  // `--prompt` only pre-fills the TUI's composer; it does not submit it.
@@ -25005,7 +25152,7 @@ function isReservedRunnerName(name) {
25005
25152
  return RESERVED_RUNNER_NAMES.has(name.toLowerCase());
25006
25153
  }
25007
25154
  function gitClone(url, destDir) {
25008
- (0, import_child_process12.execFileSync)("git", ["clone", "--depth", "1", "--", url, destDir], {
25155
+ (0, import_child_process13.execFileSync)("git", ["clone", "--depth", "1", "--", url, destDir], {
25009
25156
  stdio: "pipe",
25010
25157
  timeout: 3e4,
25011
25158
  // A repository that demands credentials should fail, not hang on a prompt.