@ordewell/cli 0.10.4 → 0.10.5
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 +174 -72
- package/package.json +3 -3
package/dist/main.js
CHANGED
|
@@ -145,6 +145,7 @@ Planning:
|
|
|
145
145
|
ordewell fork Continue in a copy of the conversation and its tasks
|
|
146
146
|
ordewell compact Condense the conversation into a summary (tasks are kept)
|
|
147
147
|
ordewell rewind [<n>] Fork the conversation from before message <n> (no <n>: list them)
|
|
148
|
+
ordewell resend cancel Keep a planner turn waiting on its usage limit from going at the reset
|
|
148
149
|
ordewell handoff [action] Review, merge, discard or clean up an isolated run
|
|
149
150
|
|
|
150
151
|
Tasks (<id> is an order number or a task ID):
|
|
@@ -153,7 +154,7 @@ Tasks (<id> is an order number or a task ID):
|
|
|
153
154
|
ordewell complete <id> Mark a task complete (alias: mark-complete)
|
|
154
155
|
ordewell uncomplete <id> Mark a completed task not done
|
|
155
156
|
ordewell skip <id> Skip a task: nothing it did lands, dependents go ahead
|
|
156
|
-
ordewell force-start <id>
|
|
157
|
+
ordewell force-start <id> Approve a task and start it now, or once its dependencies finish (asks past a merge gate)
|
|
157
158
|
ordewell run-task <id> Run only one task
|
|
158
159
|
ordewell retry <id> Re-run a failed task
|
|
159
160
|
ordewell continue <id> <msg> Continue a finished task in its saved runner session
|
|
@@ -548,8 +549,7 @@ var ApiClient = class _ApiClient {
|
|
|
548
549
|
...body ? { "Content-Type": "application/json" } : {},
|
|
549
550
|
...token ? { Authorization: (0, import_core4.bearerHeaderValue)(token) } : {}
|
|
550
551
|
},
|
|
551
|
-
// Planner research on a real repository regularly exceeds two minutes
|
|
552
|
-
// The benchmark harness uses the same 15-minute default explicitly;
|
|
552
|
+
// Planner research on a real repository regularly exceeds two minutes;
|
|
553
553
|
// keep the env override for callers that need a tighter/looser bound.
|
|
554
554
|
timeout: Number.isFinite(configuredTimeout) && configuredTimeout > 0 ? configuredTimeout : DEFAULT_HTTP_TIMEOUT_MS
|
|
555
555
|
};
|
|
@@ -644,6 +644,10 @@ var ApiClient = class _ApiClient {
|
|
|
644
644
|
cancelResume(sessionId, taskId) {
|
|
645
645
|
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/resume/cancel`, "Cancel resume failed");
|
|
646
646
|
}
|
|
647
|
+
/** Keep what waits on the planner's usage limit from being sent at the reset; `cancelled` is false when nothing waited. */
|
|
648
|
+
cancelPlannerResend(sessionId) {
|
|
649
|
+
return this.call("POST", `/api/plans/${sessionId}/converse/resend/cancel`, "Cancel resend failed");
|
|
650
|
+
}
|
|
647
651
|
/** Resume a task paused on a usage limit now, in its saved session, once the user says the limit has reset. */
|
|
648
652
|
resumeNow(sessionId, taskId) {
|
|
649
653
|
return this.call("POST", `/api/plans/${sessionId}/tasks/${taskId}/resume/now`, "Resume failed");
|
|
@@ -1610,10 +1614,21 @@ var STATE_OF = {
|
|
|
1610
1614
|
function reposOf(run) {
|
|
1611
1615
|
if (Array.isArray(run.repos)) {
|
|
1612
1616
|
const repos = run.repos.filter((r) => r && typeof r.path === "string" && typeof r.integrationBranch === "string" && typeof r.baseRef === "string");
|
|
1613
|
-
return repos.length > 0 ? repos.map((r) => ({
|
|
1617
|
+
return repos.length > 0 ? repos.map((r) => ({
|
|
1618
|
+
path: String(r.path),
|
|
1619
|
+
integrationBranch: String(r.integrationBranch),
|
|
1620
|
+
baseRef: String(r.baseRef),
|
|
1621
|
+
...typeof r.baseBranch === "string" && r.baseBranch ? { baseBranch: r.baseBranch } : {},
|
|
1622
|
+
...r.startedDetached === true ? { startedDetached: true } : {}
|
|
1623
|
+
})) : null;
|
|
1614
1624
|
}
|
|
1615
1625
|
if (typeof run.integrationBranch === "string" && typeof run.baseRef === "string") {
|
|
1616
|
-
return [{
|
|
1626
|
+
return [{
|
|
1627
|
+
path: ".",
|
|
1628
|
+
integrationBranch: run.integrationBranch,
|
|
1629
|
+
baseRef: run.baseRef,
|
|
1630
|
+
...typeof run.baseBranch === "string" && run.baseBranch ? { baseBranch: run.baseBranch } : {}
|
|
1631
|
+
}];
|
|
1617
1632
|
}
|
|
1618
1633
|
return null;
|
|
1619
1634
|
}
|
|
@@ -1664,15 +1679,11 @@ function handoffBranch(handoff) {
|
|
|
1664
1679
|
return [...new Set(handoff.repos.map((r) => r.integrationBranch))].join(", ");
|
|
1665
1680
|
}
|
|
1666
1681
|
function handoffBase(handoff, length) {
|
|
1667
|
-
const group = isRepoGroup(handoff);
|
|
1668
|
-
return handoff.repos.map((r) =>
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
}
|
|
1673
|
-
function reposWithWork(handoff) {
|
|
1674
|
-
const withWork = handoff.repos.filter((r) => r.landed.length > 0);
|
|
1675
|
-
return (withWork.length > 0 ? withWork : handoff.repos).map((r) => r.path);
|
|
1682
|
+
const group = (0, import_core10.isRepoGroup)(handoff);
|
|
1683
|
+
return handoff.repos.map((r) => {
|
|
1684
|
+
const ref = r.baseRef.slice(0, length);
|
|
1685
|
+
return `${group ? `${r.path}: ` : ""}${r.baseBranch ? `${r.baseBranch} @ ${ref}` : `${r.startedDetached ? "detached at" : "base"} ${ref}`}`;
|
|
1686
|
+
}).join(", ");
|
|
1676
1687
|
}
|
|
1677
1688
|
function repairedLanded(handoff) {
|
|
1678
1689
|
return handoff.landed.filter((t) => t.repairedFiles?.length);
|
|
@@ -1750,7 +1761,7 @@ async function followExecution(api, sessionId, start, onBlocked) {
|
|
|
1750
1761
|
for (const t of entries) {
|
|
1751
1762
|
const icon = iconFor(t.status);
|
|
1752
1763
|
const verdict = t.verdict ? `: ${t.verdict.outcome.toUpperCase()}: ${t.verdict.reason.slice(0, 60)}` : "";
|
|
1753
|
-
const waits = t.waitsForApproval ? `: ${inLine(import_core12.WAITS_FOR_APPROVAL_LABEL)}: \`ordewell
|
|
1764
|
+
const waits = t.waitsForApproval ? `: ${inLine(import_core12.WAITS_FOR_APPROVAL_LABEL)}: \`ordewell force-start <id>\` approves it, \`ordewell run\` approves all` : "";
|
|
1754
1765
|
lines.push(` ${icon} #${t.id.slice(-4)} ${t.status}${verdict}${waits}`);
|
|
1755
1766
|
}
|
|
1756
1767
|
const output = lines.join("\n");
|
|
@@ -1812,10 +1823,10 @@ Done. ${summary.completed || 0} completed, ${summary.failed || 0} failed, ${summ
|
|
|
1812
1823
|
}
|
|
1813
1824
|
if (event.type === "isolation_handoff") {
|
|
1814
1825
|
const n = event.landed.length;
|
|
1815
|
-
const where = isRepoGroup(event) ? ` in ${event.repos.map((r) => r.path).join(", ")}` : "";
|
|
1826
|
+
const where = (0, import_core10.isRepoGroup)(event) ? ` in ${event.repos.map((r) => r.path).join(", ")}` : "";
|
|
1816
1827
|
console.log(`
|
|
1817
1828
|
Run finished on ${handoffBranch(event)}${where}: ${n} task${n === 1 ? "" : "s"} landed.`);
|
|
1818
|
-
if (isRepoGroup(event)) for (const line of repoResultLines(event)) console.log(` ${line}`);
|
|
1829
|
+
if ((0, import_core10.isRepoGroup)(event)) for (const line of repoResultLines(event)) console.log(` ${line}`);
|
|
1819
1830
|
if (event.deferredTo === "supervisor") console.log(" The supervisor may merge it.");
|
|
1820
1831
|
console.log(" `ordewell handoff review|merge|discard|cleanup` to land it.");
|
|
1821
1832
|
}
|
|
@@ -2656,27 +2667,28 @@ function formatCost(currency, amount) {
|
|
|
2656
2667
|
function tokenLine(blocks2, cols) {
|
|
2657
2668
|
const usage = blocks2[blocks2.length - 1];
|
|
2658
2669
|
if (usage?.type !== "usage") return null;
|
|
2659
|
-
const parts = usageMeasures(usage.totals
|
|
2670
|
+
const parts = usageMeasures(usage.totals);
|
|
2660
2671
|
if (usage.model && !usage.contextFill) parts.push("ctx unknown");
|
|
2661
2672
|
if (usage.contextFill) parts.splice(2, 0, `${Math.round(usage.contextFill.usedTokens / usage.contextFill.windowTokens * 100)}% ctx`);
|
|
2662
2673
|
return parts.length > 0 ? style.grey(truncate(parts.join(" \xB7 "), cols)) : null;
|
|
2663
2674
|
}
|
|
2664
|
-
function usageMeasures(totals
|
|
2675
|
+
function usageMeasures(totals) {
|
|
2665
2676
|
return [
|
|
2666
|
-
...totals.inputTokens !== void 0 ? [`${compactCount(totals.inputTokens)} in`] :
|
|
2667
|
-
...totals.outputTokens !== void 0 ? [`${compactCount(totals.outputTokens)} out`] :
|
|
2677
|
+
...totals.inputTokens !== void 0 ? [`${compactCount(totals.inputTokens)} in`] : [],
|
|
2678
|
+
...totals.outputTokens !== void 0 ? [`${compactCount(totals.outputTokens)} out`] : [],
|
|
2668
2679
|
...totals.cachedInputTokens !== void 0 ? [`${compactCount(totals.cachedInputTokens)} cached`] : [],
|
|
2669
|
-
...Object.entries(totals.reportedCost ?? {}).map(([currency, amount]) => formatCost(currency, amount))
|
|
2670
|
-
...unknown && !Object.keys(totals.reportedCost ?? {}).length ? ["cost unknown"] : []
|
|
2680
|
+
...Object.entries(totals.reportedCost ?? {}).map(([currency, amount]) => formatCost(currency, amount))
|
|
2671
2681
|
];
|
|
2672
2682
|
}
|
|
2673
2683
|
function taskUsageText(usage) {
|
|
2674
2684
|
return [
|
|
2675
|
-
...usageMeasures(usage.totals
|
|
2676
|
-
...usage.attempt !== void 0 ? [usage.contextFill ?
|
|
2677
|
-
usage.wallTimeMs !== void 0 ? `${Math.floor(usage.wallTimeMs / 1e3)}s` : "time unknown"
|
|
2685
|
+
...usageMeasures(usage.totals),
|
|
2686
|
+
...usage.attempt !== void 0 ? [usage.contextFill ? `ctx ${Math.round(usage.contextFill.usedTokens / usage.contextFill.windowTokens * 100)}%` : "ctx unknown"] : []
|
|
2678
2687
|
].join(" \xB7 ");
|
|
2679
2688
|
}
|
|
2689
|
+
function assignmentFlagText(row) {
|
|
2690
|
+
return `${row.assignmentFlag} \u2014 ${row.assignmentConsequence}`;
|
|
2691
|
+
}
|
|
2680
2692
|
|
|
2681
2693
|
// src/commands/status.ts
|
|
2682
2694
|
var import_fs4 = require("fs");
|
|
@@ -2694,7 +2706,7 @@ async function handleStatus(subArgs, injectedApi) {
|
|
|
2694
2706
|
emitJson(session, outputPath);
|
|
2695
2707
|
return;
|
|
2696
2708
|
}
|
|
2697
|
-
printOneSession(sessionId, session.meta, session.plan);
|
|
2709
|
+
printOneSession(sessionId, session.meta, session.plan, await assignmentScope(api));
|
|
2698
2710
|
} else {
|
|
2699
2711
|
const sessions = await api.getSessions(workspace);
|
|
2700
2712
|
if (asJson) {
|
|
@@ -2733,7 +2745,20 @@ function emitJson(payload, outputPath) {
|
|
|
2733
2745
|
process.exit(1);
|
|
2734
2746
|
}
|
|
2735
2747
|
}
|
|
2736
|
-
function
|
|
2748
|
+
async function assignmentScope(api) {
|
|
2749
|
+
try {
|
|
2750
|
+
const [runners2, settings, models] = await Promise.all([api.getRunners(), api.getSettings(), api.getModels()]);
|
|
2751
|
+
if (!Array.isArray(runners2.runners) || runners2.runners.length === 0) return void 0;
|
|
2752
|
+
return {
|
|
2753
|
+
enabledRunners: runners2.runners.filter((r) => r.enabled).map((r) => r.id),
|
|
2754
|
+
allowlist: settings.modelAllowlist ?? {},
|
|
2755
|
+
models: models.modelsByRunner ?? {}
|
|
2756
|
+
};
|
|
2757
|
+
} catch {
|
|
2758
|
+
return void 0;
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2761
|
+
function printOneSession(sessionId, meta, plan, runnerScope) {
|
|
2737
2762
|
const allTasks = allTasksOf(plan);
|
|
2738
2763
|
const everyTask = (0, import_core15.flattenTasks)(allTasks);
|
|
2739
2764
|
const blocked = (0, import_core15.blockingFailures)(everyTask.map((t) => ({ ...t, subtasks: [], dependencies: t.dependencies ?? [] })));
|
|
@@ -2742,20 +2767,23 @@ function printOneSession(sessionId, meta, plan) {
|
|
|
2742
2767
|
console.log(`Goal: ${meta.goal}`);
|
|
2743
2768
|
console.log(`Runner: ${(meta.runners || []).join(", ")} | Status: ${meta.status}`);
|
|
2744
2769
|
console.log(`Phase: ${plan?.phase || "unknown"} | Created: ${meta.createdAt}`);
|
|
2770
|
+
const isolation = isolationOfPlan(plan);
|
|
2771
|
+
if (isolation) console.log(`Base: ${handoffBase(isolation.handoff, 8)}`);
|
|
2745
2772
|
console.log(`Tasks: ${everyTask.length} total | ${count("completed")} done | ${count("failed")} failed | ${count("in_progress")} running | ${count("blocked")} blocked | ${count("skipped")} skipped
|
|
2746
2773
|
`);
|
|
2747
2774
|
const usage = (0, import_core15.planUsage)(allTasks);
|
|
2748
2775
|
if (usage.attempts) console.log(`Tasks total: ${taskUsageText(usage)}`);
|
|
2749
2776
|
const record = servedApprovalRecord(plan);
|
|
2750
|
-
const approval = (row, ref) => row.waitsForApproval ? `${row.waitsForApproval}: \`ordewell
|
|
2777
|
+
const approval = (row, ref) => row.waitsForApproval ? `${row.waitsForApproval}: \`ordewell force-start ${ref}\` approves it, \`ordewell run\` approves all` : null;
|
|
2751
2778
|
for (const t of allTasks) {
|
|
2752
2779
|
const icon = iconFor(blocked.has(t.id) ? "blocked" : t.status);
|
|
2753
2780
|
const model = t.assignedModel ? ` (${t.assignedModel.modelLabel})` : "";
|
|
2754
2781
|
const shortId = (t.id ?? "").slice(-8);
|
|
2755
2782
|
console.log(` ${(0, import_core15.taskOrderLabel)(t).padStart(2)}. ${icon} [${shortId}] ${t.title}${model}`);
|
|
2756
|
-
const row = (0, import_core15.taskRowView)({ ...t, dependencies: t.dependencies ?? [], waitsForApproval: (0, import_core15.waitsForApproval)(t, record) }, { tasks: allTasks });
|
|
2783
|
+
const row = (0, import_core15.taskRowView)({ ...t, dependencies: t.dependencies ?? [], waitsForApproval: (0, import_core15.waitsForApproval)(t, record) }, { tasks: allTasks, runnerScope });
|
|
2757
2784
|
const waiting = approval(row, (0, import_core15.taskOrderLabel)(t));
|
|
2758
2785
|
if (waiting) console.log(` ${waiting}`);
|
|
2786
|
+
if (row.assignmentFlag) console.log(` \u26A0 ${assignmentFlagText(row)}`);
|
|
2759
2787
|
if (row.blockedBy) console.log(` ${(0, import_core15.blockedByLabel)(row.blockedBy)}`);
|
|
2760
2788
|
if (row.status === "skipped") console.log(` ${(0, import_core15.skippedLabel)(row.skipReason)}`);
|
|
2761
2789
|
if (row.usage) console.log(` ${taskUsageText(row.usage)}`);
|
|
@@ -2764,9 +2792,10 @@ function printOneSession(sessionId, meta, plan) {
|
|
|
2764
2792
|
const subModel = sub.assignedModel ? ` (${sub.assignedModel.modelLabel})` : "";
|
|
2765
2793
|
const subShortId = (sub.id ?? "").slice(-8);
|
|
2766
2794
|
console.log(` ${(0, import_core15.taskOrderLabel)(sub, t)}. ${subIcon} [${subShortId}] ${sub.title}${subModel}`);
|
|
2767
|
-
const subRow = (0, import_core15.taskRowView)({ ...sub, dependencies: sub.dependencies ?? [], waitsForApproval: (0, import_core15.waitsForApproval)(sub, record) }, { parent: t, tasks: allTasks });
|
|
2795
|
+
const subRow = (0, import_core15.taskRowView)({ ...sub, dependencies: sub.dependencies ?? [], waitsForApproval: (0, import_core15.waitsForApproval)(sub, record) }, { parent: t, tasks: allTasks, runnerScope });
|
|
2768
2796
|
const subWaiting = approval(subRow, (0, import_core15.taskOrderLabel)(sub, t));
|
|
2769
2797
|
if (subWaiting) console.log(` ${subWaiting}`);
|
|
2798
|
+
if (subRow.assignmentFlag) console.log(` \u26A0 ${assignmentFlagText(subRow)}`);
|
|
2770
2799
|
if (subRow.blockedBy) console.log(` ${(0, import_core15.blockedByLabel)(subRow.blockedBy)}`);
|
|
2771
2800
|
if (subRow.status === "skipped") console.log(` ${(0, import_core15.skippedLabel)(subRow.skipReason)}`);
|
|
2772
2801
|
if (subRow.usage) console.log(` ${taskUsageText(subRow.usage)}`);
|
|
@@ -3263,8 +3292,8 @@ function makeHandler(action, command = action) {
|
|
|
3263
3292
|
async (api, sessionId, taskId) => {
|
|
3264
3293
|
if (PASSES_GATE.has(action)) await confirmPastGate(subArgs, api, sessionId, taskId, confirm2);
|
|
3265
3294
|
try {
|
|
3266
|
-
await api.taskControl(sessionId, taskId, action);
|
|
3267
|
-
console.log(PAST_TENSE[action]);
|
|
3295
|
+
const { queued } = await api.taskControl(sessionId, taskId, action);
|
|
3296
|
+
console.log(queued ?? PAST_TENSE[action]);
|
|
3268
3297
|
} catch (err) {
|
|
3269
3298
|
console.error(`Failed to ${action} task: ${err.message}`);
|
|
3270
3299
|
process.exit(1);
|
|
@@ -3561,6 +3590,17 @@ async function handleRewind(subArgs, injectedApi) {
|
|
|
3561
3590
|
console.log("\nThe message you rewound to, to resend or edit:\n");
|
|
3562
3591
|
console.log(fork.rewoundMessage);
|
|
3563
3592
|
}
|
|
3593
|
+
async function handleResend(subArgs, injectedApi) {
|
|
3594
|
+
if (positionals(subArgs)[0] !== "cancel") fail("Usage: ordewell resend cancel [--session-id <id>] [--workspace /path]");
|
|
3595
|
+
const { api, sessionId } = await adopted(subArgs, injectedApi);
|
|
3596
|
+
let cancelled;
|
|
3597
|
+
try {
|
|
3598
|
+
({ cancelled } = await api.cancelPlannerResend(sessionId));
|
|
3599
|
+
} catch (err) {
|
|
3600
|
+
fail(`Failed to cancel the resend: ${err.message}`);
|
|
3601
|
+
}
|
|
3602
|
+
console.log(cancelled ? "Resend cancelled: what waits goes with your next message." : "Nothing waits on the planner's usage limit to be sent at a reset.");
|
|
3603
|
+
}
|
|
3564
3604
|
|
|
3565
3605
|
// src/tui/transcript.ts
|
|
3566
3606
|
var import_core22 = require("@ordewell/core");
|
|
@@ -3594,7 +3634,7 @@ var HANDOFF_ACTIONS = [
|
|
|
3594
3634
|
{ id: "cleanup", label: "Clean up", hint: "remove worktrees and task branches, keep this branch" }
|
|
3595
3635
|
];
|
|
3596
3636
|
function handoffActions(handoff) {
|
|
3597
|
-
if (!isRepoGroup(handoff)) return HANDOFF_ACTIONS;
|
|
3637
|
+
if (!(0, import_core10.isRepoGroup)(handoff)) return HANDOFF_ACTIONS;
|
|
3598
3638
|
return [
|
|
3599
3639
|
{ id: "review", label: "Review diff", hint: "each repository's branch against the commit it forked from" },
|
|
3600
3640
|
{ id: "merge", label: "Merge all", hint: "every repository's branch into what you have checked out there (asks first)" },
|
|
@@ -3619,7 +3659,7 @@ function handoffCommand(state, arg) {
|
|
|
3619
3659
|
}
|
|
3620
3660
|
function handoffArrived(state, handoff) {
|
|
3621
3661
|
const landed = handoff.landed.length;
|
|
3622
|
-
const where = isRepoGroup(handoff) ? ` in ${reposOf2(handoff)}` : "";
|
|
3662
|
+
const where = (0, import_core10.isRepoGroup)(handoff) ? ` in ${reposOf2(handoff)}` : "";
|
|
3623
3663
|
const branch = handoffBranch(handoff);
|
|
3624
3664
|
const tasks = `${landed} task${landed === 1 ? "" : "s"} landed`;
|
|
3625
3665
|
const received = { ...state, handoff };
|
|
@@ -3654,8 +3694,8 @@ function runHandoffAction(state, id) {
|
|
|
3654
3694
|
...state,
|
|
3655
3695
|
overlay: {
|
|
3656
3696
|
kind: "confirm",
|
|
3657
|
-
title: isRepoGroup(handoff) ? "Merge what has landed into your branches?" : "Merge what has landed into your branch?",
|
|
3658
|
-
message: `Merge ${handoffBranch(handoff)} as it stands now into
|
|
3697
|
+
title: (0, import_core10.isRepoGroup)(handoff) ? "Merge what has landed into your branches?" : "Merge what has landed into your branch?",
|
|
3698
|
+
message: `Merge ${handoffBranch(handoff)} as it stands now into ${(0, import_core10.mergeTarget)(handoff)}.${(0, import_core10.checkoutRule)(handoff)} The run goes on: tasks waiting for Merge all start once it has merged, and later tasks land on the same branch for the next Merge all. A merge that conflicts is aborted, and your ${(0, import_core10.isRepoGroup)(handoff) ? "trees stay" : "tree stays"} as ${(0, import_core10.isRepoGroup)(handoff) ? "they were" : "it was"}.${repairedNotice(handoff)}`,
|
|
3659
3699
|
action: { kind: "merge-run" }
|
|
3660
3700
|
}
|
|
3661
3701
|
},
|
|
@@ -3665,15 +3705,15 @@ function runHandoffAction(state, id) {
|
|
|
3665
3705
|
return {
|
|
3666
3706
|
state: {
|
|
3667
3707
|
...state,
|
|
3668
|
-
overlay: isRepoGroup(handoff) ? {
|
|
3708
|
+
overlay: (0, import_core10.isRepoGroup)(handoff) ? {
|
|
3669
3709
|
kind: "confirm",
|
|
3670
3710
|
title: "Merge all into your branches?",
|
|
3671
|
-
message: `Merge ${handoffBranch(handoff)} into
|
|
3711
|
+
message: `Merge ${handoffBranch(handoff)} into ${(0, import_core10.mergeTarget)(handoff)}.${(0, import_core10.checkoutRule)(handoff)} 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 unless a task still has work in its worktree that has not landed: then the run stays, and so does that work.${repairedNotice(handoff)}`,
|
|
3672
3712
|
action: { kind: "merge-run" }
|
|
3673
3713
|
} : {
|
|
3674
3714
|
kind: "confirm",
|
|
3675
3715
|
title: "Merge into your branch?",
|
|
3676
|
-
message: `Merge ${handoffBranch(handoff)} into
|
|
3716
|
+
message: `Merge ${handoffBranch(handoff)} into ${(0, import_core10.mergeTarget)(handoff)}.${(0, import_core10.checkoutRule)(handoff)} 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 unless a task still has work in its worktree that has not landed: then the run stays, and so does that work.${repairedNotice(handoff)}`,
|
|
3677
3717
|
action: { kind: "merge-run" }
|
|
3678
3718
|
}
|
|
3679
3719
|
},
|
|
@@ -3686,7 +3726,7 @@ function runHandoffAction(state, id) {
|
|
|
3686
3726
|
overlay: {
|
|
3687
3727
|
kind: "confirm",
|
|
3688
3728
|
title: "Discard this run?",
|
|
3689
|
-
message: `Removes the run's worktrees and task branches and deletes ${handoffBranch(handoff)}${isRepoGroup(handoff) ? ` in ${reposOf2(handoff)}` : ""}. Tasks keep their status: mark one not done if you did not keep its work.`,
|
|
3729
|
+
message: `Removes the run's worktrees and task branches and deletes ${handoffBranch(handoff)}${(0, import_core10.isRepoGroup)(handoff) ? ` in ${reposOf2(handoff)}` : ""}. Tasks keep their status: mark one not done if you did not keep its work.`,
|
|
3690
3730
|
action: { kind: "discard-run" }
|
|
3691
3731
|
}
|
|
3692
3732
|
},
|
|
@@ -3704,7 +3744,7 @@ function confirmedHandoff(state, kind) {
|
|
|
3704
3744
|
type: "isolationMerge",
|
|
3705
3745
|
sessionId,
|
|
3706
3746
|
branch: handoffBranch(handoff),
|
|
3707
|
-
...isRepoGroup(handoff) ? { group: true } : {},
|
|
3747
|
+
...(0, import_core10.isRepoGroup)(handoff) ? { group: true } : {},
|
|
3708
3748
|
...repaired.length > 0 ? { repaired } : {},
|
|
3709
3749
|
...midRun(state) ? { midRun: true } : {}
|
|
3710
3750
|
} : { type: "isolationDiscard", sessionId, branch: handoffBranch(handoff) };
|
|
@@ -3789,7 +3829,7 @@ async function handleHandoff(subArgs, injectedApi, confirm2 = askYesNo) {
|
|
|
3789
3829
|
const handoff = isolationOfPlan(plan)?.handoff;
|
|
3790
3830
|
if (!handoff) fail("This session has no isolated run to hand off.");
|
|
3791
3831
|
const branch = handoffBranch(handoff);
|
|
3792
|
-
const group = isRepoGroup(handoff);
|
|
3832
|
+
const group = (0, import_core10.isRepoGroup)(handoff);
|
|
3793
3833
|
if (group) for (const line of repoResultLines(handoff)) console.error(line);
|
|
3794
3834
|
const asked = hasFlag(subArgs, "--yes");
|
|
3795
3835
|
const confirmed = async (question) => {
|
|
@@ -3810,7 +3850,7 @@ async function handleHandoff(subArgs, injectedApi, confirm2 = askYesNo) {
|
|
|
3810
3850
|
return;
|
|
3811
3851
|
}
|
|
3812
3852
|
case "merge": {
|
|
3813
|
-
await confirmed((group ? `Merge ${branch} into
|
|
3853
|
+
await confirmed((group ? `Merge ${branch} into ${(0, import_core10.mergeTarget)(handoff)}? Nothing is merged unless every repository can take it${(0, import_core10.checkoutRule)(handoff) ? " and is on that branch" : ""}; once a finished run has merged everything, its worktrees and branches are removed.` : `Merge ${branch} into ${(0, import_core10.mergeTarget)(handoff)}?${(0, import_core10.checkoutRule)(handoff)} Once a finished run has merged everything, its worktrees and branches are removed.`) + repairedNotice(handoff));
|
|
3814
3854
|
const { ok, message } = mergeOutcome(await attempt(() => api.mergeRun(sessionId)), branch, group, repairedLanded(handoff));
|
|
3815
3855
|
if (ok) console.log(message);
|
|
3816
3856
|
else fail(message);
|
|
@@ -4426,6 +4466,7 @@ function initialState(overrides = {}) {
|
|
|
4426
4466
|
workspace: process.cwd(),
|
|
4427
4467
|
handoff: null,
|
|
4428
4468
|
runMerge: null,
|
|
4469
|
+
plannerResend: null,
|
|
4429
4470
|
overlay: null,
|
|
4430
4471
|
pendingApprovals: [],
|
|
4431
4472
|
queuedPrompts: import_core28.EMPTY_HOLD,
|
|
@@ -4666,6 +4707,7 @@ var SLASH_COMMANDS = [
|
|
|
4666
4707
|
{ name: "approve", usage: "/approve", description: "Approve the drafted plan", category: "planning" },
|
|
4667
4708
|
{ name: "run", usage: "/run", description: "Execute the approved plan", category: "planning" },
|
|
4668
4709
|
{ name: "stop", usage: "/stop", description: "Stop the planner or the running execution, whichever is active", category: "planning" },
|
|
4710
|
+
{ name: "resend", usage: "/resend cancel", description: "Keep what waits on the planner's usage limit from being sent at the reset: it goes with your next message", category: "planning" },
|
|
4669
4711
|
{ name: "handoff", usage: "/handoff [review|merge|discard|cleanup]", description: "Land an isolated run's branch: review it, merge it into yours, or discard it", category: "planning" },
|
|
4670
4712
|
// Tasks
|
|
4671
4713
|
{ name: "add-task", usage: "/add-task <title>", description: "Add a task to the current plan", category: "tasks" },
|
|
@@ -4675,7 +4717,7 @@ var SLASH_COMMANDS = [
|
|
|
4675
4717
|
{ name: "skip", usage: "/skip <id>", description: "Skip a task", category: "tasks" },
|
|
4676
4718
|
{ name: "retry", usage: "/retry <id>", description: "Re-run a failed task", category: "tasks" },
|
|
4677
4719
|
{ name: "cancel", usage: "/cancel <id>", description: "Kill a running task", category: "tasks" },
|
|
4678
|
-
{ name: "force-start", usage: "/force-start <id>", description: "
|
|
4720
|
+
{ name: "force-start", usage: "/force-start <id>", description: "Approve a task and start it now, or once its dependencies finish", category: "tasks" },
|
|
4679
4721
|
{ name: "continue", usage: "/continue <id> <message>", description: "Continue a finished task in its saved runner session, with your message", category: "tasks" },
|
|
4680
4722
|
{ name: "open", usage: "/open <id>", description: "Open a task's view: its log, and the keys that answer its checkpoint or tool request", category: "tasks" },
|
|
4681
4723
|
{ 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" },
|
|
@@ -4841,6 +4883,15 @@ function refillPicker(state, kinds) {
|
|
|
4841
4883
|
const hint = overlay.picker.action.kind === "set-model" ? providerErrorHint(state) : overlay.picker.hint;
|
|
4842
4884
|
return { ...state, overlay: { kind: "picker", picker: { ...overlay.picker, items, hint, index: 0 } } };
|
|
4843
4885
|
}
|
|
4886
|
+
function reseedRunnerChoice(before, after) {
|
|
4887
|
+
const overlay = after.overlay;
|
|
4888
|
+
if (overlay?.kind !== "picker" || overlay.picker.action.kind !== "set-runners") return after;
|
|
4889
|
+
const enabledIds = (state) => state.runners.filter((r) => r.enabled).map((r) => r.id);
|
|
4890
|
+
const shown = enabledIds(before);
|
|
4891
|
+
const untouched = overlay.picker.chosen.length === shown.length && shown.every((id) => overlay.picker.chosen.includes(id));
|
|
4892
|
+
const picker2 = { ...overlay.picker, items: pickerItemsFor(after, overlay.picker.action) };
|
|
4893
|
+
return { ...after, overlay: { kind: "picker", picker: untouched ? { ...picker2, chosen: enabledIds(after) } : picker2 } };
|
|
4894
|
+
}
|
|
4844
4895
|
function modelDetail(m) {
|
|
4845
4896
|
return [m.provider, m.pricing].filter(Boolean).join(" \xB7 ") || void 0;
|
|
4846
4897
|
}
|
|
@@ -5039,7 +5090,13 @@ var PLAN_KEYS = {
|
|
|
5039
5090
|
deps: { keys: "D", meaning: "pick the tasks it depends on", group: "Assign" },
|
|
5040
5091
|
skills: { keys: "K", meaning: "pick the skills it loads", group: "Assign" },
|
|
5041
5092
|
ops: { keys: "O", meaning: "switch between a change and an ops task", group: "Assign" },
|
|
5042
|
-
start: {
|
|
5093
|
+
start: {
|
|
5094
|
+
keys: "f",
|
|
5095
|
+
meaning: "start the task, approving it if it waits for you; it starts once its dependencies finish",
|
|
5096
|
+
group: "Run",
|
|
5097
|
+
// Added after the plan was approved, it waits for this key; the row says the same.
|
|
5098
|
+
hint: (task) => task && (0, import_core32.taskRowView)(task).waitsForApproval ? "f approve" : "f start"
|
|
5099
|
+
},
|
|
5043
5100
|
runPlan: { keys: "E", meaning: "run the whole plan", group: "Run", hint: "E run plan" },
|
|
5044
5101
|
stop: { keys: "S", meaning: "stop the run", group: "Run", hint: "S stop" },
|
|
5045
5102
|
cancel: {
|
|
@@ -5295,11 +5352,18 @@ function chatLayout(state, rows, cols) {
|
|
|
5295
5352
|
const bubbles = tv ? taskQueuedRows(state, cols) : queuedPromptRows(state, cols);
|
|
5296
5353
|
const content = [...transcript, ...bubbles.length > 0 ? ["", ...bubbles] : []];
|
|
5297
5354
|
const taskHeader = tv ? taskHeaderLines(state, tv, cols) : [];
|
|
5298
|
-
const usage = rows <= 1 ? [] : tv ? taskUsageFooter(state, tv, blocks2, cols) : tokenRow(blocks2, cols);
|
|
5355
|
+
const usage = rows <= 1 ? [] : tv ? taskUsageFooter(state, tv, blocks2, cols) : [...plannerResendRow(state, cols), ...tokenRow(blocks2, cols)];
|
|
5299
5356
|
const footer = usage.slice(0, Math.max(1, rows - taskHeader.length - 1));
|
|
5300
5357
|
const space = Math.max(0, rows - taskHeader.length - footer.length);
|
|
5301
5358
|
return { lines: content, header: taskHeader, footer, anchor: content.length > space ? "bottom" : "top", maxScroll: Math.max(0, content.length - space) };
|
|
5302
5359
|
}
|
|
5360
|
+
function plannerResendRow(state, cols) {
|
|
5361
|
+
const { plannerResend } = state;
|
|
5362
|
+
const at = plannerResend ? new Date(plannerResend.at) : null;
|
|
5363
|
+
if (!plannerResend || !at || Number.isNaN(at.getTime())) return [];
|
|
5364
|
+
const whose = plannerResend.runner ? `${plannerResend.runner}'s` : "the planner's";
|
|
5365
|
+
return [style.yellow(truncate(`\u23F8 Resending at ${(0, import_core34.resumeTime)(at)}, once ${whose} usage limit resets \xB7 /resend cancel keeps it for your next message`, cols))];
|
|
5366
|
+
}
|
|
5303
5367
|
var tokenRow = (blocks2, cols) => {
|
|
5304
5368
|
const line = tokenLine(blocks2, cols);
|
|
5305
5369
|
return line ? [line] : [];
|
|
@@ -5307,15 +5371,13 @@ var tokenRow = (blocks2, cols) => {
|
|
|
5307
5371
|
function taskUsageFooter(state, tv, blocks2, cols) {
|
|
5308
5372
|
const task = findTask(state.tasks, tv.taskId);
|
|
5309
5373
|
const reported = task?.attemptUsage ?? [];
|
|
5374
|
+
if (!task || reported.length === 0) return tokenRow(blocks2, cols);
|
|
5310
5375
|
const onScreen = tv.attempt > 0 ? reported.find((a) => a.attempt === tv.attempt) : reported.at(-1);
|
|
5311
|
-
|
|
5312
|
-
const
|
|
5313
|
-
if (
|
|
5314
|
-
const
|
|
5315
|
-
return [
|
|
5316
|
-
style.grey(truncate(taskUsageText(usage), cols)),
|
|
5317
|
-
...models.length > 1 ? models.map((m) => style.grey(truncate(`${sanitize(m.model ?? "unattributed")}: ${usageMeasures(m.totals, true).join(" \xB7 ")}`, cols))) : []
|
|
5318
|
-
];
|
|
5376
|
+
const spend = (0, import_core34.taskUsageSummary)(task);
|
|
5377
|
+
const total = taskUsageText({ ...spend, attempt: reported.at(-1)?.attempt ?? 0 });
|
|
5378
|
+
if (onScreen === reported.at(-1)) return [style.grey(truncate(total, cols))];
|
|
5379
|
+
const own = onScreen ? [style.grey(truncate(`attempt ${onScreen.attempt}: ${taskUsageText({ totals: onScreen.totals, contextFill: (0, import_core34.plannerContextFill)(onScreen), attempt: onScreen.attempt })}`, cols))] : tokenRow(blocks2, cols);
|
|
5380
|
+
return [...own, style.grey(truncate(`task total: ${total}`, cols))];
|
|
5319
5381
|
}
|
|
5320
5382
|
function chatScrollMax(state) {
|
|
5321
5383
|
return chatLayout(state, bodyRows(state), chatPaneWidth(state)).maxScroll;
|
|
@@ -5558,9 +5620,17 @@ function planScrollExtent(state) {
|
|
|
5558
5620
|
}
|
|
5559
5621
|
return planLayout(state, bodyRows(state), cols);
|
|
5560
5622
|
}
|
|
5623
|
+
function modelsByRunnerOf(models) {
|
|
5624
|
+
const out = {};
|
|
5625
|
+
for (const m of models) {
|
|
5626
|
+
for (const runner of m.runners ?? []) (out[runner] ??= []).push({ modelId: m.id, modelLabel: m.label, variants: [] });
|
|
5627
|
+
}
|
|
5628
|
+
return out;
|
|
5629
|
+
}
|
|
5561
5630
|
function taskLines(state, planRow, index, cols) {
|
|
5562
5631
|
const { task, parent } = planRow;
|
|
5563
|
-
const
|
|
5632
|
+
const runnerScope = state.runners.length > 0 ? { enabledRunners: state.runners.filter((r) => r.enabled).map((r) => r.id), allowlist: state.allowlist, models: modelsByRunnerOf(state.models) } : void 0;
|
|
5633
|
+
const row = (0, import_core34.taskRowView)(task, { parent, tasks: state.tasks, modes: modesForTask(state.modesByRunner, task), runnerScope });
|
|
5564
5634
|
const selected = state.focus === "plan" && index === state.selectedTask;
|
|
5565
5635
|
const expanded = state.expandedTaskId === task.id;
|
|
5566
5636
|
const rawIcon = row.status === "running" ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[row.status];
|
|
@@ -5582,8 +5652,7 @@ function taskLines(state, planRow, index, cols) {
|
|
|
5582
5652
|
const prefix = lineIndex === 0 ? head : continuation;
|
|
5583
5653
|
return prefix + (selected ? style.bold(line) : line);
|
|
5584
5654
|
});
|
|
5585
|
-
const
|
|
5586
|
-
const model = reported.length ? reported.join(", ") : task.assignedModel?.modelLabel ?? (task.type === "ai" ? "default model" : "");
|
|
5655
|
+
const model = task.assignedModel?.modelLabel ?? (task.type === "ai" ? "default model" : "");
|
|
5587
5656
|
const effort = task.type === "ai" ? `effort: ${task.assignedModel?.thinkingEffort ?? "default"}` : "";
|
|
5588
5657
|
const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${row.autonomous ? style.yellow(" \u26A1") : ""}` : "";
|
|
5589
5658
|
const runner = task.assignedRunner ?? "";
|
|
@@ -5599,36 +5668,39 @@ function taskLines(state, planRow, index, cols) {
|
|
|
5599
5668
|
if (row.conflict) {
|
|
5600
5669
|
const where = row.conflict.repo ? ` in ${row.conflict.repo}` : "";
|
|
5601
5670
|
const files = row.conflict.files ? ` (${row.conflict.files})` : "";
|
|
5602
|
-
lines.push(style.red
|
|
5671
|
+
lines.push(...statusLines(style.red, "\u26A0", `merge conflict${where}${files}: its work is kept on its own branch`, cols, bodyPad));
|
|
5603
5672
|
}
|
|
5604
5673
|
if (row.repairing) {
|
|
5605
5674
|
const files = row.repairing.files ? ` in ${row.repairing.files}` : "";
|
|
5606
5675
|
const attempt = row.repairing.attempt ? ` (attempt ${row.repairing.attempt.attempt}/${row.repairing.attempt.limit})` : "";
|
|
5607
|
-
lines.push(style.yellow
|
|
5676
|
+
lines.push(...statusLines(style.yellow, "\u21BB", `repairing conflict${files}${attempt}`, cols, bodyPad));
|
|
5608
5677
|
}
|
|
5609
5678
|
if (row.mergeGate) {
|
|
5610
|
-
lines.push(style.yellow
|
|
5679
|
+
lines.push(...statusLines(style.yellow, "\u23F8", `waits for Merge all: ${row.mergeGate.join(", ")} not merged into your branch yet`, cols, bodyPad));
|
|
5680
|
+
}
|
|
5681
|
+
if (row.assignmentFlag) {
|
|
5682
|
+
lines.push(...statusLines(style.yellow, "\u26A0", assignmentFlagText(row), cols, bodyPad));
|
|
5611
5683
|
}
|
|
5612
5684
|
if (row.waitsForApproval) {
|
|
5613
|
-
lines.push(style.yellow
|
|
5685
|
+
lines.push(...statusLines(style.yellow, "\u23F8", `${inRow(row.waitsForApproval)} \xB7 f approves it \xB7 /run approves all`, cols, bodyPad));
|
|
5614
5686
|
}
|
|
5615
5687
|
if (row.blockedBy) {
|
|
5616
|
-
lines.push(style.red
|
|
5688
|
+
lines.push(...statusLines(style.red, "\u2298", `${inRow((0, import_core34.blockedByLabel)(row.blockedBy))}: runs once ${row.blockedBy.length === 1 ? "it is retried and passes" : "they are retried and pass"}`, cols, bodyPad));
|
|
5617
5689
|
}
|
|
5618
5690
|
if (row.status === "skipped") {
|
|
5619
|
-
lines.push(style.grey
|
|
5691
|
+
lines.push(...statusLines(style.grey, "\xBB", `${inRow((0, import_core34.skippedLabel)(row.skipReason))}: nothing of it landed; m done lands its kept work`, cols, bodyPad));
|
|
5620
5692
|
}
|
|
5621
5693
|
const resumable = row.actions.includes("resume-now");
|
|
5622
5694
|
if (row.resumes) {
|
|
5623
|
-
lines.push(style.yellow
|
|
5695
|
+
lines.push(...statusLines(style.yellow, "\u23F8", `${inRow(row.resumes)}, once the usage limit resets: ${resumable ? "r resume now \xB7 " : ""}c don't resume`, cols, bodyPad));
|
|
5624
5696
|
} else if (resumable) {
|
|
5625
|
-
lines.push(style.yellow
|
|
5697
|
+
lines.push(...statusLines(style.yellow, "\u23F8", "waits for the usage limit to reset: r resume now", cols, bodyPad));
|
|
5626
5698
|
}
|
|
5627
5699
|
if (row.opsChangedFiles) {
|
|
5628
|
-
lines.push(style.yellow
|
|
5700
|
+
lines.push(...statusLines(style.yellow, "\u26A0", "an ops task changed tracked files: check them, then m done or /retry", cols, bodyPad));
|
|
5629
5701
|
}
|
|
5630
5702
|
if (row.repaired?.landed) {
|
|
5631
|
-
lines.push(style.grey
|
|
5703
|
+
lines.push(...statusLines(style.grey, "\u21BB", `landed after repairing conflict in ${row.repaired.files}`, cols, bodyPad));
|
|
5632
5704
|
}
|
|
5633
5705
|
let editorLine;
|
|
5634
5706
|
if (expanded) {
|
|
@@ -5670,6 +5742,11 @@ function taskLines(state, planRow, index, cols) {
|
|
|
5670
5742
|
}
|
|
5671
5743
|
return { lines, editorLine };
|
|
5672
5744
|
}
|
|
5745
|
+
function statusLines(paint, glyph, text, cols, pad2) {
|
|
5746
|
+
const lead = `${pad2}${glyph} `;
|
|
5747
|
+
const room = Math.max(1, cols - width(lead));
|
|
5748
|
+
return wrap(text, room).map((line, i) => paint(`${i === 0 ? lead : " ".repeat(width(lead))}${line}`));
|
|
5749
|
+
}
|
|
5673
5750
|
function taskText(label, text, cols, pad2 = " ") {
|
|
5674
5751
|
const room = Math.max(1, cols - width(pad2));
|
|
5675
5752
|
return [
|
|
@@ -6057,7 +6134,7 @@ function renderHandoff(state, overlay, rows, cols) {
|
|
|
6057
6134
|
const caret = active ? style.brand("\u276F") : " ";
|
|
6058
6135
|
return `${caret} ${active ? style.bold(action.label) : action.label}${style.grey(`: ${action.hint}`)}`;
|
|
6059
6136
|
});
|
|
6060
|
-
const perRepo = isRepoGroup(handoff) ? ["Per repository:", ...handoff.repos.map((repo, i) => ` ${repo.landed.length > 0 ? style.green("\u2713") : style.grey("\xB7")} ${repoResultLines(handoff)[i]}`), ""] : [];
|
|
6137
|
+
const perRepo = (0, import_core10.isRepoGroup)(handoff) ? ["Per repository:", ...handoff.repos.map((repo, i) => ` ${repo.landed.length > 0 ? style.green("\u2713") : style.grey("\xB7")} ${repoResultLines(handoff)[i]}`), ""] : [];
|
|
6061
6138
|
return frame(
|
|
6062
6139
|
"Run handoff",
|
|
6063
6140
|
[
|
|
@@ -7593,6 +7670,8 @@ function runCommand(state, { name, args }) {
|
|
|
7593
7670
|
return withIdlePlanner(state, (sessionId) => step(state, [{ type: "forkConversation", sessionId }]));
|
|
7594
7671
|
case "rewind":
|
|
7595
7672
|
return rewind(state, args[0]);
|
|
7673
|
+
case "resend":
|
|
7674
|
+
return args[0] === "cancel" ? withSession(state, (sessionId) => step(state, [{ type: "cancelPlannerResend", sessionId }])) : step(say(state, "system", "A planner turn that hits the planner's usage limit is sent again at the reset: /resend cancel keeps it for your next message instead."));
|
|
7596
7675
|
case "compact":
|
|
7597
7676
|
return withIdlePlanner(
|
|
7598
7677
|
state,
|
|
@@ -7866,7 +7945,7 @@ function runners(state, args) {
|
|
|
7866
7945
|
multi: true,
|
|
7867
7946
|
chosen: state.runners.filter((r) => r.enabled).map((r) => r.id)
|
|
7868
7947
|
})
|
|
7869
|
-
} });
|
|
7948
|
+
} }, [{ type: "loadRunners" }]);
|
|
7870
7949
|
}
|
|
7871
7950
|
function setMaxParallel(state, arg) {
|
|
7872
7951
|
if (!arg) return step(say(state, "system", `Up to ${state.maxParallel} AI task${state.maxParallel === 1 ? "" : "s"} run at once: /parallel <n> changes it.`));
|
|
@@ -8360,13 +8439,15 @@ function reduceAction(state, action) {
|
|
|
8360
8439
|
focus: "chat",
|
|
8361
8440
|
editor: { ...state.editor, text: action.text, cursor: action.text.length, historyIndex: state.editor.history.length, draft: "" }
|
|
8362
8441
|
});
|
|
8442
|
+
case "sharedSettingsChanged":
|
|
8443
|
+
return step(state, [{ type: "loadScope" }]);
|
|
8363
8444
|
case "runnersLoaded":
|
|
8364
|
-
return step(refreshStepper(refillPicker({
|
|
8445
|
+
return step(refreshStepper(reseedRunnerChoice(state, refillPicker({
|
|
8365
8446
|
...state,
|
|
8366
8447
|
runners: action.runners,
|
|
8367
8448
|
runnersKnown: true,
|
|
8368
8449
|
orchestratorModel: action.orchestratorModel ?? state.orchestratorModel
|
|
8369
|
-
}, ["set-task-runner"])));
|
|
8450
|
+
}, ["set-task-runner"]))));
|
|
8370
8451
|
case "failed": {
|
|
8371
8452
|
if (!action.turn) return step(say(taskLogAbandoned(state), "error", action.message));
|
|
8372
8453
|
if (state.forceSendPending) {
|
|
@@ -8413,6 +8494,7 @@ function showPlan(state, plan) {
|
|
|
8413
8494
|
const expandedTaskId = state.expandedTaskId && findTask(tasks, state.expandedTaskId) ? state.expandedTaskId : null;
|
|
8414
8495
|
const next = {
|
|
8415
8496
|
...folded,
|
|
8497
|
+
plannerResend: plannerResendOf(plan),
|
|
8416
8498
|
expandedTaskId,
|
|
8417
8499
|
taskEditor: expandedTaskId !== null ? state.taskEditor : null
|
|
8418
8500
|
};
|
|
@@ -8422,6 +8504,11 @@ function showPlan(state, plan) {
|
|
|
8422
8504
|
focus: tasks.length === 0 ? "chat" : state.focus
|
|
8423
8505
|
});
|
|
8424
8506
|
}
|
|
8507
|
+
function plannerResendOf(plan) {
|
|
8508
|
+
const saved = plan?.plannerResend;
|
|
8509
|
+
if (typeof saved?.at !== "string") return null;
|
|
8510
|
+
return { ...typeof saved.runner === "string" ? { runner: saved.runner } : {}, at: saved.at };
|
|
8511
|
+
}
|
|
8425
8512
|
function runStatus(state, tasks) {
|
|
8426
8513
|
if (tasks.some(import_core28.isTaskRunning)) return "executing";
|
|
8427
8514
|
if (tasks.some((t) => t.status === "awaiting_user") || state.runMerge?.paused) return "executing";
|
|
@@ -9361,6 +9448,12 @@ async function perform(effect, deps) {
|
|
|
9361
9448
|
// user gets to read — no separate notice to repeat it. The daemon also
|
|
9362
9449
|
// broadcast that summary; `redrawn` keeps a late socket copy of it from
|
|
9363
9450
|
// landing as a second, spoken turn.
|
|
9451
|
+
case "cancelPlannerResend": {
|
|
9452
|
+
const { cancelled } = await api.cancelPlannerResend(effect.sessionId);
|
|
9453
|
+
dispatch({ type: "notice", message: cancelled ? "Resend cancelled: what waits goes with your next message." : "Nothing waits on the planner's usage limit to be sent at a reset." });
|
|
9454
|
+
await refreshPlan(deps, effect.sessionId);
|
|
9455
|
+
return;
|
|
9456
|
+
}
|
|
9364
9457
|
case "compactConversation": {
|
|
9365
9458
|
const { plan } = await api.compactConversation(effect.sessionId);
|
|
9366
9459
|
const inbound = inboundFor(deps.api, deps.dispatch, effect.sessionId);
|
|
@@ -9423,6 +9516,12 @@ async function perform(effect, deps) {
|
|
|
9423
9516
|
} catch {
|
|
9424
9517
|
}
|
|
9425
9518
|
return;
|
|
9519
|
+
case "loadRunners":
|
|
9520
|
+
await loadRunners(deps);
|
|
9521
|
+
return;
|
|
9522
|
+
case "loadScope":
|
|
9523
|
+
await Promise.all([loadRunners(deps), loadSettings(deps)]);
|
|
9524
|
+
return;
|
|
9426
9525
|
case "refresh":
|
|
9427
9526
|
await Promise.all([loadRunners(deps), loadSettings(deps), loadModels(deps)]);
|
|
9428
9527
|
if (effect.announce) dispatch({ type: "notice", message: "Refreshed runners, settings and models." });
|
|
@@ -9986,10 +10085,12 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
9986
10085
|
if (terminal) app.dispatch({ type: "resize", ...terminal.size() });
|
|
9987
10086
|
}
|
|
9988
10087
|
});
|
|
10088
|
+
const watchingSettings = settings.watch(() => app.dispatch({ type: "sharedSettingsChanged" }));
|
|
9989
10089
|
const { rows, cols } = terminal.size();
|
|
9990
10090
|
app.dispatch({ type: "resize", rows, cols });
|
|
9991
10091
|
app.start();
|
|
9992
10092
|
function shutdown(code) {
|
|
10093
|
+
watchingSettings.dispose();
|
|
9993
10094
|
terminal?.close();
|
|
9994
10095
|
if (owned) stopDaemon(port).catch(() => {
|
|
9995
10096
|
});
|
|
@@ -10059,6 +10160,7 @@ var COMMANDS = {
|
|
|
10059
10160
|
fork: handleFork,
|
|
10060
10161
|
compact: handleCompact,
|
|
10061
10162
|
rewind: handleRewind,
|
|
10163
|
+
resend: handleResend,
|
|
10062
10164
|
handoff: handleHandoff,
|
|
10063
10165
|
tui: handleTui
|
|
10064
10166
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ordewell/cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
4
4
|
"description": "Ordewell CLI and TUI — plan, run and verify coding-agent tasks from the terminal.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Alessandro Costanzo Ciano",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"typecheck": "tsc --noEmit"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@ordewell/core": "^0.10.
|
|
55
|
-
"@ordewell/web": "^0.10.
|
|
54
|
+
"@ordewell/core": "^0.10.5",
|
|
55
|
+
"@ordewell/web": "^0.10.5",
|
|
56
56
|
"string-width": "^7.2.0",
|
|
57
57
|
"ws": "^8.18.0"
|
|
58
58
|
},
|