@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
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,10 +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';
|
|
50
|
-
import { isLiveTeamCard, rosterCounts } from './team-status.js';
|
|
51
|
+
import { isLiveTeamCard, lastTeamEntry, rosterCounts } from './team-status.js';
|
|
51
52
|
import { advanceBudget, decideFollowThrough, emptyBudget, } from '../todo/follow-through.js';
|
|
52
53
|
import { useTerminalSize } from './layout/useTerminalSize.js';
|
|
53
54
|
import { useHeightStore } from './use-height-store.js';
|
|
@@ -64,6 +65,13 @@ import { OverlayFrame, overlayPageSize } from './layout/OverlayFrame.js';
|
|
|
64
65
|
import { helpRows } from './overlays/HelpOverlay.js';
|
|
65
66
|
import { ModelPicker } from './overlays/ModelPicker.js';
|
|
66
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';
|
|
67
75
|
import { INACTIVE_KIT } from '../config/kit-resolution.js';
|
|
68
76
|
import { kitApplyMessage, kitNoteMessages } from '../agent/kit-apply.js';
|
|
69
77
|
import { createModelProbe } from '../agent/model-probe.js';
|
|
@@ -78,6 +86,7 @@ import { MODE_LABEL, MODE_TOGGLE_KEYS, nextMode } from '../agent/agent-mode.js';
|
|
|
78
86
|
import { createClipboardCoordinator } from './clipboard-task.js';
|
|
79
87
|
import { QueuePanel } from './QueuePanel.js';
|
|
80
88
|
import { QueueOverlay } from './overlays/QueueOverlay.js';
|
|
89
|
+
import { AgentsBoardOverlay } from './overlays/AgentsBoardOverlay.js';
|
|
81
90
|
import { buildQueueLayout } from './layout/queue-layout.js';
|
|
82
91
|
import { interactionCopy } from './interaction-copy.js';
|
|
83
92
|
import { createRunMetrics, updateRunMetrics, freezeRunMetrics, projectRunMetrics } from './run-metrics.js';
|
|
@@ -142,6 +151,25 @@ function seedViewState(seed) {
|
|
|
142
151
|
? viewReducer(base, { type: 'restoreEntries', entries: seed.entries })
|
|
143
152
|
: base;
|
|
144
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
|
+
}
|
|
145
173
|
export function App({ controller, version, initialOverlay, initialPrompt, confirmBridge, humanInputBridge, mouseSource, vtInputWarning, updateBridge, pasteBridge, terminal, scrollMode = 'app', scrollback, initialEntries, session, sessionBridge, sessionNotice, }) {
|
|
146
174
|
const native = scrollMode === 'native' && scrollback !== undefined;
|
|
147
175
|
/** Set when the native writer swallowed repeated tall frames; cleared by a resize. */
|
|
@@ -157,6 +185,19 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
157
185
|
// nonce render (the async clear lands a frame later - see the timing
|
|
158
186
|
// invariant beside the ladder itself).
|
|
159
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);
|
|
160
201
|
const [draftClearedByCtrlC, setDraftClearedByCtrlC] = useState(false);
|
|
161
202
|
const [popupRows, setPopupRows] = useState(0);
|
|
162
203
|
const [statusExpanded, setStatusExpanded] = useState(false);
|
|
@@ -620,8 +661,13 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
620
661
|
if (controller.isRunning() || controller.isCompactionBusy?.())
|
|
621
662
|
return;
|
|
622
663
|
const live = controller.getTodoSnapshot();
|
|
623
|
-
|
|
624
|
-
|
|
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;
|
|
625
671
|
budgetRef.current = { ...budgetRef.current, used: budgetRef.current.used + 1 };
|
|
626
672
|
autoContinuationRef.current = true;
|
|
627
673
|
// COUNTS ONLY, NEVER ITEM TEXT (D-19), and only at the fire point —
|
|
@@ -631,6 +677,8 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
631
677
|
noProgressStreak: budgetRef.current.noProgressStreak,
|
|
632
678
|
total: live.total,
|
|
633
679
|
done: live.doneCount,
|
|
680
|
+
accTotal: live.acceptance?.total ?? 0,
|
|
681
|
+
accChecked: live.acceptance?.checkedCount ?? 0,
|
|
634
682
|
mode: 'auto',
|
|
635
683
|
});
|
|
636
684
|
submitRef.current(decision.message, { userInitiated: false, todoPolicy: 'continue' });
|
|
@@ -1045,32 +1093,45 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1045
1093
|
// the events must never be shown advice about a mode not in effect (§6.1).
|
|
1046
1094
|
// `cli.tsx::wantMouse` now folds the platform check into the same expression,
|
|
1047
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.
|
|
1048
1099
|
useStartupNotices(dispatch, {
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
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
|
+
});
|
|
1074
1135
|
// --- Exit snapshot: the only way state reaches cli.tsx (§4.4). ----------
|
|
1075
1136
|
useEffect(() => {
|
|
1076
1137
|
const merged = mergePendingEntries(state.entries, state.pendingSteering);
|
|
@@ -1206,6 +1267,102 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1206
1267
|
// --- Helpers -----------------------------------------------------------
|
|
1207
1268
|
const notify = (level, text) => dispatch({ type: 'notice', level, text });
|
|
1208
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(); }, []);
|
|
1209
1366
|
// THE BRANCH / `--save` NOTICE (D-13). Once, on mount, and only when the
|
|
1210
1367
|
// caller supplied one: "the session you branched from is unchanged" is the
|
|
1211
1368
|
// sentence that makes `--resume` safe to reach for, and it belongs in the
|
|
@@ -1524,6 +1681,16 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1524
1681
|
}
|
|
1525
1682
|
dispatch(action);
|
|
1526
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
|
+
},
|
|
1527
1694
|
setOverlay: (overlay) => dispatch({ type: 'setOverlay', overlay }),
|
|
1528
1695
|
notify,
|
|
1529
1696
|
toast,
|
|
@@ -1685,6 +1852,11 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
1685
1852
|
notify('error', `Command failed: ${redactText(reason, getSecrets())}\nInput: ${safe}`);
|
|
1686
1853
|
}
|
|
1687
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();
|
|
1688
1860
|
if (switching) {
|
|
1689
1861
|
switchingConversation.current = false;
|
|
1690
1862
|
drainCompactionQueue.current();
|
|
@@ -2004,6 +2176,53 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2004
2176
|
setStatusExpanded(value => !value);
|
|
2005
2177
|
return;
|
|
2006
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
|
+
}
|
|
2007
2226
|
// Ctrl+K: the Agent-kit picker (agent-model-kits D-15 / §4.7).
|
|
2008
2227
|
//
|
|
2009
2228
|
// NOT AN INPUT FRAME, unlike Ctrl+I below. Ctrl+I collides with the Tab byte,
|
|
@@ -2060,7 +2279,14 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2060
2279
|
// Modes A only. `model` / `confirm` manage their own keys; registering an
|
|
2061
2280
|
// arrow handler for them would double every keypress (R-12).
|
|
2062
2281
|
const controlled = overlay === 'help' || overlay === 'settings' || overlay === 'plan'
|
|
2063
|
-
|| overlay === 'queue' || overlay === 'kit'
|
|
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';
|
|
2064
2290
|
if (key.pageUp) {
|
|
2065
2291
|
if (controlled)
|
|
2066
2292
|
setOverlayScroll((n) => Math.max(0, n - overlayPage));
|
|
@@ -2085,11 +2311,13 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2085
2311
|
// everything else falls through (AC-P2).
|
|
2086
2312
|
//
|
|
2087
2313
|
// TWO KEYS, ONE BRANCH (shift-tab-mode-toggle-still-dead-on-windows, C2).
|
|
2088
|
-
// `Ctrl+
|
|
2089
|
-
//
|
|
2090
|
-
//
|
|
2091
|
-
//
|
|
2092
|
-
|
|
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'))) {
|
|
2093
2321
|
if (stateRef.current.overlay)
|
|
2094
2322
|
return;
|
|
2095
2323
|
applyMode(nextMode(controller.getAgentMode()));
|
|
@@ -2428,7 +2656,10 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2428
2656
|
// ALWAYS TRUE (agent-model-kits §4.7 / RV-P1-2): the requirement is that
|
|
2429
2657
|
// `ctrl+G` reveals `ctrl+K`, and the user with an empty library needs that
|
|
2430
2658
|
// hint most - the picker itself carries the "create one in /settings" row.
|
|
2431
|
-
kitHint: true
|
|
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 }));
|
|
2432
2663
|
// The header and the opener are SLOTS OF THE SHELL, not printed bytes (D3/D4): at
|
|
2433
2664
|
// `H = rows - 1` a printed row is pushed off the screen by the very next frame, so
|
|
2434
2665
|
// `printBanner()` was a way of making the banner invisible. Neither is passed here.
|
|
@@ -2482,7 +2713,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2482
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,
|
|
2483
2714
|
// ONLY THE APPLY IS BLOCKED while work is in flight (§4.7): a list a
|
|
2484
2715
|
// user cannot even OPEN reads as a broken key.
|
|
2485
|
-
busy: controller.isModelSettingsBusy(), maxRows: overlayMaxRows, cols: viewCols, scrollOffset: overlayScroll, onScrollClamp: setOverlayScroll, theme: theme, caps: caps, onChoose: handleKitChoose, onClose: () => dispatch({ type: 'setOverlay', overlay: null }) })) : overlay === 'settings' ? (_jsx(SettingsScreen, { isActive: !tooSmall, initial: {
|
|
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: {
|
|
2486
2717
|
provider: cfg.provider,
|
|
2487
2718
|
model: cfg.model,
|
|
2488
2719
|
baseUrl: cfg.baseUrl ?? '',
|
|
@@ -2519,7 +2750,11 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2519
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 }),
|
|
2520
2751
|
// Read from `cfg` (render scope, re-read every frame), so `/retry off`
|
|
2521
2752
|
// while the screen is open shows the new value on the next paint.
|
|
2522
|
-
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;
|
|
2523
2758
|
const density = cfg.density;
|
|
2524
2759
|
const transcriptWindow = cfg.transcriptWindow ?? DEFAULT_TRANSCRIPT_WINDOW;
|
|
2525
2760
|
// The fixed status bar remains visible underneath every overlay and toast.
|
|
@@ -2554,6 +2789,16 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2554
2789
|
windowStart: scrollbackView.windowStart,
|
|
2555
2790
|
windowEntries: scrollbackView.tailEntries.length,
|
|
2556
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',
|
|
2557
2802
|
}
|
|
2558
2803
|
: null,
|
|
2559
2804
|
// A getter: the bridge's `frameReady` and geometry change outside React renders.
|
|
@@ -2602,6 +2847,16 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2602
2847
|
toast: latestToast ? { text: latestToast.text,
|
|
2603
2848
|
level: latestToast.level === 'success' ? 'info' : latestToast.level } : undefined,
|
|
2604
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
|
+
});
|
|
2605
2860
|
const statusInput = {
|
|
2606
2861
|
columns: viewCols, phase: state.runPhase, pendingCount: state.pendingSteering.length,
|
|
2607
2862
|
context: state.context, usageTotal: state.usageTotal, thinkingLevel: cfg.thinkingLevel,
|
|
@@ -2622,6 +2877,9 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2622
2877
|
? { running: rosterCounts(state.team.runs).running, total: state.team.runs.length }
|
|
2623
2878
|
: undefined,
|
|
2624
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 } : {}),
|
|
2625
2883
|
fastActive: state.fast?.live ? { inFlight: state.fast.inFlight } : undefined,
|
|
2626
2884
|
...feedbackProjection,
|
|
2627
2885
|
};
|
|
@@ -2633,7 +2891,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2633
2891
|
hintsEnabled: cfg.hints, modeToggleKey,
|
|
2634
2892
|
interruptPhase: interruptGesture.current.phase,
|
|
2635
2893
|
updateAvailable: !!updateSnapshot && shouldRenderUpdateLine(updateSnapshot) } });
|
|
2636
|
-
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' }) }));
|
|
2637
2895
|
const team = state.team ? (_jsx(TeamPanel, { snapshot: state.team, layout: teamLayout, rows: rows, cols: contentCols, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2638
2896
|
// --- Native panel band (release-gate-and-native-panels §6.3/§6.4) -------------
|
|
2639
2897
|
//
|
|
@@ -2644,12 +2902,18 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2644
2902
|
// panel that mounts on one value and lays out against another renders nothing and
|
|
2645
2903
|
// reports nothing (TodoPanel's own I-10 / P1-3 note).
|
|
2646
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;
|
|
2647
2909
|
const nativeRail = state.todos && nativeBand && nativeBand.railRows >= TODO_LIMITS.panelMinRows
|
|
2648
2910
|
? (_jsx(TodoPanel, { snapshot: state.todos, width: nativeBand.railCols, rows: nativeBand.railRows, running: running, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2649
|
-
// `cols` is the band
|
|
2650
|
-
//
|
|
2651
|
-
//
|
|
2652
|
-
|
|
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;
|
|
2653
2917
|
const rail = fullscreenRailVisible && state.todos ? (_jsx(TodoPanel, { snapshot: state.todos, width: railWidth, rows: railRows, running: running, reducedMotion: viewReducedMotion, theme: theme, caps: caps })) : null;
|
|
2654
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 })] }));
|
|
2655
2919
|
const detailsNode = _jsx(BottomStatusRow, { plan: details, columns: viewCols, theme: theme });
|
|
@@ -2674,7 +2938,7 @@ export function App({ controller, version, initialOverlay, initialPrompt, confir
|
|
|
2674
2938
|
? (nativeBudget?.band.railRows ?? 0) > 0
|
|
2675
2939
|
: fullscreenRailVisible)
|
|
2676
2940
|
? { todoActive: { done: state.todos.doneCount, total: state.todos.total } }
|
|
2677
|
-
: {}), ...(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 } } : {}),
|
|
2678
2942
|
// Manual compaction starts before its next snapshot is published.
|
|
2679
2943
|
compactionActive: { inFlight: statusInput.compacting ?? false }, ...(controller.isCompactionEnabled()
|
|
2680
2944
|
? {
|
package/dist/ui/Composer.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { Theme } from './theme.js';
|
|
|
5
5
|
import type { TermCapabilities } from './capabilities.js';
|
|
6
6
|
import { pickGlyphs } from './glyphs.js';
|
|
7
7
|
import type { ComposerSubmitResult } from './composer-input.js';
|
|
8
|
-
import { type CommandOption } from './PromptInput.js';
|
|
8
|
+
import { type CommandOption, type DraftFill } from './PromptInput.js';
|
|
9
9
|
import type { RunActivity } from './run-status-row.js';
|
|
10
10
|
import { type AgentMode } from '../agent/agent-mode.js';
|
|
11
11
|
/** Legacy caller shape retained for source compatibility; Composer no longer mounts it. */
|
|
@@ -77,6 +77,10 @@ export interface ComposerProps {
|
|
|
77
77
|
* PromptInput unchanged.
|
|
78
78
|
*/
|
|
79
79
|
draftClearNonce?: number;
|
|
80
|
+
/** Downward half of the fill channel; forwarded to `PromptInput` unchanged. */
|
|
81
|
+
draftFill?: DraftFill;
|
|
82
|
+
/** `prompts.insertMode`, forwarded unchanged. */
|
|
83
|
+
insertMode?: 'insert' | 'replace';
|
|
80
84
|
/** Paste refusals from `PromptInput`, routed to `App`'s notice dispatch. */
|
|
81
85
|
onNotice?: (level: 'warn' | 'error', text: string) => void;
|
|
82
86
|
theme: Theme;
|
|
@@ -101,5 +105,5 @@ declare function hintText(opts: {
|
|
|
101
105
|
}): string;
|
|
102
106
|
/** Compatibility export for existing hint contract tests. */
|
|
103
107
|
export declare const hintTextForTest: typeof hintText;
|
|
104
|
-
export declare function Composer({ deleteDisambiguated, onCompletionContextChange, cols, terminalRows, statusExpanded, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, agentMode, popupMaxRows, popupMaxHeight, onPopupRowsChange, onDraftRows, onDraftPresence, draftClearNonce, onNotice, theme, caps, onSubmit, onHelp, }: ComposerProps): React.ReactElement;
|
|
108
|
+
export declare function Composer({ deleteDisambiguated, onCompletionContextChange, cols, terminalRows, statusExpanded, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, agentMode, popupMaxRows, popupMaxHeight, onPopupRowsChange, onDraftRows, onDraftPresence, draftClearNonce, draftFill, insertMode, onNotice, theme, caps, onSubmit, onHelp, }: ComposerProps): React.ReactElement;
|
|
105
109
|
export {};
|
package/dist/ui/Composer.js
CHANGED
|
@@ -36,7 +36,7 @@ function hintText(opts) {
|
|
|
36
36
|
}
|
|
37
37
|
/** Compatibility export for existing hint contract tests. */
|
|
38
38
|
export const hintTextForTest = hintText;
|
|
39
|
-
export function Composer({ deleteDisambiguated, onCompletionContextChange, cols, terminalRows, statusExpanded, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, agentMode, popupMaxRows, popupMaxHeight, onPopupRowsChange, onDraftRows, onDraftPresence, draftClearNonce, onNotice, theme, caps, onSubmit, onHelp, }) {
|
|
39
|
+
export function Composer({ deleteDisambiguated, onCompletionContextChange, cols, terminalRows, statusExpanded, cursorVisible, onInteraction, measureRef, isActive, reducedMotion, onEscape, onEscapeDismiss, running, history, commands, cwd, agentMode, popupMaxRows, popupMaxHeight, onPopupRowsChange, onDraftRows, onDraftPresence, draftClearNonce, draftFill, insertMode, onNotice, theme, caps, onSubmit, onHelp, }) {
|
|
40
40
|
const [hasDraft, setHasDraft] = useState(false);
|
|
41
41
|
const onDraftChange = useCallback((next) => {
|
|
42
42
|
setHasDraft(next.hasDraft);
|
|
@@ -57,5 +57,5 @@ export function Composer({ deleteDisambiguated, onCompletionContextChange, cols,
|
|
|
57
57
|
: hasDraft
|
|
58
58
|
? theme.focusBorder ?? theme.primary
|
|
59
59
|
: theme.idleBorder ?? theme.border;
|
|
60
|
-
return (_jsx(Box, { ref: measureRef, width: cols, flexDirection: "column", flexShrink: 0, children: _jsx(PromptInput, { deleteDisambiguated: deleteDisambiguated, onCompletionContextChange: onCompletionContextChange, cols: cols, terminalRows: terminalRows, statusExpanded: statusExpanded, cursorVisible: cursorVisible, onInteraction: onInteraction, isActive: isActive, reducedMotion: reducedMotion, onEscape: onEscape, onEscapeDismiss: onEscapeDismiss, running: running, history: history, commands: commands, cwd: cwd, theme: theme, caps: caps, popupMaxRows: popupMaxRows, popupMaxHeight: popupMaxHeight, onPopupRowsChange: onPopupRowsChange, onSubmit: onSubmit, onHelp: onHelp, agentMode: agentMode, borderColor: borderColor, onDraftChange: onDraftChange, draftClearNonce: draftClearNonce, onNotice: onNotice }) }));
|
|
60
|
+
return (_jsx(Box, { ref: measureRef, width: cols, flexDirection: "column", flexShrink: 0, children: _jsx(PromptInput, { deleteDisambiguated: deleteDisambiguated, onCompletionContextChange: onCompletionContextChange, cols: cols, terminalRows: terminalRows, statusExpanded: statusExpanded, cursorVisible: cursorVisible, onInteraction: onInteraction, isActive: isActive, reducedMotion: reducedMotion, onEscape: onEscape, onEscapeDismiss: onEscapeDismiss, running: running, history: history, commands: commands, cwd: cwd, theme: theme, caps: caps, popupMaxRows: popupMaxRows, popupMaxHeight: popupMaxHeight, onPopupRowsChange: onPopupRowsChange, onSubmit: onSubmit, onHelp: onHelp, agentMode: agentMode, borderColor: borderColor, onDraftChange: onDraftChange, draftClearNonce: draftClearNonce, draftFill: draftFill, insertMode: insertMode, onNotice: onNotice }) }));
|
|
61
61
|
}
|
package/dist/ui/Header.d.ts
CHANGED
|
@@ -23,6 +23,14 @@ interface HeaderProps {
|
|
|
23
23
|
* forgets the prop silently teaching nothing.
|
|
24
24
|
*/
|
|
25
25
|
kitHint?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Whether the collapsed row teaches `ctrl+P` (quick-prompt-library D-15).
|
|
28
|
+
*
|
|
29
|
+
* DEFAULTS TO TRUE for the same reason `kitHint` does: the requirement is that the
|
|
30
|
+
* key be visible, and a default of `false` would leave the one caller that forgets
|
|
31
|
+
* the prop silently teaching nothing. `cfg.prompts.hint` is what turns it off.
|
|
32
|
+
*/
|
|
33
|
+
promptsHint?: boolean;
|
|
26
34
|
}
|
|
27
|
-
export declare function Header({ cwd, provider, model, hasKey, variant, theme, caps, columns, statusExpanded, scrolledLines, overlayOpen, kitHint }: HeaderProps): React.ReactElement;
|
|
35
|
+
export declare function Header({ cwd, provider, model, hasKey, variant, theme, caps, columns, statusExpanded, scrolledLines, overlayOpen, kitHint, promptsHint }: HeaderProps): React.ReactElement;
|
|
28
36
|
export {};
|
package/dist/ui/Header.js
CHANGED
|
@@ -3,7 +3,7 @@ import { basename } from 'node:path';
|
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import { Logo } from './Logo.js';
|
|
5
5
|
import { planHeaderHints, HEADER_HINT_LIMITS } from './layout/header-hints.js';
|
|
6
|
-
export function Header({ cwd, provider, model, hasKey, variant, theme, caps, columns = 80, statusExpanded = false, scrolledLines = 0, overlayOpen = false, kitHint = true }) {
|
|
6
|
+
export function Header({ cwd, provider, model, hasKey, variant, theme, caps, columns = 80, statusExpanded = false, scrolledLines = 0, overlayOpen = false, kitHint = true, promptsHint = true }) {
|
|
7
7
|
// The right-hand hints are planned, not assembled (tui-branch-terminal-shortcut
|
|
8
8
|
// 3.9 / D-11). `plan.text` is exactly `plan.cells` wide and already carries the
|
|
9
9
|
// scroll chip's own trailing spacing, so this component can no longer clip a
|
|
@@ -14,7 +14,7 @@ export function Header({ cwd, provider, model, hasKey, variant, theme, caps, col
|
|
|
14
14
|
// passes `true` always, because the requirement is that `ctrl+G` reveals the
|
|
15
15
|
// key - and the user with no kits needs that hint most.
|
|
16
16
|
const plan = planHeaderHints({
|
|
17
|
-
columns, statusExpanded, overlayOpen, scrolledLines, kitHint,
|
|
17
|
+
columns, statusExpanded, overlayOpen, scrolledLines, kitHint, promptsHint,
|
|
18
18
|
});
|
|
19
19
|
// THE TWO HALVES ARE SEPARATED BY CELLS, NOT BY LUCK (defect 12). The left half
|
|
20
20
|
// is `wrap="truncate"`, which draws its ellipsis in the LAST cell it owns; with
|