@ordewell/cli 0.7.0 → 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 +392 -254
- 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
|
|
|
@@ -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)
|
|
@@ -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);
|
|
@@ -3311,15 +3379,15 @@ function handleAuto(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" },
|
|
@@ -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) {
|
|
@@ -4161,16 +4230,16 @@ function resultLines(block, room, detailAll) {
|
|
|
4161
4230
|
const fit2 = (line) => detailAll ? wrap(line, room) : [truncate(line, room)];
|
|
4162
4231
|
const outcome = unsettledOutcome(block);
|
|
4163
4232
|
if (outcome) {
|
|
4164
|
-
const [reason, ...rest] = (0,
|
|
4233
|
+
const [reason, ...rest] = (0, import_core22.outputLines)(block.output).map(sanitize);
|
|
4165
4234
|
const status = fit2(reason ? `${outcome.label}: ${reason}` : outcome.label).map(outcome.paint);
|
|
4166
4235
|
return detailAll ? [...status, ...rest.flatMap(fit2).map(style.grey)] : status;
|
|
4167
4236
|
}
|
|
4168
4237
|
if (detailAll) {
|
|
4169
|
-
const lines2 = (0,
|
|
4238
|
+
const lines2 = (0, import_core22.outputLines)(block.output).map(sanitize);
|
|
4170
4239
|
const note = lines2.length > PREVIEW_LINES ? ["\u2026 (ctrl+o to collapse)"] : [];
|
|
4171
4240
|
return lines2.length > 0 ? [...lines2.flatMap(fit2), ...note].map(style.grey) : [style.grey("(no output)")];
|
|
4172
4241
|
}
|
|
4173
|
-
const { lines, hiddenLineCount } = (0,
|
|
4242
|
+
const { lines, hiddenLineCount } = (0, import_core22.outputPreview)(block.output, PREVIEW_LINES);
|
|
4174
4243
|
if (lines.length === 0) return [style.grey("(no output)")];
|
|
4175
4244
|
const more = hiddenLineCount > 0 ? [`\u2026 +${countOf(hiddenLineCount, "line")} (ctrl+o to expand)`] : [];
|
|
4176
4245
|
return [...lines.map(sanitize), ...more].map((line) => style.grey(truncate(line, room)));
|
|
@@ -4184,7 +4253,7 @@ var DIFF_PAINT = {
|
|
|
4184
4253
|
};
|
|
4185
4254
|
var DIFF_SIGN = { added: "+", removed: "-", context: " ", gap: "" };
|
|
4186
4255
|
function diffLines(block, diff, room, detailAll) {
|
|
4187
|
-
const rows = (0,
|
|
4256
|
+
const rows = (0, import_core22.diffRows)(block.output);
|
|
4188
4257
|
const gutter = Math.max(0, ...rows.map((row) => String(row.line ?? "").length));
|
|
4189
4258
|
const draw = (row) => {
|
|
4190
4259
|
const text = row.kind === "gap" ? `${" ".repeat(gutter)} \u22EE` : `${String(row.line ?? "").padStart(gutter)} ${DIFF_SIGN[row.kind]} ${sanitize(row.text)}`;
|
|
@@ -4193,7 +4262,7 @@ function diffLines(block, diff, room, detailAll) {
|
|
|
4193
4262
|
const shown = detailAll ? rows : rows.slice(0, DIFF_PREVIEW_ROWS);
|
|
4194
4263
|
const hidden = rows.length - shown.length;
|
|
4195
4264
|
const more = hidden > 0 ? [style.grey(`\u2026 +${countOf(hidden, "line")} (ctrl+o to expand)`)] : [];
|
|
4196
|
-
return [(0,
|
|
4265
|
+
return [(0, import_core22.diffSummary)(diff), ...shown.flatMap(draw), ...more];
|
|
4197
4266
|
}
|
|
4198
4267
|
function toolLines(block, cols, detailAll) {
|
|
4199
4268
|
const room = Math.max(1, cols - OUTPUT_INDENT.length);
|
|
@@ -4249,6 +4318,19 @@ function approvalLine(block, cols) {
|
|
|
4249
4318
|
const line = `${mark} ${status} \xB7 ${APPROVAL_KIND[block.kind]}: ${firstLine(block.subject)}${note}`;
|
|
4250
4319
|
return `${paint(mark)}${truncate(line, cols).slice(mark.length)}`;
|
|
4251
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
|
+
}
|
|
4252
4334
|
function planLine(block, cols) {
|
|
4253
4335
|
const count = block.taskCount === void 0 ? "" : ` (${countOf(block.taskCount, "task")})`;
|
|
4254
4336
|
const text = block.status === "building" ? "Building plan\u2026" : `Plan ${block.status}${count}`;
|
|
@@ -4281,11 +4363,13 @@ function drawn(block, cols, detailAll) {
|
|
|
4281
4363
|
drawnBlocks.set(block, { cols, detailAll, lines });
|
|
4282
4364
|
return lines;
|
|
4283
4365
|
}
|
|
4284
|
-
function conversationLines(blocks, cols, detailAll) {
|
|
4366
|
+
function conversationLines(blocks, cols, detailAll, answering) {
|
|
4285
4367
|
const lines = [];
|
|
4286
4368
|
for (const block of blocks) {
|
|
4287
4369
|
if (block.type === "usage") continue;
|
|
4288
|
-
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("");
|
|
4289
4373
|
}
|
|
4290
4374
|
return lines;
|
|
4291
4375
|
}
|
|
@@ -4313,7 +4397,7 @@ function tokenLine(blocks, cols) {
|
|
|
4313
4397
|
}
|
|
4314
4398
|
|
|
4315
4399
|
// src/tui/state.ts
|
|
4316
|
-
var
|
|
4400
|
+
var import_core23 = require("@ordewell/core");
|
|
4317
4401
|
var SKILL_IDS = ["tdd", "verify"];
|
|
4318
4402
|
function noSkills() {
|
|
4319
4403
|
return { tdd: false, verify: false };
|
|
@@ -4325,8 +4409,7 @@ function visibleItems(picker2) {
|
|
|
4325
4409
|
(i) => i.id.toLowerCase().includes(filter) || i.label.toLowerCase().includes(filter) || (i.detail ?? "").toLowerCase().includes(filter)
|
|
4326
4410
|
);
|
|
4327
4411
|
}
|
|
4328
|
-
var
|
|
4329
|
-
var anyTaskRunning = (state) => state.tasks.some(isTaskRunning);
|
|
4412
|
+
var anyTaskRunning = (state) => state.tasks.some(import_core23.isTaskRunning);
|
|
4330
4413
|
var plannerInFlight = (state) => state.status === "planning" || state.status === "researching";
|
|
4331
4414
|
function planRows(state) {
|
|
4332
4415
|
const rows = [];
|
|
@@ -4346,6 +4429,9 @@ function selectedPlanRow(state) {
|
|
|
4346
4429
|
function waitingApproval(tv) {
|
|
4347
4430
|
return tv.view.blocks.find((b) => b.type === "approval" && b.status === "pending" && b.approvalId !== void 0);
|
|
4348
4431
|
}
|
|
4432
|
+
function waitingCheckpoint(task) {
|
|
4433
|
+
return task?.status === "awaiting_user" && task.awaitingReason === "checkpoint" ? task.checkpoint : void 0;
|
|
4434
|
+
}
|
|
4349
4435
|
function continuesTask(task) {
|
|
4350
4436
|
return task?.continuable === true && (task.status === "completed" || task.status === "failed");
|
|
4351
4437
|
}
|
|
@@ -4360,9 +4446,9 @@ function findTask(tasks, id) {
|
|
|
4360
4446
|
function initialState(overrides = {}) {
|
|
4361
4447
|
return {
|
|
4362
4448
|
editor: emptyEditor(),
|
|
4363
|
-
conversation:
|
|
4449
|
+
conversation: import_core23.EMPTY_CONVERSATION,
|
|
4364
4450
|
taskView: null,
|
|
4365
|
-
turnGate:
|
|
4451
|
+
turnGate: import_core23.NO_TURN,
|
|
4366
4452
|
detailAll: false,
|
|
4367
4453
|
status: "idle",
|
|
4368
4454
|
busyLabel: "",
|
|
@@ -4387,7 +4473,7 @@ function initialState(overrides = {}) {
|
|
|
4387
4473
|
orchestratorModel: "",
|
|
4388
4474
|
plannerProvider: "",
|
|
4389
4475
|
plannerEffort: "",
|
|
4390
|
-
maxParallel:
|
|
4476
|
+
maxParallel: import_core23.DEFAULT_MAX_PARALLEL,
|
|
4391
4477
|
configuredProviders: [],
|
|
4392
4478
|
allowlist: {},
|
|
4393
4479
|
autonomous: true,
|
|
@@ -4399,7 +4485,7 @@ function initialState(overrides = {}) {
|
|
|
4399
4485
|
gate: null,
|
|
4400
4486
|
overlay: null,
|
|
4401
4487
|
pendingApprovals: [],
|
|
4402
|
-
queuedPrompts:
|
|
4488
|
+
queuedPrompts: import_core23.EMPTY_HOLD,
|
|
4403
4489
|
stopArmed: false,
|
|
4404
4490
|
stopArmToken: 0,
|
|
4405
4491
|
stopRequested: false,
|
|
@@ -4447,7 +4533,7 @@ function chatEditorRoomFor(state, active) {
|
|
|
4447
4533
|
}
|
|
4448
4534
|
|
|
4449
4535
|
// src/tui/layout.ts
|
|
4450
|
-
var
|
|
4536
|
+
var import_core24 = require("@ordewell/core");
|
|
4451
4537
|
var CHROME_ROWS = 4;
|
|
4452
4538
|
function chatInputWrap(state) {
|
|
4453
4539
|
const active = state.focus === "chat" && !state.overlay;
|
|
@@ -4456,7 +4542,7 @@ function chatInputWrap(state) {
|
|
|
4456
4542
|
}
|
|
4457
4543
|
function footerHints(state) {
|
|
4458
4544
|
const selected = selectedPlanRow(state)?.task;
|
|
4459
|
-
const markHint = selected
|
|
4545
|
+
const markHint = selected && (0, import_core24.markAction)(selected) === "uncomplete" ? "m undone" : "m done";
|
|
4460
4546
|
const resolveHint = selected?.isolation?.state === "conflict" ? ["x resolve conflict"] : [];
|
|
4461
4547
|
const planning = plannerInFlight(state) && !state.taskView;
|
|
4462
4548
|
const escHint = !planning ? null : state.queuedPrompts.length > 0 ? "esc unsend" : "esc \xD72 stop planning";
|
|
@@ -4487,7 +4573,7 @@ function footerHints(state) {
|
|
|
4487
4573
|
"tab chat"
|
|
4488
4574
|
];
|
|
4489
4575
|
}
|
|
4490
|
-
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"] : [];
|
|
4491
4577
|
return [
|
|
4492
4578
|
"/help",
|
|
4493
4579
|
"tab plan",
|
|
@@ -4508,20 +4594,25 @@ function queuedBubble(text, cols) {
|
|
|
4508
4594
|
function taskQueuedRows(state, cols) {
|
|
4509
4595
|
const tv = state.taskView;
|
|
4510
4596
|
if (!tv) return [];
|
|
4511
|
-
return tv.view.queued.flatMap((message, i) => queuedTaskBubble(message
|
|
4597
|
+
return tv.view.queued.flatMap((message, i) => queuedTaskBubble(message, i === tv.queuedIndex, cols));
|
|
4512
4598
|
}
|
|
4513
|
-
function
|
|
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";
|
|
4603
|
+
}
|
|
4604
|
+
function queuedTaskBubble(message, selected, cols) {
|
|
4605
|
+
const { text } = message;
|
|
4514
4606
|
const marker2 = selected ? style.accent("\u276F") : style.grey("\u25C7");
|
|
4515
|
-
const tag = selected
|
|
4607
|
+
const tag = queuedTaskTag(message, selected);
|
|
4516
4608
|
const room = Math.max(1, cols - width(`\u276F \u25C7 ${tag}`));
|
|
4517
4609
|
return wrap(text, room).map((line, i) => i === 0 ? truncate(`${marker2} ${selected ? style.bold(line) : line}${style.grey(tag)}`, cols) : truncate(` ${line}`, cols));
|
|
4518
4610
|
}
|
|
4519
4611
|
function taskActivity(task, working) {
|
|
4520
|
-
|
|
4612
|
+
const approvals = (0, import_core24.approvalLabel)(task?.awaitingApproval);
|
|
4613
|
+
if (approvals) return inRow(approvals);
|
|
4521
4614
|
if (working) return "working";
|
|
4522
|
-
if (task?.status === "awaiting_user")
|
|
4523
|
-
return task.awaitingReason ? AWAITING_LABEL[task.awaitingReason] : "waiting for your input";
|
|
4524
|
-
}
|
|
4615
|
+
if (task?.status === "awaiting_user") return inRow((0, import_core24.awaitingLabel)(task) ?? "Waiting for your input");
|
|
4525
4616
|
return task?.status ?? "";
|
|
4526
4617
|
}
|
|
4527
4618
|
function taskHeaderLines(state, tv, cols) {
|
|
@@ -4533,7 +4624,8 @@ function taskHeaderLines(state, tv, cols) {
|
|
|
4533
4624
|
taskActivity(task, tv.view.working)
|
|
4534
4625
|
].filter(Boolean);
|
|
4535
4626
|
const approval = waitingApproval(tv);
|
|
4536
|
-
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");
|
|
4537
4629
|
const index = tv.attempts.indexOf(tv.attempt);
|
|
4538
4630
|
if (tv.attempts.length > 1) hint.push(`alt\u2190/\u2192 attempt ${index >= 0 ? index + 1 : 1}/${tv.attempts.length}`);
|
|
4539
4631
|
hint.push("esc back");
|
|
@@ -4566,10 +4658,10 @@ function bodyRows(state) {
|
|
|
4566
4658
|
return Math.max(0, state.rows - CHROME_ROWS - input - footer + 2);
|
|
4567
4659
|
}
|
|
4568
4660
|
var chatBodyMemo = null;
|
|
4569
|
-
function chatBodyLines(blocks, cols, detailAll) {
|
|
4661
|
+
function chatBodyLines(blocks, cols, detailAll, answering) {
|
|
4570
4662
|
const memo = chatBodyMemo;
|
|
4571
4663
|
if (memo && memo.blocks === blocks && memo.cols === cols && memo.detailAll === detailAll) return memo.lines;
|
|
4572
|
-
const lines = conversationLines(blocks, cols, detailAll);
|
|
4664
|
+
const lines = conversationLines(blocks, cols, detailAll, answering);
|
|
4573
4665
|
chatBodyMemo = { blocks, cols, detailAll, lines };
|
|
4574
4666
|
return lines;
|
|
4575
4667
|
}
|
|
@@ -4577,7 +4669,10 @@ function chatLayout(state, rows, cols) {
|
|
|
4577
4669
|
const tv = state.taskView;
|
|
4578
4670
|
const blocks = tv ? tv.view.blocks : state.conversation.blocks;
|
|
4579
4671
|
const welcome = !tv && state.tasks.length === 0;
|
|
4580
|
-
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;
|
|
4581
4676
|
const transcript = !welcome ? body : body.length === 0 ? welcomeLines(state, cols) : [...welcomeLines(state, cols), "", ...body];
|
|
4582
4677
|
const bubbles = tv ? taskQueuedRows(state, cols) : queuedPromptRows(state, cols);
|
|
4583
4678
|
const lines = [...transcript, ...bubbles.length > 0 ? ["", ...bubbles] : []];
|
|
@@ -4592,9 +4687,9 @@ function chatScrollMax(state) {
|
|
|
4592
4687
|
}
|
|
4593
4688
|
function plannerSetup(state) {
|
|
4594
4689
|
const provider = state.plannerProvider;
|
|
4595
|
-
const registration =
|
|
4690
|
+
const registration = import_core24.ALL_PROVIDERS[provider];
|
|
4596
4691
|
if (!registration) return { label: "not set", ready: false };
|
|
4597
|
-
const runner = (0,
|
|
4692
|
+
const runner = (0, import_core24.runnerForProvider)(provider);
|
|
4598
4693
|
const ready = runner ? state.runners.some((r) => r.id === runner) : state.configuredProviders.includes(provider);
|
|
4599
4694
|
return { label: registration.label, ready };
|
|
4600
4695
|
}
|
|
@@ -4681,45 +4776,34 @@ function welcomeLines(state, cols) {
|
|
|
4681
4776
|
return lines;
|
|
4682
4777
|
}
|
|
4683
4778
|
var SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4684
|
-
var
|
|
4685
|
-
|
|
4779
|
+
var STATUS_PAINT = {
|
|
4780
|
+
done: style.green,
|
|
4686
4781
|
running: style.yellow,
|
|
4687
|
-
|
|
4782
|
+
quiet: style.cyan,
|
|
4688
4783
|
failed: style.red,
|
|
4689
4784
|
blocked: style.red,
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
skipped: style.grey
|
|
4785
|
+
awaiting: style.yellow,
|
|
4786
|
+
todo: (text) => text
|
|
4693
4787
|
};
|
|
4694
4788
|
var STATUS_ICON = {
|
|
4695
|
-
|
|
4789
|
+
done: "\u2713",
|
|
4696
4790
|
failed: "\u2717",
|
|
4697
4791
|
blocked: "!",
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
approved: "\xB7",
|
|
4702
|
-
pending: "\xB7"
|
|
4703
|
-
};
|
|
4704
|
-
var AWAITING_LABEL = {
|
|
4705
|
-
input: "waiting for your input",
|
|
4706
|
-
checkpoint: "checkpoint",
|
|
4707
|
-
conflict: "merge conflict",
|
|
4708
|
-
"files-changed": "changed tracked files"
|
|
4792
|
+
awaiting: "?",
|
|
4793
|
+
quiet: "~",
|
|
4794
|
+
todo: "\xB7"
|
|
4709
4795
|
};
|
|
4710
|
-
|
|
4711
|
-
return count > 1 ? `waiting for approval (${count})` : "waiting for approval";
|
|
4712
|
-
}
|
|
4713
|
-
var IDLE_ICON = "~";
|
|
4796
|
+
var inRow = (label) => label.charAt(0).toLowerCase() + label.slice(1);
|
|
4714
4797
|
var FIRST_ROW_LINE = 2;
|
|
4715
4798
|
function planLayout(state, rows, cols) {
|
|
4716
4799
|
const done = state.tasks.filter((t) => t.status === "completed").length;
|
|
4717
4800
|
const lines = [style.bold(`Plan ${done}/${state.tasks.length}`), ""];
|
|
4718
4801
|
const rowSpans = [];
|
|
4802
|
+
const orderOf = new Map(state.tasks.map((task) => [task.id, task.order]));
|
|
4719
4803
|
let editorLine;
|
|
4720
4804
|
planRows(state).forEach((row, i) => {
|
|
4721
4805
|
const taskStart = lines.length;
|
|
4722
|
-
const renderedTask = taskLines(state, row, i, cols);
|
|
4806
|
+
const renderedTask = taskLines(state, row, i, cols, orderOf);
|
|
4723
4807
|
if (row.task.id === state.expandedTaskId && renderedTask.editorLine !== void 0) {
|
|
4724
4808
|
editorLine = taskStart + renderedTask.editorLine;
|
|
4725
4809
|
}
|
|
@@ -4775,18 +4859,16 @@ function planScrollExtent(state) {
|
|
|
4775
4859
|
}
|
|
4776
4860
|
return planLayout(state, bodyRows(state), cols);
|
|
4777
4861
|
}
|
|
4778
|
-
function taskLines(state,
|
|
4779
|
-
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) });
|
|
4780
4865
|
const selected = state.focus === "plan" && index === state.selectedTask;
|
|
4781
4866
|
const expanded = state.expandedTaskId === task.id;
|
|
4782
|
-
const
|
|
4783
|
-
const
|
|
4784
|
-
const
|
|
4785
|
-
const rawIcon = idle ? IDLE_ICON : running ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[task.status] ?? "\xB7";
|
|
4786
|
-
const icon = paint(rawIcon);
|
|
4787
|
-
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");
|
|
4788
4870
|
const caret = selected ? style.cyan("\u276F") : " ";
|
|
4789
|
-
const orderLabel = parent ?
|
|
4871
|
+
const orderLabel = parent ? row.orderLabel : row.orderLabel.padStart(2);
|
|
4790
4872
|
const indent = parent ? " " : "";
|
|
4791
4873
|
const head = `${indent}${caret} ${icon} ${orderLabel} ${kind} `;
|
|
4792
4874
|
const titleRoom = Math.max(1, cols - width(head));
|
|
@@ -4803,58 +4885,51 @@ function taskLines(state, row, index, cols) {
|
|
|
4803
4885
|
});
|
|
4804
4886
|
const model = task.assignedModel?.modelLabel ?? (task.type === "ai" ? "default model" : "");
|
|
4805
4887
|
const effort = task.type === "ai" ? `effort: ${task.assignedModel?.thinkingEffort ?? "default"}` : "";
|
|
4806
|
-
const
|
|
4807
|
-
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") : ""}` : "";
|
|
4808
4889
|
const runner = task.assignedRunner ?? "";
|
|
4809
4890
|
const bodyPad = parent ? " " : " ";
|
|
4810
4891
|
const structured = task.transport?.kind === "structured";
|
|
4811
|
-
const
|
|
4812
|
-
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) : "";
|
|
4813
4893
|
const meta = [activity, runner, structured ? "structured" : "", model].filter(Boolean).join(" \xB7 ");
|
|
4814
4894
|
if (meta) lines.push(style.grey(truncate(`${bodyPad}${meta}`, cols)));
|
|
4815
4895
|
if (task.transport?.fallback) lines.push(style.yellow(truncate(`${bodyPad}terminal: ${task.transport.fallback}`, cols)));
|
|
4816
4896
|
if (effort || mode) lines.push(style.grey(truncate(`${bodyPad}${[effort, mode].filter(Boolean).join(" \xB7 ")}`, cols)));
|
|
4817
|
-
if (
|
|
4818
|
-
const where =
|
|
4819
|
-
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})` : "";
|
|
4820
4900
|
lines.push(style.red(truncate(`${bodyPad}\u26A0 merge conflict${where}${files} \u2014 its work is kept on its own branch`, cols)));
|
|
4821
4901
|
}
|
|
4822
|
-
if (
|
|
4823
|
-
const files =
|
|
4824
|
-
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})` : "";
|
|
4825
4905
|
lines.push(style.yellow(truncate(`${bodyPad}\u21BB repairing conflict${files}${attempt}`, cols)));
|
|
4826
4906
|
}
|
|
4827
|
-
if (
|
|
4828
|
-
|
|
4829
|
-
const dep = state.tasks.find((t) => t.id === id);
|
|
4830
|
-
return dep ? `#${dep.order}` : id;
|
|
4831
|
-
});
|
|
4832
|
-
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)));
|
|
4833
4909
|
}
|
|
4834
|
-
if (
|
|
4910
|
+
if (row.opsChangedFiles) {
|
|
4835
4911
|
lines.push(style.yellow(truncate(`${bodyPad}\u26A0 an ops task changed tracked files \u2014 check them, then m done or /retry`, cols)));
|
|
4836
4912
|
}
|
|
4837
|
-
if (
|
|
4838
|
-
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)));
|
|
4839
4915
|
}
|
|
4840
4916
|
let editorLine;
|
|
4841
4917
|
if (expanded) {
|
|
4842
4918
|
lines.push(style.grey(`${bodyPad}${task.status.replace(/_/g, " ")}`));
|
|
4843
4919
|
if (task.isolation && task.isolation.state !== "none" && task.isolation.branch) {
|
|
4844
4920
|
lines.push(...taskText("Branch", task.isolation.branch, cols, bodyPad));
|
|
4845
|
-
|
|
4846
|
-
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));
|
|
4847
4922
|
if (task.isolation.state !== "integrated" && task.isolation.worktree) {
|
|
4848
4923
|
lines.push(...taskText("Worktree", task.isolation.worktree, cols, bodyPad));
|
|
4849
4924
|
}
|
|
4850
4925
|
}
|
|
4851
|
-
if (
|
|
4926
|
+
if (row.kind === "ops") {
|
|
4852
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));
|
|
4853
4928
|
}
|
|
4854
|
-
if (
|
|
4855
|
-
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));
|
|
4856
4931
|
}
|
|
4857
|
-
if (
|
|
4932
|
+
if (row.autonomous) {
|
|
4858
4933
|
lines.push(...taskText("Autonomy", "Runs without permission prompts (Full). Change the level with /auto.", cols, bodyPad));
|
|
4859
4934
|
}
|
|
4860
4935
|
const editableSeed = task.prompt ?? task.description ?? task.title;
|
|
@@ -4868,10 +4943,8 @@ function taskLines(state, row, index, cols) {
|
|
|
4868
4943
|
editorLine = lines.length + 1 + cp.line;
|
|
4869
4944
|
lines.push(...taskPromptLines(wrapped, cp, bodyPad));
|
|
4870
4945
|
}
|
|
4871
|
-
if (
|
|
4872
|
-
|
|
4873
|
-
const dependencies = task.dependencies.map((id) => orderById.has(id) ? `#${orderById.get(id)}` : id).join(", ");
|
|
4874
|
-
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));
|
|
4875
4948
|
}
|
|
4876
4949
|
lines.push(style.grey(`${bodyPad}enter save \xB7 esc cancel`));
|
|
4877
4950
|
lines.push(style.grey(truncate(`${bodyPad}then R runner \xB7 o model \xB7 e effort \xB7 M mode \xB7 D deps`, cols)));
|
|
@@ -4918,7 +4991,13 @@ function helpLayout(rows, cols) {
|
|
|
4918
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")
|
|
4919
4992
|
);
|
|
4920
4993
|
body.push(
|
|
4921
|
-
|
|
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")
|
|
4922
5001
|
);
|
|
4923
5002
|
const lines = body.map((line) => truncate(line, Math.max(1, cols - 2)));
|
|
4924
5003
|
const room = Math.max(1, rows - 2);
|
|
@@ -4929,7 +5008,7 @@ function helpScrollMax(state) {
|
|
|
4929
5008
|
}
|
|
4930
5009
|
|
|
4931
5010
|
// src/tui/render.ts
|
|
4932
|
-
var
|
|
5011
|
+
var import_core25 = require("@ordewell/core");
|
|
4933
5012
|
function render(state) {
|
|
4934
5013
|
const { rows, cols } = state;
|
|
4935
5014
|
const rowsForBody = bodyRows(state);
|
|
@@ -5194,8 +5273,8 @@ function renderHandoff(state, overlay, rows, cols) {
|
|
|
5194
5273
|
return frame(`Diff \u2014 ${handoffBranch(handoff)}`, [...shown, "", style.grey(`${more}enter or esc goes back`)], rows, cols);
|
|
5195
5274
|
}
|
|
5196
5275
|
const landed = handoff.landed.length === 0 ? [style.grey("Nothing landed on it.")] : handoff.landed.map((task) => {
|
|
5197
|
-
const repaired = task.repairedFiles?.length ? style.yellow(` \u2014 repaired (${(0,
|
|
5198
|
-
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}`;
|
|
5199
5278
|
});
|
|
5200
5279
|
const actions = handoffActions(handoff).map((action, index) => {
|
|
5201
5280
|
const active = index === overlay.index;
|
|
@@ -5284,7 +5363,10 @@ function frame(title, body, rows, cols) {
|
|
|
5284
5363
|
}
|
|
5285
5364
|
|
|
5286
5365
|
// src/tui/reducer.ts
|
|
5287
|
-
var
|
|
5366
|
+
var import_core33 = require("@ordewell/core");
|
|
5367
|
+
|
|
5368
|
+
// src/tui/reducers/overlays.ts
|
|
5369
|
+
var import_core31 = require("@ordewell/core");
|
|
5288
5370
|
|
|
5289
5371
|
// src/tui/reducers/shared.ts
|
|
5290
5372
|
function step(state, effects = []) {
|
|
@@ -5333,10 +5415,10 @@ function handleApprovalKey(state, overlay, key) {
|
|
|
5333
5415
|
}
|
|
5334
5416
|
|
|
5335
5417
|
// src/tui/reducers/commands.ts
|
|
5336
|
-
var
|
|
5418
|
+
var import_core30 = require("@ordewell/core");
|
|
5337
5419
|
|
|
5338
5420
|
// src/tui/reducers/pickers.ts
|
|
5339
|
-
var
|
|
5421
|
+
var import_core26 = require("@ordewell/core");
|
|
5340
5422
|
var import_plan_utils2 = require("@ordewell/core/plan-utils");
|
|
5341
5423
|
var DEFAULT_EFFORT = "__runner_default__";
|
|
5342
5424
|
function picker(title, items, action, extra = {}) {
|
|
@@ -5355,11 +5437,11 @@ function modelDetail(m) {
|
|
|
5355
5437
|
function providerErrorHint(state) {
|
|
5356
5438
|
const failed = Object.keys(state.providerErrors ?? {});
|
|
5357
5439
|
if (failed.length === 0) return void 0;
|
|
5358
|
-
const names = failed.map((p) =>
|
|
5440
|
+
const names = failed.map((p) => import_core26.ALL_PROVIDERS[p]?.label ?? p);
|
|
5359
5441
|
return `\u26A0 Unavailable (key or fetch failed): ${names.join(", ")} \u2014 showing working providers only.`;
|
|
5360
5442
|
}
|
|
5361
5443
|
function plannerModelItems(state) {
|
|
5362
|
-
const runner = (0,
|
|
5444
|
+
const runner = (0, import_core26.runnerForProvider)(state.plannerProvider);
|
|
5363
5445
|
if (!runner) return state.orchestratorModels.map((m) => ({ id: m.id, label: m.label, detail: modelDetail(m) }));
|
|
5364
5446
|
const items = state.models.filter((m) => m.runners?.includes(runner)).map((m) => ({
|
|
5365
5447
|
id: m.id,
|
|
@@ -5373,7 +5455,7 @@ function plannerModelItems(state) {
|
|
|
5373
5455
|
return items.length > 0 ? items : [{ id: "", label: `No ${runner} models discovered yet`, detail: "run /refresh", disabled: true }];
|
|
5374
5456
|
}
|
|
5375
5457
|
function plannerEffortItems(state) {
|
|
5376
|
-
const runner = (0,
|
|
5458
|
+
const runner = (0, import_core26.runnerForProvider)(state.plannerProvider);
|
|
5377
5459
|
if (!runner) {
|
|
5378
5460
|
return [{ id: "", label: "Thinking effort applies to a coding-agent planner", detail: "switch with /planner", disabled: true }];
|
|
5379
5461
|
}
|
|
@@ -5437,7 +5519,7 @@ function pickerItemsFor(state, action) {
|
|
|
5437
5519
|
if (action.kind === "set-task-deps") {
|
|
5438
5520
|
return (0, import_plan_utils2.dependencyCandidates)(state.tasks, action.taskId).map((candidate) => ({
|
|
5439
5521
|
id: candidate.id,
|
|
5440
|
-
label:
|
|
5522
|
+
label: (0, import_core26.titledTaskRef)(candidate),
|
|
5441
5523
|
detail: candidate.status === "completed" ? "already completed" : void 0
|
|
5442
5524
|
}));
|
|
5443
5525
|
}
|
|
@@ -5482,26 +5564,29 @@ function pickerItemsFor(state, action) {
|
|
|
5482
5564
|
}
|
|
5483
5565
|
function plannerItems(state) {
|
|
5484
5566
|
const installed = new Set(state.runners.map((r) => r.id));
|
|
5485
|
-
const agents =
|
|
5486
|
-
const runner = (0,
|
|
5567
|
+
const agents = import_core26.CLI_PROVIDERS.map((id) => {
|
|
5568
|
+
const runner = (0, import_core26.runnerForProvider)(id);
|
|
5487
5569
|
const ready = installed.has(runner);
|
|
5488
5570
|
return {
|
|
5489
5571
|
id,
|
|
5490
|
-
label:
|
|
5572
|
+
label: import_core26.ALL_PROVIDERS[id].label,
|
|
5491
5573
|
detail: ready ? "coding agent \xB7 no API key needed" : "CLI not installed or not on PATH",
|
|
5492
5574
|
selected: state.plannerProvider === id,
|
|
5493
5575
|
disabled: !ready
|
|
5494
5576
|
};
|
|
5495
5577
|
});
|
|
5496
|
-
const vendors =
|
|
5578
|
+
const vendors = import_core26.PROVIDER_PRIORITY.filter((id) => state.configuredProviders.includes(id)).map((id) => ({
|
|
5497
5579
|
id,
|
|
5498
|
-
label:
|
|
5499
|
-
detail:
|
|
5580
|
+
label: import_core26.ALL_PROVIDERS[id].label,
|
|
5581
|
+
detail: import_core26.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
5500
5582
|
selected: state.plannerProvider === id
|
|
5501
5583
|
}));
|
|
5502
5584
|
return vendors.length > 0 ? [...agents, ...vendors] : [...agents, { id: "", label: "No API providers configured", detail: "add a key with /key", disabled: true }];
|
|
5503
5585
|
}
|
|
5504
5586
|
|
|
5587
|
+
// src/tui/reducers/planPane.ts
|
|
5588
|
+
var import_core28 = require("@ordewell/core");
|
|
5589
|
+
|
|
5505
5590
|
// src/tui/reducers/pointer.ts
|
|
5506
5591
|
var WHEEL_NOTCH = 3;
|
|
5507
5592
|
function pageNotch(viewport) {
|
|
@@ -5578,13 +5663,13 @@ function handleMouseSelect(state, key) {
|
|
|
5578
5663
|
}
|
|
5579
5664
|
|
|
5580
5665
|
// src/tui/reducers/taskView.ts
|
|
5581
|
-
var
|
|
5666
|
+
var import_core27 = require("@ordewell/core");
|
|
5582
5667
|
function openTaskView(state, sessionId, taskId) {
|
|
5583
5668
|
const task = findTask(state.tasks, taskId);
|
|
5584
5669
|
if (!task) return step(say(state, "system", `No task ${taskId} in this plan.`));
|
|
5585
5670
|
const taskView = {
|
|
5586
5671
|
taskId,
|
|
5587
|
-
view:
|
|
5672
|
+
view: import_core27.EMPTY_TASK_LOG,
|
|
5588
5673
|
attempts: [],
|
|
5589
5674
|
attempt: 0,
|
|
5590
5675
|
pending: [],
|
|
@@ -5617,13 +5702,13 @@ function applyLive(tv, attempt, events) {
|
|
|
5617
5702
|
}
|
|
5618
5703
|
next = {
|
|
5619
5704
|
...next,
|
|
5620
|
-
view:
|
|
5705
|
+
view: import_core27.EMPTY_TASK_LOG,
|
|
5621
5706
|
attempt,
|
|
5622
5707
|
attempts: next.attempts.includes(attempt) ? next.attempts : [...next.attempts, attempt]
|
|
5623
5708
|
};
|
|
5624
5709
|
}
|
|
5625
5710
|
if (attempt < next.attempt) return next;
|
|
5626
|
-
return clampQueueIndex({ ...next, view: events.reduce(
|
|
5711
|
+
return clampQueueIndex({ ...next, view: events.reduce(import_core27.reduceTaskLog, next.view) });
|
|
5627
5712
|
}
|
|
5628
5713
|
function taskLogAbandoned(state) {
|
|
5629
5714
|
const tv = state.taskView;
|
|
@@ -5643,15 +5728,22 @@ function taskLogArrived(state, action) {
|
|
|
5643
5728
|
function announceApprovals(state, action) {
|
|
5644
5729
|
if (state.taskView?.taskId === action.taskId) return state;
|
|
5645
5730
|
const task = findTask(state.tasks, action.taskId);
|
|
5646
|
-
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);
|
|
5647
5732
|
return spoken !== state ? { ...spoken, scroll: state.scroll } : spoken;
|
|
5648
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
|
+
}
|
|
5649
5741
|
function taskLogLoaded(state, action) {
|
|
5650
5742
|
const tv = state.taskView;
|
|
5651
5743
|
if (!tv || tv.taskId !== action.taskId) return state;
|
|
5652
5744
|
const loaded = action.attempt >= 1 ? {
|
|
5653
5745
|
...tv,
|
|
5654
|
-
view: (0,
|
|
5746
|
+
view: (0, import_core27.replayTaskLog)(action.events),
|
|
5655
5747
|
attempts: action.attempts ?? tv.attempts,
|
|
5656
5748
|
attempt: action.attempt
|
|
5657
5749
|
} : { ...tv, attempts: action.attempts ?? tv.attempts };
|
|
@@ -5673,6 +5765,29 @@ function answerApproval(state, tv, sessionId, decision) {
|
|
|
5673
5765
|
const next = note ? { ...state, editor: { ...state.editor, text: "", cursor: 0 } } : state;
|
|
5674
5766
|
return step(next, [{ type: "answerTaskApproval", sessionId, approvalId: approval.approvalId, answer }]);
|
|
5675
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
|
+
}
|
|
5676
5791
|
function handleTaskViewKey(state, key) {
|
|
5677
5792
|
const tv = state.taskView;
|
|
5678
5793
|
if (!tv || !state.sessionId) return null;
|
|
@@ -5692,14 +5807,16 @@ function handleTaskViewKey(state, key) {
|
|
|
5692
5807
|
if (key.name === "ctrl-r") {
|
|
5693
5808
|
const queued = tv.view.queued[tv.queuedIndex];
|
|
5694
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."));
|
|
5695
5811
|
return step(state, [{ type: "removeTaskMessage", sessionId: state.sessionId, taskId: tv.taskId, messageId: queued.id }]);
|
|
5696
5812
|
}
|
|
5813
|
+
if (key.name === "ctrl-s") return sendNow(state, tv, state.sessionId);
|
|
5697
5814
|
if (key.name === "ctrl-x") {
|
|
5698
5815
|
return step(state, [{ type: "interruptTask", sessionId: state.sessionId, taskId: tv.taskId }]);
|
|
5699
5816
|
}
|
|
5700
|
-
if (key.name === "ctrl-y") return
|
|
5817
|
+
if (key.name === "ctrl-y") return answerWaiting(state, tv, state.sessionId, "allow");
|
|
5701
5818
|
if (key.name === "ctrl-t") return answerApproval(state, tv, state.sessionId, "allowForTask");
|
|
5702
|
-
if (key.name === "ctrl-g") return
|
|
5819
|
+
if (key.name === "ctrl-g") return answerWaiting(state, tv, state.sessionId, "deny");
|
|
5703
5820
|
if (key.name === "ctrl-n" || key.name === "ctrl-p") {
|
|
5704
5821
|
const count = tv.view.queued.length;
|
|
5705
5822
|
if (count === 0) return null;
|
|
@@ -5720,7 +5837,7 @@ function openTaskRunnerPicker(state, task) {
|
|
|
5720
5837
|
...state,
|
|
5721
5838
|
overlay: {
|
|
5722
5839
|
kind: "picker",
|
|
5723
|
-
picker: picker(`Runner \xB7
|
|
5840
|
+
picker: picker(`Runner \xB7 ${(0, import_plan_utils3.titledTaskRef)(task)}`, pickerItemsFor(state, action), action, {
|
|
5724
5841
|
hint: "Changing the runner re-picks this task model, effort and mode for it."
|
|
5725
5842
|
})
|
|
5726
5843
|
}
|
|
@@ -5728,14 +5845,12 @@ function openTaskRunnerPicker(state, task) {
|
|
|
5728
5845
|
);
|
|
5729
5846
|
}
|
|
5730
5847
|
function confirmRemoveTask(state, task) {
|
|
5731
|
-
const dependents = (0, import_plan_utils3.dependentsOf)(state.tasks, task.id);
|
|
5732
|
-
const named = dependents.map((t) => `#${t.order} ${t.title}`).join(", ");
|
|
5733
5848
|
return step({
|
|
5734
5849
|
...state,
|
|
5735
5850
|
overlay: {
|
|
5736
5851
|
kind: "confirm",
|
|
5737
|
-
title: `Remove
|
|
5738
|
-
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.",
|
|
5739
5854
|
action: { kind: "remove-task", taskId: task.id }
|
|
5740
5855
|
}
|
|
5741
5856
|
});
|
|
@@ -5744,26 +5859,22 @@ function toggleTaskOps(state, task, subtask, to = !task.ops) {
|
|
|
5744
5859
|
if (!state.sessionId) return fail3(state, "No active plan.");
|
|
5745
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.");
|
|
5746
5861
|
if (subtask) return fail3(state, "A subtask runs with its parent; make the parent an ops task instead.");
|
|
5747
|
-
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.`);
|
|
5748
5863
|
return step(state, [{
|
|
5749
5864
|
type: "updateTask",
|
|
5750
5865
|
sessionId: state.sessionId,
|
|
5751
5866
|
taskId: task.id,
|
|
5752
5867
|
changes: { ops: to },
|
|
5753
|
-
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.`
|
|
5754
5869
|
}]);
|
|
5755
5870
|
}
|
|
5756
5871
|
function confirmForceStartPastGate(state, task) {
|
|
5757
|
-
const named = (task.mergeGate ?? []).map((id) => {
|
|
5758
|
-
const dep = findTask(state.tasks, id);
|
|
5759
|
-
return dep ? `#${dep.order} ${dep.title}` : id;
|
|
5760
|
-
});
|
|
5761
5872
|
return step({
|
|
5762
5873
|
...state,
|
|
5763
5874
|
overlay: {
|
|
5764
5875
|
kind: "confirm",
|
|
5765
|
-
title: `Force start
|
|
5766
|
-
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"),
|
|
5767
5878
|
action: { kind: "force-start-gated", taskId: task.id }
|
|
5768
5879
|
}
|
|
5769
5880
|
});
|
|
@@ -5771,12 +5882,12 @@ function confirmForceStartPastGate(state, task) {
|
|
|
5771
5882
|
function openTaskDepsPicker(state, task) {
|
|
5772
5883
|
const action = { kind: "set-task-deps", taskId: task.id };
|
|
5773
5884
|
const items = pickerItemsFor(state, action);
|
|
5774
|
-
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.`);
|
|
5775
5886
|
return step({
|
|
5776
5887
|
...state,
|
|
5777
5888
|
overlay: {
|
|
5778
5889
|
kind: "picker",
|
|
5779
|
-
picker: picker(`Depends on \xB7
|
|
5890
|
+
picker: picker(`Depends on \xB7 ${(0, import_plan_utils3.titledTaskRef)(task)}`, items, action, {
|
|
5780
5891
|
hint: "Only tasks earlier in the plan can be dependencies.",
|
|
5781
5892
|
multi: true,
|
|
5782
5893
|
chosen: task.dependencies.filter((id) => items.some((i) => i.id === id))
|
|
@@ -5796,7 +5907,7 @@ function openTaskModePicker(state, task) {
|
|
|
5796
5907
|
...state,
|
|
5797
5908
|
overlay: {
|
|
5798
5909
|
kind: "picker",
|
|
5799
|
-
picker: picker(`Mode \xB7
|
|
5910
|
+
picker: picker(`Mode \xB7 ${(0, import_plan_utils3.taskRef)(task)}`, pickerItemsFor(state, action), action, {
|
|
5800
5911
|
hint: `Modes declared by ${task.assignedRunner}.`
|
|
5801
5912
|
})
|
|
5802
5913
|
}
|
|
@@ -5811,7 +5922,7 @@ function openTaskModelPicker(state, task) {
|
|
|
5811
5922
|
...state,
|
|
5812
5923
|
overlay: {
|
|
5813
5924
|
kind: "picker",
|
|
5814
|
-
picker: picker(`Model \xB7
|
|
5925
|
+
picker: picker(`Model \xB7 ${(0, import_plan_utils3.titledTaskRef)(task)}`, pickerItemsFor(state, action), action, {
|
|
5815
5926
|
hint: task.assignedRunner ? `Showing models discovered for ${task.assignedRunner}.` : "Choose the model this task will run with."
|
|
5816
5927
|
})
|
|
5817
5928
|
}
|
|
@@ -5828,7 +5939,7 @@ function openTaskEffortPicker(state, task) {
|
|
|
5828
5939
|
...state,
|
|
5829
5940
|
overlay: {
|
|
5830
5941
|
kind: "picker",
|
|
5831
|
-
picker: picker(`Thinking effort \xB7
|
|
5942
|
+
picker: picker(`Thinking effort \xB7 ${(0, import_plan_utils3.taskRef)(task)}`, pickerItemsFor(state, action), action, {
|
|
5832
5943
|
hint: `${task.assignedModel.modelLabel} \xB7 choose runner default or a supported effort`
|
|
5833
5944
|
})
|
|
5834
5945
|
}
|
|
@@ -5845,7 +5956,7 @@ function assignTaskModel(state, sessionId, task, model) {
|
|
|
5845
5956
|
// JSON drops `undefined`; null is intentional here so changing models can
|
|
5846
5957
|
// also clear a stale legacy top-level effort on the persisted task.
|
|
5847
5958
|
changes: { assignedModel, thinkingEffort: assignedModel.thinkingEffort ?? null },
|
|
5848
|
-
message: `Task
|
|
5959
|
+
message: `Task ${(0, import_plan_utils3.taskRef)(task)} model set to ${model.label}.`
|
|
5849
5960
|
}]);
|
|
5850
5961
|
}
|
|
5851
5962
|
function assignTaskRunner(state, sessionId, task, runner, runnerLabel) {
|
|
@@ -5854,7 +5965,7 @@ function assignTaskRunner(state, sessionId, task, runner, runnerLabel) {
|
|
|
5854
5965
|
sessionId,
|
|
5855
5966
|
taskId: task.id,
|
|
5856
5967
|
changes: { assignedRunner: runner },
|
|
5857
|
-
message: `Task
|
|
5968
|
+
message: `Task ${(0, import_plan_utils3.taskRef)(task)} runner set to ${runnerLabel}.`
|
|
5858
5969
|
}]);
|
|
5859
5970
|
}
|
|
5860
5971
|
function assignTaskMode(state, sessionId, task, mode, modeLabel) {
|
|
@@ -5863,7 +5974,7 @@ function assignTaskMode(state, sessionId, task, mode, modeLabel) {
|
|
|
5863
5974
|
sessionId,
|
|
5864
5975
|
taskId: task.id,
|
|
5865
5976
|
changes: { taskMode: mode },
|
|
5866
|
-
message: `Task
|
|
5977
|
+
message: `Task ${(0, import_plan_utils3.taskRef)(task)} mode set to ${modeLabel}.`
|
|
5867
5978
|
}]);
|
|
5868
5979
|
}
|
|
5869
5980
|
function assignTaskEffort(state, sessionId, task, thinkingEffort) {
|
|
@@ -5873,7 +5984,7 @@ function assignTaskEffort(state, sessionId, task, thinkingEffort) {
|
|
|
5873
5984
|
sessionId,
|
|
5874
5985
|
taskId: task.id,
|
|
5875
5986
|
changes: { assignedModel, thinkingEffort: thinkingEffort ?? null },
|
|
5876
|
-
message: `Task
|
|
5987
|
+
message: `Task ${(0, import_plan_utils3.taskRef)(task)} thinking effort set to ${thinkingEffort ?? "runner default"}.`
|
|
5877
5988
|
}]);
|
|
5878
5989
|
}
|
|
5879
5990
|
function taskModelCommand(state, args) {
|
|
@@ -5975,9 +6086,6 @@ function taskActionEffect(state, sessionId, taskId, action) {
|
|
|
5975
6086
|
const watch = SPAWNS_RUNNER.includes(action) && state.status !== "executing";
|
|
5976
6087
|
return watch ? { type: "taskAction", sessionId, taskId, action, watch: true } : { type: "taskAction", sessionId, taskId, action };
|
|
5977
6088
|
}
|
|
5978
|
-
function markAction(task) {
|
|
5979
|
-
return task.status === "completed" ? "uncomplete" : "complete";
|
|
5980
|
-
}
|
|
5981
6089
|
function handlePlanKey(state, key) {
|
|
5982
6090
|
const editingTask = state.expandedTaskId ? findTask(state.tasks, state.expandedTaskId) : void 0;
|
|
5983
6091
|
if (editingTask && state.taskEditor) return handleTaskEditKey(state, editingTask, state.taskEditor, key);
|
|
@@ -6012,7 +6120,7 @@ function handlePlanKey(state, key) {
|
|
|
6012
6120
|
if (!state.sessionId || key.name !== "char") return step(state);
|
|
6013
6121
|
if (key.char === "E") return step(state, [{ type: "execute", sessionId: state.sessionId }]);
|
|
6014
6122
|
if (key.char === "S") return step(state, [{ type: "stopExecution", sessionId: state.sessionId }]);
|
|
6015
|
-
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 ?? ""];
|
|
6016
6124
|
if (action === "force-start" && task.mergeGate?.length) return confirmForceStartPastGate(state, task);
|
|
6017
6125
|
if (action) {
|
|
6018
6126
|
return step(state, [taskActionEffect(state, state.sessionId, task.id, action)]);
|
|
@@ -6047,14 +6155,14 @@ function commitTaskEdit(state, task, editor) {
|
|
|
6047
6155
|
sessionId: state.sessionId,
|
|
6048
6156
|
taskId: task.id,
|
|
6049
6157
|
changes: { prompt },
|
|
6050
|
-
message: `Task
|
|
6158
|
+
message: `Task ${(0, import_core28.taskRef)(task)} prompt updated.`
|
|
6051
6159
|
}]);
|
|
6052
6160
|
}
|
|
6053
6161
|
|
|
6054
6162
|
// src/tui/reducers/turnQueue.ts
|
|
6055
|
-
var
|
|
6163
|
+
var import_core29 = require("@ordewell/core");
|
|
6056
6164
|
function drainQueue(state, settled) {
|
|
6057
|
-
const next = (0,
|
|
6165
|
+
const next = (0, import_core29.drainNext)(state.queuedPrompts);
|
|
6058
6166
|
const ended = disarmStop({ ...settled, stopRequested: false });
|
|
6059
6167
|
if (!next) return step(ended);
|
|
6060
6168
|
const spoken = say(ended, "user", next.text);
|
|
@@ -6073,14 +6181,14 @@ function stopPlanning(state, sessionId) {
|
|
|
6073
6181
|
return step(queueToEditor(cutTurn({ ...dismissed, stopArmed: false, stopRequested: true })), [{ type: "cancelPlanning", sessionId }]);
|
|
6074
6182
|
}
|
|
6075
6183
|
function queueToEditor(state) {
|
|
6076
|
-
const all = (0,
|
|
6184
|
+
const all = (0, import_core29.unsendAll)(state.queuedPrompts);
|
|
6077
6185
|
if (!all) return state;
|
|
6078
6186
|
return { ...state, queuedPrompts: all.rest, editor: { ...state.editor, text: all.text, cursor: all.text.length } };
|
|
6079
6187
|
}
|
|
6080
6188
|
function unsendLatest(state) {
|
|
6081
|
-
const latest = (0,
|
|
6189
|
+
const latest = (0, import_core29.unsendLatest)(state.queuedPrompts);
|
|
6082
6190
|
if (!latest) return step(state);
|
|
6083
|
-
const text = (0,
|
|
6191
|
+
const text = (0, import_core29.aheadOfDraft)(latest.text, state.editor.text);
|
|
6084
6192
|
return step({
|
|
6085
6193
|
...state,
|
|
6086
6194
|
queuedPrompts: latest.rest,
|
|
@@ -6097,7 +6205,7 @@ function plannerEscape(state) {
|
|
|
6097
6205
|
}
|
|
6098
6206
|
|
|
6099
6207
|
// src/tui/reducers/commands.ts
|
|
6100
|
-
var KNOWN_PROVIDERS2 = Object.keys(
|
|
6208
|
+
var KNOWN_PROVIDERS2 = Object.keys(import_core30.ALL_PROVIDERS);
|
|
6101
6209
|
function runCommand(state, { name, args }) {
|
|
6102
6210
|
if (!findCommand(name)) {
|
|
6103
6211
|
return fail3(state, `Unknown command: /${name} \u2014 type /help to see what's available.`);
|
|
@@ -6184,6 +6292,14 @@ function runCommand(state, { name, args }) {
|
|
|
6184
6292
|
if (!text) return fail3(state, "Usage: /continue <id> <message>");
|
|
6185
6293
|
return continueTaskStep(state, sessionId, taskId, text);
|
|
6186
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
|
+
});
|
|
6187
6303
|
case "task-runner":
|
|
6188
6304
|
return taskRunnerCommand(state, args);
|
|
6189
6305
|
case "task-model":
|
|
@@ -6255,8 +6371,8 @@ function newSession(state) {
|
|
|
6255
6371
|
taskView: null,
|
|
6256
6372
|
tasks: [],
|
|
6257
6373
|
planApproved: false,
|
|
6258
|
-
conversation:
|
|
6259
|
-
turnGate:
|
|
6374
|
+
conversation: import_core30.EMPTY_CONVERSATION,
|
|
6375
|
+
turnGate: import_core30.NO_TURN,
|
|
6260
6376
|
selectedTask: 0,
|
|
6261
6377
|
expandedTaskId: null,
|
|
6262
6378
|
taskEditor: null,
|
|
@@ -6267,14 +6383,14 @@ function newSession(state) {
|
|
|
6267
6383
|
// Prompts belong to the session that raised them; the old planner is gone
|
|
6268
6384
|
// and its pending requests deny on their own timeout.
|
|
6269
6385
|
pendingApprovals: [],
|
|
6270
|
-
queuedPrompts:
|
|
6386
|
+
queuedPrompts: import_core30.EMPTY_HOLD,
|
|
6271
6387
|
stopArmed: false,
|
|
6272
6388
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
6273
6389
|
}, closeEffects);
|
|
6274
6390
|
}
|
|
6275
6391
|
function setModel(state, args) {
|
|
6276
6392
|
if (args[0] === "set" && args[1]) {
|
|
6277
|
-
const runner = (0,
|
|
6393
|
+
const runner = (0, import_core30.runnerForProvider)(state.plannerProvider);
|
|
6278
6394
|
const known = runner ? modelsForRunner(state.models, runner) : [];
|
|
6279
6395
|
if (runner && known.length > 0 && !known.some((m) => m.id === args[1])) {
|
|
6280
6396
|
return fail3(state, `${args[1]} was not discovered for ${runner}.`);
|
|
@@ -6347,16 +6463,16 @@ function setKey(state, args) {
|
|
|
6347
6463
|
}
|
|
6348
6464
|
return step(state, [{ type: "setApiKey", provider, key: args.slice(2).join(" ") }]);
|
|
6349
6465
|
}
|
|
6350
|
-
const items =
|
|
6466
|
+
const items = import_core30.PROVIDER_PRIORITY.map((id) => ({
|
|
6351
6467
|
id,
|
|
6352
|
-
label:
|
|
6353
|
-
detail:
|
|
6468
|
+
label: import_core30.ALL_PROVIDERS[id].label,
|
|
6469
|
+
detail: import_core30.ALL_PROVIDERS[id].apiKeyEnvVar,
|
|
6354
6470
|
selected: state.configuredProviders.includes(id)
|
|
6355
6471
|
}));
|
|
6356
6472
|
return step({ ...state, overlay: { kind: "picker", picker: picker("API provider key", items, { kind: "set-key" }) } });
|
|
6357
6473
|
}
|
|
6358
6474
|
function keyPrompt(provider) {
|
|
6359
|
-
const meta =
|
|
6475
|
+
const meta = import_core30.ALL_PROVIDERS[provider];
|
|
6360
6476
|
return {
|
|
6361
6477
|
kind: "prompt",
|
|
6362
6478
|
title: `${meta.label} API key`,
|
|
@@ -6433,19 +6549,19 @@ function runners(state, args) {
|
|
|
6433
6549
|
}
|
|
6434
6550
|
function setMaxParallel(state, arg) {
|
|
6435
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.`));
|
|
6436
|
-
const limit = (0,
|
|
6552
|
+
const limit = (0, import_core30.parseMaxParallel)(arg);
|
|
6437
6553
|
if (limit === null) return fail3(state, "Usage: /parallel [<number of tasks, 1 or more>]");
|
|
6438
6554
|
return step({ ...state, maxParallel: limit }, [{ type: "setMaxParallel", limit }]);
|
|
6439
6555
|
}
|
|
6440
6556
|
function setAutonomous(state, arg) {
|
|
6441
|
-
if (arg === void 0) return step(say(state, "system", `Autonomy level: ${(0,
|
|
6442
|
-
const enabled = (0,
|
|
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);
|
|
6443
6559
|
if (enabled === null) return fail3(state, "Usage: /auto [full|guarded]");
|
|
6444
6560
|
return step({ ...state, autonomous: enabled }, [{ type: "setAutonomous", enabled }]);
|
|
6445
6561
|
}
|
|
6446
6562
|
function setTransport(state, arg) {
|
|
6447
6563
|
const value = arg?.toLowerCase() ?? (state.runnerTransport === "structured" ? "terminal" : "structured");
|
|
6448
|
-
if (!(0,
|
|
6564
|
+
if (!(0, import_core30.isRunnerTransport)(value)) return fail3(state, "Usage: /transport [terminal|structured]");
|
|
6449
6565
|
return step(state, [{ type: "setTransport", transport: value }]);
|
|
6450
6566
|
}
|
|
6451
6567
|
function setMouseCapture(state, arg) {
|
|
@@ -6622,7 +6738,7 @@ function choose(state, picker2, item) {
|
|
|
6622
6738
|
sessionId: state.sessionId,
|
|
6623
6739
|
taskId: task.id,
|
|
6624
6740
|
changes: { dependencies: picker2.chosen },
|
|
6625
|
-
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.`
|
|
6626
6742
|
}]);
|
|
6627
6743
|
}
|
|
6628
6744
|
if (!item) return step(state);
|
|
@@ -6699,7 +6815,7 @@ function choose(state, picker2, item) {
|
|
|
6699
6815
|
}
|
|
6700
6816
|
|
|
6701
6817
|
// src/tui/reducers/incoming.ts
|
|
6702
|
-
var
|
|
6818
|
+
var import_core32 = require("@ordewell/core");
|
|
6703
6819
|
var planText = (value, fallback = "") => sanitize(String(value ?? fallback));
|
|
6704
6820
|
var planLabel = (value, fallback = "") => planText(value, fallback).replace(/\n+/g, " ");
|
|
6705
6821
|
var asRecord = (value) => typeof value === "object" && value !== null ? value : {};
|
|
@@ -6730,7 +6846,7 @@ function toTaskView2(t, index) {
|
|
|
6730
6846
|
prompt: typeof t.prompt === "string" ? sanitize(t.prompt) : void 0,
|
|
6731
6847
|
type: t.type === "user" ? "user" : "ai",
|
|
6732
6848
|
status: planLabel(t.status, "pending"),
|
|
6733
|
-
awaitingReason: t.status === "awaiting_user" && (0,
|
|
6849
|
+
awaitingReason: t.status === "awaiting_user" && (0, import_core32.isAwaitingReason)(t.awaitingReason) ? t.awaitingReason : void 0,
|
|
6734
6850
|
dependencies: Array.isArray(t.dependencies) ? t.dependencies.map((id) => planLabel(id)) : [],
|
|
6735
6851
|
assignedRunner: typeof t.assignedRunner === "string" ? planLabel(t.assignedRunner) : void 0,
|
|
6736
6852
|
taskMode: typeof t.taskMode === "string" ? planLabel(t.taskMode) : void 0,
|
|
@@ -6763,12 +6879,12 @@ function applySettings(state, settings) {
|
|
|
6763
6879
|
plannerProvider: typeof settings.aiProvider === "string" ? settings.aiProvider : state.plannerProvider,
|
|
6764
6880
|
plannerEffort: typeof settings.plannerThinkingEffort === "string" ? settings.plannerThinkingEffort : state.plannerEffort,
|
|
6765
6881
|
maxParallel: typeof settings.maxParallel === "number" ? settings.maxParallel : state.maxParallel,
|
|
6766
|
-
runnerTransport: (0,
|
|
6882
|
+
runnerTransport: (0, import_core32.isRunnerTransport)(settings.runnerTransport) ? settings.runnerTransport : state.runnerTransport,
|
|
6767
6883
|
allowlist: settings.modelAllowlist && typeof settings.modelAllowlist === "object" ? settings.modelAllowlist : state.allowlist
|
|
6768
6884
|
};
|
|
6769
6885
|
}
|
|
6770
6886
|
function runLabel(tasks, gate = null) {
|
|
6771
|
-
const running = tasks.filter(isTaskRunning);
|
|
6887
|
+
const running = tasks.filter(import_core23.isTaskRunning);
|
|
6772
6888
|
if (running.length > 0) {
|
|
6773
6889
|
const [first] = running;
|
|
6774
6890
|
const name = first.assignedRunner ? `${first.title} \xB7 ${first.assignedRunner}` : first.title;
|
|
@@ -6834,13 +6950,13 @@ function reduceAction(state, action) {
|
|
|
6834
6950
|
goal: "",
|
|
6835
6951
|
taskView: null,
|
|
6836
6952
|
pendingApprovals: [],
|
|
6837
|
-
queuedPrompts:
|
|
6953
|
+
queuedPrompts: import_core33.EMPTY_HOLD,
|
|
6838
6954
|
stopArmed: false,
|
|
6839
6955
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
6840
6956
|
});
|
|
6841
6957
|
case "chatRestored":
|
|
6842
6958
|
if (stale(state, action.sessionId)) return step(state);
|
|
6843
|
-
return step({ ...state, conversation: (0,
|
|
6959
|
+
return step({ ...state, conversation: (0, import_core33.fromTranscript)(action.history, action.researchLog, action.plannerUsage), scroll: 0 });
|
|
6844
6960
|
case "planUpdated": {
|
|
6845
6961
|
if (stale(state, action.sessionId)) return step(state);
|
|
6846
6962
|
const isolation = isolationOfPlan(action.plan);
|
|
@@ -6887,14 +7003,17 @@ function reduceAction(state, action) {
|
|
|
6887
7003
|
case "taskStarted": {
|
|
6888
7004
|
if (stale(state, action.sessionId)) return step(state);
|
|
6889
7005
|
const existing = findTask(state.tasks, action.taskId);
|
|
6890
|
-
if (existing && isTaskRunning(existing)) return step(state);
|
|
6891
|
-
const tasks = state.tasks.map((t) => t.id === action.taskId && !isTaskRunning(t) ? { ...t, status: "in_progress" } : t);
|
|
6892
|
-
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));
|
|
6893
7009
|
return step({ ...spoken, tasks, status: "executing", busyLabel: runLabel(tasks) });
|
|
6894
7010
|
}
|
|
7011
|
+
case "taskCheckpoint":
|
|
7012
|
+
if (stale(state, action.sessionId)) return step(state);
|
|
7013
|
+
return step(announceCheckpoint(state, action));
|
|
6895
7014
|
case "taskStatus": {
|
|
6896
7015
|
if (stale(state, action.sessionId) || !state.tasks.some((t) => t.id === action.taskId)) return step(state);
|
|
6897
|
-
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);
|
|
6898
7017
|
const status = runStatus(state, tasks);
|
|
6899
7018
|
return step({ ...state, status, tasks, busyLabel: status === "executing" ? runLabel(tasks, state.gate) : state.busyLabel });
|
|
6900
7019
|
}
|
|
@@ -6909,13 +7028,14 @@ function reduceAction(state, action) {
|
|
|
6909
7028
|
const isolation = update.isolation ?? t.isolation;
|
|
6910
7029
|
const transport = update.transport;
|
|
6911
7030
|
const awaitingReason = update.awaitingReason;
|
|
7031
|
+
const checkpoint = update.checkpoint;
|
|
6912
7032
|
const continuable = update.continuable === true;
|
|
6913
7033
|
const awaitingApproval = update.awaitingApproval;
|
|
6914
7034
|
const mergeGate = update.mergeGate;
|
|
6915
7035
|
const forcedPastGate = update.forcedPastGate;
|
|
6916
|
-
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)) {
|
|
6917
7037
|
changed = true;
|
|
6918
|
-
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 };
|
|
6919
7039
|
}
|
|
6920
7040
|
return t;
|
|
6921
7041
|
});
|
|
@@ -6998,7 +7118,7 @@ function reduceAction(state, action) {
|
|
|
6998
7118
|
busyLabel: "",
|
|
6999
7119
|
planApproved: false,
|
|
7000
7120
|
pendingApprovals: [],
|
|
7001
|
-
queuedPrompts:
|
|
7121
|
+
queuedPrompts: import_core33.EMPTY_HOLD,
|
|
7002
7122
|
stopArmed: false,
|
|
7003
7123
|
overlay: state.overlay?.kind === "approval" ? null : state.overlay
|
|
7004
7124
|
});
|
|
@@ -7065,7 +7185,7 @@ function sameGate(a, b) {
|
|
|
7065
7185
|
return a.paused === b.paused && JSON.stringify(a.handoff) === JSON.stringify(b.handoff);
|
|
7066
7186
|
}
|
|
7067
7187
|
function runStatus(state, tasks) {
|
|
7068
|
-
if (tasks.some(isTaskRunning)) return "executing";
|
|
7188
|
+
if (tasks.some(import_core23.isTaskRunning)) return "executing";
|
|
7069
7189
|
if (tasks.some((t) => t.status === "awaiting_user") || state.gate?.paused) return "executing";
|
|
7070
7190
|
return plannerInFlight(state) ? state.status : "idle";
|
|
7071
7191
|
}
|
|
@@ -7134,7 +7254,7 @@ function submit(state) {
|
|
|
7134
7254
|
return step(cleared, [{ type: "sendTaskMessage", sessionId: state.sessionId, taskId, text }]);
|
|
7135
7255
|
}
|
|
7136
7256
|
if (plannerInFlight(state)) {
|
|
7137
|
-
return step({ ...cleared, queuedPrompts: (0,
|
|
7257
|
+
return step({ ...cleared, queuedPrompts: (0, import_core33.holdPrompt)(state.queuedPrompts, text) });
|
|
7138
7258
|
}
|
|
7139
7259
|
const spoken = say(cleared, "user", text);
|
|
7140
7260
|
const effect = state.sessionId ? { type: "sendMessage", sessionId: state.sessionId, message: text } : { type: "startConversation", goal: text };
|
|
@@ -7194,10 +7314,10 @@ function createApp(deps) {
|
|
|
7194
7314
|
|
|
7195
7315
|
// src/tui/effects.ts
|
|
7196
7316
|
var import_child_process4 = require("child_process");
|
|
7197
|
-
var
|
|
7317
|
+
var import_core35 = require("@ordewell/core");
|
|
7198
7318
|
|
|
7199
7319
|
// src/tui/inbound.ts
|
|
7200
|
-
var
|
|
7320
|
+
var import_core34 = require("@ordewell/core");
|
|
7201
7321
|
var CONVERSATION_TYPES = /* @__PURE__ */ new Set([
|
|
7202
7322
|
"planner_message",
|
|
7203
7323
|
"planner_turn_started",
|
|
@@ -7412,7 +7532,8 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
7412
7532
|
idleSince: task.idleSince ?? null,
|
|
7413
7533
|
isolation: task.isolation,
|
|
7414
7534
|
transport: task.transport,
|
|
7415
|
-
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) } : {},
|
|
7416
7537
|
...task.continuable ? { continuable: true } : {},
|
|
7417
7538
|
awaitingApproval: typeof task.awaitingApproval === "number" && task.awaitingApproval > 0 ? task.awaitingApproval : void 0,
|
|
7418
7539
|
...task.mergeGate?.length ? { mergeGate: task.mergeGate.map(String) } : {},
|
|
@@ -7426,10 +7547,10 @@ function dispatchLifecycle(dispatch, event, sessionId) {
|
|
|
7426
7547
|
case "task_started":
|
|
7427
7548
|
dispatch({ type: "taskStarted", taskId: String(event.taskId), title: String(event.title ?? event.taskId), runner: event.runner, sessionId });
|
|
7428
7549
|
return;
|
|
7429
|
-
// The extension shows these in its checkpoint panel; here they are
|
|
7430
|
-
// 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.
|
|
7431
7552
|
case "checkpoint":
|
|
7432
|
-
dispatch({ type: "
|
|
7553
|
+
dispatch({ type: "taskCheckpoint", taskId: String(event.taskId), title: String(event.taskTitle), summary: event.summary, sessionId });
|
|
7433
7554
|
return;
|
|
7434
7555
|
case "review_needed":
|
|
7435
7556
|
dispatch({ type: "notice", message: "Plan needs your sign-off \u2014 /approve to continue." });
|
|
@@ -7587,20 +7708,18 @@ async function perform(effect, deps) {
|
|
|
7587
7708
|
return;
|
|
7588
7709
|
case "taskAction": {
|
|
7589
7710
|
const { sessionId, taskId, action } = effect;
|
|
7590
|
-
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);
|
|
7591
7712
|
await (effect.watch ? withExecutionStream(deps, sessionId, request) : request());
|
|
7592
7713
|
await refreshPlan(deps, sessionId);
|
|
7593
7714
|
return;
|
|
7594
7715
|
}
|
|
7595
|
-
case "addTask":
|
|
7596
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
7599
|
-
prompt: effect.title,
|
|
7600
|
-
type: "ai"
|
|
7601
|
-
});
|
|
7716
|
+
case "addTask": {
|
|
7717
|
+
const fields = (0, import_core35.newTaskFields)(effect.title);
|
|
7718
|
+
if (!fields) return;
|
|
7719
|
+
await api.addTask(effect.sessionId, { ...fields });
|
|
7602
7720
|
await refreshPlan(deps, effect.sessionId);
|
|
7603
7721
|
return;
|
|
7722
|
+
}
|
|
7604
7723
|
case "updateTask":
|
|
7605
7724
|
await api.updateTask(effect.sessionId, effect.taskId, effect.changes);
|
|
7606
7725
|
await refreshPlan(deps, effect.sessionId);
|
|
@@ -7627,6 +7746,13 @@ async function perform(effect, deps) {
|
|
|
7627
7746
|
}
|
|
7628
7747
|
return;
|
|
7629
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;
|
|
7630
7756
|
// A task with a saved log ran structured, whatever its `transport` says
|
|
7631
7757
|
// after a reload; it has no tmux window, so open the log rather than fail.
|
|
7632
7758
|
case "openTaskTerminal": {
|
|
@@ -7676,6 +7802,16 @@ async function perform(effect, deps) {
|
|
|
7676
7802
|
if (!removed) dispatch({ type: "notice", message: "That message was already delivered to the task." });
|
|
7677
7803
|
return;
|
|
7678
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
|
+
}
|
|
7679
7815
|
case "interruptTask":
|
|
7680
7816
|
await api.interruptTask(effect.sessionId, effect.taskId);
|
|
7681
7817
|
dispatch({ type: "notice", message: "Interrupting the task\u2026" });
|
|
@@ -7699,7 +7835,7 @@ async function perform(effect, deps) {
|
|
|
7699
7835
|
dispatch({ type: "notice", message: `Orchestrator model set to ${effect.modelId}.` });
|
|
7700
7836
|
return;
|
|
7701
7837
|
case "setPlanner": {
|
|
7702
|
-
const meta =
|
|
7838
|
+
const meta = import_core35.ALL_PROVIDERS[effect.provider];
|
|
7703
7839
|
if (!meta) throw new Error(`Unknown planner: ${effect.provider}`);
|
|
7704
7840
|
const env = { AI_PROVIDER: effect.provider };
|
|
7705
7841
|
const settings = await api.updateSettings({ env });
|
|
@@ -7711,7 +7847,7 @@ async function perform(effect, deps) {
|
|
|
7711
7847
|
});
|
|
7712
7848
|
dispatch({
|
|
7713
7849
|
type: "notice",
|
|
7714
|
-
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)}`
|
|
7715
7851
|
});
|
|
7716
7852
|
await loadModels(deps);
|
|
7717
7853
|
return;
|
|
@@ -7723,7 +7859,7 @@ async function perform(effect, deps) {
|
|
|
7723
7859
|
dispatch({ type: "notice", message: `Planner effort set to ${effect.effort || "the runner default"}.` });
|
|
7724
7860
|
return;
|
|
7725
7861
|
case "setApiKey": {
|
|
7726
|
-
const meta =
|
|
7862
|
+
const meta = import_core35.ALL_PROVIDERS[effect.provider];
|
|
7727
7863
|
if (!meta) throw new Error(`Unknown provider: ${effect.provider}`);
|
|
7728
7864
|
await api.updateSettings({ env: { [meta.apiKeyEnvVar]: effect.key } });
|
|
7729
7865
|
deps.setEnvVar(meta.apiKeyEnvVar, effect.key);
|
|
@@ -7761,7 +7897,7 @@ async function perform(effect, deps) {
|
|
|
7761
7897
|
return;
|
|
7762
7898
|
case "setAutonomous":
|
|
7763
7899
|
deps.setEnvVar("ORDEWELL_AUTONOMOUS_MODE", String(effect.enabled));
|
|
7764
|
-
dispatch({ type: "notice", message: `Autonomy level: ${(0,
|
|
7900
|
+
dispatch({ type: "notice", message: `Autonomy level: ${(0, import_core35.autonomyLevelLabel)(effect.enabled)} for new plans.` });
|
|
7765
7901
|
return;
|
|
7766
7902
|
case "setMouseCapture":
|
|
7767
7903
|
deps.setMouseCapture(effect.enabled);
|
|
@@ -7895,7 +8031,7 @@ function osc52(text) {
|
|
|
7895
8031
|
function copySelection(deps, text) {
|
|
7896
8032
|
if (!text) return;
|
|
7897
8033
|
const lines = text.split("\n").length;
|
|
7898
|
-
const command = (0,
|
|
8034
|
+
const command = (0, import_core35.clipboardCopyCommand)(deps.hasBin);
|
|
7899
8035
|
if (command) {
|
|
7900
8036
|
try {
|
|
7901
8037
|
(deps.pipeToClipboard ?? pipeToClipboardDefault)(command, text);
|
|
@@ -8012,6 +8148,7 @@ var CONTROL2 = {
|
|
|
8012
8148
|
"": "ctrl-o",
|
|
8013
8149
|
"": "ctrl-p",
|
|
8014
8150
|
"": "ctrl-r",
|
|
8151
|
+
"": "ctrl-s",
|
|
8015
8152
|
"": "ctrl-t",
|
|
8016
8153
|
"": "ctrl-u",
|
|
8017
8154
|
"": "ctrl-w",
|
|
@@ -8285,7 +8422,7 @@ async function handleTui(subArgs) {
|
|
|
8285
8422
|
}
|
|
8286
8423
|
const workspace = flag(subArgs, "--workspace") || process.cwd();
|
|
8287
8424
|
try {
|
|
8288
|
-
(0,
|
|
8425
|
+
(0, import_core36.assertWorkspaceExists)(workspace);
|
|
8289
8426
|
} catch {
|
|
8290
8427
|
console.error(
|
|
8291
8428
|
`Workspace "${workspace}" does not exist.
|
|
@@ -8294,7 +8431,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8294
8431
|
);
|
|
8295
8432
|
process.exit(1);
|
|
8296
8433
|
}
|
|
8297
|
-
registerSkillCommands((0,
|
|
8434
|
+
registerSkillCommands((0, import_core36.createSkillsService)(workspace).listSkills());
|
|
8298
8435
|
const explicitPort = flag(subArgs, "--port") || process.env.ORDEWELL_PORT;
|
|
8299
8436
|
const targetPort = explicitPort ? resolvePort(subArgs) : await findFreePort();
|
|
8300
8437
|
const { port, owned: ownedAtLaunch } = await ensureDaemonOwned(targetPort, { detached: false });
|
|
@@ -8311,7 +8448,7 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
8311
8448
|
workspace,
|
|
8312
8449
|
port,
|
|
8313
8450
|
dispatch: (action) => app.dispatch(action),
|
|
8314
|
-
newSessionId: () => (0,
|
|
8451
|
+
newSessionId: () => (0, import_core36.mintSessionId)(),
|
|
8315
8452
|
setEnvVar: (key, value) => {
|
|
8316
8453
|
writeEnvVar(findEnvFile(), key, value);
|
|
8317
8454
|
process.env[key] = value;
|
|
@@ -8402,6 +8539,7 @@ var COMMANDS = {
|
|
|
8402
8539
|
"retry": handleRetry,
|
|
8403
8540
|
"cancel": handleCancel,
|
|
8404
8541
|
continue: handleContinue,
|
|
8542
|
+
checkpoint: handleCheckpoint,
|
|
8405
8543
|
"add-task": handleAddTask,
|
|
8406
8544
|
"remove-task": handleRemoveTask,
|
|
8407
8545
|
"task-runner": handleTaskRunner,
|
|
@@ -8435,7 +8573,7 @@ function cliVersion() {
|
|
|
8435
8573
|
// src/main.ts
|
|
8436
8574
|
var TUI_FLAGS = /* @__PURE__ */ new Set(["--workspace", "--port"]);
|
|
8437
8575
|
async function main() {
|
|
8438
|
-
(0,
|
|
8576
|
+
(0, import_core37.migrateOldConfigDir)();
|
|
8439
8577
|
loadEnvFile();
|
|
8440
8578
|
const argv = process.argv.slice(2);
|
|
8441
8579
|
if (TUI_FLAGS.has(argv[0] ?? "")) argv.unshift("tui");
|