@ordewell/core 0.6.2 → 0.6.4
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/{IFileSystem-DuEiG_kA.d.ts → IFileSystem-DQEKyC6H.d.ts} +1 -1
- package/dist/{IFileSystem-d_SV7jAH.d.mts → IFileSystem-Qj2MWHn_.d.mts} +1 -1
- package/dist/{ModeResolver-DXxV4n_V.d.ts → ModeResolver-ByNpC0EP.d.ts} +1 -1
- package/dist/{ModeResolver-DIReYAOq.d.mts → ModeResolver-rqaY_4Ex.d.mts} +1 -1
- package/dist/{Task-ib_U0YP7.d.mts → Task-xqdxugt2.d.mts} +31 -1
- package/dist/{Task-ib_U0YP7.d.ts → Task-xqdxugt2.d.ts} +31 -1
- package/dist/index.d.mts +86 -81
- package/dist/index.d.ts +86 -81
- package/dist/index.js +194 -38
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +185 -29
- package/dist/index.mjs.map +1 -1
- package/dist/order-labels.d.mts +1 -1
- package/dist/order-labels.d.ts +1 -1
- package/dist/{parsing-C8Z22twN.d.ts → parsing-COPG8-Ep.d.ts} +2 -2
- package/dist/{parsing-DjRJJX-y.d.mts → parsing-DDZ1rUYa.d.mts} +2 -2
- package/dist/parsing.d.mts +3 -3
- package/dist/parsing.d.ts +3 -3
- package/dist/{plan-utils-2K_N5ZJe.d.ts → plan-utils-Br8UAoiu.d.ts} +2 -2
- package/dist/{plan-utils-D94CJ7CA.d.mts → plan-utils-DMgajMvd.d.mts} +2 -2
- package/dist/plan-utils.d.mts +3 -3
- package/dist/plan-utils.d.ts +3 -3
- package/dist/testing.d.mts +2 -2
- package/dist/testing.d.ts +2 -2
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -2345,7 +2345,10 @@ var WRAPPER_FAMILY = {
|
|
|
2345
2345
|
// The multi-call binary ships its own `rm`, `mv` and `sh`, so the refused
|
|
2346
2346
|
// name arrives as its first argument.
|
|
2347
2347
|
busybox: { booleanFlags: ["--list", "--install", ...HELP_FLAGS] },
|
|
2348
|
-
command: { booleanFlags: ["-p"], noExecFlags: ["-v", "-V"] }
|
|
2348
|
+
command: { booleanFlags: ["-p"], noExecFlags: ["-v", "-V"] },
|
|
2349
|
+
// Runs the named shell builtin, so `builtin eval …` is an `eval`. Classified
|
|
2350
|
+
// on its own name it only asked, and a grant for `builtin echo` covered it.
|
|
2351
|
+
builtin: {}
|
|
2349
2352
|
};
|
|
2350
2353
|
var XARGS_SPEC = {
|
|
2351
2354
|
valueFlags: [
|
|
@@ -2477,6 +2480,8 @@ function lex(command, nested, dialect) {
|
|
|
2477
2480
|
let unbalanced = false;
|
|
2478
2481
|
let grouped = false;
|
|
2479
2482
|
let joinedBy;
|
|
2483
|
+
let followable = true;
|
|
2484
|
+
let bareSegment = false;
|
|
2480
2485
|
let tokens = [];
|
|
2481
2486
|
let expandable = false;
|
|
2482
2487
|
let current = "";
|
|
@@ -2506,9 +2511,20 @@ function lex(command, nested, dialect) {
|
|
|
2506
2511
|
current = "";
|
|
2507
2512
|
started = false;
|
|
2508
2513
|
};
|
|
2509
|
-
const endSegment = (nextPiped, next = "other") => {
|
|
2514
|
+
const endSegment = (nextPiped, next = "other", last = false) => {
|
|
2510
2515
|
endToken();
|
|
2511
|
-
if (
|
|
2516
|
+
if (!last && next === "other") followable = false;
|
|
2517
|
+
if (tokens.length > 0) {
|
|
2518
|
+
const seg = toSegment(tokens, piped, dialect);
|
|
2519
|
+
if (!seg.binary) {
|
|
2520
|
+
followable = false;
|
|
2521
|
+
bareSegment = true;
|
|
2522
|
+
}
|
|
2523
|
+
segments.push({ ...seg, expandable, inputs, joinedBy });
|
|
2524
|
+
} else if (!last || segments.length > 0 || joinedBy !== void 0) {
|
|
2525
|
+
followable = false;
|
|
2526
|
+
bareSegment = true;
|
|
2527
|
+
}
|
|
2512
2528
|
tokens = [];
|
|
2513
2529
|
inputs = [];
|
|
2514
2530
|
expandable = false;
|
|
@@ -2663,8 +2679,8 @@ function lex(command, nested, dialect) {
|
|
|
2663
2679
|
i++;
|
|
2664
2680
|
}
|
|
2665
2681
|
if (quote !== "") unbalanced = true;
|
|
2666
|
-
endSegment(false);
|
|
2667
|
-
return { segments, unsafeRedirect, processSubstitution, unbalanced, grouped };
|
|
2682
|
+
endSegment(false, "other", true);
|
|
2683
|
+
return { segments, unsafeRedirect, processSubstitution, unbalanced, grouped, followable, bareSegment };
|
|
2668
2684
|
}
|
|
2669
2685
|
function binaryName(token, dialect) {
|
|
2670
2686
|
const base = token.split(/[/\\]/).pop() ?? "";
|
|
@@ -2808,7 +2824,8 @@ function lexAll(command, dialect, cdpathSet = false) {
|
|
|
2808
2824
|
const queue = [];
|
|
2809
2825
|
const top = lex(command, queue, dialect);
|
|
2810
2826
|
const all = { ...top, segments: top.segments.filter((s) => s.binary) };
|
|
2811
|
-
|
|
2827
|
+
markShellState(all.segments, top.bareSegment);
|
|
2828
|
+
if (top.followable && !top.grouped && queue.length === 0 && !cdpathSet) followCd(all.segments);
|
|
2812
2829
|
for (let depth = 0; depth < 32 && queue.length > 0; depth++) {
|
|
2813
2830
|
const inner = lex(queue.shift(), queue, dialect);
|
|
2814
2831
|
all.segments.push(...inner.segments.filter((s) => s.binary));
|
|
@@ -2825,22 +2842,51 @@ function literalCdTarget(seg) {
|
|
|
2825
2842
|
return target === "" || target.startsWith("-") || target.startsWith("~") || /[*?[]/.test(target) ? void 0 : target;
|
|
2826
2843
|
}
|
|
2827
2844
|
function followCd(segments) {
|
|
2828
|
-
if (segments.some((s) => s.joinedBy === "other")) return;
|
|
2845
|
+
if (segments.some((s) => s.joinedBy === "other" || SHELL_STATE_COMMANDS.has(s.binary))) return;
|
|
2846
|
+
const inPipeline = (i) => segments[i].joinedBy === "pipe" || segments[i + 1]?.joinedBy === "pipe";
|
|
2847
|
+
if (segments.some((s, i) => s.binary === "cd" && !inPipeline(i) && literalCdTarget(s) === void 0)) return;
|
|
2829
2848
|
let chain = [];
|
|
2830
2849
|
segments.forEach((seg, i) => {
|
|
2831
2850
|
seg.cwd = chain;
|
|
2832
|
-
const
|
|
2833
|
-
const target = inPipeline ? void 0 : literalCdTarget(seg);
|
|
2851
|
+
const target = inPipeline(i) ? void 0 : literalCdTarget(seg);
|
|
2834
2852
|
if (target !== void 0) chain = [...chain, target];
|
|
2835
2853
|
});
|
|
2836
2854
|
}
|
|
2855
|
+
function markShellState(segments, bareSegment) {
|
|
2856
|
+
if (!bareSegment && !segments.some((s) => SHELL_STATE_COMMANDS.has(s.binary))) return;
|
|
2857
|
+
for (const seg of segments) if (seg.binary === "cd") seg.cdSteered = true;
|
|
2858
|
+
}
|
|
2859
|
+
var SHELL_STATE_COMMANDS = /* @__PURE__ */ new Set([
|
|
2860
|
+
"export",
|
|
2861
|
+
"set",
|
|
2862
|
+
"shopt",
|
|
2863
|
+
"declare",
|
|
2864
|
+
"typeset",
|
|
2865
|
+
"readonly",
|
|
2866
|
+
"local",
|
|
2867
|
+
"unset",
|
|
2868
|
+
"let",
|
|
2869
|
+
"source",
|
|
2870
|
+
".",
|
|
2871
|
+
"eval",
|
|
2872
|
+
"exec",
|
|
2873
|
+
"pushd",
|
|
2874
|
+
"popd",
|
|
2875
|
+
"alias",
|
|
2876
|
+
"unalias",
|
|
2877
|
+
"builtin",
|
|
2878
|
+
"command",
|
|
2879
|
+
"enable",
|
|
2880
|
+
"hash",
|
|
2881
|
+
"trap"
|
|
2882
|
+
]);
|
|
2837
2883
|
function looksLikePath(arg) {
|
|
2838
2884
|
if (arg.startsWith("--") && arg.includes("=")) return looksLikePath(arg.slice(arg.indexOf("=") + 1));
|
|
2839
2885
|
return arg.startsWith("/") || arg.startsWith("~") || arg.startsWith("../") || arg === ".." || arg.startsWith("./") || /^[A-Za-z]:/.test(arg) || arg.startsWith("\\") || arg.startsWith("..\\") || arg.startsWith(".\\") || /(^|[\\/])\.\.([\\/]|$)/.test(arg);
|
|
2840
2886
|
}
|
|
2841
2887
|
function pathRefs(command, opts = {}) {
|
|
2842
2888
|
const dialect = dialectFor(opts.dialect);
|
|
2843
|
-
return lexAll(command, dialect, opts.cdpathSet).segments.flatMap((seg) => {
|
|
2889
|
+
return lexAll(command.trim(), dialect, opts.cdpathSet).segments.flatMap((seg) => {
|
|
2844
2890
|
const patterns = patternArgs(seg);
|
|
2845
2891
|
const cwd = seg.cwd ?? [];
|
|
2846
2892
|
return [...seg.args.filter((_, i) => !patterns.has(i)), ...seg.inputs].flatMap(pathIn).map((path26) => ({ path: path26, cwd }));
|
|
@@ -3008,7 +3054,7 @@ function scopeFor(seg) {
|
|
|
3008
3054
|
return [seg.binary, ...lead].join(" ");
|
|
3009
3055
|
}
|
|
3010
3056
|
function isAuto(seg, opts) {
|
|
3011
|
-
if (seg.binary === "cd") return !opts.cdpathSet && literalCdTarget(seg) !== void 0;
|
|
3057
|
+
if (seg.binary === "cd") return !opts.cdpathSet && !seg.cdSteered && literalCdTarget(seg) !== void 0;
|
|
3012
3058
|
if (!AUTO_COMMANDS.includes(seg.binary)) return false;
|
|
3013
3059
|
if (seg.expandable) return false;
|
|
3014
3060
|
if (seg.binary !== "git") return true;
|
|
@@ -3032,6 +3078,9 @@ function refusalFor(seg) {
|
|
|
3032
3078
|
if (seg.binary === "source" || seg.binary === ".") {
|
|
3033
3079
|
return `"${seg.binary}" runs a file's contents as commands, which this classifier cannot inspect. Use the read-only research tools, or describe it as a task.`;
|
|
3034
3080
|
}
|
|
3081
|
+
if (seg.binary === "enable" && seg.args.some((a) => /^-[a-zA-Z]*f/.test(a))) {
|
|
3082
|
+
return `"enable -f" loads a shared library into the shell, which runs its code. Use the read-only research tools, or describe it as a task.`;
|
|
3083
|
+
}
|
|
3035
3084
|
if (SHELL_KEYWORDS.includes(seg.binary)) {
|
|
3036
3085
|
return `"${seg.binary}" is a shell keyword or compound-command opener. This classifier only inspects the command that runs first, and a keyword hides the real one from it. Run the inner command directly, or describe it as a task.`;
|
|
3037
3086
|
}
|
|
@@ -3142,6 +3191,9 @@ function classifyCommand(command, opts = {}) {
|
|
|
3142
3191
|
}
|
|
3143
3192
|
const nonAuto = unwrapped.filter((u) => u.runner !== void 0 || !isAuto(u.seg, opts));
|
|
3144
3193
|
if (nonAuto.length === 0) return { tier: "auto", scope: "" };
|
|
3194
|
+
if (nonAuto.some((u) => u.seg.binary === "cd" || u.seg.binary !== "command" && SHELL_STATE_COMMANDS.has(u.seg.binary))) {
|
|
3195
|
+
return { tier: "ask", scope: trimmed };
|
|
3196
|
+
}
|
|
3145
3197
|
const scope = [...new Set(nonAuto.map((u) => u.runner ? `${u.runner} ${scopeFor(u.seg)}` : scopeFor(u.seg)))].sort().join(" + ");
|
|
3146
3198
|
return { tier: "ask", scope };
|
|
3147
3199
|
}
|
|
@@ -3210,6 +3262,9 @@ function researchShellWarning(shell) {
|
|
|
3210
3262
|
if (shell.dialect !== "cmd") return null;
|
|
3211
3263
|
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
3264
|
}
|
|
3265
|
+
function researchPolicyOptions(shell) {
|
|
3266
|
+
return { dialect: shell.dialect, cdpathSet: !!process.env.CDPATH };
|
|
3267
|
+
}
|
|
3213
3268
|
|
|
3214
3269
|
// src/services/pathScope.ts
|
|
3215
3270
|
import * as os from "os";
|
|
@@ -3443,7 +3498,7 @@ ${refs.output.trim()}`);
|
|
|
3443
3498
|
return [...found].map(([scope, abs]) => ({ abs, scope }));
|
|
3444
3499
|
}
|
|
3445
3500
|
policyOptions() {
|
|
3446
|
-
return
|
|
3501
|
+
return researchPolicyOptions(this.researchShell);
|
|
3447
3502
|
}
|
|
3448
3503
|
/**
|
|
3449
3504
|
* Three tiers (see `commandPolicy.ts`): read-only inspection runs silently,
|
|
@@ -7726,16 +7781,15 @@ function nonPromptingFs(fs16) {
|
|
|
7726
7781
|
findSymbol: (symbol, opts) => withinWorkspace(opts?.path) ? fs16.findSymbol(symbol, opts) : Promise.resolve(outsideRefusal(opts.path)),
|
|
7727
7782
|
listDir: (p, depth) => withinWorkspace(p) ? fs16.listDir(p, depth) : Promise.resolve(outsideRefusal(p)),
|
|
7728
7783
|
bash: async (command, signal) => {
|
|
7729
|
-
const
|
|
7730
|
-
const policy = { dialect, cdpathSet: !!process.env.CDPATH };
|
|
7784
|
+
const policy = researchPolicyOptions(resolveResearchShell());
|
|
7731
7785
|
const { tier, reason } = classifyCommand(command, policy);
|
|
7732
7786
|
if (tier !== "auto") {
|
|
7733
7787
|
return refused2(
|
|
7734
7788
|
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
7789
|
);
|
|
7736
7790
|
}
|
|
7737
|
-
const escaping = pathRefs(command, policy).
|
|
7738
|
-
if (escaping) return outsideRefusal(escaping.
|
|
7791
|
+
const escaping = pathRefs(command, policy).map((ref) => resolveRef(fs16.getWorkspaceRoot(), ref)).find((r) => !r.inside);
|
|
7792
|
+
if (escaping) return outsideRefusal(escaping.abs);
|
|
7739
7793
|
return fs16.bash(command, signal);
|
|
7740
7794
|
}
|
|
7741
7795
|
};
|
|
@@ -10774,11 +10828,17 @@ function buildShellInvocation(command, args) {
|
|
|
10774
10828
|
const inner = [command, ...args].map(posixShellQuote).join(" ");
|
|
10775
10829
|
return { shellPath: "/bin/bash", shellArgs: ["-lc", inner] };
|
|
10776
10830
|
}
|
|
10831
|
+
var BSD_SCRIPT_PLATFORMS = /* @__PURE__ */ new Set(["darwin", "freebsd", "openbsd", "netbsd"]);
|
|
10832
|
+
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
10833
|
function wrapWithPty(command, args, opts = {}) {
|
|
10778
10834
|
const inner = [command, ...args].map(posixShellQuote).join(" ");
|
|
10779
10835
|
const watcher = opts.controlChannel ? 'exec 4<&0; ( while read -r C R <&3; do stty cols "$C" rows "$R" <&4; done ) & ' : "";
|
|
10780
10836
|
const setup = opts.size ? `stty cols ${opts.size.cols} rows ${opts.size.rows}; ` : "";
|
|
10781
|
-
|
|
10837
|
+
const line = `${watcher}${setup}${inner}`;
|
|
10838
|
+
if (BSD_SCRIPT_PLATFORMS.has(opts.platform ?? process.platform)) {
|
|
10839
|
+
return { command: "/bin/sh", args: ["-c", BSD_SCRIPT_FEED, line] };
|
|
10840
|
+
}
|
|
10841
|
+
return { command: "script", args: ["-q", "-e", "-f", "-c", line, "/dev/null"] };
|
|
10782
10842
|
}
|
|
10783
10843
|
|
|
10784
10844
|
// src/services/blockingPrompts.ts
|
|
@@ -10973,24 +11033,37 @@ var Block = /* @__PURE__ */ ((Block2) => {
|
|
|
10973
11033
|
Block2[Block2["IfHeadlessSession"] = 8] = "IfHeadlessSession";
|
|
10974
11034
|
Block2[Block2["IfInteractiveVariant"] = 9] = "IfInteractiveVariant";
|
|
10975
11035
|
Block2[Block2["IfProjectTrust"] = 10] = "IfProjectTrust";
|
|
11036
|
+
Block2[Block2["IfHeadlessModel"] = 11] = "IfHeadlessModel";
|
|
10976
11037
|
return Block2;
|
|
10977
11038
|
})(Block || {});
|
|
10978
11039
|
function isInteractive(ctx) {
|
|
10979
11040
|
return ctx.interactive ?? !ctx.headless;
|
|
10980
11041
|
}
|
|
11042
|
+
function majorVersion(version) {
|
|
11043
|
+
const match = version?.match(/(\d+)\.\d+/);
|
|
11044
|
+
return match ? Number(match[1]) : void 0;
|
|
11045
|
+
}
|
|
11046
|
+
function versionedInvocation(manifest, ctx) {
|
|
11047
|
+
const major = majorVersion(ctx.runnerVersion);
|
|
11048
|
+
if (major === void 0) return void 0;
|
|
11049
|
+
return (manifest.runner.versioned ?? []).filter((v) => major >= v.minMajor).sort((a, b) => b.minMajor - a.minMajor)[0];
|
|
11050
|
+
}
|
|
10981
11051
|
function resolveArgs(manifest, ctx) {
|
|
10982
11052
|
const args = [];
|
|
10983
11053
|
const env = {};
|
|
10984
|
-
|
|
10985
|
-
|
|
11054
|
+
const versioned = versionedInvocation(manifest, ctx);
|
|
11055
|
+
const argsTemplate = versioned?.argsTemplate ?? manifest.runner.argsTemplate;
|
|
11056
|
+
const envTemplate = versioned ? versioned.env : manifest.runner.env;
|
|
11057
|
+
if (envTemplate) {
|
|
11058
|
+
for (const [key, value] of Object.entries(envTemplate)) {
|
|
10986
11059
|
const resolved = substituteSimple(value, ctx);
|
|
10987
11060
|
if (resolved.length > 0) env[key] = resolved;
|
|
10988
11061
|
}
|
|
10989
11062
|
}
|
|
10990
11063
|
let block = 0 /* None */;
|
|
10991
11064
|
let include = true;
|
|
10992
|
-
for (let i = 0; i <
|
|
10993
|
-
const raw =
|
|
11065
|
+
for (let i = 0; i < argsTemplate.length; i++) {
|
|
11066
|
+
const raw = argsTemplate[i];
|
|
10994
11067
|
const blockOpen = parseBlockOpen(raw);
|
|
10995
11068
|
if (blockOpen !== null) {
|
|
10996
11069
|
if (block !== 0 /* None */) throw new ResolveError(`Nested conditional block at position ${i}: "${raw}"`);
|
|
@@ -11049,6 +11122,8 @@ function parseBlockOpen(raw) {
|
|
|
11049
11122
|
return 9 /* IfInteractiveVariant */;
|
|
11050
11123
|
case "{{if projectTrust}}":
|
|
11051
11124
|
return 10 /* IfProjectTrust */;
|
|
11125
|
+
case "{{if headlessModel}}":
|
|
11126
|
+
return 11 /* IfHeadlessModel */;
|
|
11052
11127
|
default:
|
|
11053
11128
|
return null;
|
|
11054
11129
|
}
|
|
@@ -11088,6 +11163,9 @@ function shouldIncludeBlock(block, ctx) {
|
|
|
11088
11163
|
// there is no cwd to name.
|
|
11089
11164
|
case 10 /* IfProjectTrust */:
|
|
11090
11165
|
return isInteractive(ctx) && !!ctx.cwd;
|
|
11166
|
+
// A model flag that only the non-interactive subcommand takes.
|
|
11167
|
+
case 11 /* IfHeadlessModel */:
|
|
11168
|
+
return !!ctx.model && !isInteractive(ctx);
|
|
11091
11169
|
default:
|
|
11092
11170
|
return true;
|
|
11093
11171
|
}
|
|
@@ -11133,6 +11211,10 @@ function resolveToken(token, manifest, ctx) {
|
|
|
11133
11211
|
if (token === "{{model}}") return ctx.model || "";
|
|
11134
11212
|
if (token === "{{thinkingEffort}}") return ctx.thinkingEffort || "";
|
|
11135
11213
|
if (token === "{{mode}}") return ctx.mode || "build";
|
|
11214
|
+
if (token === "{{modelWithVariant}}") {
|
|
11215
|
+
if (!ctx.model) return "";
|
|
11216
|
+
return ctx.thinkingEffort ? `${ctx.model}#${ctx.thinkingEffort}` : ctx.model;
|
|
11217
|
+
}
|
|
11136
11218
|
if (token === "{{projectTrust}}") {
|
|
11137
11219
|
if (!ctx.cwd) return "";
|
|
11138
11220
|
return `projects.${JSON.stringify(ctx.cwd)}.trust_level="trusted"`;
|
|
@@ -11207,13 +11289,21 @@ function resolveOpencodeVariantConfig(ctx) {
|
|
|
11207
11289
|
}
|
|
11208
11290
|
return JSON.stringify(config);
|
|
11209
11291
|
}
|
|
11292
|
+
function resolveOpencodeSessionConfig(ctx) {
|
|
11293
|
+
const mode = ctx.mode || "build";
|
|
11294
|
+
const pinned = resolveOpencodeVariantConfig(ctx);
|
|
11295
|
+
const config = pinned ? JSON.parse(pinned) : {};
|
|
11296
|
+
config.default_agent = mode;
|
|
11297
|
+
if (ctx.model && !pinned) config.agent = { [mode]: { model: ctx.model } };
|
|
11298
|
+
return JSON.stringify(config);
|
|
11299
|
+
}
|
|
11210
11300
|
function substituteSimple(value, ctx) {
|
|
11211
11301
|
const conditioned = value.replace(/\{\{if (\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g, (match, name, body) => {
|
|
11212
11302
|
const block = parseBlockOpen(`{{if ${name}}}`);
|
|
11213
11303
|
if (block === null) return match;
|
|
11214
11304
|
return shouldIncludeBlock(block, ctx) ? body : "";
|
|
11215
11305
|
});
|
|
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");
|
|
11306
|
+
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
11307
|
}
|
|
11218
11308
|
|
|
11219
11309
|
// src/utils/launch.ts
|
|
@@ -20138,13 +20228,33 @@ function buildRunnerInvocation(opts) {
|
|
|
20138
20228
|
mode: opts.mode ?? "build",
|
|
20139
20229
|
headless: opts.headless,
|
|
20140
20230
|
interactive: opts.interactive,
|
|
20141
|
-
cwd: opts.cwd
|
|
20231
|
+
cwd: opts.cwd,
|
|
20232
|
+
runnerVersion: opts.runnerVersion
|
|
20142
20233
|
});
|
|
20143
20234
|
}
|
|
20144
20235
|
|
|
20145
20236
|
// src/services/HeadlessRunner.ts
|
|
20146
20237
|
import { spawn as nodeSpawn2, execSync } from "child_process";
|
|
20147
20238
|
|
|
20239
|
+
// src/services/runnerVersion.ts
|
|
20240
|
+
import { execFile as execFile4 } from "child_process";
|
|
20241
|
+
var VERSION_TIMEOUT_MS = 5e3;
|
|
20242
|
+
var cache = /* @__PURE__ */ new Map();
|
|
20243
|
+
var probeRunnerVersion = (command, PATH) => {
|
|
20244
|
+
const key = `${command}\0${PATH}`;
|
|
20245
|
+
let pending = cache.get(key);
|
|
20246
|
+
if (!pending) {
|
|
20247
|
+
pending = new Promise((resolve7) => {
|
|
20248
|
+
execFile4(command, ["--version"], { env: { ...process.env, PATH }, timeout: VERSION_TIMEOUT_MS }, (err, stdout) => {
|
|
20249
|
+
const line = String(stdout ?? "").trim();
|
|
20250
|
+
resolve7(err || !line ? void 0 : line);
|
|
20251
|
+
});
|
|
20252
|
+
});
|
|
20253
|
+
cache.set(key, pending);
|
|
20254
|
+
}
|
|
20255
|
+
return pending;
|
|
20256
|
+
};
|
|
20257
|
+
|
|
20148
20258
|
// src/services/AbstractRunner.ts
|
|
20149
20259
|
import { EventEmitter } from "events";
|
|
20150
20260
|
var AbstractTerminalSession = class {
|
|
@@ -20265,6 +20375,10 @@ Process error: ${err.message}
|
|
|
20265
20375
|
this.process.stdin.write(text);
|
|
20266
20376
|
}
|
|
20267
20377
|
}
|
|
20378
|
+
/** Closes the runner's stdin: what it has read so far is all it will get. */
|
|
20379
|
+
endInput() {
|
|
20380
|
+
this.process?.stdin?.end();
|
|
20381
|
+
}
|
|
20268
20382
|
/** PTY resize requests from a surface that owns the terminal rendering the wrapper. */
|
|
20269
20383
|
writeControl(text) {
|
|
20270
20384
|
if (this.controlStream && !this.controlStream.destroyed) {
|
|
@@ -20277,6 +20391,7 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
20277
20391
|
hasScriptCmd;
|
|
20278
20392
|
resolvePath;
|
|
20279
20393
|
launchDeps;
|
|
20394
|
+
runnerVersion;
|
|
20280
20395
|
spawnCount = 0;
|
|
20281
20396
|
/**
|
|
20282
20397
|
* Session shape: a piped subprocess is not a terminal, so runners get their
|
|
@@ -20292,6 +20407,7 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
20292
20407
|
this.hasScriptCmd = deps.hasScriptCmd ?? defaultHasScriptCmd;
|
|
20293
20408
|
this.resolvePath = deps.resolvePath ?? augmentedPath;
|
|
20294
20409
|
this.launchDeps = { resolvePath: this.resolvePath, ...deps.launchDeps };
|
|
20410
|
+
this.runnerVersion = deps.runnerVersion ?? probeRunnerVersion;
|
|
20295
20411
|
}
|
|
20296
20412
|
/**
|
|
20297
20413
|
* Unique per spawn, not per task: a retry reuses its task id and ids often
|
|
@@ -20309,6 +20425,9 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
20309
20425
|
/** Everything up to, but not including, spawning — so a surface that owns its own child reaches the same decisions. */
|
|
20310
20426
|
async prepareLaunch(opts, ptyOptions) {
|
|
20311
20427
|
const interactive = this.defaultInteractive;
|
|
20428
|
+
const resolvedPath = await this.resolvePath();
|
|
20429
|
+
const manifest = opts.registry?.get(opts.runner)?.manifest;
|
|
20430
|
+
const runnerVersion = manifest?.runner.versioned?.length ? await this.runnerVersion(manifest.runner.command, resolvedPath) : void 0;
|
|
20312
20431
|
const invocation = buildRunnerInvocation({
|
|
20313
20432
|
runner: opts.runner,
|
|
20314
20433
|
prompt: opts.prompt,
|
|
@@ -20319,18 +20438,18 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
20319
20438
|
headless: opts.headless ?? true,
|
|
20320
20439
|
interactive,
|
|
20321
20440
|
cwd: opts.cwd,
|
|
20441
|
+
runnerVersion,
|
|
20322
20442
|
registry: opts.registry
|
|
20323
20443
|
});
|
|
20324
|
-
const manifest = opts.registry?.get(opts.runner)?.manifest;
|
|
20325
20444
|
const pty = (manifest?.runner.requiresTty === true || interactive) && this.hasScriptCmd();
|
|
20326
|
-
const
|
|
20327
|
-
const { command, args } = pty ? wrapWithPty(invocation.command, invocation.args, ptyOptions) : { command: invocation.command, args: invocation.args };
|
|
20445
|
+
const { command, args } = pty ? wrapWithPty(invocation.command, invocation.args, { platform: this.launchDeps.platform, ...ptyOptions }) : { command: invocation.command, args: invocation.args };
|
|
20328
20446
|
return {
|
|
20329
20447
|
launch: await planDirectLaunch(command, args, this.launchDeps),
|
|
20330
20448
|
resolvedPath,
|
|
20331
20449
|
env: { ...opts.env, ...invocation.env },
|
|
20332
20450
|
pty,
|
|
20333
|
-
submitPromptKey: invocation.submitPromptKey
|
|
20451
|
+
submitPromptKey: invocation.submitPromptKey,
|
|
20452
|
+
closeStdin: !pty && !interactive && invocation.promptInArgs
|
|
20334
20453
|
};
|
|
20335
20454
|
}
|
|
20336
20455
|
async spawn(opts) {
|
|
@@ -20341,13 +20460,14 @@ var HeadlessRunner = class extends AbstractRunner {
|
|
|
20341
20460
|
console.error(`[headless] Starting ${opts.runner} [${modeStr}] (${opts.modelId || "default"}) for task ${opts.taskId.slice(0, 8)}${prepared.pty ? " (PTY)" : ""}`);
|
|
20342
20461
|
session.start(prepared.launch, opts.cwd, prepared.resolvedPath, prepared.env);
|
|
20343
20462
|
if (prepared.submitPromptKey) session.write("\r");
|
|
20463
|
+
if (prepared.closeStdin) session.endInput();
|
|
20344
20464
|
this.registerSession(id, session);
|
|
20345
20465
|
return session;
|
|
20346
20466
|
}
|
|
20347
20467
|
};
|
|
20348
20468
|
|
|
20349
20469
|
// src/services/TmuxRunner.ts
|
|
20350
|
-
import { execFile as
|
|
20470
|
+
import { execFile as execFile5 } from "child_process";
|
|
20351
20471
|
import { promisify as promisify5 } from "util";
|
|
20352
20472
|
import { tmpdir } from "os";
|
|
20353
20473
|
import { join as join19 } from "path";
|
|
@@ -20404,7 +20524,7 @@ function clipboardCopyCommand(hasBin = defaultHasBin, platform = process.platfor
|
|
|
20404
20524
|
var EXIT_RE = /<<<ORDEWELL_TMUX_EXIT:(\d+)>>>/;
|
|
20405
20525
|
var LIVENESS_INTERVAL_MS = 5e3;
|
|
20406
20526
|
var LIVENESS_MISSES = 2;
|
|
20407
|
-
var execFileAsync2 = promisify5(
|
|
20527
|
+
var execFileAsync2 = promisify5(execFile5);
|
|
20408
20528
|
var defaultExecFile3 = async (command, args) => {
|
|
20409
20529
|
const { stdout, stderr } = await execFileAsync2(command, args);
|
|
20410
20530
|
return { stdout: String(stdout), stderr: String(stderr) };
|
|
@@ -20554,6 +20674,7 @@ var TmuxRunner = class extends AbstractRunner {
|
|
|
20554
20674
|
pollIntervalMs;
|
|
20555
20675
|
logDir;
|
|
20556
20676
|
clipboardCommand;
|
|
20677
|
+
runnerVersion;
|
|
20557
20678
|
/** Retries get a freshly named window so a failed attempt's output stays inspectable. */
|
|
20558
20679
|
attempts = /* @__PURE__ */ new Map();
|
|
20559
20680
|
ready = null;
|
|
@@ -20566,6 +20687,7 @@ var TmuxRunner = class extends AbstractRunner {
|
|
|
20566
20687
|
this.pollIntervalMs = deps.pollIntervalMs ?? 500;
|
|
20567
20688
|
this.logDir = deps.logDir ?? tmpdir();
|
|
20568
20689
|
this.clipboardCommand = deps.clipboardCommand ?? (() => clipboardCopyCommand());
|
|
20690
|
+
this.runnerVersion = deps.runnerVersion ?? probeRunnerVersion;
|
|
20569
20691
|
}
|
|
20570
20692
|
/**
|
|
20571
20693
|
* Reaps a session orphaned by a crashed prior daemon on the same port, then
|
|
@@ -20649,6 +20771,8 @@ var TmuxRunner = class extends AbstractRunner {
|
|
|
20649
20771
|
}
|
|
20650
20772
|
}
|
|
20651
20773
|
async spawn(opts) {
|
|
20774
|
+
const manifest = opts.registry?.get(opts.runner)?.manifest;
|
|
20775
|
+
const runnerVersion = manifest?.runner.versioned?.length ? await this.runnerVersion(manifest.runner.command, await this.resolvePath()) : void 0;
|
|
20652
20776
|
const invocation = buildRunnerInvocation({
|
|
20653
20777
|
runner: opts.runner,
|
|
20654
20778
|
prompt: opts.prompt,
|
|
@@ -20664,6 +20788,7 @@ var TmuxRunner = class extends AbstractRunner {
|
|
|
20664
20788
|
headless: opts.headless ?? true,
|
|
20665
20789
|
interactive: true,
|
|
20666
20790
|
cwd: opts.cwd,
|
|
20791
|
+
runnerVersion,
|
|
20667
20792
|
registry: opts.registry
|
|
20668
20793
|
});
|
|
20669
20794
|
await this.ensureSession();
|
|
@@ -21580,6 +21705,37 @@ var OPENCODE_MANIFEST = {
|
|
|
21580
21705
|
env: {
|
|
21581
21706
|
OPENCODE_CONFIG_CONTENT: "{{if interactiveVariant}}{{opencodeVariantConfig}}{{/if}}"
|
|
21582
21707
|
},
|
|
21708
|
+
// OpenCode 2.x changed both shapes. Its TUI takes no `--model`/`--agent`,
|
|
21709
|
+
// so they ride on OPENCODE_CONFIG_CONTENT instead, and its `run` has no
|
|
21710
|
+
// `--variant` — the variant is a `#suffix` on the model id. Both run
|
|
21711
|
+
// `--standalone`: without it the TUI and `run` attach to the user's
|
|
21712
|
+
// background service, which neither reads this process's config nor
|
|
21713
|
+
// starts in the task's directory and environment.
|
|
21714
|
+
versioned: [{
|
|
21715
|
+
minMajor: 2,
|
|
21716
|
+
argsTemplate: [
|
|
21717
|
+
"{{if headlessSession}}",
|
|
21718
|
+
"run",
|
|
21719
|
+
"--agent",
|
|
21720
|
+
"{{mode}}",
|
|
21721
|
+
"{{/if}}",
|
|
21722
|
+
"{{if headlessModel}}",
|
|
21723
|
+
"--model",
|
|
21724
|
+
"{{modelWithVariant}}",
|
|
21725
|
+
"{{/if}}",
|
|
21726
|
+
"--standalone",
|
|
21727
|
+
"{{if buildMode}}",
|
|
21728
|
+
"--auto",
|
|
21729
|
+
"{{/if}}",
|
|
21730
|
+
"{{if interactive}}",
|
|
21731
|
+
"--prompt",
|
|
21732
|
+
"{{/if}}",
|
|
21733
|
+
"{{prompt}}"
|
|
21734
|
+
],
|
|
21735
|
+
env: {
|
|
21736
|
+
OPENCODE_CONFIG_CONTENT: "{{if interactive}}{{opencodeSessionConfig}}{{/if}}"
|
|
21737
|
+
}
|
|
21738
|
+
}],
|
|
21583
21739
|
promptInArgs: true,
|
|
21584
21740
|
requiresTty: true,
|
|
21585
21741
|
// `--prompt` only pre-fills the TUI's composer; it does not submit it.
|