@ordewell/cli 0.5.5 → 0.6.0
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/README.md +4 -2
- package/dist/main.js +658 -201
- package/package.json +4 -3
package/dist/main.js
CHANGED
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/main.ts
|
|
27
|
-
var
|
|
27
|
+
var import_core34 = require("@ordewell/core");
|
|
28
28
|
|
|
29
29
|
// src/utils/env.ts
|
|
30
30
|
var import_fs = require("fs");
|
|
@@ -33,42 +33,56 @@ var import_core = require("@ordewell/core");
|
|
|
33
33
|
function findEnvFile() {
|
|
34
34
|
return (0, import_path.join)((0, import_core.globalDataDir)(), ".env");
|
|
35
35
|
}
|
|
36
|
+
function unquote(value) {
|
|
37
|
+
const quote = value[0];
|
|
38
|
+
return value.length >= 2 && (quote === '"' || quote === "'") && value.endsWith(quote) ? value.slice(1, -1) : value;
|
|
39
|
+
}
|
|
36
40
|
function loadEnvFile() {
|
|
37
41
|
const filePath = findEnvFile();
|
|
38
42
|
if (!(0, import_fs.existsSync)(filePath)) return;
|
|
39
43
|
const content = (0, import_fs.readFileSync)(filePath, "utf8");
|
|
40
44
|
for (const line of content.split("\n")) {
|
|
41
|
-
const trimmed = line.trim();
|
|
45
|
+
const trimmed = line.trim().replace(/^export\s+/, "");
|
|
42
46
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
43
47
|
const eq = trimmed.indexOf("=");
|
|
44
48
|
if (eq === -1) continue;
|
|
45
49
|
const key = trimmed.slice(0, eq).trim();
|
|
46
|
-
const value = trimmed.slice(eq + 1).trim();
|
|
50
|
+
const value = unquote(trimmed.slice(eq + 1).trim());
|
|
47
51
|
if (key && process.env[key] === void 0) {
|
|
48
52
|
process.env[key] = value;
|
|
49
53
|
}
|
|
50
54
|
}
|
|
51
55
|
}
|
|
56
|
+
var ENV_KEY_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
57
|
+
function assertValidEnvKey(key) {
|
|
58
|
+
if (!ENV_KEY_PATTERN.test(key)) {
|
|
59
|
+
throw new Error(`"${key}" is not a valid environment variable name.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
52
62
|
function writeEnvVar(filePath, key, value) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
assertValidEnvKey(key);
|
|
64
|
+
if (/[\r\n]/.test(value)) {
|
|
65
|
+
throw new Error(`Refusing to write ${key}: the value contains a newline.`);
|
|
66
|
+
}
|
|
67
|
+
let content = "";
|
|
68
|
+
if ((0, import_fs.existsSync)(filePath)) {
|
|
69
|
+
content = (0, import_fs.readFileSync)(filePath, "utf8");
|
|
70
|
+
const lines = content.split("\n");
|
|
71
|
+
const index = lines.findIndex((line) => line.startsWith(`${key}=`));
|
|
72
|
+
if (index === -1) {
|
|
73
|
+
if (content.length > 0 && !content.endsWith("\n")) content += "\n";
|
|
74
|
+
content += `${key}=${value}
|
|
65
75
|
`;
|
|
66
|
-
}
|
|
67
76
|
} else {
|
|
68
|
-
|
|
69
|
-
|
|
77
|
+
lines[index] = `${key}=${value}`;
|
|
78
|
+
content = lines.join("\n");
|
|
70
79
|
}
|
|
71
|
-
|
|
80
|
+
} else {
|
|
81
|
+
content = `${key}=${value}
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
(0, import_core.writePrivateFile)(filePath, content);
|
|
72
86
|
} catch (err) {
|
|
73
87
|
console.error(`Could not write to ${filePath}: ${err.message}`);
|
|
74
88
|
}
|
|
@@ -106,6 +120,7 @@ Tasks (<id> is an order number or a task ID):
|
|
|
106
120
|
ordewell force-start <id> Start a task now, ignoring dependencies
|
|
107
121
|
ordewell run-task <id> Run only one task
|
|
108
122
|
ordewell retry <id> Re-run a failed task
|
|
123
|
+
ordewell continue <id> <msg> Continue a finished task in its saved runner session
|
|
109
124
|
ordewell cancel <id> Kill a running task
|
|
110
125
|
ordewell terminal <id> Open a real terminal attached to a task's runner
|
|
111
126
|
|
|
@@ -123,7 +138,8 @@ Planner, models and runners:
|
|
|
123
138
|
ordewell key [set <p> <key>] Show which providers have a key, or store one
|
|
124
139
|
ordewell runners [<id> on|off] Enable or disable runners (claude-code, opencode, codex)
|
|
125
140
|
ordewell allowlist set|clear|show Limit which models a runner may use
|
|
126
|
-
ordewell auto [
|
|
141
|
+
ordewell auto [full|auto] Autonomy level for new sessions: Full auto or Auto
|
|
142
|
+
ordewell transport [terminal|structured] Structured (default) or terminal task transport, from the next run
|
|
127
143
|
ordewell parallel [<n>] How many AI tasks run at once (default 3; applies to a live run)
|
|
128
144
|
ordewell refresh Re-discover runners and model catalogs
|
|
129
145
|
ordewell models List every provider's catalog (works without a server)
|
|
@@ -196,7 +212,7 @@ Environment:
|
|
|
196
212
|
ORDEWELL_PLANNER_EFFORT Thinking effort for a coding-agent planner
|
|
197
213
|
ORCHESTRATOR_MODEL Default: deepseek/deepseek-v4-flash
|
|
198
214
|
OPENROUTER_BASE_URL Default: https://openrouter.ai/api/v1
|
|
199
|
-
ORDEWELL_AUTONOMOUS_MODE
|
|
215
|
+
ORDEWELL_AUTONOMOUS_MODE true: Full auto, false: Auto, for new sessions (see "ordewell auto")
|
|
200
216
|
ORDEWELL_PORT Daemon port CLI commands target (default: 3742)
|
|
201
217
|
ORDEWELL_DIRENV false: agents skip the project's direnv .envrc
|
|
202
218
|
ORDEWELL_RESEARCH_ENABLED true (default) or false
|
|
@@ -583,6 +599,40 @@ var ApiClient = class _ApiClient {
|
|
|
583
599
|
}
|
|
584
600
|
return res.data;
|
|
585
601
|
}
|
|
602
|
+
/** A user message to a structured task: delivered now if it waits for input, else queued behind its turn. */
|
|
603
|
+
async sendTaskMessage(sessionId, taskId, text) {
|
|
604
|
+
const res = await this.httpRequest("POST", `/api/plans/${sessionId}/tasks/${taskId}/messages`, { text });
|
|
605
|
+
if (res.status !== 200) {
|
|
606
|
+
throw new Error(res.data?.error || "Send message failed");
|
|
607
|
+
}
|
|
608
|
+
return res.data;
|
|
609
|
+
}
|
|
610
|
+
/** Continue a finished structured task in its saved session, with `text` as its next turn. */
|
|
611
|
+
async continueTask(sessionId, taskId, text) {
|
|
612
|
+
const res = await this.httpRequest("POST", `/api/plans/${sessionId}/tasks/${taskId}/continue`, { text });
|
|
613
|
+
if (res.status !== 200) {
|
|
614
|
+
throw new Error(res.data?.error || "Continue failed");
|
|
615
|
+
}
|
|
616
|
+
return res.data;
|
|
617
|
+
}
|
|
618
|
+
/** `removed` is false once the message was already delivered. */
|
|
619
|
+
async removeQueuedTaskMessage(sessionId, taskId, messageId) {
|
|
620
|
+
const res = await this.httpRequest(
|
|
621
|
+
"DELETE",
|
|
622
|
+
`/api/plans/${sessionId}/tasks/${taskId}/messages/${encodeURIComponent(messageId)}`
|
|
623
|
+
);
|
|
624
|
+
if (res.status !== 200) {
|
|
625
|
+
throw new Error(res.data?.error || "Remove message failed");
|
|
626
|
+
}
|
|
627
|
+
return res.data;
|
|
628
|
+
}
|
|
629
|
+
async interruptTask(sessionId, taskId) {
|
|
630
|
+
const res = await this.httpRequest("POST", `/api/plans/${sessionId}/tasks/${taskId}/interrupt`);
|
|
631
|
+
if (res.status !== 200) {
|
|
632
|
+
throw new Error(res.data?.error || "Interrupt failed");
|
|
633
|
+
}
|
|
634
|
+
return res.data;
|
|
635
|
+
}
|
|
586
636
|
async addTask(sessionId, task) {
|
|
587
637
|
const res = await this.httpRequest("POST", `/api/plans/${sessionId}/tasks`, task);
|
|
588
638
|
if (res.status !== 200) {
|
|
@@ -742,6 +792,20 @@ var ApiClient = class _ApiClient {
|
|
|
742
792
|
}
|
|
743
793
|
return res.data;
|
|
744
794
|
}
|
|
795
|
+
/** The attempts of a structured task that have a saved log, oldest first. */
|
|
796
|
+
async getTaskLogAttempts(sessionId, taskId, workspace) {
|
|
797
|
+
const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
|
|
798
|
+
const res = await this.httpRequest("GET", `/api/sessions/${sessionId}/tasks/${encodeURIComponent(taskId)}/log${qs}`);
|
|
799
|
+
if (res.status !== 200) throw new Error(res.data?.error || "Could not read the task log");
|
|
800
|
+
return res.data.attempts;
|
|
801
|
+
}
|
|
802
|
+
/** One attempt's saved log, for `replayTaskLog`. */
|
|
803
|
+
async getTaskLog(sessionId, taskId, attempt, workspace) {
|
|
804
|
+
const qs = workspace ? `?workspace=${encodeURIComponent(workspace)}` : "";
|
|
805
|
+
const res = await this.httpRequest("GET", `/api/sessions/${sessionId}/tasks/${encodeURIComponent(taskId)}/log/${attempt}${qs}`);
|
|
806
|
+
if (res.status !== 200) throw new Error(res.data?.error || "Could not read the task log");
|
|
807
|
+
return res.data.events;
|
|
808
|
+
}
|
|
745
809
|
/**
|
|
746
810
|
* The one place a session socket is built, so the token travels on both
|
|
747
811
|
* stream paths. It rides as a subprotocol rather than a query parameter, so
|
|
@@ -808,12 +872,16 @@ var ApiClient = class _ApiClient {
|
|
|
808
872
|
});
|
|
809
873
|
});
|
|
810
874
|
}
|
|
811
|
-
/**
|
|
812
|
-
|
|
875
|
+
/**
|
|
876
|
+
* Answer an approval prompt: the planner's with a yes/no, a task runner's
|
|
877
|
+
* with the whole decision (ADR-0018, A1). The planner's requests arrive over
|
|
878
|
+
* the session socket; a runner's, in its task log.
|
|
879
|
+
*/
|
|
880
|
+
async respondToApproval(sessionId, approvalId, answer) {
|
|
813
881
|
const { status, data } = await this.httpRequest(
|
|
814
882
|
"POST",
|
|
815
883
|
`/api/approvals/${encodeURIComponent(sessionId)}/${encodeURIComponent(approvalId)}`,
|
|
816
|
-
{ granted }
|
|
884
|
+
typeof answer === "boolean" ? { granted: answer } : answer
|
|
817
885
|
);
|
|
818
886
|
if (status !== 200) throw new Error(`Failed to answer approval ${approvalId} (HTTP ${status})`);
|
|
819
887
|
return data;
|
|
@@ -1931,6 +1999,7 @@ ${providerLabel(provider)}`);
|
|
|
1931
1999
|
|
|
1932
2000
|
// src/commands/setup.ts
|
|
1933
2001
|
var import_fs6 = require("fs");
|
|
2002
|
+
var import_core13 = require("@ordewell/core");
|
|
1934
2003
|
function handleSetup(subArgs) {
|
|
1935
2004
|
const modelIdx = subArgs.indexOf("--model");
|
|
1936
2005
|
const chosenModel = modelIdx !== -1 ? subArgs[modelIdx + 1] : null;
|
|
@@ -2002,15 +2071,15 @@ function handleSetup(subArgs) {
|
|
|
2002
2071
|
# STEPFUN_API_KEY=...
|
|
2003
2072
|
# SILICONFLOW_API_KEY=...
|
|
2004
2073
|
`;
|
|
2005
|
-
(0,
|
|
2074
|
+
(0, import_core13.writePrivateFile)(envFile, content);
|
|
2006
2075
|
console.log(`Wrote: ${envFile}`);
|
|
2007
2076
|
console.log("Review and edit the file, then run: ordewell tui");
|
|
2008
2077
|
}
|
|
2009
2078
|
|
|
2010
2079
|
// src/commands/plugins.ts
|
|
2011
|
-
var
|
|
2080
|
+
var import_core14 = require("@ordewell/core");
|
|
2012
2081
|
function handlePlugins(subArgs) {
|
|
2013
|
-
const registry = new
|
|
2082
|
+
const registry = new import_core14.RunnerRegistry();
|
|
2014
2083
|
registry.loadUserPlugins();
|
|
2015
2084
|
const sub = subArgs[0];
|
|
2016
2085
|
if (sub === "list" || !sub) {
|
|
@@ -2046,7 +2115,7 @@ ${plugins.length} plugin(s):
|
|
|
2046
2115
|
process.exit(1);
|
|
2047
2116
|
}
|
|
2048
2117
|
try {
|
|
2049
|
-
const resolved = (0,
|
|
2118
|
+
const resolved = (0, import_core14.classifyPluginSource)(source);
|
|
2050
2119
|
const manifest = resolved.kind === "git" ? registry.installFromGit(resolved.url) : registry.installFromPath(resolved.path);
|
|
2051
2120
|
console.log(`Installed plugin: ${manifest.name} v${manifest.version}`);
|
|
2052
2121
|
} catch (err) {
|
|
@@ -2207,6 +2276,22 @@ async function handleVerify(subArgs, api) {
|
|
|
2207
2276
|
}
|
|
2208
2277
|
}
|
|
2209
2278
|
|
|
2279
|
+
// src/commands/transport.ts
|
|
2280
|
+
var describe = (transport) => `Runner transport: ${transport === "terminal" ? "terminal" : "structured"} \u2014 applies from the next run`;
|
|
2281
|
+
async function handleTransport(subArgs, api) {
|
|
2282
|
+
const action = subArgs[0];
|
|
2283
|
+
const port = api ? void 0 : await ensureDaemon(resolvePort(subArgs));
|
|
2284
|
+
const client = api || new ApiClient(port);
|
|
2285
|
+
if (action === "terminal" || action === "structured") {
|
|
2286
|
+
const result = await client.sendCommand("transport", { action });
|
|
2287
|
+
console.log(describe(result.settings?.runnerTransport));
|
|
2288
|
+
} else {
|
|
2289
|
+
const settings = await client.getSettings();
|
|
2290
|
+
console.log(describe(settings.runnerTransport));
|
|
2291
|
+
console.log("Usage: ordewell transport [terminal|structured]");
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2210
2295
|
// src/commands/task-control.ts
|
|
2211
2296
|
var PAST_TENSE = {
|
|
2212
2297
|
run: "Task started.",
|
|
@@ -2263,6 +2348,23 @@ function makeHandler(action, command = action) {
|
|
|
2263
2348
|
);
|
|
2264
2349
|
};
|
|
2265
2350
|
}
|
|
2351
|
+
async function handleContinue(subArgs, injectedApi) {
|
|
2352
|
+
const usage = "Usage: ordewell continue <task-id-or-order> <message> [--session-id <id>] [--workspace /path]";
|
|
2353
|
+
const message = positionals(subArgs).slice(1).join(" ").trim();
|
|
2354
|
+
if (!message) {
|
|
2355
|
+
console.error(usage);
|
|
2356
|
+
process.exit(1);
|
|
2357
|
+
}
|
|
2358
|
+
await withResolvedTask(subArgs, usage, injectedApi, async (api, sessionId, taskId) => {
|
|
2359
|
+
try {
|
|
2360
|
+
await api.continueTask(sessionId, taskId, message);
|
|
2361
|
+
console.log("Task continued.");
|
|
2362
|
+
} catch (err) {
|
|
2363
|
+
console.error(`Failed to continue task: ${err.message}`);
|
|
2364
|
+
process.exit(1);
|
|
2365
|
+
}
|
|
2366
|
+
});
|
|
2367
|
+
}
|
|
2266
2368
|
var handleRunTask = makeHandler("run", "run-task");
|
|
2267
2369
|
var handleForceStart = makeHandler("force-start");
|
|
2268
2370
|
var handleRetry = makeHandler("retry");
|
|
@@ -2515,24 +2617,24 @@ async function handleRewind(subArgs, injectedApi) {
|
|
|
2515
2617
|
var import_readline2 = require("readline");
|
|
2516
2618
|
|
|
2517
2619
|
// src/tui/transcript.ts
|
|
2518
|
-
var
|
|
2620
|
+
var import_core15 = require("@ordewell/core");
|
|
2519
2621
|
function show(state, { view: conversation, gate: turnGate }) {
|
|
2520
2622
|
const gated = turnGate === state.turnGate ? state : { ...state, turnGate };
|
|
2521
2623
|
if (conversation === state.conversation) return gated;
|
|
2522
|
-
|
|
2523
|
-
return { ...gated, conversation, scroll };
|
|
2624
|
+
return { ...gated, conversation };
|
|
2524
2625
|
}
|
|
2525
2626
|
function fold(state, input) {
|
|
2526
|
-
return show(state, { view: (0,
|
|
2627
|
+
return show(state, { view: (0, import_core15.reduceConversation)(state.conversation, input), gate: state.turnGate });
|
|
2527
2628
|
}
|
|
2528
2629
|
function say(state, role, text) {
|
|
2529
|
-
|
|
2630
|
+
const said = fold(state, { type: "local_entry", role, text });
|
|
2631
|
+
return said.conversation.nextId > state.conversation.nextId ? { ...said, scroll: 0 } : said;
|
|
2530
2632
|
}
|
|
2531
2633
|
function hear(state, message) {
|
|
2532
|
-
return show(state, (0,
|
|
2634
|
+
return show(state, (0, import_core15.followTurn)(state.conversation, state.turnGate, message));
|
|
2533
2635
|
}
|
|
2534
2636
|
function cutTurn(state) {
|
|
2535
|
-
return show(state, (0,
|
|
2637
|
+
return show(state, (0, import_core15.stopTurn)(state.conversation, state.turnGate));
|
|
2536
2638
|
}
|
|
2537
2639
|
function wiped(view) {
|
|
2538
2640
|
return { ...view, blocks: view.blocks.filter((b) => b.type === "usage") };
|
|
@@ -2677,7 +2779,13 @@ var style = {
|
|
|
2677
2779
|
blue: colour("34"),
|
|
2678
2780
|
magenta: colour("35"),
|
|
2679
2781
|
cyan: colour("36"),
|
|
2680
|
-
grey: colour("90")
|
|
2782
|
+
grey: colour("90"),
|
|
2783
|
+
/**
|
|
2784
|
+
* The task view's accent: a bright blue, separate from the planner chat's
|
|
2785
|
+
* cyan and magenta. Bright rather than the plain 34, which reads as black on
|
|
2786
|
+
* a dark background.
|
|
2787
|
+
*/
|
|
2788
|
+
accent: colour("94")
|
|
2681
2789
|
};
|
|
2682
2790
|
|
|
2683
2791
|
// src/tui/handoff.ts
|
|
@@ -2927,7 +3035,7 @@ async function handleHandoff(subArgs, injectedApi, confirm = askYesNo) {
|
|
|
2927
3035
|
}
|
|
2928
3036
|
|
|
2929
3037
|
// src/commands/planner.ts
|
|
2930
|
-
var
|
|
3038
|
+
var import_core16 = require("@ordewell/core");
|
|
2931
3039
|
|
|
2932
3040
|
// src/plannerModelSwitch.ts
|
|
2933
3041
|
function describePlannerSwitch(settings, provider) {
|
|
@@ -2946,7 +3054,7 @@ function rememberedModelFor(settings, provider) {
|
|
|
2946
3054
|
}
|
|
2947
3055
|
|
|
2948
3056
|
// src/commands/planner.ts
|
|
2949
|
-
var KNOWN_PROVIDERS = Object.keys(
|
|
3057
|
+
var KNOWN_PROVIDERS = Object.keys(import_core16.ALL_PROVIDERS);
|
|
2950
3058
|
async function handlePlanner(subArgs, injectedApi) {
|
|
2951
3059
|
const api = await connect(subArgs, injectedApi);
|
|
2952
3060
|
const [wanted] = positionals(subArgs);
|
|
@@ -2961,13 +3069,13 @@ async function handlePlanner(subArgs, injectedApi) {
|
|
|
2961
3069
|
const settings = await persistEnv(api, { AI_PROVIDER: provider });
|
|
2962
3070
|
const recall2 = describePlannerSwitch(settings, provider);
|
|
2963
3071
|
writeEnv({ ORCHESTRATOR_MODEL: recall2.model, ORDEWELL_PLANNER_EFFORT: recall2.effort });
|
|
2964
|
-
const meta =
|
|
3072
|
+
const meta = import_core16.ALL_PROVIDERS[provider];
|
|
2965
3073
|
const catalog = await fetchCatalog(api);
|
|
2966
3074
|
console.log(
|
|
2967
|
-
(0,
|
|
3075
|
+
(0, import_core16.isCliProvider)(provider) ? `Planning with ${meta.label} \u2014 no API key needed, it uses that agent's own subscription.` : `Planner set to ${meta.label}.`
|
|
2968
3076
|
);
|
|
2969
3077
|
console.log(plannerModelLine(recall2));
|
|
2970
|
-
if (!(0,
|
|
3078
|
+
if (!(0, import_core16.isCliProvider)(provider) && !catalog.providers.includes(provider)) {
|
|
2971
3079
|
console.log(` No ${meta.apiKeyEnvVar} configured yet \u2014 set one with \`ordewell key set ${provider} <key>\`.`);
|
|
2972
3080
|
}
|
|
2973
3081
|
}
|
|
@@ -2985,15 +3093,15 @@ async function showPlanners(api) {
|
|
|
2985
3093
|
const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
|
|
2986
3094
|
const current = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
2987
3095
|
console.log("\nCoding agents (no API key \u2014 they use their own subscription)");
|
|
2988
|
-
for (const id of
|
|
2989
|
-
const runner = (0,
|
|
3096
|
+
for (const id of import_core16.CLI_PROVIDERS) {
|
|
3097
|
+
const runner = (0, import_core16.runnerForProvider)(id);
|
|
2990
3098
|
const discovered = runner ? catalog.models.some((m) => m.runners?.includes(runner)) : false;
|
|
2991
|
-
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${
|
|
3099
|
+
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${import_core16.ALL_PROVIDERS[id].label}${discovered ? "" : " (not detected)"}`);
|
|
2992
3100
|
}
|
|
2993
3101
|
console.log("\nAPI providers");
|
|
2994
|
-
for (const id of
|
|
3102
|
+
for (const id of import_core16.PROVIDER_PRIORITY) {
|
|
2995
3103
|
const configured = catalog.providers.includes(id);
|
|
2996
|
-
const meta =
|
|
3104
|
+
const meta = import_core16.ALL_PROVIDERS[id];
|
|
2997
3105
|
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${meta.label}${configured ? "" : ` (no ${meta.apiKeyEnvVar})`}`);
|
|
2998
3106
|
}
|
|
2999
3107
|
console.log(`
|
|
@@ -3010,7 +3118,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
|
|
|
3010
3118
|
const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
3011
3119
|
const modelId = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
|
|
3012
3120
|
const currentEffort = typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : "";
|
|
3013
|
-
const runner = (0,
|
|
3121
|
+
const runner = (0, import_core16.runnerForProvider)(provider);
|
|
3014
3122
|
if (!runner) {
|
|
3015
3123
|
fail(
|
|
3016
3124
|
"Thinking effort applies to a coding-agent planner.",
|
|
@@ -3050,7 +3158,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
|
|
|
3050
3158
|
}
|
|
3051
3159
|
|
|
3052
3160
|
// src/commands/parallel.ts
|
|
3053
|
-
var
|
|
3161
|
+
var import_core17 = require("@ordewell/core");
|
|
3054
3162
|
var USAGE5 = "Usage: ordewell parallel [<number of tasks, 1 or more>]";
|
|
3055
3163
|
async function handleParallel(subArgs, injectedApi) {
|
|
3056
3164
|
const [wanted] = positionals(subArgs);
|
|
@@ -3060,14 +3168,14 @@ async function handleParallel(subArgs, injectedApi) {
|
|
|
3060
3168
|
console.log(`Up to ${String(settings.maxParallel ?? "?")} AI tasks run at once. Change with: ordewell parallel <n>`);
|
|
3061
3169
|
return;
|
|
3062
3170
|
}
|
|
3063
|
-
const limit = (0,
|
|
3171
|
+
const limit = (0, import_core17.parseMaxParallel)(wanted);
|
|
3064
3172
|
if (limit === null) fail(`"${wanted}" is not a number of tasks (1 or more).`, USAGE5);
|
|
3065
3173
|
await persistEnv(api, { ORDEWELL_MAX_PARALLEL: String(limit) });
|
|
3066
3174
|
console.log(`Up to ${limit} AI task${limit === 1 ? "" : "s"} now run at once.`);
|
|
3067
3175
|
}
|
|
3068
3176
|
|
|
3069
3177
|
// src/commands/key.ts
|
|
3070
|
-
var
|
|
3178
|
+
var import_core18 = require("@ordewell/core");
|
|
3071
3179
|
var USAGE6 = "Usage: ordewell key [list | set <provider> <key>]";
|
|
3072
3180
|
async function handleKey(subArgs, injectedApi) {
|
|
3073
3181
|
const [action, provider, ...keyParts] = positionals(subArgs);
|
|
@@ -3078,8 +3186,8 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
3078
3186
|
if (action !== "set") {
|
|
3079
3187
|
const catalog = await fetchCatalog(api);
|
|
3080
3188
|
console.log("\nAPI provider keys\n");
|
|
3081
|
-
for (const id2 of
|
|
3082
|
-
const meta2 =
|
|
3189
|
+
for (const id2 of import_core18.PROVIDER_PRIORITY) {
|
|
3190
|
+
const meta2 = import_core18.ALL_PROVIDERS[id2];
|
|
3083
3191
|
const configured = catalog.providers.includes(id2);
|
|
3084
3192
|
console.log(` ${configured ? "\u2713" : " "} ${id2.padEnd(14)} ${meta2.apiKeyEnvVar.padEnd(28)} ${meta2.label}`);
|
|
3085
3193
|
}
|
|
@@ -3089,7 +3197,7 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
3089
3197
|
}
|
|
3090
3198
|
if (!provider) fail(USAGE6);
|
|
3091
3199
|
const id = provider.toLowerCase();
|
|
3092
|
-
const meta =
|
|
3200
|
+
const meta = import_core18.ALL_PROVIDERS[id];
|
|
3093
3201
|
if (!meta) {
|
|
3094
3202
|
fail(`Unknown provider: ${provider}`, "Run `ordewell key` to see the list.");
|
|
3095
3203
|
}
|
|
@@ -3100,6 +3208,7 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
3100
3208
|
}
|
|
3101
3209
|
|
|
3102
3210
|
// src/commands/runners.ts
|
|
3211
|
+
var import_core19 = require("@ordewell/core");
|
|
3103
3212
|
var USAGE7 = "Usage: ordewell runners [<runner-id> on|off]";
|
|
3104
3213
|
async function handleRunners(subArgs, injectedApi) {
|
|
3105
3214
|
const [runner, toggle] = positionals(subArgs);
|
|
@@ -3124,21 +3233,21 @@ async function handleRunners(subArgs, injectedApi) {
|
|
|
3124
3233
|
await api.setRunnerEnabled(runner, enabled);
|
|
3125
3234
|
console.log(`${runner} ${enabled ? "enabled" : "disabled"}.`);
|
|
3126
3235
|
}
|
|
3127
|
-
var AUTO_USAGE = "Usage: ordewell auto [
|
|
3236
|
+
var AUTO_USAGE = "Usage: ordewell auto [full|auto]";
|
|
3128
3237
|
function handleAuto(subArgs) {
|
|
3129
3238
|
const [arg] = positionals(subArgs);
|
|
3130
3239
|
const currentRaw = process.env.ORDEWELL_AUTONOMOUS_MODE;
|
|
3131
3240
|
const current = currentRaw !== "false" && currentRaw !== "0";
|
|
3132
3241
|
if (!arg) {
|
|
3133
|
-
console.log(`
|
|
3242
|
+
console.log(`Autonomy level: ${(0, import_core19.autonomyLevelLabel)(current)}`);
|
|
3134
3243
|
console.log(` ${AUTO_USAGE}`);
|
|
3135
3244
|
return;
|
|
3136
3245
|
}
|
|
3137
|
-
const enabled =
|
|
3246
|
+
const enabled = (0, import_core19.parseAutonomyLevel)(arg);
|
|
3138
3247
|
if (enabled === null) fail(AUTO_USAGE);
|
|
3139
3248
|
writeEnvVar(findEnvFile(), "ORDEWELL_AUTONOMOUS_MODE", String(enabled));
|
|
3140
3249
|
process.env.ORDEWELL_AUTONOMOUS_MODE = String(enabled);
|
|
3141
|
-
console.log(`
|
|
3250
|
+
console.log(`Autonomy level: ${(0, import_core19.autonomyLevelLabel)(enabled)} for new sessions.`);
|
|
3142
3251
|
}
|
|
3143
3252
|
async function handleRefresh(subArgs, injectedApi) {
|
|
3144
3253
|
const api = await connect(subArgs, injectedApi);
|
|
@@ -3170,7 +3279,7 @@ async function handleApprove(subArgs, injectedApi) {
|
|
|
3170
3279
|
// src/tui/terminalLauncher.ts
|
|
3171
3280
|
var import_child_process3 = require("child_process");
|
|
3172
3281
|
var import_util = require("util");
|
|
3173
|
-
var
|
|
3282
|
+
var import_core20 = require("@ordewell/core");
|
|
3174
3283
|
var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
|
|
3175
3284
|
var defaultExecFile = async (command, args) => {
|
|
3176
3285
|
const { stdout, stderr } = await execFileAsync(command, args);
|
|
@@ -3190,16 +3299,16 @@ var defaultWhich = (bin) => {
|
|
|
3190
3299
|
async function openTaskTerminal(port, planSessionId, taskId, deps = {}) {
|
|
3191
3300
|
const platform = deps.platform ?? process.platform;
|
|
3192
3301
|
const env = deps.env ?? process.env;
|
|
3193
|
-
const hasTmuxImpl = deps.hasTmuxImpl ??
|
|
3302
|
+
const hasTmuxImpl = deps.hasTmuxImpl ?? import_core20.hasTmux;
|
|
3194
3303
|
const execFileImpl = deps.execFileImpl ?? defaultExecFile;
|
|
3195
3304
|
const spawnImpl = deps.spawnImpl ?? defaultSpawn;
|
|
3196
3305
|
const which = deps.which ?? defaultWhich;
|
|
3197
3306
|
if (!hasTmuxImpl()) {
|
|
3198
|
-
return { ok: false, message: "tmux is not installed
|
|
3307
|
+
return { ok: false, message: "No terminal window for this task: tmux is not installed. Install tmux to get one for terminal-transport tasks; a structured task has its log view instead." };
|
|
3199
3308
|
}
|
|
3200
|
-
const session = (0,
|
|
3201
|
-
const socket = (0,
|
|
3202
|
-
const base = (0,
|
|
3309
|
+
const session = (0, import_core20.tmuxSessionName)(port);
|
|
3310
|
+
const socket = (0, import_core20.tmuxSocketName)(port);
|
|
3311
|
+
const base = (0, import_core20.tmuxWindowName)(taskId, planSessionId);
|
|
3203
3312
|
let windows;
|
|
3204
3313
|
try {
|
|
3205
3314
|
const { stdout } = await execFileImpl("tmux", ["-L", socket, "list-windows", "-t", session, "-F", "#{window_name}"]);
|
|
@@ -3209,7 +3318,7 @@ async function openTaskTerminal(port, planSessionId, taskId, deps = {}) {
|
|
|
3209
3318
|
}
|
|
3210
3319
|
const window = latestAttempt(windows, base);
|
|
3211
3320
|
if (!window) {
|
|
3212
|
-
return { ok: false, message: "This task
|
|
3321
|
+
return { ok: false, message: "This task has no live terminal and no saved log \u2014 it may still be pending." };
|
|
3213
3322
|
}
|
|
3214
3323
|
const manualHint = `tmux -L ${socket} attach -t ${session}:${window}`;
|
|
3215
3324
|
const viewer = `${session}-view-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -3493,7 +3602,7 @@ Dependencies \xB7 #${task.order} ${task.title}
|
|
|
3493
3602
|
}
|
|
3494
3603
|
|
|
3495
3604
|
// src/tui/index.ts
|
|
3496
|
-
var
|
|
3605
|
+
var import_core33 = require("@ordewell/core");
|
|
3497
3606
|
|
|
3498
3607
|
// src/tui/editor.ts
|
|
3499
3608
|
function emptyEditor() {
|
|
@@ -3641,7 +3750,8 @@ var SLASH_COMMANDS = [
|
|
|
3641
3750
|
{ name: "retry", usage: "/retry <id>", description: "Re-run a failed task", category: "tasks" },
|
|
3642
3751
|
{ name: "cancel", usage: "/cancel <id>", description: "Kill a running task", category: "tasks" },
|
|
3643
3752
|
{ name: "force-start", usage: "/force-start <id>", description: "Start a task now, ignoring dependencies", category: "tasks" },
|
|
3644
|
-
{ name: "
|
|
3753
|
+
{ name: "continue", usage: "/continue <id> <message>", description: "Continue a finished task in its saved runner session, with your message", category: "tasks" },
|
|
3754
|
+
{ name: "terminal", usage: "/terminal <id>", description: "Open a task: its OS terminal, or a structured task's live log", category: "tasks" },
|
|
3645
3755
|
{ name: "task-runner", usage: "/task-runner <id> [runner]", description: "Choose or set a task's executor (re-picks its model and mode)", category: "tasks" },
|
|
3646
3756
|
{ name: "task-model", usage: "/task-model <id> [model]", description: "Choose or set a task's executor model", category: "tasks" },
|
|
3647
3757
|
{ name: "task-effort", usage: "/task-effort <id> [level]", description: "Choose or set a task's thinking effort", category: "tasks" },
|
|
@@ -3654,7 +3764,8 @@ var SLASH_COMMANDS = [
|
|
|
3654
3764
|
{ name: "key", usage: "/key [set <provider> <key>]", description: "Configure API provider keys", category: "models" },
|
|
3655
3765
|
{ name: "allowlist", usage: "/allowlist [set <runner> <ids> | clear <runner>]", description: "Limit which models a runner may use", category: "models" },
|
|
3656
3766
|
{ name: "runners", usage: "/runners [<id> on|off]", description: "Enable or disable runners (claude-code, opencode, codex)", category: "models" },
|
|
3657
|
-
{ name: "auto", usage: "/auto [
|
|
3767
|
+
{ name: "auto", usage: "/auto [full|auto]", description: "Autonomy level for new plans: Full auto or Auto", category: "models" },
|
|
3768
|
+
{ name: "transport", usage: "/transport [terminal|structured]", description: "Drive tasks through each runner's protocol (structured) or a terminal, from the next run", category: "models" },
|
|
3658
3769
|
{ name: "parallel", usage: "/parallel [<n>]", description: "How many AI tasks run at once (applies to a live run)", category: "models" },
|
|
3659
3770
|
{ name: "refresh", usage: "/refresh", description: "Re-discover runners and model catalogs", category: "models" },
|
|
3660
3771
|
// Skills
|
|
@@ -3739,7 +3850,7 @@ function tokenCompletions(token) {
|
|
|
3739
3850
|
}
|
|
3740
3851
|
|
|
3741
3852
|
// src/tui/blocks.ts
|
|
3742
|
-
var
|
|
3853
|
+
var import_core21 = require("@ordewell/core");
|
|
3743
3854
|
|
|
3744
3855
|
// src/tui/markdown.ts
|
|
3745
3856
|
function renderMarkdown(source, cols) {
|
|
@@ -3872,13 +3983,14 @@ function tailOf(text, max) {
|
|
|
3872
3983
|
var ROLE_PREFIX = {
|
|
3873
3984
|
user: (t) => `${style.cyan("\u276F")} ${t}`,
|
|
3874
3985
|
planner: (t) => `${style.magenta("\u25C6")} ${t}`,
|
|
3986
|
+
agent: (t) => `${style.magenta("\u25C6")} ${t}`,
|
|
3875
3987
|
system: (t) => style.grey(`\xB7 ${t}`),
|
|
3876
3988
|
error: (t) => `${style.red("\u2717")} ${style.red(t)}`
|
|
3877
3989
|
};
|
|
3878
3990
|
function messageLines(block, cols) {
|
|
3879
3991
|
const text = sanitize(block.text);
|
|
3880
3992
|
const room = Math.max(1, cols - 2);
|
|
3881
|
-
const wrapped = block.role === "planner" ? renderMarkdown(text, room) : wrap(text, room);
|
|
3993
|
+
const wrapped = block.role === "planner" || block.role === "agent" ? renderMarkdown(text, room) : wrap(text, room);
|
|
3882
3994
|
return wrapped.map((line, i) => i === 0 ? ROLE_PREFIX[block.role](line) : ` ${line}`);
|
|
3883
3995
|
}
|
|
3884
3996
|
function thinkingLines(block, cols, detailAll) {
|
|
@@ -3946,16 +4058,16 @@ function resultLines(block, room, detailAll) {
|
|
|
3946
4058
|
const fit2 = (line) => detailAll ? wrap(line, room) : [truncate(line, room)];
|
|
3947
4059
|
const outcome = unsettledOutcome(block);
|
|
3948
4060
|
if (outcome) {
|
|
3949
|
-
const [reason, ...rest] = (0,
|
|
4061
|
+
const [reason, ...rest] = (0, import_core21.outputLines)(block.output).map(sanitize);
|
|
3950
4062
|
const status = fit2(reason ? `${outcome.label}: ${reason}` : outcome.label).map(outcome.paint);
|
|
3951
4063
|
return detailAll ? [...status, ...rest.flatMap(fit2).map(style.grey)] : status;
|
|
3952
4064
|
}
|
|
3953
4065
|
if (detailAll) {
|
|
3954
|
-
const lines2 = (0,
|
|
4066
|
+
const lines2 = (0, import_core21.outputLines)(block.output).map(sanitize);
|
|
3955
4067
|
const note = lines2.length > PREVIEW_LINES ? ["\u2026 (ctrl+o to collapse)"] : [];
|
|
3956
4068
|
return lines2.length > 0 ? [...lines2.flatMap(fit2), ...note].map(style.grey) : [style.grey("(no output)")];
|
|
3957
4069
|
}
|
|
3958
|
-
const { lines, hiddenLineCount } = (0,
|
|
4070
|
+
const { lines, hiddenLineCount } = (0, import_core21.outputPreview)(block.output, PREVIEW_LINES);
|
|
3959
4071
|
if (lines.length === 0) return [style.grey("(no output)")];
|
|
3960
4072
|
const more = hiddenLineCount > 0 ? [`\u2026 +${countOf(hiddenLineCount, "line")} (ctrl+o to expand)`] : [];
|
|
3961
4073
|
return [...lines.map(sanitize), ...more].map((line) => style.grey(truncate(line, room)));
|
|
@@ -3996,7 +4108,8 @@ function subagentLines(block, cols, detailAll) {
|
|
|
3996
4108
|
var APPROVAL_KIND = {
|
|
3997
4109
|
shell_command: "Run a command",
|
|
3998
4110
|
url_fetch: "Fetch a URL",
|
|
3999
|
-
external_path: "Read outside the workspace"
|
|
4111
|
+
external_path: "Read outside the workspace",
|
|
4112
|
+
runner_tool: "Use a tool"
|
|
4000
4113
|
};
|
|
4001
4114
|
var DECIDED_BY = {
|
|
4002
4115
|
"pre-approved": "pre-approved",
|
|
@@ -4006,8 +4119,9 @@ var DECIDED_BY = {
|
|
|
4006
4119
|
};
|
|
4007
4120
|
function approvalLine(block, cols) {
|
|
4008
4121
|
const policy = block.decidedBy && block.decidedBy !== "asked" ? DECIDED_BY[block.decidedBy] : void 0;
|
|
4009
|
-
const [mark, status, paint] = block.status === "pending" ? ["?", "Waiting for you", style.yellow] : block.status === "granted" ? ["\u2713", policy ? `Auto-approved (${policy})` : "Approved", style.green] : ["\u2298", policy ? `Auto-denied (${policy})` : "Denied", style.yellow];
|
|
4010
|
-
const
|
|
4122
|
+
const [mark, status, paint] = block.status === "pending" ? ["?", "Waiting for you", style.yellow] : block.status === "granted" ? ["\u2713", policy ? `Auto-approved (${policy})` : block.forTask ? "Approved for this task" : "Approved", style.green] : block.status === "withdrawn" ? ["\xB7", "Withdrawn", style.grey] : ["\u2298", policy ? `Auto-denied (${policy})` : "Denied", style.yellow];
|
|
4123
|
+
const note = block.note ? ` \u2014 \u201C${firstLine(block.note)}\u201D` : "";
|
|
4124
|
+
const line = `${mark} ${status} \xB7 ${APPROVAL_KIND[block.kind]}: ${firstLine(block.subject)}${note}`;
|
|
4011
4125
|
return `${paint(mark)}${truncate(line, cols).slice(mark.length)}`;
|
|
4012
4126
|
}
|
|
4013
4127
|
function planLine(block, cols) {
|
|
@@ -4073,38 +4187,8 @@ function tokenLine(blocks, cols) {
|
|
|
4073
4187
|
return parts.length > 0 ? style.grey(truncate(parts.join(" \xB7 "), cols)) : null;
|
|
4074
4188
|
}
|
|
4075
4189
|
|
|
4076
|
-
// src/tui/geometry.ts
|
|
4077
|
-
var PLAN_MIN_COLS = 32;
|
|
4078
|
-
var PLAN_MAX_COLS = 64;
|
|
4079
|
-
var PANE_INDENT = 4;
|
|
4080
|
-
var CHAT_PROMPT_COLS = 2;
|
|
4081
|
-
function planPaneWidth(state) {
|
|
4082
|
-
if (state.tasks.length === 0) return 0;
|
|
4083
|
-
const wanted = Math.min(PLAN_MAX_COLS, Math.floor(state.cols * 0.46));
|
|
4084
|
-
return wanted >= PLAN_MIN_COLS && state.cols - wanted > PLAN_MIN_COLS ? wanted : 0;
|
|
4085
|
-
}
|
|
4086
|
-
function chatPaneWidth(state) {
|
|
4087
|
-
const plan = planPaneWidth(state);
|
|
4088
|
-
return plan === 0 ? state.cols : state.cols - plan - 1;
|
|
4089
|
-
}
|
|
4090
|
-
function paneColumns(state, pane) {
|
|
4091
|
-
const plan = planPaneWidth(state);
|
|
4092
|
-
if (plan === 0) return { first: 1, last: state.cols };
|
|
4093
|
-
const chat = chatPaneWidth(state);
|
|
4094
|
-
return pane === "chat" ? { first: 1, last: chat } : { first: chat + 2, last: state.cols };
|
|
4095
|
-
}
|
|
4096
|
-
function paneTextRoom(paneCols) {
|
|
4097
|
-
return Math.max(1, paneCols - PANE_INDENT);
|
|
4098
|
-
}
|
|
4099
|
-
function taskEditorRoom(state) {
|
|
4100
|
-
return paneTextRoom(planPaneWidth(state));
|
|
4101
|
-
}
|
|
4102
|
-
function chatEditorRoom(cols, active) {
|
|
4103
|
-
return Math.max(1, cols - CHAT_PROMPT_COLS - (active ? 1 : 0));
|
|
4104
|
-
}
|
|
4105
|
-
|
|
4106
4190
|
// src/tui/state.ts
|
|
4107
|
-
var
|
|
4191
|
+
var import_core22 = require("@ordewell/core");
|
|
4108
4192
|
var SKILL_IDS = ["tdd", "verify"];
|
|
4109
4193
|
function noSkills() {
|
|
4110
4194
|
return { tdd: false, verify: false };
|
|
@@ -4134,6 +4218,12 @@ function planRows(state) {
|
|
|
4134
4218
|
function selectedPlanRow(state) {
|
|
4135
4219
|
return planRows(state)[state.selectedTask] ?? null;
|
|
4136
4220
|
}
|
|
4221
|
+
function waitingApproval(tv) {
|
|
4222
|
+
return tv.view.blocks.find((b) => b.type === "approval" && b.status === "pending" && b.approvalId !== void 0);
|
|
4223
|
+
}
|
|
4224
|
+
function continuesTask(task) {
|
|
4225
|
+
return task?.continuable === true && (task.status === "completed" || task.status === "failed");
|
|
4226
|
+
}
|
|
4137
4227
|
function findTask(tasks, id) {
|
|
4138
4228
|
for (const task of tasks) {
|
|
4139
4229
|
if (task.id === id) return task;
|
|
@@ -4145,8 +4235,9 @@ function findTask(tasks, id) {
|
|
|
4145
4235
|
function initialState(overrides = {}) {
|
|
4146
4236
|
return {
|
|
4147
4237
|
editor: emptyEditor(),
|
|
4148
|
-
conversation:
|
|
4149
|
-
|
|
4238
|
+
conversation: import_core22.EMPTY_CONVERSATION,
|
|
4239
|
+
taskView: null,
|
|
4240
|
+
turnGate: import_core22.NO_TURN,
|
|
4150
4241
|
detailAll: false,
|
|
4151
4242
|
status: "idle",
|
|
4152
4243
|
busyLabel: "",
|
|
@@ -4171,17 +4262,18 @@ function initialState(overrides = {}) {
|
|
|
4171
4262
|
orchestratorModel: "",
|
|
4172
4263
|
plannerProvider: "",
|
|
4173
4264
|
plannerEffort: "",
|
|
4174
|
-
maxParallel:
|
|
4265
|
+
maxParallel: import_core22.DEFAULT_MAX_PARALLEL,
|
|
4175
4266
|
configuredProviders: [],
|
|
4176
4267
|
allowlist: {},
|
|
4177
4268
|
autonomous: true,
|
|
4269
|
+
runnerTransport: "structured",
|
|
4178
4270
|
mouseCapture: true,
|
|
4179
4271
|
selection: null,
|
|
4180
4272
|
workspace: process.cwd(),
|
|
4181
4273
|
handoff: null,
|
|
4182
4274
|
overlay: null,
|
|
4183
4275
|
pendingApprovals: [],
|
|
4184
|
-
queuedPrompts:
|
|
4276
|
+
queuedPrompts: import_core22.EMPTY_HOLD,
|
|
4185
4277
|
stopArmed: false,
|
|
4186
4278
|
stopArmToken: 0,
|
|
4187
4279
|
stopRequested: false,
|
|
@@ -4194,19 +4286,53 @@ function initialState(overrides = {}) {
|
|
|
4194
4286
|
};
|
|
4195
4287
|
}
|
|
4196
4288
|
|
|
4289
|
+
// src/tui/geometry.ts
|
|
4290
|
+
var PLAN_MIN_COLS = 32;
|
|
4291
|
+
var PLAN_MAX_COLS = 64;
|
|
4292
|
+
var PANE_INDENT = 4;
|
|
4293
|
+
function planPaneWidth(state) {
|
|
4294
|
+
if (state.tasks.length === 0) return 0;
|
|
4295
|
+
const wanted = Math.min(PLAN_MAX_COLS, Math.floor(state.cols * 0.46));
|
|
4296
|
+
return wanted >= PLAN_MIN_COLS && state.cols - wanted > PLAN_MIN_COLS ? wanted : 0;
|
|
4297
|
+
}
|
|
4298
|
+
function chatPaneWidth(state) {
|
|
4299
|
+
const plan = planPaneWidth(state);
|
|
4300
|
+
return plan === 0 ? state.cols : state.cols - plan - 1;
|
|
4301
|
+
}
|
|
4302
|
+
function paneColumns(state, pane) {
|
|
4303
|
+
const plan = planPaneWidth(state);
|
|
4304
|
+
if (plan === 0) return { first: 1, last: state.cols };
|
|
4305
|
+
const chat = chatPaneWidth(state);
|
|
4306
|
+
return pane === "chat" ? { first: 1, last: chat } : { first: chat + 2, last: state.cols };
|
|
4307
|
+
}
|
|
4308
|
+
function paneTextRoom(paneCols) {
|
|
4309
|
+
return Math.max(1, paneCols - PANE_INDENT);
|
|
4310
|
+
}
|
|
4311
|
+
function taskEditorRoom(state) {
|
|
4312
|
+
return paneTextRoom(planPaneWidth(state));
|
|
4313
|
+
}
|
|
4314
|
+
function chatPromptLabel(state) {
|
|
4315
|
+
if (!state.taskView) return "\u276F";
|
|
4316
|
+
const task = findTask(state.tasks, state.taskView.taskId);
|
|
4317
|
+
return `\u2192 ${continuesTask(task) ? "Continue task" : "Task"} ${task?.order ?? "?"}`;
|
|
4318
|
+
}
|
|
4319
|
+
function chatEditorRoomFor(state, active) {
|
|
4320
|
+
return Math.max(1, state.cols - width(chatPromptLabel(state)) - 1 - (active ? 1 : 0));
|
|
4321
|
+
}
|
|
4322
|
+
|
|
4197
4323
|
// src/tui/layout.ts
|
|
4198
|
-
var
|
|
4324
|
+
var import_core23 = require("@ordewell/core");
|
|
4199
4325
|
var CHROME_ROWS = 4;
|
|
4200
4326
|
function chatInputWrap(state) {
|
|
4201
4327
|
const active = state.focus === "chat" && !state.overlay;
|
|
4202
4328
|
if (!active) return null;
|
|
4203
|
-
return wrapLines(state.editor.text,
|
|
4329
|
+
return wrapLines(state.editor.text, chatEditorRoomFor(state, true));
|
|
4204
4330
|
}
|
|
4205
4331
|
function footerHints(state) {
|
|
4206
4332
|
const selected = selectedPlanRow(state)?.task;
|
|
4207
4333
|
const markHint = selected?.status === "completed" ? "m undone" : "m done";
|
|
4208
4334
|
const resolveHint = selected?.isolation?.state === "conflict" ? ["x resolve conflict"] : [];
|
|
4209
|
-
const planning = plannerInFlight(state);
|
|
4335
|
+
const planning = plannerInFlight(state) && !state.taskView;
|
|
4210
4336
|
const escHint = !planning ? null : state.queuedPrompts.length > 0 ? "esc unsend" : "esc \xD72 stop planning";
|
|
4211
4337
|
if (state.focus === "plan") {
|
|
4212
4338
|
if (state.taskEditor) {
|
|
@@ -4234,7 +4360,7 @@ function footerHints(state) {
|
|
|
4234
4360
|
"tab chat"
|
|
4235
4361
|
];
|
|
4236
4362
|
}
|
|
4237
|
-
const detailHint = (0,
|
|
4363
|
+
const detailHint = (0, import_core23.hasHiddenDetail)(state.taskView ? state.taskView.view.blocks : state.conversation.blocks) ? [state.detailAll ? "ctrl-o collapse all" : "ctrl-o expand all"] : [];
|
|
4238
4364
|
return [
|
|
4239
4365
|
"/help",
|
|
4240
4366
|
"tab plan",
|
|
@@ -4252,6 +4378,43 @@ function queuedBubble(text, cols) {
|
|
|
4252
4378
|
const room = Math.max(1, cols - width("\u25C7 \xB7 queued \xB7 esc to unsend"));
|
|
4253
4379
|
return wrap(text, room).map((line, i) => i === 0 ? truncate(`${style.grey(`\u25C7 ${line}`)} \xB7 queued \xB7 esc to unsend`, cols) : truncate(style.grey(` ${line}`), cols));
|
|
4254
4380
|
}
|
|
4381
|
+
function taskQueuedRows(state, cols) {
|
|
4382
|
+
const tv = state.taskView;
|
|
4383
|
+
if (!tv) return [];
|
|
4384
|
+
return tv.view.queued.flatMap((message, i) => queuedTaskBubble(message.text, i === tv.queuedIndex, cols));
|
|
4385
|
+
}
|
|
4386
|
+
function queuedTaskBubble(text, selected, cols) {
|
|
4387
|
+
const marker2 = selected ? style.accent("\u276F") : style.grey("\u25C7");
|
|
4388
|
+
const tag = selected ? " \xB7 queued \xB7 ctrl-r removes" : " \xB7 queued";
|
|
4389
|
+
const room = Math.max(1, cols - width(`\u276F \u25C7 ${tag}`));
|
|
4390
|
+
return wrap(text, room).map((line, i) => i === 0 ? truncate(`${marker2} ${selected ? style.bold(line) : line}${style.grey(tag)}`, cols) : truncate(` ${line}`, cols));
|
|
4391
|
+
}
|
|
4392
|
+
function taskActivity(task, working) {
|
|
4393
|
+
if (task?.awaitingApproval) return approvalLabel(task.awaitingApproval);
|
|
4394
|
+
if (working) return "working";
|
|
4395
|
+
if (task?.status === "awaiting_user") {
|
|
4396
|
+
return task.awaitingReason ? AWAITING_LABEL[task.awaitingReason] : "waiting for your input";
|
|
4397
|
+
}
|
|
4398
|
+
return task?.status ?? "";
|
|
4399
|
+
}
|
|
4400
|
+
function taskHeaderLines(state, tv, cols) {
|
|
4401
|
+
const task = findTask(state.tasks, tv.taskId);
|
|
4402
|
+
const parts = [
|
|
4403
|
+
`\u2192 Task ${task?.order ?? "?"}`,
|
|
4404
|
+
task?.title ?? "(task removed)",
|
|
4405
|
+
task?.assignedRunner ?? "",
|
|
4406
|
+
taskActivity(task, tv.view.working)
|
|
4407
|
+
].filter(Boolean);
|
|
4408
|
+
const approval = waitingApproval(tv);
|
|
4409
|
+
const hint = approval ? ["ctrl-y allow", ...approval.allowForTask ? ["ctrl-t allow for task"] : [], "ctrl-g deny (composer text is the note)"] : ["ctrl-r remove queued", "ctrl-x interrupt"];
|
|
4410
|
+
const index = tv.attempts.indexOf(tv.attempt);
|
|
4411
|
+
if (tv.attempts.length > 1) hint.push(`alt\u2190/\u2192 attempt ${index >= 0 ? index + 1 : 1}/${tv.attempts.length}`);
|
|
4412
|
+
hint.push("esc back");
|
|
4413
|
+
return [
|
|
4414
|
+
style.accent(truncate(parts.join(" \xB7 "), cols)),
|
|
4415
|
+
style.grey(truncate(hint.join(" \xB7 "), cols))
|
|
4416
|
+
];
|
|
4417
|
+
}
|
|
4255
4418
|
function stopHint(state) {
|
|
4256
4419
|
return plannerInFlight(state) && state.stopArmed ? "Press Esc again to stop" : "";
|
|
4257
4420
|
}
|
|
@@ -4284,24 +4447,27 @@ function chatBodyLines(blocks, cols, detailAll) {
|
|
|
4284
4447
|
return lines;
|
|
4285
4448
|
}
|
|
4286
4449
|
function chatLayout(state, rows, cols) {
|
|
4287
|
-
const
|
|
4288
|
-
const
|
|
4450
|
+
const tv = state.taskView;
|
|
4451
|
+
const blocks = tv ? tv.view.blocks : state.conversation.blocks;
|
|
4452
|
+
const welcome = !tv && state.tasks.length === 0;
|
|
4453
|
+
const body = chatBodyLines(blocks, cols, state.detailAll);
|
|
4289
4454
|
const transcript = !welcome ? body : body.length === 0 ? welcomeLines(state, cols) : [...welcomeLines(state, cols), "", ...body];
|
|
4290
|
-
const bubbles = queuedPromptRows(state, cols);
|
|
4455
|
+
const bubbles = tv ? taskQueuedRows(state, cols) : queuedPromptRows(state, cols);
|
|
4291
4456
|
const lines = [...transcript, ...bubbles.length > 0 ? ["", ...bubbles] : []];
|
|
4292
|
-
const
|
|
4457
|
+
const header = tv ? taskHeaderLines(state, tv, cols) : [];
|
|
4458
|
+
const usage = rows > 1 ? tokenLine(blocks, cols) : null;
|
|
4293
4459
|
const footer = usage ? [usage] : [];
|
|
4294
|
-
const room = rows - footer.length;
|
|
4295
|
-
return { lines, footer, anchor: lines.length > room ? "bottom" : "top", maxScroll: Math.max(0, lines.length - room) };
|
|
4460
|
+
const room = Math.max(0, rows - header.length - footer.length);
|
|
4461
|
+
return { lines, header, footer, anchor: lines.length > room ? "bottom" : "top", maxScroll: Math.max(0, lines.length - room) };
|
|
4296
4462
|
}
|
|
4297
4463
|
function chatScrollMax(state) {
|
|
4298
4464
|
return chatLayout(state, bodyRows(state), chatPaneWidth(state)).maxScroll;
|
|
4299
4465
|
}
|
|
4300
4466
|
function plannerSetup(state) {
|
|
4301
4467
|
const provider = state.plannerProvider;
|
|
4302
|
-
const registration =
|
|
4468
|
+
const registration = import_core23.ALL_PROVIDERS[provider];
|
|
4303
4469
|
if (!registration) return { label: "not set", ready: false };
|
|
4304
|
-
const runner = (0,
|
|
4470
|
+
const runner = (0, import_core23.runnerForProvider)(provider);
|
|
4305
4471
|
const ready = runner ? state.runners.some((r) => r.id === runner) : state.configuredProviders.includes(provider);
|
|
4306
4472
|
return { label: registration.label, ready };
|
|
4307
4473
|
}
|
|
@@ -4408,6 +4574,14 @@ var STATUS_ICON = {
|
|
|
4408
4574
|
approved: "\xB7",
|
|
4409
4575
|
pending: "\xB7"
|
|
4410
4576
|
};
|
|
4577
|
+
var AWAITING_LABEL = {
|
|
4578
|
+
input: "waiting for your input",
|
|
4579
|
+
checkpoint: "checkpoint",
|
|
4580
|
+
conflict: "merge conflict"
|
|
4581
|
+
};
|
|
4582
|
+
function approvalLabel(count) {
|
|
4583
|
+
return count > 1 ? `waiting for approval (${count})` : "waiting for approval";
|
|
4584
|
+
}
|
|
4411
4585
|
var IDLE_ICON = "~";
|
|
4412
4586
|
var FIRST_ROW_LINE = 2;
|
|
4413
4587
|
function planLayout(state, rows, cols) {
|
|
@@ -4484,7 +4658,7 @@ function taskLines(state, row, index, cols) {
|
|
|
4484
4658
|
const icon = paint(rawIcon);
|
|
4485
4659
|
const kind = running ? style.yellow("RUN") : task.type === "user" ? style.yellow("MAN") : style.grey(" AI");
|
|
4486
4660
|
const caret = selected ? style.cyan("\u276F") : " ";
|
|
4487
|
-
const orderLabel = parent ? (0,
|
|
4661
|
+
const orderLabel = parent ? (0, import_core23.taskOrderLabel)(task, parent) : String(task.order).padStart(2);
|
|
4488
4662
|
const indent = parent ? " " : "";
|
|
4489
4663
|
const head = `${indent}${caret} ${icon} ${orderLabel} ${kind} `;
|
|
4490
4664
|
const titleRoom = Math.max(1, cols - width(head));
|
|
@@ -4505,22 +4679,25 @@ function taskLines(state, row, index, cols) {
|
|
|
4505
4679
|
const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${modeInfo?.autonomous ? style.yellow(" \u26A1") : ""}` : "";
|
|
4506
4680
|
const runner = task.assignedRunner ?? "";
|
|
4507
4681
|
const bodyPad = parent ? " " : " ";
|
|
4508
|
-
const
|
|
4509
|
-
const
|
|
4682
|
+
const structured = task.transport?.kind === "structured";
|
|
4683
|
+
const waiting = task.status === "awaiting_user" && task.awaitingReason ? AWAITING_LABEL[task.awaitingReason] : "";
|
|
4684
|
+
const activity = task.awaitingApproval ? `${approvalLabel(task.awaitingApproval)} \u2014 t opens it` : idle ? structured ? "quiet" : "quiet \u2014 t opens its terminal" : running ? "working" : waiting;
|
|
4685
|
+
const meta = [activity, runner, structured ? "structured" : "", model].filter(Boolean).join(" \xB7 ");
|
|
4510
4686
|
if (meta) lines.push(style.grey(truncate(`${bodyPad}${meta}`, cols)));
|
|
4687
|
+
if (task.transport?.fallback) lines.push(style.yellow(truncate(`${bodyPad}terminal: ${task.transport.fallback}`, cols)));
|
|
4511
4688
|
if (effort || mode) lines.push(style.grey(truncate(`${bodyPad}${[effort, mode].filter(Boolean).join(" \xB7 ")}`, cols)));
|
|
4512
4689
|
if (task.isolation?.state === "conflict") {
|
|
4513
4690
|
const where = task.isolation.conflictRepo && task.isolation.conflictRepo !== "." ? ` in ${task.isolation.conflictRepo}` : "";
|
|
4514
|
-
const files = task.isolation.conflictFiles?.length ? ` (${(0,
|
|
4691
|
+
const files = task.isolation.conflictFiles?.length ? ` (${(0, import_core23.capConflictFiles)(task.isolation.conflictFiles)})` : "";
|
|
4515
4692
|
lines.push(style.red(truncate(`${bodyPad}\u26A0 merge conflict${where}${files} \u2014 its work is kept on its own branch`, cols)));
|
|
4516
4693
|
}
|
|
4517
4694
|
if (task.isolation?.state === "repairing") {
|
|
4518
|
-
const files = task.isolation.conflictFiles?.length ? ` in ${(0,
|
|
4695
|
+
const files = task.isolation.conflictFiles?.length ? ` in ${(0, import_core23.capConflictFiles)(task.isolation.conflictFiles)}` : "";
|
|
4519
4696
|
const attempt = task.isolation.repair ? ` (attempt ${task.isolation.repair.attempt}/${task.isolation.repair.limit})` : "";
|
|
4520
4697
|
lines.push(style.yellow(truncate(`${bodyPad}\u21BB repairing conflict${files}${attempt}`, cols)));
|
|
4521
4698
|
}
|
|
4522
4699
|
if (task.isolation?.state === "integrated" && task.isolation.repairedFiles?.length) {
|
|
4523
|
-
lines.push(style.grey(truncate(`${bodyPad}\u21BB landed after repairing conflict in ${(0,
|
|
4700
|
+
lines.push(style.grey(truncate(`${bodyPad}\u21BB landed after repairing conflict in ${(0, import_core23.capConflictFiles)(task.isolation.repairedFiles)}`, cols)));
|
|
4524
4701
|
}
|
|
4525
4702
|
let editorLine;
|
|
4526
4703
|
if (expanded) {
|
|
@@ -4534,7 +4711,7 @@ function taskLines(state, row, index, cols) {
|
|
|
4534
4711
|
}
|
|
4535
4712
|
}
|
|
4536
4713
|
if (modeInfo?.autonomous) {
|
|
4537
|
-
lines.push(...taskText("Autonomy", "Runs without permission prompts.
|
|
4714
|
+
lines.push(...taskText("Autonomy", "Runs without permission prompts (Full auto). Change the level with /auto.", cols, bodyPad));
|
|
4538
4715
|
}
|
|
4539
4716
|
const editableSeed = task.prompt ?? task.description ?? task.title;
|
|
4540
4717
|
if (task.description && task.description !== task.title && task.description !== editableSeed) {
|
|
@@ -4596,6 +4773,9 @@ function helpLayout(rows, cols) {
|
|
|
4596
4773
|
body.push(
|
|
4597
4774
|
style.grey("tab switches panes \xB7 pgup/pgdn scroll \xB7 ctrl-o toggles full detail \xB7 esc takes back a queued prompt, otherwise esc twice stops \xB7 ctrl-l clears \xB7 ctrl-c quits")
|
|
4598
4775
|
);
|
|
4776
|
+
body.push(
|
|
4777
|
+
style.grey("in a task view (t or /terminal on a structured task): ctrl-r removes the selected queued message \xB7 ctrl-x interrupts \xB7 ctrl-y allows a tool request \xB7 ctrl-t allows it for the task \xB7 ctrl-g denies it, with the composer text as the note \xB7 alt\u2190/\u2192 changes attempt \xB7 esc returns")
|
|
4778
|
+
);
|
|
4599
4779
|
const lines = body.map((line) => truncate(line, Math.max(1, cols - 2)));
|
|
4600
4780
|
const room = Math.max(1, rows - 2);
|
|
4601
4781
|
return { lines, room, maxScroll: Math.max(0, lines.length - room) };
|
|
@@ -4605,7 +4785,7 @@ function helpScrollMax(state) {
|
|
|
4605
4785
|
}
|
|
4606
4786
|
|
|
4607
4787
|
// src/tui/render.ts
|
|
4608
|
-
var
|
|
4788
|
+
var import_core24 = require("@ordewell/core");
|
|
4609
4789
|
function render(state) {
|
|
4610
4790
|
const { rows, cols } = state;
|
|
4611
4791
|
const rowsForBody = bodyRows(state);
|
|
@@ -4614,7 +4794,7 @@ function render(state) {
|
|
|
4614
4794
|
renderSkills(state, cols),
|
|
4615
4795
|
...body,
|
|
4616
4796
|
renderStatus(state, cols),
|
|
4617
|
-
...renderInput(state
|
|
4797
|
+
...renderInput(state),
|
|
4618
4798
|
...renderFooter(state, cols)
|
|
4619
4799
|
];
|
|
4620
4800
|
const frame2 = lines.slice(Math.max(0, lines.length - rows)).map((l) => pad(l, cols));
|
|
@@ -4700,8 +4880,9 @@ function renderSkills(state, cols) {
|
|
|
4700
4880
|
const badges = SKILL_IDS.map(
|
|
4701
4881
|
(id) => state.skills[id] ? style.green(`\u25CF ${id}`) : style.grey(`\u25CB ${id}`)
|
|
4702
4882
|
);
|
|
4703
|
-
const auto = state.autonomous ? style.yellow("\u25CF auto") : style.
|
|
4704
|
-
|
|
4883
|
+
const auto = state.autonomous ? style.yellow("\u25CF Full auto") : style.green("\u25CF Auto");
|
|
4884
|
+
const terminal = state.runnerTransport === "terminal" ? [style.yellow("\u25CF terminal")] : [];
|
|
4885
|
+
return truncate([...badges, auto, ...terminal].join(" "), cols);
|
|
4705
4886
|
}
|
|
4706
4887
|
function scrolledBackMark(state) {
|
|
4707
4888
|
return state.focus !== "plan" && state.scroll > 0 ? "\u2191 scrolled back \xB7 pgdn for live" : "";
|
|
@@ -4746,10 +4927,10 @@ function paintSpans(text, base, spans) {
|
|
|
4746
4927
|
}
|
|
4747
4928
|
return out + text.slice(cursor);
|
|
4748
4929
|
}
|
|
4749
|
-
function renderInput(state
|
|
4750
|
-
const prompt = state.focus === "plan" ? style.grey("\u276F ") : style.cyan("\u276F ");
|
|
4930
|
+
function renderInput(state) {
|
|
4931
|
+
const prompt = state.taskView ? style.accent(`${chatPromptLabel(state)} `) : state.focus === "plan" ? style.grey("\u276F ") : style.cyan("\u276F ");
|
|
4751
4932
|
const lines = chatInputWrap(state);
|
|
4752
|
-
const room =
|
|
4933
|
+
const room = chatEditorRoomFor(state, lines !== null);
|
|
4753
4934
|
const { text, cursor } = state.editor;
|
|
4754
4935
|
const continuation = " ".repeat(width(prompt));
|
|
4755
4936
|
const spans = highlightSpans(text);
|
|
@@ -4792,16 +4973,18 @@ function renderBody(state, rows, cols) {
|
|
|
4792
4973
|
const chatCols = chatPaneWidth(state);
|
|
4793
4974
|
const chat = renderChat(state, rows, chatCols);
|
|
4794
4975
|
const plan = renderPlan(state, rows, planCols);
|
|
4795
|
-
const
|
|
4976
|
+
const border = state.taskView ? style.accent("\u2502") : style.grey("\u2502");
|
|
4977
|
+
const divider = `${ERASE_TO_END}${atColumn(chatCols + 1)}${border}`;
|
|
4796
4978
|
return Array.from(
|
|
4797
4979
|
{ length: rows },
|
|
4798
4980
|
(_, i) => `${pad(chat[i] ?? "", chatCols)}${divider}${pad(plan[i] ?? "", planCols)}`
|
|
4799
4981
|
);
|
|
4800
4982
|
}
|
|
4801
4983
|
function renderChat(state, rows, cols) {
|
|
4802
|
-
const { lines, footer, anchor, maxScroll } = chatLayout(state, rows, cols);
|
|
4984
|
+
const { lines, header, footer, anchor, maxScroll } = chatLayout(state, rows, cols);
|
|
4803
4985
|
const back = Math.min(state.scroll, maxScroll);
|
|
4804
|
-
|
|
4986
|
+
const body = Math.max(0, rows - header.length - footer.length);
|
|
4987
|
+
return [...header, ...fit(lines.slice(0, lines.length - back), body, anchor), ...footer];
|
|
4805
4988
|
}
|
|
4806
4989
|
function renderPlan(state, rows, cols) {
|
|
4807
4990
|
const layout = planLayout(state, rows, cols);
|
|
@@ -4867,7 +5050,7 @@ function renderHandoff(state, overlay, rows, cols) {
|
|
|
4867
5050
|
return frame(`Diff \u2014 ${handoffBranch(handoff)}`, [...shown, "", style.grey(`${more}enter or esc goes back`)], rows, cols);
|
|
4868
5051
|
}
|
|
4869
5052
|
const landed = handoff.landed.length === 0 ? [style.grey("Nothing landed on it.")] : handoff.landed.map((task) => {
|
|
4870
|
-
const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0,
|
|
5053
|
+
const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0, import_core24.capConflictFiles)(task.repairedFiles)})`) : "";
|
|
4871
5054
|
return ` ${style.green("\u2713")} #${task.order} ${task.title}${repaired}`;
|
|
4872
5055
|
});
|
|
4873
5056
|
const actions = handoffActions(handoff).map((action, index) => {
|
|
@@ -4957,7 +5140,7 @@ function frame(title, body, rows, cols) {
|
|
|
4957
5140
|
}
|
|
4958
5141
|
|
|
4959
5142
|
// src/tui/reducer.ts
|
|
4960
|
-
var
|
|
5143
|
+
var import_core30 = require("@ordewell/core");
|
|
4961
5144
|
|
|
4962
5145
|
// src/tui/reducers/shared.ts
|
|
4963
5146
|
function step(state, effects = []) {
|
|
@@ -5006,10 +5189,10 @@ function handleApprovalKey(state, overlay, key) {
|
|
|
5006
5189
|
}
|
|
5007
5190
|
|
|
5008
5191
|
// src/tui/reducers/commands.ts
|
|
5009
|
-
var
|
|
5192
|
+
var import_core28 = require("@ordewell/core");
|
|
5010
5193
|
|
|
5011
5194
|
// src/tui/reducers/pickers.ts
|
|
5012
|
-
var
|
|
5195
|
+
var import_core25 = require("@ordewell/core");
|
|
5013
5196
|
var import_plan_utils2 = require("@ordewell/core/plan-utils");
|
|
5014
5197
|
var DEFAULT_EFFORT = "__runner_default__";
|
|
5015
5198
|
function picker(title, items, action, extra = {}) {
|
|
@@ -5028,11 +5211,11 @@ function modelDetail(m) {
|
|
|
5028
5211
|
function providerErrorHint(state) {
|
|
5029
5212
|
const failed = Object.keys(state.providerErrors ?? {});
|
|
5030
5213
|
if (failed.length === 0) return void 0;
|
|
5031
|
-
const names = failed.map((p) =>
|
|
5214
|
+
const names = failed.map((p) => import_core25.ALL_PROVIDERS[p]?.label ?? p);
|
|
5032
5215
|
return `\u26A0 Unavailable (key or fetch failed): ${names.join(", ")} \u2014 showing working providers only.`;
|
|
5033
5216
|
}
|
|
5034
5217
|
function plannerModelItems(state) {
|
|
5035
|
-
const runner = (0,
|
|
5218
|
+
const runner = (0, import_core25.runnerForProvider)(state.plannerProvider);
|
|
5036
5219
|
if (!runner) return state.orchestratorModels.map((m) => ({ id: m.id, label: m.label, detail: modelDetail(m) }));
|
|
5037
5220
|
const items = state.models.filter((m) => m.runners?.includes(runner)).map((m) => ({
|
|
5038
5221
|
id: m.id,
|
|
@@ -5046,7 +5229,7 @@ function plannerModelItems(state) {
|
|
|
5046
5229
|
return items.length > 0 ? items : [{ id: "", label: `No ${runner} models discovered yet`, detail: "run /refresh", disabled: true }];
|
|
5047
5230
|
}
|
|
5048
5231
|
function plannerEffortItems(state) {
|
|
5049
|
-
const runner = (0,
|
|
5232
|
+
const runner = (0, import_core25.runnerForProvider)(state.plannerProvider);
|
|
5050
5233
|
if (!runner) {
|
|
5051
5234
|
return [{ id: "", label: "Thinking effort applies to a coding-agent planner", detail: "switch with /planner", disabled: true }];
|
|
5052
5235
|
}
|
|
@@ -5155,21 +5338,21 @@ function pickerItemsFor(state, action) {
|
|
|
5155
5338
|
}
|
|
5156
5339
|
function plannerItems(state) {
|
|
5157
5340
|
const installed = new Set(state.runners.map((r) => r.id));
|
|
5158
|
-
const agents =
|
|
5159
|
-
const runner = (0,
|
|
5341
|
+
const agents = import_core25.CLI_PROVIDERS.map((id) => {
|
|
5342
|
+
const runner = (0, import_core25.runnerForProvider)(id);
|
|
5160
5343
|
const ready = installed.has(runner);
|
|
5161
5344
|
return {
|
|
5162
5345
|
id,
|
|
5163
|
-
label:
|
|
5346
|
+
label: import_core25.ALL_PROVIDERS[id].label,
|
|
5164
5347
|
detail: ready ? "coding agent \xB7 no API key needed" : "CLI not installed or not on PATH",
|
|
5165
5348
|
selected: state.plannerProvider === id,
|
|
5166
5349
|
disabled: !ready
|
|
5167
5350
|
};
|
|
5168
5351
|
});
|
|
5169
|
-
const vendors =
|
|
5352
|
+
const vendors = import_core25.PROVIDER_PRIORITY.filter((id) => state.configuredProviders.includes(id)).map((id) => ({
|
|
5170
5353
|
id,
|
|
5171
|
-
label:
|
|
5172
|
-
detail:
|
|
5354
|
+
label: import_core25.ALL_PROVIDERS[id].label,
|
|
5355
|
+
detail: import_core25.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
5173
5356
|
selected: state.plannerProvider === id
|
|
5174
5357
|
}));
|
|
5175
5358
|
return vendors.length > 0 ? [...agents, ...vendors] : [...agents, { id: "", label: "No API providers configured", detail: "add a key with /key", disabled: true }];
|
|
@@ -5250,6 +5433,139 @@ function handleMouseSelect(state, key) {
|
|
|
5250
5433
|
return step({ ...state, selection: null }, [{ type: "copyText", text: selectedText({ ...state, selection: moved }) }]);
|
|
5251
5434
|
}
|
|
5252
5435
|
|
|
5436
|
+
// src/tui/reducers/taskView.ts
|
|
5437
|
+
var import_core26 = require("@ordewell/core");
|
|
5438
|
+
function openTaskView(state, sessionId, taskId) {
|
|
5439
|
+
const task = findTask(state.tasks, taskId);
|
|
5440
|
+
if (!task) return step(say(state, "system", `No task ${taskId} in this plan.`));
|
|
5441
|
+
const taskView = {
|
|
5442
|
+
taskId,
|
|
5443
|
+
view: import_core26.EMPTY_TASK_LOG,
|
|
5444
|
+
attempts: [],
|
|
5445
|
+
attempt: 0,
|
|
5446
|
+
pending: [],
|
|
5447
|
+
loaded: false,
|
|
5448
|
+
followLatest: true,
|
|
5449
|
+
queuedIndex: 0
|
|
5450
|
+
};
|
|
5451
|
+
return step({ ...state, taskView, focus: "chat", scroll: 0 }, [{ type: "openTaskLog", sessionId, taskId }]);
|
|
5452
|
+
}
|
|
5453
|
+
function openTaskTerminalOrView(state, sessionId, taskId) {
|
|
5454
|
+
const task = findTask(state.tasks, taskId);
|
|
5455
|
+
if (task?.transport?.kind === "structured") return openTaskView(state, sessionId, taskId);
|
|
5456
|
+
return step(state, [{ type: "openTaskTerminal", sessionId, taskId }]);
|
|
5457
|
+
}
|
|
5458
|
+
function continueTaskStep(state, sessionId, taskId, text) {
|
|
5459
|
+
const opened = state.taskView?.taskId === taskId ? step({ ...state, taskView: { ...state.taskView, followLatest: true } }) : openTaskView(state, sessionId, taskId);
|
|
5460
|
+
const watch = state.status !== "executing";
|
|
5461
|
+
return step(opened.state, [...opened.effects, { type: "continueTask", sessionId, taskId, text, ...watch ? { watch: true } : {} }]);
|
|
5462
|
+
}
|
|
5463
|
+
function clampQueueIndex(tv) {
|
|
5464
|
+
const max = Math.max(0, tv.view.queued.length - 1);
|
|
5465
|
+
return tv.queuedIndex <= max ? tv : { ...tv, queuedIndex: max };
|
|
5466
|
+
}
|
|
5467
|
+
function applyLive(tv, attempt, events) {
|
|
5468
|
+
if (attempt <= 0) return tv;
|
|
5469
|
+
let next = tv;
|
|
5470
|
+
if (attempt > next.attempt) {
|
|
5471
|
+
if (!next.followLatest) {
|
|
5472
|
+
return next.attempts.includes(attempt) ? next : { ...next, attempts: [...next.attempts, attempt] };
|
|
5473
|
+
}
|
|
5474
|
+
next = {
|
|
5475
|
+
...next,
|
|
5476
|
+
view: import_core26.EMPTY_TASK_LOG,
|
|
5477
|
+
attempt,
|
|
5478
|
+
attempts: next.attempts.includes(attempt) ? next.attempts : [...next.attempts, attempt]
|
|
5479
|
+
};
|
|
5480
|
+
}
|
|
5481
|
+
if (attempt < next.attempt) return next;
|
|
5482
|
+
return clampQueueIndex({ ...next, view: events.reduce(import_core26.reduceTaskLog, next.view) });
|
|
5483
|
+
}
|
|
5484
|
+
function taskLogAbandoned(state) {
|
|
5485
|
+
const tv = state.taskView;
|
|
5486
|
+
if (!tv || tv.loaded) return state;
|
|
5487
|
+
let next = { ...tv, pending: [], loaded: true };
|
|
5488
|
+
for (const batch of tv.pending) next = applyLive(next, batch.attempt, batch.events);
|
|
5489
|
+
return { ...state, taskView: clampQueueIndex(next) };
|
|
5490
|
+
}
|
|
5491
|
+
function taskLogArrived(state, action) {
|
|
5492
|
+
const tv = state.taskView;
|
|
5493
|
+
if (!tv || tv.taskId !== action.taskId) return state;
|
|
5494
|
+
if (!tv.loaded) {
|
|
5495
|
+
return { ...state, taskView: { ...tv, pending: [...tv.pending, { attempt: action.attempt, events: action.events }] } };
|
|
5496
|
+
}
|
|
5497
|
+
return { ...state, taskView: applyLive(tv, action.attempt, action.events) };
|
|
5498
|
+
}
|
|
5499
|
+
function announceApprovals(state, action) {
|
|
5500
|
+
if (state.taskView?.taskId === action.taskId) return state;
|
|
5501
|
+
const task = findTask(state.tasks, action.taskId);
|
|
5502
|
+
const spoken = action.events.reduce((s, event) => event.type === "approval_requested" ? say(s, "system", `\xB7 Task ${task?.order ?? "?"} waits for approval: ${(0, import_core26.runnerToolSubject)(event.tool, event.args)} \u2014 t on it to answer`) : s, state);
|
|
5503
|
+
return spoken !== state ? { ...spoken, scroll: state.scroll } : spoken;
|
|
5504
|
+
}
|
|
5505
|
+
function taskLogLoaded(state, action) {
|
|
5506
|
+
const tv = state.taskView;
|
|
5507
|
+
if (!tv || tv.taskId !== action.taskId) return state;
|
|
5508
|
+
const loaded = action.attempt >= 1 ? {
|
|
5509
|
+
...tv,
|
|
5510
|
+
view: (0, import_core26.replayTaskLog)(action.events),
|
|
5511
|
+
attempts: action.attempts ?? tv.attempts,
|
|
5512
|
+
attempt: action.attempt
|
|
5513
|
+
} : { ...tv, attempts: action.attempts ?? tv.attempts };
|
|
5514
|
+
let next = { ...loaded, pending: [], loaded: true };
|
|
5515
|
+
for (const batch of tv.pending) {
|
|
5516
|
+
if (batch.attempt === action.attempt) continue;
|
|
5517
|
+
next = applyLive(next, batch.attempt, batch.events);
|
|
5518
|
+
}
|
|
5519
|
+
return { ...state, taskView: clampQueueIndex(next) };
|
|
5520
|
+
}
|
|
5521
|
+
function answerApproval(state, tv, sessionId, decision) {
|
|
5522
|
+
const approval = waitingApproval(tv);
|
|
5523
|
+
if (!approval?.approvalId) return step(say(state, "system", "Nothing is waiting for approval."));
|
|
5524
|
+
if (decision === "allowForTask" && !approval.allowForTask) {
|
|
5525
|
+
return step(say(state, "system", "The runner offered no grant for the rest of this task \u2014 ctrl-y allows this call."));
|
|
5526
|
+
}
|
|
5527
|
+
const note = decision === "deny" ? state.editor.text.trim() : "";
|
|
5528
|
+
const answer = decision === "deny" ? { decision, ...note ? { note } : {} } : { decision };
|
|
5529
|
+
const next = note ? { ...state, editor: { ...state.editor, text: "", cursor: 0 } } : state;
|
|
5530
|
+
return step(next, [{ type: "answerTaskApproval", sessionId, approvalId: approval.approvalId, answer }]);
|
|
5531
|
+
}
|
|
5532
|
+
function handleTaskViewKey(state, key) {
|
|
5533
|
+
const tv = state.taskView;
|
|
5534
|
+
if (!tv || !state.sessionId) return null;
|
|
5535
|
+
if (key.name === "alt-left" || key.name === "alt-right") {
|
|
5536
|
+
const index = tv.attempts.indexOf(tv.attempt);
|
|
5537
|
+
if (index < 0) return null;
|
|
5538
|
+
const target = key.name === "alt-left" ? index - 1 : index + 1;
|
|
5539
|
+
const attempt = tv.attempts[target];
|
|
5540
|
+
if (attempt === void 0) {
|
|
5541
|
+
return step(say(state, "system", target < 0 ? "This is the first attempt." : "This is the latest attempt."));
|
|
5542
|
+
}
|
|
5543
|
+
return step(
|
|
5544
|
+
{ ...state, taskView: { ...tv, followLatest: false } },
|
|
5545
|
+
[{ type: "loadTaskAttempt", sessionId: state.sessionId, taskId: tv.taskId, attempt }]
|
|
5546
|
+
);
|
|
5547
|
+
}
|
|
5548
|
+
if (key.name === "ctrl-r") {
|
|
5549
|
+
const queued = tv.view.queued[tv.queuedIndex];
|
|
5550
|
+
if (!queued) return step(say(state, "system", "No queued message to remove."));
|
|
5551
|
+
return step(state, [{ type: "removeTaskMessage", sessionId: state.sessionId, taskId: tv.taskId, messageId: queued.id }]);
|
|
5552
|
+
}
|
|
5553
|
+
if (key.name === "ctrl-x") {
|
|
5554
|
+
return step(state, [{ type: "interruptTask", sessionId: state.sessionId, taskId: tv.taskId }]);
|
|
5555
|
+
}
|
|
5556
|
+
if (key.name === "ctrl-y") return answerApproval(state, tv, state.sessionId, "allow");
|
|
5557
|
+
if (key.name === "ctrl-t") return answerApproval(state, tv, state.sessionId, "allowForTask");
|
|
5558
|
+
if (key.name === "ctrl-g") return answerApproval(state, tv, state.sessionId, "deny");
|
|
5559
|
+
if (key.name === "ctrl-n" || key.name === "ctrl-p") {
|
|
5560
|
+
const count = tv.view.queued.length;
|
|
5561
|
+
if (count === 0) return null;
|
|
5562
|
+
const delta = key.name === "ctrl-n" ? 1 : -1;
|
|
5563
|
+
const queuedIndex = Math.max(0, Math.min(count - 1, tv.queuedIndex + delta));
|
|
5564
|
+
return step({ ...state, taskView: { ...tv, queuedIndex } });
|
|
5565
|
+
}
|
|
5566
|
+
return null;
|
|
5567
|
+
}
|
|
5568
|
+
|
|
5253
5569
|
// src/tui/reducers/taskEdits.ts
|
|
5254
5570
|
var import_plan_utils3 = require("@ordewell/core/plan-utils");
|
|
5255
5571
|
function openTaskRunnerPicker(state, task) {
|
|
@@ -5520,9 +5836,7 @@ function handlePlanKey(state, key) {
|
|
|
5520
5836
|
return step(state, [taskActionEffect(state, state.sessionId, task.id, action)]);
|
|
5521
5837
|
}
|
|
5522
5838
|
if (key.char === "d") return confirmRemoveTask(state, task);
|
|
5523
|
-
if (key.char === "t")
|
|
5524
|
-
return step(state, [{ type: "openTaskTerminal", sessionId: state.sessionId, taskId: task.id }]);
|
|
5525
|
-
}
|
|
5839
|
+
if (key.char === "t") return openTaskTerminalOrView(state, state.sessionId, task.id);
|
|
5526
5840
|
if (key.char === "x" && task.isolation?.state === "conflict") {
|
|
5527
5841
|
return step(state, [{ type: "resolveConflict", sessionId: state.sessionId, taskId: task.id }]);
|
|
5528
5842
|
}
|
|
@@ -5555,9 +5869,9 @@ function commitTaskEdit(state, task, editor) {
|
|
|
5555
5869
|
}
|
|
5556
5870
|
|
|
5557
5871
|
// src/tui/reducers/turnQueue.ts
|
|
5558
|
-
var
|
|
5872
|
+
var import_core27 = require("@ordewell/core");
|
|
5559
5873
|
function drainQueue(state, settled) {
|
|
5560
|
-
const next = (0,
|
|
5874
|
+
const next = (0, import_core27.drainNext)(state.queuedPrompts);
|
|
5561
5875
|
const ended = disarmStop({ ...settled, stopRequested: false });
|
|
5562
5876
|
if (!next) return step(ended);
|
|
5563
5877
|
const spoken = say(ended, "user", next.text);
|
|
@@ -5576,14 +5890,14 @@ function stopPlanning(state, sessionId) {
|
|
|
5576
5890
|
return step(queueToEditor(cutTurn({ ...dismissed, stopArmed: false, stopRequested: true })), [{ type: "cancelPlanning", sessionId }]);
|
|
5577
5891
|
}
|
|
5578
5892
|
function queueToEditor(state) {
|
|
5579
|
-
const all = (0,
|
|
5893
|
+
const all = (0, import_core27.unsendAll)(state.queuedPrompts);
|
|
5580
5894
|
if (!all) return state;
|
|
5581
5895
|
return { ...state, queuedPrompts: all.rest, editor: { ...state.editor, text: all.text, cursor: all.text.length } };
|
|
5582
5896
|
}
|
|
5583
5897
|
function unsendLatest(state) {
|
|
5584
|
-
const latest = (0,
|
|
5898
|
+
const latest = (0, import_core27.unsendLatest)(state.queuedPrompts);
|
|
5585
5899
|
if (!latest) return step(state);
|
|
5586
|
-
const text = (0,
|
|
5900
|
+
const text = (0, import_core27.aheadOfDraft)(latest.text, state.editor.text);
|
|
5587
5901
|
return step({
|
|
5588
5902
|
...state,
|
|
5589
5903
|
queuedPrompts: latest.rest,
|
|
@@ -5600,7 +5914,7 @@ function plannerEscape(state) {
|
|
|
5600
5914
|
}
|
|
5601
5915
|
|
|
5602
5916
|
// src/tui/reducers/commands.ts
|
|
5603
|
-
var KNOWN_PROVIDERS2 = Object.keys(
|
|
5917
|
+
var KNOWN_PROVIDERS2 = Object.keys(import_core28.ALL_PROVIDERS);
|
|
5604
5918
|
function runCommand(state, { name, args }) {
|
|
5605
5919
|
if (!findCommand(name)) {
|
|
5606
5920
|
return fail3(state, `Unknown command: /${name} \u2014 type /help to see what's available.`);
|
|
@@ -5641,6 +5955,8 @@ function runCommand(state, { name, args }) {
|
|
|
5641
5955
|
return runners(state, args);
|
|
5642
5956
|
case "auto":
|
|
5643
5957
|
return setAutonomous(state, args[0]);
|
|
5958
|
+
case "transport":
|
|
5959
|
+
return setTransport(state, args[0]);
|
|
5644
5960
|
case "parallel":
|
|
5645
5961
|
return setMaxParallel(state, args[0]);
|
|
5646
5962
|
case "mouse":
|
|
@@ -5678,11 +5994,13 @@ function runCommand(state, { name, args }) {
|
|
|
5678
5994
|
(sessionId, taskId) => step(state, [{ type: "removeTask", sessionId, taskId }])
|
|
5679
5995
|
);
|
|
5680
5996
|
case "terminal":
|
|
5681
|
-
return taskCommand(
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5997
|
+
return taskCommand(state, args[0], (sessionId, taskId) => openTaskTerminalOrView(state, sessionId, taskId));
|
|
5998
|
+
case "continue":
|
|
5999
|
+
return taskCommand(state, args[0], (sessionId, taskId) => {
|
|
6000
|
+
const text = args.slice(1).join(" ").trim();
|
|
6001
|
+
if (!text) return fail3(state, "Usage: /continue <id> <message>");
|
|
6002
|
+
return continueTaskStep(state, sessionId, taskId, text);
|
|
6003
|
+
});
|
|
5686
6004
|
case "task-runner":
|
|
5687
6005
|
return taskRunnerCommand(state, args);
|
|
5688
6006
|
case "task-model":
|
|
@@ -5749,10 +6067,11 @@ function newSession(state) {
|
|
|
5749
6067
|
handoff: null,
|
|
5750
6068
|
sessionId: null,
|
|
5751
6069
|
goal: "",
|
|
6070
|
+
taskView: null,
|
|
5752
6071
|
tasks: [],
|
|
5753
6072
|
planApproved: false,
|
|
5754
|
-
conversation:
|
|
5755
|
-
turnGate:
|
|
6073
|
+
conversation: import_core28.EMPTY_CONVERSATION,
|
|
6074
|
+
turnGate: import_core28.NO_TURN,
|
|
5756
6075
|
selectedTask: 0,
|
|
5757
6076
|
expandedTaskId: null,
|
|
5758
6077
|
taskEditor: null,
|
|
@@ -5763,14 +6082,14 @@ function newSession(state) {
|
|
|
5763
6082
|
// Prompts belong to the session that raised them; the old planner is gone
|
|
5764
6083
|
// and its pending requests deny on their own timeout.
|
|
5765
6084
|
pendingApprovals: [],
|
|
5766
|
-
queuedPrompts:
|
|
6085
|
+
queuedPrompts: import_core28.EMPTY_HOLD,
|
|
5767
6086
|
stopArmed: false,
|
|
5768
6087
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
5769
6088
|
}, closeEffects);
|
|
5770
6089
|
}
|
|
5771
6090
|
function setModel(state, args) {
|
|
5772
6091
|
if (args[0] === "set" && args[1]) {
|
|
5773
|
-
const runner = (0,
|
|
6092
|
+
const runner = (0, import_core28.runnerForProvider)(state.plannerProvider);
|
|
5774
6093
|
const known = runner ? modelsForRunner(state.models, runner) : [];
|
|
5775
6094
|
if (runner && known.length > 0 && !known.some((m) => m.id === args[1])) {
|
|
5776
6095
|
return fail3(state, `${args[1]} was not discovered for ${runner}.`);
|
|
@@ -5843,16 +6162,16 @@ function setKey(state, args) {
|
|
|
5843
6162
|
}
|
|
5844
6163
|
return step(state, [{ type: "setApiKey", provider, key: args.slice(2).join(" ") }]);
|
|
5845
6164
|
}
|
|
5846
|
-
const items =
|
|
6165
|
+
const items = import_core28.PROVIDER_PRIORITY.map((id) => ({
|
|
5847
6166
|
id,
|
|
5848
|
-
label:
|
|
5849
|
-
detail:
|
|
6167
|
+
label: import_core28.ALL_PROVIDERS[id].label,
|
|
6168
|
+
detail: import_core28.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
5850
6169
|
selected: state.configuredProviders.includes(id)
|
|
5851
6170
|
}));
|
|
5852
6171
|
return step({ ...state, overlay: { kind: "picker", picker: picker("API provider key", items, { kind: "set-key" }) } });
|
|
5853
6172
|
}
|
|
5854
6173
|
function keyPrompt(provider) {
|
|
5855
|
-
const meta =
|
|
6174
|
+
const meta = import_core28.ALL_PROVIDERS[provider];
|
|
5856
6175
|
return {
|
|
5857
6176
|
kind: "prompt",
|
|
5858
6177
|
title: `${meta.label} API key`,
|
|
@@ -5929,15 +6248,21 @@ function runners(state, args) {
|
|
|
5929
6248
|
}
|
|
5930
6249
|
function setMaxParallel(state, arg) {
|
|
5931
6250
|
if (!arg) return step(say(state, "system", `Up to ${state.maxParallel} AI task${state.maxParallel === 1 ? "" : "s"} run at once \u2014 /parallel <n> changes it.`));
|
|
5932
|
-
const limit = (0,
|
|
6251
|
+
const limit = (0, import_core28.parseMaxParallel)(arg);
|
|
5933
6252
|
if (limit === null) return fail3(state, "Usage: /parallel [<number of tasks, 1 or more>]");
|
|
5934
6253
|
return step({ ...state, maxParallel: limit }, [{ type: "setMaxParallel", limit }]);
|
|
5935
6254
|
}
|
|
5936
6255
|
function setAutonomous(state, arg) {
|
|
5937
|
-
|
|
5938
|
-
|
|
6256
|
+
if (arg === void 0) return step(say(state, "system", `Autonomy level: ${(0, import_core28.autonomyLevelLabel)(state.autonomous)} \u2014 /auto full or /auto auto changes it for new plans.`));
|
|
6257
|
+
const enabled = (0, import_core28.parseAutonomyLevel)(arg);
|
|
6258
|
+
if (enabled === null) return fail3(state, "Usage: /auto [full|auto]");
|
|
5939
6259
|
return step({ ...state, autonomous: enabled }, [{ type: "setAutonomous", enabled }]);
|
|
5940
6260
|
}
|
|
6261
|
+
function setTransport(state, arg) {
|
|
6262
|
+
const value = arg?.toLowerCase() ?? (state.runnerTransport === "structured" ? "terminal" : "structured");
|
|
6263
|
+
if (!(0, import_core28.isRunnerTransport)(value)) return fail3(state, "Usage: /transport [terminal|structured]");
|
|
6264
|
+
return step(state, [{ type: "setTransport", transport: value }]);
|
|
6265
|
+
}
|
|
5941
6266
|
function setMouseCapture(state, arg) {
|
|
5942
6267
|
const enabled = resolveToggle(arg, state.mouseCapture);
|
|
5943
6268
|
if (enabled === null) return fail3(state, "Usage: /mouse [on|off]");
|
|
@@ -6185,6 +6510,7 @@ function choose(state, picker2, item) {
|
|
|
6185
6510
|
}
|
|
6186
6511
|
|
|
6187
6512
|
// src/tui/reducers/incoming.ts
|
|
6513
|
+
var import_core29 = require("@ordewell/core");
|
|
6188
6514
|
var planText = (value, fallback = "") => sanitize(String(value ?? fallback));
|
|
6189
6515
|
var planLabel = (value, fallback = "") => planText(value, fallback).replace(/\n+/g, " ");
|
|
6190
6516
|
var asRecord = (value) => typeof value === "object" && value !== null ? value : {};
|
|
@@ -6215,6 +6541,7 @@ function toTaskView2(t, index) {
|
|
|
6215
6541
|
prompt: typeof t.prompt === "string" ? sanitize(t.prompt) : void 0,
|
|
6216
6542
|
type: t.type === "user" ? "user" : "ai",
|
|
6217
6543
|
status: planLabel(t.status, "pending"),
|
|
6544
|
+
awaitingReason: t.status === "awaiting_user" && (0, import_core29.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
|
|
6218
6545
|
dependencies: Array.isArray(t.dependencies) ? t.dependencies.map((id) => planLabel(id)) : [],
|
|
6219
6546
|
assignedRunner: typeof t.assignedRunner === "string" ? planLabel(t.assignedRunner) : void 0,
|
|
6220
6547
|
taskMode: typeof t.taskMode === "string" ? planLabel(t.taskMode) : void 0,
|
|
@@ -6245,6 +6572,7 @@ function applySettings(state, settings) {
|
|
|
6245
6572
|
plannerProvider: typeof settings.aiProvider === "string" ? settings.aiProvider : state.plannerProvider,
|
|
6246
6573
|
plannerEffort: typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : state.plannerEffort,
|
|
6247
6574
|
maxParallel: typeof settings.maxParallel === "number" ? settings.maxParallel : state.maxParallel,
|
|
6575
|
+
runnerTransport: (0, import_core29.isRunnerTransport)(settings.runnerTransport) ? settings.runnerTransport : state.runnerTransport,
|
|
6248
6576
|
allowlist: settings.modelAllowlist && typeof settings.modelAllowlist === "object" ? settings.modelAllowlist : state.allowlist
|
|
6249
6577
|
};
|
|
6250
6578
|
}
|
|
@@ -6279,20 +6607,32 @@ function followSession(state, message) {
|
|
|
6279
6607
|
const heard = turnActivity(hear(state, message));
|
|
6280
6608
|
if (message.type === "approval_request") {
|
|
6281
6609
|
const { id, kind, subject, scope, detail } = message;
|
|
6610
|
+
if (kind === "runner_tool") return heard;
|
|
6282
6611
|
return enqueueApproval(heard, { id, kind, subject, scope, ...detail ? { detail } : {} });
|
|
6283
6612
|
}
|
|
6284
6613
|
return message.type === "approval_settled" ? dropApproval(heard, message.id) : heard;
|
|
6285
6614
|
}
|
|
6286
6615
|
|
|
6287
6616
|
// src/tui/reducer.ts
|
|
6617
|
+
function holdReadingPlace(before, after) {
|
|
6618
|
+
if (before.scroll <= 0 || after.scroll !== before.scroll) return after;
|
|
6619
|
+
if (before.taskView?.taskId !== after.taskView?.taskId) return after;
|
|
6620
|
+
const grown = chatScrollMax(after) - chatScrollMax(before);
|
|
6621
|
+
return grown > 0 ? { ...after, scroll: clamp(after.scroll + grown, chatScrollMax(after)) } : after;
|
|
6622
|
+
}
|
|
6288
6623
|
function reduce(state, action) {
|
|
6624
|
+
const result = reduceAction(state, action);
|
|
6625
|
+
if (action.type !== "sessionMessage" && action.type !== "taskLog") return result;
|
|
6626
|
+
return { ...result, state: holdReadingPlace(state, result.state) };
|
|
6627
|
+
}
|
|
6628
|
+
function reduceAction(state, action) {
|
|
6289
6629
|
switch (action.type) {
|
|
6290
6630
|
case "key":
|
|
6291
6631
|
return handleKey2(state, action.key);
|
|
6292
6632
|
// Isolation belongs to one session's plan, and task ids repeat across
|
|
6293
6633
|
// sessions, so nothing of the last one's may carry over into this.
|
|
6294
6634
|
case "sessionStarted":
|
|
6295
|
-
return step(clearIsolation({ ...state, sessionId: action.sessionId, goal: action.goal }));
|
|
6635
|
+
return step(clearIsolation({ ...state, sessionId: action.sessionId, goal: action.goal, taskView: null }));
|
|
6296
6636
|
// The daemon registers a session only once planning succeeds; when it does
|
|
6297
6637
|
// not, holding on to the id would send every next message to a session the
|
|
6298
6638
|
// server never had.
|
|
@@ -6301,14 +6641,15 @@ function reduce(state, action) {
|
|
|
6301
6641
|
...clearIsolation(state),
|
|
6302
6642
|
sessionId: null,
|
|
6303
6643
|
goal: "",
|
|
6644
|
+
taskView: null,
|
|
6304
6645
|
pendingApprovals: [],
|
|
6305
|
-
queuedPrompts:
|
|
6646
|
+
queuedPrompts: import_core30.EMPTY_HOLD,
|
|
6306
6647
|
stopArmed: false,
|
|
6307
6648
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
6308
6649
|
});
|
|
6309
6650
|
case "chatRestored":
|
|
6310
6651
|
if (stale(state, action.sessionId)) return step(state);
|
|
6311
|
-
return step({ ...state, conversation: (0,
|
|
6652
|
+
return step({ ...state, conversation: (0, import_core30.fromTranscript)(action.history, action.researchLog, action.plannerUsage), scroll: 0 });
|
|
6312
6653
|
case "planUpdated": {
|
|
6313
6654
|
if (stale(state, action.sessionId)) return step(state);
|
|
6314
6655
|
const isolation = isolationOfPlan(action.plan);
|
|
@@ -6340,6 +6681,15 @@ function reduce(state, action) {
|
|
|
6340
6681
|
case "sessionMessage":
|
|
6341
6682
|
if (stale(state, action.sessionId)) return step(state);
|
|
6342
6683
|
return step(followSession(state, action.message));
|
|
6684
|
+
case "taskLog":
|
|
6685
|
+
if (stale(state, action.sessionId)) return step(state);
|
|
6686
|
+
return step(announceApprovals(taskLogArrived(state, action), action));
|
|
6687
|
+
case "taskViewRequested":
|
|
6688
|
+
if (stale(state, action.sessionId)) return step(state);
|
|
6689
|
+
return openTaskView(state, action.sessionId, action.taskId);
|
|
6690
|
+
case "taskLogLoaded":
|
|
6691
|
+
if (stale(state, action.sessionId)) return step(state);
|
|
6692
|
+
return step(taskLogLoaded(state, action));
|
|
6343
6693
|
// A settled transcript line, not a research step: nothing ever settles a
|
|
6344
6694
|
// task's start, so as a step it stayed "⋯" forever and was counted into
|
|
6345
6695
|
// the planner's next "(+N more)".
|
|
@@ -6348,12 +6698,12 @@ function reduce(state, action) {
|
|
|
6348
6698
|
const existing = findTask(state.tasks, action.taskId);
|
|
6349
6699
|
if (existing && isTaskRunning(existing)) return step(state);
|
|
6350
6700
|
const tasks = state.tasks.map((t) => t.id === action.taskId && !isTaskRunning(t) ? { ...t, status: "in_progress" } : t);
|
|
6351
|
-
const spoken = say(state, "system", (0,
|
|
6701
|
+
const spoken = say(state, "system", (0, import_core30.taskStartedNotice)(action.title, action.runner));
|
|
6352
6702
|
return step({ ...spoken, tasks, status: "executing", busyLabel: runLabel(tasks) });
|
|
6353
6703
|
}
|
|
6354
6704
|
case "taskStatus": {
|
|
6355
6705
|
if (stale(state, action.sessionId) || !state.tasks.some((t) => t.id === action.taskId)) return step(state);
|
|
6356
|
-
const tasks = state.tasks.map((t) => t.id === action.taskId ? { ...t, status: action.status } : t);
|
|
6706
|
+
const tasks = state.tasks.map((t) => t.id === action.taskId ? { ...t, status: action.status, awaitingReason: void 0 } : t);
|
|
6357
6707
|
const status = runStatus(state, tasks);
|
|
6358
6708
|
return step({ ...state, status, tasks, busyLabel: status === "executing" ? runLabel(tasks) : state.busyLabel });
|
|
6359
6709
|
}
|
|
@@ -6366,9 +6716,13 @@ function reduce(state, action) {
|
|
|
6366
6716
|
if (update === void 0) return t;
|
|
6367
6717
|
const idleSince = update.idleSince ?? null;
|
|
6368
6718
|
const isolation = update.isolation ?? t.isolation;
|
|
6369
|
-
|
|
6719
|
+
const transport = update.transport;
|
|
6720
|
+
const awaitingReason = update.awaitingReason;
|
|
6721
|
+
const continuable = update.continuable === true;
|
|
6722
|
+
const awaitingApproval = update.awaitingApproval;
|
|
6723
|
+
if (update.status !== t.status || idleSince !== (t.idleSince ?? null) || !sameIsolation(isolation, t.isolation) || !sameTransport(transport, t.transport) || awaitingReason !== t.awaitingReason || continuable !== (t.continuable ?? false) || awaitingApproval !== t.awaitingApproval) {
|
|
6370
6724
|
changed = true;
|
|
6371
|
-
return { ...t, status: update.status, idleSince, isolation };
|
|
6725
|
+
return { ...t, status: update.status, idleSince, isolation, transport, awaitingReason, continuable, awaitingApproval };
|
|
6372
6726
|
}
|
|
6373
6727
|
return t;
|
|
6374
6728
|
});
|
|
@@ -6443,11 +6797,12 @@ function reduce(state, action) {
|
|
|
6443
6797
|
...clearIsolation(state),
|
|
6444
6798
|
sessionId: action.sessionId,
|
|
6445
6799
|
goal: action.goal,
|
|
6800
|
+
taskView: null,
|
|
6446
6801
|
status: "idle",
|
|
6447
6802
|
busyLabel: "",
|
|
6448
6803
|
planApproved: false,
|
|
6449
6804
|
pendingApprovals: [],
|
|
6450
|
-
queuedPrompts:
|
|
6805
|
+
queuedPrompts: import_core30.EMPTY_HOLD,
|
|
6451
6806
|
stopArmed: false,
|
|
6452
6807
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
6453
6808
|
});
|
|
@@ -6467,6 +6822,7 @@ function reduce(state, action) {
|
|
|
6467
6822
|
orchestratorModel: action.orchestratorModel ?? state.orchestratorModel
|
|
6468
6823
|
}, ["set-planner", "set-task-runner"]));
|
|
6469
6824
|
case "failed": {
|
|
6825
|
+
state = taskLogAbandoned(state);
|
|
6470
6826
|
const quiet = plannerInFlight(state) && state.stopRequested;
|
|
6471
6827
|
const reported = { ...quiet ? state : say(state, "error", action.message), status: "idle", busyLabel: "" };
|
|
6472
6828
|
if (!plannerInFlight(state)) return step(reported);
|
|
@@ -6502,6 +6858,9 @@ function reduce(state, action) {
|
|
|
6502
6858
|
return step(disarmStop(state));
|
|
6503
6859
|
}
|
|
6504
6860
|
}
|
|
6861
|
+
function sameTransport(a, b) {
|
|
6862
|
+
return a?.kind === b?.kind && a?.fallback === b?.fallback;
|
|
6863
|
+
}
|
|
6505
6864
|
function runStatus(state, tasks) {
|
|
6506
6865
|
if (tasks.some(isTaskRunning)) return "executing";
|
|
6507
6866
|
if (tasks.some((t) => t.status === "awaiting_user")) return "executing";
|
|
@@ -6520,6 +6879,10 @@ function handleKey2(state, key) {
|
|
|
6520
6879
|
}
|
|
6521
6880
|
if (key.name === "ctrl-l") return step({ ...state, conversation: wiped(state.conversation), scroll: 0 });
|
|
6522
6881
|
if (key.name === "ctrl-o" && state.focus === "chat" && !state.overlay) return toggleDetail(state);
|
|
6882
|
+
if (key.name === "escape" && state.taskView && !state.overlay) {
|
|
6883
|
+
if (state.editor.text) return step({ ...state, editor: { ...state.editor, text: "", cursor: 0 } });
|
|
6884
|
+
return step({ ...state, taskView: null, scroll: 0 });
|
|
6885
|
+
}
|
|
6523
6886
|
if (key.name === "escape") {
|
|
6524
6887
|
const stopped = plannerEscape(state);
|
|
6525
6888
|
if (stopped) return stopped;
|
|
@@ -6543,12 +6906,16 @@ function handleKey2(state, key) {
|
|
|
6543
6906
|
return step({ ...state, focus: state.focus === "chat" ? "plan" : "chat" });
|
|
6544
6907
|
}
|
|
6545
6908
|
if (state.focus === "plan") return handlePlanKey(state, key);
|
|
6909
|
+
if (state.taskView) {
|
|
6910
|
+
const handled = handleTaskViewKey(state, key);
|
|
6911
|
+
if (handled) return handled;
|
|
6912
|
+
}
|
|
6546
6913
|
if (key.name === "enter") return submit(state);
|
|
6547
6914
|
if (key.name === "escape") return step({ ...state, editor: { ...state.editor, text: "", cursor: 0 } });
|
|
6548
6915
|
const scroll = scrollDelta(key, state);
|
|
6549
6916
|
if (scroll !== null) return scrollChat(state, scroll);
|
|
6550
6917
|
const multilineEditor = state.editor.text.includes("\n");
|
|
6551
|
-
const room =
|
|
6918
|
+
const room = chatEditorRoomFor(state, true);
|
|
6552
6919
|
return step({ ...state, editor: applyKey(state.editor, key, multilineEditor ? room : void 0) });
|
|
6553
6920
|
}
|
|
6554
6921
|
function submit(state) {
|
|
@@ -6557,8 +6924,14 @@ function submit(state) {
|
|
|
6557
6924
|
const cleared = { ...state, editor: commit(state.editor) };
|
|
6558
6925
|
const command = parseSlash(text);
|
|
6559
6926
|
if (command && findCommand(command.name)?.source !== "skill") return runCommand(cleared, command);
|
|
6927
|
+
if (state.taskView) {
|
|
6928
|
+
if (!state.sessionId) return step(state);
|
|
6929
|
+
const taskId = state.taskView.taskId;
|
|
6930
|
+
if (continuesTask(findTask(state.tasks, taskId))) return continueTaskStep(cleared, state.sessionId, taskId, text);
|
|
6931
|
+
return step(cleared, [{ type: "sendTaskMessage", sessionId: state.sessionId, taskId, text }]);
|
|
6932
|
+
}
|
|
6560
6933
|
if (plannerInFlight(state)) {
|
|
6561
|
-
return step({ ...cleared, queuedPrompts: (0,
|
|
6934
|
+
return step({ ...cleared, queuedPrompts: (0, import_core30.holdPrompt)(state.queuedPrompts, text) });
|
|
6562
6935
|
}
|
|
6563
6936
|
const spoken = say(cleared, "user", text);
|
|
6564
6937
|
const effect = state.sessionId ? { type: "sendMessage", sessionId: state.sessionId, message: text } : { type: "startConversation", goal: text };
|
|
@@ -6618,10 +6991,10 @@ function createApp(deps) {
|
|
|
6618
6991
|
|
|
6619
6992
|
// src/tui/effects.ts
|
|
6620
6993
|
var import_child_process4 = require("child_process");
|
|
6621
|
-
var
|
|
6994
|
+
var import_core32 = require("@ordewell/core");
|
|
6622
6995
|
|
|
6623
6996
|
// src/tui/inbound.ts
|
|
6624
|
-
var
|
|
6997
|
+
var import_core31 = require("@ordewell/core");
|
|
6625
6998
|
var CONVERSATION_TYPES = /* @__PURE__ */ new Set([
|
|
6626
6999
|
"planner_message",
|
|
6627
7000
|
"planner_turn_started",
|
|
@@ -6831,7 +7204,15 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
6831
7204
|
case "status_update": {
|
|
6832
7205
|
const updates = {};
|
|
6833
7206
|
for (const task of event.tasks ?? []) {
|
|
6834
|
-
updates[String(task.id)] = {
|
|
7207
|
+
updates[String(task.id)] = {
|
|
7208
|
+
status: String(task.status),
|
|
7209
|
+
idleSince: task.idleSince ?? null,
|
|
7210
|
+
isolation: task.isolation,
|
|
7211
|
+
transport: task.transport,
|
|
7212
|
+
awaitingReason: (0, import_core31.isAwaitingReason)(task.awaitingReason) ? task.awaitingReason : void 0,
|
|
7213
|
+
...task.continuable ? { continuable: true } : {},
|
|
7214
|
+
awaitingApproval: typeof task.awaitingApproval === "number" && task.awaitingApproval > 0 ? task.awaitingApproval : void 0
|
|
7215
|
+
};
|
|
6835
7216
|
}
|
|
6836
7217
|
dispatch({ type: "tasksStatus", updates, sessionId });
|
|
6837
7218
|
return;
|
|
@@ -6842,7 +7223,7 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
6842
7223
|
// The extension shows these in its checkpoint panel; here they are
|
|
6843
7224
|
// transcript lines, which is the TUI's equivalent surface.
|
|
6844
7225
|
case "checkpoint":
|
|
6845
|
-
dispatch({ type: "notice", message: `\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0,
|
|
7226
|
+
dispatch({ type: "notice", message: `\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0, import_core31.truncateCheckpointSummary)(event.summary)}` });
|
|
6846
7227
|
return;
|
|
6847
7228
|
case "review_needed":
|
|
6848
7229
|
dispatch({ type: "notice", message: "Plan needs your sign-off \u2014 /approve to continue." });
|
|
@@ -6875,6 +7256,12 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
6875
7256
|
case "isolation_handoff":
|
|
6876
7257
|
dispatch({ type: "isolationHandoff", handoff: { repos: event.repos, landed: event.landed }, sessionId });
|
|
6877
7258
|
return;
|
|
7259
|
+
// A structured task's log as it happens (ADR-0018, P1): the task view is
|
|
7260
|
+
// its only reader, and the reducer drops it unless that view is open on
|
|
7261
|
+
// this task. The run's watcher itself does nothing with it.
|
|
7262
|
+
case "task_log":
|
|
7263
|
+
dispatch({ type: "taskLog", taskId: String(event.taskId), attempt: event.attempt, events: event.events, sessionId });
|
|
7264
|
+
return;
|
|
6878
7265
|
// Raw runner chatter. Listed rather than defaulted, so a new lifecycle
|
|
6879
7266
|
// variant fails to compile here until someone decides what a run's watcher
|
|
6880
7267
|
// does with it.
|
|
@@ -7025,17 +7412,80 @@ async function perform(effect, deps) {
|
|
|
7025
7412
|
}
|
|
7026
7413
|
return;
|
|
7027
7414
|
}
|
|
7415
|
+
// The card settles when the task log reports the answer, not here.
|
|
7416
|
+
case "answerTaskApproval": {
|
|
7417
|
+
try {
|
|
7418
|
+
await api.respondToApproval(effect.sessionId, effect.approvalId, effect.answer);
|
|
7419
|
+
} catch {
|
|
7420
|
+
dispatch({ type: "failed", message: "Could not deliver the answer \u2014 the task is still waiting for approval." });
|
|
7421
|
+
}
|
|
7422
|
+
return;
|
|
7423
|
+
}
|
|
7424
|
+
// A task with a saved log ran structured, whatever its `transport` says
|
|
7425
|
+
// after a reload; it has no tmux window, so open the log rather than fail.
|
|
7028
7426
|
case "openTaskTerminal": {
|
|
7427
|
+
const saved = await api.getTaskLogAttempts(effect.sessionId, effect.taskId, workspace).catch(() => []);
|
|
7428
|
+
if (saved.length > 0) {
|
|
7429
|
+
dispatch({ type: "taskViewRequested", sessionId: effect.sessionId, taskId: effect.taskId });
|
|
7430
|
+
return;
|
|
7431
|
+
}
|
|
7029
7432
|
const result = await deps.openTerminal(effect.sessionId, effect.taskId);
|
|
7030
7433
|
dispatch(result.ok ? { type: "notice", message: result.message } : { type: "failed", message: result.message });
|
|
7031
7434
|
return;
|
|
7032
7435
|
}
|
|
7436
|
+
// The saved log is read off disk (ADR-0018, P1) so the view opens with the
|
|
7437
|
+
// task's history; the live `task_log` stream then catches it up in place.
|
|
7438
|
+
case "openTaskLog": {
|
|
7439
|
+
const attempts = await api.getTaskLogAttempts(effect.sessionId, effect.taskId, workspace);
|
|
7440
|
+
if (attempts.length === 0) {
|
|
7441
|
+
dispatch({ type: "taskLogLoaded", taskId: effect.taskId, attempts: [], attempt: 0, events: [], sessionId: effect.sessionId });
|
|
7442
|
+
return;
|
|
7443
|
+
}
|
|
7444
|
+
const attempt = attempts[attempts.length - 1];
|
|
7445
|
+
const events = await api.getTaskLog(effect.sessionId, effect.taskId, attempt, workspace);
|
|
7446
|
+
dispatch({ type: "taskLogLoaded", taskId: effect.taskId, attempts, attempt, events, sessionId: effect.sessionId });
|
|
7447
|
+
return;
|
|
7448
|
+
}
|
|
7449
|
+
case "loadTaskAttempt": {
|
|
7450
|
+
const events = await api.getTaskLog(effect.sessionId, effect.taskId, effect.attempt, workspace);
|
|
7451
|
+
dispatch({ type: "taskLogLoaded", taskId: effect.taskId, attempt: effect.attempt, events, sessionId: effect.sessionId });
|
|
7452
|
+
return;
|
|
7453
|
+
}
|
|
7454
|
+
// The daemon queues the message (or delivers it to a waiting task) and
|
|
7455
|
+
// broadcasts the `task_log` event that shows it; nothing to dispatch here.
|
|
7456
|
+
case "sendTaskMessage":
|
|
7457
|
+
await api.sendTaskMessage(effect.sessionId, effect.taskId, effect.text);
|
|
7458
|
+
return;
|
|
7459
|
+
// The daemon starts the new attempt; its `task_log` and status arrive over
|
|
7460
|
+
// the execution stream, which a TUI not already watching a run holds open.
|
|
7461
|
+
case "continueTask": {
|
|
7462
|
+
const { sessionId, taskId, text } = effect;
|
|
7463
|
+
const request = () => api.continueTask(sessionId, taskId, text);
|
|
7464
|
+
await (effect.watch ? withExecutionStream(deps, sessionId, request) : request());
|
|
7465
|
+
await refreshPlan(deps, sessionId);
|
|
7466
|
+
return;
|
|
7467
|
+
}
|
|
7468
|
+
case "removeTaskMessage": {
|
|
7469
|
+
const { removed } = await api.removeQueuedTaskMessage(effect.sessionId, effect.taskId, effect.messageId);
|
|
7470
|
+
if (!removed) dispatch({ type: "notice", message: "That message was already delivered to the task." });
|
|
7471
|
+
return;
|
|
7472
|
+
}
|
|
7473
|
+
case "interruptTask":
|
|
7474
|
+
await api.interruptTask(effect.sessionId, effect.taskId);
|
|
7475
|
+
dispatch({ type: "notice", message: "Interrupting the task\u2026" });
|
|
7476
|
+
return;
|
|
7033
7477
|
case "command": {
|
|
7034
7478
|
const result = await api.sendCommand(effect.name, { action: effect.action });
|
|
7035
7479
|
if (result.settings) dispatch({ type: "settingsLoaded", settings: result.settings });
|
|
7036
7480
|
dispatch({ type: "notice", message: `${effect.name} is ${effect.action}.` });
|
|
7037
7481
|
return;
|
|
7038
7482
|
}
|
|
7483
|
+
case "setTransport": {
|
|
7484
|
+
const result = await api.sendCommand("transport", { action: effect.transport });
|
|
7485
|
+
if (result.settings) dispatch({ type: "settingsLoaded", settings: result.settings });
|
|
7486
|
+
dispatch({ type: "notice", message: `Runner transport is ${effect.transport} \u2014 it applies from the next run.` });
|
|
7487
|
+
return;
|
|
7488
|
+
}
|
|
7039
7489
|
case "setModel":
|
|
7040
7490
|
await api.updateSettings({ orchestratorModel: effect.modelId });
|
|
7041
7491
|
deps.setEnvVar("ORCHESTRATOR_MODEL", effect.modelId);
|
|
@@ -7043,7 +7493,7 @@ async function perform(effect, deps) {
|
|
|
7043
7493
|
dispatch({ type: "notice", message: `Orchestrator model set to ${effect.modelId}.` });
|
|
7044
7494
|
return;
|
|
7045
7495
|
case "setPlanner": {
|
|
7046
|
-
const meta =
|
|
7496
|
+
const meta = import_core32.ALL_PROVIDERS[effect.provider];
|
|
7047
7497
|
if (!meta) throw new Error(`Unknown planner: ${effect.provider}`);
|
|
7048
7498
|
const env = { AI_PROVIDER: effect.provider };
|
|
7049
7499
|
const settings = await api.updateSettings({ env });
|
|
@@ -7055,7 +7505,7 @@ async function perform(effect, deps) {
|
|
|
7055
7505
|
});
|
|
7056
7506
|
dispatch({
|
|
7057
7507
|
type: "notice",
|
|
7058
|
-
message: `${(0,
|
|
7508
|
+
message: `${(0, import_core32.isCliProvider)(effect.provider) ? `Planning with ${meta.label} \u2014 no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
|
|
7059
7509
|
});
|
|
7060
7510
|
await loadModels(deps);
|
|
7061
7511
|
return;
|
|
@@ -7067,7 +7517,7 @@ async function perform(effect, deps) {
|
|
|
7067
7517
|
dispatch({ type: "notice", message: `Planner effort set to ${effect.effort || "the runner default"}.` });
|
|
7068
7518
|
return;
|
|
7069
7519
|
case "setApiKey": {
|
|
7070
|
-
const meta =
|
|
7520
|
+
const meta = import_core32.ALL_PROVIDERS[effect.provider];
|
|
7071
7521
|
if (!meta) throw new Error(`Unknown provider: ${effect.provider}`);
|
|
7072
7522
|
await api.updateSettings({ env: { [meta.apiKeyEnvVar]: effect.key } });
|
|
7073
7523
|
deps.setEnvVar(meta.apiKeyEnvVar, effect.key);
|
|
@@ -7105,7 +7555,7 @@ async function perform(effect, deps) {
|
|
|
7105
7555
|
return;
|
|
7106
7556
|
case "setAutonomous":
|
|
7107
7557
|
deps.setEnvVar("ORDEWELL_AUTONOMOUS_MODE", String(effect.enabled));
|
|
7108
|
-
dispatch({ type: "notice", message: `
|
|
7558
|
+
dispatch({ type: "notice", message: `Autonomy level: ${(0, import_core32.autonomyLevelLabel)(effect.enabled)} for new plans.` });
|
|
7109
7559
|
return;
|
|
7110
7560
|
case "setMouseCapture":
|
|
7111
7561
|
deps.setMouseCapture(effect.enabled);
|
|
@@ -7238,7 +7688,7 @@ function osc52(text) {
|
|
|
7238
7688
|
function copySelection(deps, text) {
|
|
7239
7689
|
if (!text) return;
|
|
7240
7690
|
const lines = text.split("\n").length;
|
|
7241
|
-
const command = (0,
|
|
7691
|
+
const command = (0, import_core32.clipboardCopyCommand)(deps.hasBin);
|
|
7242
7692
|
if (command) {
|
|
7243
7693
|
try {
|
|
7244
7694
|
(deps.pipeToClipboard ?? pipeToClipboardDefault)(command, text);
|
|
@@ -7348,13 +7798,18 @@ var CONTROL2 = {
|
|
|
7348
7798
|
"": "ctrl-c",
|
|
7349
7799
|
"": "ctrl-d",
|
|
7350
7800
|
"": "ctrl-e",
|
|
7801
|
+
"\x07": "ctrl-g",
|
|
7351
7802
|
"\v": "ctrl-k",
|
|
7352
7803
|
"\f": "ctrl-l",
|
|
7353
7804
|
"": "ctrl-n",
|
|
7354
7805
|
"": "ctrl-o",
|
|
7355
7806
|
"": "ctrl-p",
|
|
7807
|
+
"": "ctrl-r",
|
|
7808
|
+
"": "ctrl-t",
|
|
7356
7809
|
"": "ctrl-u",
|
|
7357
|
-
"": "ctrl-w"
|
|
7810
|
+
"": "ctrl-w",
|
|
7811
|
+
"": "ctrl-x",
|
|
7812
|
+
"": "ctrl-y"
|
|
7358
7813
|
};
|
|
7359
7814
|
var ESCAPES = {
|
|
7360
7815
|
"\x1B[A": "up",
|
|
@@ -7623,7 +8078,7 @@ async function handleTui(subArgs) {
|
|
|
7623
8078
|
}
|
|
7624
8079
|
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
7625
8080
|
try {
|
|
7626
|
-
(0,
|
|
8081
|
+
(0, import_core33.assertWorkspaceExists)(workspace);
|
|
7627
8082
|
} catch {
|
|
7628
8083
|
console.error(
|
|
7629
8084
|
`Workspace "${workspace}" does not exist.
|
|
@@ -7632,7 +8087,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
7632
8087
|
);
|
|
7633
8088
|
process.exit(1);
|
|
7634
8089
|
}
|
|
7635
|
-
registerSkillCommands((0,
|
|
8090
|
+
registerSkillCommands((0, import_core33.createSkillsService)(workspace).listSkills());
|
|
7636
8091
|
const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
|
|
7637
8092
|
const targetPort = explicitPort ? resolvePort(subArgs) : await findFreePort();
|
|
7638
8093
|
const { port, owned: ownedAtLaunch } = await ensureDaemonOwned(targetPort, { detached: false });
|
|
@@ -7649,7 +8104,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
7649
8104
|
workspace,
|
|
7650
8105
|
port,
|
|
7651
8106
|
dispatch: (action) => app.dispatch(action),
|
|
7652
|
-
newSessionId: () => (0,
|
|
8107
|
+
newSessionId: () => (0, import_core33.mintSessionId)(),
|
|
7653
8108
|
setEnvVar: (key, value) => {
|
|
7654
8109
|
writeEnvVar(findEnvFile(), key, value);
|
|
7655
8110
|
process.env[key] = value;
|
|
@@ -7725,6 +8180,7 @@ var COMMANDS = {
|
|
|
7725
8180
|
key: handleKey,
|
|
7726
8181
|
runners: handleRunners,
|
|
7727
8182
|
auto: handleAuto,
|
|
8183
|
+
transport: handleTransport,
|
|
7728
8184
|
refresh: handleRefresh,
|
|
7729
8185
|
setup: handleSetup,
|
|
7730
8186
|
plugins: handlePlugins,
|
|
@@ -7738,6 +8194,7 @@ var COMMANDS = {
|
|
|
7738
8194
|
"force-start": handleForceStart,
|
|
7739
8195
|
"retry": handleRetry,
|
|
7740
8196
|
"cancel": handleCancel,
|
|
8197
|
+
continue: handleContinue,
|
|
7741
8198
|
"add-task": handleAddTask,
|
|
7742
8199
|
"remove-task": handleRemoveTask,
|
|
7743
8200
|
"task-runner": handleTaskRunner,
|
|
@@ -7770,7 +8227,7 @@ function cliVersion() {
|
|
|
7770
8227
|
// src/main.ts
|
|
7771
8228
|
var TUI_FLAGS = /* @__PURE__ */ new Set(["--workspace", "--port"]);
|
|
7772
8229
|
async function main() {
|
|
7773
|
-
(0,
|
|
8230
|
+
(0, import_core34.migrateOldConfigDir)();
|
|
7774
8231
|
loadEnvFile();
|
|
7775
8232
|
const argv = process.argv.slice(2);
|
|
7776
8233
|
if (TUI_FLAGS.has(argv[0] ?? "")) argv.unshift("tui");
|