@aragon-agent/cli 0.6.34 → 0.6.36
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 +173 -7
- package/dist/agent/agent-mode.d.ts +20 -4
- package/dist/agent/agent-mode.js +20 -4
- package/dist/agent/controller.d.ts +67 -3
- package/dist/agent/controller.js +106 -5
- package/dist/agent/headless.js +31 -0
- package/dist/agent/kit-apply.d.ts +56 -0
- package/dist/agent/kit-apply.js +209 -0
- package/dist/agent/model-profile-settings.d.ts +28 -0
- package/dist/agent/model-profile-settings.js +142 -29
- 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 +135 -4
- package/dist/commands/kit-command.d.ts +20 -0
- package/dist/commands/kit-command.js +131 -0
- 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/kit-resolution.d.ts +93 -0
- package/dist/config/kit-resolution.js +226 -0
- package/dist/config/load.d.ts +10 -0
- package/dist/config/load.js +8 -1
- package/dist/config/model-kits.d.ts +224 -0
- package/dist/config/model-kits.js +302 -0
- package/dist/config/model-profile-store.d.ts +25 -0
- package/dist/config/model-profiles.d.ts +41 -3
- package/dist/config/model-profiles.js +46 -5
- package/dist/config/schema.d.ts +84 -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/input/keymap.js +18 -4
- 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 +414 -54
- package/dist/ui/Composer.d.ts +6 -2
- package/dist/ui/Composer.js +2 -2
- package/dist/ui/Header.d.ts +17 -1
- package/dist/ui/Header.js +8 -2
- package/dist/ui/PromptInput.d.ts +21 -1
- package/dist/ui/PromptInput.js +22 -2
- package/dist/ui/ScrollbackShell.d.ts +22 -7
- package/dist/ui/ScrollbackShell.js +8 -1
- package/dist/ui/StatusBar.d.ts +18 -0
- package/dist/ui/StatusBar.js +11 -3
- package/dist/ui/TeamPanel.d.ts +35 -41
- package/dist/ui/TeamPanel.js +53 -163
- package/dist/ui/TodoPanel.d.ts +17 -14
- package/dist/ui/TodoPanel.js +146 -38
- package/dist/ui/Transcript.js +23 -3
- package/dist/ui/composer-input.d.ts +10 -0
- package/dist/ui/composer-input.js +57 -24
- package/dist/ui/entries/TeamCard.d.ts +18 -2
- package/dist/ui/entries/TeamCard.js +47 -13
- 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 +23 -3
- package/dist/ui/layout/header-hints.js +73 -7
- package/dist/ui/layout/kit-chip.d.ts +50 -0
- package/dist/ui/layout/kit-chip.js +81 -0
- 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 +32 -3
- package/dist/ui/overlays/AgentsBoardOverlay.d.ts +57 -0
- package/dist/ui/overlays/AgentsBoardOverlay.js +183 -0
- package/dist/ui/overlays/HelpOverlay.js +24 -2
- package/dist/ui/overlays/KitEditor.d.ts +23 -0
- package/dist/ui/overlays/KitEditor.js +101 -0
- package/dist/ui/overlays/KitManager.d.ts +23 -0
- package/dist/ui/overlays/KitManager.js +104 -0
- package/dist/ui/overlays/KitPicker.d.ts +49 -0
- package/dist/ui/overlays/KitPicker.js +58 -0
- package/dist/ui/overlays/ModelProfilePicker.js +16 -6
- 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/SettingsScreen.js +139 -16
- package/dist/ui/overlays/kit-picker-state.d.ts +87 -0
- package/dist/ui/overlays/kit-picker-state.js +136 -0
- package/dist/ui/overlays/kit-settings-state.d.ts +129 -0
- package/dist/ui/overlays/kit-settings-state.js +267 -0
- package/dist/ui/overlays/model-profile-state.d.ts +26 -3
- package/dist/ui/overlays/model-profile-state.js +59 -6
- 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/LiveTail.d.ts +2 -2
- package/dist/ui/scrollback/LiveTail.js +7 -2
- package/dist/ui/scrollback/band.js +14 -1
- package/dist/ui/scrollback/live-preview.js +10 -3
- package/dist/ui/scrollback/tail-window.d.ts +12 -0
- package/dist/ui/scrollback/tail-window.js +17 -2
- package/dist/ui/scrollback/use-scrollback-view.js +7 -0
- package/dist/ui/team-status.d.ts +166 -0
- package/dist/ui/team-status.js +252 -0
- package/dist/ui/transcript-text.js +13 -4
- 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 +10 -2
- package/package.json +2 -2
package/dist/ui/App.js
CHANGED
|
@@ -16,13 +16,13 @@ import { computeCost } from '../agent/usage.js';
|
|
|
16
16
|
import { parseMaxTokensInput, DEFAULT_TRANSCRIPT_RETAIN, DEFAULT_TRANSCRIPT_WINDOW, } from '../config/schema.js';
|
|
17
17
|
import { updatePersistedConfig } from '../config/store.js';
|
|
18
18
|
import { appendPrompt, loadPromptHistory } from '../config/prompt-history.js';
|
|
19
|
-
import { bumpSubmitCount, setMouseNoticeVersion, setScrollModeNoticeVersion, setVtInputNoticeVersion, } from '../config/ui-state.js';
|
|
19
|
+
import { bumpSubmitCount, setMouseNoticeVersion, setPromptKeyNoticeVersion, setScrollModeNoticeVersion, setVtInputNoticeVersion, } from '../config/ui-state.js';
|
|
20
20
|
import { getLogger } from '../logging/logger.js';
|
|
21
21
|
import { isTerminalStatus } from '../proc/types.js';
|
|
22
22
|
import { getSecrets } from '../logging/secret-registry.js';
|
|
23
23
|
import { redactText } from '../logging/redact.js';
|
|
24
24
|
import { resolveTuiCapabilities } from './capabilities.js';
|
|
25
|
-
import { useStartupNotices, MOUSE_NOTICE_VERSION, SCROLL_MODE_NOTICE_VERSION, VT_INPUT_NOTICE_VERSION, } from './use-startup-notices.js';
|
|
25
|
+
import { useStartupNotices, MOUSE_NOTICE_VERSION, PROMPT_KEY_NOTICE_VERSION, SCROLL_MODE_NOTICE_VERSION, VT_INPUT_NOTICE_VERSION, } from './use-startup-notices.js';
|
|
26
26
|
import { getTheme } from './theme.js';
|
|
27
27
|
import { pickGlyphs } from './glyphs.js';
|
|
28
28
|
import { Header } from './Header.js';
|
|
@@ -44,9 +44,11 @@ import { buildExitSummary } from './scrollback/exit-summary.js';
|
|
|
44
44
|
import { LiveTail } from './scrollback/LiveTail.js';
|
|
45
45
|
import { SCROLLBACK_LIMITS } from './scrollback/limits.js';
|
|
46
46
|
import { TODO_LIMITS } from '../todo/limits.js';
|
|
47
|
+
import { planIsResolved } from '../todo/acceptance.js';
|
|
47
48
|
import { ScrollViewport } from './layout/ScrollViewport.js';
|
|
48
|
-
import { buildTodoRailLayout } from './layout/todo-layout.js';
|
|
49
|
+
import { acceptanceChip, acceptanceRung, buildTodoRailLayout } from './layout/todo-layout.js';
|
|
49
50
|
import { buildTeamPanelLayout } from './layout/team-panel.js';
|
|
51
|
+
import { isLiveTeamCard, lastTeamEntry, rosterCounts } from './team-status.js';
|
|
50
52
|
import { advanceBudget, decideFollowThrough, emptyBudget, } from '../todo/follow-through.js';
|
|
51
53
|
import { useTerminalSize } from './layout/useTerminalSize.js';
|
|
52
54
|
import { useHeightStore } from './use-height-store.js';
|
|
@@ -62,6 +64,16 @@ import { publishSessionView } from './session-bridge.js';
|
|
|
62
64
|
import { OverlayFrame, overlayPageSize } from './layout/OverlayFrame.js';
|
|
63
65
|
import { helpRows } from './overlays/HelpOverlay.js';
|
|
64
66
|
import { ModelPicker } from './overlays/ModelPicker.js';
|
|
67
|
+
import { KitPicker } from './overlays/KitPicker.js';
|
|
68
|
+
import { PromptLibraryOverlay } from './overlays/PromptLibraryOverlay.js';
|
|
69
|
+
import { PromptPicker } from './overlays/PromptPicker.js';
|
|
70
|
+
import { draftToInput } from './overlays/prompt-library-state.js';
|
|
71
|
+
import { createPrompt, deletePrompt, flushPromptStats, loadPromptLibrary, movePrompt, recordPromptUse, setPromptFavorite, takePromptRepairNotice, updatePrompt, } from '../prompts/store.js';
|
|
72
|
+
import { resolvePromptRef } from '../prompts/resolve.js';
|
|
73
|
+
import { promptLineCount } from '../prompts/fill.js';
|
|
74
|
+
import { PROMPT_CORRUPT_COPY, PROMPT_INSERTED_COPY, promptFilledCopy, promptSkippedCopy, } from '../prompts/copy.js';
|
|
75
|
+
import { INACTIVE_KIT } from '../config/kit-resolution.js';
|
|
76
|
+
import { kitApplyMessage, kitNoteMessages } from '../agent/kit-apply.js';
|
|
65
77
|
import { createModelProbe } from '../agent/model-probe.js';
|
|
66
78
|
import { SettingsScreen, compactionSettingsFrom, fastSettingsFrom, } from './overlays/SettingsScreen.js';
|
|
67
79
|
import { ConfirmDialog } from './overlays/ConfirmDialog.js';
|
|
@@ -74,6 +86,7 @@ import { MODE_LABEL, MODE_TOGGLE_KEYS, nextMode } from '../agent/agent-mode.js';
|
|
|
74
86
|
import { createClipboardCoordinator } from './clipboard-task.js';
|
|
75
87
|
import { QueuePanel } from './QueuePanel.js';
|
|
76
88
|
import { QueueOverlay } from './overlays/QueueOverlay.js';
|
|
89
|
+
import { AgentsBoardOverlay } from './overlays/AgentsBoardOverlay.js';
|
|
77
90
|
import { buildQueueLayout } from './layout/queue-layout.js';
|
|
78
91
|
import { interactionCopy } from './interaction-copy.js';
|
|
79
92
|
import { createRunMetrics, updateRunMetrics, freezeRunMetrics, projectRunMetrics } from './run-metrics.js';
|
|
@@ -138,6 +151,25 @@ function seedViewState(seed) {
|
|
|
138
151
|
? viewReducer(base, { type: 'restoreEntries', entries: seed.entries })
|
|
139
152
|
: base;
|
|
140
153
|
}
|
|
154
|
+
/**
|
|
155
|
+
* One prompt-library mutation, translated into the store call it names.
|
|
156
|
+
*
|
|
157
|
+
* MODULE-LEVEL, so it is a table rather than a closure rebuilt on every render: the
|
|
158
|
+
* overlay sends an INTENT (`{kind:'create', draft}`), and this is the single place that
|
|
159
|
+
* intent becomes a write. Every branch returns the store's own result, which carries
|
|
160
|
+
* the new snapshot on success AND on `write_failed` (D-4).
|
|
161
|
+
*/
|
|
162
|
+
function applyPromptMutation(action) {
|
|
163
|
+
if (action.kind === 'create')
|
|
164
|
+
return createPrompt(draftToInput(action.draft));
|
|
165
|
+
if (action.kind === 'update')
|
|
166
|
+
return updatePrompt(action.id, draftToInput(action.draft));
|
|
167
|
+
if (action.kind === 'delete')
|
|
168
|
+
return deletePrompt(action.id);
|
|
169
|
+
if (action.kind === 'move')
|
|
170
|
+
return movePrompt(action.id, action.delta);
|
|
171
|
+
return setPromptFavorite(action.id, action.favorite);
|
|
172
|
+
}
|
|
141
173
|
export function App({ controller, version, initialOverlay, initialPrompt, confirmBridge, humanInputBridge, mouseSource, vtInputWarning, updateBridge, pasteBridge, terminal, scrollMode = 'app', scrollback, initialEntries, session, sessionBridge, sessionNotice, }) {
|
|
142
174
|
const native = scrollMode === 'native' && scrollback !== undefined;
|
|
143
175
|
/** Set when the native writer swallowed repeated tall frames; cleared by a resize. */
|
|
@@ -153,6 +185,19 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
153
185
|
// nonce render (the async clear lands a frame later - see the timing
|
|
154
186
|
// invariant beside the ladder itself).
|
|
155
187
|
const [draftClearNonce, setDraftClearNonce] = useState(0);
|
|
188
|
+
// --- Quick prompt library (quick-prompt-library §5.2) --------------------
|
|
189
|
+
//
|
|
190
|
+
// THE SNAPSHOT IS READ ONCE, from the store's own cache, and re-read only after a
|
|
191
|
+
// mutation or a command that might have written the file. `App` never edits it: the
|
|
192
|
+
// store is the only write authority (I-6), so the render can never disagree with the
|
|
193
|
+
// disk about what exists.
|
|
194
|
+
const [promptLibrary, setPromptLibrary] = useState(loadPromptLibrary);
|
|
195
|
+
const [promptIntent, setPromptIntent] = useState(null);
|
|
196
|
+
const promptIntentNonce = useRef(0);
|
|
197
|
+
// The fill channel's payload (D-6). `null` until the first fill, so the composer's
|
|
198
|
+
// effect has nothing to do on every mount.
|
|
199
|
+
const [draftFill, setDraftFill] = useState(null);
|
|
200
|
+
const draftFillNonce = useRef(0);
|
|
156
201
|
const [draftClearedByCtrlC, setDraftClearedByCtrlC] = useState(false);
|
|
157
202
|
const [popupRows, setPopupRows] = useState(0);
|
|
158
203
|
const [statusExpanded, setStatusExpanded] = useState(false);
|
|
@@ -616,8 +661,13 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
616
661
|
if (controller.isRunning() || controller.isCompactionBusy?.())
|
|
617
662
|
return;
|
|
618
663
|
const live = controller.getTodoSnapshot();
|
|
619
|
-
|
|
620
|
-
|
|
664
|
+
// `planIsResolved`, NOT `doneCount >= total` (todo-acceptance-criteria
|
|
665
|
+
// §6.7 / AC-20). With steps all done but criteria unchecked the old test
|
|
666
|
+
// said "no work left" and returned silently - one second after the notice
|
|
667
|
+
// promised "Continuing with 2 unchecked acceptance criteria in 3s". A
|
|
668
|
+
// promise that cancels itself is the worst failure this design has.
|
|
669
|
+
if (!live || planIsResolved(live.items, live.acceptance?.items ?? null))
|
|
670
|
+
return;
|
|
621
671
|
budgetRef.current = { ...budgetRef.current, used: budgetRef.current.used + 1 };
|
|
622
672
|
autoContinuationRef.current = true;
|
|
623
673
|
// COUNTS ONLY, NEVER ITEM TEXT (D-19), and only at the fire point —
|
|
@@ -627,6 +677,8 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
627
677
|
noProgressStreak: budgetRef.current.noProgressStreak,
|
|
628
678
|
total: live.total,
|
|
629
679
|
done: live.doneCount,
|
|
680
|
+
accTotal: live.acceptance?.total ?? 0,
|
|
681
|
+
accChecked: live.acceptance?.checkedCount ?? 0,
|
|
630
682
|
mode: 'auto',
|
|
631
683
|
});
|
|
632
684
|
submitRef.current(decision.message, { userInitiated: false, todoPolicy: 'continue' });
|
|
@@ -1041,32 +1093,45 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1041
1093
|
// the events must never be shown advice about a mode not in effect (§6.1).
|
|
1042
1094
|
// `cli.tsx::wantMouse` now folds the platform check into the same expression,
|
|
1043
1095
|
// so on a console that reports nothing there is no source and no advice.
|
|
1096
|
+
// SIX NOTICES, TWO ARGUMENTS (quick-prompt-library D-20 / P1-6). The hook took five
|
|
1097
|
+
// positional arguments and this feature added a sixth, which `CLAUDE.md` forbids; the
|
|
1098
|
+
// options object is also what makes an older caller that passes nothing byte-identical.
|
|
1044
1099
|
useStartupNotices(dispatch, {
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1100
|
+
mouse: {
|
|
1101
|
+
enabled: !!mouseSource,
|
|
1102
|
+
// NOT re-derived here (see `MouseNoticeOptions`): `cli.tsx` computes
|
|
1103
|
+
// `mouseFilter !== null && cfg.mouseSelect` once and four things read it,
|
|
1104
|
+
// so a second derivation is a second chance for them to disagree.
|
|
1105
|
+
selectEnabled: terminal?.mouseSelect ?? false,
|
|
1106
|
+
// `state.json`, not `config.json`: whether a one-shot notice has been shown
|
|
1107
|
+
// is bookkeeping, and the comment on the old persisted key said so.
|
|
1108
|
+
//
|
|
1109
|
+
// THE VERSION, NOT `true` (P1-8). The gate compares against
|
|
1110
|
+
// `MOUSE_NOTICE_VERSION`, so storing anything else either replays the
|
|
1111
|
+
// notice forever or silences a future revision of the text.
|
|
1112
|
+
onSeen: () => setMouseNoticeVersion(MOUSE_NOTICE_VERSION),
|
|
1113
|
+
},
|
|
1114
|
+
// Present only when `loadConfig` had to raise the user's own
|
|
1115
|
+
// `transcriptRetain` to `transcriptWindow` (tui-render-performance §5.1).
|
|
1116
|
+
...(cfg.transcriptRetainRequested !== undefined
|
|
1117
|
+
? { retain: { requested: cfg.transcriptRetainRequested, resolved: cfg.transcriptRetain } }
|
|
1118
|
+
: {}),
|
|
1119
|
+
...(vtInputWarning
|
|
1120
|
+
? {
|
|
1121
|
+
vtInput: {
|
|
1122
|
+
nodeVersion: vtInputWarning.nodeVersion,
|
|
1123
|
+
onSeen: () => setVtInputNoticeVersion(VT_INPUT_NOTICE_VERSION),
|
|
1124
|
+
},
|
|
1125
|
+
}
|
|
1126
|
+
: {}),
|
|
1127
|
+
...(native
|
|
1128
|
+
? { scrollMode: { onSeen: () => setScrollModeNoticeVersion(SCROLL_MODE_NOTICE_VERSION) } }
|
|
1129
|
+
: {}),
|
|
1130
|
+
// THE KEY MIGRATION (D-20 / AC-32). Always supplied: the hook gates it on
|
|
1131
|
+
// `state.json::promptKeyNoticeVersion`, so a user who has already been told pays
|
|
1132
|
+
// nothing and `state.json` is never touched for a notice that will not be shown.
|
|
1133
|
+
promptKeys: { onSeen: () => setPromptKeyNoticeVersion(PROMPT_KEY_NOTICE_VERSION) },
|
|
1134
|
+
});
|
|
1070
1135
|
// --- Exit snapshot: the only way state reaches cli.tsx (§4.4). ----------
|
|
1071
1136
|
useEffect(() => {
|
|
1072
1137
|
const merged = mergePendingEntries(state.entries, state.pendingSteering);
|
|
@@ -1202,6 +1267,102 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1202
1267
|
// --- Helpers -----------------------------------------------------------
|
|
1203
1268
|
const notify = (level, text) => dispatch({ type: 'notice', level, text });
|
|
1204
1269
|
const toast = (level, text) => dispatch({ type: 'pushToast', level, text });
|
|
1270
|
+
// --- Quick prompt library: two one-shot channels and the store port ------
|
|
1271
|
+
const refreshPromptLibrary = useCallback(() => setPromptLibrary(loadPromptLibrary()), []);
|
|
1272
|
+
/**
|
|
1273
|
+
* Open the manager on a specific page (D-21).
|
|
1274
|
+
*
|
|
1275
|
+
* THE NONCE IS BUMPED BEFORE THE OVERLAY OPENS, and the overlay consumes it at most
|
|
1276
|
+
* once. Without that, two `/prompts edit 1` in a row would be one intent and the
|
|
1277
|
+
* second would look like a no-op (I-8).
|
|
1278
|
+
*/
|
|
1279
|
+
const openPromptLibrary = useCallback((entry) => {
|
|
1280
|
+
promptIntentNonce.current += 1;
|
|
1281
|
+
setPromptIntent({ nonce: promptIntentNonce.current, entry });
|
|
1282
|
+
dispatch({ type: 'setOverlay', overlay: 'prompts' });
|
|
1283
|
+
}, []);
|
|
1284
|
+
/**
|
|
1285
|
+
* Hand the composer a body to insert (D-6 / §4.6).
|
|
1286
|
+
*
|
|
1287
|
+
* `App` DOES NOT TOUCH THE DRAFT. It only carries the payload and closes the overlay;
|
|
1288
|
+
* `PromptInput` owns the editor, the caret and the one submission path (P0-1).
|
|
1289
|
+
*/
|
|
1290
|
+
const fillComposer = useCallback((text, submit = false) => {
|
|
1291
|
+
draftFillNonce.current += 1;
|
|
1292
|
+
setDraftFill({
|
|
1293
|
+
nonce: draftFillNonce.current,
|
|
1294
|
+
text,
|
|
1295
|
+
...(submit ? { submit: true } : {}),
|
|
1296
|
+
});
|
|
1297
|
+
dispatch({ type: 'setOverlay', overlay: null });
|
|
1298
|
+
}, []);
|
|
1299
|
+
const handlePromptMutate = useCallback((action) => {
|
|
1300
|
+
const result = applyPromptMutation(action);
|
|
1301
|
+
// ON FAILURE TOO, and that is not a mistake: a `write_failed` still applied in
|
|
1302
|
+
// memory (D-4), so the user must be able to see the prompt they just created.
|
|
1303
|
+
setPromptLibrary(result.library);
|
|
1304
|
+
return result;
|
|
1305
|
+
}, []);
|
|
1306
|
+
/**
|
|
1307
|
+
* Closing the picker is when the usage counters reach the disk (D-7).
|
|
1308
|
+
*
|
|
1309
|
+
* The snapshot is refreshed FIRST so the next open shows the counts this session just
|
|
1310
|
+
* produced, then the stats are flushed - two independent concerns that happen to share
|
|
1311
|
+
* a moment.
|
|
1312
|
+
*/
|
|
1313
|
+
const closePromptPicker = () => {
|
|
1314
|
+
refreshPromptLibrary();
|
|
1315
|
+
flushPromptStats();
|
|
1316
|
+
dispatch({ type: 'setOverlay', overlay: null });
|
|
1317
|
+
};
|
|
1318
|
+
const handlePromptFill = (id, submit) => {
|
|
1319
|
+
// THE FRESH LIBRARY, not the render snapshot: a `/prompts fill` can arrive in the
|
|
1320
|
+
// same tick as a mutation, and the snapshot would then be one render behind.
|
|
1321
|
+
const prompt = loadPromptLibrary().prompts.find((entry) => entry.id === id);
|
|
1322
|
+
if (!prompt)
|
|
1323
|
+
return;
|
|
1324
|
+
recordPromptUse(id);
|
|
1325
|
+
fillComposer(prompt.content, submit);
|
|
1326
|
+
// The usage counters change, so the list the picker re-opens with is the one with
|
|
1327
|
+
// the new counts (D-7: memory first, disk on a debounce).
|
|
1328
|
+
refreshPromptLibrary();
|
|
1329
|
+
// TWO ACKNOWLEDGEMENTS, ONE OF THEM HONEST ABOUT WHAT HAPPENED (D-6). When the draft
|
|
1330
|
+
// already had text and the mode is `insert`, the sentence has to say that the words
|
|
1331
|
+
// are still there - otherwise "filled" reads as "replaced" and the user retypes what
|
|
1332
|
+
// they had. `hasDraftRef` is the composer's own presence flag (the host has no copy
|
|
1333
|
+
// of the text), so this costs no new state.
|
|
1334
|
+
const keptDraft = cfg.prompts?.insertMode !== 'replace' && hasDraftRef.current;
|
|
1335
|
+
notify('info', keptDraft
|
|
1336
|
+
? PROMPT_INSERTED_COPY
|
|
1337
|
+
: promptFilledCopy(prompt.title, promptLineCount(prompt.content)));
|
|
1338
|
+
};
|
|
1339
|
+
/**
|
|
1340
|
+
* One-shot report of a repaired or partially unreadable library (§8).
|
|
1341
|
+
*
|
|
1342
|
+
* A DURABLE `notice`, not a toast: the user has to be able to scroll back to the
|
|
1343
|
+
* sentence that explains why entries are missing, and a `.bak` file is only useful to
|
|
1344
|
+
* someone who knows it exists.
|
|
1345
|
+
*/
|
|
1346
|
+
useEffect(() => {
|
|
1347
|
+
const repair = takePromptRepairNotice();
|
|
1348
|
+
if (!repair)
|
|
1349
|
+
return;
|
|
1350
|
+
if (repair.repaired !== 'entries')
|
|
1351
|
+
notify('warn', PROMPT_CORRUPT_COPY);
|
|
1352
|
+
if (repair.dropped > 0)
|
|
1353
|
+
notify('warn', promptSkippedCopy(repair.dropped));
|
|
1354
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1355
|
+
}, []);
|
|
1356
|
+
// Usage counters are written when the surface that produced them closes, and again
|
|
1357
|
+
// on unmount (D-7). The 1 s debounce inside the store covers the common case; these
|
|
1358
|
+
// two make a clean exit cost exactly one write instead of waiting for a timer.
|
|
1359
|
+
const promptOverlayOpen = state.overlay === 'prompts' || state.overlay === 'prompt-pick';
|
|
1360
|
+
useEffect(() => {
|
|
1361
|
+
if (!promptOverlayOpen)
|
|
1362
|
+
return undefined;
|
|
1363
|
+
return () => { flushPromptStats(); };
|
|
1364
|
+
}, [promptOverlayOpen]);
|
|
1365
|
+
useEffect(() => () => { flushPromptStats(); }, []);
|
|
1205
1366
|
// THE BRANCH / `--save` NOTICE (D-13). Once, on mount, and only when the
|
|
1206
1367
|
// caller supplied one: "the session you branched from is unchanged" is the
|
|
1207
1368
|
// sentence that makes `--resume` safe to reach for, and it belongs in the
|
|
@@ -1520,6 +1681,16 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1520
1681
|
}
|
|
1521
1682
|
dispatch(action);
|
|
1522
1683
|
},
|
|
1684
|
+
// The prompt library's three capabilities (quick-prompt-library §4.3 / P1-3).
|
|
1685
|
+
// Present even when `prompts.enabled` is false: `/prompts` has to be able to SAY
|
|
1686
|
+
// that it is off, and a missing port would answer "not available in this build"
|
|
1687
|
+
// instead (AC-30). `resolve` reads the store's cache, so it always sees the last
|
|
1688
|
+
// mutation rather than this render's snapshot.
|
|
1689
|
+
prompts: {
|
|
1690
|
+
openPicker: () => dispatch({ type: 'setOverlay', overlay: 'prompt-pick' }),
|
|
1691
|
+
openManager: (entry) => openPromptLibrary(entry),
|
|
1692
|
+
resolve: (ref) => resolvePromptRef(loadPromptLibrary(), ref),
|
|
1693
|
+
},
|
|
1523
1694
|
setOverlay: (overlay) => dispatch({ type: 'setOverlay', overlay }),
|
|
1524
1695
|
notify,
|
|
1525
1696
|
toast,
|
|
@@ -1681,6 +1852,11 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1681
1852
|
notify('error', `Command failed: ${redactText(reason, getSecrets())}\nInput: ${safe}`);
|
|
1682
1853
|
}
|
|
1683
1854
|
finally {
|
|
1855
|
+
// A command may have written the prompt library (`/prompts add|rm|move|sort`).
|
|
1856
|
+
// The store's cache already holds the new snapshot, so this is a `setState`, not
|
|
1857
|
+
// a disk read - and without it the list would show the library as it was when
|
|
1858
|
+
// the overlay opened.
|
|
1859
|
+
refreshPromptLibrary();
|
|
1684
1860
|
if (switching) {
|
|
1685
1861
|
switchingConversation.current = false;
|
|
1686
1862
|
drainCompactionQueue.current();
|
|
@@ -1724,6 +1900,39 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1724
1900
|
dispatch({ type: 'setOverlay', overlay: null });
|
|
1725
1901
|
toast('info', `Model set to ${provider}:${model}.`);
|
|
1726
1902
|
};
|
|
1903
|
+
/**
|
|
1904
|
+
* `ctrl+K` chose a row (agent-model-kits §4.7 / AC-25).
|
|
1905
|
+
*
|
|
1906
|
+
* `null` is `Current setup`, which is the RELEASE action, not a no-op: it runs
|
|
1907
|
+
* `clearSessionKit` so the live connections go back to the file's bindings.
|
|
1908
|
+
* Both paths close the overlay, and both report through the same toast funnel -
|
|
1909
|
+
* including every `KitNote`, because a kit that turns the fast tier on spends
|
|
1910
|
+
* money and must never do it silently (§4.2 / F-23).
|
|
1911
|
+
*/
|
|
1912
|
+
const handleKitChoose = (kitId) => {
|
|
1913
|
+
// NO KIT PORT, NO CRASH: a legacy host can still press the key, and the only
|
|
1914
|
+
// honest answer is a sentence rather than a stack trace.
|
|
1915
|
+
const port = controller;
|
|
1916
|
+
if (typeof port.applySessionKit !== 'function' || typeof port.clearSessionKit !== 'function') {
|
|
1917
|
+
dispatch({ type: 'setOverlay', overlay: null });
|
|
1918
|
+
toast('error', 'This build cannot switch kits.');
|
|
1919
|
+
return;
|
|
1920
|
+
}
|
|
1921
|
+
const result = kitId === null
|
|
1922
|
+
? controller.clearSessionKit() : controller.applySessionKit(kitId);
|
|
1923
|
+
dispatch({ type: 'setOverlay', overlay: null });
|
|
1924
|
+
// The config is already updated synchronously, so this reads the kit that is
|
|
1925
|
+
// in effect RIGHT NOW rather than the id the user asked for - if the apply
|
|
1926
|
+
// fell back to anything, the toast says so.
|
|
1927
|
+
const applied = controller.getActiveKit().name;
|
|
1928
|
+
const text = !result.ok ? kitApplyMessage(result)
|
|
1929
|
+
: kitId === null ? "Kit released; using the config file's settings."
|
|
1930
|
+
: `Kit ${applied} applied for this session.`;
|
|
1931
|
+
const notes = kitNoteMessages(result.notes);
|
|
1932
|
+
// A NOTE IS A WARNING, NOT A SUCCESS (F-23): the toast colour is the only
|
|
1933
|
+
// signal a user gets that applying a kit can start spending money.
|
|
1934
|
+
toast(result.ok ? (notes.length ? 'warn' : 'success') : 'error', [text, ...notes].join(' '));
|
|
1935
|
+
};
|
|
1727
1936
|
const closeConfirm = () => {
|
|
1728
1937
|
setConfirmState(null);
|
|
1729
1938
|
dispatch({ type: 'setOverlay', overlay: null });
|
|
@@ -1967,6 +2176,71 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1967
2176
|
setStatusExpanded(value => !value);
|
|
1968
2177
|
return;
|
|
1969
2178
|
}
|
|
2179
|
+
// Ctrl+B: the Agents board (tui-subagent-panel D-6 / §6.5).
|
|
2180
|
+
//
|
|
2181
|
+
// THE GUARD ORDER IS Ctrl+G's EXACTLY, plus one rung: pressing it again CLOSES the
|
|
2182
|
+
// board, and any OTHER overlay keeps the keyboard (a second overlay drawn over the
|
|
2183
|
+
// first would leave two `isActive` input handlers fighting for Up/Down).
|
|
2184
|
+
//
|
|
2185
|
+
// Two byte forms, like Ctrl+K: the raw 0x02 byte and `key.ctrl` + `b`. 0x02 is
|
|
2186
|
+
// unbound everywhere in this app, and `Alt+B` (keymap.ts's `wordLeft`) is a
|
|
2187
|
+
// different modifier with a different prefix, so neither collides.
|
|
2188
|
+
const isCtrlB = (key.ctrl && (input === 'b' || input === 'B')) || input === '\x02';
|
|
2189
|
+
if (isCtrlB) {
|
|
2190
|
+
if (tooSmall)
|
|
2191
|
+
return;
|
|
2192
|
+
const openOverlay = stateRef.current.overlay;
|
|
2193
|
+
if (openOverlay === 'agents') {
|
|
2194
|
+
dispatch({ type: 'setOverlay', overlay: null });
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
if (openOverlay)
|
|
2198
|
+
return;
|
|
2199
|
+
// ONE GUARD, SHARED WITH `/agents` (AC-18): an empty board would read as a broken
|
|
2200
|
+
// feature, and the honest answer is the same notice the command gives.
|
|
2201
|
+
if (stateRef.current.team === null && lastTeamEntry(stateRef.current.entries) === null) {
|
|
2202
|
+
toast('info', 'No subagent dispatch yet.');
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
dispatch({ type: 'setOverlay', overlay: 'agents' });
|
|
2206
|
+
return;
|
|
2207
|
+
}
|
|
2208
|
+
// Ctrl+P: the quick prompt library (quick-prompt-library D-1).
|
|
2209
|
+
//
|
|
2210
|
+
// THIS KEY USED TO BE THE MODE FALLBACK and now opens a library, because the
|
|
2211
|
+
// requirement names it as a first-class entry point. The guards are exactly the
|
|
2212
|
+
// ones Ctrl+K and Ctrl+G use: an overlay owns the keyboard, and on a too-small
|
|
2213
|
+
// terminal the frame has nowhere to go. `prompts.enabled=false` is read at KEY
|
|
2214
|
+
// TIME rather than at mount, so the switch takes effect without a restart - and
|
|
2215
|
+
// the command stays registered so `/prompts` can say it is off (D-19 / AC-30).
|
|
2216
|
+
if (key.ctrl && (input === 'p' || input === 'P')) {
|
|
2217
|
+
if (tooSmall || stateRef.current.overlay)
|
|
2218
|
+
return;
|
|
2219
|
+
// `!== false`, so a hand-built `CliConfig` without the section behaves like the
|
|
2220
|
+
// default (enabled) rather than like a dead key.
|
|
2221
|
+
if (controller.getConfig().prompts?.enabled === false)
|
|
2222
|
+
return;
|
|
2223
|
+
openPromptLibrary({ kind: 'list' });
|
|
2224
|
+
return;
|
|
2225
|
+
}
|
|
2226
|
+
// Ctrl+K: the Agent-kit picker (agent-model-kits D-15 / §4.7).
|
|
2227
|
+
//
|
|
2228
|
+
// NOT AN INPUT FRAME, unlike Ctrl+I below. Ctrl+I collides with the Tab byte,
|
|
2229
|
+
// so a stdin translator has to disambiguate it; 0x0B is unambiguous in every
|
|
2230
|
+
// encoding this app supports (utf8 byte, win32-input-mode's pass-through of
|
|
2231
|
+
// control chars, and CSI-u's `ctrlByte` fold), so the raw byte IS the key.
|
|
2232
|
+
//
|
|
2233
|
+
// `killToEnd` HAS MOVED TO Alt+K FIRST (keymap.ts): `PromptInput`'s
|
|
2234
|
+
// recognizer runs before its `key.ctrl` early-return, so leaving the old
|
|
2235
|
+
// binding would delete the draft in one listener while this one opened the
|
|
2236
|
+
// overlay. Two listeners both see the event; `return` only leaves one.
|
|
2237
|
+
const isCtrlK = (key.ctrl && (input === 'k' || input === 'K')) || input === '\x0b';
|
|
2238
|
+
if (isCtrlK) {
|
|
2239
|
+
if (tooSmall || stateRef.current.overlay)
|
|
2240
|
+
return;
|
|
2241
|
+
dispatch({ type: 'setOverlay', overlay: 'kit' });
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
1970
2244
|
// Ctrl+I arrives as INDEX_KEY_FRAME, never as key.ctrl + 'i': legacy
|
|
1971
2245
|
// terminals encode it as the Tab byte and the stdin filter's translators
|
|
1972
2246
|
// are the only layer that can tell them apart (see input/limits.ts).
|
|
@@ -2004,7 +2278,15 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2004
2278
|
const overlay = stateRef.current.overlay;
|
|
2005
2279
|
// Modes A only. `model` / `confirm` manage their own keys; registering an
|
|
2006
2280
|
// arrow handler for them would double every keypress (R-12).
|
|
2007
|
-
const controlled = overlay === 'help' || overlay === 'settings' || overlay === 'plan'
|
|
2281
|
+
const controlled = overlay === 'help' || overlay === 'settings' || overlay === 'plan'
|
|
2282
|
+
|| overlay === 'queue' || overlay === 'kit'
|
|
2283
|
+
// The Agents board pages through `overlayScroll` too, but keeps Up/Down for its
|
|
2284
|
+
// CHILD CURSOR - the same trade `settings` makes with its field cursor.
|
|
2285
|
+
|| overlay === 'agents'
|
|
2286
|
+
// The prompt surfaces page themselves through `overlayScroll` too
|
|
2287
|
+
// (quick-prompt-library appendix B-1). Without them a `PgDn` in the list does
|
|
2288
|
+
// nothing and the wheel falls through to the transcript behind it.
|
|
2289
|
+
|| overlay === 'prompts' || overlay === 'prompt-pick';
|
|
2008
2290
|
if (key.pageUp) {
|
|
2009
2291
|
if (controlled)
|
|
2010
2292
|
setOverlayScroll((n) => Math.max(0, n - overlayPage));
|
|
@@ -2029,11 +2311,13 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2029
2311
|
// everything else falls through (AC-P2).
|
|
2030
2312
|
//
|
|
2031
2313
|
// TWO KEYS, ONE BRANCH (shift-tab-mode-toggle-still-dead-on-windows, C2).
|
|
2032
|
-
// `Ctrl+
|
|
2033
|
-
//
|
|
2034
|
-
//
|
|
2035
|
-
//
|
|
2036
|
-
|
|
2314
|
+
// `Ctrl+N` is not a Windows-only alias bolted on beside the real binding: it is the
|
|
2315
|
+
// rung that survives when `Shift+Tab` cannot be delivered at all, and it shares this
|
|
2316
|
+
// branch precisely so the two can never drift into different behaviour. See
|
|
2317
|
+
// `MODE_TOGGLE_KEYS` for why this key and not another - and for why it is `Ctrl+N`
|
|
2318
|
+
// now rather than the `Ctrl+P` this branch used to own (quick-prompt-library D-1:
|
|
2319
|
+
// `Ctrl+P` opens the prompt library, and one key may not mean two things).
|
|
2320
|
+
if ((key.tab && key.shift) || (key.ctrl && (input === 'n' || input === 'N'))) {
|
|
2037
2321
|
if (stateRef.current.overlay)
|
|
2038
2322
|
return;
|
|
2039
2323
|
applyMode(nextMode(controller.getAgentMode()));
|
|
@@ -2114,10 +2398,16 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2114
2398
|
const entries = stateRef.current.entries;
|
|
2115
2399
|
const card = [...entries]
|
|
2116
2400
|
.reverse()
|
|
2117
|
-
.find((e) =>
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2401
|
+
.find((e) =>
|
|
2402
|
+
// THE LIVE DISPATCH CARD IS NOT EXPANDABLE (tui-subagent-double-roster
|
|
2403
|
+
// A-2'): it is suppressed on screen, so expanding it would print a frozen
|
|
2404
|
+
// copy of a card that is still being rewritten. Once it settles it becomes
|
|
2405
|
+
// an ordinary history card and this key reaches it again.
|
|
2406
|
+
!isLiveTeamCard(e) &&
|
|
2407
|
+
(e.kind === 'tool' ||
|
|
2408
|
+
e.kind === 'team' ||
|
|
2409
|
+
e.kind === 'compaction' ||
|
|
2410
|
+
(e.kind === 'service' && !e.terminal)));
|
|
2121
2411
|
// NATIVE MODE ONLY. The fallback exists because native mode cannot scroll inside the
|
|
2122
2412
|
// frame - the answer's head is reachable only if it is printed. Full-screen mode has a
|
|
2123
2413
|
// viewport that already reaches it, and it has no `expanded` rendering for an answer,
|
|
@@ -2224,6 +2514,12 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2224
2514
|
const overlay = state.overlay;
|
|
2225
2515
|
const settingsDraft = useMemo(() => overlay === 'settings'
|
|
2226
2516
|
? controller.createModelSettingsDraft() : undefined, [controller, overlay]);
|
|
2517
|
+
// THE FILE'S BINDINGS, FOR THE KIT PICKER'S FIRST ROW (§4.6 / D-13). While a
|
|
2518
|
+
// session kit is held the live config mirrors the KIT (I-6), so `Current setup`
|
|
2519
|
+
// can only describe what choosing it restores by reading the file - one read per
|
|
2520
|
+
// opening, the same price `settingsDraft` one line up already pays.
|
|
2521
|
+
const kitDiskConfig = useMemo(() => overlay === 'kit'
|
|
2522
|
+
? controller.createModelSettingsDraft().baseline : undefined, [controller, overlay]);
|
|
2227
2523
|
// ONE PROBE PER APP (model-profile-connection-test 5 row 9): the port needs the
|
|
2228
2524
|
// controller's provider registry, which the overlay has no business owning.
|
|
2229
2525
|
// Built only while the settings overlay is open, so no other screen pays for it.
|
|
@@ -2237,6 +2533,22 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2237
2533
|
return createModelProbe({ registry: controller.getProviderRegistry() });
|
|
2238
2534
|
}, [controller, overlay]);
|
|
2239
2535
|
const hasKey = controller.hasApiKey();
|
|
2536
|
+
// THE ACTIVE KIT, DERIVED PER FRAME (D-5 / F-22): `controller.getActiveKit()` is
|
|
2537
|
+
// pure, so it cannot go stale the way a cached copy would after `ctrl+K`,
|
|
2538
|
+
// `/model` or a saved default. Absent when no kit is in effect, which is what
|
|
2539
|
+
// keeps the status row byte-identical for users who own none (I-9).
|
|
2540
|
+
//
|
|
2541
|
+
// A TEST DOUBLE OR AN EMBEDDED HOST MAY PREDATE THIS PORT: the same tolerance
|
|
2542
|
+
// the probe port gets one screen over ("absent means the rows read
|
|
2543
|
+
// unresolved") applies here - no kit, no crash.
|
|
2544
|
+
const activeKit = typeof controller.getActiveKit === 'function'
|
|
2545
|
+
? controller.getActiveKit() : INACTIVE_KIT;
|
|
2546
|
+
const kitChip = activeKit.source === 'none' ? undefined : {
|
|
2547
|
+
name: activeKit.name,
|
|
2548
|
+
modified: activeKit.modified,
|
|
2549
|
+
otherNames: (cfg.modelProfiles?.kits ?? [])
|
|
2550
|
+
.filter((kit) => kit.id !== activeKit.id).map((kit) => kit.name),
|
|
2551
|
+
};
|
|
2240
2552
|
const empty = state.entries.length === 0;
|
|
2241
2553
|
// RENDER SCOPE, so the chip and the composer hint agree with what the Ctrl+C
|
|
2242
2554
|
// handler will do on the very next keystroke.
|
|
@@ -2340,7 +2652,14 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2340
2652
|
// ALWAYS CALLED, INERT WHEN `enabled` IS FALSE: hooks cannot be conditional, and
|
|
2341
2653
|
// an inert hook is what keeps `scrollMode: 'app'` byte-identical (I-S9).
|
|
2342
2654
|
const viewCols = native ? Math.max(1, cols - 1) : cols;
|
|
2343
|
-
const header = (_jsx(Header, { columns: viewCols, statusExpanded: statusExpanded, scrolledLines: scrolledLines, overlayOpen: overlay !== null, cwd: controller.getCwd(), provider: cfg.provider, model: cfg.model, hasKey: hasKey, variant: pickHeaderVariant(cols), theme: theme, caps: caps
|
|
2655
|
+
const header = (_jsx(Header, { columns: viewCols, statusExpanded: statusExpanded, scrolledLines: scrolledLines, overlayOpen: overlay !== null, cwd: controller.getCwd(), provider: cfg.provider, model: cfg.model, hasKey: hasKey, variant: pickHeaderVariant(cols), theme: theme, caps: caps,
|
|
2656
|
+
// ALWAYS TRUE (agent-model-kits §4.7 / RV-P1-2): the requirement is that
|
|
2657
|
+
// `ctrl+G` reveals `ctrl+K`, and the user with an empty library needs that
|
|
2658
|
+
// hint most - the picker itself carries the "create one in /settings" row.
|
|
2659
|
+
kitHint: true,
|
|
2660
|
+
// `prompts.hint=false` removes the `^P prompts` clause and nothing else; the key
|
|
2661
|
+
// still works (AC-3).
|
|
2662
|
+
promptsHint: cfg.prompts?.hint !== false }));
|
|
2344
2663
|
// The header and the opener are SLOTS OF THE SHELL, not printed bytes (D3/D4): at
|
|
2345
2664
|
// `H = rows - 1` a printed row is pushed off the screen by the very next frame, so
|
|
2346
2665
|
// `printBanner()` was a way of making the banner invisible. Neither is passed here.
|
|
@@ -2391,7 +2710,10 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2391
2710
|
const openerCols = nativeBudget ? nativeBudget.previewCols : contentCols;
|
|
2392
2711
|
const opener = empty ? (_jsx(SessionOpener, { variant: pickOpenerVariant(openerRows, openerCols), version: version, cwd: controller.getCwd(), hasKey: hasKey, viewportRows: openerRows, theme: theme, caps: caps })) : null;
|
|
2393
2712
|
const overlayMaxRows = nativeBudget ? nativeBudget.overlayRows : viewportBudget;
|
|
2394
|
-
const overlayNode = overlay === 'queue' ? (_jsx(QueueOverlay, { pending: state.pendingSteering, cols: viewCols, maxRows: overlayMaxRows, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps })) : overlay === 'help' ? (_jsx(OverlayFrame, { title: "Help", hint: `Esc close ${glyphs.midDot} PgUp/PgDn scroll`, maxRows: overlayMaxRows, cols: viewCols, rows: helpRows(theme, caps, native), scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps })) : overlay === 'model' ? (_jsx(ModelPicker, { isActive: !tooSmall, registry: controller.getModelRegistry(), currentProvider: cfg.provider, currentModel: cfg.model, maxRows: overlayMaxRows, cols: viewCols, theme: theme, caps: caps, onSelect: handleModelSelect })) : overlay === '
|
|
2713
|
+
const overlayNode = overlay === 'queue' ? (_jsx(QueueOverlay, { pending: state.pendingSteering, cols: viewCols, maxRows: overlayMaxRows, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps })) : overlay === 'help' ? (_jsx(OverlayFrame, { title: "Help", hint: `Esc close ${glyphs.midDot} PgUp/PgDn scroll`, maxRows: overlayMaxRows, cols: viewCols, rows: helpRows(theme, caps, native), scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps })) : overlay === 'model' ? (_jsx(ModelPicker, { isActive: !tooSmall, registry: controller.getModelRegistry(), currentProvider: cfg.provider, currentModel: cfg.model, maxRows: overlayMaxRows, cols: viewCols, theme: theme, caps: caps, onSelect: handleModelSelect })) : overlay === 'kit' ? (_jsx(KitPicker, { isActive: !tooSmall, library: cfg.modelProfiles, config: cfg, ...(kitDiskConfig ? { diskConfig: kitDiskConfig } : {}), active: activeKit,
|
|
2714
|
+
// ONLY THE APPLY IS BLOCKED while work is in flight (§4.7): a list a
|
|
2715
|
+
// user cannot even OPEN reads as a broken key.
|
|
2716
|
+
busy: controller.isModelSettingsBusy(), maxRows: overlayMaxRows, cols: viewCols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, onChoose: handleKitChoose, onClose: () => dispatch({ type: 'setOverlay', overlay: null }) })) : overlay === 'prompts' ? (_jsx(PromptLibraryOverlay, { isActive: !tooSmall, library: promptLibrary, ...(promptIntent ? { intent: promptIntent } : {}), cols: viewCols, maxRows: overlayMaxRows, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, onMutate: handlePromptMutate, onNotice: notify, onClose: () => dispatch({ type: 'setOverlay', overlay: null }) })) : overlay === 'prompt-pick' ? (_jsx(PromptPicker, { isActive: !tooSmall, library: promptLibrary, cols: viewCols, maxRows: overlayMaxRows, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, onFill: handlePromptFill, onManage: (entry) => openPromptLibrary(entry), onClose: closePromptPicker })) : overlay === 'settings' ? (_jsx(SettingsScreen, { isActive: !tooSmall, initial: {
|
|
2395
2717
|
provider: cfg.provider,
|
|
2396
2718
|
model: cfg.model,
|
|
2397
2719
|
baseUrl: cfg.baseUrl ?? '',
|
|
@@ -2428,7 +2750,11 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2428
2750
|
}, apiKeys: cfg.apiKeys, maxRows: overlayMaxRows, cols: viewCols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, draft: settingsDraft, probe: probe, onProfileSave: handleProfileSave, onReload: () => controller.createModelSettingsDraft(), onClose: () => dispatch({ type: 'setOverlay', overlay: null }),
|
|
2429
2751
|
// Read from `cfg` (render scope, re-read every frame), so `/retry off`
|
|
2430
2752
|
// while the screen is open shows the new value on the next paint.
|
|
2431
|
-
retrySummary: cfg.retry.enabled && cfg.retry.maxRetries > 0 ? String(cfg.retry.maxRetries) : 'off' })) : overlay === '
|
|
2753
|
+
retrySummary: cfg.retry.enabled && cfg.retry.maxRetries > 0 ? String(cfg.retry.maxRetries) : 'off' })) : overlay === 'agents' ? (
|
|
2754
|
+
// THE BRANCH AND THE `Overlay` MEMBER SHIP TOGETHER (R-11): a missing branch is not
|
|
2755
|
+
// a type error, it is a SILENT BLANK FRAME, because `active={overlay === null}` has
|
|
2756
|
+
// already disabled the transcript underneath.
|
|
2757
|
+
_jsx(AgentsBoardOverlay, { team: state.team, entries: state.entries, cols: viewCols, maxRows: overlayMaxRows, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, isActive: !tooSmall, native: native, theme: theme, caps: caps })) : overlay === 'confirm' && confirmState ? (_jsx(ConfirmDialog, { isActive: !tooSmall, state: confirmState, maxRows: overlayMaxRows, cols: viewCols, theme: theme, caps: caps, onClose: closeConfirm })) : overlay === 'question' && humanRequest?.kind === 'questions' ? (_jsx(QuestionOverlay, { isActive: !tooSmall, questions: humanRequest.questions, maxRows: overlayMaxRows, cols: viewCols, theme: theme, caps: caps, onSubmit: (answers) => resolveHuman({ kind: 'answers', answers, cancelled: false }) })) : overlay === 'plan' && humanRequest?.kind === 'plan' ? (_jsx(PlanReviewOverlay, { isActive: !tooSmall, plan: humanRequest.plan, maxRows: overlayMaxRows, cols: viewCols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, onVerdict: handlePlanVerdict })) : null;
|
|
2432
2758
|
const density = cfg.density;
|
|
2433
2759
|
const transcriptWindow = cfg.transcriptWindow ?? DEFAULT_TRANSCRIPT_WINDOW;
|
|
2434
2760
|
// The fixed status bar remains visible underneath every overlay and toast.
|
|
@@ -2463,6 +2789,16 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2463
2789
|
windowStart: scrollbackView.windowStart,
|
|
2464
2790
|
windowEntries: scrollbackView.tailEntries.length,
|
|
2465
2791
|
headRows: scrollbackView.headRows,
|
|
2792
|
+
// WHICH RUNG THE ACCEPTANCE SECTION REACHED, from the same function the rail
|
|
2793
|
+
// rendered with (§6.9.6). `n/a` is the honest answer when the plan has no
|
|
2794
|
+
// criteria at all, which is a different fact from `absent`.
|
|
2795
|
+
acceptance: state.todos?.acceptance
|
|
2796
|
+
? acceptanceRung({
|
|
2797
|
+
snapshot: state.todos,
|
|
2798
|
+
railRows: nativeBudget.band.railRows,
|
|
2799
|
+
width: railWidth,
|
|
2800
|
+
})
|
|
2801
|
+
: 'n/a',
|
|
2466
2802
|
}
|
|
2467
2803
|
: null,
|
|
2468
2804
|
// A getter: the bridge's `frameReady` and geometry change outside React renders.
|
|
@@ -2511,6 +2847,16 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2511
2847
|
toast: latestToast ? { text: latestToast.text,
|
|
2512
2848
|
level: latestToast.level === 'success' ? 'info' : latestToast.level } : undefined,
|
|
2513
2849
|
});
|
|
2850
|
+
// THE ACCEPTANCE CHIP, COMPUTED ONCE for both status surfaces (P1-8 / C-9).
|
|
2851
|
+
// Whether the rail is already reporting the count is a question about the rail, so
|
|
2852
|
+
// it is asked of the module that owns the rail's geometry - both here and in the
|
|
2853
|
+
// detail row. `App` contributes the two numbers only it knows: which rail was drawn
|
|
2854
|
+
// and how wide it was.
|
|
2855
|
+
const acceptanceActive = acceptanceChip({
|
|
2856
|
+
snapshot: state.todos,
|
|
2857
|
+
railRows: native ? (nativeBudget?.band.railRows ?? 0) : (fullscreenRailVisible ? railRows : 0),
|
|
2858
|
+
width: native ? (nativeBudget?.band.railCols ?? 0) : railWidth,
|
|
2859
|
+
});
|
|
2514
2860
|
const statusInput = {
|
|
2515
2861
|
columns: viewCols, phase: state.runPhase, pendingCount: state.pendingSteering.length,
|
|
2516
2862
|
context: state.context, usageTotal: state.usageTotal, thinkingLevel: cfg.thinkingLevel,
|
|
@@ -2524,9 +2870,16 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2524
2870
|
compacting: compactionLive || !!state.compaction?.inFlight,
|
|
2525
2871
|
mode: state.agentMode, pendingMode: state.pendingAgentMode,
|
|
2526
2872
|
servicesActive: { live: liveServices }, ecoRung: governor.rung,
|
|
2527
|
-
|
|
2528
|
-
|
|
2873
|
+
// THE STATUS-DETAIL ROW COUNTS LIKE THE ROSTER AND THE CHIP (A-1): this was the
|
|
2874
|
+
// last inline phase filter, left behind when the StatusBar chip converged, and it
|
|
2875
|
+
// feeds the `Team n/m` on the second status row.
|
|
2876
|
+
teamActive: state.team
|
|
2877
|
+
? { running: rosterCounts(state.team.runs).running, total: state.team.runs.length }
|
|
2878
|
+
: undefined,
|
|
2529
2879
|
todoActive: state.todos ? { done: state.todos.doneCount, total: state.todos.total } : undefined,
|
|
2880
|
+
// ONE LINE, NO ROW ALGEBRA HERE (P1-8 / C-9): whether the acceptance count still
|
|
2881
|
+
// needs the second status row was decided once, above, by `acceptanceChip`.
|
|
2882
|
+
...(acceptanceActive ? { acceptanceActive } : {}),
|
|
2530
2883
|
fastActive: state.fast?.live ? { inFlight: state.fast.inFlight } : undefined,
|
|
2531
2884
|
...feedbackProjection,
|
|
2532
2885
|
};
|
|
@@ -2538,7 +2891,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2538
2891
|
hintsEnabled: cfg.hints, modeToggleKey,
|
|
2539
2892
|
interruptPhase: interruptGesture.current.phase,
|
|
2540
2893
|
updateAvailable: !!updateSnapshot && shouldRenderUpdateLine(updateSnapshot) } });
|
|
2541
|
-
const composer = (_jsx(Composer, { deleteDisambiguated: terminal?.deleteDisambiguated ?? false, onCompletionContextChange: setCompletion, cols: frameBudget.composerCols, terminalRows: rows, statusExpanded: statusExpanded, onInteraction: returnToComposer, reducedMotion: cfg.reducedMotion ?? false, onEscape: handleComposerEscape, onEscapeDismiss: cancelConfirmation, isActive: overlay === null && !tooSmall, running: state.status === 'running', history: promptHistory, commands: commandOptions, cwd: controller.getCwd(), showHint: false, submitCount: cfg.submitCount, hintsEnabled: cfg.hints, agentMode: state.agentMode, services: liveServices, modeToggleKey: modeToggleKey, popupMaxRows: popupMaxRows, popupMaxHeight: frameBudget.popupMaxHeight, onPopupRowsChange: onPopupRowsChange, onDraftRows: onDraftRows, onDraftPresence: onDraftPresence, draftClearNonce: draftClearNonce, onNotice: notify, theme: theme, caps: caps, onSubmit: handleSubmit, onHelp: () => dispatch({ type: 'setOverlay', overlay: 'help' }) }));
|
|
2894
|
+
const composer = (_jsx(Composer, { deleteDisambiguated: terminal?.deleteDisambiguated ?? false, onCompletionContextChange: setCompletion, cols: frameBudget.composerCols, terminalRows: rows, statusExpanded: statusExpanded, onInteraction: returnToComposer, reducedMotion: cfg.reducedMotion ?? false, onEscape: handleComposerEscape, onEscapeDismiss: cancelConfirmation, isActive: overlay === null && !tooSmall, running: state.status === 'running', history: promptHistory, commands: commandOptions, cwd: controller.getCwd(), showHint: false, submitCount: cfg.submitCount, hintsEnabled: cfg.hints, agentMode: state.agentMode, services: liveServices, modeToggleKey: modeToggleKey, popupMaxRows: popupMaxRows, popupMaxHeight: frameBudget.popupMaxHeight, onPopupRowsChange: onPopupRowsChange, onDraftRows: onDraftRows, onDraftPresence: onDraftPresence, draftClearNonce: draftClearNonce, ...(draftFill ? { draftFill } : {}), insertMode: cfg.prompts?.insertMode ?? 'insert', onNotice: notify, theme: theme, caps: caps, onSubmit: handleSubmit, onHelp: () => dispatch({ type: 'setOverlay', overlay: 'help' }) }));
|
|
2542
2895
|
const team = state.team ? (_jsx(TeamPanel, { snapshot: state.team, layout: teamLayout, rows: rows, cols: contentCols, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2543
2896
|
// --- Native panel band (release-gate-and-native-panels §6.3/§6.4) -------------
|
|
2544
2897
|
//
|
|
@@ -2549,16 +2902,22 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2549
2902
|
// panel that mounts on one value and lays out against another renders nothing and
|
|
2550
2903
|
// reports nothing (TodoPanel's own I-10 / P1-3 note).
|
|
2551
2904
|
const nativeBand = nativeBudget ? nativeBudget.band : null;
|
|
2905
|
+
// The band's FULL width, from the budget rather than from the band object: `band`
|
|
2906
|
+
// carries `previewCols` (which `rail.ts` narrows) but `contentCols` lives on the
|
|
2907
|
+
// budget, and the roster spans the whole thing (tui-subagent-panel D-2).
|
|
2908
|
+
const nativeBandCols = nativeBudget ? nativeBudget.contentCols : 0;
|
|
2552
2909
|
const nativeRail = state.todos && nativeBand && nativeBand.railRows >= TODO_LIMITS.panelMinRows
|
|
2553
2910
|
? (_jsx(TodoPanel, { snapshot: state.todos, width: nativeBand.railCols, rows: nativeBand.railRows, running: running, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2554
|
-
// `cols` is the band
|
|
2555
|
-
//
|
|
2556
|
-
//
|
|
2557
|
-
|
|
2911
|
+
// `cols` is the WHOLE band, not its left column (tui-subagent-panel §4.1.2 / D-2): the
|
|
2912
|
+
// roster is a full-width slot spanning the TODO rail's columns as well, so a row laid
|
|
2913
|
+
// out against `previewCols` would stop its clock one rail short of the screen's edge.
|
|
2914
|
+
// `teamRowGeometry` is continuous in `cols`, so the number that decides whether the
|
|
2915
|
+
// panel mounts IS the number it lays out against.
|
|
2916
|
+
const nativeTeam = state.team && nativeBand ? (_jsx(TeamPanel, { snapshot: state.team, layout: teamLayout, rows: rows, cols: nativeBandCols, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2558
2917
|
const rail = fullscreenRailVisible && state.todos ? (_jsx(TodoPanel, { snapshot: state.todos, width: railWidth, rows: railRows, running: running, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2559
2918
|
const viewport = (_jsxs(ScrollViewport, { active: overlay === null && !tooSmall, overlay: overlayNode, footer: _jsxs(_Fragment, { children: [team, _jsx(QueuePanel, { layout: queueLayout, theme: theme })] }), rail: rail, scrollbar: terminal?.scrollbar, resetKey: scrollResetKey, intent: scrollIntent, pinToBottomNonce: pinToBottomNonce, onScrolledLinesChange: setScrolledLines, onViewportShiftChange: onViewportShiftChange, onGeometryChange: onGeometryChange, geometryKey: `${rows}:${cols}:${viewportBudget}:${frameBudget.composerSlotRows}:${frameBudget.statusRows}:${contentCols}:${overlay}:${tooSmall}`, tailRowsRef: tailSink, hold: selectionHold, resumeMs: cfg.scrollResumeMs, showScrollIndicator: true, cols: contentCols, theme: theme, caps: caps, children: [opener, _jsx(TranscriptList, { entries: transcriptEntries, expandedToolIds: state.expandedToolIds, thinkingVisible: state.thinkingVisible, reducedMotion: viewReducedMotion, density: density, nowSec: nowSec, theme: theme, caps: caps, windowSize: transcriptWindow, cols: contentCols, heights: heights, mountedSink: mountedCount, tailSink: tailSink })] }));
|
|
2560
2919
|
const detailsNode = _jsx(BottomStatusRow, { plan: details, columns: viewCols, theme: theme });
|
|
2561
|
-
const statusNode = (_jsx(StatusBar, { columns: viewCols, speedKnown: speedKnown, ...feedbackProjection, interruptPhase: interruptGesture.current.phase, pendingSteering: state.pendingSteering, runPhase: state.runPhase, activeTool: state.activeTool, ...(wait ? { wait } : {}), runOutcome: state.runOutcome, reducedMotion: reducedMotion, ...(sessionId ? { sessionId } : {}), waitingForConfirmation: !!humanRequest || !!confirmState, stopping: interruptGesture.current.phase === 'stopping', interruptHint: interruptGesture.current.phase === 'stopping'
|
|
2920
|
+
const statusNode = (_jsx(StatusBar, { columns: viewCols, speedKnown: speedKnown, ...feedbackProjection, interruptPhase: interruptGesture.current.phase, pendingSteering: state.pendingSteering, runPhase: state.runPhase, activeTool: state.activeTool, ...(wait ? { wait } : {}), runOutcome: state.runOutcome, reducedMotion: reducedMotion, ...(sessionId ? { sessionId } : {}), ...(kitChip ? { kit: kitChip } : {}), waitingForConfirmation: !!humanRequest || !!confirmState, stopping: interruptGesture.current.phase === 'stopping', interruptHint: interruptGesture.current.phase === 'stopping'
|
|
2562
2921
|
? 'Esc force-stop' : 'Esc x2 interrupt', model: cfg.model, provider: cfg.provider, usageTotal: state.usageTotal,
|
|
2563
2922
|
// ONE OBJECT INSTEAD OF FOUR PARALLEL PROPS (§4.3). The four were a
|
|
2564
2923
|
// projection of "many sources of truth" into the props layer, and a
|
|
@@ -2568,9 +2927,10 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2568
2927
|
// `contextWindow` override reach the bar.
|
|
2569
2928
|
context: state.context, status: state.status, elapsedMs: elapsedMs, thinkingLevel: cfg.thinkingLevel, tokPerSec: tokPerSec, theme: theme, caps: caps, scrolledLines: scrolledLines, redrawNonce: redrawNonce, agentMode: state.agentMode, pendingAgentMode: state.pendingAgentMode, ecoRung: governor.rung, ...(state.team
|
|
2570
2929
|
? {
|
|
2930
|
+
// `rosterCounts`, NOT a fourth inline phase filter (A-1): the chip and
|
|
2931
|
+
// the roster header above it must never disagree.
|
|
2571
2932
|
teamActive: {
|
|
2572
|
-
running: state.team.runs
|
|
2573
|
-
r.phase !== 'failed' && r.phase !== 'aborted').length,
|
|
2933
|
+
running: rosterCounts(state.team.runs).running,
|
|
2574
2934
|
total: state.team.runs.length,
|
|
2575
2935
|
},
|
|
2576
2936
|
}
|
|
@@ -2578,7 +2938,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2578
2938
|
? (nativeBudget?.band.railRows ?? 0) > 0
|
|
2579
2939
|
: fullscreenRailVisible)
|
|
2580
2940
|
? { todoActive: { done: state.todos.doneCount, total: state.todos.total } }
|
|
2581
|
-
: {}), ...(liveServices > 0 ? { servicesActive: { live: liveServices } } : {}), ...(state.fast?.live ? { fastActive: { inFlight: state.fast.inFlight } } : {}),
|
|
2941
|
+
: {}), ...(acceptanceActive ? { acceptanceActive } : {}), ...(liveServices > 0 ? { servicesActive: { live: liveServices } } : {}), ...(state.fast?.live ? { fastActive: { inFlight: state.fast.inFlight } } : {}),
|
|
2582
2942
|
// Manual compaction starts before its next snapshot is published.
|
|
2583
2943
|
compactionActive: { inFlight: statusInput.compacting ?? false }, ...(controller.isCompactionEnabled()
|
|
2584
2944
|
? {
|