@ordewell/cli 0.6.4 → 0.7.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/dist/main.js +434 -262
- 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_core37 = require("@ordewell/core");
|
|
28
28
|
|
|
29
29
|
// src/utils/env.ts
|
|
30
30
|
var import_fs = require("fs");
|
|
@@ -121,6 +121,7 @@ Tasks (<id> is an order number or a task ID):
|
|
|
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
|
|
124
|
+
ordewell checkpoint <id> approve|reject [reason] Answer the checkpoint a task waits at
|
|
124
125
|
ordewell cancel <id> Kill a running task
|
|
125
126
|
ordewell terminal <id> Open a real terminal attached to a task's runner
|
|
126
127
|
|
|
@@ -139,7 +140,7 @@ Planner, models and runners:
|
|
|
139
140
|
ordewell key [set <p> <key>] Show which providers have a key, or store one
|
|
140
141
|
ordewell runners [<id> on|off] Enable or disable runners (claude-code, opencode, codex)
|
|
141
142
|
ordewell allowlist set|clear|show Limit which models a runner may use
|
|
142
|
-
ordewell auto [full|
|
|
143
|
+
ordewell auto [full|guarded] Autonomy level for new sessions: Full or Guarded
|
|
143
144
|
ordewell transport [terminal|structured] Structured (default) or terminal task transport, from the next run
|
|
144
145
|
ordewell parallel [<n>] How many AI tasks run at once (default 3; applies to a live run)
|
|
145
146
|
ordewell refresh Re-discover runners and model catalogs
|
|
@@ -160,6 +161,12 @@ TUI options:
|
|
|
160
161
|
--workspace /path Workspace directory (default: cwd)
|
|
161
162
|
--port N Target a daemon on port N
|
|
162
163
|
|
|
164
|
+
TUI task view keys (t on a structured task; /help in the TUI lists them all):
|
|
165
|
+
ctrl-s Send now: interrupt the running step and deliver the composer text next
|
|
166
|
+
(with the composer empty, the selected queued message)
|
|
167
|
+
ctrl-n / ctrl-p Select a queued message; ctrl-r removes it
|
|
168
|
+
ctrl-x Interrupt the running turn
|
|
169
|
+
|
|
163
170
|
Plan options:
|
|
164
171
|
--goal "text" Task description (required)
|
|
165
172
|
--runner <id> Runner to use (repeatable; default: all enabled runners)
|
|
@@ -213,7 +220,7 @@ Environment:
|
|
|
213
220
|
ORDEWELL_PLANNER_EFFORT Thinking effort for a coding-agent planner
|
|
214
221
|
ORCHESTRATOR_MODEL Default: deepseek/deepseek-v4-flash
|
|
215
222
|
OPENROUTER_BASE_URL Default: https://openrouter.ai/api/v1
|
|
216
|
-
ORDEWELL_AUTONOMOUS_MODE true: Full
|
|
223
|
+
ORDEWELL_AUTONOMOUS_MODE true: Full, false: Guarded, for new sessions (see "ordewell auto")
|
|
217
224
|
ORDEWELL_PORT Daemon port CLI commands target (default: 3742)
|
|
218
225
|
ORDEWELL_DIRENV false: agents skip the project's direnv .envrc
|
|
219
226
|
ORDEWELL_RESEARCH_ENABLED true (default) or false
|
|
@@ -610,6 +617,25 @@ var ApiClient = class _ApiClient {
|
|
|
610
617
|
}
|
|
611
618
|
return res.data;
|
|
612
619
|
}
|
|
620
|
+
/** Force send: interrupt the task's running turn and deliver `text` next, ahead of anything queued (ADR-0023, F1). */
|
|
621
|
+
async forceSendTaskMessage(sessionId, taskId, text) {
|
|
622
|
+
const res = await this.httpRequest("POST", `/api/plans/${sessionId}/tasks/${taskId}/messages/now`, { text });
|
|
623
|
+
if (res.status !== 200) {
|
|
624
|
+
throw new Error(res.data?.error || "Send now failed");
|
|
625
|
+
}
|
|
626
|
+
return res.data;
|
|
627
|
+
}
|
|
628
|
+
/** Force send a message still queued; `sent` is false once the runner already has it. */
|
|
629
|
+
async forceSendQueuedTaskMessage(sessionId, taskId, messageId) {
|
|
630
|
+
const res = await this.httpRequest(
|
|
631
|
+
"POST",
|
|
632
|
+
`/api/plans/${sessionId}/tasks/${taskId}/messages/${encodeURIComponent(messageId)}/now`
|
|
633
|
+
);
|
|
634
|
+
if (res.status !== 200) {
|
|
635
|
+
throw new Error(res.data?.error || "Send now failed");
|
|
636
|
+
}
|
|
637
|
+
return res.data;
|
|
638
|
+
}
|
|
613
639
|
/** Continue a finished structured task in its saved session, with `text` as its next turn. */
|
|
614
640
|
async continueTask(sessionId, taskId, text) {
|
|
615
641
|
const res = await this.httpRequest("POST", `/api/plans/${sessionId}/tasks/${taskId}/continue`, { text });
|
|
@@ -636,6 +662,26 @@ var ApiClient = class _ApiClient {
|
|
|
636
662
|
}
|
|
637
663
|
return res.data;
|
|
638
664
|
}
|
|
665
|
+
/** Let a task go on from the checkpoint it waits at; refused, with the reason, when none waits. */
|
|
666
|
+
async approveTaskCheckpoint(sessionId, taskId) {
|
|
667
|
+
const res = await this.httpRequest("POST", `/api/plans/${sessionId}/tasks/${taskId}/checkpoint/approve`);
|
|
668
|
+
if (res.status !== 200) {
|
|
669
|
+
throw new Error(res.data?.error || "Approve checkpoint failed");
|
|
670
|
+
}
|
|
671
|
+
return res.data;
|
|
672
|
+
}
|
|
673
|
+
/** Turn a task back at its checkpoint; `reason` is what the agent is told. */
|
|
674
|
+
async rejectTaskCheckpoint(sessionId, taskId, reason) {
|
|
675
|
+
const res = await this.httpRequest(
|
|
676
|
+
"POST",
|
|
677
|
+
`/api/plans/${sessionId}/tasks/${taskId}/checkpoint/reject`,
|
|
678
|
+
reason ? { reason } : {}
|
|
679
|
+
);
|
|
680
|
+
if (res.status !== 200) {
|
|
681
|
+
throw new Error(res.data?.error || "Reject checkpoint failed");
|
|
682
|
+
}
|
|
683
|
+
return res.data;
|
|
684
|
+
}
|
|
639
685
|
async addTask(sessionId, task) {
|
|
640
686
|
const res = await this.httpRequest("POST", `/api/plans/${sessionId}/tasks`, task);
|
|
641
687
|
if (res.status !== 200) {
|
|
@@ -1528,9 +1574,6 @@ function repoResultLines(handoff) {
|
|
|
1528
1574
|
return `${path}: ${n === 0 ? "nothing to merge" : `${n} task${n === 1 ? "" : "s"} landed`}`;
|
|
1529
1575
|
});
|
|
1530
1576
|
}
|
|
1531
|
-
function taskRepoNames(isolation) {
|
|
1532
|
-
return (isolation?.repos ?? []).filter((r) => r !== ".");
|
|
1533
|
-
}
|
|
1534
1577
|
function mergeOutcome(result, branch, group, repaired = []) {
|
|
1535
1578
|
const ok = result.outcome === "merged";
|
|
1536
1579
|
if (!group && result.outcome === "conflict") {
|
|
@@ -1717,7 +1760,7 @@ async function followExecution(api, sessionId, start, onBlocked) {
|
|
|
1717
1760
|
`);
|
|
1718
1761
|
}
|
|
1719
1762
|
if (event.type === "checkpoint") {
|
|
1720
|
-
process.stderr.write(`\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0, import_core9.truncateCheckpointSummary)(event.summary)}
|
|
1763
|
+
process.stderr.write(`\xB7 Checkpoint \u2014 ${event.taskTitle}: ${(0, import_core9.truncateCheckpointSummary)(event.summary)} \u2014 \`ordewell checkpoint <id> approve|reject [reason]\` answers it
|
|
1721
1764
|
`);
|
|
1722
1765
|
}
|
|
1723
1766
|
if (event.type === "status_update" && event.tasks) {
|
|
@@ -2345,6 +2388,7 @@ async function handleTransport(subArgs, api) {
|
|
|
2345
2388
|
}
|
|
2346
2389
|
|
|
2347
2390
|
// src/commands/task-control.ts
|
|
2391
|
+
var import_core15 = require("@ordewell/core");
|
|
2348
2392
|
var PAST_TENSE = {
|
|
2349
2393
|
run: "Task started.",
|
|
2350
2394
|
"force-start": "Task force-started.",
|
|
@@ -2387,7 +2431,7 @@ async function confirmPastGate(subArgs, api, sessionId, taskId, confirm) {
|
|
|
2387
2431
|
if (hasFlag(subArgs, "--yes")) return;
|
|
2388
2432
|
const unmerged = await api.getMergeGate(sessionId, taskId).catch(() => []);
|
|
2389
2433
|
if (unmerged.length === 0) return;
|
|
2390
|
-
const question =
|
|
2434
|
+
const question = `${(0, import_core15.pastGateConfirmation)(unmerged.map((d) => (0, import_core15.titledTaskRef)(d)))} Start it anyway?`;
|
|
2391
2435
|
if (!await confirm(question)) fail("Not started \u2014 nothing was changed. Pass --yes to start it without a prompt.");
|
|
2392
2436
|
}
|
|
2393
2437
|
function makeHandler(action, command = action) {
|
|
@@ -2427,6 +2471,30 @@ async function handleContinue(subArgs, injectedApi) {
|
|
|
2427
2471
|
}
|
|
2428
2472
|
});
|
|
2429
2473
|
}
|
|
2474
|
+
async function handleCheckpoint(subArgs, injectedApi) {
|
|
2475
|
+
const usage = "Usage: ordewell checkpoint <task-id-or-order> approve|reject [reason] [--session-id <id>] [--workspace /path]";
|
|
2476
|
+
const [, answer, ...words] = positionals(subArgs);
|
|
2477
|
+
const reason = words.join(" ").trim();
|
|
2478
|
+
if (answer !== "approve" && answer !== "reject") {
|
|
2479
|
+
console.error(usage);
|
|
2480
|
+
process.exit(1);
|
|
2481
|
+
}
|
|
2482
|
+
if (answer === "approve" && reason) {
|
|
2483
|
+
console.error(`Approving takes no note \u2014 reject with a reason to tell the task something.
|
|
2484
|
+
${usage}`);
|
|
2485
|
+
process.exit(1);
|
|
2486
|
+
}
|
|
2487
|
+
await withResolvedTask(subArgs, usage, injectedApi, async (api, sessionId, taskId) => {
|
|
2488
|
+
try {
|
|
2489
|
+
if (answer === "approve") await api.approveTaskCheckpoint(sessionId, taskId);
|
|
2490
|
+
else await api.rejectTaskCheckpoint(sessionId, taskId, reason || void 0);
|
|
2491
|
+
console.log(answer === "approve" ? "Checkpoint approved." : "Checkpoint rejected.");
|
|
2492
|
+
} catch (err) {
|
|
2493
|
+
console.error(`Failed to ${answer} checkpoint: ${err.message}`);
|
|
2494
|
+
process.exit(1);
|
|
2495
|
+
}
|
|
2496
|
+
});
|
|
2497
|
+
}
|
|
2430
2498
|
var handleRunTask = makeHandler("run", "run-task");
|
|
2431
2499
|
var handleForceStart = makeHandler("force-start");
|
|
2432
2500
|
var handleRetry = makeHandler("retry");
|
|
@@ -2676,24 +2744,24 @@ async function handleRewind(subArgs, injectedApi) {
|
|
|
2676
2744
|
}
|
|
2677
2745
|
|
|
2678
2746
|
// src/tui/transcript.ts
|
|
2679
|
-
var
|
|
2747
|
+
var import_core16 = require("@ordewell/core");
|
|
2680
2748
|
function show(state, { view: conversation, gate: turnGate }) {
|
|
2681
2749
|
const gated = turnGate === state.turnGate ? state : { ...state, turnGate };
|
|
2682
2750
|
if (conversation === state.conversation) return gated;
|
|
2683
2751
|
return { ...gated, conversation };
|
|
2684
2752
|
}
|
|
2685
2753
|
function fold(state, input) {
|
|
2686
|
-
return show(state, { view: (0,
|
|
2754
|
+
return show(state, { view: (0, import_core16.reduceConversation)(state.conversation, input), gate: state.turnGate });
|
|
2687
2755
|
}
|
|
2688
2756
|
function say(state, role, text) {
|
|
2689
2757
|
const said = fold(state, { type: "local_entry", role, text });
|
|
2690
2758
|
return said.conversation.nextId > state.conversation.nextId ? { ...said, scroll: 0 } : said;
|
|
2691
2759
|
}
|
|
2692
2760
|
function hear(state, message) {
|
|
2693
|
-
return show(state, (0,
|
|
2761
|
+
return show(state, (0, import_core16.followTurn)(state.conversation, state.turnGate, message));
|
|
2694
2762
|
}
|
|
2695
2763
|
function cutTurn(state) {
|
|
2696
|
-
return show(state, (0,
|
|
2764
|
+
return show(state, (0, import_core16.stopTurn)(state.conversation, state.turnGate));
|
|
2697
2765
|
}
|
|
2698
2766
|
function wiped(view) {
|
|
2699
2767
|
return { ...view, blocks: view.blocks.filter((b) => b.type === "usage") };
|
|
@@ -2919,12 +2987,12 @@ function runHandoffAction(state, id) {
|
|
|
2919
2987
|
overlay: isRepoGroup(handoff) ? {
|
|
2920
2988
|
kind: "confirm",
|
|
2921
2989
|
title: "Merge all into your branches?",
|
|
2922
|
-
message: `Merge ${handoffBranch(handoff)} into whatever each of ${reposWithWork(handoff).join(", ")} has checked out. Ordewell never does this on its own. It checks every repository first and merges none unless every repository can take it; on git older than 2.38 it goes repository by repository and stops at the first that fails. A merge that conflicts is aborted, and your trees stay as they were. Once every repository has merged, the run's worktrees and branches are removed.${repairedNotice(handoff)}`,
|
|
2990
|
+
message: `Merge ${handoffBranch(handoff)} into whatever each of ${reposWithWork(handoff).join(", ")} has checked out. Ordewell never does this on its own. It checks every repository first and merges none unless every repository can take it; on git older than 2.38 it goes repository by repository and stops at the first that fails. A merge that conflicts is aborted, and your trees stay as they were. Once every repository has merged, the run's worktrees and branches are removed \u2014 unless a task still has work in its worktree that has not landed: then the run stays, and so does that work.${repairedNotice(handoff)}`,
|
|
2923
2991
|
action: { kind: "merge-run" }
|
|
2924
2992
|
} : {
|
|
2925
2993
|
kind: "confirm",
|
|
2926
2994
|
title: "Merge into your branch?",
|
|
2927
|
-
message: `Merge ${handoffBranch(handoff)} into whatever you have checked out. Ordewell never does this on its own. If it conflicts the merge is aborted and your tree stays as it was. Once it has merged, the run's worktrees and branches are removed.${repairedNotice(handoff)}`,
|
|
2995
|
+
message: `Merge ${handoffBranch(handoff)} into whatever you have checked out. Ordewell never does this on its own. If it conflicts the merge is aborted and your tree stays as it was. Once it has merged, the run's worktrees and branches are removed \u2014 unless a task still has work in its worktree that has not landed: then the run stays, and so does that work.${repairedNotice(handoff)}`,
|
|
2928
2996
|
action: { kind: "merge-run" }
|
|
2929
2997
|
}
|
|
2930
2998
|
},
|
|
@@ -3107,7 +3175,7 @@ async function handleHandoff(subArgs, injectedApi, confirm = askYesNo) {
|
|
|
3107
3175
|
}
|
|
3108
3176
|
|
|
3109
3177
|
// src/commands/planner.ts
|
|
3110
|
-
var
|
|
3178
|
+
var import_core17 = require("@ordewell/core");
|
|
3111
3179
|
|
|
3112
3180
|
// src/plannerModelSwitch.ts
|
|
3113
3181
|
function describePlannerSwitch(settings, provider) {
|
|
@@ -3126,7 +3194,7 @@ function rememberedModelFor(settings, provider) {
|
|
|
3126
3194
|
}
|
|
3127
3195
|
|
|
3128
3196
|
// src/commands/planner.ts
|
|
3129
|
-
var KNOWN_PROVIDERS = Object.keys(
|
|
3197
|
+
var KNOWN_PROVIDERS = Object.keys(import_core17.ALL_PROVIDERS);
|
|
3130
3198
|
async function handlePlanner(subArgs, injectedApi) {
|
|
3131
3199
|
const api = await connect(subArgs, injectedApi);
|
|
3132
3200
|
const [wanted] = positionals(subArgs);
|
|
@@ -3141,13 +3209,13 @@ async function handlePlanner(subArgs, injectedApi) {
|
|
|
3141
3209
|
const settings = await persistEnv(api, { AI_PROVIDER: provider });
|
|
3142
3210
|
const recall2 = describePlannerSwitch(settings, provider);
|
|
3143
3211
|
writeEnv({ ORCHESTRATOR_MODEL: recall2.model, ORDEWELL_PLANNER_EFFORT: recall2.effort });
|
|
3144
|
-
const meta =
|
|
3212
|
+
const meta = import_core17.ALL_PROVIDERS[provider];
|
|
3145
3213
|
const catalog = await fetchCatalog(api);
|
|
3146
3214
|
console.log(
|
|
3147
|
-
(0,
|
|
3215
|
+
(0, import_core17.isCliProvider)(provider) ? `Planning with ${meta.label} \u2014 no API key needed, it uses that agent's own subscription.` : `Planner set to ${meta.label}.`
|
|
3148
3216
|
);
|
|
3149
3217
|
console.log(plannerModelLine(recall2));
|
|
3150
|
-
if (!(0,
|
|
3218
|
+
if (!(0, import_core17.isCliProvider)(provider) && !catalog.providers.includes(provider)) {
|
|
3151
3219
|
console.log(` No ${meta.apiKeyEnvVar} configured yet \u2014 set one with \`ordewell key set ${provider} <key>\`.`);
|
|
3152
3220
|
}
|
|
3153
3221
|
}
|
|
@@ -3165,15 +3233,15 @@ async function showPlanners(api) {
|
|
|
3165
3233
|
const [settings, catalog] = await Promise.all([api.getSettings(), fetchCatalog(api)]);
|
|
3166
3234
|
const current = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
3167
3235
|
console.log("\nCoding agents (no API key \u2014 they use their own subscription)");
|
|
3168
|
-
for (const id of
|
|
3169
|
-
const runner = (0,
|
|
3236
|
+
for (const id of import_core17.CLI_PROVIDERS) {
|
|
3237
|
+
const runner = (0, import_core17.runnerForProvider)(id);
|
|
3170
3238
|
const discovered = runner ? catalog.models.some((m) => m.runners?.includes(runner)) : false;
|
|
3171
|
-
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${
|
|
3239
|
+
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${import_core17.ALL_PROVIDERS[id].label}${discovered ? "" : " (not detected)"}`);
|
|
3172
3240
|
}
|
|
3173
3241
|
console.log("\nAPI providers");
|
|
3174
|
-
for (const id of
|
|
3242
|
+
for (const id of import_core17.PROVIDER_PRIORITY) {
|
|
3175
3243
|
const configured = catalog.providers.includes(id);
|
|
3176
|
-
const meta =
|
|
3244
|
+
const meta = import_core17.ALL_PROVIDERS[id];
|
|
3177
3245
|
console.log(` ${marker(id === current)} ${id.padEnd(14)} ${meta.label}${configured ? "" : ` (no ${meta.apiKeyEnvVar})`}`);
|
|
3178
3246
|
}
|
|
3179
3247
|
console.log(`
|
|
@@ -3190,7 +3258,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
|
|
|
3190
3258
|
const provider = typeof settings.aiProvider === "string" ? settings.aiProvider : "";
|
|
3191
3259
|
const modelId = typeof settings.orchestratorModel === "string" ? settings.orchestratorModel : "";
|
|
3192
3260
|
const currentEffort = typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : "";
|
|
3193
|
-
const runner = (0,
|
|
3261
|
+
const runner = (0, import_core17.runnerForProvider)(provider);
|
|
3194
3262
|
if (!runner) {
|
|
3195
3263
|
fail(
|
|
3196
3264
|
"Thinking effort applies to a coding-agent planner.",
|
|
@@ -3230,7 +3298,7 @@ async function handlePlannerEffort(subArgs, injectedApi) {
|
|
|
3230
3298
|
}
|
|
3231
3299
|
|
|
3232
3300
|
// src/commands/parallel.ts
|
|
3233
|
-
var
|
|
3301
|
+
var import_core18 = require("@ordewell/core");
|
|
3234
3302
|
var USAGE5 = "Usage: ordewell parallel [<number of tasks, 1 or more>]";
|
|
3235
3303
|
async function handleParallel(subArgs, injectedApi) {
|
|
3236
3304
|
const [wanted] = positionals(subArgs);
|
|
@@ -3240,14 +3308,14 @@ async function handleParallel(subArgs, injectedApi) {
|
|
|
3240
3308
|
console.log(`Up to ${String(settings.maxParallel ?? "?")} AI tasks run at once. Change with: ordewell parallel <n>`);
|
|
3241
3309
|
return;
|
|
3242
3310
|
}
|
|
3243
|
-
const limit = (0,
|
|
3311
|
+
const limit = (0, import_core18.parseMaxParallel)(wanted);
|
|
3244
3312
|
if (limit === null) fail(`"${wanted}" is not a number of tasks (1 or more).`, USAGE5);
|
|
3245
3313
|
await persistEnv(api, { ORDEWELL_MAX_PARALLEL: String(limit) });
|
|
3246
3314
|
console.log(`Up to ${limit} AI task${limit === 1 ? "" : "s"} now run at once.`);
|
|
3247
3315
|
}
|
|
3248
3316
|
|
|
3249
3317
|
// src/commands/key.ts
|
|
3250
|
-
var
|
|
3318
|
+
var import_core19 = require("@ordewell/core");
|
|
3251
3319
|
var USAGE6 = "Usage: ordewell key [list | set <provider> <key>]";
|
|
3252
3320
|
async function handleKey(subArgs, injectedApi) {
|
|
3253
3321
|
const [action, provider, ...keyParts] = positionals(subArgs);
|
|
@@ -3258,8 +3326,8 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
3258
3326
|
if (action !== "set") {
|
|
3259
3327
|
const catalog = await fetchCatalog(api);
|
|
3260
3328
|
console.log("\nAPI provider keys\n");
|
|
3261
|
-
for (const id2 of
|
|
3262
|
-
const meta2 =
|
|
3329
|
+
for (const id2 of import_core19.PROVIDER_PRIORITY) {
|
|
3330
|
+
const meta2 = import_core19.ALL_PROVIDERS[id2];
|
|
3263
3331
|
const configured = catalog.providers.includes(id2);
|
|
3264
3332
|
console.log(` ${configured ? "\u2713" : " "} ${id2.padEnd(14)} ${meta2.apiKeyEnvVar.padEnd(28)} ${meta2.label}`);
|
|
3265
3333
|
}
|
|
@@ -3269,7 +3337,7 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
3269
3337
|
}
|
|
3270
3338
|
if (!provider) fail(USAGE6);
|
|
3271
3339
|
const id = provider.toLowerCase();
|
|
3272
|
-
const meta =
|
|
3340
|
+
const meta = import_core19.ALL_PROVIDERS[id];
|
|
3273
3341
|
if (!meta) {
|
|
3274
3342
|
fail(`Unknown provider: ${provider}`, "Run `ordewell key` to see the list.");
|
|
3275
3343
|
}
|
|
@@ -3280,7 +3348,7 @@ async function handleKey(subArgs, injectedApi) {
|
|
|
3280
3348
|
}
|
|
3281
3349
|
|
|
3282
3350
|
// src/commands/runners.ts
|
|
3283
|
-
var
|
|
3351
|
+
var import_core20 = require("@ordewell/core");
|
|
3284
3352
|
var USAGE7 = "Usage: ordewell runners [<runner-id> on|off]";
|
|
3285
3353
|
async function handleRunners(subArgs, injectedApi) {
|
|
3286
3354
|
const [runner, toggle] = positionals(subArgs);
|
|
@@ -3305,21 +3373,21 @@ async function handleRunners(subArgs, injectedApi) {
|
|
|
3305
3373
|
await api.setRunnerEnabled(runner, enabled);
|
|
3306
3374
|
console.log(`${runner} ${enabled ? "enabled" : "disabled"}.`);
|
|
3307
3375
|
}
|
|
3308
|
-
var AUTO_USAGE = "Usage: ordewell auto [full|
|
|
3376
|
+
var AUTO_USAGE = "Usage: ordewell auto [full|guarded]";
|
|
3309
3377
|
function handleAuto(subArgs) {
|
|
3310
3378
|
const [arg] = positionals(subArgs);
|
|
3311
3379
|
const currentRaw = process.env.ORDEWELL_AUTONOMOUS_MODE;
|
|
3312
3380
|
const current = currentRaw !== "false" && currentRaw !== "0";
|
|
3313
3381
|
if (!arg) {
|
|
3314
|
-
console.log(`Autonomy level: ${(0,
|
|
3382
|
+
console.log(`Autonomy level: ${(0, import_core20.autonomyLevelLabel)(current)}`);
|
|
3315
3383
|
console.log(` ${AUTO_USAGE}`);
|
|
3316
3384
|
return;
|
|
3317
3385
|
}
|
|
3318
|
-
const enabled = (0,
|
|
3386
|
+
const enabled = (0, import_core20.parseAutonomyLevel)(arg);
|
|
3319
3387
|
if (enabled === null) fail(AUTO_USAGE);
|
|
3320
3388
|
writeEnvVar(findEnvFile(), "ORDEWELL_AUTONOMOUS_MODE", String(enabled));
|
|
3321
3389
|
process.env.ORDEWELL_AUTONOMOUS_MODE = String(enabled);
|
|
3322
|
-
console.log(`Autonomy level: ${(0,
|
|
3390
|
+
console.log(`Autonomy level: ${(0, import_core20.autonomyLevelLabel)(enabled)} for new sessions.`);
|
|
3323
3391
|
}
|
|
3324
3392
|
async function handleRefresh(subArgs, injectedApi) {
|
|
3325
3393
|
const api = await connect(subArgs, injectedApi);
|
|
@@ -3351,7 +3419,7 @@ async function handleApprove(subArgs, injectedApi) {
|
|
|
3351
3419
|
// src/tui/terminalLauncher.ts
|
|
3352
3420
|
var import_child_process3 = require("child_process");
|
|
3353
3421
|
var import_util = require("util");
|
|
3354
|
-
var
|
|
3422
|
+
var import_core21 = require("@ordewell/core");
|
|
3355
3423
|
var execFileAsync = (0, import_util.promisify)(import_child_process3.execFile);
|
|
3356
3424
|
var defaultExecFile = async (command, args) => {
|
|
3357
3425
|
const { stdout, stderr } = await execFileAsync(command, args);
|
|
@@ -3371,16 +3439,16 @@ var defaultWhich = (bin) => {
|
|
|
3371
3439
|
async function openTaskTerminal(port, planSessionId, taskId, deps = {}) {
|
|
3372
3440
|
const platform = deps.platform ?? process.platform;
|
|
3373
3441
|
const env = deps.env ?? process.env;
|
|
3374
|
-
const hasTmuxImpl = deps.hasTmuxImpl ??
|
|
3442
|
+
const hasTmuxImpl = deps.hasTmuxImpl ?? import_core21.hasTmux;
|
|
3375
3443
|
const execFileImpl = deps.execFileImpl ?? defaultExecFile;
|
|
3376
3444
|
const spawnImpl = deps.spawnImpl ?? defaultSpawn;
|
|
3377
3445
|
const which = deps.which ?? defaultWhich;
|
|
3378
3446
|
if (!hasTmuxImpl()) {
|
|
3379
3447
|
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." };
|
|
3380
3448
|
}
|
|
3381
|
-
const session = (0,
|
|
3382
|
-
const socket = (0,
|
|
3383
|
-
const base = (0,
|
|
3449
|
+
const session = (0, import_core21.tmuxSessionName)(port);
|
|
3450
|
+
const socket = (0, import_core21.tmuxSocketName)(port);
|
|
3451
|
+
const base = (0, import_core21.tmuxWindowName)(taskId, planSessionId);
|
|
3384
3452
|
let windows;
|
|
3385
3453
|
try {
|
|
3386
3454
|
const { stdout } = await execFileImpl("tmux", ["-L", socket, "list-windows", "-t", session, "-F", "#{window_name}"]);
|
|
@@ -3529,7 +3597,7 @@ async function handleTaskRunner(subArgs, injectedApi) {
|
|
|
3529
3597
|
const state = await api.getRunners();
|
|
3530
3598
|
if (!runner) {
|
|
3531
3599
|
console.log(`
|
|
3532
|
-
Runner \xB7
|
|
3600
|
+
Runner \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}
|
|
3533
3601
|
`);
|
|
3534
3602
|
for (const r of state.runners) {
|
|
3535
3603
|
console.log(` ${r.id === task.assignedRunner ? "*" : " "} ${r.id.padEnd(14)} ${r.name}${r.enabled ? "" : " (not enabled for planning)"}`);
|
|
@@ -3542,7 +3610,7 @@ Runner \xB7 #${task.order} ${task.title}
|
|
|
3542
3610
|
fail(`Unknown runner: ${runner}`, `This daemon knows: ${state.runners.map((r) => r.id).join(", ")}`);
|
|
3543
3611
|
}
|
|
3544
3612
|
await api.updateTask(sessionId, task.id, { assignedRunner: runner });
|
|
3545
|
-
console.log(`Task
|
|
3613
|
+
console.log(`Task ${(0, import_plan_utils.taskRef)(task)} runner set to ${runner} \u2014 its model, effort and mode were re-picked for it.`);
|
|
3546
3614
|
});
|
|
3547
3615
|
}
|
|
3548
3616
|
var MODEL_USAGE = "Usage: ordewell task-model <task-id-or-order> [<model-id>] [--session-id <id>]";
|
|
@@ -3552,7 +3620,7 @@ async function handleTaskModel(subArgs, injectedApi) {
|
|
|
3552
3620
|
const options = modelsForTask(catalog.models, task);
|
|
3553
3621
|
if (!modelId) {
|
|
3554
3622
|
console.log(`
|
|
3555
|
-
Model \xB7
|
|
3623
|
+
Model \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}${task.assignedRunner ? ` (${task.assignedRunner})` : ""}
|
|
3556
3624
|
`);
|
|
3557
3625
|
if (options.length === 0) console.log(" No models discovered for this runner \u2014 try `ordewell refresh`.");
|
|
3558
3626
|
for (const m of options) {
|
|
@@ -3575,7 +3643,7 @@ Model \xB7 #${task.order} ${task.title}${task.assignedRunner ? ` (${task.assigne
|
|
|
3575
3643
|
assignedModel,
|
|
3576
3644
|
thinkingEffort: assignedModel.thinkingEffort ?? null
|
|
3577
3645
|
});
|
|
3578
|
-
console.log(`Task
|
|
3646
|
+
console.log(`Task ${(0, import_plan_utils.taskRef)(task)} model set to ${model.label}.`);
|
|
3579
3647
|
if (task.assignedModel?.thinkingEffort && !assignedModel.thinkingEffort) {
|
|
3580
3648
|
console.log(` ${model.label} does not expose "${task.assignedModel.thinkingEffort}", so the effort went back to the runner default.`);
|
|
3581
3649
|
}
|
|
@@ -3589,7 +3657,7 @@ async function handleTaskEffort(subArgs, injectedApi) {
|
|
|
3589
3657
|
const current = task.assignedModel?.thinkingEffort;
|
|
3590
3658
|
if (!level) {
|
|
3591
3659
|
console.log(`
|
|
3592
|
-
Thinking effort \xB7
|
|
3660
|
+
Thinking effort \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}
|
|
3593
3661
|
`);
|
|
3594
3662
|
console.log(` ${current ? " " : "*"} default Let the executor choose`);
|
|
3595
3663
|
for (const v of variants) {
|
|
@@ -3614,7 +3682,7 @@ Thinking effort \xB7 #${task.order} ${task.title}
|
|
|
3614
3682
|
assignedModel: task.assignedModel ? { ...task.assignedModel, thinkingEffort } : void 0,
|
|
3615
3683
|
thinkingEffort: thinkingEffort ?? null
|
|
3616
3684
|
});
|
|
3617
|
-
console.log(`Task
|
|
3685
|
+
console.log(`Task ${(0, import_plan_utils.taskRef)(task)} thinking effort set to ${thinkingEffort ?? "runner default"}.`);
|
|
3618
3686
|
});
|
|
3619
3687
|
}
|
|
3620
3688
|
var MODE_USAGE = "Usage: ordewell task-mode <task-id-or-order> [<mode>] [--session-id <id>]";
|
|
@@ -3624,7 +3692,7 @@ async function handleTaskMode(subArgs, injectedApi) {
|
|
|
3624
3692
|
const modes = modesForTask(catalog.modesByRunner, task);
|
|
3625
3693
|
if (!mode) {
|
|
3626
3694
|
console.log(`
|
|
3627
|
-
Mode \xB7
|
|
3695
|
+
Mode \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}${task.assignedRunner ? ` (${task.assignedRunner})` : ""}
|
|
3628
3696
|
`);
|
|
3629
3697
|
if (modes.length === 0) {
|
|
3630
3698
|
console.log(" This task has no runner, or its runner declares no modes.");
|
|
@@ -3643,7 +3711,7 @@ Mode \xB7 #${task.order} ${task.title}${task.assignedRunner ? ` (${task.assigned
|
|
|
3643
3711
|
);
|
|
3644
3712
|
}
|
|
3645
3713
|
await api.updateTask(sessionId, task.id, { taskMode: chosen.id });
|
|
3646
|
-
console.log(`Task
|
|
3714
|
+
console.log(`Task ${(0, import_plan_utils.taskRef)(task)} mode set to ${chosen.label}.`);
|
|
3647
3715
|
});
|
|
3648
3716
|
}
|
|
3649
3717
|
var DEPS_USAGE = "Usage: ordewell task-deps <task-id-or-order> [<id,id,\u2026>|none] [--session-id <id>]";
|
|
@@ -3652,14 +3720,14 @@ async function handleTaskDeps(subArgs, injectedApi) {
|
|
|
3652
3720
|
const candidates = (0, import_plan_utils.dependencyCandidates)(tasks, task.id);
|
|
3653
3721
|
if (!value) {
|
|
3654
3722
|
console.log(`
|
|
3655
|
-
Dependencies \xB7
|
|
3723
|
+
Dependencies \xB7 ${(0, import_plan_utils.titledTaskRef)(task)}
|
|
3656
3724
|
`);
|
|
3657
3725
|
if (candidates.length === 0) {
|
|
3658
|
-
console.log(` Nothing runs before
|
|
3726
|
+
console.log(` Nothing runs before ${(0, import_plan_utils.taskRef)(task)}, so it has no possible dependencies.`);
|
|
3659
3727
|
}
|
|
3660
3728
|
for (const c of candidates) {
|
|
3661
3729
|
const chosen = task.dependencies.includes(c.id);
|
|
3662
|
-
console.log(` ${chosen ? "*" : " "} ${c.id}
|
|
3730
|
+
console.log(` ${chosen ? "*" : " "} ${c.id} ${(0, import_plan_utils.titledTaskRef)(c)}${c.status === "completed" ? " (already completed)" : ""}`);
|
|
3663
3731
|
}
|
|
3664
3732
|
console.log(`
|
|
3665
3733
|
* = current. ${DEPS_USAGE}`);
|
|
@@ -3669,7 +3737,7 @@ Dependencies \xB7 #${task.order} ${task.title}
|
|
|
3669
3737
|
const check = (0, import_plan_utils.canSetDependencies)(tasks, task.id, dependencies);
|
|
3670
3738
|
if (!check.ok) fail(check.error ?? "Invalid dependencies.");
|
|
3671
3739
|
await api.updateTask(sessionId, task.id, { dependencies });
|
|
3672
|
-
console.log(dependencies.length > 0 ? `Task
|
|
3740
|
+
console.log(dependencies.length > 0 ? `Task ${(0, import_plan_utils.taskRef)(task)} now depends on ${dependencies.length} task${dependencies.length === 1 ? "" : "s"}.` : `Task ${(0, import_plan_utils.taskRef)(task)} no longer depends on anything.`);
|
|
3673
3741
|
});
|
|
3674
3742
|
}
|
|
3675
3743
|
var OPS_USAGE = "Usage: ordewell task-ops <task-id-or-order> [on|off] [--session-id <id>]";
|
|
@@ -3677,7 +3745,7 @@ async function handleTaskOps(subArgs, injectedApi) {
|
|
|
3677
3745
|
await withTask(subArgs, OPS_USAGE, injectedApi, async (api, sessionId, task, tasks, value) => {
|
|
3678
3746
|
if (!value) {
|
|
3679
3747
|
console.log(`
|
|
3680
|
-
|
|
3748
|
+
${(0, import_plan_utils.titledTaskRef)(task)} 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"}.`);
|
|
3681
3749
|
console.log(`
|
|
3682
3750
|
${OPS_USAGE}`);
|
|
3683
3751
|
return;
|
|
@@ -3687,12 +3755,12 @@ async function handleTaskOps(subArgs, injectedApi) {
|
|
|
3687
3755
|
if (task.type !== "ai") fail("Only an AI task can be an ops task \u2014 a manual task already runs outside any worktree.");
|
|
3688
3756
|
if (!tasks.some((t) => t.id === task.id)) fail("A subtask runs with its parent; make the parent an ops task instead.");
|
|
3689
3757
|
await api.updateTask(sessionId, task.id, { ops: to === "on" });
|
|
3690
|
-
console.log(to === "on" ? `Task
|
|
3758
|
+
console.log(to === "on" ? `Task ${(0, import_plan_utils.taskRef)(task)} is an ops task: it runs in your checkout once the work it depends on is merged.` : `Task ${(0, import_plan_utils.taskRef)(task)} is a change task: it runs in its own worktree.`);
|
|
3691
3759
|
});
|
|
3692
3760
|
}
|
|
3693
3761
|
|
|
3694
3762
|
// src/tui/index.ts
|
|
3695
|
-
var
|
|
3763
|
+
var import_core36 = require("@ordewell/core");
|
|
3696
3764
|
|
|
3697
3765
|
// src/tui/editor.ts
|
|
3698
3766
|
function emptyEditor() {
|
|
@@ -3841,6 +3909,7 @@ var SLASH_COMMANDS = [
|
|
|
3841
3909
|
{ name: "cancel", usage: "/cancel <id>", description: "Kill a running task", category: "tasks" },
|
|
3842
3910
|
{ name: "force-start", usage: "/force-start <id>", description: "Start a task now, ignoring dependencies", category: "tasks" },
|
|
3843
3911
|
{ name: "continue", usage: "/continue <id> <message>", description: "Continue a finished task in its saved runner session, with your message", category: "tasks" },
|
|
3912
|
+
{ name: "checkpoint", usage: "/checkpoint <id> approve|reject [reason]", description: "Answer the checkpoint a task waits at: approve lets it go on, reject tells it why not", category: "tasks" },
|
|
3844
3913
|
{ name: "terminal", usage: "/terminal <id>", description: "Open a task: its OS terminal, or a structured task's live log", category: "tasks" },
|
|
3845
3914
|
{ name: "task-runner", usage: "/task-runner <id> [runner]", description: "Choose or set a task's executor (re-picks its model and mode)", category: "tasks" },
|
|
3846
3915
|
{ name: "task-model", usage: "/task-model <id> [model]", description: "Choose or set a task's executor model", category: "tasks" },
|
|
@@ -3855,7 +3924,7 @@ var SLASH_COMMANDS = [
|
|
|
3855
3924
|
{ name: "key", usage: "/key [set <provider> <key>]", description: "Configure API provider keys", category: "models" },
|
|
3856
3925
|
{ name: "allowlist", usage: "/allowlist [set <runner> <ids> | clear <runner>]", description: "Limit which models a runner may use", category: "models" },
|
|
3857
3926
|
{ name: "runners", usage: "/runners [<id> on|off]", description: "Enable or disable runners (claude-code, opencode, codex)", category: "models" },
|
|
3858
|
-
{ name: "auto", usage: "/auto [full|
|
|
3927
|
+
{ name: "auto", usage: "/auto [full|guarded]", description: "Autonomy level for new plans: Full or Guarded", category: "models" },
|
|
3859
3928
|
{ name: "transport", usage: "/transport [terminal|structured]", description: "Drive tasks through each runner's protocol (structured) or a terminal, from the next run", category: "models" },
|
|
3860
3929
|
{ name: "parallel", usage: "/parallel [<n>]", description: "How many AI tasks run at once (applies to a live run)", category: "models" },
|
|
3861
3930
|
{ name: "refresh", usage: "/refresh", description: "Re-discover runners and model catalogs", category: "models" },
|
|
@@ -3941,7 +4010,7 @@ function tokenCompletions(token) {
|
|
|
3941
4010
|
}
|
|
3942
4011
|
|
|
3943
4012
|
// src/tui/blocks.ts
|
|
3944
|
-
var
|
|
4013
|
+
var import_core22 = require("@ordewell/core");
|
|
3945
4014
|
|
|
3946
4015
|
// src/tui/markdown.ts
|
|
3947
4016
|
function renderMarkdown(source, cols) {
|
|
@@ -4119,6 +4188,17 @@ function commandMark(block) {
|
|
|
4119
4188
|
if (block.status === "pending") return style.grey("\u25CB");
|
|
4120
4189
|
return (unsettledOutcome(block)?.paint ?? style.green)("\u25CF");
|
|
4121
4190
|
}
|
|
4191
|
+
var PATH_LIKE = /^(?![a-z][a-z0-9+.-]*:\/\/)\S*\/\S*$/i;
|
|
4192
|
+
function truncatePath(path, max) {
|
|
4193
|
+
if (width(path) <= max) return path;
|
|
4194
|
+
let tail = "";
|
|
4195
|
+
for (const char of Array.from(path).reverse()) {
|
|
4196
|
+
if (width(char + tail) > max - 1) break;
|
|
4197
|
+
tail = char + tail;
|
|
4198
|
+
}
|
|
4199
|
+
const slash = tail.indexOf("/");
|
|
4200
|
+
return `\u2026${slash > 0 ? tail.slice(slash) : tail}`;
|
|
4201
|
+
}
|
|
4122
4202
|
function commandHeader(block, cols, detailAll) {
|
|
4123
4203
|
const name = sanitize(block.headline.name);
|
|
4124
4204
|
const keyArg = sanitize(block.headline.keyArg);
|
|
@@ -4127,7 +4207,8 @@ function commandHeader(block, cols, detailAll) {
|
|
|
4127
4207
|
}
|
|
4128
4208
|
const open = `\u25CF ${name}(`;
|
|
4129
4209
|
const room = cols - width(open) - 1;
|
|
4130
|
-
const
|
|
4210
|
+
const cut = PATH_LIKE.test(keyArg) ? truncatePath(keyArg, room) : truncate(keyArg, room);
|
|
4211
|
+
const text = room > 1 ? `${open}${cut})` : truncate(`${open}${keyArg})`, cols);
|
|
4131
4212
|
return [`${commandMark(block)}${text.slice(1)}`];
|
|
4132
4213
|
}
|
|
4133
4214
|
function argumentLines(args) {
|
|
@@ -4149,24 +4230,46 @@ function resultLines(block, room, detailAll) {
|
|
|
4149
4230
|
const fit2 = (line) => detailAll ? wrap(line, room) : [truncate(line, room)];
|
|
4150
4231
|
const outcome = unsettledOutcome(block);
|
|
4151
4232
|
if (outcome) {
|
|
4152
|
-
const [reason, ...rest] = (0,
|
|
4233
|
+
const [reason, ...rest] = (0, import_core22.outputLines)(block.output).map(sanitize);
|
|
4153
4234
|
const status = fit2(reason ? `${outcome.label}: ${reason}` : outcome.label).map(outcome.paint);
|
|
4154
4235
|
return detailAll ? [...status, ...rest.flatMap(fit2).map(style.grey)] : status;
|
|
4155
4236
|
}
|
|
4156
4237
|
if (detailAll) {
|
|
4157
|
-
const lines2 = (0,
|
|
4238
|
+
const lines2 = (0, import_core22.outputLines)(block.output).map(sanitize);
|
|
4158
4239
|
const note = lines2.length > PREVIEW_LINES ? ["\u2026 (ctrl+o to collapse)"] : [];
|
|
4159
4240
|
return lines2.length > 0 ? [...lines2.flatMap(fit2), ...note].map(style.grey) : [style.grey("(no output)")];
|
|
4160
4241
|
}
|
|
4161
|
-
const { lines, hiddenLineCount } = (0,
|
|
4242
|
+
const { lines, hiddenLineCount } = (0, import_core22.outputPreview)(block.output, PREVIEW_LINES);
|
|
4162
4243
|
if (lines.length === 0) return [style.grey("(no output)")];
|
|
4163
4244
|
const more = hiddenLineCount > 0 ? [`\u2026 +${countOf(hiddenLineCount, "line")} (ctrl+o to expand)`] : [];
|
|
4164
4245
|
return [...lines.map(sanitize), ...more].map((line) => style.grey(truncate(line, room)));
|
|
4165
4246
|
}
|
|
4247
|
+
var DIFF_PREVIEW_ROWS = 10;
|
|
4248
|
+
var DIFF_PAINT = {
|
|
4249
|
+
added: style.green,
|
|
4250
|
+
removed: style.red,
|
|
4251
|
+
context: style.grey,
|
|
4252
|
+
gap: style.grey
|
|
4253
|
+
};
|
|
4254
|
+
var DIFF_SIGN = { added: "+", removed: "-", context: " ", gap: "" };
|
|
4255
|
+
function diffLines(block, diff, room, detailAll) {
|
|
4256
|
+
const rows = (0, import_core22.diffRows)(block.output);
|
|
4257
|
+
const gutter = Math.max(0, ...rows.map((row) => String(row.line ?? "").length));
|
|
4258
|
+
const draw = (row) => {
|
|
4259
|
+
const text = row.kind === "gap" ? `${" ".repeat(gutter)} \u22EE` : `${String(row.line ?? "").padStart(gutter)} ${DIFF_SIGN[row.kind]} ${sanitize(row.text)}`;
|
|
4260
|
+
return (detailAll ? wrap(text, room) : [truncate(text, room)]).map(DIFF_PAINT[row.kind]);
|
|
4261
|
+
};
|
|
4262
|
+
const shown = detailAll ? rows : rows.slice(0, DIFF_PREVIEW_ROWS);
|
|
4263
|
+
const hidden = rows.length - shown.length;
|
|
4264
|
+
const more = hidden > 0 ? [style.grey(`\u2026 +${countOf(hidden, "line")} (ctrl+o to expand)`)] : [];
|
|
4265
|
+
return [(0, import_core22.diffSummary)(diff), ...shown.flatMap(draw), ...more];
|
|
4266
|
+
}
|
|
4166
4267
|
function toolLines(block, cols, detailAll) {
|
|
4167
4268
|
const room = Math.max(1, cols - OUTPUT_INDENT.length);
|
|
4168
|
-
const
|
|
4169
|
-
|
|
4269
|
+
const diff = block.diff && block.status === "ok" ? block.diff : null;
|
|
4270
|
+
const args = detailAll && !diff ? argumentLines(block.args).flatMap((line) => wrap(line, room)).map((line) => OUTPUT_INDENT + style.grey(line)) : [];
|
|
4271
|
+
const result = diff ? diffLines(block, diff, room, detailAll) : resultLines(block, room, detailAll);
|
|
4272
|
+
return [...commandHeader(block, cols, detailAll), ...args, ...underHeader(result)];
|
|
4170
4273
|
}
|
|
4171
4274
|
var SUBAGENT_PAINT = {
|
|
4172
4275
|
running: style.yellow,
|
|
@@ -4215,6 +4318,19 @@ function approvalLine(block, cols) {
|
|
|
4215
4318
|
const line = `${mark} ${status} \xB7 ${APPROVAL_KIND[block.kind]}: ${firstLine(block.subject)}${note}`;
|
|
4216
4319
|
return `${paint(mark)}${truncate(line, cols).slice(mark.length)}`;
|
|
4217
4320
|
}
|
|
4321
|
+
function approvalKeysLine(block, cols) {
|
|
4322
|
+
const keys = ["ctrl-y allow", ...block.allowForTask ? ["ctrl-t allow for task"] : [], "ctrl-g deny (composer text is the note)"];
|
|
4323
|
+
return style.grey(truncate(` ${keys.join(" \xB7 ")}`, cols));
|
|
4324
|
+
}
|
|
4325
|
+
function checkpointCardLines(order, question, cols, approvalFirst) {
|
|
4326
|
+
const room = Math.max(1, cols - 2);
|
|
4327
|
+
const keys = approvalFirst ? "answer the tool request above first (ctrl-y / ctrl-g), then ctrl-y approves \xB7 ctrl-g rejects (composer text is the reason)" : "ctrl-y approve \xB7 ctrl-g reject (composer text is the reason)";
|
|
4328
|
+
return [
|
|
4329
|
+
truncate(`${style.accent("\u25C6")} ${style.bold(`Task ${order} asks`)}`, cols),
|
|
4330
|
+
...wrap(sanitize(question).trim(), room).map((line) => ` ${line}`),
|
|
4331
|
+
...wrap(keys, room).map((line) => style.grey(` ${line}`))
|
|
4332
|
+
];
|
|
4333
|
+
}
|
|
4218
4334
|
function planLine(block, cols) {
|
|
4219
4335
|
const count = block.taskCount === void 0 ? "" : ` (${countOf(block.taskCount, "task")})`;
|
|
4220
4336
|
const text = block.status === "building" ? "Building plan\u2026" : `Plan ${block.status}${count}`;
|
|
@@ -4247,11 +4363,13 @@ function drawn(block, cols, detailAll) {
|
|
|
4247
4363
|
drawnBlocks.set(block, { cols, detailAll, lines });
|
|
4248
4364
|
return lines;
|
|
4249
4365
|
}
|
|
4250
|
-
function conversationLines(blocks, cols, detailAll) {
|
|
4366
|
+
function conversationLines(blocks, cols, detailAll, answering) {
|
|
4251
4367
|
const lines = [];
|
|
4252
4368
|
for (const block of blocks) {
|
|
4253
4369
|
if (block.type === "usage") continue;
|
|
4254
|
-
lines.push(...drawn(block, cols, detailAll)
|
|
4370
|
+
lines.push(...drawn(block, cols, detailAll));
|
|
4371
|
+
if (block === answering) lines.push(approvalKeysLine(block, cols));
|
|
4372
|
+
lines.push("");
|
|
4255
4373
|
}
|
|
4256
4374
|
return lines;
|
|
4257
4375
|
}
|
|
@@ -4279,7 +4397,7 @@ function tokenLine(blocks, cols) {
|
|
|
4279
4397
|
}
|
|
4280
4398
|
|
|
4281
4399
|
// src/tui/state.ts
|
|
4282
|
-
var
|
|
4400
|
+
var import_core23 = require("@ordewell/core");
|
|
4283
4401
|
var SKILL_IDS = ["tdd", "verify"];
|
|
4284
4402
|
function noSkills() {
|
|
4285
4403
|
return { tdd: false, verify: false };
|
|
@@ -4291,8 +4409,7 @@ function visibleItems(picker2) {
|
|
|
4291
4409
|
(i) => i.id.toLowerCase().includes(filter) || i.label.toLowerCase().includes(filter) || (i.detail ?? "").toLowerCase().includes(filter)
|
|
4292
4410
|
);
|
|
4293
4411
|
}
|
|
4294
|
-
var
|
|
4295
|
-
var anyTaskRunning = (state) => state.tasks.some(isTaskRunning);
|
|
4412
|
+
var anyTaskRunning = (state) => state.tasks.some(import_core23.isTaskRunning);
|
|
4296
4413
|
var plannerInFlight = (state) => state.status === "planning" || state.status === "researching";
|
|
4297
4414
|
function planRows(state) {
|
|
4298
4415
|
const rows = [];
|
|
@@ -4312,6 +4429,9 @@ function selectedPlanRow(state) {
|
|
|
4312
4429
|
function waitingApproval(tv) {
|
|
4313
4430
|
return tv.view.blocks.find((b) => b.type === "approval" && b.status === "pending" && b.approvalId !== void 0);
|
|
4314
4431
|
}
|
|
4432
|
+
function waitingCheckpoint(task) {
|
|
4433
|
+
return task?.status === "awaiting_user" && task.awaitingReason === "checkpoint" ? task.checkpoint : void 0;
|
|
4434
|
+
}
|
|
4315
4435
|
function continuesTask(task) {
|
|
4316
4436
|
return task?.continuable === true && (task.status === "completed" || task.status === "failed");
|
|
4317
4437
|
}
|
|
@@ -4326,9 +4446,9 @@ function findTask(tasks, id) {
|
|
|
4326
4446
|
function initialState(overrides = {}) {
|
|
4327
4447
|
return {
|
|
4328
4448
|
editor: emptyEditor(),
|
|
4329
|
-
conversation:
|
|
4449
|
+
conversation: import_core23.EMPTY_CONVERSATION,
|
|
4330
4450
|
taskView: null,
|
|
4331
|
-
turnGate:
|
|
4451
|
+
turnGate: import_core23.NO_TURN,
|
|
4332
4452
|
detailAll: false,
|
|
4333
4453
|
status: "idle",
|
|
4334
4454
|
busyLabel: "",
|
|
@@ -4353,7 +4473,7 @@ function initialState(overrides = {}) {
|
|
|
4353
4473
|
orchestratorModel: "",
|
|
4354
4474
|
plannerProvider: "",
|
|
4355
4475
|
plannerEffort: "",
|
|
4356
|
-
maxParallel:
|
|
4476
|
+
maxParallel: import_core23.DEFAULT_MAX_PARALLEL,
|
|
4357
4477
|
configuredProviders: [],
|
|
4358
4478
|
allowlist: {},
|
|
4359
4479
|
autonomous: true,
|
|
@@ -4365,7 +4485,7 @@ function initialState(overrides = {}) {
|
|
|
4365
4485
|
gate: null,
|
|
4366
4486
|
overlay: null,
|
|
4367
4487
|
pendingApprovals: [],
|
|
4368
|
-
queuedPrompts:
|
|
4488
|
+
queuedPrompts: import_core23.EMPTY_HOLD,
|
|
4369
4489
|
stopArmed: false,
|
|
4370
4490
|
stopArmToken: 0,
|
|
4371
4491
|
stopRequested: false,
|
|
@@ -4413,7 +4533,7 @@ function chatEditorRoomFor(state, active) {
|
|
|
4413
4533
|
}
|
|
4414
4534
|
|
|
4415
4535
|
// src/tui/layout.ts
|
|
4416
|
-
var
|
|
4536
|
+
var import_core24 = require("@ordewell/core");
|
|
4417
4537
|
var CHROME_ROWS = 4;
|
|
4418
4538
|
function chatInputWrap(state) {
|
|
4419
4539
|
const active = state.focus === "chat" && !state.overlay;
|
|
@@ -4422,7 +4542,7 @@ function chatInputWrap(state) {
|
|
|
4422
4542
|
}
|
|
4423
4543
|
function footerHints(state) {
|
|
4424
4544
|
const selected = selectedPlanRow(state)?.task;
|
|
4425
|
-
const markHint = selected
|
|
4545
|
+
const markHint = selected && (0, import_core24.markAction)(selected) === "uncomplete" ? "m undone" : "m done";
|
|
4426
4546
|
const resolveHint = selected?.isolation?.state === "conflict" ? ["x resolve conflict"] : [];
|
|
4427
4547
|
const planning = plannerInFlight(state) && !state.taskView;
|
|
4428
4548
|
const escHint = !planning ? null : state.queuedPrompts.length > 0 ? "esc unsend" : "esc \xD72 stop planning";
|
|
@@ -4453,7 +4573,7 @@ function footerHints(state) {
|
|
|
4453
4573
|
"tab chat"
|
|
4454
4574
|
];
|
|
4455
4575
|
}
|
|
4456
|
-
const detailHint = (0,
|
|
4576
|
+
const detailHint = (0, import_core24.hasHiddenDetail)(state.taskView ? state.taskView.view.blocks : state.conversation.blocks) ? [state.detailAll ? "ctrl-o collapse all" : "ctrl-o expand all"] : [];
|
|
4457
4577
|
return [
|
|
4458
4578
|
"/help",
|
|
4459
4579
|
"tab plan",
|
|
@@ -4474,20 +4594,25 @@ function queuedBubble(text, cols) {
|
|
|
4474
4594
|
function taskQueuedRows(state, cols) {
|
|
4475
4595
|
const tv = state.taskView;
|
|
4476
4596
|
if (!tv) return [];
|
|
4477
|
-
return tv.view.queued.flatMap((message, i) => queuedTaskBubble(message
|
|
4597
|
+
return tv.view.queued.flatMap((message, i) => queuedTaskBubble(message, i === tv.queuedIndex, cols));
|
|
4598
|
+
}
|
|
4599
|
+
function queuedTaskTag({ handedOver, forced }, selected) {
|
|
4600
|
+
if (forced) return " \xB7 sending now";
|
|
4601
|
+
if (handedOver) return " \xB7 handed over";
|
|
4602
|
+
return selected ? " \xB7 queued \xB7 ctrl-r removes" : " \xB7 queued";
|
|
4478
4603
|
}
|
|
4479
|
-
function queuedTaskBubble(
|
|
4604
|
+
function queuedTaskBubble(message, selected, cols) {
|
|
4605
|
+
const { text } = message;
|
|
4480
4606
|
const marker2 = selected ? style.accent("\u276F") : style.grey("\u25C7");
|
|
4481
|
-
const tag = selected
|
|
4607
|
+
const tag = queuedTaskTag(message, selected);
|
|
4482
4608
|
const room = Math.max(1, cols - width(`\u276F \u25C7 ${tag}`));
|
|
4483
4609
|
return wrap(text, room).map((line, i) => i === 0 ? truncate(`${marker2} ${selected ? style.bold(line) : line}${style.grey(tag)}`, cols) : truncate(` ${line}`, cols));
|
|
4484
4610
|
}
|
|
4485
4611
|
function taskActivity(task, working) {
|
|
4486
|
-
|
|
4612
|
+
const approvals = (0, import_core24.approvalLabel)(task?.awaitingApproval);
|
|
4613
|
+
if (approvals) return inRow(approvals);
|
|
4487
4614
|
if (working) return "working";
|
|
4488
|
-
if (task?.status === "awaiting_user")
|
|
4489
|
-
return task.awaitingReason ? AWAITING_LABEL[task.awaitingReason] : "waiting for your input";
|
|
4490
|
-
}
|
|
4615
|
+
if (task?.status === "awaiting_user") return inRow((0, import_core24.awaitingLabel)(task) ?? "Waiting for your input");
|
|
4491
4616
|
return task?.status ?? "";
|
|
4492
4617
|
}
|
|
4493
4618
|
function taskHeaderLines(state, tv, cols) {
|
|
@@ -4499,7 +4624,8 @@ function taskHeaderLines(state, tv, cols) {
|
|
|
4499
4624
|
taskActivity(task, tv.view.working)
|
|
4500
4625
|
].filter(Boolean);
|
|
4501
4626
|
const approval = waitingApproval(tv);
|
|
4502
|
-
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"];
|
|
4627
|
+
const hint = approval ? ["ctrl-y allow", ...approval.allowForTask ? ["ctrl-t allow for task"] : [], "ctrl-g deny (composer text is the note)"] : waitingCheckpoint(task) ? ["ctrl-y approve", "ctrl-g reject (composer text is the reason)", "ctrl-x interrupt"] : ["ctrl-r remove queued", "ctrl-x interrupt", "ctrl-s send now"];
|
|
4628
|
+
if (approval && waitingCheckpoint(task)) hint.push("then the checkpoint");
|
|
4503
4629
|
const index = tv.attempts.indexOf(tv.attempt);
|
|
4504
4630
|
if (tv.attempts.length > 1) hint.push(`alt\u2190/\u2192 attempt ${index >= 0 ? index + 1 : 1}/${tv.attempts.length}`);
|
|
4505
4631
|
hint.push("esc back");
|
|
@@ -4532,10 +4658,10 @@ function bodyRows(state) {
|
|
|
4532
4658
|
return Math.max(0, state.rows - CHROME_ROWS - input - footer + 2);
|
|
4533
4659
|
}
|
|
4534
4660
|
var chatBodyMemo = null;
|
|
4535
|
-
function chatBodyLines(blocks, cols, detailAll) {
|
|
4661
|
+
function chatBodyLines(blocks, cols, detailAll, answering) {
|
|
4536
4662
|
const memo = chatBodyMemo;
|
|
4537
4663
|
if (memo && memo.blocks === blocks && memo.cols === cols && memo.detailAll === detailAll) return memo.lines;
|
|
4538
|
-
const lines = conversationLines(blocks, cols, detailAll);
|
|
4664
|
+
const lines = conversationLines(blocks, cols, detailAll, answering);
|
|
4539
4665
|
chatBodyMemo = { blocks, cols, detailAll, lines };
|
|
4540
4666
|
return lines;
|
|
4541
4667
|
}
|
|
@@ -4543,7 +4669,10 @@ function chatLayout(state, rows, cols) {
|
|
|
4543
4669
|
const tv = state.taskView;
|
|
4544
4670
|
const blocks = tv ? tv.view.blocks : state.conversation.blocks;
|
|
4545
4671
|
const welcome = !tv && state.tasks.length === 0;
|
|
4546
|
-
const
|
|
4672
|
+
const checkpoint = tv ? waitingCheckpoint(findTask(state.tasks, tv.taskId)) : void 0;
|
|
4673
|
+
const card = tv && checkpoint ? checkpointCardLines(findTask(state.tasks, tv.taskId)?.order ?? 0, checkpoint, cols, waitingApproval(tv) !== void 0) : [];
|
|
4674
|
+
const answering = chatBodyLines(blocks, cols, state.detailAll, tv ? waitingApproval(tv) : void 0);
|
|
4675
|
+
const body = card.length > 0 ? [...answering, ...card] : answering;
|
|
4547
4676
|
const transcript = !welcome ? body : body.length === 0 ? welcomeLines(state, cols) : [...welcomeLines(state, cols), "", ...body];
|
|
4548
4677
|
const bubbles = tv ? taskQueuedRows(state, cols) : queuedPromptRows(state, cols);
|
|
4549
4678
|
const lines = [...transcript, ...bubbles.length > 0 ? ["", ...bubbles] : []];
|
|
@@ -4558,9 +4687,9 @@ function chatScrollMax(state) {
|
|
|
4558
4687
|
}
|
|
4559
4688
|
function plannerSetup(state) {
|
|
4560
4689
|
const provider = state.plannerProvider;
|
|
4561
|
-
const registration =
|
|
4690
|
+
const registration = import_core24.ALL_PROVIDERS[provider];
|
|
4562
4691
|
if (!registration) return { label: "not set", ready: false };
|
|
4563
|
-
const runner = (0,
|
|
4692
|
+
const runner = (0, import_core24.runnerForProvider)(provider);
|
|
4564
4693
|
const ready = runner ? state.runners.some((r) => r.id === runner) : state.configuredProviders.includes(provider);
|
|
4565
4694
|
return { label: registration.label, ready };
|
|
4566
4695
|
}
|
|
@@ -4647,45 +4776,34 @@ function welcomeLines(state, cols) {
|
|
|
4647
4776
|
return lines;
|
|
4648
4777
|
}
|
|
4649
4778
|
var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4650
|
-
var
|
|
4651
|
-
|
|
4779
|
+
var STATUS_PAINT = {
|
|
4780
|
+
done: style.green,
|
|
4652
4781
|
running: style.yellow,
|
|
4653
|
-
|
|
4782
|
+
quiet: style.cyan,
|
|
4654
4783
|
failed: style.red,
|
|
4655
4784
|
blocked: style.red,
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
skipped: style.grey
|
|
4785
|
+
awaiting: style.yellow,
|
|
4786
|
+
todo: (text) => text
|
|
4659
4787
|
};
|
|
4660
4788
|
var STATUS_ICON = {
|
|
4661
|
-
|
|
4789
|
+
done: "\u2713",
|
|
4662
4790
|
failed: "\u2717",
|
|
4663
4791
|
blocked: "!",
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
approved: "\xB7",
|
|
4668
|
-
pending: "\xB7"
|
|
4792
|
+
awaiting: "?",
|
|
4793
|
+
quiet: "~",
|
|
4794
|
+
todo: "\xB7"
|
|
4669
4795
|
};
|
|
4670
|
-
var
|
|
4671
|
-
input: "waiting for your input",
|
|
4672
|
-
checkpoint: "checkpoint",
|
|
4673
|
-
conflict: "merge conflict",
|
|
4674
|
-
"files-changed": "changed tracked files"
|
|
4675
|
-
};
|
|
4676
|
-
function approvalLabel(count) {
|
|
4677
|
-
return count > 1 ? `waiting for approval (${count})` : "waiting for approval";
|
|
4678
|
-
}
|
|
4679
|
-
var IDLE_ICON = "~";
|
|
4796
|
+
var inRow = (label) => label.charAt(0).toLowerCase() + label.slice(1);
|
|
4680
4797
|
var FIRST_ROW_LINE = 2;
|
|
4681
4798
|
function planLayout(state, rows, cols) {
|
|
4682
4799
|
const done = state.tasks.filter((t) => t.status === "completed").length;
|
|
4683
4800
|
const lines = [style.bold(`Plan ${done}/${state.tasks.length}`), ""];
|
|
4684
4801
|
const rowSpans = [];
|
|
4802
|
+
const orderOf = new Map(state.tasks.map((task) => [task.id, task.order]));
|
|
4685
4803
|
let editorLine;
|
|
4686
4804
|
planRows(state).forEach((row, i) => {
|
|
4687
4805
|
const taskStart = lines.length;
|
|
4688
|
-
const renderedTask = taskLines(state, row, i, cols);
|
|
4806
|
+
const renderedTask = taskLines(state, row, i, cols, orderOf);
|
|
4689
4807
|
if (row.task.id === state.expandedTaskId && renderedTask.editorLine !== void 0) {
|
|
4690
4808
|
editorLine = taskStart + renderedTask.editorLine;
|
|
4691
4809
|
}
|
|
@@ -4741,18 +4859,16 @@ function planScrollExtent(state) {
|
|
|
4741
4859
|
}
|
|
4742
4860
|
return planLayout(state, bodyRows(state), cols);
|
|
4743
4861
|
}
|
|
4744
|
-
function taskLines(state,
|
|
4745
|
-
const { task, parent } =
|
|
4862
|
+
function taskLines(state, planRow, index, cols, orderOf) {
|
|
4863
|
+
const { task, parent } = planRow;
|
|
4864
|
+
const row = (0, import_core24.taskRowView)(task, { parent, orderOf, modes: modesForTask(state.modesByRunner, task) });
|
|
4746
4865
|
const selected = state.focus === "plan" && index === state.selectedTask;
|
|
4747
4866
|
const expanded = state.expandedTaskId === task.id;
|
|
4748
|
-
const
|
|
4749
|
-
const
|
|
4750
|
-
const
|
|
4751
|
-
const rawIcon = idle ? IDLE_ICON : running ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[task.status] ?? "\xB7";
|
|
4752
|
-
const icon = paint(rawIcon);
|
|
4753
|
-
const kind = running ? style.yellow("RUN") : task.type === "user" ? style.yellow("MAN") : task.ops ? style.magenta("OPS") : style.grey(" AI");
|
|
4867
|
+
const rawIcon = row.status === "running" ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[row.status];
|
|
4868
|
+
const icon = STATUS_PAINT[row.status](rawIcon);
|
|
4869
|
+
const kind = row.running ? style.yellow("RUN") : row.kind === "user" ? style.yellow("MAN") : row.kind === "ops" ? style.magenta("OPS") : style.grey(" AI");
|
|
4754
4870
|
const caret = selected ? style.cyan("\u276F") : " ";
|
|
4755
|
-
const orderLabel = parent ?
|
|
4871
|
+
const orderLabel = parent ? row.orderLabel : row.orderLabel.padStart(2);
|
|
4756
4872
|
const indent = parent ? " " : "";
|
|
4757
4873
|
const head = `${indent}${caret} ${icon} ${orderLabel} ${kind} `;
|
|
4758
4874
|
const titleRoom = Math.max(1, cols - width(head));
|
|
@@ -4769,59 +4885,52 @@ function taskLines(state, row, index, cols) {
|
|
|
4769
4885
|
});
|
|
4770
4886
|
const model = task.assignedModel?.modelLabel ?? (task.type === "ai" ? "default model" : "");
|
|
4771
4887
|
const effort = task.type === "ai" ? `effort: ${task.assignedModel?.thinkingEffort ?? "default"}` : "";
|
|
4772
|
-
const
|
|
4773
|
-
const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${modeInfo?.autonomous ? style.yellow(" \u26A1") : ""}` : "";
|
|
4888
|
+
const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${row.autonomous ? style.yellow(" \u26A1") : ""}` : "";
|
|
4774
4889
|
const runner = task.assignedRunner ?? "";
|
|
4775
4890
|
const bodyPad = parent ? " " : " ";
|
|
4776
4891
|
const structured = task.transport?.kind === "structured";
|
|
4777
|
-
const
|
|
4778
|
-
const activity = task.awaitingApproval ? `${approvalLabel(task.awaitingApproval)} \u2014 t opens it` : idle ? structured ? "quiet" : "quiet \u2014 t opens its terminal" : running ? "working" : waiting;
|
|
4892
|
+
const activity = row.approvals ? `${inRow(row.approvals)} \u2014 t opens it` : row.status === "quiet" ? structured ? "quiet" : "quiet \u2014 t opens its terminal" : row.running ? "working" : row.awaiting ? inRow(row.awaiting) : "";
|
|
4779
4893
|
const meta = [activity, runner, structured ? "structured" : "", model].filter(Boolean).join(" \xB7 ");
|
|
4780
4894
|
if (meta) lines.push(style.grey(truncate(`${bodyPad}${meta}`, cols)));
|
|
4781
4895
|
if (task.transport?.fallback) lines.push(style.yellow(truncate(`${bodyPad}terminal: ${task.transport.fallback}`, cols)));
|
|
4782
4896
|
if (effort || mode) lines.push(style.grey(truncate(`${bodyPad}${[effort, mode].filter(Boolean).join(" \xB7 ")}`, cols)));
|
|
4783
|
-
if (
|
|
4784
|
-
const where =
|
|
4785
|
-
const files =
|
|
4897
|
+
if (row.conflict) {
|
|
4898
|
+
const where = row.conflict.repo ? ` in ${row.conflict.repo}` : "";
|
|
4899
|
+
const files = row.conflict.files ? ` (${row.conflict.files})` : "";
|
|
4786
4900
|
lines.push(style.red(truncate(`${bodyPad}\u26A0 merge conflict${where}${files} \u2014 its work is kept on its own branch`, cols)));
|
|
4787
4901
|
}
|
|
4788
|
-
if (
|
|
4789
|
-
const files =
|
|
4790
|
-
const attempt =
|
|
4902
|
+
if (row.repairing) {
|
|
4903
|
+
const files = row.repairing.files ? ` in ${row.repairing.files}` : "";
|
|
4904
|
+
const attempt = row.repairing.attempt ? ` (attempt ${row.repairing.attempt.attempt}/${row.repairing.attempt.limit})` : "";
|
|
4791
4905
|
lines.push(style.yellow(truncate(`${bodyPad}\u21BB repairing conflict${files}${attempt}`, cols)));
|
|
4792
4906
|
}
|
|
4793
|
-
if (
|
|
4794
|
-
|
|
4795
|
-
const dep = state.tasks.find((t) => t.id === id);
|
|
4796
|
-
return dep ? `#${dep.order}` : id;
|
|
4797
|
-
});
|
|
4798
|
-
lines.push(style.yellow(truncate(`${bodyPad}\u23F8 waits for Merge all \u2014 ${deps.join(", ")} not merged into your branch yet`, cols)));
|
|
4907
|
+
if (row.mergeGate) {
|
|
4908
|
+
lines.push(style.yellow(truncate(`${bodyPad}\u23F8 waits for Merge all \u2014 ${row.mergeGate.join(", ")} not merged into your branch yet`, cols)));
|
|
4799
4909
|
}
|
|
4800
|
-
if (
|
|
4910
|
+
if (row.opsChangedFiles) {
|
|
4801
4911
|
lines.push(style.yellow(truncate(`${bodyPad}\u26A0 an ops task changed tracked files \u2014 check them, then m done or /retry`, cols)));
|
|
4802
4912
|
}
|
|
4803
|
-
if (
|
|
4804
|
-
lines.push(style.grey(truncate(`${bodyPad}\u21BB landed after repairing conflict in ${
|
|
4913
|
+
if (row.repaired?.landed) {
|
|
4914
|
+
lines.push(style.grey(truncate(`${bodyPad}\u21BB landed after repairing conflict in ${row.repaired.files}`, cols)));
|
|
4805
4915
|
}
|
|
4806
4916
|
let editorLine;
|
|
4807
4917
|
if (expanded) {
|
|
4808
4918
|
lines.push(style.grey(`${bodyPad}${task.status.replace(/_/g, " ")}`));
|
|
4809
4919
|
if (task.isolation && task.isolation.state !== "none" && task.isolation.branch) {
|
|
4810
4920
|
lines.push(...taskText("Branch", task.isolation.branch, cols, bodyPad));
|
|
4811
|
-
|
|
4812
|
-
if (repos.length > 0) lines.push(...taskText("Repos", repos.join(", "), cols, bodyPad));
|
|
4921
|
+
if (row.repos.length > 0) lines.push(...taskText("Repos", row.repos.join(", "), cols, bodyPad));
|
|
4813
4922
|
if (task.isolation.state !== "integrated" && task.isolation.worktree) {
|
|
4814
4923
|
lines.push(...taskText("Worktree", task.isolation.worktree, cols, bodyPad));
|
|
4815
4924
|
}
|
|
4816
4925
|
}
|
|
4817
|
-
if (
|
|
4926
|
+
if (row.kind === "ops") {
|
|
4818
4927
|
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));
|
|
4819
4928
|
}
|
|
4820
|
-
if (
|
|
4821
|
-
lines.push(...taskText("Forced", `Started before the work of ${
|
|
4929
|
+
if (row.forcedPastGate) {
|
|
4930
|
+
lines.push(...taskText("Forced", `Started before the work of ${row.forcedPastGate.join(", ")} was merged into your branch.`, cols, bodyPad));
|
|
4822
4931
|
}
|
|
4823
|
-
if (
|
|
4824
|
-
lines.push(...taskText("Autonomy", "Runs without permission prompts (Full
|
|
4932
|
+
if (row.autonomous) {
|
|
4933
|
+
lines.push(...taskText("Autonomy", "Runs without permission prompts (Full). Change the level with /auto.", cols, bodyPad));
|
|
4825
4934
|
}
|
|
4826
4935
|
const editableSeed = task.prompt ?? task.description ?? task.title;
|
|
4827
4936
|
if (task.description && task.description !== task.title && task.description !== editableSeed) {
|
|
@@ -4834,10 +4943,8 @@ function taskLines(state, row, index, cols) {
|
|
|
4834
4943
|
editorLine = lines.length + 1 + cp.line;
|
|
4835
4944
|
lines.push(...taskPromptLines(wrapped, cp, bodyPad));
|
|
4836
4945
|
}
|
|
4837
|
-
if (
|
|
4838
|
-
|
|
4839
|
-
const dependencies = task.dependencies.map((id) => orderById.has(id) ? `#${orderById.get(id)}` : id).join(", ");
|
|
4840
|
-
lines.push(...taskText("Depends on", dependencies, cols, bodyPad));
|
|
4946
|
+
if (row.dependencies.length > 0) {
|
|
4947
|
+
lines.push(...taskText("Depends on", row.dependencies.join(", "), cols, bodyPad));
|
|
4841
4948
|
}
|
|
4842
4949
|
lines.push(style.grey(`${bodyPad}enter save \xB7 esc cancel`));
|
|
4843
4950
|
lines.push(style.grey(truncate(`${bodyPad}then R runner \xB7 o model \xB7 e effort \xB7 M mode \xB7 D deps`, cols)));
|
|
@@ -4884,7 +4991,13 @@ function helpLayout(rows, cols) {
|
|
|
4884
4991
|
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")
|
|
4885
4992
|
);
|
|
4886
4993
|
body.push(
|
|
4887
|
-
|
|
4994
|
+
"",
|
|
4995
|
+
style.bold("In a task view (t or /terminal on a structured task)"),
|
|
4996
|
+
style.grey(" ctrl-s sends now: interrupts the running step, then delivers the composer text \u2014 or, with it empty, the selected queued message"),
|
|
4997
|
+
style.grey(" ctrl-n/ctrl-p select a queued message \xB7 ctrl-r removes it \xB7 ctrl-x interrupts"),
|
|
4998
|
+
style.grey(" ctrl-y allows a tool request (its keys sit under it) \xB7 ctrl-t allows it for the task \xB7 ctrl-g denies it, with the composer text as the note"),
|
|
4999
|
+
style.grey(" at a checkpoint, ctrl-y approves and ctrl-g rejects, with the composer text as the reason \xB7 /checkpoint <id> approve|reject [reason] from anywhere"),
|
|
5000
|
+
style.grey(" alt\u2190/\u2192 changes attempt \xB7 esc returns")
|
|
4888
5001
|
);
|
|
4889
5002
|
const lines = body.map((line) => truncate(line, Math.max(1, cols - 2)));
|
|
4890
5003
|
const room = Math.max(1, rows - 2);
|
|
@@ -4895,7 +5008,7 @@ function helpScrollMax(state) {
|
|
|
4895
5008
|
}
|
|
4896
5009
|
|
|
4897
5010
|
// src/tui/render.ts
|
|
4898
|
-
var
|
|
5011
|
+
var import_core25 = require("@ordewell/core");
|
|
4899
5012
|
function render(state) {
|
|
4900
5013
|
const { rows, cols } = state;
|
|
4901
5014
|
const rowsForBody = bodyRows(state);
|
|
@@ -4990,7 +5103,7 @@ function renderSkills(state, cols) {
|
|
|
4990
5103
|
const badges = SKILL_IDS.map(
|
|
4991
5104
|
(id) => state.skills[id] ? style.green(`\u25CF ${id}`) : style.grey(`\u25CB ${id}`)
|
|
4992
5105
|
);
|
|
4993
|
-
const auto = state.autonomous ? style.yellow("\u25CF Full
|
|
5106
|
+
const auto = state.autonomous ? style.yellow("\u25CF Full") : style.green("\u25CF Guarded");
|
|
4994
5107
|
const terminal = state.runnerTransport === "terminal" ? [style.yellow("\u25CF terminal")] : [];
|
|
4995
5108
|
return truncate([...badges, auto, ...terminal].join(" "), cols);
|
|
4996
5109
|
}
|
|
@@ -5160,8 +5273,8 @@ function renderHandoff(state, overlay, rows, cols) {
|
|
|
5160
5273
|
return frame(`Diff \u2014 ${handoffBranch(handoff)}`, [...shown, "", style.grey(`${more}enter or esc goes back`)], rows, cols);
|
|
5161
5274
|
}
|
|
5162
5275
|
const landed = handoff.landed.length === 0 ? [style.grey("Nothing landed on it.")] : handoff.landed.map((task) => {
|
|
5163
|
-
const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0,
|
|
5164
|
-
return ` ${style.green("\u2713")}
|
|
5276
|
+
const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0, import_core25.capConflictFiles)(task.repairedFiles)})`) : "";
|
|
5277
|
+
return ` ${style.green("\u2713")} ${(0, import_core25.titledTaskRef)(task)}${repaired}`;
|
|
5165
5278
|
});
|
|
5166
5279
|
const actions = handoffActions(handoff).map((action, index) => {
|
|
5167
5280
|
const active = index === overlay.index;
|
|
@@ -5250,7 +5363,10 @@ function frame(title, body, rows, cols) {
|
|
|
5250
5363
|
}
|
|
5251
5364
|
|
|
5252
5365
|
// src/tui/reducer.ts
|
|
5253
|
-
var
|
|
5366
|
+
var import_core33 = require("@ordewell/core");
|
|
5367
|
+
|
|
5368
|
+
// src/tui/reducers/overlays.ts
|
|
5369
|
+
var import_core31 = require("@ordewell/core");
|
|
5254
5370
|
|
|
5255
5371
|
// src/tui/reducers/shared.ts
|
|
5256
5372
|
function step(state, effects = []) {
|
|
@@ -5299,10 +5415,10 @@ function handleApprovalKey(state, overlay, key) {
|
|
|
5299
5415
|
}
|
|
5300
5416
|
|
|
5301
5417
|
// src/tui/reducers/commands.ts
|
|
5302
|
-
var
|
|
5418
|
+
var import_core30 = require("@ordewell/core");
|
|
5303
5419
|
|
|
5304
5420
|
// src/tui/reducers/pickers.ts
|
|
5305
|
-
var
|
|
5421
|
+
var import_core26 = require("@ordewell/core");
|
|
5306
5422
|
var import_plan_utils2 = require("@ordewell/core/plan-utils");
|
|
5307
5423
|
var DEFAULT_EFFORT = "__runner_default__";
|
|
5308
5424
|
function picker(title, items, action, extra = {}) {
|
|
@@ -5321,11 +5437,11 @@ function modelDetail(m) {
|
|
|
5321
5437
|
function providerErrorHint(state) {
|
|
5322
5438
|
const failed = Object.keys(state.providerErrors ?? {});
|
|
5323
5439
|
if (failed.length === 0) return void 0;
|
|
5324
|
-
const names = failed.map((p) =>
|
|
5440
|
+
const names = failed.map((p) => import_core26.ALL_PROVIDERS[p]?.label ?? p);
|
|
5325
5441
|
return `\u26A0 Unavailable (key or fetch failed): ${names.join(", ")} \u2014 showing working providers only.`;
|
|
5326
5442
|
}
|
|
5327
5443
|
function plannerModelItems(state) {
|
|
5328
|
-
const runner = (0,
|
|
5444
|
+
const runner = (0, import_core26.runnerForProvider)(state.plannerProvider);
|
|
5329
5445
|
if (!runner) return state.orchestratorModels.map((m) => ({ id: m.id, label: m.label, detail: modelDetail(m) }));
|
|
5330
5446
|
const items = state.models.filter((m) => m.runners?.includes(runner)).map((m) => ({
|
|
5331
5447
|
id: m.id,
|
|
@@ -5339,7 +5455,7 @@ function plannerModelItems(state) {
|
|
|
5339
5455
|
return items.length > 0 ? items : [{ id: "", label: `No ${runner} models discovered yet`, detail: "run /refresh", disabled: true }];
|
|
5340
5456
|
}
|
|
5341
5457
|
function plannerEffortItems(state) {
|
|
5342
|
-
const runner = (0,
|
|
5458
|
+
const runner = (0, import_core26.runnerForProvider)(state.plannerProvider);
|
|
5343
5459
|
if (!runner) {
|
|
5344
5460
|
return [{ id: "", label: "Thinking effort applies to a coding-agent planner", detail: "switch with /planner", disabled: true }];
|
|
5345
5461
|
}
|
|
@@ -5403,7 +5519,7 @@ function pickerItemsFor(state, action) {
|
|
|
5403
5519
|
if (action.kind === "set-task-deps") {
|
|
5404
5520
|
return (0, import_plan_utils2.dependencyCandidates)(state.tasks, action.taskId).map((candidate) => ({
|
|
5405
5521
|
id: candidate.id,
|
|
5406
|
-
label:
|
|
5522
|
+
label: (0, import_core26.titledTaskRef)(candidate),
|
|
5407
5523
|
detail: candidate.status === "completed" ? "already completed" : void 0
|
|
5408
5524
|
}));
|
|
5409
5525
|
}
|
|
@@ -5448,26 +5564,29 @@ function pickerItemsFor(state, action) {
|
|
|
5448
5564
|
}
|
|
5449
5565
|
function plannerItems(state) {
|
|
5450
5566
|
const installed = new Set(state.runners.map((r) => r.id));
|
|
5451
|
-
const agents =
|
|
5452
|
-
const runner = (0,
|
|
5567
|
+
const agents = import_core26.CLI_PROVIDERS.map((id) => {
|
|
5568
|
+
const runner = (0, import_core26.runnerForProvider)(id);
|
|
5453
5569
|
const ready = installed.has(runner);
|
|
5454
5570
|
return {
|
|
5455
5571
|
id,
|
|
5456
|
-
label:
|
|
5572
|
+
label: import_core26.ALL_PROVIDERS[id].label,
|
|
5457
5573
|
detail: ready ? "coding agent \xB7 no API key needed" : "CLI not installed or not on PATH",
|
|
5458
5574
|
selected: state.plannerProvider === id,
|
|
5459
5575
|
disabled: !ready
|
|
5460
5576
|
};
|
|
5461
5577
|
});
|
|
5462
|
-
const vendors =
|
|
5578
|
+
const vendors = import_core26.PROVIDER_PRIORITY.filter((id) => state.configuredProviders.includes(id)).map((id) => ({
|
|
5463
5579
|
id,
|
|
5464
|
-
label:
|
|
5465
|
-
detail:
|
|
5580
|
+
label: import_core26.ALL_PROVIDERS[id].label,
|
|
5581
|
+
detail: import_core26.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
5466
5582
|
selected: state.plannerProvider === id
|
|
5467
5583
|
}));
|
|
5468
5584
|
return vendors.length > 0 ? [...agents, ...vendors] : [...agents, { id: "", label: "No API providers configured", detail: "add a key with /key", disabled: true }];
|
|
5469
5585
|
}
|
|
5470
5586
|
|
|
5587
|
+
// src/tui/reducers/planPane.ts
|
|
5588
|
+
var import_core28 = require("@ordewell/core");
|
|
5589
|
+
|
|
5471
5590
|
// src/tui/reducers/pointer.ts
|
|
5472
5591
|
var WHEEL_NOTCH = 3;
|
|
5473
5592
|
function pageNotch(viewport) {
|
|
@@ -5544,13 +5663,13 @@ function handleMouseSelect(state, key) {
|
|
|
5544
5663
|
}
|
|
5545
5664
|
|
|
5546
5665
|
// src/tui/reducers/taskView.ts
|
|
5547
|
-
var
|
|
5666
|
+
var import_core27 = require("@ordewell/core");
|
|
5548
5667
|
function openTaskView(state, sessionId, taskId) {
|
|
5549
5668
|
const task = findTask(state.tasks, taskId);
|
|
5550
5669
|
if (!task) return step(say(state, "system", `No task ${taskId} in this plan.`));
|
|
5551
5670
|
const taskView = {
|
|
5552
5671
|
taskId,
|
|
5553
|
-
view:
|
|
5672
|
+
view: import_core27.EMPTY_TASK_LOG,
|
|
5554
5673
|
attempts: [],
|
|
5555
5674
|
attempt: 0,
|
|
5556
5675
|
pending: [],
|
|
@@ -5583,13 +5702,13 @@ function applyLive(tv, attempt, events) {
|
|
|
5583
5702
|
}
|
|
5584
5703
|
next = {
|
|
5585
5704
|
...next,
|
|
5586
|
-
view:
|
|
5705
|
+
view: import_core27.EMPTY_TASK_LOG,
|
|
5587
5706
|
attempt,
|
|
5588
5707
|
attempts: next.attempts.includes(attempt) ? next.attempts : [...next.attempts, attempt]
|
|
5589
5708
|
};
|
|
5590
5709
|
}
|
|
5591
5710
|
if (attempt < next.attempt) return next;
|
|
5592
|
-
return clampQueueIndex({ ...next, view: events.reduce(
|
|
5711
|
+
return clampQueueIndex({ ...next, view: events.reduce(import_core27.reduceTaskLog, next.view) });
|
|
5593
5712
|
}
|
|
5594
5713
|
function taskLogAbandoned(state) {
|
|
5595
5714
|
const tv = state.taskView;
|
|
@@ -5609,15 +5728,22 @@ function taskLogArrived(state, action) {
|
|
|
5609
5728
|
function announceApprovals(state, action) {
|
|
5610
5729
|
if (state.taskView?.taskId === action.taskId) return state;
|
|
5611
5730
|
const task = findTask(state.tasks, action.taskId);
|
|
5612
|
-
const spoken = action.events.reduce((s, event) => event.type === "approval_requested" ? say(s, "system", `\xB7 Task ${task?.order ?? "?"} waits for approval: ${(0,
|
|
5731
|
+
const spoken = action.events.reduce((s, event) => event.type === "approval_requested" ? say(s, "system", `\xB7 Task ${task?.order ?? "?"} waits for approval: ${(0, import_core27.runnerToolSubject)(event.tool, event.args)} \u2014 t on it, then ctrl-y to allow or ctrl-g to deny`) : s, state);
|
|
5613
5732
|
return spoken !== state ? { ...spoken, scroll: state.scroll } : spoken;
|
|
5614
5733
|
}
|
|
5734
|
+
function announceCheckpoint(state, action) {
|
|
5735
|
+
const task = findTask(state.tasks, action.taskId) ?? state.tasks.find((t) => t.title === action.title);
|
|
5736
|
+
const label = task ? `Task ${task.order}` : action.title;
|
|
5737
|
+
const where = task?.transport?.kind === "structured" ? "t on it to answer" : `/checkpoint ${task?.order ?? "<id>"} approve|reject to answer`;
|
|
5738
|
+
const spoken = say(state, "system", `\xB7 ${label} asks: ${(0, import_core27.truncateCheckpointSummary)(action.summary)} \u2014 ${where}`);
|
|
5739
|
+
return { ...spoken, scroll: state.scroll };
|
|
5740
|
+
}
|
|
5615
5741
|
function taskLogLoaded(state, action) {
|
|
5616
5742
|
const tv = state.taskView;
|
|
5617
5743
|
if (!tv || tv.taskId !== action.taskId) return state;
|
|
5618
5744
|
const loaded = action.attempt >= 1 ? {
|
|
5619
5745
|
...tv,
|
|
5620
|
-
view: (0,
|
|
5746
|
+
view: (0, import_core27.replayTaskLog)(action.events),
|
|
5621
5747
|
attempts: action.attempts ?? tv.attempts,
|
|
5622
5748
|
attempt: action.attempt
|
|
5623
5749
|
} : { ...tv, attempts: action.attempts ?? tv.attempts };
|
|
@@ -5639,6 +5765,29 @@ function answerApproval(state, tv, sessionId, decision) {
|
|
|
5639
5765
|
const next = note ? { ...state, editor: { ...state.editor, text: "", cursor: 0 } } : state;
|
|
5640
5766
|
return step(next, [{ type: "answerTaskApproval", sessionId, approvalId: approval.approvalId, answer }]);
|
|
5641
5767
|
}
|
|
5768
|
+
function answerCheckpoint(state, sessionId, taskId, answer, reason = "") {
|
|
5769
|
+
const task = findTask(state.tasks, taskId);
|
|
5770
|
+
if (!waitingCheckpoint(task)) return step(say(state, "system", `Task ${task?.order ?? taskId} is not waiting at a checkpoint.`));
|
|
5771
|
+
const note = answer === "reject" ? reason.trim() : "";
|
|
5772
|
+
return step(state, [{ type: "answerTaskCheckpoint", sessionId, taskId, answer, ...note ? { reason: note } : {} }]);
|
|
5773
|
+
}
|
|
5774
|
+
function answerWaiting(state, tv, sessionId, decision) {
|
|
5775
|
+
if (waitingApproval(tv) || !waitingCheckpoint(findTask(state.tasks, tv.taskId))) return answerApproval(state, tv, sessionId, decision);
|
|
5776
|
+
if (decision === "allow") return answerCheckpoint(state, sessionId, tv.taskId, "approve");
|
|
5777
|
+
const answered = answerCheckpoint(state, sessionId, tv.taskId, "reject", state.editor.text);
|
|
5778
|
+
return state.editor.text.trim() ? { ...answered, state: { ...answered.state, editor: { ...answered.state.editor, text: "", cursor: 0 } } } : answered;
|
|
5779
|
+
}
|
|
5780
|
+
function sendNow(state, tv, sessionId) {
|
|
5781
|
+
const text = state.editor.text.trim();
|
|
5782
|
+
if (text) {
|
|
5783
|
+
if (continuesTask(findTask(state.tasks, tv.taskId))) return step(say(state, "system", "The task has finished, so there is no turn to interrupt \u2014 enter continues it."));
|
|
5784
|
+
return step({ ...state, editor: commit(state.editor) }, [{ type: "forceSendTaskMessage", sessionId, taskId: tv.taskId, text }]);
|
|
5785
|
+
}
|
|
5786
|
+
const queued = tv.view.queued[tv.queuedIndex];
|
|
5787
|
+
if (!queued) return step(say(state, "system", "Nothing to send now: type a message, or pick a queued one with ctrl-n/ctrl-p."));
|
|
5788
|
+
if (queued.handedOver) return step(say(state, "system", "The runner already has that message; it reads it after its current step."));
|
|
5789
|
+
return step(state, [{ type: "forceSendQueuedTaskMessage", sessionId, taskId: tv.taskId, messageId: queued.id }]);
|
|
5790
|
+
}
|
|
5642
5791
|
function handleTaskViewKey(state, key) {
|
|
5643
5792
|
const tv = state.taskView;
|
|
5644
5793
|
if (!tv || !state.sessionId) return null;
|
|
@@ -5658,14 +5807,16 @@ function handleTaskViewKey(state, key) {
|
|
|
5658
5807
|
if (key.name === "ctrl-r") {
|
|
5659
5808
|
const queued = tv.view.queued[tv.queuedIndex];
|
|
5660
5809
|
if (!queued) return step(say(state, "system", "No queued message to remove."));
|
|
5810
|
+
if (queued.handedOver) return step(say(state, "system", "The runner already has that message; it can no longer be taken back."));
|
|
5661
5811
|
return step(state, [{ type: "removeTaskMessage", sessionId: state.sessionId, taskId: tv.taskId, messageId: queued.id }]);
|
|
5662
5812
|
}
|
|
5813
|
+
if (key.name === "ctrl-s") return sendNow(state, tv, state.sessionId);
|
|
5663
5814
|
if (key.name === "ctrl-x") {
|
|
5664
5815
|
return step(state, [{ type: "interruptTask", sessionId: state.sessionId, taskId: tv.taskId }]);
|
|
5665
5816
|
}
|
|
5666
|
-
if (key.name === "ctrl-y") return
|
|
5817
|
+
if (key.name === "ctrl-y") return answerWaiting(state, tv, state.sessionId, "allow");
|
|
5667
5818
|
if (key.name === "ctrl-t") return answerApproval(state, tv, state.sessionId, "allowForTask");
|
|
5668
|
-
if (key.name === "ctrl-g") return
|
|
5819
|
+
if (key.name === "ctrl-g") return answerWaiting(state, tv, state.sessionId, "deny");
|
|
5669
5820
|
if (key.name === "ctrl-n" || key.name === "ctrl-p") {
|
|
5670
5821
|
const count = tv.view.queued.length;
|
|
5671
5822
|
if (count === 0) return null;
|
|
@@ -5686,7 +5837,7 @@ function openTaskRunnerPicker(state, task) {
|
|
|
5686
5837
|
...state,
|
|
5687
5838
|
overlay: {
|
|
5688
5839
|
kind: "picker",
|
|
5689
|
-
picker: picker(`Runner \xB7
|
|
5840
|
+
picker: picker(`Runner \xB7 ${(0, import_plan_utils3.titledTaskRef)(task)}`, pickerItemsFor(state, action), action, {
|
|
5690
5841
|
hint: "Changing the runner re-picks this task model, effort and mode for it."
|
|
5691
5842
|
})
|
|
5692
5843
|
}
|
|
@@ -5694,14 +5845,12 @@ function openTaskRunnerPicker(state, task) {
|
|
|
5694
5845
|
);
|
|
5695
5846
|
}
|
|
5696
5847
|
function confirmRemoveTask(state, task) {
|
|
5697
|
-
const dependents = (0, import_plan_utils3.dependentsOf)(state.tasks, task.id);
|
|
5698
|
-
const named = dependents.map((t) => `#${t.order} ${t.title}`).join(", ");
|
|
5699
5848
|
return step({
|
|
5700
5849
|
...state,
|
|
5701
5850
|
overlay: {
|
|
5702
5851
|
kind: "confirm",
|
|
5703
|
-
title: `Remove
|
|
5704
|
-
message:
|
|
5852
|
+
title: `Remove ${(0, import_plan_utils3.titledTaskRef)(task)}?`,
|
|
5853
|
+
message: (0, import_plan_utils3.dependentsNotice)((0, import_plan_utils3.dependentsOf)(state.tasks, task.id)) ?? "This cannot be undone.",
|
|
5705
5854
|
action: { kind: "remove-task", taskId: task.id }
|
|
5706
5855
|
}
|
|
5707
5856
|
});
|
|
@@ -5710,26 +5859,22 @@ function toggleTaskOps(state, task, subtask, to = !task.ops) {
|
|
|
5710
5859
|
if (!state.sessionId) return fail3(state, "No active plan.");
|
|
5711
5860
|
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.");
|
|
5712
5861
|
if (subtask) return fail3(state, "A subtask runs with its parent; make the parent an ops task instead.");
|
|
5713
|
-
if (!!task.ops === to) return fail3(state,
|
|
5862
|
+
if (!!task.ops === to) return fail3(state, `${(0, import_plan_utils3.taskRef)(task)} is already ${to ? "an ops" : "a change"} task.`);
|
|
5714
5863
|
return step(state, [{
|
|
5715
5864
|
type: "updateTask",
|
|
5716
5865
|
sessionId: state.sessionId,
|
|
5717
5866
|
taskId: task.id,
|
|
5718
5867
|
changes: { ops: to },
|
|
5719
|
-
message: to ? `Task
|
|
5868
|
+
message: to ? `Task ${(0, import_plan_utils3.taskRef)(task)} is an ops task: it runs in your checkout once the work it depends on is merged.` : `Task ${(0, import_plan_utils3.taskRef)(task)} is a change task: it runs in its own worktree.`
|
|
5720
5869
|
}]);
|
|
5721
5870
|
}
|
|
5722
5871
|
function confirmForceStartPastGate(state, task) {
|
|
5723
|
-
const named = (task.mergeGate ?? []).map((id) => {
|
|
5724
|
-
const dep = findTask(state.tasks, id);
|
|
5725
|
-
return dep ? `#${dep.order} ${dep.title}` : id;
|
|
5726
|
-
});
|
|
5727
5872
|
return step({
|
|
5728
5873
|
...state,
|
|
5729
5874
|
overlay: {
|
|
5730
5875
|
kind: "confirm",
|
|
5731
|
-
title: `Force start
|
|
5732
|
-
message:
|
|
5876
|
+
title: `Force start ${(0, import_plan_utils3.titledTaskRef)(task)}?`,
|
|
5877
|
+
message: (0, import_plan_utils3.pastGateConfirmation)((0, import_plan_utils3.titledRefs)(task.mergeGate ?? [], state.tasks), "It"),
|
|
5733
5878
|
action: { kind: "force-start-gated", taskId: task.id }
|
|
5734
5879
|
}
|
|
5735
5880
|
});
|
|
@@ -5737,12 +5882,12 @@ function confirmForceStartPastGate(state, task) {
|
|
|
5737
5882
|
function openTaskDepsPicker(state, task) {
|
|
5738
5883
|
const action = { kind: "set-task-deps", taskId: task.id };
|
|
5739
5884
|
const items = pickerItemsFor(state, action);
|
|
5740
|
-
if (items.length === 0) return fail3(state, `Nothing runs before
|
|
5885
|
+
if (items.length === 0) return fail3(state, `Nothing runs before ${(0, import_plan_utils3.taskRef)(task)}, so it has no possible dependencies.`);
|
|
5741
5886
|
return step({
|
|
5742
5887
|
...state,
|
|
5743
5888
|
overlay: {
|
|
5744
5889
|
kind: "picker",
|
|
5745
|
-
picker: picker(`Depends on \xB7
|
|
5890
|
+
picker: picker(`Depends on \xB7 ${(0, import_plan_utils3.titledTaskRef)(task)}`, items, action, {
|
|
5746
5891
|
hint: "Only tasks earlier in the plan can be dependencies.",
|
|
5747
5892
|
multi: true,
|
|
5748
5893
|
chosen: task.dependencies.filter((id) => items.some((i) => i.id === id))
|
|
@@ -5762,7 +5907,7 @@ function openTaskModePicker(state, task) {
|
|
|
5762
5907
|
...state,
|
|
5763
5908
|
overlay: {
|
|
5764
5909
|
kind: "picker",
|
|
5765
|
-
picker: picker(`Mode \xB7
|
|
5910
|
+
picker: picker(`Mode \xB7 ${(0, import_plan_utils3.taskRef)(task)}`, pickerItemsFor(state, action), action, {
|
|
5766
5911
|
hint: `Modes declared by ${task.assignedRunner}.`
|
|
5767
5912
|
})
|
|
5768
5913
|
}
|
|
@@ -5777,7 +5922,7 @@ function openTaskModelPicker(state, task) {
|
|
|
5777
5922
|
...state,
|
|
5778
5923
|
overlay: {
|
|
5779
5924
|
kind: "picker",
|
|
5780
|
-
picker: picker(`Model \xB7
|
|
5925
|
+
picker: picker(`Model \xB7 ${(0, import_plan_utils3.titledTaskRef)(task)}`, pickerItemsFor(state, action), action, {
|
|
5781
5926
|
hint: task.assignedRunner ? `Showing models discovered for ${task.assignedRunner}.` : "Choose the model this task will run with."
|
|
5782
5927
|
})
|
|
5783
5928
|
}
|
|
@@ -5794,7 +5939,7 @@ function openTaskEffortPicker(state, task) {
|
|
|
5794
5939
|
...state,
|
|
5795
5940
|
overlay: {
|
|
5796
5941
|
kind: "picker",
|
|
5797
|
-
picker: picker(`Thinking effort \xB7
|
|
5942
|
+
picker: picker(`Thinking effort \xB7 ${(0, import_plan_utils3.taskRef)(task)}`, pickerItemsFor(state, action), action, {
|
|
5798
5943
|
hint: `${task.assignedModel.modelLabel} \xB7 choose runner default or a supported effort`
|
|
5799
5944
|
})
|
|
5800
5945
|
}
|
|
@@ -5811,7 +5956,7 @@ function assignTaskModel(state, sessionId, task, model) {
|
|
|
5811
5956
|
// JSON drops `undefined`; null is intentional here so changing models can
|
|
5812
5957
|
// also clear a stale legacy top-level effort on the persisted task.
|
|
5813
5958
|
changes: { assignedModel, thinkingEffort: assignedModel.thinkingEffort ?? null },
|
|
5814
|
-
message: `Task
|
|
5959
|
+
message: `Task ${(0, import_plan_utils3.taskRef)(task)} model set to ${model.label}.`
|
|
5815
5960
|
}]);
|
|
5816
5961
|
}
|
|
5817
5962
|
function assignTaskRunner(state, sessionId, task, runner, runnerLabel) {
|
|
@@ -5820,7 +5965,7 @@ function assignTaskRunner(state, sessionId, task, runner, runnerLabel) {
|
|
|
5820
5965
|
sessionId,
|
|
5821
5966
|
taskId: task.id,
|
|
5822
5967
|
changes: { assignedRunner: runner },
|
|
5823
|
-
message: `Task
|
|
5968
|
+
message: `Task ${(0, import_plan_utils3.taskRef)(task)} runner set to ${runnerLabel}.`
|
|
5824
5969
|
}]);
|
|
5825
5970
|
}
|
|
5826
5971
|
function assignTaskMode(state, sessionId, task, mode, modeLabel) {
|
|
@@ -5829,7 +5974,7 @@ function assignTaskMode(state, sessionId, task, mode, modeLabel) {
|
|
|
5829
5974
|
sessionId,
|
|
5830
5975
|
taskId: task.id,
|
|
5831
5976
|
changes: { taskMode: mode },
|
|
5832
|
-
message: `Task
|
|
5977
|
+
message: `Task ${(0, import_plan_utils3.taskRef)(task)} mode set to ${modeLabel}.`
|
|
5833
5978
|
}]);
|
|
5834
5979
|
}
|
|
5835
5980
|
function assignTaskEffort(state, sessionId, task, thinkingEffort) {
|
|
@@ -5839,7 +5984,7 @@ function assignTaskEffort(state, sessionId, task, thinkingEffort) {
|
|
|
5839
5984
|
sessionId,
|
|
5840
5985
|
taskId: task.id,
|
|
5841
5986
|
changes: { assignedModel, thinkingEffort: thinkingEffort ?? null },
|
|
5842
|
-
message: `Task
|
|
5987
|
+
message: `Task ${(0, import_plan_utils3.taskRef)(task)} thinking effort set to ${thinkingEffort ?? "runner default"}.`
|
|
5843
5988
|
}]);
|
|
5844
5989
|
}
|
|
5845
5990
|
function taskModelCommand(state, args) {
|
|
@@ -5941,9 +6086,6 @@ function taskActionEffect(state, sessionId, taskId, action) {
|
|
|
5941
6086
|
const watch = SPAWNS_RUNNER.includes(action) && state.status !== "executing";
|
|
5942
6087
|
return watch ? { type: "taskAction", sessionId, taskId, action, watch: true } : { type: "taskAction", sessionId, taskId, action };
|
|
5943
6088
|
}
|
|
5944
|
-
function markAction(task) {
|
|
5945
|
-
return task.status === "completed" ? "uncomplete" : "complete";
|
|
5946
|
-
}
|
|
5947
6089
|
function handlePlanKey(state, key) {
|
|
5948
6090
|
const editingTask = state.expandedTaskId ? findTask(state.tasks, state.expandedTaskId) : void 0;
|
|
5949
6091
|
if (editingTask && state.taskEditor) return handleTaskEditKey(state, editingTask, state.taskEditor, key);
|
|
@@ -5978,7 +6120,7 @@ function handlePlanKey(state, key) {
|
|
|
5978
6120
|
if (!state.sessionId || key.name !== "char") return step(state);
|
|
5979
6121
|
if (key.char === "E") return step(state, [{ type: "execute", sessionId: state.sessionId }]);
|
|
5980
6122
|
if (key.char === "S") return step(state, [{ type: "stopExecution", sessionId: state.sessionId }]);
|
|
5981
|
-
const action = key.char === "m" ? markAction(task) : PLAN_SHORTCUTS[key.char ?? ""];
|
|
6123
|
+
const action = key.char === "m" ? (0, import_core28.markAction)(task) : PLAN_SHORTCUTS[key.char ?? ""];
|
|
5982
6124
|
if (action === "force-start" && task.mergeGate?.length) return confirmForceStartPastGate(state, task);
|
|
5983
6125
|
if (action) {
|
|
5984
6126
|
return step(state, [taskActionEffect(state, state.sessionId, task.id, action)]);
|
|
@@ -6013,14 +6155,14 @@ function commitTaskEdit(state, task, editor) {
|
|
|
6013
6155
|
sessionId: state.sessionId,
|
|
6014
6156
|
taskId: task.id,
|
|
6015
6157
|
changes: { prompt },
|
|
6016
|
-
message: `Task
|
|
6158
|
+
message: `Task ${(0, import_core28.taskRef)(task)} prompt updated.`
|
|
6017
6159
|
}]);
|
|
6018
6160
|
}
|
|
6019
6161
|
|
|
6020
6162
|
// src/tui/reducers/turnQueue.ts
|
|
6021
|
-
var
|
|
6163
|
+
var import_core29 = require("@ordewell/core");
|
|
6022
6164
|
function drainQueue(state, settled) {
|
|
6023
|
-
const next = (0,
|
|
6165
|
+
const next = (0, import_core29.drainNext)(state.queuedPrompts);
|
|
6024
6166
|
const ended = disarmStop({ ...settled, stopRequested: false });
|
|
6025
6167
|
if (!next) return step(ended);
|
|
6026
6168
|
const spoken = say(ended, "user", next.text);
|
|
@@ -6039,14 +6181,14 @@ function stopPlanning(state, sessionId) {
|
|
|
6039
6181
|
return step(queueToEditor(cutTurn({ ...dismissed, stopArmed: false, stopRequested: true })), [{ type: "cancelPlanning", sessionId }]);
|
|
6040
6182
|
}
|
|
6041
6183
|
function queueToEditor(state) {
|
|
6042
|
-
const all = (0,
|
|
6184
|
+
const all = (0, import_core29.unsendAll)(state.queuedPrompts);
|
|
6043
6185
|
if (!all) return state;
|
|
6044
6186
|
return { ...state, queuedPrompts: all.rest, editor: { ...state.editor, text: all.text, cursor: all.text.length } };
|
|
6045
6187
|
}
|
|
6046
6188
|
function unsendLatest(state) {
|
|
6047
|
-
const latest = (0,
|
|
6189
|
+
const latest = (0, import_core29.unsendLatest)(state.queuedPrompts);
|
|
6048
6190
|
if (!latest) return step(state);
|
|
6049
|
-
const text = (0,
|
|
6191
|
+
const text = (0, import_core29.aheadOfDraft)(latest.text, state.editor.text);
|
|
6050
6192
|
return step({
|
|
6051
6193
|
...state,
|
|
6052
6194
|
queuedPrompts: latest.rest,
|
|
@@ -6063,7 +6205,7 @@ function plannerEscape(state) {
|
|
|
6063
6205
|
}
|
|
6064
6206
|
|
|
6065
6207
|
// src/tui/reducers/commands.ts
|
|
6066
|
-
var KNOWN_PROVIDERS2 = Object.keys(
|
|
6208
|
+
var KNOWN_PROVIDERS2 = Object.keys(import_core30.ALL_PROVIDERS);
|
|
6067
6209
|
function runCommand(state, { name, args }) {
|
|
6068
6210
|
if (!findCommand(name)) {
|
|
6069
6211
|
return fail3(state, `Unknown command: /${name} \u2014 type /help to see what's available.`);
|
|
@@ -6150,6 +6292,14 @@ function runCommand(state, { name, args }) {
|
|
|
6150
6292
|
if (!text) return fail3(state, "Usage: /continue <id> <message>");
|
|
6151
6293
|
return continueTaskStep(state, sessionId, taskId, text);
|
|
6152
6294
|
});
|
|
6295
|
+
case "checkpoint":
|
|
6296
|
+
return taskCommand(state, args[0], (sessionId, taskId) => {
|
|
6297
|
+
const answer = args[1]?.toLowerCase();
|
|
6298
|
+
const reason = args.slice(2).join(" ").trim();
|
|
6299
|
+
if (answer !== "approve" && answer !== "reject") return fail3(state, "Usage: /checkpoint <id> approve|reject [reason]");
|
|
6300
|
+
if (answer === "approve" && reason) return fail3(state, "Approving takes no note \u2014 reject with a reason to tell the task something.");
|
|
6301
|
+
return answerCheckpoint(state, sessionId, taskId, answer, reason);
|
|
6302
|
+
});
|
|
6153
6303
|
case "task-runner":
|
|
6154
6304
|
return taskRunnerCommand(state, args);
|
|
6155
6305
|
case "task-model":
|
|
@@ -6221,8 +6371,8 @@ function newSession(state) {
|
|
|
6221
6371
|
taskView: null,
|
|
6222
6372
|
tasks: [],
|
|
6223
6373
|
planApproved: false,
|
|
6224
|
-
conversation:
|
|
6225
|
-
turnGate:
|
|
6374
|
+
conversation: import_core30.EMPTY_CONVERSATION,
|
|
6375
|
+
turnGate: import_core30.NO_TURN,
|
|
6226
6376
|
selectedTask: 0,
|
|
6227
6377
|
expandedTaskId: null,
|
|
6228
6378
|
taskEditor: null,
|
|
@@ -6233,14 +6383,14 @@ function newSession(state) {
|
|
|
6233
6383
|
// Prompts belong to the session that raised them; the old planner is gone
|
|
6234
6384
|
// and its pending requests deny on their own timeout.
|
|
6235
6385
|
pendingApprovals: [],
|
|
6236
|
-
queuedPrompts:
|
|
6386
|
+
queuedPrompts: import_core30.EMPTY_HOLD,
|
|
6237
6387
|
stopArmed: false,
|
|
6238
6388
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
6239
6389
|
}, closeEffects);
|
|
6240
6390
|
}
|
|
6241
6391
|
function setModel(state, args) {
|
|
6242
6392
|
if (args[0] === "set" && args[1]) {
|
|
6243
|
-
const runner = (0,
|
|
6393
|
+
const runner = (0, import_core30.runnerForProvider)(state.plannerProvider);
|
|
6244
6394
|
const known = runner ? modelsForRunner(state.models, runner) : [];
|
|
6245
6395
|
if (runner && known.length > 0 && !known.some((m) => m.id === args[1])) {
|
|
6246
6396
|
return fail3(state, `${args[1]} was not discovered for ${runner}.`);
|
|
@@ -6313,16 +6463,16 @@ function setKey(state, args) {
|
|
|
6313
6463
|
}
|
|
6314
6464
|
return step(state, [{ type: "setApiKey", provider, key: args.slice(2).join(" ") }]);
|
|
6315
6465
|
}
|
|
6316
|
-
const items =
|
|
6466
|
+
const items = import_core30.PROVIDER_PRIORITY.map((id) => ({
|
|
6317
6467
|
id,
|
|
6318
|
-
label:
|
|
6319
|
-
detail:
|
|
6468
|
+
label: import_core30.ALL_PROVIDERS[id].label,
|
|
6469
|
+
detail: import_core30.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
6320
6470
|
selected: state.configuredProviders.includes(id)
|
|
6321
6471
|
}));
|
|
6322
6472
|
return step({ ...state, overlay: { kind: "picker", picker: picker("API provider key", items, { kind: "set-key" }) } });
|
|
6323
6473
|
}
|
|
6324
6474
|
function keyPrompt(provider) {
|
|
6325
|
-
const meta =
|
|
6475
|
+
const meta = import_core30.ALL_PROVIDERS[provider];
|
|
6326
6476
|
return {
|
|
6327
6477
|
kind: "prompt",
|
|
6328
6478
|
title: `${meta.label} API key`,
|
|
@@ -6399,19 +6549,19 @@ function runners(state, args) {
|
|
|
6399
6549
|
}
|
|
6400
6550
|
function setMaxParallel(state, arg) {
|
|
6401
6551
|
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.`));
|
|
6402
|
-
const limit = (0,
|
|
6552
|
+
const limit = (0, import_core30.parseMaxParallel)(arg);
|
|
6403
6553
|
if (limit === null) return fail3(state, "Usage: /parallel [<number of tasks, 1 or more>]");
|
|
6404
6554
|
return step({ ...state, maxParallel: limit }, [{ type: "setMaxParallel", limit }]);
|
|
6405
6555
|
}
|
|
6406
6556
|
function setAutonomous(state, arg) {
|
|
6407
|
-
if (arg === void 0) return step(say(state, "system", `Autonomy level: ${(0,
|
|
6408
|
-
const enabled = (0,
|
|
6409
|
-
if (enabled === null) return fail3(state, "Usage: /auto [full|
|
|
6557
|
+
if (arg === void 0) return step(say(state, "system", `Autonomy level: ${(0, import_core30.autonomyLevelLabel)(state.autonomous)} \u2014 /auto full or /auto guarded changes it for new plans.`));
|
|
6558
|
+
const enabled = (0, import_core30.parseAutonomyLevel)(arg);
|
|
6559
|
+
if (enabled === null) return fail3(state, "Usage: /auto [full|guarded]");
|
|
6410
6560
|
return step({ ...state, autonomous: enabled }, [{ type: "setAutonomous", enabled }]);
|
|
6411
6561
|
}
|
|
6412
6562
|
function setTransport(state, arg) {
|
|
6413
6563
|
const value = arg?.toLowerCase() ?? (state.runnerTransport === "structured" ? "terminal" : "structured");
|
|
6414
|
-
if (!(0,
|
|
6564
|
+
if (!(0, import_core30.isRunnerTransport)(value)) return fail3(state, "Usage: /transport [terminal|structured]");
|
|
6415
6565
|
return step(state, [{ type: "setTransport", transport: value }]);
|
|
6416
6566
|
}
|
|
6417
6567
|
function setMouseCapture(state, arg) {
|
|
@@ -6588,7 +6738,7 @@ function choose(state, picker2, item) {
|
|
|
6588
6738
|
sessionId: state.sessionId,
|
|
6589
6739
|
taskId: task.id,
|
|
6590
6740
|
changes: { dependencies: picker2.chosen },
|
|
6591
|
-
message: picker2.chosen.length > 0 ? `Task
|
|
6741
|
+
message: picker2.chosen.length > 0 ? `Task ${(0, import_core31.taskRef)(task)} now depends on ${picker2.chosen.length} task${picker2.chosen.length === 1 ? "" : "s"}.` : `Task ${(0, import_core31.taskRef)(task)} no longer depends on anything.`
|
|
6592
6742
|
}]);
|
|
6593
6743
|
}
|
|
6594
6744
|
if (!item) return step(state);
|
|
@@ -6665,7 +6815,7 @@ function choose(state, picker2, item) {
|
|
|
6665
6815
|
}
|
|
6666
6816
|
|
|
6667
6817
|
// src/tui/reducers/incoming.ts
|
|
6668
|
-
var
|
|
6818
|
+
var import_core32 = require("@ordewell/core");
|
|
6669
6819
|
var planText = (value, fallback = "") => sanitize(String(value ?? fallback));
|
|
6670
6820
|
var planLabel = (value, fallback = "") => planText(value, fallback).replace(/\n+/g, " ");
|
|
6671
6821
|
var asRecord = (value) => typeof value === "object" && value !== null ? value : {};
|
|
@@ -6696,7 +6846,7 @@ function toTaskView2(t, index) {
|
|
|
6696
6846
|
prompt: typeof t.prompt === "string" ? sanitize(t.prompt) : void 0,
|
|
6697
6847
|
type: t.type === "user" ? "user" : "ai",
|
|
6698
6848
|
status: planLabel(t.status, "pending"),
|
|
6699
|
-
awaitingReason: t.status === "awaiting_user" && (0,
|
|
6849
|
+
awaitingReason: t.status === "awaiting_user" && (0, import_core32.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
|
|
6700
6850
|
dependencies: Array.isArray(t.dependencies) ? t.dependencies.map((id) => planLabel(id)) : [],
|
|
6701
6851
|
assignedRunner: typeof t.assignedRunner === "string" ? planLabel(t.assignedRunner) : void 0,
|
|
6702
6852
|
taskMode: typeof t.taskMode === "string" ? planLabel(t.taskMode) : void 0,
|
|
@@ -6729,12 +6879,12 @@ function applySettings(state, settings) {
|
|
|
6729
6879
|
plannerProvider: typeof settings.aiProvider === "string" ? settings.aiProvider : state.plannerProvider,
|
|
6730
6880
|
plannerEffort: typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : state.plannerEffort,
|
|
6731
6881
|
maxParallel: typeof settings.maxParallel === "number" ? settings.maxParallel : state.maxParallel,
|
|
6732
|
-
runnerTransport: (0,
|
|
6882
|
+
runnerTransport: (0, import_core32.isRunnerTransport)(settings.runnerTransport) ? settings.runnerTransport : state.runnerTransport,
|
|
6733
6883
|
allowlist: settings.modelAllowlist && typeof settings.modelAllowlist === "object" ? settings.modelAllowlist : state.allowlist
|
|
6734
6884
|
};
|
|
6735
6885
|
}
|
|
6736
6886
|
function runLabel(tasks, gate = null) {
|
|
6737
|
-
const running = tasks.filter(isTaskRunning);
|
|
6887
|
+
const running = tasks.filter(import_core23.isTaskRunning);
|
|
6738
6888
|
if (running.length > 0) {
|
|
6739
6889
|
const [first] = running;
|
|
6740
6890
|
const name = first.assignedRunner ? `${first.title} \xB7 ${first.assignedRunner}` : first.title;
|
|
@@ -6800,13 +6950,13 @@ function reduceAction(state, action) {
|
|
|
6800
6950
|
goal: "",
|
|
6801
6951
|
taskView: null,
|
|
6802
6952
|
pendingApprovals: [],
|
|
6803
|
-
queuedPrompts:
|
|
6953
|
+
queuedPrompts: import_core33.EMPTY_HOLD,
|
|
6804
6954
|
stopArmed: false,
|
|
6805
6955
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
6806
6956
|
});
|
|
6807
6957
|
case "chatRestored":
|
|
6808
6958
|
if (stale(state, action.sessionId)) return step(state);
|
|
6809
|
-
return step({ ...state, conversation: (0,
|
|
6959
|
+
return step({ ...state, conversation: (0, import_core33.fromTranscript)(action.history, action.researchLog, action.plannerUsage), scroll: 0 });
|
|
6810
6960
|
case "planUpdated": {
|
|
6811
6961
|
if (stale(state, action.sessionId)) return step(state);
|
|
6812
6962
|
const isolation = isolationOfPlan(action.plan);
|
|
@@ -6853,14 +7003,17 @@ function reduceAction(state, action) {
|
|
|
6853
7003
|
case "taskStarted": {
|
|
6854
7004
|
if (stale(state, action.sessionId)) return step(state);
|
|
6855
7005
|
const existing = findTask(state.tasks, action.taskId);
|
|
6856
|
-
if (existing && isTaskRunning(existing)) return step(state);
|
|
6857
|
-
const tasks = state.tasks.map((t) => t.id === action.taskId && !isTaskRunning(t) ? { ...t, status: "in_progress" } : t);
|
|
6858
|
-
const spoken = say(state, "system", (0,
|
|
7006
|
+
if (existing && (0, import_core23.isTaskRunning)(existing)) return step(state);
|
|
7007
|
+
const tasks = state.tasks.map((t) => t.id === action.taskId && !(0, import_core23.isTaskRunning)(t) ? { ...t, status: "in_progress" } : t);
|
|
7008
|
+
const spoken = say(state, "system", (0, import_core33.taskStartedNotice)(action.title, action.runner));
|
|
6859
7009
|
return step({ ...spoken, tasks, status: "executing", busyLabel: runLabel(tasks) });
|
|
6860
7010
|
}
|
|
7011
|
+
case "taskCheckpoint":
|
|
7012
|
+
if (stale(state, action.sessionId)) return step(state);
|
|
7013
|
+
return step(announceCheckpoint(state, action));
|
|
6861
7014
|
case "taskStatus": {
|
|
6862
7015
|
if (stale(state, action.sessionId) || !state.tasks.some((t) => t.id === action.taskId)) return step(state);
|
|
6863
|
-
const tasks = state.tasks.map((t) => t.id === action.taskId ? { ...t, status: action.status, awaitingReason: void 0 } : t);
|
|
7016
|
+
const tasks = state.tasks.map((t) => t.id === action.taskId ? { ...t, status: action.status, awaitingReason: void 0, checkpoint: void 0 } : t);
|
|
6864
7017
|
const status = runStatus(state, tasks);
|
|
6865
7018
|
return step({ ...state, status, tasks, busyLabel: status === "executing" ? runLabel(tasks, state.gate) : state.busyLabel });
|
|
6866
7019
|
}
|
|
@@ -6875,13 +7028,14 @@ function reduceAction(state, action) {
|
|
|
6875
7028
|
const isolation = update.isolation ?? t.isolation;
|
|
6876
7029
|
const transport = update.transport;
|
|
6877
7030
|
const awaitingReason = update.awaitingReason;
|
|
7031
|
+
const checkpoint = update.checkpoint;
|
|
6878
7032
|
const continuable = update.continuable === true;
|
|
6879
7033
|
const awaitingApproval = update.awaitingApproval;
|
|
6880
7034
|
const mergeGate = update.mergeGate;
|
|
6881
7035
|
const forcedPastGate = update.forcedPastGate;
|
|
6882
|
-
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)) {
|
|
7036
|
+
if (update.status !== t.status || idleSince !== (t.idleSince ?? null) || !sameIsolation(isolation, t.isolation) || !sameTransport(transport, t.transport) || awaitingReason !== t.awaitingReason || checkpoint !== t.checkpoint || continuable !== (t.continuable ?? false) || awaitingApproval !== t.awaitingApproval || !sameList(mergeGate, t.mergeGate) || !sameList(forcedPastGate, t.forcedPastGate)) {
|
|
6883
7037
|
changed = true;
|
|
6884
|
-
return { ...t, status: update.status, idleSince, isolation, transport, awaitingReason, continuable, awaitingApproval, mergeGate, forcedPastGate };
|
|
7038
|
+
return { ...t, status: update.status, idleSince, isolation, transport, awaitingReason, checkpoint, continuable, awaitingApproval, mergeGate, forcedPastGate };
|
|
6885
7039
|
}
|
|
6886
7040
|
return t;
|
|
6887
7041
|
});
|
|
@@ -6964,7 +7118,7 @@ function reduceAction(state, action) {
|
|
|
6964
7118
|
busyLabel: "",
|
|
6965
7119
|
planApproved: false,
|
|
6966
7120
|
pendingApprovals: [],
|
|
6967
|
-
queuedPrompts:
|
|
7121
|
+
queuedPrompts: import_core33.EMPTY_HOLD,
|
|
6968
7122
|
stopArmed: false,
|
|
6969
7123
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
6970
7124
|
});
|
|
@@ -7031,7 +7185,7 @@ function sameGate(a, b) {
|
|
|
7031
7185
|
return a.paused === b.paused && JSON.stringify(a.handoff) === JSON.stringify(b.handoff);
|
|
7032
7186
|
}
|
|
7033
7187
|
function runStatus(state, tasks) {
|
|
7034
|
-
if (tasks.some(isTaskRunning)) return "executing";
|
|
7188
|
+
if (tasks.some(import_core23.isTaskRunning)) return "executing";
|
|
7035
7189
|
if (tasks.some((t) => t.status === "awaiting_user") || state.gate?.paused) return "executing";
|
|
7036
7190
|
return plannerInFlight(state) ? state.status : "idle";
|
|
7037
7191
|
}
|
|
@@ -7100,7 +7254,7 @@ function submit(state) {
|
|
|
7100
7254
|
return step(cleared, [{ type: "sendTaskMessage", sessionId: state.sessionId, taskId, text }]);
|
|
7101
7255
|
}
|
|
7102
7256
|
if (plannerInFlight(state)) {
|
|
7103
|
-
return step({ ...cleared, queuedPrompts: (0,
|
|
7257
|
+
return step({ ...cleared, queuedPrompts: (0, import_core33.holdPrompt)(state.queuedPrompts, text) });
|
|
7104
7258
|
}
|
|
7105
7259
|
const spoken = say(cleared, "user", text);
|
|
7106
7260
|
const effect = state.sessionId ? { type: "sendMessage", sessionId: state.sessionId, message: text } : { type: "startConversation", goal: text };
|
|
@@ -7160,10 +7314,10 @@ function createApp(deps) {
|
|
|
7160
7314
|
|
|
7161
7315
|
// src/tui/effects.ts
|
|
7162
7316
|
var import_child_process4 = require("child_process");
|
|
7163
|
-
var
|
|
7317
|
+
var import_core35 = require("@ordewell/core");
|
|
7164
7318
|
|
|
7165
7319
|
// src/tui/inbound.ts
|
|
7166
|
-
var
|
|
7320
|
+
var import_core34 = require("@ordewell/core");
|
|
7167
7321
|
var CONVERSATION_TYPES = /* @__PURE__ */ new Set([
|
|
7168
7322
|
"planner_message",
|
|
7169
7323
|
"planner_turn_started",
|
|
@@ -7378,7 +7532,8 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
7378
7532
|
idleSince: task.idleSince ?? null,
|
|
7379
7533
|
isolation: task.isolation,
|
|
7380
7534
|
transport: task.transport,
|
|
7381
|
-
awaitingReason: (0,
|
|
7535
|
+
awaitingReason: (0, import_core34.isAwaitingReason)(task.awaitingReason) ? task.awaitingReason : void 0,
|
|
7536
|
+
...typeof task.checkpoint === "string" && task.checkpoint ? { checkpoint: sanitize(task.checkpoint) } : {},
|
|
7382
7537
|
...task.continuable ? { continuable: true } : {},
|
|
7383
7538
|
awaitingApproval: typeof task.awaitingApproval === "number" && task.awaitingApproval > 0 ? task.awaitingApproval : void 0,
|
|
7384
7539
|
...task.mergeGate?.length ? { mergeGate: task.mergeGate.map(String) } : {},
|
|
@@ -7392,10 +7547,10 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
7392
7547
|
case "task_started":
|
|
7393
7548
|
dispatch({ type: "taskStarted", taskId: String(event.taskId), title: String(event.title ?? event.taskId), runner: event.runner, sessionId });
|
|
7394
7549
|
return;
|
|
7395
|
-
// The extension shows these in its checkpoint panel; here they are
|
|
7396
|
-
// transcript
|
|
7550
|
+
// The extension shows these in its checkpoint panel; here they are a
|
|
7551
|
+
// transcript line, and the task view carries the card that answers them.
|
|
7397
7552
|
case "checkpoint":
|
|
7398
|
-
dispatch({ type: "
|
|
7553
|
+
dispatch({ type: "taskCheckpoint", taskId: String(event.taskId), title: String(event.taskTitle), summary: event.summary, sessionId });
|
|
7399
7554
|
return;
|
|
7400
7555
|
case "review_needed":
|
|
7401
7556
|
dispatch({ type: "notice", message: "Plan needs your sign-off \u2014 /approve to continue." });
|
|
@@ -7553,20 +7708,18 @@ async function perform(effect, deps) {
|
|
|
7553
7708
|
return;
|
|
7554
7709
|
case "taskAction": {
|
|
7555
7710
|
const { sessionId, taskId, action } = effect;
|
|
7556
|
-
const request = action === "
|
|
7711
|
+
const request = action === "cancel" || action === "retry" || action === "force-start" ? () => api.taskControl(sessionId, taskId, action) : (0, import_core35.markRequestFor)(action) === "uncomplete" ? () => api.markTaskIncomplete(sessionId, taskId) : () => api.markTaskComplete(sessionId, taskId);
|
|
7557
7712
|
await (effect.watch ? withExecutionStream(deps, sessionId, request) : request());
|
|
7558
7713
|
await refreshPlan(deps, sessionId);
|
|
7559
7714
|
return;
|
|
7560
7715
|
}
|
|
7561
|
-
case "addTask":
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
prompt: effect.title,
|
|
7566
|
-
type: "ai"
|
|
7567
|
-
});
|
|
7716
|
+
case "addTask": {
|
|
7717
|
+
const fields = (0, import_core35.newTaskFields)(effect.title);
|
|
7718
|
+
if (!fields) return;
|
|
7719
|
+
await api.addTask(effect.sessionId, { ...fields });
|
|
7568
7720
|
await refreshPlan(deps, effect.sessionId);
|
|
7569
7721
|
return;
|
|
7722
|
+
}
|
|
7570
7723
|
case "updateTask":
|
|
7571
7724
|
await api.updateTask(effect.sessionId, effect.taskId, effect.changes);
|
|
7572
7725
|
await refreshPlan(deps, effect.sessionId);
|
|
@@ -7593,6 +7746,13 @@ async function perform(effect, deps) {
|
|
|
7593
7746
|
}
|
|
7594
7747
|
return;
|
|
7595
7748
|
}
|
|
7749
|
+
// The card goes when the daemon's next status says the task left the
|
|
7750
|
+
// checkpoint; a refusal (settled elsewhere, withdrawn) surfaces as the
|
|
7751
|
+
// failure the daemon words.
|
|
7752
|
+
case "answerTaskCheckpoint":
|
|
7753
|
+
if (effect.answer === "approve") await api.approveTaskCheckpoint(effect.sessionId, effect.taskId);
|
|
7754
|
+
else await api.rejectTaskCheckpoint(effect.sessionId, effect.taskId, effect.reason);
|
|
7755
|
+
return;
|
|
7596
7756
|
// A task with a saved log ran structured, whatever its `transport` says
|
|
7597
7757
|
// after a reload; it has no tmux window, so open the log rather than fail.
|
|
7598
7758
|
case "openTaskTerminal": {
|
|
@@ -7642,6 +7802,16 @@ async function perform(effect, deps) {
|
|
|
7642
7802
|
if (!removed) dispatch({ type: "notice", message: "That message was already delivered to the task." });
|
|
7643
7803
|
return;
|
|
7644
7804
|
}
|
|
7805
|
+
// The task log shows the forced message at the head of the queue, then
|
|
7806
|
+
// the interrupt, then the turn it opens; nothing to dispatch here.
|
|
7807
|
+
case "forceSendTaskMessage":
|
|
7808
|
+
await api.forceSendTaskMessage(effect.sessionId, effect.taskId, effect.text);
|
|
7809
|
+
return;
|
|
7810
|
+
case "forceSendQueuedTaskMessage": {
|
|
7811
|
+
const { sent } = await api.forceSendQueuedTaskMessage(effect.sessionId, effect.taskId, effect.messageId);
|
|
7812
|
+
if (!sent) dispatch({ type: "notice", message: "The runner already has that message; it reads it after its current step." });
|
|
7813
|
+
return;
|
|
7814
|
+
}
|
|
7645
7815
|
case "interruptTask":
|
|
7646
7816
|
await api.interruptTask(effect.sessionId, effect.taskId);
|
|
7647
7817
|
dispatch({ type: "notice", message: "Interrupting the task\u2026" });
|
|
@@ -7665,7 +7835,7 @@ async function perform(effect, deps) {
|
|
|
7665
7835
|
dispatch({ type: "notice", message: `Orchestrator model set to ${effect.modelId}.` });
|
|
7666
7836
|
return;
|
|
7667
7837
|
case "setPlanner": {
|
|
7668
|
-
const meta =
|
|
7838
|
+
const meta = import_core35.ALL_PROVIDERS[effect.provider];
|
|
7669
7839
|
if (!meta) throw new Error(`Unknown planner: ${effect.provider}`);
|
|
7670
7840
|
const env = { AI_PROVIDER: effect.provider };
|
|
7671
7841
|
const settings = await api.updateSettings({ env });
|
|
@@ -7677,7 +7847,7 @@ async function perform(effect, deps) {
|
|
|
7677
7847
|
});
|
|
7678
7848
|
dispatch({
|
|
7679
7849
|
type: "notice",
|
|
7680
|
-
message: `${(0,
|
|
7850
|
+
message: `${(0, import_core35.isCliProvider)(effect.provider) ? `Planning with ${meta.label} \u2014 no API key needed.` : `Planner set to ${meta.label}.`} ${plannerModelNotice(recall2)}`
|
|
7681
7851
|
});
|
|
7682
7852
|
await loadModels(deps);
|
|
7683
7853
|
return;
|
|
@@ -7689,7 +7859,7 @@ async function perform(effect, deps) {
|
|
|
7689
7859
|
dispatch({ type: "notice", message: `Planner effort set to ${effect.effort || "the runner default"}.` });
|
|
7690
7860
|
return;
|
|
7691
7861
|
case "setApiKey": {
|
|
7692
|
-
const meta =
|
|
7862
|
+
const meta = import_core35.ALL_PROVIDERS[effect.provider];
|
|
7693
7863
|
if (!meta) throw new Error(`Unknown provider: ${effect.provider}`);
|
|
7694
7864
|
await api.updateSettings({ env: { [meta.apiKeyEnvVar]: effect.key } });
|
|
7695
7865
|
deps.setEnvVar(meta.apiKeyEnvVar, effect.key);
|
|
@@ -7727,7 +7897,7 @@ async function perform(effect, deps) {
|
|
|
7727
7897
|
return;
|
|
7728
7898
|
case "setAutonomous":
|
|
7729
7899
|
deps.setEnvVar("ORDEWELL_AUTONOMOUS_MODE", String(effect.enabled));
|
|
7730
|
-
dispatch({ type: "notice", message: `Autonomy level: ${(0,
|
|
7900
|
+
dispatch({ type: "notice", message: `Autonomy level: ${(0, import_core35.autonomyLevelLabel)(effect.enabled)} for new plans.` });
|
|
7731
7901
|
return;
|
|
7732
7902
|
case "setMouseCapture":
|
|
7733
7903
|
deps.setMouseCapture(effect.enabled);
|
|
@@ -7861,7 +8031,7 @@ function osc52(text) {
|
|
|
7861
8031
|
function copySelection(deps, text) {
|
|
7862
8032
|
if (!text) return;
|
|
7863
8033
|
const lines = text.split("\n").length;
|
|
7864
|
-
const command = (0,
|
|
8034
|
+
const command = (0, import_core35.clipboardCopyCommand)(deps.hasBin);
|
|
7865
8035
|
if (command) {
|
|
7866
8036
|
try {
|
|
7867
8037
|
(deps.pipeToClipboard ?? pipeToClipboardDefault)(command, text);
|
|
@@ -7978,6 +8148,7 @@ var CONTROL2 = {
|
|
|
7978
8148
|
"": "ctrl-o",
|
|
7979
8149
|
"": "ctrl-p",
|
|
7980
8150
|
"": "ctrl-r",
|
|
8151
|
+
"": "ctrl-s",
|
|
7981
8152
|
"": "ctrl-t",
|
|
7982
8153
|
"": "ctrl-u",
|
|
7983
8154
|
"": "ctrl-w",
|
|
@@ -8251,7 +8422,7 @@ async function handleTui(subArgs) {
|
|
|
8251
8422
|
}
|
|
8252
8423
|
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
8253
8424
|
try {
|
|
8254
|
-
(0,
|
|
8425
|
+
(0, import_core36.assertWorkspaceExists)(workspace);
|
|
8255
8426
|
} catch {
|
|
8256
8427
|
console.error(
|
|
8257
8428
|
`Workspace "${workspace}" does not exist.
|
|
@@ -8260,7 +8431,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8260
8431
|
);
|
|
8261
8432
|
process.exit(1);
|
|
8262
8433
|
}
|
|
8263
|
-
registerSkillCommands((0,
|
|
8434
|
+
registerSkillCommands((0, import_core36.createSkillsService)(workspace).listSkills());
|
|
8264
8435
|
const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
|
|
8265
8436
|
const targetPort = explicitPort ? resolvePort(subArgs) : await findFreePort();
|
|
8266
8437
|
const { port, owned: ownedAtLaunch } = await ensureDaemonOwned(targetPort, { detached: false });
|
|
@@ -8277,7 +8448,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8277
8448
|
workspace,
|
|
8278
8449
|
port,
|
|
8279
8450
|
dispatch: (action) => app.dispatch(action),
|
|
8280
|
-
newSessionId: () => (0,
|
|
8451
|
+
newSessionId: () => (0, import_core36.mintSessionId)(),
|
|
8281
8452
|
setEnvVar: (key, value) => {
|
|
8282
8453
|
writeEnvVar(findEnvFile(), key, value);
|
|
8283
8454
|
process.env[key] = value;
|
|
@@ -8368,6 +8539,7 @@ var COMMANDS = {
|
|
|
8368
8539
|
"retry": handleRetry,
|
|
8369
8540
|
"cancel": handleCancel,
|
|
8370
8541
|
continue: handleContinue,
|
|
8542
|
+
checkpoint: handleCheckpoint,
|
|
8371
8543
|
"add-task": handleAddTask,
|
|
8372
8544
|
"remove-task": handleRemoveTask,
|
|
8373
8545
|
"task-runner": handleTaskRunner,
|
|
@@ -8401,7 +8573,7 @@ function cliVersion() {
|
|
|
8401
8573
|
// src/main.ts
|
|
8402
8574
|
var TUI_FLAGS = /* @__PURE__ */ new Set(["--workspace", "--port"]);
|
|
8403
8575
|
async function main() {
|
|
8404
|
-
(0,
|
|
8576
|
+
(0, import_core37.migrateOldConfigDir)();
|
|
8405
8577
|
loadEnvFile();
|
|
8406
8578
|
const argv = process.argv.slice(2);
|
|
8407
8579
|
if (TUI_FLAGS.has(argv[0] ?? "")) argv.unshift("tui");
|