@ordewell/cli 0.5.6 → 0.6.1
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 +333 -155
- package/package.json +3 -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");
|
|
@@ -109,7 +109,7 @@ Planning:
|
|
|
109
109
|
ordewell fork Continue in a copy of the conversation and its tasks
|
|
110
110
|
ordewell compact Condense the conversation into a summary (tasks are kept)
|
|
111
111
|
ordewell rewind [<n>] Fork the conversation from before message <n> (no <n>: list them)
|
|
112
|
-
ordewell handoff [action] Review, merge, discard or clean up
|
|
112
|
+
ordewell handoff [action] Review, merge, discard or clean up an isolated run
|
|
113
113
|
|
|
114
114
|
Tasks (<id> is an order number or a task ID):
|
|
115
115
|
ordewell add-task --title "..." Add a task to the current plan
|
|
@@ -117,7 +117,7 @@ Tasks (<id> is an order number or a task ID):
|
|
|
117
117
|
ordewell complete <id> Mark a task complete (alias: mark-complete)
|
|
118
118
|
ordewell uncomplete <id> Mark a completed task not done
|
|
119
119
|
ordewell skip <id> Skip a task (marks it complete so dependents can run)
|
|
120
|
-
ordewell force-start <id> Start a task now, ignoring dependencies
|
|
120
|
+
ordewell force-start <id> Start a task now, ignoring dependencies (asks past a merge gate)
|
|
121
121
|
ordewell run-task <id> Run only one task
|
|
122
122
|
ordewell retry <id> Re-run a failed task
|
|
123
123
|
ordewell continue <id> <msg> Continue a finished task in its saved runner session
|
|
@@ -130,6 +130,7 @@ Per-task assignment (omit the value to list the options):
|
|
|
130
130
|
ordewell task-effort <id> [level] Set a task's thinking effort ("default" to clear)
|
|
131
131
|
ordewell task-mode <id> [mode] Set a task's runner mode
|
|
132
132
|
ordewell task-deps <id> [a,b|none] Set which earlier tasks a task waits for
|
|
133
|
+
ordewell task-ops <id> [on|off] Make a task an ops task (runs in your checkout once its dependencies are merged) or a change task
|
|
133
134
|
|
|
134
135
|
Planner, models and runners:
|
|
135
136
|
ordewell planner [<provider>] Choose who plans \u2014 an API provider or a coding agent (no API key)
|
|
@@ -138,8 +139,8 @@ Planner, models and runners:
|
|
|
138
139
|
ordewell key [set <p> <key>] Show which providers have a key, or store one
|
|
139
140
|
ordewell runners [<id> on|off] Enable or disable runners (claude-code, opencode, codex)
|
|
140
141
|
ordewell allowlist set|clear|show Limit which models a runner may use
|
|
141
|
-
ordewell auto [
|
|
142
|
-
ordewell transport [terminal|structured]
|
|
142
|
+
ordewell auto [full|auto] Autonomy level for new sessions: Full auto or Auto
|
|
143
|
+
ordewell transport [terminal|structured] Structured (default) or terminal task transport, from the next run
|
|
143
144
|
ordewell parallel [<n>] How many AI tasks run at once (default 3; applies to a live run)
|
|
144
145
|
ordewell refresh Re-discover runners and model catalogs
|
|
145
146
|
ordewell models List every provider's catalog (works without a server)
|
|
@@ -212,7 +213,7 @@ Environment:
|
|
|
212
213
|
ORDEWELL_PLANNER_EFFORT Thinking effort for a coding-agent planner
|
|
213
214
|
ORCHESTRATOR_MODEL Default: deepseek/deepseek-v4-flash
|
|
214
215
|
OPENROUTER_BASE_URL Default: https://openrouter.ai/api/v1
|
|
215
|
-
ORDEWELL_AUTONOMOUS_MODE
|
|
216
|
+
ORDEWELL_AUTONOMOUS_MODE true: Full auto, false: Auto, for new sessions (see "ordewell auto")
|
|
216
217
|
ORDEWELL_PORT Daemon port CLI commands target (default: 3742)
|
|
217
218
|
ORDEWELL_DIRENV false: agents skip the project's direnv .envrc
|
|
218
219
|
ORDEWELL_RESEARCH_ENABLED true (default) or false
|
|
@@ -713,6 +714,17 @@ var ApiClient = class _ApiClient {
|
|
|
713
714
|
}
|
|
714
715
|
return { plan: res.data.plan, summary: res.data.summary, keptMessages: res.data.keptMessages };
|
|
715
716
|
}
|
|
717
|
+
/** The dependencies a task waits on at its merge gate (ADR-0020); empty when nothing gates it. */
|
|
718
|
+
async getMergeGate(sessionId, taskId) {
|
|
719
|
+
const res = await this.httpRequest(
|
|
720
|
+
"GET",
|
|
721
|
+
`/api/plans/${sessionId}/tasks/${taskId}/merge-gate`
|
|
722
|
+
);
|
|
723
|
+
if (res.status !== 200) {
|
|
724
|
+
throw new Error(res.data?.error || "Could not read the merge gate");
|
|
725
|
+
}
|
|
726
|
+
return res.data.mergeGate ?? [];
|
|
727
|
+
}
|
|
716
728
|
async reviewRunDiff(sessionId) {
|
|
717
729
|
const res = await this.httpRequest("GET", `/api/plans/${sessionId}/isolation/diff`);
|
|
718
730
|
if (res.status !== 200) {
|
|
@@ -1252,7 +1264,7 @@ Plan: ${tasks.length} task${tasks.length === 1 ? "" : "s"} (${aiCount} AI, ${man
|
|
|
1252
1264
|
console.log(`Session: ${sessionId}
|
|
1253
1265
|
`);
|
|
1254
1266
|
for (const t of tasks) {
|
|
1255
|
-
const typeIcon = t.type === "user" ? "[MAN]" : "[ AI]";
|
|
1267
|
+
const typeIcon = t.type === "user" ? "[MAN]" : t.ops ? "[OPS]" : "[ AI]";
|
|
1256
1268
|
const deps = t.dependencies?.length ? ` \u2190 ${t.dependencies.join(", ")}` : "";
|
|
1257
1269
|
console.log(` ${(0, import_core7.taskOrderLabel)(t).padStart(2)}. ${typeIcon} ${t.title}${modelSuffix(t)}${deps}`);
|
|
1258
1270
|
for (const sub of t.subtasks || []) {
|
|
@@ -1264,6 +1276,9 @@ Plan: ${tasks.length} task${tasks.length === 1 ? "" : "s"} (${aiCount} AI, ${man
|
|
|
1264
1276
|
if (manCount > 0) {
|
|
1265
1277
|
console.log("\n [MAN] = manual step \u2014 run `ordewell tui` to work through it");
|
|
1266
1278
|
}
|
|
1279
|
+
if (tasks.some((t) => t.ops)) {
|
|
1280
|
+
console.log(" [OPS] = ops task \u2014 runs in your checkout once the work it depends on is merged (`ordewell handoff merge`)");
|
|
1281
|
+
}
|
|
1267
1282
|
}
|
|
1268
1283
|
var PLANNER_ENV = ["AI_PROVIDER", "ORCHESTRATOR_MODEL", "ORDEWELL_PLANNER_EFFORT"];
|
|
1269
1284
|
async function forwardPlannerEnv(api) {
|
|
@@ -1456,7 +1471,7 @@ function isolationOfPlan(plan) {
|
|
|
1456
1471
|
// `tasksStatus` update fills it in once the stream catches up.
|
|
1457
1472
|
...repairedFiles ? { repairedFiles } : {}
|
|
1458
1473
|
};
|
|
1459
|
-
if (state === "integrated") {
|
|
1474
|
+
if (state === "integrated" && record.inHead !== true) {
|
|
1460
1475
|
landed.push({ taskId, order: Number(record.order ?? 0), title: String(record.title ?? taskId), changed, ...repairedFiles ? { repairedFiles } : {} });
|
|
1461
1476
|
}
|
|
1462
1477
|
}
|
|
@@ -1531,6 +1546,9 @@ function iconFor(status) {
|
|
|
1531
1546
|
// src/commands/run.ts
|
|
1532
1547
|
var import_core9 = require("@ordewell/core");
|
|
1533
1548
|
|
|
1549
|
+
// src/commands/shared.ts
|
|
1550
|
+
var import_readline2 = require("readline");
|
|
1551
|
+
|
|
1534
1552
|
// src/catalog.ts
|
|
1535
1553
|
function normalizeCatalog(result) {
|
|
1536
1554
|
const runnersByModel = /* @__PURE__ */ new Map();
|
|
@@ -1586,6 +1604,16 @@ function fail(...lines) {
|
|
|
1586
1604
|
for (const line of lines) console.error(line);
|
|
1587
1605
|
process.exit(1);
|
|
1588
1606
|
}
|
|
1607
|
+
async function askYesNo(question) {
|
|
1608
|
+
if (!process.stdin.isTTY) return false;
|
|
1609
|
+
const rl = (0, import_readline2.createInterface)({ input: process.stdin, output: process.stderr });
|
|
1610
|
+
try {
|
|
1611
|
+
const answer = await new Promise((resolve3) => rl.question(`${question} [y/N] `, resolve3));
|
|
1612
|
+
return /^y(es)?$/i.test(answer.trim());
|
|
1613
|
+
} finally {
|
|
1614
|
+
rl.close();
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1589
1617
|
function writeEnv(env) {
|
|
1590
1618
|
const envFile = findEnvFile();
|
|
1591
1619
|
for (const [key, value] of Object.entries(env)) {
|
|
@@ -1611,7 +1639,8 @@ function toTaskView(task) {
|
|
|
1611
1639
|
assignedRunner: task.assignedRunner || void 0,
|
|
1612
1640
|
taskMode: task.taskMode || void 0,
|
|
1613
1641
|
assignedModel: task.assignedModel ?? void 0,
|
|
1614
|
-
subtasks: [...task.subtasks ?? []].sort((a, b) => a.order - b.order).map(toTaskView)
|
|
1642
|
+
subtasks: [...task.subtasks ?? []].sort((a, b) => a.order - b.order).map(toTaskView),
|
|
1643
|
+
...task.ops ? { ops: true } : {}
|
|
1615
1644
|
};
|
|
1616
1645
|
}
|
|
1617
1646
|
function taskViews(plan) {
|
|
@@ -1658,6 +1687,7 @@ async function followExecution(api, sessionId, start, onBlocked) {
|
|
|
1658
1687
|
lastPrinted = output;
|
|
1659
1688
|
}
|
|
1660
1689
|
}
|
|
1690
|
+
let pausedAtGate = false;
|
|
1661
1691
|
let settleReady = () => {
|
|
1662
1692
|
};
|
|
1663
1693
|
const streamReady = new Promise((resolve3, reject) => {
|
|
@@ -1674,6 +1704,9 @@ async function followExecution(api, sessionId, start, onBlocked) {
|
|
|
1674
1704
|
}
|
|
1675
1705
|
if (event.type === "status_update" && event.tasks) {
|
|
1676
1706
|
printStatus(event.tasks);
|
|
1707
|
+
const paused = event.gate?.paused === true;
|
|
1708
|
+
if (paused && !pausedAtGate) process.stderr.write("\xB7 Paused for Merge all \u2014 `ordewell handoff merge` merges what has landed, and the waiting tasks start.\n");
|
|
1709
|
+
pausedAtGate = paused;
|
|
1677
1710
|
}
|
|
1678
1711
|
if (event.type === "review_needed") {
|
|
1679
1712
|
console.log("\nPlan needs your sign-off \u2014 run `ordewell approve` to continue.");
|
|
@@ -2277,7 +2310,7 @@ async function handleVerify(subArgs, api) {
|
|
|
2277
2310
|
}
|
|
2278
2311
|
|
|
2279
2312
|
// src/commands/transport.ts
|
|
2280
|
-
var describe = (transport) => `Runner transport
|
|
2313
|
+
var describe = (transport) => `Runner transport: ${transport === "terminal" ? "terminal" : "structured"} \u2014 applies from the next run`;
|
|
2281
2314
|
async function handleTransport(subArgs, api) {
|
|
2282
2315
|
const action = subArgs[0];
|
|
2283
2316
|
const port = api ? void 0 : await ensureDaemon(resolvePort(subArgs));
|
|
@@ -2330,13 +2363,23 @@ async function withResolvedTask(subArgs, usage, injectedApi, run) {
|
|
|
2330
2363
|
}
|
|
2331
2364
|
await run(api, sessionId, taskId, plan);
|
|
2332
2365
|
}
|
|
2366
|
+
var PASSES_GATE = /* @__PURE__ */ new Set(["run", "force-start"]);
|
|
2367
|
+
async function confirmPastGate(subArgs, api, sessionId, taskId, confirm) {
|
|
2368
|
+
if (hasFlag(subArgs, "--yes")) return;
|
|
2369
|
+
const unmerged = await api.getMergeGate(sessionId, taskId).catch(() => []);
|
|
2370
|
+
if (unmerged.length === 0) return;
|
|
2371
|
+
const question = `This task waits for Merge all: the work of ${unmerged.map((d) => `#${d.order} ${d.title}`).join(", ")} is not merged into your branch yet, so it would act without it. Starting it now is kept on the task. Start it anyway?`;
|
|
2372
|
+
if (!await confirm(question)) fail("Not started \u2014 nothing was changed. Pass --yes to start it without a prompt.");
|
|
2373
|
+
}
|
|
2333
2374
|
function makeHandler(action, command = action) {
|
|
2334
|
-
|
|
2375
|
+
const yes = PASSES_GATE.has(action) ? " [--yes]" : "";
|
|
2376
|
+
return async function handle(subArgs, injectedApi, confirm = askYesNo) {
|
|
2335
2377
|
await withResolvedTask(
|
|
2336
2378
|
subArgs,
|
|
2337
|
-
`Usage: ordewell ${command} <task-id-or-order> [--session-id <id>] [--workspace /path]`,
|
|
2379
|
+
`Usage: ordewell ${command} <task-id-or-order> [--session-id <id>] [--workspace /path]${yes}`,
|
|
2338
2380
|
injectedApi,
|
|
2339
2381
|
async (api, sessionId, taskId) => {
|
|
2382
|
+
if (PASSES_GATE.has(action)) await confirmPastGate(subArgs, api, sessionId, taskId, confirm);
|
|
2340
2383
|
try {
|
|
2341
2384
|
await api.taskControl(sessionId, taskId, action);
|
|
2342
2385
|
console.log(PAST_TENSE[action]);
|
|
@@ -2613,22 +2656,19 @@ async function handleRewind(subArgs, injectedApi) {
|
|
|
2613
2656
|
console.log(fork.rewoundMessage);
|
|
2614
2657
|
}
|
|
2615
2658
|
|
|
2616
|
-
// src/commands/handoff.ts
|
|
2617
|
-
var import_readline2 = require("readline");
|
|
2618
|
-
|
|
2619
2659
|
// src/tui/transcript.ts
|
|
2620
2660
|
var import_core15 = require("@ordewell/core");
|
|
2621
2661
|
function show(state, { view: conversation, gate: turnGate }) {
|
|
2622
2662
|
const gated = turnGate === state.turnGate ? state : { ...state, turnGate };
|
|
2623
2663
|
if (conversation === state.conversation) return gated;
|
|
2624
|
-
|
|
2625
|
-
return { ...gated, conversation, scroll };
|
|
2664
|
+
return { ...gated, conversation };
|
|
2626
2665
|
}
|
|
2627
2666
|
function fold(state, input) {
|
|
2628
2667
|
return show(state, { view: (0, import_core15.reduceConversation)(state.conversation, input), gate: state.turnGate });
|
|
2629
2668
|
}
|
|
2630
2669
|
function say(state, role, text) {
|
|
2631
|
-
|
|
2670
|
+
const said = fold(state, { type: "local_entry", role, text });
|
|
2671
|
+
return said.conversation.nextId > state.conversation.nextId ? { ...said, scroll: 0 } : said;
|
|
2632
2672
|
}
|
|
2633
2673
|
function hear(state, message) {
|
|
2634
2674
|
return show(state, (0, import_core15.followTurn)(state.conversation, state.turnGate, message));
|
|
@@ -2808,8 +2848,12 @@ var reposOf2 = (handoff) => handoff.repos.map((r) => r.path).join(", ");
|
|
|
2808
2848
|
var HANDOFF_USAGE = `/handoff [${HANDOFF_ACTIONS.map((a) => a.id).join("|")}]`;
|
|
2809
2849
|
var diffRoom = (rows) => Math.max(1, rows - 3);
|
|
2810
2850
|
var fail2 = (state, content) => ({ state: say(state, "error", content), effects: [] });
|
|
2851
|
+
function currentHandoff(state) {
|
|
2852
|
+
return state.gate?.handoff ?? state.handoff;
|
|
2853
|
+
}
|
|
2854
|
+
var midRun = (state) => state.status === "executing";
|
|
2811
2855
|
function handoffCommand(state, arg) {
|
|
2812
|
-
if (!state.sessionId || !state
|
|
2856
|
+
if (!state.sessionId || !currentHandoff(state)) return fail2(state, "No isolated run to hand off \u2014 a plan that ran in worktrees leaves one here.");
|
|
2813
2857
|
if (arg === void 0) return { state: { ...state, overlay: { kind: "handoff", index: 0, diff: null } }, effects: [] };
|
|
2814
2858
|
const action = HANDOFF_ACTIONS.find((a) => a.id === arg.toLowerCase());
|
|
2815
2859
|
if (!action) return fail2(state, `Usage: ${HANDOFF_USAGE}`);
|
|
@@ -2826,7 +2870,8 @@ function handoffArrived(state, handoff) {
|
|
|
2826
2870
|
return state.overlay ? told : { ...told, overlay: { kind: "handoff", index: 0, diff: null } };
|
|
2827
2871
|
}
|
|
2828
2872
|
function runHandoffAction(state, id) {
|
|
2829
|
-
const { sessionId
|
|
2873
|
+
const { sessionId } = state;
|
|
2874
|
+
const handoff = currentHandoff(state);
|
|
2830
2875
|
if (!sessionId || !handoff) return fail2(state, "No isolated run to hand off.");
|
|
2831
2876
|
const closed = { ...state, overlay: null };
|
|
2832
2877
|
switch (id) {
|
|
@@ -2835,6 +2880,20 @@ function runHandoffAction(state, id) {
|
|
|
2835
2880
|
case "cleanup":
|
|
2836
2881
|
return { state: closed, effects: [{ type: "isolationCleanup", sessionId, branch: handoffBranch(handoff) }] };
|
|
2837
2882
|
case "merge":
|
|
2883
|
+
if (midRun(state)) {
|
|
2884
|
+
return {
|
|
2885
|
+
state: {
|
|
2886
|
+
...state,
|
|
2887
|
+
overlay: {
|
|
2888
|
+
kind: "confirm",
|
|
2889
|
+
title: isRepoGroup(handoff) ? "Merge what has landed into your branches?" : "Merge what has landed into your branch?",
|
|
2890
|
+
message: `Merge ${handoffBranch(handoff)} as it stands now into whatever ${isRepoGroup(handoff) ? `each of ${reposWithWork(handoff).join(", ")} has` : "you have"} checked out. Ordewell never does this on its own. The run goes on: tasks waiting for Merge all start once it has merged, and later tasks land on the same branch for the next Merge all. A merge that conflicts is aborted, and your ${isRepoGroup(handoff) ? "trees stay" : "tree stays"} as ${isRepoGroup(handoff) ? "they were" : "it was"}.${repairedNotice(handoff)}`,
|
|
2891
|
+
action: { kind: "merge-run" }
|
|
2892
|
+
}
|
|
2893
|
+
},
|
|
2894
|
+
effects: []
|
|
2895
|
+
};
|
|
2896
|
+
}
|
|
2838
2897
|
return {
|
|
2839
2898
|
state: {
|
|
2840
2899
|
...state,
|
|
@@ -2869,20 +2928,23 @@ function runHandoffAction(state, id) {
|
|
|
2869
2928
|
}
|
|
2870
2929
|
function confirmedHandoff(state, kind) {
|
|
2871
2930
|
const closed = { ...state, overlay: null };
|
|
2872
|
-
|
|
2873
|
-
|
|
2931
|
+
const handoff = currentHandoff(state);
|
|
2932
|
+
if (!state.sessionId || !handoff) return { state: closed, effects: [] };
|
|
2933
|
+
const { sessionId } = state;
|
|
2874
2934
|
const repaired = repairedLanded(handoff);
|
|
2875
2935
|
const effect = kind === "merge-run" ? {
|
|
2876
2936
|
type: "isolationMerge",
|
|
2877
2937
|
sessionId,
|
|
2878
2938
|
branch: handoffBranch(handoff),
|
|
2879
2939
|
...isRepoGroup(handoff) ? { group: true } : {},
|
|
2880
|
-
...repaired.length > 0 ? { repaired } : {}
|
|
2940
|
+
...repaired.length > 0 ? { repaired } : {},
|
|
2941
|
+
...midRun(state) ? { midRun: true } : {}
|
|
2881
2942
|
} : { type: "isolationDiscard", sessionId, branch: handoffBranch(handoff) };
|
|
2882
2943
|
return { state: closed, effects: [effect] };
|
|
2883
2944
|
}
|
|
2884
2945
|
function showDiff(state, diff) {
|
|
2885
|
-
|
|
2946
|
+
const handoff = currentHandoff(state);
|
|
2947
|
+
if (diff.trim() === "") return say(state, "system", `Nothing differs from ${handoff ? handoffBase(handoff, 8) : "the base commit"}.`);
|
|
2886
2948
|
const index = state.overlay?.kind === "handoff" ? state.overlay.index : 0;
|
|
2887
2949
|
const lines = sanitize(diff.replace(/\t/g, " ")).split("\n");
|
|
2888
2950
|
return { ...state, overlay: { kind: "handoff", index, diff: { lines, scroll: 0 } } };
|
|
@@ -2955,6 +3017,7 @@ function clearIsolation(state) {
|
|
|
2955
3017
|
return {
|
|
2956
3018
|
...state,
|
|
2957
3019
|
handoff: null,
|
|
3020
|
+
gate: null,
|
|
2958
3021
|
tasks: mapTasks(state.tasks, ({ isolation: _dropped, ...task }) => task)
|
|
2959
3022
|
};
|
|
2960
3023
|
}
|
|
@@ -2972,16 +3035,6 @@ function isolationForPlan(tasks, record, previous) {
|
|
|
2972
3035
|
|
|
2973
3036
|
// src/commands/handoff.ts
|
|
2974
3037
|
var USAGE4 = "Usage: ordewell handoff [review|merge|discard|cleanup] [--session-id <id>] [--workspace /path] [--yes]";
|
|
2975
|
-
async function askYesNo(question) {
|
|
2976
|
-
if (!process.stdin.isTTY) return false;
|
|
2977
|
-
const rl = (0, import_readline2.createInterface)({ input: process.stdin, output: process.stderr });
|
|
2978
|
-
try {
|
|
2979
|
-
const answer = await new Promise((resolve3) => rl.question(`${question} [y/N] `, resolve3));
|
|
2980
|
-
return /^y(es)?$/i.test(answer.trim());
|
|
2981
|
-
} finally {
|
|
2982
|
-
rl.close();
|
|
2983
|
-
}
|
|
2984
|
-
}
|
|
2985
3038
|
async function handleHandoff(subArgs, injectedApi, confirm = askYesNo) {
|
|
2986
3039
|
const action = positionals(subArgs)[0]?.toLowerCase();
|
|
2987
3040
|
if (action === void 0) {
|
|
@@ -3016,7 +3069,7 @@ async function handleHandoff(subArgs, injectedApi, confirm = askYesNo) {
|
|
|
3016
3069
|
return;
|
|
3017
3070
|
}
|
|
3018
3071
|
case "merge": {
|
|
3019
|
-
await confirmed((group ? `Merge ${branch} into the branch checked out in each of ${reposWithWork(handoff).join(", ")}? Nothing is merged unless every repository can take it; once
|
|
3072
|
+
await confirmed((group ? `Merge ${branch} into the branch checked out in each of ${reposWithWork(handoff).join(", ")}? Nothing is merged unless every repository can take it; once a finished run has merged everything, its worktrees and branches are removed.` : `Merge ${branch} into the branch you have checked out? Once a finished run has merged everything, its worktrees and branches are removed.`) + repairedNotice(handoff));
|
|
3020
3073
|
const { ok, message } = mergeOutcome(await attempt(() => api.mergeRun(sessionId)), branch, group, repairedLanded(handoff));
|
|
3021
3074
|
if (ok) console.log(message);
|
|
3022
3075
|
else fail(message);
|
|
@@ -3208,6 +3261,7 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
3208
3261
|
}
|
|
3209
3262
|
|
|
3210
3263
|
// src/commands/runners.ts
|
|
3264
|
+
var import_core19 = require("@ordewell/core");
|
|
3211
3265
|
var USAGE7 = "Usage: ordewell runners [<runner-id> on|off]";
|
|
3212
3266
|
async function handleRunners(subArgs, injectedApi) {
|
|
3213
3267
|
const [runner, toggle] = positionals(subArgs);
|
|
@@ -3232,21 +3286,21 @@ async function handleRunners(subArgs, injectedApi) {
|
|
|
3232
3286
|
await api.setRunnerEnabled(runner, enabled);
|
|
3233
3287
|
console.log(`${runner} ${enabled ? "enabled" : "disabled"}.`);
|
|
3234
3288
|
}
|
|
3235
|
-
var AUTO_USAGE = "Usage: ordewell auto [
|
|
3289
|
+
var AUTO_USAGE = "Usage: ordewell auto [full|auto]";
|
|
3236
3290
|
function handleAuto(subArgs) {
|
|
3237
3291
|
const [arg] = positionals(subArgs);
|
|
3238
3292
|
const currentRaw = process.env.ORDEWELL_AUTONOMOUS_MODE;
|
|
3239
3293
|
const current = currentRaw !== "false" && currentRaw !== "0";
|
|
3240
3294
|
if (!arg) {
|
|
3241
|
-
console.log(`
|
|
3295
|
+
console.log(`Autonomy level: ${(0, import_core19.autonomyLevelLabel)(current)}`);
|
|
3242
3296
|
console.log(` ${AUTO_USAGE}`);
|
|
3243
3297
|
return;
|
|
3244
3298
|
}
|
|
3245
|
-
const enabled =
|
|
3299
|
+
const enabled = (0, import_core19.parseAutonomyLevel)(arg);
|
|
3246
3300
|
if (enabled === null) fail(AUTO_USAGE);
|
|
3247
3301
|
writeEnvVar(findEnvFile(), "ORDEWELL_AUTONOMOUS_MODE", String(enabled));
|
|
3248
3302
|
process.env.ORDEWELL_AUTONOMOUS_MODE = String(enabled);
|
|
3249
|
-
console.log(`
|
|
3303
|
+
console.log(`Autonomy level: ${(0, import_core19.autonomyLevelLabel)(enabled)} for new sessions.`);
|
|
3250
3304
|
}
|
|
3251
3305
|
async function handleRefresh(subArgs, injectedApi) {
|
|
3252
3306
|
const api = await connect(subArgs, injectedApi);
|
|
@@ -3278,7 +3332,7 @@ async function handleApprove(subArgs, injectedApi) {
|
|
|
3278
3332
|
// src/tui/terminalLauncher.ts
|
|
3279
3333
|
var import_child_process3 = require("child_process");
|
|
3280
3334
|
var import_util = require("util");
|
|
3281
|
-
var
|
|
3335
|
+
var import_core20 = require("@ordewell/core");
|
|
3282
3336
|
var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
|
|
3283
3337
|
var defaultExecFile = async (command, args) => {
|
|
3284
3338
|
const { stdout, stderr } = await execFileAsync(command, args);
|
|
@@ -3298,16 +3352,16 @@ var defaultWhich = (bin) => {
|
|
|
3298
3352
|
async function openTaskTerminal(port, planSessionId, taskId, deps = {}) {
|
|
3299
3353
|
const platform = deps.platform ?? process.platform;
|
|
3300
3354
|
const env = deps.env ?? process.env;
|
|
3301
|
-
const hasTmuxImpl = deps.hasTmuxImpl ??
|
|
3355
|
+
const hasTmuxImpl = deps.hasTmuxImpl ?? import_core20.hasTmux;
|
|
3302
3356
|
const execFileImpl = deps.execFileImpl ?? defaultExecFile;
|
|
3303
3357
|
const spawnImpl = deps.spawnImpl ?? defaultSpawn;
|
|
3304
3358
|
const which = deps.which ?? defaultWhich;
|
|
3305
3359
|
if (!hasTmuxImpl()) {
|
|
3306
|
-
return { ok: false, message: "tmux is not installed
|
|
3360
|
+
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." };
|
|
3307
3361
|
}
|
|
3308
|
-
const session = (0,
|
|
3309
|
-
const socket = (0,
|
|
3310
|
-
const base = (0,
|
|
3362
|
+
const session = (0, import_core20.tmuxSessionName)(port);
|
|
3363
|
+
const socket = (0, import_core20.tmuxSocketName)(port);
|
|
3364
|
+
const base = (0, import_core20.tmuxWindowName)(taskId, planSessionId);
|
|
3311
3365
|
let windows;
|
|
3312
3366
|
try {
|
|
3313
3367
|
const { stdout } = await execFileImpl("tmux", ["-L", socket, "list-windows", "-t", session, "-F", "#{window_name}"]);
|
|
@@ -3317,7 +3371,7 @@ async function openTaskTerminal(port, planSessionId, taskId, deps = {}) {
|
|
|
3317
3371
|
}
|
|
3318
3372
|
const window = latestAttempt(windows, base);
|
|
3319
3373
|
if (!window) {
|
|
3320
|
-
return { ok: false, message: "This task
|
|
3374
|
+
return { ok: false, message: "This task has no live terminal and no saved log \u2014 it may still be pending." };
|
|
3321
3375
|
}
|
|
3322
3376
|
const manualHint = `tmux -L ${socket} attach -t ${session}:${window}`;
|
|
3323
3377
|
const viewer = `${session}-view-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
@@ -3599,9 +3653,27 @@ Dependencies \xB7 #${task.order} ${task.title}
|
|
|
3599
3653
|
console.log(dependencies.length > 0 ? `Task #${task.order} now depends on ${dependencies.length} task${dependencies.length === 1 ? "" : "s"}.` : `Task #${task.order} no longer depends on anything.`);
|
|
3600
3654
|
});
|
|
3601
3655
|
}
|
|
3656
|
+
var OPS_USAGE = "Usage: ordewell task-ops <task-id-or-order> [on|off] [--session-id <id>]";
|
|
3657
|
+
async function handleTaskOps(subArgs, injectedApi) {
|
|
3658
|
+
await withTask(subArgs, OPS_USAGE, injectedApi, async (api, sessionId, task, tasks, value) => {
|
|
3659
|
+
if (!value) {
|
|
3660
|
+
console.log(`
|
|
3661
|
+
#${task.order} ${task.title} is ${task.ops ? "an ops task: it runs in your checkout once the work it depends on is merged" : "a change task: it runs in its own worktree"}.`);
|
|
3662
|
+
console.log(`
|
|
3663
|
+
${OPS_USAGE}`);
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3666
|
+
const to = value.toLowerCase();
|
|
3667
|
+
if (to !== "on" && to !== "off") fail(OPS_USAGE);
|
|
3668
|
+
if (task.type !== "ai") fail("Only an AI task can be an ops task \u2014 a manual task already runs outside any worktree.");
|
|
3669
|
+
if (!tasks.some((t) => t.id === task.id)) fail("A subtask runs with its parent; make the parent an ops task instead.");
|
|
3670
|
+
await api.updateTask(sessionId, task.id, { ops: to === "on" });
|
|
3671
|
+
console.log(to === "on" ? `Task #${task.order} is an ops task: it runs in your checkout once the work it depends on is merged.` : `Task #${task.order} is a change task: it runs in its own worktree.`);
|
|
3672
|
+
});
|
|
3673
|
+
}
|
|
3602
3674
|
|
|
3603
3675
|
// src/tui/index.ts
|
|
3604
|
-
var
|
|
3676
|
+
var import_core33 = require("@ordewell/core");
|
|
3605
3677
|
|
|
3606
3678
|
// src/tui/editor.ts
|
|
3607
3679
|
function emptyEditor() {
|
|
@@ -3756,6 +3828,7 @@ var SLASH_COMMANDS = [
|
|
|
3756
3828
|
{ name: "task-effort", usage: "/task-effort <id> [level]", description: "Choose or set a task's thinking effort", category: "tasks" },
|
|
3757
3829
|
{ name: "task-mode", usage: "/task-mode <id> [mode]", description: "Choose or set a task's runner mode", category: "tasks" },
|
|
3758
3830
|
{ name: "task-deps", usage: "/task-deps <id>", description: "Edit which earlier tasks a task waits for", category: "tasks" },
|
|
3831
|
+
{ name: "task-ops", usage: "/task-ops <id> [on|off]", description: "Make a task an ops task \u2014 run in your checkout once its dependencies are merged \u2014 or a change task", category: "tasks" },
|
|
3759
3832
|
// Models & providers
|
|
3760
3833
|
{ name: "planner", usage: "/planner [<provider>]", description: "Choose who plans \u2014 an API provider or a coding agent (no API key)", category: "models" },
|
|
3761
3834
|
{ name: "model", usage: "/model [set <id>]", description: "Show or set the orchestrator model", category: "models" },
|
|
@@ -3763,8 +3836,8 @@ var SLASH_COMMANDS = [
|
|
|
3763
3836
|
{ name: "key", usage: "/key [set <provider> <key>]", description: "Configure API provider keys", category: "models" },
|
|
3764
3837
|
{ name: "allowlist", usage: "/allowlist [set <runner> <ids> | clear <runner>]", description: "Limit which models a runner may use", category: "models" },
|
|
3765
3838
|
{ name: "runners", usage: "/runners [<id> on|off]", description: "Enable or disable runners (claude-code, opencode, codex)", category: "models" },
|
|
3766
|
-
{ name: "auto", usage: "/auto [
|
|
3767
|
-
{ name: "transport", usage: "/transport [terminal|structured]", description: "
|
|
3839
|
+
{ name: "auto", usage: "/auto [full|auto]", description: "Autonomy level for new plans: Full auto or Auto", category: "models" },
|
|
3840
|
+
{ name: "transport", usage: "/transport [terminal|structured]", description: "Drive tasks through each runner's protocol (structured) or a terminal, from the next run", category: "models" },
|
|
3768
3841
|
{ name: "parallel", usage: "/parallel [<n>]", description: "How many AI tasks run at once (applies to a live run)", category: "models" },
|
|
3769
3842
|
{ name: "refresh", usage: "/refresh", description: "Re-discover runners and model catalogs", category: "models" },
|
|
3770
3843
|
// Skills
|
|
@@ -3849,7 +3922,7 @@ function tokenCompletions(token) {
|
|
|
3849
3922
|
}
|
|
3850
3923
|
|
|
3851
3924
|
// src/tui/blocks.ts
|
|
3852
|
-
var
|
|
3925
|
+
var import_core21 = require("@ordewell/core");
|
|
3853
3926
|
|
|
3854
3927
|
// src/tui/markdown.ts
|
|
3855
3928
|
function renderMarkdown(source, cols) {
|
|
@@ -4057,16 +4130,16 @@ function resultLines(block, room, detailAll) {
|
|
|
4057
4130
|
const fit2 = (line) => detailAll ? wrap(line, room) : [truncate(line, room)];
|
|
4058
4131
|
const outcome = unsettledOutcome(block);
|
|
4059
4132
|
if (outcome) {
|
|
4060
|
-
const [reason, ...rest] = (0,
|
|
4133
|
+
const [reason, ...rest] = (0, import_core21.outputLines)(block.output).map(sanitize);
|
|
4061
4134
|
const status = fit2(reason ? `${outcome.label}: ${reason}` : outcome.label).map(outcome.paint);
|
|
4062
4135
|
return detailAll ? [...status, ...rest.flatMap(fit2).map(style.grey)] : status;
|
|
4063
4136
|
}
|
|
4064
4137
|
if (detailAll) {
|
|
4065
|
-
const lines2 = (0,
|
|
4138
|
+
const lines2 = (0, import_core21.outputLines)(block.output).map(sanitize);
|
|
4066
4139
|
const note = lines2.length > PREVIEW_LINES ? ["\u2026 (ctrl+o to collapse)"] : [];
|
|
4067
4140
|
return lines2.length > 0 ? [...lines2.flatMap(fit2), ...note].map(style.grey) : [style.grey("(no output)")];
|
|
4068
4141
|
}
|
|
4069
|
-
const { lines, hiddenLineCount } = (0,
|
|
4142
|
+
const { lines, hiddenLineCount } = (0, import_core21.outputPreview)(block.output, PREVIEW_LINES);
|
|
4070
4143
|
if (lines.length === 0) return [style.grey("(no output)")];
|
|
4071
4144
|
const more = hiddenLineCount > 0 ? [`\u2026 +${countOf(hiddenLineCount, "line")} (ctrl+o to expand)`] : [];
|
|
4072
4145
|
return [...lines.map(sanitize), ...more].map((line) => style.grey(truncate(line, room)));
|
|
@@ -4187,7 +4260,7 @@ function tokenLine(blocks, cols) {
|
|
|
4187
4260
|
}
|
|
4188
4261
|
|
|
4189
4262
|
// src/tui/state.ts
|
|
4190
|
-
var
|
|
4263
|
+
var import_core22 = require("@ordewell/core");
|
|
4191
4264
|
var SKILL_IDS = ["tdd", "verify"];
|
|
4192
4265
|
function noSkills() {
|
|
4193
4266
|
return { tdd: false, verify: false };
|
|
@@ -4234,9 +4307,9 @@ function findTask(tasks, id) {
|
|
|
4234
4307
|
function initialState(overrides = {}) {
|
|
4235
4308
|
return {
|
|
4236
4309
|
editor: emptyEditor(),
|
|
4237
|
-
conversation:
|
|
4310
|
+
conversation: import_core22.EMPTY_CONVERSATION,
|
|
4238
4311
|
taskView: null,
|
|
4239
|
-
turnGate:
|
|
4312
|
+
turnGate: import_core22.NO_TURN,
|
|
4240
4313
|
detailAll: false,
|
|
4241
4314
|
status: "idle",
|
|
4242
4315
|
busyLabel: "",
|
|
@@ -4261,18 +4334,19 @@ function initialState(overrides = {}) {
|
|
|
4261
4334
|
orchestratorModel: "",
|
|
4262
4335
|
plannerProvider: "",
|
|
4263
4336
|
plannerEffort: "",
|
|
4264
|
-
maxParallel:
|
|
4337
|
+
maxParallel: import_core22.DEFAULT_MAX_PARALLEL,
|
|
4265
4338
|
configuredProviders: [],
|
|
4266
4339
|
allowlist: {},
|
|
4267
4340
|
autonomous: true,
|
|
4268
|
-
runnerTransport: "
|
|
4341
|
+
runnerTransport: "structured",
|
|
4269
4342
|
mouseCapture: true,
|
|
4270
4343
|
selection: null,
|
|
4271
4344
|
workspace: process.cwd(),
|
|
4272
4345
|
handoff: null,
|
|
4346
|
+
gate: null,
|
|
4273
4347
|
overlay: null,
|
|
4274
4348
|
pendingApprovals: [],
|
|
4275
|
-
queuedPrompts:
|
|
4349
|
+
queuedPrompts: import_core22.EMPTY_HOLD,
|
|
4276
4350
|
stopArmed: false,
|
|
4277
4351
|
stopArmToken: 0,
|
|
4278
4352
|
stopRequested: false,
|
|
@@ -4320,7 +4394,7 @@ function chatEditorRoomFor(state, active) {
|
|
|
4320
4394
|
}
|
|
4321
4395
|
|
|
4322
4396
|
// src/tui/layout.ts
|
|
4323
|
-
var
|
|
4397
|
+
var import_core23 = require("@ordewell/core");
|
|
4324
4398
|
var CHROME_ROWS = 4;
|
|
4325
4399
|
function chatInputWrap(state) {
|
|
4326
4400
|
const active = state.focus === "chat" && !state.overlay;
|
|
@@ -4345,6 +4419,7 @@ function footerHints(state) {
|
|
|
4345
4419
|
"e effort",
|
|
4346
4420
|
"M mode",
|
|
4347
4421
|
"D deps",
|
|
4422
|
+
"O ops",
|
|
4348
4423
|
"f start",
|
|
4349
4424
|
"E run plan",
|
|
4350
4425
|
"S stop",
|
|
@@ -4359,7 +4434,7 @@ function footerHints(state) {
|
|
|
4359
4434
|
"tab chat"
|
|
4360
4435
|
];
|
|
4361
4436
|
}
|
|
4362
|
-
const detailHint = (0,
|
|
4437
|
+
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"] : [];
|
|
4363
4438
|
return [
|
|
4364
4439
|
"/help",
|
|
4365
4440
|
"tab plan",
|
|
@@ -4464,9 +4539,9 @@ function chatScrollMax(state) {
|
|
|
4464
4539
|
}
|
|
4465
4540
|
function plannerSetup(state) {
|
|
4466
4541
|
const provider = state.plannerProvider;
|
|
4467
|
-
const registration =
|
|
4542
|
+
const registration = import_core23.ALL_PROVIDERS[provider];
|
|
4468
4543
|
if (!registration) return { label: "not set", ready: false };
|
|
4469
|
-
const runner = (0,
|
|
4544
|
+
const runner = (0, import_core23.runnerForProvider)(provider);
|
|
4470
4545
|
const ready = runner ? state.runners.some((r) => r.id === runner) : state.configuredProviders.includes(provider);
|
|
4471
4546
|
return { label: registration.label, ready };
|
|
4472
4547
|
}
|
|
@@ -4576,7 +4651,8 @@ var STATUS_ICON = {
|
|
|
4576
4651
|
var AWAITING_LABEL = {
|
|
4577
4652
|
input: "waiting for your input",
|
|
4578
4653
|
checkpoint: "checkpoint",
|
|
4579
|
-
conflict: "merge conflict"
|
|
4654
|
+
conflict: "merge conflict",
|
|
4655
|
+
"files-changed": "changed tracked files"
|
|
4580
4656
|
};
|
|
4581
4657
|
function approvalLabel(count) {
|
|
4582
4658
|
return count > 1 ? `waiting for approval (${count})` : "waiting for approval";
|
|
@@ -4655,9 +4731,9 @@ function taskLines(state, row, index, cols) {
|
|
|
4655
4731
|
const paint = idle ? style.cyan : STATUS_MARK[task.status] ?? ((t) => t);
|
|
4656
4732
|
const rawIcon = idle ? IDLE_ICON : running ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[task.status] ?? "\xB7";
|
|
4657
4733
|
const icon = paint(rawIcon);
|
|
4658
|
-
const kind = running ? style.yellow("RUN") : task.type === "user" ? style.yellow("MAN") : style.grey(" AI");
|
|
4734
|
+
const kind = running ? style.yellow("RUN") : task.type === "user" ? style.yellow("MAN") : task.ops ? style.magenta("OPS") : style.grey(" AI");
|
|
4659
4735
|
const caret = selected ? style.cyan("\u276F") : " ";
|
|
4660
|
-
const orderLabel = parent ? (0,
|
|
4736
|
+
const orderLabel = parent ? (0, import_core23.taskOrderLabel)(task, parent) : String(task.order).padStart(2);
|
|
4661
4737
|
const indent = parent ? " " : "";
|
|
4662
4738
|
const head = `${indent}${caret} ${icon} ${orderLabel} ${kind} `;
|
|
4663
4739
|
const titleRoom = Math.max(1, cols - width(head));
|
|
@@ -4687,16 +4763,26 @@ function taskLines(state, row, index, cols) {
|
|
|
4687
4763
|
if (effort || mode) lines.push(style.grey(truncate(`${bodyPad}${[effort, mode].filter(Boolean).join(" \xB7 ")}`, cols)));
|
|
4688
4764
|
if (task.isolation?.state === "conflict") {
|
|
4689
4765
|
const where = task.isolation.conflictRepo && task.isolation.conflictRepo !== "." ? ` in ${task.isolation.conflictRepo}` : "";
|
|
4690
|
-
const files = task.isolation.conflictFiles?.length ? ` (${(0,
|
|
4766
|
+
const files = task.isolation.conflictFiles?.length ? ` (${(0, import_core23.capConflictFiles)(task.isolation.conflictFiles)})` : "";
|
|
4691
4767
|
lines.push(style.red(truncate(`${bodyPad}\u26A0 merge conflict${where}${files} \u2014 its work is kept on its own branch`, cols)));
|
|
4692
4768
|
}
|
|
4693
4769
|
if (task.isolation?.state === "repairing") {
|
|
4694
|
-
const files = task.isolation.conflictFiles?.length ? ` in ${(0,
|
|
4770
|
+
const files = task.isolation.conflictFiles?.length ? ` in ${(0, import_core23.capConflictFiles)(task.isolation.conflictFiles)}` : "";
|
|
4695
4771
|
const attempt = task.isolation.repair ? ` (attempt ${task.isolation.repair.attempt}/${task.isolation.repair.limit})` : "";
|
|
4696
4772
|
lines.push(style.yellow(truncate(`${bodyPad}\u21BB repairing conflict${files}${attempt}`, cols)));
|
|
4697
4773
|
}
|
|
4774
|
+
if (task.mergeGate?.length) {
|
|
4775
|
+
const deps = task.mergeGate.map((id) => {
|
|
4776
|
+
const dep = state.tasks.find((t) => t.id === id);
|
|
4777
|
+
return dep ? `#${dep.order}` : id;
|
|
4778
|
+
});
|
|
4779
|
+
lines.push(style.yellow(truncate(`${bodyPad}\u23F8 waits for Merge all \u2014 ${deps.join(", ")} not merged into your branch yet`, cols)));
|
|
4780
|
+
}
|
|
4781
|
+
if (task.status === "awaiting_user" && task.awaitingReason === "files-changed") {
|
|
4782
|
+
lines.push(style.yellow(truncate(`${bodyPad}\u26A0 an ops task changed tracked files \u2014 check them, then m done or /retry`, cols)));
|
|
4783
|
+
}
|
|
4698
4784
|
if (task.isolation?.state === "integrated" && task.isolation.repairedFiles?.length) {
|
|
4699
|
-
lines.push(style.grey(truncate(`${bodyPad}\u21BB landed after repairing conflict in ${(0,
|
|
4785
|
+
lines.push(style.grey(truncate(`${bodyPad}\u21BB landed after repairing conflict in ${(0, import_core23.capConflictFiles)(task.isolation.repairedFiles)}`, cols)));
|
|
4700
4786
|
}
|
|
4701
4787
|
let editorLine;
|
|
4702
4788
|
if (expanded) {
|
|
@@ -4709,8 +4795,14 @@ function taskLines(state, row, index, cols) {
|
|
|
4709
4795
|
lines.push(...taskText("Worktree", task.isolation.worktree, cols, bodyPad));
|
|
4710
4796
|
}
|
|
4711
4797
|
}
|
|
4798
|
+
if (task.ops) {
|
|
4799
|
+
lines.push(...taskText("Ops", "Runs in your checkout, not a worktree, once the work it depends on is merged. O makes it a change task.", cols, bodyPad));
|
|
4800
|
+
}
|
|
4801
|
+
if (task.forcedPastGate?.length) {
|
|
4802
|
+
lines.push(...taskText("Forced", `Started before the work of ${task.forcedPastGate.join(", ")} was merged into your branch.`, cols, bodyPad));
|
|
4803
|
+
}
|
|
4712
4804
|
if (modeInfo?.autonomous) {
|
|
4713
|
-
lines.push(...taskText("Autonomy", "Runs without permission prompts.
|
|
4805
|
+
lines.push(...taskText("Autonomy", "Runs without permission prompts (Full auto). Change the level with /auto.", cols, bodyPad));
|
|
4714
4806
|
}
|
|
4715
4807
|
const editableSeed = task.prompt ?? task.description ?? task.title;
|
|
4716
4808
|
if (task.description && task.description !== task.title && task.description !== editableSeed) {
|
|
@@ -4784,7 +4876,7 @@ function helpScrollMax(state) {
|
|
|
4784
4876
|
}
|
|
4785
4877
|
|
|
4786
4878
|
// src/tui/render.ts
|
|
4787
|
-
var
|
|
4879
|
+
var import_core24 = require("@ordewell/core");
|
|
4788
4880
|
function render(state) {
|
|
4789
4881
|
const { rows, cols } = state;
|
|
4790
4882
|
const rowsForBody = bodyRows(state);
|
|
@@ -4879,9 +4971,9 @@ function renderSkills(state, cols) {
|
|
|
4879
4971
|
const badges = SKILL_IDS.map(
|
|
4880
4972
|
(id) => state.skills[id] ? style.green(`\u25CF ${id}`) : style.grey(`\u25CB ${id}`)
|
|
4881
4973
|
);
|
|
4882
|
-
const auto = state.autonomous ? style.yellow("\u25CF auto") : style.
|
|
4883
|
-
const
|
|
4884
|
-
return truncate([...badges, auto, ...
|
|
4974
|
+
const auto = state.autonomous ? style.yellow("\u25CF Full auto") : style.green("\u25CF Auto");
|
|
4975
|
+
const terminal = state.runnerTransport === "terminal" ? [style.yellow("\u25CF terminal")] : [];
|
|
4976
|
+
return truncate([...badges, auto, ...terminal].join(" "), cols);
|
|
4885
4977
|
}
|
|
4886
4978
|
function scrolledBackMark(state) {
|
|
4887
4979
|
return state.focus !== "plan" && state.scroll > 0 ? "\u2191 scrolled back \xB7 pgdn for live" : "";
|
|
@@ -5039,7 +5131,7 @@ function renderOverlay(state, rows, cols) {
|
|
|
5039
5131
|
return renderPicker(overlay.picker, rows, cols);
|
|
5040
5132
|
}
|
|
5041
5133
|
function renderHandoff(state, overlay, rows, cols) {
|
|
5042
|
-
const handoff = state
|
|
5134
|
+
const handoff = currentHandoff(state);
|
|
5043
5135
|
if (!handoff) return frame("Handoff", [style.grey("Nothing to hand off.")], rows, cols);
|
|
5044
5136
|
if (overlay.diff) {
|
|
5045
5137
|
const room = diffRoom(rows);
|
|
@@ -5049,7 +5141,7 @@ function renderHandoff(state, overlay, rows, cols) {
|
|
|
5049
5141
|
return frame(`Diff \u2014 ${handoffBranch(handoff)}`, [...shown, "", style.grey(`${more}enter or esc goes back`)], rows, cols);
|
|
5050
5142
|
}
|
|
5051
5143
|
const landed = handoff.landed.length === 0 ? [style.grey("Nothing landed on it.")] : handoff.landed.map((task) => {
|
|
5052
|
-
const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0,
|
|
5144
|
+
const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0, import_core24.capConflictFiles)(task.repairedFiles)})`) : "";
|
|
5053
5145
|
return ` ${style.green("\u2713")} #${task.order} ${task.title}${repaired}`;
|
|
5054
5146
|
});
|
|
5055
5147
|
const actions = handoffActions(handoff).map((action, index) => {
|
|
@@ -5139,7 +5231,7 @@ function frame(title, body, rows, cols) {
|
|
|
5139
5231
|
}
|
|
5140
5232
|
|
|
5141
5233
|
// src/tui/reducer.ts
|
|
5142
|
-
var
|
|
5234
|
+
var import_core30 = require("@ordewell/core");
|
|
5143
5235
|
|
|
5144
5236
|
// src/tui/reducers/shared.ts
|
|
5145
5237
|
function step(state, effects = []) {
|
|
@@ -5188,10 +5280,10 @@ function handleApprovalKey(state, overlay, key) {
|
|
|
5188
5280
|
}
|
|
5189
5281
|
|
|
5190
5282
|
// src/tui/reducers/commands.ts
|
|
5191
|
-
var
|
|
5283
|
+
var import_core28 = require("@ordewell/core");
|
|
5192
5284
|
|
|
5193
5285
|
// src/tui/reducers/pickers.ts
|
|
5194
|
-
var
|
|
5286
|
+
var import_core25 = require("@ordewell/core");
|
|
5195
5287
|
var import_plan_utils2 = require("@ordewell/core/plan-utils");
|
|
5196
5288
|
var DEFAULT_EFFORT = "__runner_default__";
|
|
5197
5289
|
function picker(title, items, action, extra = {}) {
|
|
@@ -5210,11 +5302,11 @@ function modelDetail(m) {
|
|
|
5210
5302
|
function providerErrorHint(state) {
|
|
5211
5303
|
const failed = Object.keys(state.providerErrors ?? {});
|
|
5212
5304
|
if (failed.length === 0) return void 0;
|
|
5213
|
-
const names = failed.map((p) =>
|
|
5305
|
+
const names = failed.map((p) => import_core25.ALL_PROVIDERS[p]?.label ?? p);
|
|
5214
5306
|
return `\u26A0 Unavailable (key or fetch failed): ${names.join(", ")} \u2014 showing working providers only.`;
|
|
5215
5307
|
}
|
|
5216
5308
|
function plannerModelItems(state) {
|
|
5217
|
-
const runner = (0,
|
|
5309
|
+
const runner = (0, import_core25.runnerForProvider)(state.plannerProvider);
|
|
5218
5310
|
if (!runner) return state.orchestratorModels.map((m) => ({ id: m.id, label: m.label, detail: modelDetail(m) }));
|
|
5219
5311
|
const items = state.models.filter((m) => m.runners?.includes(runner)).map((m) => ({
|
|
5220
5312
|
id: m.id,
|
|
@@ -5228,7 +5320,7 @@ function plannerModelItems(state) {
|
|
|
5228
5320
|
return items.length > 0 ? items : [{ id: "", label: `No ${runner} models discovered yet`, detail: "run /refresh", disabled: true }];
|
|
5229
5321
|
}
|
|
5230
5322
|
function plannerEffortItems(state) {
|
|
5231
|
-
const runner = (0,
|
|
5323
|
+
const runner = (0, import_core25.runnerForProvider)(state.plannerProvider);
|
|
5232
5324
|
if (!runner) {
|
|
5233
5325
|
return [{ id: "", label: "Thinking effort applies to a coding-agent planner", detail: "switch with /planner", disabled: true }];
|
|
5234
5326
|
}
|
|
@@ -5337,21 +5429,21 @@ function pickerItemsFor(state, action) {
|
|
|
5337
5429
|
}
|
|
5338
5430
|
function plannerItems(state) {
|
|
5339
5431
|
const installed = new Set(state.runners.map((r) => r.id));
|
|
5340
|
-
const agents =
|
|
5341
|
-
const runner = (0,
|
|
5432
|
+
const agents = import_core25.CLI_PROVIDERS.map((id) => {
|
|
5433
|
+
const runner = (0, import_core25.runnerForProvider)(id);
|
|
5342
5434
|
const ready = installed.has(runner);
|
|
5343
5435
|
return {
|
|
5344
5436
|
id,
|
|
5345
|
-
label:
|
|
5437
|
+
label: import_core25.ALL_PROVIDERS[id].label,
|
|
5346
5438
|
detail: ready ? "coding agent \xB7 no API key needed" : "CLI not installed or not on PATH",
|
|
5347
5439
|
selected: state.plannerProvider === id,
|
|
5348
5440
|
disabled: !ready
|
|
5349
5441
|
};
|
|
5350
5442
|
});
|
|
5351
|
-
const vendors =
|
|
5443
|
+
const vendors = import_core25.PROVIDER_PRIORITY.filter((id) => state.configuredProviders.includes(id)).map((id) => ({
|
|
5352
5444
|
id,
|
|
5353
|
-
label:
|
|
5354
|
-
detail:
|
|
5445
|
+
label: import_core25.ALL_PROVIDERS[id].label,
|
|
5446
|
+
detail: import_core25.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
5355
5447
|
selected: state.plannerProvider === id
|
|
5356
5448
|
}));
|
|
5357
5449
|
return vendors.length > 0 ? [...agents, ...vendors] : [...agents, { id: "", label: "No API providers configured", detail: "add a key with /key", disabled: true }];
|
|
@@ -5433,13 +5525,13 @@ function handleMouseSelect(state, key) {
|
|
|
5433
5525
|
}
|
|
5434
5526
|
|
|
5435
5527
|
// src/tui/reducers/taskView.ts
|
|
5436
|
-
var
|
|
5528
|
+
var import_core26 = require("@ordewell/core");
|
|
5437
5529
|
function openTaskView(state, sessionId, taskId) {
|
|
5438
5530
|
const task = findTask(state.tasks, taskId);
|
|
5439
5531
|
if (!task) return step(say(state, "system", `No task ${taskId} in this plan.`));
|
|
5440
5532
|
const taskView = {
|
|
5441
5533
|
taskId,
|
|
5442
|
-
view:
|
|
5534
|
+
view: import_core26.EMPTY_TASK_LOG,
|
|
5443
5535
|
attempts: [],
|
|
5444
5536
|
attempt: 0,
|
|
5445
5537
|
pending: [],
|
|
@@ -5472,13 +5564,13 @@ function applyLive(tv, attempt, events) {
|
|
|
5472
5564
|
}
|
|
5473
5565
|
next = {
|
|
5474
5566
|
...next,
|
|
5475
|
-
view:
|
|
5567
|
+
view: import_core26.EMPTY_TASK_LOG,
|
|
5476
5568
|
attempt,
|
|
5477
5569
|
attempts: next.attempts.includes(attempt) ? next.attempts : [...next.attempts, attempt]
|
|
5478
5570
|
};
|
|
5479
5571
|
}
|
|
5480
5572
|
if (attempt < next.attempt) return next;
|
|
5481
|
-
return clampQueueIndex({ ...next, view: events.reduce(
|
|
5573
|
+
return clampQueueIndex({ ...next, view: events.reduce(import_core26.reduceTaskLog, next.view) });
|
|
5482
5574
|
}
|
|
5483
5575
|
function taskLogAbandoned(state) {
|
|
5484
5576
|
const tv = state.taskView;
|
|
@@ -5498,15 +5590,15 @@ function taskLogArrived(state, action) {
|
|
|
5498
5590
|
function announceApprovals(state, action) {
|
|
5499
5591
|
if (state.taskView?.taskId === action.taskId) return state;
|
|
5500
5592
|
const task = findTask(state.tasks, action.taskId);
|
|
5501
|
-
const spoken = action.events.reduce((s, event) => event.type === "approval_requested" ? say(s, "system", `\xB7 Task ${task?.order ?? "?"} waits for approval: ${(0,
|
|
5502
|
-
return
|
|
5593
|
+
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);
|
|
5594
|
+
return spoken !== state ? { ...spoken, scroll: state.scroll } : spoken;
|
|
5503
5595
|
}
|
|
5504
5596
|
function taskLogLoaded(state, action) {
|
|
5505
5597
|
const tv = state.taskView;
|
|
5506
5598
|
if (!tv || tv.taskId !== action.taskId) return state;
|
|
5507
5599
|
const loaded = action.attempt >= 1 ? {
|
|
5508
5600
|
...tv,
|
|
5509
|
-
view: (0,
|
|
5601
|
+
view: (0, import_core26.replayTaskLog)(action.events),
|
|
5510
5602
|
attempts: action.attempts ?? tv.attempts,
|
|
5511
5603
|
attempt: action.attempt
|
|
5512
5604
|
} : { ...tv, attempts: action.attempts ?? tv.attempts };
|
|
@@ -5595,6 +5687,34 @@ function confirmRemoveTask(state, task) {
|
|
|
5595
5687
|
}
|
|
5596
5688
|
});
|
|
5597
5689
|
}
|
|
5690
|
+
function toggleTaskOps(state, task, subtask, to = !task.ops) {
|
|
5691
|
+
if (!state.sessionId) return fail3(state, "No active plan.");
|
|
5692
|
+
if (task.type !== "ai") return fail3(state, "Only an AI task can be an ops task \u2014 a manual task already runs outside any worktree.");
|
|
5693
|
+
if (subtask) return fail3(state, "A subtask runs with its parent; make the parent an ops task instead.");
|
|
5694
|
+
if (!!task.ops === to) return fail3(state, `#${task.order} is already ${to ? "an ops" : "a change"} task.`);
|
|
5695
|
+
return step(state, [{
|
|
5696
|
+
type: "updateTask",
|
|
5697
|
+
sessionId: state.sessionId,
|
|
5698
|
+
taskId: task.id,
|
|
5699
|
+
changes: { ops: to },
|
|
5700
|
+
message: to ? `Task #${task.order} is an ops task: it runs in your checkout once the work it depends on is merged.` : `Task #${task.order} is a change task: it runs in its own worktree.`
|
|
5701
|
+
}]);
|
|
5702
|
+
}
|
|
5703
|
+
function confirmForceStartPastGate(state, task) {
|
|
5704
|
+
const named = (task.mergeGate ?? []).map((id) => {
|
|
5705
|
+
const dep = findTask(state.tasks, id);
|
|
5706
|
+
return dep ? `#${dep.order} ${dep.title}` : id;
|
|
5707
|
+
});
|
|
5708
|
+
return step({
|
|
5709
|
+
...state,
|
|
5710
|
+
overlay: {
|
|
5711
|
+
kind: "confirm",
|
|
5712
|
+
title: `Force start #${task.order} ${task.title}?`,
|
|
5713
|
+
message: `It waits for Merge all: the work of ${named.join(", ")} is not merged into your branch yet, so it would act without it. Starting it now is kept on the task.`,
|
|
5714
|
+
action: { kind: "force-start-gated", taskId: task.id }
|
|
5715
|
+
}
|
|
5716
|
+
});
|
|
5717
|
+
}
|
|
5598
5718
|
function openTaskDepsPicker(state, task) {
|
|
5599
5719
|
const action = { kind: "set-task-deps", taskId: task.id };
|
|
5600
5720
|
const items = pickerItemsFor(state, action);
|
|
@@ -5751,6 +5871,15 @@ function taskEffortCommand(state, args) {
|
|
|
5751
5871
|
return assignTaskEffort(state, sessionId, task, value);
|
|
5752
5872
|
});
|
|
5753
5873
|
}
|
|
5874
|
+
function taskOpsCommand(state, args) {
|
|
5875
|
+
return taskCommand(state, args[0], (_sessionId, taskId) => {
|
|
5876
|
+
const task = findTask(state.tasks, taskId);
|
|
5877
|
+
const subtask = !state.tasks.some((t) => t.id === taskId);
|
|
5878
|
+
const value = args[1]?.toLowerCase();
|
|
5879
|
+
if (value !== void 0 && value !== "on" && value !== "off") return fail3(state, "Usage: /task-ops <id> [on|off]");
|
|
5880
|
+
return toggleTaskOps(state, task, subtask, value === void 0 ? !task.ops : value === "on");
|
|
5881
|
+
});
|
|
5882
|
+
}
|
|
5754
5883
|
function addTask(state, title) {
|
|
5755
5884
|
if (!state.sessionId) {
|
|
5756
5885
|
return fail3(state, "No active plan to add a task to \u2014 describe a goal first, then /add-task <title>.");
|
|
@@ -5831,9 +5960,11 @@ function handlePlanKey(state, key) {
|
|
|
5831
5960
|
if (key.char === "E") return step(state, [{ type: "execute", sessionId: state.sessionId }]);
|
|
5832
5961
|
if (key.char === "S") return step(state, [{ type: "stopExecution", sessionId: state.sessionId }]);
|
|
5833
5962
|
const action = key.char === "m" ? markAction(task) : PLAN_SHORTCUTS[key.char ?? ""];
|
|
5963
|
+
if (action === "force-start" && task.mergeGate?.length) return confirmForceStartPastGate(state, task);
|
|
5834
5964
|
if (action) {
|
|
5835
5965
|
return step(state, [taskActionEffect(state, state.sessionId, task.id, action)]);
|
|
5836
5966
|
}
|
|
5967
|
+
if (key.char === "O") return toggleTaskOps(state, task, row.parent !== null);
|
|
5837
5968
|
if (key.char === "d") return confirmRemoveTask(state, task);
|
|
5838
5969
|
if (key.char === "t") return openTaskTerminalOrView(state, state.sessionId, task.id);
|
|
5839
5970
|
if (key.char === "x" && task.isolation?.state === "conflict") {
|
|
@@ -5868,9 +5999,9 @@ function commitTaskEdit(state, task, editor) {
|
|
|
5868
5999
|
}
|
|
5869
6000
|
|
|
5870
6001
|
// src/tui/reducers/turnQueue.ts
|
|
5871
|
-
var
|
|
6002
|
+
var import_core27 = require("@ordewell/core");
|
|
5872
6003
|
function drainQueue(state, settled) {
|
|
5873
|
-
const next = (0,
|
|
6004
|
+
const next = (0, import_core27.drainNext)(state.queuedPrompts);
|
|
5874
6005
|
const ended = disarmStop({ ...settled, stopRequested: false });
|
|
5875
6006
|
if (!next) return step(ended);
|
|
5876
6007
|
const spoken = say(ended, "user", next.text);
|
|
@@ -5889,14 +6020,14 @@ function stopPlanning(state, sessionId) {
|
|
|
5889
6020
|
return step(queueToEditor(cutTurn({ ...dismissed, stopArmed: false, stopRequested: true })), [{ type: "cancelPlanning", sessionId }]);
|
|
5890
6021
|
}
|
|
5891
6022
|
function queueToEditor(state) {
|
|
5892
|
-
const all = (0,
|
|
6023
|
+
const all = (0, import_core27.unsendAll)(state.queuedPrompts);
|
|
5893
6024
|
if (!all) return state;
|
|
5894
6025
|
return { ...state, queuedPrompts: all.rest, editor: { ...state.editor, text: all.text, cursor: all.text.length } };
|
|
5895
6026
|
}
|
|
5896
6027
|
function unsendLatest(state) {
|
|
5897
|
-
const latest = (0,
|
|
6028
|
+
const latest = (0, import_core27.unsendLatest)(state.queuedPrompts);
|
|
5898
6029
|
if (!latest) return step(state);
|
|
5899
|
-
const text = (0,
|
|
6030
|
+
const text = (0, import_core27.aheadOfDraft)(latest.text, state.editor.text);
|
|
5900
6031
|
return step({
|
|
5901
6032
|
...state,
|
|
5902
6033
|
queuedPrompts: latest.rest,
|
|
@@ -5913,7 +6044,7 @@ function plannerEscape(state) {
|
|
|
5913
6044
|
}
|
|
5914
6045
|
|
|
5915
6046
|
// src/tui/reducers/commands.ts
|
|
5916
|
-
var KNOWN_PROVIDERS2 = Object.keys(
|
|
6047
|
+
var KNOWN_PROVIDERS2 = Object.keys(import_core28.ALL_PROVIDERS);
|
|
5917
6048
|
function runCommand(state, { name, args }) {
|
|
5918
6049
|
if (!findCommand(name)) {
|
|
5919
6050
|
return fail3(state, `Unknown command: /${name} \u2014 type /help to see what's available.`);
|
|
@@ -6008,6 +6139,8 @@ function runCommand(state, { name, args }) {
|
|
|
6008
6139
|
return taskEffortCommand(state, args);
|
|
6009
6140
|
case "task-mode":
|
|
6010
6141
|
return taskModeCommand(state, args);
|
|
6142
|
+
case "task-ops":
|
|
6143
|
+
return taskOpsCommand(state, args);
|
|
6011
6144
|
case "task-deps":
|
|
6012
6145
|
return taskCommand(
|
|
6013
6146
|
state,
|
|
@@ -6020,11 +6153,11 @@ function runCommand(state, { name, args }) {
|
|
|
6020
6153
|
case "retry":
|
|
6021
6154
|
case "cancel":
|
|
6022
6155
|
case "force-start":
|
|
6023
|
-
return taskCommand(
|
|
6024
|
-
state,
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
);
|
|
6156
|
+
return taskCommand(state, args[0], (sessionId, taskId) => {
|
|
6157
|
+
const task = findTask(state.tasks, taskId);
|
|
6158
|
+
if (name === "force-start" && task?.mergeGate?.length) return confirmForceStartPastGate(state, task);
|
|
6159
|
+
return step(state, [taskActionEffect(state, sessionId, taskId, name)]);
|
|
6160
|
+
});
|
|
6028
6161
|
}
|
|
6029
6162
|
return fail3(state, `/${name} is not wired up yet.`);
|
|
6030
6163
|
}
|
|
@@ -6069,8 +6202,8 @@ function newSession(state) {
|
|
|
6069
6202
|
taskView: null,
|
|
6070
6203
|
tasks: [],
|
|
6071
6204
|
planApproved: false,
|
|
6072
|
-
conversation:
|
|
6073
|
-
turnGate:
|
|
6205
|
+
conversation: import_core28.EMPTY_CONVERSATION,
|
|
6206
|
+
turnGate: import_core28.NO_TURN,
|
|
6074
6207
|
selectedTask: 0,
|
|
6075
6208
|
expandedTaskId: null,
|
|
6076
6209
|
taskEditor: null,
|
|
@@ -6081,14 +6214,14 @@ function newSession(state) {
|
|
|
6081
6214
|
// Prompts belong to the session that raised them; the old planner is gone
|
|
6082
6215
|
// and its pending requests deny on their own timeout.
|
|
6083
6216
|
pendingApprovals: [],
|
|
6084
|
-
queuedPrompts:
|
|
6217
|
+
queuedPrompts: import_core28.EMPTY_HOLD,
|
|
6085
6218
|
stopArmed: false,
|
|
6086
6219
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
6087
6220
|
}, closeEffects);
|
|
6088
6221
|
}
|
|
6089
6222
|
function setModel(state, args) {
|
|
6090
6223
|
if (args[0] === "set" && args[1]) {
|
|
6091
|
-
const runner = (0,
|
|
6224
|
+
const runner = (0, import_core28.runnerForProvider)(state.plannerProvider);
|
|
6092
6225
|
const known = runner ? modelsForRunner(state.models, runner) : [];
|
|
6093
6226
|
if (runner && known.length > 0 && !known.some((m) => m.id === args[1])) {
|
|
6094
6227
|
return fail3(state, `${args[1]} was not discovered for ${runner}.`);
|
|
@@ -6161,16 +6294,16 @@ function setKey(state, args) {
|
|
|
6161
6294
|
}
|
|
6162
6295
|
return step(state, [{ type: "setApiKey", provider, key: args.slice(2).join(" ") }]);
|
|
6163
6296
|
}
|
|
6164
|
-
const items =
|
|
6297
|
+
const items = import_core28.PROVIDER_PRIORITY.map((id) => ({
|
|
6165
6298
|
id,
|
|
6166
|
-
label:
|
|
6167
|
-
detail:
|
|
6299
|
+
label: import_core28.ALL_PROVIDERS[id].label,
|
|
6300
|
+
detail: import_core28.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
6168
6301
|
selected: state.configuredProviders.includes(id)
|
|
6169
6302
|
}));
|
|
6170
6303
|
return step({ ...state, overlay: { kind: "picker", picker: picker("API provider key", items, { kind: "set-key" }) } });
|
|
6171
6304
|
}
|
|
6172
6305
|
function keyPrompt(provider) {
|
|
6173
|
-
const meta =
|
|
6306
|
+
const meta = import_core28.ALL_PROVIDERS[provider];
|
|
6174
6307
|
return {
|
|
6175
6308
|
kind: "prompt",
|
|
6176
6309
|
title: `${meta.label} API key`,
|
|
@@ -6247,18 +6380,19 @@ function runners(state, args) {
|
|
|
6247
6380
|
}
|
|
6248
6381
|
function setMaxParallel(state, arg) {
|
|
6249
6382
|
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.`));
|
|
6250
|
-
const limit = (0,
|
|
6383
|
+
const limit = (0, import_core28.parseMaxParallel)(arg);
|
|
6251
6384
|
if (limit === null) return fail3(state, "Usage: /parallel [<number of tasks, 1 or more>]");
|
|
6252
6385
|
return step({ ...state, maxParallel: limit }, [{ type: "setMaxParallel", limit }]);
|
|
6253
6386
|
}
|
|
6254
6387
|
function setAutonomous(state, arg) {
|
|
6255
|
-
|
|
6256
|
-
|
|
6388
|
+
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.`));
|
|
6389
|
+
const enabled = (0, import_core28.parseAutonomyLevel)(arg);
|
|
6390
|
+
if (enabled === null) return fail3(state, "Usage: /auto [full|auto]");
|
|
6257
6391
|
return step({ ...state, autonomous: enabled }, [{ type: "setAutonomous", enabled }]);
|
|
6258
6392
|
}
|
|
6259
6393
|
function setTransport(state, arg) {
|
|
6260
6394
|
const value = arg?.toLowerCase() ?? (state.runnerTransport === "structured" ? "terminal" : "structured");
|
|
6261
|
-
if (!(0,
|
|
6395
|
+
if (!(0, import_core28.isRunnerTransport)(value)) return fail3(state, "Usage: /transport [terminal|structured]");
|
|
6262
6396
|
return step(state, [{ type: "setTransport", transport: value }]);
|
|
6263
6397
|
}
|
|
6264
6398
|
function setMouseCapture(state, arg) {
|
|
@@ -6344,6 +6478,10 @@ function runConfirm(state, overlay) {
|
|
|
6344
6478
|
if (!state.sessionId) return step(closed);
|
|
6345
6479
|
return step(closed, [{ type: "removeTask", sessionId: state.sessionId, taskId: overlay.action.taskId }]);
|
|
6346
6480
|
}
|
|
6481
|
+
if (overlay.action.kind === "force-start-gated") {
|
|
6482
|
+
if (!state.sessionId) return step(closed);
|
|
6483
|
+
return step(closed, [taskActionEffect(closed, state.sessionId, overlay.action.taskId, "force-start")]);
|
|
6484
|
+
}
|
|
6347
6485
|
if (overlay.action.kind === "merge-run" || overlay.action.kind === "discard-run") {
|
|
6348
6486
|
return confirmedHandoff(state, overlay.action.kind);
|
|
6349
6487
|
}
|
|
@@ -6508,7 +6646,7 @@ function choose(state, picker2, item) {
|
|
|
6508
6646
|
}
|
|
6509
6647
|
|
|
6510
6648
|
// src/tui/reducers/incoming.ts
|
|
6511
|
-
var
|
|
6649
|
+
var import_core29 = require("@ordewell/core");
|
|
6512
6650
|
var planText = (value, fallback = "") => sanitize(String(value ?? fallback));
|
|
6513
6651
|
var planLabel = (value, fallback = "") => planText(value, fallback).replace(/\n+/g, " ");
|
|
6514
6652
|
var asRecord = (value) => typeof value === "object" && value !== null ? value : {};
|
|
@@ -6539,7 +6677,7 @@ function toTaskView2(t, index) {
|
|
|
6539
6677
|
prompt: typeof t.prompt === "string" ? sanitize(t.prompt) : void 0,
|
|
6540
6678
|
type: t.type === "user" ? "user" : "ai",
|
|
6541
6679
|
status: planLabel(t.status, "pending"),
|
|
6542
|
-
awaitingReason: t.status === "awaiting_user" && (0,
|
|
6680
|
+
awaitingReason: t.status === "awaiting_user" && (0, import_core29.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
|
|
6543
6681
|
dependencies: Array.isArray(t.dependencies) ? t.dependencies.map((id) => planLabel(id)) : [],
|
|
6544
6682
|
assignedRunner: typeof t.assignedRunner === "string" ? planLabel(t.assignedRunner) : void 0,
|
|
6545
6683
|
taskMode: typeof t.taskMode === "string" ? planLabel(t.taskMode) : void 0,
|
|
@@ -6549,7 +6687,9 @@ function toTaskView2(t, index) {
|
|
|
6549
6687
|
thinkingEffort: typeof model.thinkingEffort === "string" ? planLabel(model.thinkingEffort) : typeof t.thinkingEffort === "string" ? planLabel(t.thinkingEffort) : void 0,
|
|
6550
6688
|
availableVariants: Array.isArray(model.availableVariants) ? model.availableVariants.map((variant) => planLabel(variant)) : void 0
|
|
6551
6689
|
} : void 0,
|
|
6552
|
-
subtasks: Array.isArray(t.subtasks) ? [...t.subtasks].sort((a, b) => wireOrder(a) - wireOrder(b)).map((s, i) => toTaskView2(asRecord(s), i)) : void 0
|
|
6690
|
+
subtasks: Array.isArray(t.subtasks) ? [...t.subtasks].sort((a, b) => wireOrder(a) - wireOrder(b)).map((s, i) => toTaskView2(asRecord(s), i)) : void 0,
|
|
6691
|
+
...t.ops === true ? { ops: true } : {},
|
|
6692
|
+
...Array.isArray(t.forcedPastGate) && t.forcedPastGate.length > 0 ? { forcedPastGate: t.forcedPastGate.map((title) => planLabel(title)) } : {}
|
|
6553
6693
|
};
|
|
6554
6694
|
}
|
|
6555
6695
|
var enabledFlag = (value) => typeof value?.enabled === "boolean" ? value.enabled : void 0;
|
|
@@ -6570,11 +6710,11 @@ function applySettings(state, settings) {
|
|
|
6570
6710
|
plannerProvider: typeof settings.aiProvider === "string" ? settings.aiProvider : state.plannerProvider,
|
|
6571
6711
|
plannerEffort: typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : state.plannerEffort,
|
|
6572
6712
|
maxParallel: typeof settings.maxParallel === "number" ? settings.maxParallel : state.maxParallel,
|
|
6573
|
-
runnerTransport: (0,
|
|
6713
|
+
runnerTransport: (0, import_core29.isRunnerTransport)(settings.runnerTransport) ? settings.runnerTransport : state.runnerTransport,
|
|
6574
6714
|
allowlist: settings.modelAllowlist && typeof settings.modelAllowlist === "object" ? settings.modelAllowlist : state.allowlist
|
|
6575
6715
|
};
|
|
6576
6716
|
}
|
|
6577
|
-
function runLabel(tasks) {
|
|
6717
|
+
function runLabel(tasks, gate = null) {
|
|
6578
6718
|
const running = tasks.filter(isTaskRunning);
|
|
6579
6719
|
if (running.length > 0) {
|
|
6580
6720
|
const [first] = running;
|
|
@@ -6582,8 +6722,8 @@ function runLabel(tasks) {
|
|
|
6582
6722
|
return running.length > 1 ? `${name} (+${running.length - 1} more)` : name;
|
|
6583
6723
|
}
|
|
6584
6724
|
const waiting = tasks.filter((t) => t.status === "awaiting_user").length;
|
|
6585
|
-
if (waiting
|
|
6586
|
-
return
|
|
6725
|
+
if (waiting > 0) return waiting === 1 ? "1 task waits for you" : `${waiting} tasks wait for you`;
|
|
6726
|
+
return gate?.paused ? "paused for Merge all \u2014 /handoff merge" : "";
|
|
6587
6727
|
}
|
|
6588
6728
|
var statusText = (text) => sanitize(text).replace(/\s+/g, " ").trim();
|
|
6589
6729
|
function inFlight(blocks) {
|
|
@@ -6612,7 +6752,18 @@ function followSession(state, message) {
|
|
|
6612
6752
|
}
|
|
6613
6753
|
|
|
6614
6754
|
// src/tui/reducer.ts
|
|
6755
|
+
function holdReadingPlace(before, after) {
|
|
6756
|
+
if (before.scroll <= 0 || after.scroll !== before.scroll) return after;
|
|
6757
|
+
if (before.taskView?.taskId !== after.taskView?.taskId) return after;
|
|
6758
|
+
const grown = chatScrollMax(after) - chatScrollMax(before);
|
|
6759
|
+
return grown > 0 ? { ...after, scroll: clamp(after.scroll + grown, chatScrollMax(after)) } : after;
|
|
6760
|
+
}
|
|
6615
6761
|
function reduce(state, action) {
|
|
6762
|
+
const result = reduceAction(state, action);
|
|
6763
|
+
if (action.type !== "sessionMessage" && action.type !== "taskLog") return result;
|
|
6764
|
+
return { ...result, state: holdReadingPlace(state, result.state) };
|
|
6765
|
+
}
|
|
6766
|
+
function reduceAction(state, action) {
|
|
6616
6767
|
switch (action.type) {
|
|
6617
6768
|
case "key":
|
|
6618
6769
|
return handleKey2(state, action.key);
|
|
@@ -6630,13 +6781,13 @@ function reduce(state, action) {
|
|
|
6630
6781
|
goal: "",
|
|
6631
6782
|
taskView: null,
|
|
6632
6783
|
pendingApprovals: [],
|
|
6633
|
-
queuedPrompts:
|
|
6784
|
+
queuedPrompts: import_core30.EMPTY_HOLD,
|
|
6634
6785
|
stopArmed: false,
|
|
6635
6786
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
6636
6787
|
});
|
|
6637
6788
|
case "chatRestored":
|
|
6638
6789
|
if (stale(state, action.sessionId)) return step(state);
|
|
6639
|
-
return step({ ...state, conversation: (0,
|
|
6790
|
+
return step({ ...state, conversation: (0, import_core30.fromTranscript)(action.history, action.researchLog, action.plannerUsage), scroll: 0 });
|
|
6640
6791
|
case "planUpdated": {
|
|
6641
6792
|
if (stale(state, action.sessionId)) return step(state);
|
|
6642
6793
|
const isolation = isolationOfPlan(action.plan);
|
|
@@ -6671,6 +6822,9 @@ function reduce(state, action) {
|
|
|
6671
6822
|
case "taskLog":
|
|
6672
6823
|
if (stale(state, action.sessionId)) return step(state);
|
|
6673
6824
|
return step(announceApprovals(taskLogArrived(state, action), action));
|
|
6825
|
+
case "taskViewRequested":
|
|
6826
|
+
if (stale(state, action.sessionId)) return step(state);
|
|
6827
|
+
return openTaskView(state, action.sessionId, action.taskId);
|
|
6674
6828
|
case "taskLogLoaded":
|
|
6675
6829
|
if (stale(state, action.sessionId)) return step(state);
|
|
6676
6830
|
return step(taskLogLoaded(state, action));
|
|
@@ -6682,14 +6836,14 @@ function reduce(state, action) {
|
|
|
6682
6836
|
const existing = findTask(state.tasks, action.taskId);
|
|
6683
6837
|
if (existing && isTaskRunning(existing)) return step(state);
|
|
6684
6838
|
const tasks = state.tasks.map((t) => t.id === action.taskId && !isTaskRunning(t) ? { ...t, status: "in_progress" } : t);
|
|
6685
|
-
const spoken = say(state, "system", (0,
|
|
6839
|
+
const spoken = say(state, "system", (0, import_core30.taskStartedNotice)(action.title, action.runner));
|
|
6686
6840
|
return step({ ...spoken, tasks, status: "executing", busyLabel: runLabel(tasks) });
|
|
6687
6841
|
}
|
|
6688
6842
|
case "taskStatus": {
|
|
6689
6843
|
if (stale(state, action.sessionId) || !state.tasks.some((t) => t.id === action.taskId)) return step(state);
|
|
6690
6844
|
const tasks = state.tasks.map((t) => t.id === action.taskId ? { ...t, status: action.status, awaitingReason: void 0 } : t);
|
|
6691
6845
|
const status = runStatus(state, tasks);
|
|
6692
|
-
return step({ ...state, status, tasks, busyLabel: status === "executing" ? runLabel(tasks) : state.busyLabel });
|
|
6846
|
+
return step({ ...state, status, tasks, busyLabel: status === "executing" ? runLabel(tasks, state.gate) : state.busyLabel });
|
|
6693
6847
|
}
|
|
6694
6848
|
case "tasksStatus": {
|
|
6695
6849
|
if (stale(state, action.sessionId)) return step(state);
|
|
@@ -6704,15 +6858,20 @@ function reduce(state, action) {
|
|
|
6704
6858
|
const awaitingReason = update.awaitingReason;
|
|
6705
6859
|
const continuable = update.continuable === true;
|
|
6706
6860
|
const awaitingApproval = update.awaitingApproval;
|
|
6707
|
-
|
|
6861
|
+
const mergeGate = update.mergeGate;
|
|
6862
|
+
const forcedPastGate = update.forcedPastGate;
|
|
6863
|
+
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 || !sameList(mergeGate, t.mergeGate) || !sameList(forcedPastGate, t.forcedPastGate)) {
|
|
6708
6864
|
changed = true;
|
|
6709
|
-
return { ...t, status: update.status, idleSince, isolation, transport, awaitingReason, continuable, awaitingApproval };
|
|
6865
|
+
return { ...t, status: update.status, idleSince, isolation, transport, awaitingReason, continuable, awaitingApproval, mergeGate, forcedPastGate };
|
|
6710
6866
|
}
|
|
6711
6867
|
return t;
|
|
6712
6868
|
});
|
|
6713
|
-
|
|
6714
|
-
const
|
|
6715
|
-
|
|
6869
|
+
const gate = action.gate === void 0 ? state.gate : action.gate;
|
|
6870
|
+
const gateChanged = !sameGate(gate, state.gate);
|
|
6871
|
+
if (!changed && !gateChanged) return step(state);
|
|
6872
|
+
const next = { ...state, tasks, gate };
|
|
6873
|
+
const status = runStatus(next, tasks);
|
|
6874
|
+
return step({ ...next, status, busyLabel: status === "executing" ? runLabel(tasks, gate) : state.busyLabel });
|
|
6716
6875
|
}
|
|
6717
6876
|
case "queueReady": {
|
|
6718
6877
|
if (stale(state, action.sessionId)) return step(state);
|
|
@@ -6786,7 +6945,7 @@ function reduce(state, action) {
|
|
|
6786
6945
|
busyLabel: "",
|
|
6787
6946
|
planApproved: false,
|
|
6788
6947
|
pendingApprovals: [],
|
|
6789
|
-
queuedPrompts:
|
|
6948
|
+
queuedPrompts: import_core30.EMPTY_HOLD,
|
|
6790
6949
|
stopArmed: false,
|
|
6791
6950
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
6792
6951
|
});
|
|
@@ -6845,9 +7004,16 @@ function reduce(state, action) {
|
|
|
6845
7004
|
function sameTransport(a, b) {
|
|
6846
7005
|
return a?.kind === b?.kind && a?.fallback === b?.fallback;
|
|
6847
7006
|
}
|
|
7007
|
+
function sameList(a, b) {
|
|
7008
|
+
return (a ?? []).join("\0") === (b ?? []).join("\0");
|
|
7009
|
+
}
|
|
7010
|
+
function sameGate(a, b) {
|
|
7011
|
+
if (!a || !b) return a === b;
|
|
7012
|
+
return a.paused === b.paused && JSON.stringify(a.handoff) === JSON.stringify(b.handoff);
|
|
7013
|
+
}
|
|
6848
7014
|
function runStatus(state, tasks) {
|
|
6849
7015
|
if (tasks.some(isTaskRunning)) return "executing";
|
|
6850
|
-
if (tasks.some((t) => t.status === "awaiting_user")) return "executing";
|
|
7016
|
+
if (tasks.some((t) => t.status === "awaiting_user") || state.gate?.paused) return "executing";
|
|
6851
7017
|
return plannerInFlight(state) ? state.status : "idle";
|
|
6852
7018
|
}
|
|
6853
7019
|
function interrupt(state) {
|
|
@@ -6915,7 +7081,7 @@ function submit(state) {
|
|
|
6915
7081
|
return step(cleared, [{ type: "sendTaskMessage", sessionId: state.sessionId, taskId, text }]);
|
|
6916
7082
|
}
|
|
6917
7083
|
if (plannerInFlight(state)) {
|
|
6918
|
-
return step({ ...cleared, queuedPrompts: (0,
|
|
7084
|
+
return step({ ...cleared, queuedPrompts: (0, import_core30.holdPrompt)(state.queuedPrompts, text) });
|
|
6919
7085
|
}
|
|
6920
7086
|
const spoken = say(cleared, "user", text);
|
|
6921
7087
|
const effect = state.sessionId ? { type: "sendMessage", sessionId: state.sessionId, message: text } : { type: "startConversation", goal: text };
|
|
@@ -6975,10 +7141,10 @@ function createApp(deps) {
|
|
|
6975
7141
|
|
|
6976
7142
|
// src/tui/effects.ts
|
|
6977
7143
|
var import_child_process4 = require("child_process");
|
|
6978
|
-
var
|
|
7144
|
+
var import_core32 = require("@ordewell/core");
|
|
6979
7145
|
|
|
6980
7146
|
// src/tui/inbound.ts
|
|
6981
|
-
var
|
|
7147
|
+
var import_core31 = require("@ordewell/core");
|
|
6982
7148
|
var CONVERSATION_TYPES = /* @__PURE__ */ new Set([
|
|
6983
7149
|
"planner_message",
|
|
6984
7150
|
"planner_turn_started",
|
|
@@ -7193,12 +7359,15 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
7193
7359
|
idleSince: task.idleSince ?? null,
|
|
7194
7360
|
isolation: task.isolation,
|
|
7195
7361
|
transport: task.transport,
|
|
7196
|
-
awaitingReason: (0,
|
|
7362
|
+
awaitingReason: (0, import_core31.isAwaitingReason)(task.awaitingReason) ? task.awaitingReason : void 0,
|
|
7197
7363
|
...task.continuable ? { continuable: true } : {},
|
|
7198
|
-
awaitingApproval: typeof task.awaitingApproval === "number" && task.awaitingApproval > 0 ? task.awaitingApproval : void 0
|
|
7364
|
+
awaitingApproval: typeof task.awaitingApproval === "number" && task.awaitingApproval > 0 ? task.awaitingApproval : void 0,
|
|
7365
|
+
...task.mergeGate?.length ? { mergeGate: task.mergeGate.map(String) } : {},
|
|
7366
|
+
...task.forcedPastGate?.length ? { forcedPastGate: task.forcedPastGate.map(String) } : {}
|
|
7199
7367
|
};
|
|
7200
7368
|
}
|
|
7201
|
-
|
|
7369
|
+
const gate = event.gate ? { paused: event.gate.paused === true, handoff: { repos: event.gate.repos, landed: event.gate.landed } } : null;
|
|
7370
|
+
dispatch({ type: "tasksStatus", updates, gate, sessionId });
|
|
7202
7371
|
return;
|
|
7203
7372
|
}
|
|
7204
7373
|
case "task_started":
|
|
@@ -7207,7 +7376,7 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
7207
7376
|
// The extension shows these in its checkpoint panel; here they are
|
|
7208
7377
|
// transcript lines, which is the TUI's equivalent surface.
|
|
7209
7378
|
case "checkpoint":
|
|
7210
|
-
dispatch({ type: "notice", message: `\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0,
|
|
7379
|
+
dispatch({ type: "notice", message: `\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0, import_core31.truncateCheckpointSummary)(event.summary)}` });
|
|
7211
7380
|
return;
|
|
7212
7381
|
case "review_needed":
|
|
7213
7382
|
dispatch({ type: "notice", message: "Plan needs your sign-off \u2014 /approve to continue." });
|
|
@@ -7405,7 +7574,14 @@ async function perform(effect, deps) {
|
|
|
7405
7574
|
}
|
|
7406
7575
|
return;
|
|
7407
7576
|
}
|
|
7577
|
+
// A task with a saved log ran structured, whatever its `transport` says
|
|
7578
|
+
// after a reload; it has no tmux window, so open the log rather than fail.
|
|
7408
7579
|
case "openTaskTerminal": {
|
|
7580
|
+
const saved = await api.getTaskLogAttempts(effect.sessionId, effect.taskId, workspace).catch(() => []);
|
|
7581
|
+
if (saved.length > 0) {
|
|
7582
|
+
dispatch({ type: "taskViewRequested", sessionId: effect.sessionId, taskId: effect.taskId });
|
|
7583
|
+
return;
|
|
7584
|
+
}
|
|
7409
7585
|
const result = await deps.openTerminal(effect.sessionId, effect.taskId);
|
|
7410
7586
|
dispatch(result.ok ? { type: "notice", message: result.message } : { type: "failed", message: result.message });
|
|
7411
7587
|
return;
|
|
@@ -7460,7 +7636,7 @@ async function perform(effect, deps) {
|
|
|
7460
7636
|
case "setTransport": {
|
|
7461
7637
|
const result = await api.sendCommand("transport", { action: effect.transport });
|
|
7462
7638
|
if (result.settings) dispatch({ type: "settingsLoaded", settings: result.settings });
|
|
7463
|
-
dispatch({ type: "notice", message: `Runner transport is ${effect.transport}
|
|
7639
|
+
dispatch({ type: "notice", message: `Runner transport is ${effect.transport} \u2014 it applies from the next run.` });
|
|
7464
7640
|
return;
|
|
7465
7641
|
}
|
|
7466
7642
|
case "setModel":
|
|
@@ -7470,7 +7646,7 @@ async function perform(effect, deps) {
|
|
|
7470
7646
|
dispatch({ type: "notice", message: `Orchestrator model set to ${effect.modelId}.` });
|
|
7471
7647
|
return;
|
|
7472
7648
|
case "setPlanner": {
|
|
7473
|
-
const meta =
|
|
7649
|
+
const meta = import_core32.ALL_PROVIDERS[effect.provider];
|
|
7474
7650
|
if (!meta) throw new Error(`Unknown planner: ${effect.provider}`);
|
|
7475
7651
|
const env = { AI_PROVIDER: effect.provider };
|
|
7476
7652
|
const settings = await api.updateSettings({ env });
|
|
@@ -7482,7 +7658,7 @@ async function perform(effect, deps) {
|
|
|
7482
7658
|
});
|
|
7483
7659
|
dispatch({
|
|
7484
7660
|
type: "notice",
|
|
7485
|
-
message: `${(0,
|
|
7661
|
+
message: `${(0, import_core32.isCliProvider)(effect.provider) ? `Planning with ${meta.label} \u2014 no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
|
|
7486
7662
|
});
|
|
7487
7663
|
await loadModels(deps);
|
|
7488
7664
|
return;
|
|
@@ -7494,7 +7670,7 @@ async function perform(effect, deps) {
|
|
|
7494
7670
|
dispatch({ type: "notice", message: `Planner effort set to ${effect.effort || "the runner default"}.` });
|
|
7495
7671
|
return;
|
|
7496
7672
|
case "setApiKey": {
|
|
7497
|
-
const meta =
|
|
7673
|
+
const meta = import_core32.ALL_PROVIDERS[effect.provider];
|
|
7498
7674
|
if (!meta) throw new Error(`Unknown provider: ${effect.provider}`);
|
|
7499
7675
|
await api.updateSettings({ env: { [meta.apiKeyEnvVar]: effect.key } });
|
|
7500
7676
|
deps.setEnvVar(meta.apiKeyEnvVar, effect.key);
|
|
@@ -7532,7 +7708,7 @@ async function perform(effect, deps) {
|
|
|
7532
7708
|
return;
|
|
7533
7709
|
case "setAutonomous":
|
|
7534
7710
|
deps.setEnvVar("ORDEWELL_AUTONOMOUS_MODE", String(effect.enabled));
|
|
7535
|
-
dispatch({ type: "notice", message: `
|
|
7711
|
+
dispatch({ type: "notice", message: `Autonomy level: ${(0, import_core32.autonomyLevelLabel)(effect.enabled)} for new plans.` });
|
|
7536
7712
|
return;
|
|
7537
7713
|
case "setMouseCapture":
|
|
7538
7714
|
deps.setMouseCapture(effect.enabled);
|
|
@@ -7603,10 +7779,11 @@ async function perform(effect, deps) {
|
|
|
7603
7779
|
return;
|
|
7604
7780
|
// A conflict or a refusal is an answer, not a fault: either way the user's
|
|
7605
7781
|
// tree is exactly as it was, and the words say what to do next. A full
|
|
7606
|
-
// merge leaves nothing to hand over — the daemon has
|
|
7782
|
+
// merge of a settled run leaves nothing to hand over — the daemon has
|
|
7783
|
+
// cleared the run up; one mid-run leaves the run going (ADR-0020).
|
|
7607
7784
|
case "isolationMerge": {
|
|
7608
7785
|
const { ok, message } = mergeOutcome(await api.mergeRun(effect.sessionId), effect.branch, effect.group === true, effect.repaired ?? []);
|
|
7609
|
-
if (ok) dispatch({ type: "runCleared", sessionId: effect.sessionId });
|
|
7786
|
+
if (ok && !effect.midRun) dispatch({ type: "runCleared", sessionId: effect.sessionId });
|
|
7610
7787
|
dispatch({ type: ok ? "notice" : "failed", message });
|
|
7611
7788
|
return;
|
|
7612
7789
|
}
|
|
@@ -7665,7 +7842,7 @@ function osc52(text) {
|
|
|
7665
7842
|
function copySelection(deps, text) {
|
|
7666
7843
|
if (!text) return;
|
|
7667
7844
|
const lines = text.split("\n").length;
|
|
7668
|
-
const command = (0,
|
|
7845
|
+
const command = (0, import_core32.clipboardCopyCommand)(deps.hasBin);
|
|
7669
7846
|
if (command) {
|
|
7670
7847
|
try {
|
|
7671
7848
|
(deps.pipeToClipboard ?? pipeToClipboardDefault)(command, text);
|
|
@@ -8055,7 +8232,7 @@ async function handleTui(subArgs) {
|
|
|
8055
8232
|
}
|
|
8056
8233
|
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
8057
8234
|
try {
|
|
8058
|
-
(0,
|
|
8235
|
+
(0, import_core33.assertWorkspaceExists)(workspace);
|
|
8059
8236
|
} catch {
|
|
8060
8237
|
console.error(
|
|
8061
8238
|
`Workspace "${workspace}" does not exist.
|
|
@@ -8064,7 +8241,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8064
8241
|
);
|
|
8065
8242
|
process.exit(1);
|
|
8066
8243
|
}
|
|
8067
|
-
registerSkillCommands((0,
|
|
8244
|
+
registerSkillCommands((0, import_core33.createSkillsService)(workspace).listSkills());
|
|
8068
8245
|
const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
|
|
8069
8246
|
const targetPort = explicitPort ? resolvePort(subArgs) : await findFreePort();
|
|
8070
8247
|
const { port, owned: ownedAtLaunch } = await ensureDaemonOwned(targetPort, { detached: false });
|
|
@@ -8081,7 +8258,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8081
8258
|
workspace,
|
|
8082
8259
|
port,
|
|
8083
8260
|
dispatch: (action) => app.dispatch(action),
|
|
8084
|
-
newSessionId: () => (0,
|
|
8261
|
+
newSessionId: () => (0, import_core33.mintSessionId)(),
|
|
8085
8262
|
setEnvVar: (key, value) => {
|
|
8086
8263
|
writeEnvVar(findEnvFile(), key, value);
|
|
8087
8264
|
process.env[key] = value;
|
|
@@ -8179,6 +8356,7 @@ var COMMANDS = {
|
|
|
8179
8356
|
"task-effort": handleTaskEffort,
|
|
8180
8357
|
"task-mode": handleTaskMode,
|
|
8181
8358
|
"task-deps": handleTaskDeps,
|
|
8359
|
+
"task-ops": handleTaskOps,
|
|
8182
8360
|
terminal: handleTerminal,
|
|
8183
8361
|
"sessions": handleSessions,
|
|
8184
8362
|
fork: handleFork,
|
|
@@ -8204,7 +8382,7 @@ function cliVersion() {
|
|
|
8204
8382
|
// src/main.ts
|
|
8205
8383
|
var TUI_FLAGS = /* @__PURE__ */ new Set(["--workspace", "--port"]);
|
|
8206
8384
|
async function main() {
|
|
8207
|
-
(0,
|
|
8385
|
+
(0, import_core34.migrateOldConfigDir)();
|
|
8208
8386
|
loadEnvFile();
|
|
8209
8387
|
const argv = process.argv.slice(2);
|
|
8210
8388
|
if (TUI_FLAGS.has(argv[0] ?? "")) argv.unshift("tui");
|