@aragon-agent/cli 0.6.35 → 0.6.37
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/CHANGELOG.md +21 -0
- package/README.md +3173 -3071
- package/dist/agent/agent-mode.d.ts +20 -4
- package/dist/agent/agent-mode.js +20 -4
- package/dist/agent/controller.d.ts +38 -3
- package/dist/agent/controller.js +64 -4
- package/dist/agent/headless.js +31 -0
- package/dist/agent/reducer.d.ts +13 -2
- package/dist/agent/reducer.js +1 -0
- package/dist/cli.js +12 -0
- package/dist/commands/agents-command.d.ts +15 -0
- package/dist/commands/agents-command.js +27 -0
- package/dist/commands/builtins.js +131 -4
- package/dist/commands/perf.d.ts +11 -0
- package/dist/commands/perf.js +2 -1
- package/dist/commands/registry.d.ts +14 -0
- package/dist/config/app-paths.d.ts +15 -0
- package/dist/config/app-paths.js +33 -2
- package/dist/config/env.d.ts +4 -1
- package/dist/config/env.js +28 -2
- package/dist/config/load.d.ts +10 -0
- package/dist/config/load.js +8 -1
- package/dist/config/schema.d.ts +68 -0
- package/dist/config/schema.js +39 -0
- package/dist/config/store.js +12 -1
- package/dist/config/ui-state.d.ts +18 -0
- package/dist/config/ui-state.js +9 -0
- package/dist/exec/events.d.ts +25 -0
- package/dist/exec/events.js +4 -0
- package/dist/exec/index.js +16 -1
- package/dist/exec/runner.js +10 -0
- package/dist/proc/limits.d.ts +16 -0
- package/dist/proc/limits.js +16 -0
- package/dist/proc/supervisor.d.ts +20 -1
- package/dist/proc/supervisor.js +42 -14
- package/dist/prompts/command.d.ts +34 -0
- package/dist/prompts/command.js +237 -0
- package/dist/prompts/copy.d.ts +89 -0
- package/dist/prompts/copy.js +131 -0
- package/dist/prompts/fill.d.ts +51 -0
- package/dist/prompts/fill.js +46 -0
- package/dist/prompts/limits.d.ts +53 -0
- package/dist/prompts/limits.js +53 -0
- package/dist/prompts/normalize.d.ts +53 -0
- package/dist/prompts/normalize.js +190 -0
- package/dist/prompts/resolve.d.ts +31 -0
- package/dist/prompts/resolve.js +60 -0
- package/dist/prompts/search.d.ts +58 -0
- package/dist/prompts/search.js +146 -0
- package/dist/prompts/store.d.ts +86 -0
- package/dist/prompts/store.js +453 -0
- package/dist/prompts/types.d.ts +204 -0
- package/dist/prompts/types.js +13 -0
- package/dist/session/checkpoint.d.ts +8 -1
- package/dist/session/checkpoint.js +6 -0
- package/dist/session/persist.d.ts +19 -1
- package/dist/session/persist.js +13 -1
- package/dist/session/startup-session.d.ts +7 -2
- package/dist/session/startup-session.js +5 -2
- package/dist/session/store.js +3 -0
- package/dist/session/validate-session.js +64 -1
- package/dist/team/limits.d.ts +25 -0
- package/dist/team/limits.js +26 -0
- package/dist/team/runtime.js +27 -2
- package/dist/team/settled.d.ts +57 -0
- package/dist/team/settled.js +87 -0
- package/dist/team/subagent.js +48 -2
- package/dist/team/timeline.d.ts +74 -0
- package/dist/team/timeline.js +101 -0
- package/dist/team/types.d.ts +24 -1
- package/dist/todo/acceptance.d.ts +120 -0
- package/dist/todo/acceptance.js +399 -0
- package/dist/todo/follow-through.d.ts +48 -23
- package/dist/todo/follow-through.js +110 -44
- package/dist/todo/limits.d.ts +62 -1
- package/dist/todo/limits.js +62 -1
- package/dist/todo/panel-rows.d.ts +29 -39
- package/dist/todo/panel-rows.js +37 -83
- package/dist/todo/plan-sections.d.ts +60 -0
- package/dist/todo/plan-sections.js +108 -0
- package/dist/todo/prompt.d.ts +17 -12
- package/dist/todo/prompt.js +59 -40
- package/dist/todo/row-window.d.ts +51 -0
- package/dist/todo/row-window.js +107 -0
- package/dist/todo/store.d.ts +139 -31
- package/dist/todo/store.js +271 -73
- package/dist/todo/todo-tool.d.ts +33 -8
- package/dist/todo/todo-tool.js +186 -30
- package/dist/todo/types.d.ts +58 -0
- package/dist/ui/App.js +310 -46
- package/dist/ui/Composer.d.ts +6 -2
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/Header.d.ts +9 -1
- package/dist/ui/Header.js +2 -2
- package/dist/ui/PromptInput.d.ts +21 -1
- package/dist/ui/PromptInput.js +21 -1
- package/dist/ui/ScrollbackShell.d.ts +19 -19
- package/dist/ui/ScrollbackShell.js +8 -1
- package/dist/ui/StatusBar.d.ts +5 -0
- package/dist/ui/StatusBar.js +1 -0
- package/dist/ui/TeamPanel.d.ts +26 -44
- package/dist/ui/TeamPanel.js +46 -135
- package/dist/ui/TodoPanel.d.ts +17 -14
- package/dist/ui/TodoPanel.js +146 -38
- package/dist/ui/Transcript.js +1 -1
- package/dist/ui/composer-input.d.ts +10 -0
- package/dist/ui/composer-input.js +57 -24
- package/dist/ui/entries/TodoCard.d.ts +26 -11
- package/dist/ui/entries/TodoCard.js +56 -15
- package/dist/ui/glyphs.d.ts +19 -0
- package/dist/ui/glyphs.js +4 -0
- package/dist/ui/layout/agents-board.d.ts +102 -0
- package/dist/ui/layout/agents-board.js +389 -0
- package/dist/ui/layout/cells.d.ts +61 -0
- package/dist/ui/layout/cells.js +156 -0
- package/dist/ui/layout/header-hints.d.ts +13 -3
- package/dist/ui/layout/header-hints.js +48 -14
- package/dist/ui/layout/status-detail-layout.js +9 -0
- package/dist/ui/layout/status-layout.d.ts +13 -0
- package/dist/ui/layout/team-row.d.ts +139 -0
- package/dist/ui/layout/team-row.js +246 -0
- package/dist/ui/layout/todo-layout.d.ts +38 -0
- package/dist/ui/layout/todo-layout.js +75 -0
- package/dist/ui/layout/virtual-window.js +23 -3
- package/dist/ui/overlays/AgentsBoardOverlay.d.ts +57 -0
- package/dist/ui/overlays/AgentsBoardOverlay.js +183 -0
- package/dist/ui/overlays/HelpOverlay.js +17 -2
- package/dist/ui/overlays/PromptEditorPage.d.ts +48 -0
- package/dist/ui/overlays/PromptEditorPage.js +227 -0
- package/dist/ui/overlays/PromptLibraryOverlay.d.ts +37 -0
- package/dist/ui/overlays/PromptLibraryOverlay.js +191 -0
- package/dist/ui/overlays/PromptPicker.d.ts +36 -0
- package/dist/ui/overlays/PromptPicker.js +129 -0
- package/dist/ui/overlays/PromptTextArea.d.ts +86 -0
- package/dist/ui/overlays/PromptTextArea.js +141 -0
- package/dist/ui/overlays/prompt-library-state.d.ts +162 -0
- package/dist/ui/overlays/prompt-library-state.js +335 -0
- package/dist/ui/overlays/prompt-picker-state.d.ts +71 -0
- package/dist/ui/overlays/prompt-picker-state.js +98 -0
- package/dist/ui/scrollback/band.js +14 -1
- package/dist/ui/team-status.d.ts +25 -0
- package/dist/ui/team-status.js +31 -3
- package/dist/ui/use-startup-notices.d.ts +35 -1
- package/dist/ui/use-startup-notices.js +29 -2
- package/dist/ui/use-wheel-routing.js +9 -1
- package/package.json +2 -2
|
@@ -41,17 +41,33 @@ export declare function nextMode(current: AgentMode): AgentMode;
|
|
|
41
41
|
* PowerShell, a foreign process resetting the mode), so a rung that depends on
|
|
42
42
|
* NOTHING has to exist underneath it.
|
|
43
43
|
*
|
|
44
|
-
* `ctrl+
|
|
44
|
+
* `ctrl+n` IS MEASURED, not guessed. On the lossy path it arrives as `0x0e`,
|
|
45
45
|
* because Windows generates a real control character for `Ctrl+<letter>` and
|
|
46
46
|
* libuv's character branch forwards it untouched; `Alt+M` produces no bytes at
|
|
47
47
|
* all and `Ctrl+Tab` collapses exactly like `Shift+Tab` (analysis section 3.6).
|
|
48
48
|
* Any idle `Ctrl+<letter>` in the `0x01-0x1A` range would work; this one is free
|
|
49
|
-
* throughout the package - `keymap.ts` claims a/e/w/u/k, `App` claims c/l/t
|
|
49
|
+
* throughout the package - `keymap.ts` claims a/e/w/u/k, `App` claims c/g/k/l/o/r/t,
|
|
50
50
|
* and `PromptInput` returns early on every `key.ctrl` - so binding it takes no
|
|
51
51
|
* behaviour away from anyone. `Ctrl+B` survives the same path but is tmux's
|
|
52
52
|
* default prefix, and this binding is unconditional and cross-platform, so it
|
|
53
53
|
* would be silently dead for a whole class of macOS and Linux users.
|
|
54
54
|
*
|
|
55
|
+
* THIS KEY MOVED FROM `ctrl+p`, WHICH NOW OPENS THE PROMPT LIBRARY
|
|
56
|
+
* (quick-prompt-library D-1). The requirement names `ctrl+p` as the first entry
|
|
57
|
+
* to a feature users will build muscle memory for, and a key that is sometimes
|
|
58
|
+
* the prompt library and sometimes the mode switch is a key nobody can learn.
|
|
59
|
+
* `ctrl+n` was the only idle C0 byte left that survives the same lossy path, and
|
|
60
|
+
* it carries no conflicting behaviour: readline's `next-history` is `Up`/`Down`
|
|
61
|
+
* in this product.
|
|
62
|
+
*
|
|
63
|
+
* `^N` IS NOT AN ACCEPTABLE SPELLING OF IT, ANYWHERE. `^N` is already this
|
|
64
|
+
* product's word for TRANSCRIPT LINE OFFSET - the header's scroll chip reads
|
|
65
|
+
* `^<n>` and the help overlay has a `^N` row about the distance from the bottom -
|
|
66
|
+
* while every other `^X` in the same row is a key. Writing the mode fallback as
|
|
67
|
+
* `^N` would put two meanings of `^` on one line, so the header, the help
|
|
68
|
+
* overlay, `doctor`, the README and the one-shot migration notice all spell out
|
|
69
|
+
* `Ctrl+N`.
|
|
70
|
+
*
|
|
55
71
|
* BOUND UNCONDITIONALLY, on every platform. A binding that only exists where it
|
|
56
72
|
* is needed cannot be documented in one place, cannot be taught by muscle
|
|
57
73
|
* memory, and is missing on precisely the machine whose capability probe was
|
|
@@ -59,13 +75,13 @@ export declare function nextMode(current: AgentMode): AgentMode;
|
|
|
59
75
|
*
|
|
60
76
|
* THESE ARE THE LABELS, NOT THE BINDING. Editing a value here changes only what
|
|
61
77
|
* the user is TOLD to press - the hint row, the startup notice, `doctor`. The
|
|
62
|
-
* key that is actually listened for is `input === '
|
|
78
|
+
* key that is actually listened for is `input === 'n'` in `App`'s `useInput`,
|
|
63
79
|
* and the help overlay and README spell both keys out in their own title case.
|
|
64
80
|
* Change one without the others and the product advertises a key it ignores.
|
|
65
81
|
*/
|
|
66
82
|
export declare const MODE_TOGGLE_KEYS: {
|
|
67
83
|
readonly primary: "shift+tab";
|
|
68
|
-
readonly fallback: "ctrl+
|
|
84
|
+
readonly fallback: "ctrl+n";
|
|
69
85
|
};
|
|
70
86
|
/** The two tools that only exist because a human is attached. */
|
|
71
87
|
export declare const PLAN_TOOL_NAMES: readonly ["ask_user", "submit_plan"];
|
package/dist/agent/agent-mode.js
CHANGED
|
@@ -43,17 +43,33 @@ export function nextMode(current) {
|
|
|
43
43
|
* PowerShell, a foreign process resetting the mode), so a rung that depends on
|
|
44
44
|
* NOTHING has to exist underneath it.
|
|
45
45
|
*
|
|
46
|
-
* `ctrl+
|
|
46
|
+
* `ctrl+n` IS MEASURED, not guessed. On the lossy path it arrives as `0x0e`,
|
|
47
47
|
* because Windows generates a real control character for `Ctrl+<letter>` and
|
|
48
48
|
* libuv's character branch forwards it untouched; `Alt+M` produces no bytes at
|
|
49
49
|
* all and `Ctrl+Tab` collapses exactly like `Shift+Tab` (analysis section 3.6).
|
|
50
50
|
* Any idle `Ctrl+<letter>` in the `0x01-0x1A` range would work; this one is free
|
|
51
|
-
* throughout the package - `keymap.ts` claims a/e/w/u/k, `App` claims c/l/t
|
|
51
|
+
* throughout the package - `keymap.ts` claims a/e/w/u/k, `App` claims c/g/k/l/o/r/t,
|
|
52
52
|
* and `PromptInput` returns early on every `key.ctrl` - so binding it takes no
|
|
53
53
|
* behaviour away from anyone. `Ctrl+B` survives the same path but is tmux's
|
|
54
54
|
* default prefix, and this binding is unconditional and cross-platform, so it
|
|
55
55
|
* would be silently dead for a whole class of macOS and Linux users.
|
|
56
56
|
*
|
|
57
|
+
* THIS KEY MOVED FROM `ctrl+p`, WHICH NOW OPENS THE PROMPT LIBRARY
|
|
58
|
+
* (quick-prompt-library D-1). The requirement names `ctrl+p` as the first entry
|
|
59
|
+
* to a feature users will build muscle memory for, and a key that is sometimes
|
|
60
|
+
* the prompt library and sometimes the mode switch is a key nobody can learn.
|
|
61
|
+
* `ctrl+n` was the only idle C0 byte left that survives the same lossy path, and
|
|
62
|
+
* it carries no conflicting behaviour: readline's `next-history` is `Up`/`Down`
|
|
63
|
+
* in this product.
|
|
64
|
+
*
|
|
65
|
+
* `^N` IS NOT AN ACCEPTABLE SPELLING OF IT, ANYWHERE. `^N` is already this
|
|
66
|
+
* product's word for TRANSCRIPT LINE OFFSET - the header's scroll chip reads
|
|
67
|
+
* `^<n>` and the help overlay has a `^N` row about the distance from the bottom -
|
|
68
|
+
* while every other `^X` in the same row is a key. Writing the mode fallback as
|
|
69
|
+
* `^N` would put two meanings of `^` on one line, so the header, the help
|
|
70
|
+
* overlay, `doctor`, the README and the one-shot migration notice all spell out
|
|
71
|
+
* `Ctrl+N`.
|
|
72
|
+
*
|
|
57
73
|
* BOUND UNCONDITIONALLY, on every platform. A binding that only exists where it
|
|
58
74
|
* is needed cannot be documented in one place, cannot be taught by muscle
|
|
59
75
|
* memory, and is missing on precisely the machine whose capability probe was
|
|
@@ -61,11 +77,11 @@ export function nextMode(current) {
|
|
|
61
77
|
*
|
|
62
78
|
* THESE ARE THE LABELS, NOT THE BINDING. Editing a value here changes only what
|
|
63
79
|
* the user is TOLD to press - the hint row, the startup notice, `doctor`. The
|
|
64
|
-
* key that is actually listened for is `input === '
|
|
80
|
+
* key that is actually listened for is `input === 'n'` in `App`'s `useInput`,
|
|
65
81
|
* and the help overlay and README spell both keys out in their own title case.
|
|
66
82
|
* Change one without the others and the product advertises a key it ignores.
|
|
67
83
|
*/
|
|
68
|
-
export const MODE_TOGGLE_KEYS = { primary: 'shift+tab', fallback: 'ctrl+
|
|
84
|
+
export const MODE_TOGGLE_KEYS = { primary: 'shift+tab', fallback: 'ctrl+n' };
|
|
69
85
|
/** The two tools that only exist because a human is attached. */
|
|
70
86
|
export const PLAN_TOOL_NAMES = ['ask_user', 'submit_plan'];
|
|
71
87
|
/**
|
|
@@ -30,7 +30,7 @@ import { type FastStatus } from '../fast/wiring.js';
|
|
|
30
30
|
import type { FastEventListener, FastSnapshot } from '../fast/types.js';
|
|
31
31
|
import { ContextMeter, type ContextUsageListener } from '../compaction/meter.js';
|
|
32
32
|
import type { CompactionEventListener, CompactionSnapshot, ContextUsageSnapshot } from '../compaction/types.js';
|
|
33
|
-
import type { TodoEventListener, TodoSnapshot } from '../todo/types.js';
|
|
33
|
+
import type { AcceptanceItem, TodoEventListener, TodoSnapshot } from '../todo/types.js';
|
|
34
34
|
import { type HumanInputGate } from '../tools/human-input.js';
|
|
35
35
|
import { SkillService, type ApprovalGate } from '../skills/service.js';
|
|
36
36
|
import { type ConversationResetOptions, type ConversationResetResult } from './conversation-reset.js';
|
|
@@ -587,6 +587,37 @@ export declare class AgentController {
|
|
|
587
587
|
*/
|
|
588
588
|
setTodoConfig(patch: Partial<TodoConfig>): TodoConfig;
|
|
589
589
|
getTodoSnapshot(): TodoSnapshot | null;
|
|
590
|
+
/**
|
|
591
|
+
* Whether the acceptance table is live for this session: the config key AND the
|
|
592
|
+
* session switch. Two flags, exactly as `isTodoEnabled` needs `isTodoRegistered`
|
|
593
|
+
* first - a capability that is on while `todo_write` refuses is not a
|
|
594
|
+
* capability.
|
|
595
|
+
*/
|
|
596
|
+
isTodoAcceptanceEnabled(): boolean;
|
|
597
|
+
/**
|
|
598
|
+
* Flip the acceptance capability and RE-RENDER THE PROMPT, byte for byte the
|
|
599
|
+
* contract `setTodoEnabled` records above: the paragraph is part of
|
|
600
|
+
* `<todo_planning>`, and the tool reads the same live value through its
|
|
601
|
+
* `hasAcceptance` closure (D-11).
|
|
602
|
+
*/
|
|
603
|
+
setTodoAcceptanceEnabled(enabled: boolean): void;
|
|
604
|
+
/**
|
|
605
|
+
* The acceptance table as DATA, independent of whether it is being drawn.
|
|
606
|
+
*
|
|
607
|
+
* The persistence surfaces must use this and not `getTodoSnapshot()?.acceptance`:
|
|
608
|
+
* the projection is `null` whenever the capability is off, and a checkpoint or a
|
|
609
|
+
* `/save` that silently dropped the user's verdicts because a display switch was
|
|
610
|
+
* off would be a data loss dressed as a setting.
|
|
611
|
+
*/
|
|
612
|
+
getAcceptanceItems(): AcceptanceItem[];
|
|
613
|
+
/**
|
|
614
|
+
* `/todo acceptance reset`: every verdict back to `pending`, evidence dropped.
|
|
615
|
+
*
|
|
616
|
+
* The count comes BACK from the store rather than being recomputed by the
|
|
617
|
+
* command, so the notice and the state cannot disagree about how many criteria
|
|
618
|
+
* moved.
|
|
619
|
+
*/
|
|
620
|
+
reopenAcceptance(): number;
|
|
590
621
|
/**
|
|
591
622
|
* Subscribe to the CLI-local todo event stream.
|
|
592
623
|
*
|
|
@@ -594,8 +625,12 @@ export declare class AgentController {
|
|
|
594
625
|
* every caller can subscribe unconditionally.
|
|
595
626
|
*/
|
|
596
627
|
subscribeTodos(listener: TodoEventListener): () => void;
|
|
597
|
-
/**
|
|
598
|
-
|
|
628
|
+
/**
|
|
629
|
+
* `/resume`. An absent or empty todo list CLEARS both tables — see
|
|
630
|
+
* `TodoStore.restore` (P0-2). `acceptance` is optional so every existing caller
|
|
631
|
+
* keeps compiling, and an omitted table reads as "this file has none".
|
|
632
|
+
*/
|
|
633
|
+
restoreTodos(items: unknown, acceptance?: unknown): void;
|
|
599
634
|
/**
|
|
600
635
|
* `/todo clear` and `/clear screen`. A user override of a projection (I-2's
|
|
601
636
|
* standing exception). `/todo` refuses mid-run before it gets here;
|
package/dist/agent/controller.js
CHANGED
|
@@ -346,7 +346,9 @@ export class AgentController {
|
|
|
346
346
|
this.todoRegistered = config.todo.enabled;
|
|
347
347
|
this.todoEnabled = config.todo.enabled;
|
|
348
348
|
this.todoPanelCapable = deps.todoPanelCapable === true;
|
|
349
|
-
this.todos = this.todoRegistered
|
|
349
|
+
this.todos = this.todoRegistered
|
|
350
|
+
? new TodoStore(Date.now, () => this.isTodoAcceptanceEnabled())
|
|
351
|
+
: null;
|
|
350
352
|
// --- Fast model tier ---------------------------------------------------
|
|
351
353
|
//
|
|
352
354
|
// RESOLVED BEFORE THE AGENT, because `fastRegistered` decides whether the
|
|
@@ -558,6 +560,12 @@ export class AgentController {
|
|
|
558
560
|
store: this.todos,
|
|
559
561
|
// Read LIVE: `/todo off` flips the flag, it cannot unregister.
|
|
560
562
|
isEnabled: () => this.todoEnabled,
|
|
563
|
+
// LIVE for the same reason (D-11): `/todo acceptance off` must take
|
|
564
|
+
// effect in a session already running, because the tool array
|
|
565
|
+
// itself cannot change. THE SAME PREDICATE the store was given, on
|
|
566
|
+
// purpose: two switches for one capability that could disagree is
|
|
567
|
+
// the shape of a session that cannot exist (IF-7).
|
|
568
|
+
hasAcceptance: () => this.isTodoAcceptanceEnabled(),
|
|
561
569
|
}),
|
|
562
570
|
]
|
|
563
571
|
: [],
|
|
@@ -912,6 +920,9 @@ export class AgentController {
|
|
|
912
920
|
todoBlock: this.todoRegistered && this.todoEnabled
|
|
913
921
|
? buildTodoBlock({
|
|
914
922
|
panelVisible: this.todoPanelCapable && config.todo.panel,
|
|
923
|
+
// LIVE, never snapshotted (D-11): the paragraph is part of the
|
|
924
|
+
// block, and `/todo acceptance off` rebuilds the prompt.
|
|
925
|
+
acceptance: config.todo.acceptance,
|
|
915
926
|
})
|
|
916
927
|
: '',
|
|
917
928
|
// The SAME two flags one more time, plus the live resolution — all three
|
|
@@ -1594,6 +1605,51 @@ export class AgentController {
|
|
|
1594
1605
|
getTodoSnapshot() {
|
|
1595
1606
|
return this.todos?.snapshot() ?? null;
|
|
1596
1607
|
}
|
|
1608
|
+
/**
|
|
1609
|
+
* Whether the acceptance table is live for this session: the config key AND the
|
|
1610
|
+
* session switch. Two flags, exactly as `isTodoEnabled` needs `isTodoRegistered`
|
|
1611
|
+
* first - a capability that is on while `todo_write` refuses is not a
|
|
1612
|
+
* capability.
|
|
1613
|
+
*/
|
|
1614
|
+
isTodoAcceptanceEnabled() {
|
|
1615
|
+
return this.config.todo.acceptance && this.todoEnabled;
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Flip the acceptance capability and RE-RENDER THE PROMPT, byte for byte the
|
|
1619
|
+
* contract `setTodoEnabled` records above: the paragraph is part of
|
|
1620
|
+
* `<todo_planning>`, and the tool reads the same live value through its
|
|
1621
|
+
* `hasAcceptance` closure (D-11).
|
|
1622
|
+
*/
|
|
1623
|
+
setTodoAcceptanceEnabled(enabled) {
|
|
1624
|
+
this.setTodoConfig({ acceptance: enabled });
|
|
1625
|
+
// THE RAIL READS THE PROJECTION, NOT THE FLAG, so the flip has to re-project or
|
|
1626
|
+
// `/todo acceptance off` would leave the second section on screen until the
|
|
1627
|
+
// model's next write (P0-2's complaint, one path further in). The verdicts
|
|
1628
|
+
// themselves are kept: `getAcceptanceItems()` still returns them for `/save`,
|
|
1629
|
+
// the checkpoint and `exec`.
|
|
1630
|
+
this.todos?.refreshAcceptanceProjection();
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* The acceptance table as DATA, independent of whether it is being drawn.
|
|
1634
|
+
*
|
|
1635
|
+
* The persistence surfaces must use this and not `getTodoSnapshot()?.acceptance`:
|
|
1636
|
+
* the projection is `null` whenever the capability is off, and a checkpoint or a
|
|
1637
|
+
* `/save` that silently dropped the user's verdicts because a display switch was
|
|
1638
|
+
* off would be a data loss dressed as a setting.
|
|
1639
|
+
*/
|
|
1640
|
+
getAcceptanceItems() {
|
|
1641
|
+
return this.todos?.acceptanceItems() ?? [];
|
|
1642
|
+
}
|
|
1643
|
+
/**
|
|
1644
|
+
* `/todo acceptance reset`: every verdict back to `pending`, evidence dropped.
|
|
1645
|
+
*
|
|
1646
|
+
* The count comes BACK from the store rather than being recomputed by the
|
|
1647
|
+
* command, so the notice and the state cannot disagree about how many criteria
|
|
1648
|
+
* moved.
|
|
1649
|
+
*/
|
|
1650
|
+
reopenAcceptance() {
|
|
1651
|
+
return this.todos?.reopenAll() ?? 0;
|
|
1652
|
+
}
|
|
1597
1653
|
/**
|
|
1598
1654
|
* Subscribe to the CLI-local todo event stream.
|
|
1599
1655
|
*
|
|
@@ -1605,9 +1661,13 @@ export class AgentController {
|
|
|
1605
1661
|
return () => { };
|
|
1606
1662
|
return this.todos.subscribe(listener);
|
|
1607
1663
|
}
|
|
1608
|
-
/**
|
|
1609
|
-
|
|
1610
|
-
|
|
1664
|
+
/**
|
|
1665
|
+
* `/resume`. An absent or empty todo list CLEARS both tables — see
|
|
1666
|
+
* `TodoStore.restore` (P0-2). `acceptance` is optional so every existing caller
|
|
1667
|
+
* keeps compiling, and an omitted table reads as "this file has none".
|
|
1668
|
+
*/
|
|
1669
|
+
restoreTodos(items, acceptance = undefined) {
|
|
1670
|
+
this.todos?.restore(items, acceptance);
|
|
1611
1671
|
}
|
|
1612
1672
|
/**
|
|
1613
1673
|
* `/todo clear` and `/clear screen`. A user override of a projection (I-2's
|
package/dist/agent/headless.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { formatCost, formatTokens, formatDuration, computeCost } from './usage.js';
|
|
8
8
|
import { formatStreamError } from './reducer.js';
|
|
9
|
+
import { derivePlanPhase } from '../todo/acceptance.js';
|
|
9
10
|
import { advanceBudget, decideFollowThrough, emptyBudget, } from '../todo/follow-through.js';
|
|
10
11
|
/** Run a single prompt headlessly and resolve with the process exit code. */
|
|
11
12
|
export async function runHeadless(controller, prompt, options = {}) {
|
|
@@ -139,6 +140,11 @@ export async function runHeadless(controller, prompt, options = {}) {
|
|
|
139
140
|
// stream that is already `--quiet`-able in full.
|
|
140
141
|
let announcedTotal = 0;
|
|
141
142
|
let lastActive = -1;
|
|
143
|
+
// THE ACCEPTANCE SECTION'S DEDUPE KEY (todo-acceptance-criteria §6.13.1 / P2-9).
|
|
144
|
+
// `phase` is DERIVED, never cached (I-3), so the key carries a freshly computed
|
|
145
|
+
// phase plus the two counters that can only move when a verdict lands: a
|
|
146
|
+
// constant-size string is cheaper than a field that could drift from the truth.
|
|
147
|
+
let lastAcceptance = '';
|
|
142
148
|
const unsubscribeTodos = controller.subscribeTodos?.((event) => {
|
|
143
149
|
if (quiet || event.type !== 'updated')
|
|
144
150
|
return;
|
|
@@ -147,6 +153,20 @@ export async function runHeadless(controller, prompt, options = {}) {
|
|
|
147
153
|
announcedTotal = total;
|
|
148
154
|
err.write(`\n[todo] ${total} ${total === 1 ? 'step' : 'steps'} planned\n`);
|
|
149
155
|
lastActive = -1;
|
|
156
|
+
// A NEW PLAN GETS ITS OWN FIRST `[accept]` LINE, for the reason `lastActive`
|
|
157
|
+
// is reset one line up: the key below is (phase, checked, failed) and two
|
|
158
|
+
// different plans can share all three. Without this reset the second plan's
|
|
159
|
+
// 0/3 in a `-p` run that already printed 0/3 would go unreported.
|
|
160
|
+
lastAcceptance = '';
|
|
161
|
+
}
|
|
162
|
+
const acceptance = event.snapshot.acceptance;
|
|
163
|
+
if (acceptance && acceptance.total > 0) {
|
|
164
|
+
const key = `${derivePlanPhase(items, acceptance.items)}|` +
|
|
165
|
+
`${acceptance.checkedCount}|${acceptance.failedCount}`;
|
|
166
|
+
if (key !== lastAcceptance) {
|
|
167
|
+
lastAcceptance = key;
|
|
168
|
+
err.write(`[accept] ${acceptanceLine(acceptance)}\n`);
|
|
169
|
+
}
|
|
150
170
|
}
|
|
151
171
|
if (activeIndex < 0) {
|
|
152
172
|
if (lastActive !== -2) {
|
|
@@ -220,6 +240,17 @@ export async function runHeadless(controller, prompt, options = {}) {
|
|
|
220
240
|
`(${formatTokens(r.tailRelief.charsRemoved)} chars)\n`);
|
|
221
241
|
}
|
|
222
242
|
});
|
|
243
|
+
/**
|
|
244
|
+
* `[accept] <n>/<total> checked`, plus the failure count when there is one.
|
|
245
|
+
*
|
|
246
|
+
* ONE LINE PER TRANSITION, and the transitions are the ones a `-p` reader can
|
|
247
|
+
* act on: a verdict landed, or a criterion failed. `evidence` is NEVER printed
|
|
248
|
+
* (I-8): it belongs to the human-facing surfaces, and a machine log is not one.
|
|
249
|
+
*/
|
|
250
|
+
function acceptanceLine(acceptance) {
|
|
251
|
+
const base = `${acceptance.checkedCount}/${acceptance.total} checked`;
|
|
252
|
+
return acceptance.failedCount > 0 ? `${base}, ${acceptance.failedCount} failed` : base;
|
|
253
|
+
}
|
|
223
254
|
// --- The bounded continuation loop (todo-plan-followthrough §3.6). --------
|
|
224
255
|
//
|
|
225
256
|
// THE THREE `unsubscribe` CALLS STAY OUTSIDE IT (rule 4). The loop wraps the
|
package/dist/agent/reducer.d.ts
CHANGED
|
@@ -16,13 +16,13 @@ import type { AgentMode } from './agent-mode.js';
|
|
|
16
16
|
import type { DispatchOutcome, SubagentRun, SubagentSpec, TeamSnapshot } from '../team/types.js';
|
|
17
17
|
import type { FastReview, FastSnapshot } from '../fast/types.js';
|
|
18
18
|
import type { CompactionMode, CompactionRecord, CompactionSnapshot, CompactionUiTrigger, ContextUsageSnapshot } from '../compaction/types.js';
|
|
19
|
-
import type { TodoItem, TodoSnapshot } from '../todo/types.js';
|
|
19
|
+
import type { AcceptanceSnapshot, TodoItem, TodoSnapshot } from '../todo/types.js';
|
|
20
20
|
import type { JobKind, ServiceSnapshot, ServiceStatus } from '../proc/types.js';
|
|
21
21
|
import type { RetryPhase } from './retry-view.js';
|
|
22
22
|
import type { FilePatch } from '../tools/patch.js';
|
|
23
23
|
export type NoticeLevel = 'info' | 'warn' | 'error';
|
|
24
24
|
export type ToastLevel = NoticeLevel | 'success';
|
|
25
|
-
export type Overlay = null | 'settings' | 'model' | 'help' | 'queue' | 'confirm' | 'question' | 'plan' | 'kit';
|
|
25
|
+
export type Overlay = null | 'settings' | 'model' | 'help' | 'queue' | 'confirm' | 'question' | 'plan' | 'kit' | 'prompts' | 'prompt-pick' | 'agents';
|
|
26
26
|
export type ToolStatus = 'pending' | 'running' | 'done' | 'error';
|
|
27
27
|
/** Ephemeral, auto-dismissed acknowledgement (spec §3.9). */
|
|
28
28
|
export interface Toast {
|
|
@@ -147,6 +147,17 @@ export type Entry = {
|
|
|
147
147
|
items: TodoItem[];
|
|
148
148
|
doneCount: number;
|
|
149
149
|
total: number;
|
|
150
|
+
/**
|
|
151
|
+
* The acceptance projection, copied from `TodoSnapshot.acceptance` on every
|
|
152
|
+
* `todoUpdate` (todo-acceptance-criteria §6.10).
|
|
153
|
+
*
|
|
154
|
+
* OPTIONAL, NOT `AcceptanceSnapshot | null` REQUIRED, and that asymmetry with
|
|
155
|
+
* the snapshot is deliberate (P1-4): `TodoSnapshot` has exactly one writer, so
|
|
156
|
+
* it can fail closed; an `Entry` does not - its sources include `entries` read
|
|
157
|
+
* back from a session file written before this feature existed, where the key
|
|
158
|
+
* is simply absent.
|
|
159
|
+
*/
|
|
160
|
+
acceptance?: AcceptanceSnapshot | null;
|
|
150
161
|
/** The turn that owns this card is still running, so it may be rewritten. */
|
|
151
162
|
live: boolean;
|
|
152
163
|
/**
|
package/dist/agent/reducer.js
CHANGED
|
@@ -1020,6 +1020,7 @@ export function viewReducer(state, action) {
|
|
|
1020
1020
|
items: action.snapshot.items,
|
|
1021
1021
|
doneCount: action.snapshot.doneCount,
|
|
1022
1022
|
total: action.snapshot.total,
|
|
1023
|
+
acceptance: action.snapshot.acceptance,
|
|
1023
1024
|
live: true,
|
|
1024
1025
|
});
|
|
1025
1026
|
// Rewrite the turn's card when there is one; otherwise append and record
|
package/dist/cli.js
CHANGED
|
@@ -170,6 +170,10 @@ function toFlags(opts) {
|
|
|
170
170
|
// C-9). A value flag is no less inert than a boolean one when this line is
|
|
171
171
|
// missing.
|
|
172
172
|
todoFollow: opts.todoFollow,
|
|
173
|
+
// Site three of three once more, for `--todo-acceptance`: a flag declared in
|
|
174
|
+
// commander and on `CliFlags` but missing HERE is inert, with no compile error
|
|
175
|
+
// and no runtime error (todo-acceptance-criteria §6.8.2).
|
|
176
|
+
todoAcceptance: opts.todoAcceptance,
|
|
173
177
|
// Site three of three for `--retry` / `--retry-max` — five features, same
|
|
174
178
|
// trap (`mouse` P1-2, `team` P1-4, `todo` C-9, render L4, now retry). A flag
|
|
175
179
|
// needs the `.option()` declaration below, a field on `CliFlags`, AND a line
|
|
@@ -1319,6 +1323,9 @@ function createSessionWriter(input) {
|
|
|
1319
1323
|
readView: input.readView,
|
|
1320
1324
|
readHistory: () => controller.getSessionSnapshot(),
|
|
1321
1325
|
readTodos: () => controller.getTodoSnapshot()?.items ?? [],
|
|
1326
|
+
// The verdicts travel with the plan; read from the STORE, not from the render
|
|
1327
|
+
// projection, so a checkpoint taken while the capability is off still saves them.
|
|
1328
|
+
readAcceptance: () => controller.getAcceptanceItems(),
|
|
1322
1329
|
// READ AT WRITE TIME: `/model` rebuilds the config, and a checkpoint that
|
|
1323
1330
|
// recorded the model from launch would name the wrong one after a switch.
|
|
1324
1331
|
readModel: () => {
|
|
@@ -2068,6 +2075,11 @@ function buildProgram() {
|
|
|
2068
2075
|
// passed, so `!== undefined` in `resolveTodoConfig` already distinguishes it
|
|
2069
2076
|
// from silence. An unrecognized mode is clamped to `notify`, not rejected.
|
|
2070
2077
|
.option('--todo-follow <mode>', 'What to do when a run ends with unfinished steps: notify (default) | auto | off')
|
|
2078
|
+
// A PAIR, and for the same reason `--todo-panel` needs one: `acceptance` is
|
|
2079
|
+
// PERSISTED, so a lone `--no-todo-acceptance` would make commander default
|
|
2080
|
+
// `opts.todoAcceptance` to `true` and overwrite a stored `false`.
|
|
2081
|
+
.option('--todo-acceptance', 'Plan acceptance criteria with every todo list (the default)')
|
|
2082
|
+
.option('--no-todo-acceptance', 'Plan steps only: no acceptance criteria, no verify phase')
|
|
2071
2083
|
// BOTH FORMS, positive first, for the reason recorded five times above:
|
|
2072
2084
|
// `retry.enabled` is PERSISTED, so a lone `--no-retry` would make commander
|
|
2073
2085
|
// default `opts.retry` to `true` and silently overwrite a stored `false`.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/agents` — open the subagent board (tui-subagent-panel §6.4).
|
|
3
|
+
*
|
|
4
|
+
* A SECOND DOOR, not a fallback. `Ctrl+B` is the fast path, but a key is undiscoverable
|
|
5
|
+
* unless the user reads `/help`, and `/agents` is what the palette advertises. Both
|
|
6
|
+
* entries share ONE guard (`state.team === null && lastTeamEntry(...) === null`) so the
|
|
7
|
+
* key and the command can never disagree about whether there is a board to show: an
|
|
8
|
+
* empty board would read as a broken feature, and the honest answer is a notice that
|
|
9
|
+
* says no dispatch has happened yet.
|
|
10
|
+
*
|
|
11
|
+
* The body lives in its own module for the reason `kit-command.ts` and
|
|
12
|
+
* `queue-command.ts` do: `builtins.ts` is already well past a thousand lines.
|
|
13
|
+
*/
|
|
14
|
+
import type { CommandRegistry } from './registry.js';
|
|
15
|
+
export declare function registerAgentsCommand(registry: CommandRegistry): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `/agents` — open the subagent board (tui-subagent-panel §6.4).
|
|
3
|
+
*
|
|
4
|
+
* A SECOND DOOR, not a fallback. `Ctrl+B` is the fast path, but a key is undiscoverable
|
|
5
|
+
* unless the user reads `/help`, and `/agents` is what the palette advertises. Both
|
|
6
|
+
* entries share ONE guard (`state.team === null && lastTeamEntry(...) === null`) so the
|
|
7
|
+
* key and the command can never disagree about whether there is a board to show: an
|
|
8
|
+
* empty board would read as a broken feature, and the honest answer is a notice that
|
|
9
|
+
* says no dispatch has happened yet.
|
|
10
|
+
*
|
|
11
|
+
* The body lives in its own module for the reason `kit-command.ts` and
|
|
12
|
+
* `queue-command.ts` do: `builtins.ts` is already well past a thousand lines.
|
|
13
|
+
*/
|
|
14
|
+
import { lastTeamEntry } from '../ui/team-status.js';
|
|
15
|
+
export function registerAgentsCommand(registry) {
|
|
16
|
+
registry.register({
|
|
17
|
+
name: 'agents',
|
|
18
|
+
description: 'Open the subagent board: every child, its facts and its timeline',
|
|
19
|
+
run: (ctx) => {
|
|
20
|
+
if (ctx.state.team === null && lastTeamEntry(ctx.state.entries) === null) {
|
|
21
|
+
ctx.notify('info', 'No subagent dispatch yet.');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
ctx.setOverlay('agents');
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -14,6 +14,7 @@ import { computeCost, formatCost, formatDuration, formatTokens } from '../agent/
|
|
|
14
14
|
import { mergePendingEntries } from '../agent/queued-messages.js';
|
|
15
15
|
import { TODO_FOLLOW_LIMITS } from '../todo/limits.js';
|
|
16
16
|
import { buildContinuationMessage } from '../todo/follow-through.js';
|
|
17
|
+
import { planIsResolved } from '../todo/acceptance.js';
|
|
17
18
|
import { MODE_LABEL, nextMode } from '../agent/agent-mode.js';
|
|
18
19
|
import { loadSession, resolveSessionPath, saveSession } from '../session/persist.js';
|
|
19
20
|
import { applyRestoredSession } from '../session/startup-session.js';
|
|
@@ -23,7 +24,9 @@ import { getLogger } from '../logging/logger.js';
|
|
|
23
24
|
import { resolveFastTier } from '../fast/resolve.js';
|
|
24
25
|
import { subagentModelSummary } from '../ui/overlays/model-profile-state.js';
|
|
25
26
|
import { registerQueueCommand } from './queue-command.js';
|
|
27
|
+
import { registerPromptCommands } from '../prompts/command.js';
|
|
26
28
|
import { registerKitCommand } from './kit-command.js';
|
|
29
|
+
import { registerAgentsCommand } from './agents-command.js';
|
|
27
30
|
import { perfCommand } from './perf.js';
|
|
28
31
|
import { runCompactCommand } from '../compaction/command.js';
|
|
29
32
|
import { runClearCommand } from './clear-command.js';
|
|
@@ -158,6 +161,102 @@ function runTeamModelCommand(ctx, subject) {
|
|
|
158
161
|
ctx.persistConfig({ team: teamPatch });
|
|
159
162
|
ctx.toast('success', `Subagent model: ${entry.name}${disambiguation} (${entry.provider}:${entry.model}).`);
|
|
160
163
|
}
|
|
164
|
+
/** Rows `/todo status` prints before it says how many it did not. */
|
|
165
|
+
const ACCEPT_STATUS_ROWS = 8;
|
|
166
|
+
/**
|
|
167
|
+
* `/todo acceptance on|off|reset`, and the bare form equal to `/todo status`'s
|
|
168
|
+
* acceptance section (todo-acceptance-criteria §6.14).
|
|
169
|
+
*
|
|
170
|
+
* EVERY MUTATING BRANCH IS "TWO CALLS PLUS PERSIST", the rule `/todo panel` and
|
|
171
|
+
* `/todo follow` already follow: `setTodoConfig` (or `setTodoAcceptanceEnabled`) is
|
|
172
|
+
* what the session sees, and `persistConfig` is what survives it. Only one of the two
|
|
173
|
+
* reports success while changing nothing.
|
|
174
|
+
*/
|
|
175
|
+
function runTodoAcceptance(ctx, value) {
|
|
176
|
+
const controller = ctx.controller;
|
|
177
|
+
if (!value || value === 'status') {
|
|
178
|
+
const lines = acceptanceStatusLines(controller.getTodoSnapshot()?.acceptance ?? null, controller.getTodoConfig().acceptance);
|
|
179
|
+
if (lines.length === 0) {
|
|
180
|
+
ctx.notify('info', 'No acceptance criteria. Todo planning: ' +
|
|
181
|
+
(controller.isTodoEnabled() ? 'on' : 'off') + '.');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
for (const line of lines)
|
|
185
|
+
ctx.notify('info', line);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (value === 'on' || value === 'off') {
|
|
189
|
+
const acceptance = value === 'on';
|
|
190
|
+
controller.setTodoAcceptanceEnabled(acceptance);
|
|
191
|
+
ctx.persistConfig({ todo: { acceptance } });
|
|
192
|
+
ctx.toast('success', `Acceptance criteria ${acceptance ? 'on' : 'off'}.`);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (value === 'reset') {
|
|
196
|
+
if (controller.isRunning()) {
|
|
197
|
+
ctx.notify('warn', 'Cannot reset the acceptance criteria while a run is in progress.');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
// THE COUNT COMES FROM THE STORE (a named writer, §6.2.3), never recomputed here,
|
|
201
|
+
// and `0` means the table was already fully unchecked - in which case a notice
|
|
202
|
+
// would announce that nothing happened.
|
|
203
|
+
const reopened = controller.reopenAcceptance();
|
|
204
|
+
if (reopened === 0) {
|
|
205
|
+
ctx.notify('info', 'No checked criteria to reset.');
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
ctx.toast('info', `${reopened} ${reopened === 1 ? 'criterion is' : 'criteria are'} ` +
|
|
209
|
+
'unchecked again.');
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
ctx.notify('warn', 'Usage: /todo acceptance [on|off|reset]');
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* The acceptance section of `/todo status`, as at most two notification bodies.
|
|
216
|
+
*
|
|
217
|
+
* RETURNS NOTHING WHEN THERE IS NOTHING TO SAY, which is what keeps the disabled
|
|
218
|
+
* path and the no-criteria path byte-identical to the pre-feature command (I-5).
|
|
219
|
+
* The list is bounded at eight rows by "unchecked and failed first, then the rest",
|
|
220
|
+
* with `... and N more` closing it: the full table lives in the transcript card, and
|
|
221
|
+
* a command that floods the transcript is not a status command.
|
|
222
|
+
*/
|
|
223
|
+
function acceptanceStatusLines(acceptance, enabled) {
|
|
224
|
+
if (!enabled || !acceptance || acceptance.total === 0)
|
|
225
|
+
return [];
|
|
226
|
+
const header = `Acceptance ${acceptance.checkedCount}/${acceptance.total} checked` +
|
|
227
|
+
(acceptance.failedCount > 0 ? ` (${acceptance.failedCount} failed)` : '');
|
|
228
|
+
const rows = acceptance.items
|
|
229
|
+
.map((item, index) => ({ item, index }))
|
|
230
|
+
.sort((left, right) => rank(left.item.status) - rank(right.item.status));
|
|
231
|
+
const shown = rows.slice(0, ACCEPT_STATUS_ROWS);
|
|
232
|
+
const lines = shown.map(({ item, index }) => ` ${String(index + 1).padStart(2)}. ${markerWord(item.status)} ${item.content}` +
|
|
233
|
+
(item.status === 'failed' && item.evidence.length > 0 ? `\n ${item.evidence}` : ''));
|
|
234
|
+
if (rows.length > shown.length)
|
|
235
|
+
lines.push(` ... and ${rows.length - shown.length} more`);
|
|
236
|
+
return [header, ...lines];
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Failures first, then everything still open, then the settled verdicts: `/todo status`
|
|
240
|
+
* is read to find out what is WRONG, and the eight-row budget means the order decides
|
|
241
|
+
* what survives the cut.
|
|
242
|
+
*/
|
|
243
|
+
function rank(status) {
|
|
244
|
+
if (status === 'failed')
|
|
245
|
+
return 0;
|
|
246
|
+
if (status === 'in_progress' || status === 'pending')
|
|
247
|
+
return 1;
|
|
248
|
+
return 2;
|
|
249
|
+
}
|
|
250
|
+
/** ASCII markers: this module is inside the glyph scanner's scope. */
|
|
251
|
+
function markerWord(status) {
|
|
252
|
+
if (status === 'passed')
|
|
253
|
+
return '[x]';
|
|
254
|
+
if (status === 'failed')
|
|
255
|
+
return '[!]';
|
|
256
|
+
if (status === 'in_progress')
|
|
257
|
+
return ' > ';
|
|
258
|
+
return '[ ]';
|
|
259
|
+
}
|
|
161
260
|
function formatFastStatus(controller) {
|
|
162
261
|
const status = controller.getFastStatus();
|
|
163
262
|
const cfg = controller.getFastConfig();
|
|
@@ -688,7 +787,8 @@ const COMMANDS = [
|
|
|
688
787
|
},
|
|
689
788
|
{
|
|
690
789
|
name: 'todo',
|
|
691
|
-
description: 'Todo planning: status | on | off | panel on|off | follow <mode> |
|
|
790
|
+
description: 'Todo planning: status | on | off | panel on|off | follow <mode> | ' +
|
|
791
|
+
'acceptance [on|off|reset] | clear | continue',
|
|
692
792
|
/**
|
|
693
793
|
* `/todo on` CANNOT turn planning on in a session that started with
|
|
694
794
|
* `--no-todo`, for the reason `/team on` above records at length: the tool
|
|
@@ -739,6 +839,16 @@ const COMMANDS = [
|
|
|
739
839
|
: '';
|
|
740
840
|
ctx.notify('info', `Todos: ${snapshot.doneCount}/${snapshot.total} done.${active} Panel: ${panel}. ` +
|
|
741
841
|
`Follow-through: ${follow}.`);
|
|
842
|
+
// THE ACCEPTANCE SECTION, appended and NOTHING ELSE (todo-acceptance-criteria
|
|
843
|
+
// §6.14): with no criteria the two calls above are byte-identical to the
|
|
844
|
+
// version without this feature.
|
|
845
|
+
for (const line of acceptanceStatusLines(snapshot.acceptance, todoCfg.acceptance)) {
|
|
846
|
+
ctx.notify('info', line);
|
|
847
|
+
}
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
if (verb === 'acceptance') {
|
|
851
|
+
runTodoAcceptance(ctx, value);
|
|
742
852
|
return;
|
|
743
853
|
}
|
|
744
854
|
if (verb === 'on' || verb === 'off') {
|
|
@@ -820,7 +930,10 @@ const COMMANDS = [
|
|
|
820
930
|
return;
|
|
821
931
|
}
|
|
822
932
|
const snapshot = controller.getTodoSnapshot();
|
|
823
|
-
|
|
933
|
+
// `planIsResolved`, NOT `doneCount >= total` (§6.14): in the verification
|
|
934
|
+
// phase every step is done and the command has to keep working, or the user's
|
|
935
|
+
// only manual route into the last stage is refused with "Nothing left".
|
|
936
|
+
if (!snapshot || planIsResolved(snapshot.items, snapshot.acceptance?.items ?? null)) {
|
|
824
937
|
ctx.notify('info', 'Nothing left to continue.');
|
|
825
938
|
return;
|
|
826
939
|
}
|
|
@@ -839,7 +952,8 @@ const COMMANDS = [
|
|
|
839
952
|
return;
|
|
840
953
|
}
|
|
841
954
|
ctx.notify('warn', `Unknown argument "${verb}" - use ` +
|
|
842
|
-
'/todo [status|on|off|panel on|off|follow <mode>|
|
|
955
|
+
'/todo [status|on|off|panel on|off|follow <mode>|acceptance [on|off|reset]|' +
|
|
956
|
+
'clear|continue].');
|
|
843
957
|
},
|
|
844
958
|
},
|
|
845
959
|
{
|
|
@@ -1017,6 +1131,11 @@ const COMMANDS = [
|
|
|
1017
1131
|
// `/resume` restores `messages`, so the belief that justifies the
|
|
1018
1132
|
// panel comes back — and the panel has to come back with it (§3.13).
|
|
1019
1133
|
todos: ctx.controller.getTodoSnapshot()?.items ?? [],
|
|
1134
|
+
// The verdicts travel with the plan, for the same reason: a resume that
|
|
1135
|
+
// restored the steps and dropped the conclusions would show a plan as
|
|
1136
|
+
// "not started" that the user had already watched being verified. Read
|
|
1137
|
+
// from the STORE, so a save taken while the capability is off keeps them.
|
|
1138
|
+
acceptance: ctx.controller.getAcceptanceItems(),
|
|
1020
1139
|
});
|
|
1021
1140
|
ctx.notify('info', `Saved session to ${path}`);
|
|
1022
1141
|
}
|
|
@@ -1375,6 +1494,14 @@ export function registerBuiltinCommands(registry) {
|
|
|
1375
1494
|
// The command BODY lives in its own module (builtins.ts is already 1.5k lines);
|
|
1376
1495
|
// this line is the whole registration (agent-model-kits §4.9).
|
|
1377
1496
|
registerKitCommand(registry);
|
|
1497
|
+
// Same shape, one feature later (quick-prompt-library §4.3 / P1-3): the body is
|
|
1498
|
+
// `prompts/command.ts` and its capabilities arrive on `CommandContext.prompts`.
|
|
1499
|
+
// REGISTERED UNCONDITIONALLY, even with `prompts.enabled=false` - that is the only
|
|
1500
|
+
// way the command can report that the feature is off (D-19 / AC-30).
|
|
1501
|
+
registerPromptCommands(registry);
|
|
1502
|
+
// The read-only subagent board (tui-subagent-panel §6.4). Same shape again: the
|
|
1503
|
+
// body is `commands/agents-command.ts` and the registration is this one line.
|
|
1504
|
+
registerAgentsCommand(registry);
|
|
1378
1505
|
}
|
|
1379
1506
|
/**
|
|
1380
1507
|
* Every built-in command name INCLUDING aliases (`quit`), for documentation and
|
|
@@ -1384,7 +1511,7 @@ export function registerBuiltinCommands(registry) {
|
|
|
1384
1511
|
* `registry.get(name)` instead, because that is the only check that stays
|
|
1385
1512
|
* correct when commands are registered dynamically (§7.1 / P1-2).
|
|
1386
1513
|
*/
|
|
1387
|
-
export const BUILTIN_COMMAND_NAMES = ['queue', ...COMMANDS.flatMap((c) => [
|
|
1514
|
+
export const BUILTIN_COMMAND_NAMES = ['queue', 'prompts', 'agents', ...COMMANDS.flatMap((c) => [
|
|
1388
1515
|
c.name,
|
|
1389
1516
|
...(c.aliases ?? []),
|
|
1390
1517
|
])].sort();
|