@ordewell/cli 0.10.3 → 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 +181 -77
- 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
|
}
|
|
@@ -4993,15 +5044,17 @@ function plannerItems(state) {
|
|
|
4993
5044
|
const runner = (0, import_core31.runnerForProvider)(id);
|
|
4994
5045
|
usability[runner] = installed.has(runner) ? { usable: true } : { usable: false, reason: "CLI not installed or not on PATH" };
|
|
4995
5046
|
}
|
|
4996
|
-
const
|
|
4997
|
-
|
|
5047
|
+
const offered2 = (0, import_core31.plannerBackendEntries)(usability, state.configuredProviders).filter((entry) => entry.usable);
|
|
5048
|
+
if (offered2.length === 0) {
|
|
5049
|
+
return [{ id: "", label: "No planner available", detail: "install a coding agent or add a key with /key", disabled: true }];
|
|
5050
|
+
}
|
|
5051
|
+
const items = offered2.map((entry) => ({
|
|
4998
5052
|
id: entry.id,
|
|
4999
5053
|
label: entry.label,
|
|
5000
5054
|
detail: entry.reason,
|
|
5001
|
-
selected: state.plannerProvider === entry.id
|
|
5002
|
-
disabled: !entry.usable
|
|
5055
|
+
selected: state.plannerProvider === entry.id
|
|
5003
5056
|
}));
|
|
5004
|
-
return
|
|
5057
|
+
return offered2.some((entry) => entry.kind === "vendor") ? items : [...items, { id: "", label: "No API providers configured", detail: "add a key with /key", disabled: true }];
|
|
5005
5058
|
}
|
|
5006
5059
|
|
|
5007
5060
|
// src/version.ts
|
|
@@ -5037,7 +5090,13 @@ var PLAN_KEYS = {
|
|
|
5037
5090
|
deps: { keys: "D", meaning: "pick the tasks it depends on", group: "Assign" },
|
|
5038
5091
|
skills: { keys: "K", meaning: "pick the skills it loads", group: "Assign" },
|
|
5039
5092
|
ops: { keys: "O", meaning: "switch between a change and an ops task", group: "Assign" },
|
|
5040
|
-
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
|
+
},
|
|
5041
5100
|
runPlan: { keys: "E", meaning: "run the whole plan", group: "Run", hint: "E run plan" },
|
|
5042
5101
|
stop: { keys: "S", meaning: "stop the run", group: "Run", hint: "S stop" },
|
|
5043
5102
|
cancel: {
|
|
@@ -5293,11 +5352,18 @@ function chatLayout(state, rows, cols) {
|
|
|
5293
5352
|
const bubbles = tv ? taskQueuedRows(state, cols) : queuedPromptRows(state, cols);
|
|
5294
5353
|
const content = [...transcript, ...bubbles.length > 0 ? ["", ...bubbles] : []];
|
|
5295
5354
|
const taskHeader = tv ? taskHeaderLines(state, tv, cols) : [];
|
|
5296
|
-
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)];
|
|
5297
5356
|
const footer = usage.slice(0, Math.max(1, rows - taskHeader.length - 1));
|
|
5298
5357
|
const space = Math.max(0, rows - taskHeader.length - footer.length);
|
|
5299
5358
|
return { lines: content, header: taskHeader, footer, anchor: content.length > space ? "bottom" : "top", maxScroll: Math.max(0, content.length - space) };
|
|
5300
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
|
+
}
|
|
5301
5367
|
var tokenRow = (blocks2, cols) => {
|
|
5302
5368
|
const line = tokenLine(blocks2, cols);
|
|
5303
5369
|
return line ? [line] : [];
|
|
@@ -5305,15 +5371,13 @@ var tokenRow = (blocks2, cols) => {
|
|
|
5305
5371
|
function taskUsageFooter(state, tv, blocks2, cols) {
|
|
5306
5372
|
const task = findTask(state.tasks, tv.taskId);
|
|
5307
5373
|
const reported = task?.attemptUsage ?? [];
|
|
5374
|
+
if (!task || reported.length === 0) return tokenRow(blocks2, cols);
|
|
5308
5375
|
const onScreen = tv.attempt > 0 ? reported.find((a) => a.attempt === tv.attempt) : reported.at(-1);
|
|
5309
|
-
|
|
5310
|
-
const
|
|
5311
|
-
if (
|
|
5312
|
-
const
|
|
5313
|
-
return [
|
|
5314
|
-
style.grey(truncate(taskUsageText(usage), cols)),
|
|
5315
|
-
...models.length > 1 ? models.map((m) => style.grey(truncate(`${sanitize(m.model ?? "unattributed")}: ${usageMeasures(m.totals, true).join(" \xB7 ")}`, cols))) : []
|
|
5316
|
-
];
|
|
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))];
|
|
5317
5381
|
}
|
|
5318
5382
|
function chatScrollMax(state) {
|
|
5319
5383
|
return chatLayout(state, bodyRows(state), chatPaneWidth(state)).maxScroll;
|
|
@@ -5556,9 +5620,17 @@ function planScrollExtent(state) {
|
|
|
5556
5620
|
}
|
|
5557
5621
|
return planLayout(state, bodyRows(state), cols);
|
|
5558
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
|
+
}
|
|
5559
5630
|
function taskLines(state, planRow, index, cols) {
|
|
5560
5631
|
const { task, parent } = planRow;
|
|
5561
|
-
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 });
|
|
5562
5634
|
const selected = state.focus === "plan" && index === state.selectedTask;
|
|
5563
5635
|
const expanded = state.expandedTaskId === task.id;
|
|
5564
5636
|
const rawIcon = row.status === "running" ? SPINNER[state.spinnerFrame % SPINNER.length] : STATUS_ICON[row.status];
|
|
@@ -5580,8 +5652,7 @@ function taskLines(state, planRow, index, cols) {
|
|
|
5580
5652
|
const prefix = lineIndex === 0 ? head : continuation;
|
|
5581
5653
|
return prefix + (selected ? style.bold(line) : line);
|
|
5582
5654
|
});
|
|
5583
|
-
const
|
|
5584
|
-
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" : "");
|
|
5585
5656
|
const effort = task.type === "ai" ? `effort: ${task.assignedModel?.thinkingEffort ?? "default"}` : "";
|
|
5586
5657
|
const mode = task.type === "ai" ? `mode: ${task.taskMode ?? "default"}${row.autonomous ? style.yellow(" \u26A1") : ""}` : "";
|
|
5587
5658
|
const runner = task.assignedRunner ?? "";
|
|
@@ -5597,36 +5668,39 @@ function taskLines(state, planRow, index, cols) {
|
|
|
5597
5668
|
if (row.conflict) {
|
|
5598
5669
|
const where = row.conflict.repo ? ` in ${row.conflict.repo}` : "";
|
|
5599
5670
|
const files = row.conflict.files ? ` (${row.conflict.files})` : "";
|
|
5600
|
-
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));
|
|
5601
5672
|
}
|
|
5602
5673
|
if (row.repairing) {
|
|
5603
5674
|
const files = row.repairing.files ? ` in ${row.repairing.files}` : "";
|
|
5604
5675
|
const attempt = row.repairing.attempt ? ` (attempt ${row.repairing.attempt.attempt}/${row.repairing.attempt.limit})` : "";
|
|
5605
|
-
lines.push(style.yellow
|
|
5676
|
+
lines.push(...statusLines(style.yellow, "\u21BB", `repairing conflict${files}${attempt}`, cols, bodyPad));
|
|
5606
5677
|
}
|
|
5607
5678
|
if (row.mergeGate) {
|
|
5608
|
-
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));
|
|
5609
5683
|
}
|
|
5610
5684
|
if (row.waitsForApproval) {
|
|
5611
|
-
lines.push(style.yellow
|
|
5685
|
+
lines.push(...statusLines(style.yellow, "\u23F8", `${inRow(row.waitsForApproval)} \xB7 f approves it \xB7 /run approves all`, cols, bodyPad));
|
|
5612
5686
|
}
|
|
5613
5687
|
if (row.blockedBy) {
|
|
5614
|
-
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));
|
|
5615
5689
|
}
|
|
5616
5690
|
if (row.status === "skipped") {
|
|
5617
|
-
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));
|
|
5618
5692
|
}
|
|
5619
5693
|
const resumable = row.actions.includes("resume-now");
|
|
5620
5694
|
if (row.resumes) {
|
|
5621
|
-
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));
|
|
5622
5696
|
} else if (resumable) {
|
|
5623
|
-
lines.push(style.yellow
|
|
5697
|
+
lines.push(...statusLines(style.yellow, "\u23F8", "waits for the usage limit to reset: r resume now", cols, bodyPad));
|
|
5624
5698
|
}
|
|
5625
5699
|
if (row.opsChangedFiles) {
|
|
5626
|
-
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));
|
|
5627
5701
|
}
|
|
5628
5702
|
if (row.repaired?.landed) {
|
|
5629
|
-
lines.push(style.grey
|
|
5703
|
+
lines.push(...statusLines(style.grey, "\u21BB", `landed after repairing conflict in ${row.repaired.files}`, cols, bodyPad));
|
|
5630
5704
|
}
|
|
5631
5705
|
let editorLine;
|
|
5632
5706
|
if (expanded) {
|
|
@@ -5668,6 +5742,11 @@ function taskLines(state, planRow, index, cols) {
|
|
|
5668
5742
|
}
|
|
5669
5743
|
return { lines, editorLine };
|
|
5670
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
|
+
}
|
|
5671
5750
|
function taskText(label, text, cols, pad2 = " ") {
|
|
5672
5751
|
const room = Math.max(1, cols - width(pad2));
|
|
5673
5752
|
return [
|
|
@@ -6055,7 +6134,7 @@ function renderHandoff(state, overlay, rows, cols) {
|
|
|
6055
6134
|
const caret = active ? style.brand("\u276F") : " ";
|
|
6056
6135
|
return `${caret} ${active ? style.bold(action.label) : action.label}${style.grey(`: ${action.hint}`)}`;
|
|
6057
6136
|
});
|
|
6058
|
-
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]}`), ""] : [];
|
|
6059
6138
|
return frame(
|
|
6060
6139
|
"Run handoff",
|
|
6061
6140
|
[
|
|
@@ -7591,6 +7670,8 @@ function runCommand(state, { name, args }) {
|
|
|
7591
7670
|
return withIdlePlanner(state, (sessionId) => step(state, [{ type: "forkConversation", sessionId }]));
|
|
7592
7671
|
case "rewind":
|
|
7593
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."));
|
|
7594
7675
|
case "compact":
|
|
7595
7676
|
return withIdlePlanner(
|
|
7596
7677
|
state,
|
|
@@ -7864,7 +7945,7 @@ function runners(state, args) {
|
|
|
7864
7945
|
multi: true,
|
|
7865
7946
|
chosen: state.runners.filter((r) => r.enabled).map((r) => r.id)
|
|
7866
7947
|
})
|
|
7867
|
-
} });
|
|
7948
|
+
} }, [{ type: "loadRunners" }]);
|
|
7868
7949
|
}
|
|
7869
7950
|
function setMaxParallel(state, arg) {
|
|
7870
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.`));
|
|
@@ -8358,13 +8439,15 @@ function reduceAction(state, action) {
|
|
|
8358
8439
|
focus: "chat",
|
|
8359
8440
|
editor: { ...state.editor, text: action.text, cursor: action.text.length, historyIndex: state.editor.history.length, draft: "" }
|
|
8360
8441
|
});
|
|
8442
|
+
case "sharedSettingsChanged":
|
|
8443
|
+
return step(state, [{ type: "loadScope" }]);
|
|
8361
8444
|
case "runnersLoaded":
|
|
8362
|
-
return step(refreshStepper(refillPicker({
|
|
8445
|
+
return step(refreshStepper(reseedRunnerChoice(state, refillPicker({
|
|
8363
8446
|
...state,
|
|
8364
8447
|
runners: action.runners,
|
|
8365
8448
|
runnersKnown: true,
|
|
8366
8449
|
orchestratorModel: action.orchestratorModel ?? state.orchestratorModel
|
|
8367
|
-
}, ["set-task-runner"])));
|
|
8450
|
+
}, ["set-task-runner"]))));
|
|
8368
8451
|
case "failed": {
|
|
8369
8452
|
if (!action.turn) return step(say(taskLogAbandoned(state), "error", action.message));
|
|
8370
8453
|
if (state.forceSendPending) {
|
|
@@ -8411,6 +8494,7 @@ function showPlan(state, plan) {
|
|
|
8411
8494
|
const expandedTaskId = state.expandedTaskId && findTask(tasks, state.expandedTaskId) ? state.expandedTaskId : null;
|
|
8412
8495
|
const next = {
|
|
8413
8496
|
...folded,
|
|
8497
|
+
plannerResend: plannerResendOf(plan),
|
|
8414
8498
|
expandedTaskId,
|
|
8415
8499
|
taskEditor: expandedTaskId !== null ? state.taskEditor : null
|
|
8416
8500
|
};
|
|
@@ -8420,6 +8504,11 @@ function showPlan(state, plan) {
|
|
|
8420
8504
|
focus: tasks.length === 0 ? "chat" : state.focus
|
|
8421
8505
|
});
|
|
8422
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
|
+
}
|
|
8423
8512
|
function runStatus(state, tasks) {
|
|
8424
8513
|
if (tasks.some(import_core28.isTaskRunning)) return "executing";
|
|
8425
8514
|
if (tasks.some((t) => t.status === "awaiting_user") || state.runMerge?.paused) return "executing";
|
|
@@ -9359,6 +9448,12 @@ async function perform(effect, deps) {
|
|
|
9359
9448
|
// user gets to read — no separate notice to repeat it. The daemon also
|
|
9360
9449
|
// broadcast that summary; `redrawn` keeps a late socket copy of it from
|
|
9361
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
|
+
}
|
|
9362
9457
|
case "compactConversation": {
|
|
9363
9458
|
const { plan } = await api.compactConversation(effect.sessionId);
|
|
9364
9459
|
const inbound = inboundFor(deps.api, deps.dispatch, effect.sessionId);
|
|
@@ -9421,6 +9516,12 @@ async function perform(effect, deps) {
|
|
|
9421
9516
|
} catch {
|
|
9422
9517
|
}
|
|
9423
9518
|
return;
|
|
9519
|
+
case "loadRunners":
|
|
9520
|
+
await loadRunners(deps);
|
|
9521
|
+
return;
|
|
9522
|
+
case "loadScope":
|
|
9523
|
+
await Promise.all([loadRunners(deps), loadSettings(deps)]);
|
|
9524
|
+
return;
|
|
9424
9525
|
case "refresh":
|
|
9425
9526
|
await Promise.all([loadRunners(deps), loadSettings(deps), loadModels(deps)]);
|
|
9426
9527
|
if (effect.announce) dispatch({ type: "notice", message: "Refreshed runners, settings and models." });
|
|
@@ -9984,10 +10085,12 @@ cd into a directory that exists, or pass --workspace <path> naming one that does
|
|
|
9984
10085
|
if (terminal) app.dispatch({ type: "resize", ...terminal.size() });
|
|
9985
10086
|
}
|
|
9986
10087
|
});
|
|
10088
|
+
const watchingSettings = settings.watch(() => app.dispatch({ type: "sharedSettingsChanged" }));
|
|
9987
10089
|
const { rows, cols } = terminal.size();
|
|
9988
10090
|
app.dispatch({ type: "resize", rows, cols });
|
|
9989
10091
|
app.start();
|
|
9990
10092
|
function shutdown(code) {
|
|
10093
|
+
watchingSettings.dispose();
|
|
9991
10094
|
terminal?.close();
|
|
9992
10095
|
if (owned) stopDaemon(port).catch(() => {
|
|
9993
10096
|
});
|
|
@@ -10057,6 +10160,7 @@ var COMMANDS = {
|
|
|
10057
10160
|
fork: handleFork,
|
|
10058
10161
|
compact: handleCompact,
|
|
10059
10162
|
rewind: handleRewind,
|
|
10163
|
+
resend: handleResend,
|
|
10060
10164
|
handoff: handleHandoff,
|
|
10061
10165
|
tui: handleTui
|
|
10062
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
|
},
|