cvc-tui 0.4.4 → 0.4.7
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/NOTICES.md +13 -0
- package/dist/app/completion.js +102 -0
- package/dist/app/createGatewayEventHandler.js +508 -0
- package/dist/app/createSlashHandler.js +101 -0
- package/dist/app/delegationStore.js +51 -0
- package/dist/app/gatewayContext.js +17 -0
- package/dist/app/historyStore.js +123 -0
- package/dist/app/inputBuffer.js +120 -0
- package/dist/app/inputSelectionStore.js +8 -0
- package/dist/app/inputStore.js +28 -0
- package/dist/app/interfaces.js +6 -0
- package/dist/app/overlayStore.js +40 -0
- package/dist/app/promptStore.js +44 -0
- package/dist/app/queueStore.js +25 -0
- package/dist/app/scroll.js +44 -0
- package/dist/app/setupHandoff.js +28 -0
- package/dist/app/slash/commands/core.js +479 -0
- package/dist/app/slash/commands/debug.js +44 -0
- package/dist/app/slash/commands/ops.js +512 -0
- package/dist/app/slash/commands/session.js +431 -0
- package/dist/app/slash/commands/setup.js +20 -0
- package/dist/app/slash/commands/toggles.js +40 -0
- package/dist/app/slash/registry.js +18 -0
- package/dist/app/slash/types.js +1 -0
- package/dist/app/spawnHistoryStore.js +105 -0
- package/dist/app/turnController.js +650 -0
- package/dist/app/turnStore.js +48 -0
- package/dist/app/uiStore.js +36 -0
- package/dist/app/useComposerState.js +265 -0
- package/dist/app/useConfigSync.js +144 -0
- package/dist/app/useInputHandlers.js +403 -0
- package/dist/app/useLongRunToolCharms.js +50 -0
- package/dist/app/useMainApp.js +638 -0
- package/dist/app/useSessionLifecycle.js +175 -0
- package/dist/app/useSubmission.js +287 -0
- package/dist/app.js +15 -0
- package/dist/banner.js +63 -0
- package/dist/components/agentsOverlay.js +474 -0
- package/dist/components/appChrome.js +252 -0
- package/dist/components/appLayout.js +122 -0
- package/dist/components/appOverlays.js +65 -0
- package/dist/components/branding.js +97 -0
- package/dist/components/fpsOverlay.js +22 -0
- package/dist/components/helpHint.js +21 -0
- package/dist/components/markdown.js +501 -0
- package/dist/components/maskedPrompt.js +12 -0
- package/dist/components/messageLine.js +82 -0
- package/dist/components/modelPicker.js +254 -0
- package/dist/components/overlayControls.js +30 -0
- package/dist/components/overlays/confirmPrompt.js +25 -0
- package/dist/components/overlays/helpOverlay.js +76 -0
- package/dist/components/overlays/historySearch.js +49 -0
- package/dist/components/overlays/modelPicker.js +60 -0
- package/dist/components/overlays/overlayUtils.js +19 -0
- package/dist/components/overlays/secretPrompt.js +36 -0
- package/dist/components/overlays/sessionPicker.js +93 -0
- package/dist/components/overlays/skillsHub.js +71 -0
- package/dist/components/prompts.js +95 -0
- package/dist/components/queuedMessages.js +24 -0
- package/dist/components/sessionPicker.js +130 -0
- package/dist/components/skillsHub.js +165 -0
- package/dist/components/streamingAssistant.js +35 -0
- package/dist/components/streamingMarkdown.js +144 -0
- package/dist/components/textInput.js +794 -0
- package/dist/components/themed.js +12 -0
- package/dist/components/thinking.js +496 -0
- package/dist/components/todoPanel.js +40 -0
- package/dist/components/transcript.js +22 -0
- package/dist/config/env.js +18 -0
- package/dist/config/limits.js +22 -0
- package/dist/config/timing.js +25 -0
- package/dist/content/charms.js +5 -0
- package/dist/content/faces.js +21 -0
- package/dist/content/fortunes.js +29 -0
- package/dist/content/hotkeys.js +38 -0
- package/dist/content/placeholders.js +15 -0
- package/dist/content/setup.js +14 -0
- package/dist/content/verbs.js +41 -0
- package/dist/domain/details.js +53 -0
- package/dist/domain/messages.js +63 -0
- package/dist/domain/paths.js +16 -0
- package/dist/domain/providers.js +11 -0
- package/dist/domain/roles.js +6 -0
- package/dist/domain/slash.js +11 -0
- package/dist/domain/usage.js +1 -0
- package/dist/domain/viewport.js +33 -0
- package/dist/entry.js +64 -70236
- package/dist/gateway/client.js +312 -0
- package/dist/gatewayClient.js +574 -0
- package/dist/gatewayTypes.js +1 -0
- package/dist/hooks/useCompletion.js +86 -0
- package/dist/hooks/useGitBranch.js +58 -0
- package/dist/hooks/useInputHistory.js +12 -0
- package/dist/hooks/useQueue.js +57 -0
- package/dist/hooks/useVirtualHistory.js +401 -0
- package/dist/lib/circularBuffer.js +43 -0
- package/dist/lib/clipboard.js +126 -0
- package/dist/lib/editor.js +41 -0
- package/dist/lib/editor.test.js +58 -0
- package/dist/lib/emoji.js +49 -0
- package/dist/lib/externalCli.js +11 -0
- package/dist/lib/forceTruecolor.js +26 -0
- package/dist/lib/fpsStore.js +36 -0
- package/dist/lib/gracefulExit.js +29 -0
- package/dist/lib/history.js +69 -0
- package/dist/lib/inputMetrics.js +143 -0
- package/dist/lib/liveProgress.js +51 -0
- package/dist/lib/liveProgress.test.js +89 -0
- package/dist/lib/localSessionInfo.js +116 -0
- package/dist/lib/mathUnicode.js +685 -0
- package/dist/lib/memory.js +123 -0
- package/dist/lib/memoryMonitor.js +76 -0
- package/dist/lib/messages.js +3 -0
- package/dist/lib/messages.test.js +25 -0
- package/dist/lib/osc52.js +53 -0
- package/dist/lib/perfPane.js +94 -0
- package/dist/lib/platform.js +312 -0
- package/dist/lib/precisionWheel.js +25 -0
- package/dist/lib/react-devtools-stub.js +12 -0
- package/dist/lib/reasoning.js +39 -0
- package/dist/lib/rpc.js +26 -0
- package/dist/lib/subagentTree.js +287 -0
- package/dist/lib/syntax.js +89 -0
- package/dist/lib/terminalModes.js +46 -0
- package/dist/lib/terminalParity.js +48 -0
- package/dist/lib/terminalSetup.js +321 -0
- package/dist/lib/text.js +203 -0
- package/dist/lib/text.test.js +18 -0
- package/dist/lib/todo.js +2 -0
- package/dist/lib/todo.test.js +22 -0
- package/dist/lib/viewportStore.js +82 -0
- package/dist/lib/virtualHeights.js +61 -0
- package/dist/lib/wheelAccel.js +143 -0
- package/dist/protocol/interpolation.js +4 -0
- package/dist/protocol/paste.js +3 -0
- package/dist/theme.js +398 -0
- package/dist/types.js +1 -0
- package/dist/vendor/cvc-ink/dist/entry-exports.js +52737 -0
- package/dist/vendor/cvc-ink/index.js +1 -0
- package/dist/vendor/cvc-ink/package.json +9 -0
- package/dist/vendor/cvc-ink/text-input.js +1 -0
- package/package.json +9 -9
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Ported from CVC Agent (https://github.com/NousResearch/cvc)
|
|
4
|
+
// Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
|
|
5
|
+
import { attachedImageNotice, introMsg, toTranscriptMessages } from '../../../domain/messages.js';
|
|
6
|
+
import { TUI_SESSION_MODEL_FLAG } from '../../../domain/slash.js';
|
|
7
|
+
import { formatVoiceRecordKey, parseVoiceRecordKey } from '../../../lib/platform.js';
|
|
8
|
+
import { fmtK } from '../../../lib/text.js';
|
|
9
|
+
import { DEFAULT_INDICATOR_STYLE, INDICATOR_STYLES } from '../../interfaces.js';
|
|
10
|
+
import { patchOverlayState } from '../../overlayStore.js';
|
|
11
|
+
import { patchUiState } from '../../uiStore.js';
|
|
12
|
+
const TUI_SESSION_MODEL_RE = new RegExp(`(?:^|\\s)${TUI_SESSION_MODEL_FLAG}(?:\\s|$)`);
|
|
13
|
+
const TUI_SESSION_STRIP_RE = new RegExp(`\\s*${TUI_SESSION_MODEL_FLAG}\\b\\s*`, 'g');
|
|
14
|
+
const stripTuiSessionFlag = (trimmed) => trimmed.replace(TUI_SESSION_STRIP_RE, ' ').replace(/\s+/g, ' ').trim();
|
|
15
|
+
const modelValueForConfigSet = (arg) => {
|
|
16
|
+
const trimmed = arg.trim();
|
|
17
|
+
if (!trimmed) {
|
|
18
|
+
return trimmed;
|
|
19
|
+
}
|
|
20
|
+
if (TUI_SESSION_MODEL_RE.test(trimmed)) {
|
|
21
|
+
return stripTuiSessionFlag(trimmed);
|
|
22
|
+
}
|
|
23
|
+
return trimmed;
|
|
24
|
+
};
|
|
25
|
+
export const sessionCommands = [
|
|
26
|
+
{
|
|
27
|
+
aliases: ['bg', 'btw'],
|
|
28
|
+
help: 'launch a background prompt',
|
|
29
|
+
name: 'background',
|
|
30
|
+
run: (arg, ctx) => {
|
|
31
|
+
if (!arg) {
|
|
32
|
+
return ctx.transcript.sys('/background <prompt>');
|
|
33
|
+
}
|
|
34
|
+
ctx.gateway.rpc('prompt.background', { session_id: ctx.sid, text: arg }).then(ctx.guarded(r => {
|
|
35
|
+
if (!r.task_id) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
patchUiState(state => ({ ...state, bgTasks: new Set(state.bgTasks).add(r.task_id) }));
|
|
39
|
+
ctx.transcript.sys(`bg ${r.task_id} started`);
|
|
40
|
+
}));
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
help: 'change or show model',
|
|
45
|
+
name: 'model',
|
|
46
|
+
run: (arg, ctx) => {
|
|
47
|
+
if (ctx.session.guardBusySessionSwitch('change models')) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!arg.trim()) {
|
|
51
|
+
return patchOverlayState({ modelPicker: true });
|
|
52
|
+
}
|
|
53
|
+
ctx.gateway
|
|
54
|
+
.rpc('config.set', { key: 'model', session_id: ctx.sid, value: modelValueForConfigSet(arg) })
|
|
55
|
+
.then(ctx.guarded(r => {
|
|
56
|
+
if (!r.value) {
|
|
57
|
+
return ctx.transcript.sys('error: invalid response: model switch');
|
|
58
|
+
}
|
|
59
|
+
ctx.transcript.sys(`model → ${r.value}`);
|
|
60
|
+
ctx.local.maybeWarn(r);
|
|
61
|
+
patchUiState(state => ({
|
|
62
|
+
...state,
|
|
63
|
+
info: state.info ? { ...state.info, model: r.value } : { model: r.value, skills: {}, tools: {} }
|
|
64
|
+
}));
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
help: 'browse and resume previous sessions',
|
|
70
|
+
name: 'sessions',
|
|
71
|
+
run: (arg, ctx) => {
|
|
72
|
+
if (ctx.session.guardBusySessionSwitch('switch sessions')) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!arg.trim()) {
|
|
76
|
+
return patchOverlayState({ picker: true });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
help: 'attach an image',
|
|
82
|
+
name: 'image',
|
|
83
|
+
run: (arg, ctx) => {
|
|
84
|
+
ctx.gateway.rpc('image.attach', { path: arg, session_id: ctx.sid }).then(ctx.guarded(r => {
|
|
85
|
+
ctx.transcript.sys(attachedImageNotice(r));
|
|
86
|
+
if (r.remainder) {
|
|
87
|
+
ctx.composer.setInput(r.remainder);
|
|
88
|
+
}
|
|
89
|
+
}));
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
help: 'switch personality for this session',
|
|
94
|
+
name: 'personality',
|
|
95
|
+
run: (arg, ctx) => {
|
|
96
|
+
if (!arg) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
ctx.gateway.rpc('config.set', { key: 'personality', session_id: ctx.sid, value: arg }).then(ctx.guarded(r => {
|
|
100
|
+
if (r.history_reset) {
|
|
101
|
+
ctx.session.resetVisibleHistory(r.info ?? null);
|
|
102
|
+
}
|
|
103
|
+
ctx.transcript.sys(`personality: ${r.value || 'default'}${r.history_reset ? ' · transcript cleared' : ''}`);
|
|
104
|
+
ctx.local.maybeWarn(r);
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
help: 'compress transcript',
|
|
110
|
+
name: 'compress',
|
|
111
|
+
run: (arg, ctx) => {
|
|
112
|
+
ctx.gateway
|
|
113
|
+
.rpc('session.compress', {
|
|
114
|
+
session_id: ctx.sid,
|
|
115
|
+
...(arg ? { focus_topic: arg } : {})
|
|
116
|
+
})
|
|
117
|
+
.then(ctx.guarded(r => {
|
|
118
|
+
if (Array.isArray(r.messages)) {
|
|
119
|
+
const rows = toTranscriptMessages(r.messages);
|
|
120
|
+
ctx.transcript.setHistoryItems(r.info ? [introMsg(r.info), ...rows] : rows);
|
|
121
|
+
}
|
|
122
|
+
if (r.info) {
|
|
123
|
+
patchUiState({ info: r.info });
|
|
124
|
+
}
|
|
125
|
+
if (r.usage) {
|
|
126
|
+
patchUiState(state => ({ ...state, usage: { ...state.usage, ...r.usage } }));
|
|
127
|
+
}
|
|
128
|
+
if (r.summary?.headline) {
|
|
129
|
+
const prefix = r.summary.noop ? '' : '✓ ';
|
|
130
|
+
ctx.transcript.sys(`${prefix}${r.summary.headline}`);
|
|
131
|
+
if (r.summary.token_line) {
|
|
132
|
+
ctx.transcript.sys(` ${r.summary.token_line}`);
|
|
133
|
+
}
|
|
134
|
+
if (r.summary.note) {
|
|
135
|
+
ctx.transcript.sys(` ${r.summary.note}`);
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if ((r.removed ?? 0) <= 0) {
|
|
140
|
+
return ctx.transcript.sys('nothing to compress');
|
|
141
|
+
}
|
|
142
|
+
ctx.transcript.sys(`compressed ${r.removed} messages${r.usage?.total ? ` · ${fmtK(r.usage.total)} tok` : ''}`);
|
|
143
|
+
}))
|
|
144
|
+
.catch(ctx.guardedErr);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
aliases: ['fork'],
|
|
149
|
+
help: 'branch the session',
|
|
150
|
+
name: 'branch',
|
|
151
|
+
run: (arg, ctx) => {
|
|
152
|
+
const prevSid = ctx.sid;
|
|
153
|
+
ctx.gateway.rpc('session.branch', { name: arg, session_id: ctx.sid }).then(ctx.guarded(r => {
|
|
154
|
+
if (!r.session_id) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
void ctx.session.closeSession(prevSid);
|
|
158
|
+
patchUiState({ sid: r.session_id });
|
|
159
|
+
ctx.session.setSessionStartedAt(Date.now());
|
|
160
|
+
ctx.transcript.setHistoryItems([]);
|
|
161
|
+
ctx.transcript.sys(`branched → ${r.title ?? ''}`);
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
help: 'voice mode: [on|off|tts|status]',
|
|
167
|
+
name: 'voice',
|
|
168
|
+
run: (arg, ctx) => {
|
|
169
|
+
const normalized = (arg ?? '').trim().toLowerCase();
|
|
170
|
+
const action = normalized === 'on' || normalized === 'off' || normalized === 'tts' || normalized === 'status'
|
|
171
|
+
? normalized
|
|
172
|
+
: 'status';
|
|
173
|
+
ctx.gateway.rpc('voice.toggle', { action }).then(ctx.guarded(r => {
|
|
174
|
+
ctx.voice.setVoiceEnabled(!!r.enabled);
|
|
175
|
+
// Render the configured record key (config.yaml ``voice.record_key``)
|
|
176
|
+
// instead of hardcoded "Ctrl+B" — the gateway response carries the
|
|
177
|
+
// current value so /voice status and /voice on stay in sync with
|
|
178
|
+
// both the CLI and the TUI's actual binding (#18994).
|
|
179
|
+
//
|
|
180
|
+
// Copilot review on #19835 caught that rendering from the fresh
|
|
181
|
+
// backend response WITHOUT updating the frontend ``voice.recordKey``
|
|
182
|
+
// state would skew display and binding between config-edit and
|
|
183
|
+
// the next ``mtime`` poll (~5s). Parse once, push into state so
|
|
184
|
+
// ``useInputHandlers()`` picks up the new binding immediately.
|
|
185
|
+
//
|
|
186
|
+
// Round-2 follow-up: only push state when the response actually
|
|
187
|
+
// carries ``record_key`` — otherwise an older gateway (or a future
|
|
188
|
+
// branch that forgets to include it) would clobber a custom user
|
|
189
|
+
// binding back to the default on every /voice invocation. The
|
|
190
|
+
// label still falls back to the documented default for display.
|
|
191
|
+
const parsed = r.record_key ? parseVoiceRecordKey(r.record_key) : undefined;
|
|
192
|
+
if (parsed) {
|
|
193
|
+
ctx.voice.setVoiceRecordKey(parsed);
|
|
194
|
+
}
|
|
195
|
+
const recordKeyLabel = formatVoiceRecordKey(parsed ?? parseVoiceRecordKey('ctrl+b'));
|
|
196
|
+
// Match CLI's _show_voice_status / _enable_voice_mode /
|
|
197
|
+
// _toggle_voice_tts output shape so users don't have to learn
|
|
198
|
+
// two vocabularies.
|
|
199
|
+
if (action === 'status') {
|
|
200
|
+
const mode = r.enabled ? 'ON' : 'OFF';
|
|
201
|
+
const tts = r.tts ? 'ON' : 'OFF';
|
|
202
|
+
ctx.transcript.sys('Voice Mode Status');
|
|
203
|
+
ctx.transcript.sys(` Mode: ${mode}`);
|
|
204
|
+
ctx.transcript.sys(` TTS: ${tts}`);
|
|
205
|
+
ctx.transcript.sys(` Record key: ${recordKeyLabel}`);
|
|
206
|
+
// CLI's "Requirements:" block — surfaces STT/audio setup issues
|
|
207
|
+
// so the user sees "STT provider: MISSING ..." instead of
|
|
208
|
+
// silently failing on every record-key press.
|
|
209
|
+
if (r.details) {
|
|
210
|
+
ctx.transcript.sys('');
|
|
211
|
+
ctx.transcript.sys(' Requirements:');
|
|
212
|
+
for (const line of r.details.split('\n')) {
|
|
213
|
+
if (line.trim()) {
|
|
214
|
+
ctx.transcript.sys(` ${line}`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (action === 'tts') {
|
|
221
|
+
ctx.transcript.sys(`Voice TTS ${r.tts ? 'enabled' : 'disabled'}.`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
// on/off — mirror cli.py:_enable_voice_mode's 3-line output
|
|
225
|
+
if (r.enabled) {
|
|
226
|
+
const tts = r.tts ? ' (TTS enabled)' : '';
|
|
227
|
+
ctx.transcript.sys(`Voice mode enabled${tts}`);
|
|
228
|
+
ctx.transcript.sys(` ${recordKeyLabel} to start/stop recording`);
|
|
229
|
+
ctx.transcript.sys(' /voice tts to toggle speech output');
|
|
230
|
+
ctx.transcript.sys(' /voice off to disable voice mode');
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
ctx.transcript.sys('Voice mode disabled.');
|
|
234
|
+
}
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
help: 'switch theme skin (fires skin.changed)',
|
|
240
|
+
name: 'skin',
|
|
241
|
+
run: (arg, ctx) => {
|
|
242
|
+
if (!arg) {
|
|
243
|
+
return ctx.gateway
|
|
244
|
+
.rpc('config.get', { key: 'skin' })
|
|
245
|
+
.then(ctx.guarded(r => ctx.transcript.sys(`skin: ${r.value || 'default'}`)));
|
|
246
|
+
}
|
|
247
|
+
ctx.gateway
|
|
248
|
+
.rpc('config.set', { key: 'skin', value: arg })
|
|
249
|
+
.then(ctx.guarded(r => r.value && ctx.transcript.sys(`skin → ${r.value}`)));
|
|
250
|
+
}
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
help: 'pick the busy indicator: kaomoji (default), emoji, unicode (braille), or ascii',
|
|
254
|
+
name: 'indicator',
|
|
255
|
+
usage: `/indicator [${INDICATOR_STYLES.join('|')}]`,
|
|
256
|
+
run: (arg, ctx) => {
|
|
257
|
+
const value = arg.trim().toLowerCase();
|
|
258
|
+
if (!value) {
|
|
259
|
+
return ctx.gateway
|
|
260
|
+
.rpc('config.get', { key: 'indicator' })
|
|
261
|
+
.then(ctx.guarded(r => ctx.transcript.sys(`indicator: ${r.value || DEFAULT_INDICATOR_STYLE}`)));
|
|
262
|
+
}
|
|
263
|
+
if (!INDICATOR_STYLES.includes(value)) {
|
|
264
|
+
return ctx.transcript.sys(`usage: /indicator [${INDICATOR_STYLES.join('|')}]`);
|
|
265
|
+
}
|
|
266
|
+
ctx.gateway.rpc('config.set', { key: 'indicator', value }).then(ctx.guarded(r => {
|
|
267
|
+
if (!r.value) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
// Hot-swap the running TUI immediately so the next render
|
|
271
|
+
// uses the new style without waiting for the 5s mtime poll
|
|
272
|
+
// to re-apply config.full.
|
|
273
|
+
patchUiState({ indicatorStyle: value });
|
|
274
|
+
ctx.transcript.sys(`indicator → ${r.value}`);
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
help: 'toggle yolo mode (per-session approvals)',
|
|
280
|
+
name: 'yolo',
|
|
281
|
+
run: (_arg, ctx) => {
|
|
282
|
+
ctx.gateway
|
|
283
|
+
.rpc('config.set', { key: 'yolo', session_id: ctx.sid })
|
|
284
|
+
.then(ctx.guarded(r => ctx.transcript.sys(`yolo ${r.value === '1' ? 'on' : 'off'}`)));
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
help: 'inspect or set reasoning effort (updates live agent)',
|
|
289
|
+
name: 'reasoning',
|
|
290
|
+
run: (arg, ctx) => {
|
|
291
|
+
if (!arg) {
|
|
292
|
+
return ctx.gateway
|
|
293
|
+
.rpc('config.get', { key: 'reasoning' })
|
|
294
|
+
.then(ctx.guarded(r => r.value && ctx.transcript.sys(`reasoning: ${r.value} · display ${r.display || 'hide'}`)));
|
|
295
|
+
}
|
|
296
|
+
ctx.gateway
|
|
297
|
+
.rpc('config.set', { key: 'reasoning', session_id: ctx.sid, value: arg })
|
|
298
|
+
.then(ctx.guarded(r => {
|
|
299
|
+
if (!r.value) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (r.value === 'hide') {
|
|
303
|
+
patchUiState(state => ({
|
|
304
|
+
...state,
|
|
305
|
+
sections: { ...state.sections, thinking: 'hidden' },
|
|
306
|
+
showReasoning: false
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
309
|
+
else if (r.value === 'show') {
|
|
310
|
+
patchUiState(state => ({
|
|
311
|
+
...state,
|
|
312
|
+
sections: { ...state.sections, thinking: 'expanded' },
|
|
313
|
+
showReasoning: true
|
|
314
|
+
}));
|
|
315
|
+
}
|
|
316
|
+
ctx.transcript.sys(`reasoning: ${r.value}`);
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
help: 'toggle fast mode [normal|fast|status|on|off|toggle]',
|
|
322
|
+
name: 'fast',
|
|
323
|
+
run: (arg, ctx) => {
|
|
324
|
+
const mode = arg.trim().toLowerCase();
|
|
325
|
+
const valid = new Set(['', 'status', 'normal', 'fast', 'on', 'off', 'toggle']);
|
|
326
|
+
if (!valid.has(mode)) {
|
|
327
|
+
return ctx.transcript.sys('usage: /fast [normal|fast|status|on|off|toggle]');
|
|
328
|
+
}
|
|
329
|
+
if (!mode || mode === 'status') {
|
|
330
|
+
return ctx.gateway
|
|
331
|
+
.rpc('config.get', { key: 'fast', session_id: ctx.sid })
|
|
332
|
+
.then(ctx.guarded(r => ctx.transcript.sys(`fast mode: ${r.value === 'fast' ? 'fast' : 'normal'}`)))
|
|
333
|
+
.catch(ctx.guardedErr);
|
|
334
|
+
}
|
|
335
|
+
ctx.gateway
|
|
336
|
+
.rpc('config.set', { key: 'fast', session_id: ctx.sid, value: mode })
|
|
337
|
+
.then(ctx.guarded(r => {
|
|
338
|
+
const next = r.value === 'fast' ? 'fast' : 'normal';
|
|
339
|
+
ctx.transcript.sys(`fast mode: ${next}`);
|
|
340
|
+
patchUiState(state => ({
|
|
341
|
+
...state,
|
|
342
|
+
info: state.info
|
|
343
|
+
? {
|
|
344
|
+
...state.info,
|
|
345
|
+
fast: next === 'fast',
|
|
346
|
+
service_tier: next === 'fast' ? 'priority' : ''
|
|
347
|
+
}
|
|
348
|
+
: state.info
|
|
349
|
+
}));
|
|
350
|
+
}))
|
|
351
|
+
.catch(ctx.guardedErr);
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
help: 'control busy enter mode [queue|steer|interrupt|status]',
|
|
356
|
+
name: 'busy',
|
|
357
|
+
run: (arg, ctx) => {
|
|
358
|
+
const mode = arg.trim().toLowerCase();
|
|
359
|
+
const valid = new Set(['', 'status', 'queue', 'steer', 'interrupt']);
|
|
360
|
+
if (!valid.has(mode)) {
|
|
361
|
+
return ctx.transcript.sys('usage: /busy [queue|steer|interrupt|status]');
|
|
362
|
+
}
|
|
363
|
+
if (!mode || mode === 'status') {
|
|
364
|
+
return ctx.gateway
|
|
365
|
+
.rpc('config.get', { key: 'busy' })
|
|
366
|
+
.then(ctx.guarded(r => {
|
|
367
|
+
const current = r.value || 'interrupt';
|
|
368
|
+
ctx.transcript.sys(`busy input mode: ${current}`);
|
|
369
|
+
}))
|
|
370
|
+
.catch(ctx.guardedErr);
|
|
371
|
+
}
|
|
372
|
+
ctx.gateway
|
|
373
|
+
.rpc('config.set', { key: 'busy', value: mode })
|
|
374
|
+
.then(ctx.guarded(r => {
|
|
375
|
+
const next = r.value || mode;
|
|
376
|
+
ctx.transcript.sys(`busy input mode: ${next}`);
|
|
377
|
+
}))
|
|
378
|
+
.catch(ctx.guardedErr);
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
help: 'cycle verbose tool-output mode (updates live agent)',
|
|
383
|
+
name: 'verbose',
|
|
384
|
+
run: (arg, ctx) => {
|
|
385
|
+
ctx.gateway
|
|
386
|
+
.rpc('config.set', { key: 'verbose', session_id: ctx.sid, value: arg || 'cycle' })
|
|
387
|
+
.then(ctx.guarded(r => r.value && ctx.transcript.sys(`verbose: ${r.value}`)));
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
help: 'session usage (live counts — worker sees zeros)',
|
|
392
|
+
name: 'usage',
|
|
393
|
+
run: (_arg, ctx) => {
|
|
394
|
+
ctx.gateway.rpc('session.usage', { session_id: ctx.sid }).then(r => {
|
|
395
|
+
if (ctx.stale()) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (r) {
|
|
399
|
+
patchUiState({
|
|
400
|
+
usage: { calls: r.calls ?? 0, input: r.input ?? 0, output: r.output ?? 0, total: r.total ?? 0 }
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
if (!r?.calls) {
|
|
404
|
+
return ctx.transcript.sys('no API calls yet');
|
|
405
|
+
}
|
|
406
|
+
const f = (v) => (v ?? 0).toLocaleString();
|
|
407
|
+
const cost = r.cost_usd != null ? `${r.cost_status === 'estimated' ? '~' : ''}$${r.cost_usd.toFixed(4)}` : null;
|
|
408
|
+
const rows = [
|
|
409
|
+
['Model', r.model ?? ''],
|
|
410
|
+
['Input tokens', f(r.input)],
|
|
411
|
+
['Cache read tokens', f(r.cache_read)],
|
|
412
|
+
['Cache write tokens', f(r.cache_write)],
|
|
413
|
+
['Output tokens', f(r.output)],
|
|
414
|
+
['Total tokens', f(r.total)],
|
|
415
|
+
['API calls', f(r.calls)]
|
|
416
|
+
];
|
|
417
|
+
if (cost) {
|
|
418
|
+
rows.push(['Cost', cost]);
|
|
419
|
+
}
|
|
420
|
+
const sections = [{ rows }];
|
|
421
|
+
if (r.context_max) {
|
|
422
|
+
sections.push({ text: `Context: ${f(r.context_used)} / ${f(r.context_max)} (${r.context_percent}%)` });
|
|
423
|
+
}
|
|
424
|
+
if (r.compressions) {
|
|
425
|
+
sections.push({ text: `Compressions: ${r.compressions}` });
|
|
426
|
+
}
|
|
427
|
+
ctx.transcript.panel('Usage', sections);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Ported from CVC Agent (https://github.com/NousResearch/cvc)
|
|
4
|
+
// Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
|
|
5
|
+
import { withInkSuspended } from '../../../vendor/cvc-ink/index.js';
|
|
6
|
+
import { launchHermesCommand } from '../../../lib/externalCli.js';
|
|
7
|
+
import { runExternalSetup } from '../../setupHandoff.js';
|
|
8
|
+
export const setupCommands = [
|
|
9
|
+
{
|
|
10
|
+
help: 'run full setup wizard (launches `hermes setup`)',
|
|
11
|
+
name: 'setup',
|
|
12
|
+
run: (arg, ctx) => void runExternalSetup({
|
|
13
|
+
args: ['setup', ...arg.split(/\s+/).filter(Boolean)],
|
|
14
|
+
ctx,
|
|
15
|
+
done: 'setup complete — starting session…',
|
|
16
|
+
launcher: launchHermesCommand,
|
|
17
|
+
suspend: withInkSuspended
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Ported from CVC Agent (https://github.com/NousResearch/cvc)
|
|
4
|
+
// Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
|
|
5
|
+
// Trivial toggle-style commands — boolean UI flags routed through patchUi
|
|
6
|
+
// + a config.set RPC so the gateway can persist them.
|
|
7
|
+
import { flagFromArg } from '../types.js';
|
|
8
|
+
const TOGGLES = [
|
|
9
|
+
{ name: 'verbose', help: 'toggle verbose logging', key: 'verbose' },
|
|
10
|
+
{ name: 'reasoning', help: 'toggle reasoning trace display', key: 'reasoning' },
|
|
11
|
+
{ name: 'fast', help: 'toggle fast mode (skip extras)', key: 'fast' },
|
|
12
|
+
{ name: 'yolo', help: 'toggle YOLO mode (auto-approve)', key: 'yolo' },
|
|
13
|
+
{ name: 'busy', help: 'toggle busy indicator', key: 'busy' },
|
|
14
|
+
{ name: 'queue', help: 'toggle queued-input mode', key: 'queue' },
|
|
15
|
+
{ name: 'background', help: 'toggle background-task display', key: 'background' },
|
|
16
|
+
{ name: 'browser', help: 'toggle browser tool', key: 'browserEnabled', aliases: ['br'] },
|
|
17
|
+
{ name: 'voice', help: 'toggle voice input/output', key: 'voice' },
|
|
18
|
+
{ name: 'image', help: 'toggle inline image rendering', key: 'image' },
|
|
19
|
+
];
|
|
20
|
+
export const toggleCommands = TOGGLES.map(t => ({
|
|
21
|
+
name: t.name,
|
|
22
|
+
aliases: t.aliases,
|
|
23
|
+
category: 'toggles',
|
|
24
|
+
help: t.help,
|
|
25
|
+
run: (arg, ctx) => {
|
|
26
|
+
const cur = Boolean(ctx.ui[t.key]);
|
|
27
|
+
const next = flagFromArg(arg, cur);
|
|
28
|
+
if (next === null) {
|
|
29
|
+
ctx.emit({ kind: 'sys', text: `usage: /${t.name} [on|off|toggle]` });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
ctx.emit({ kind: 'patchUi', ui: { [t.key]: next } });
|
|
33
|
+
ctx.emit({
|
|
34
|
+
kind: 'rpc',
|
|
35
|
+
method: 'config.set',
|
|
36
|
+
params: { key: t.configKey ?? t.name, value: next ? 'on' : 'off' },
|
|
37
|
+
});
|
|
38
|
+
ctx.emit({ kind: 'sys', text: `${t.name} ${next ? 'on' : 'off'}` });
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Ported from CVC Agent (https://github.com/NousResearch/cvc)
|
|
4
|
+
// Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
|
|
5
|
+
import { coreCommands } from './commands/core.js';
|
|
6
|
+
import { debugCommands } from './commands/debug.js';
|
|
7
|
+
import { opsCommands } from './commands/ops.js';
|
|
8
|
+
import { sessionCommands } from './commands/session.js';
|
|
9
|
+
import { setupCommands } from './commands/setup.js';
|
|
10
|
+
export const SLASH_COMMANDS = [
|
|
11
|
+
...coreCommands,
|
|
12
|
+
...sessionCommands,
|
|
13
|
+
...opsCommands,
|
|
14
|
+
...setupCommands,
|
|
15
|
+
...debugCommands
|
|
16
|
+
];
|
|
17
|
+
const byName = new Map(SLASH_COMMANDS.flatMap(cmd => [cmd.name, ...(cmd.aliases ?? [])].map(name => [name, cmd])));
|
|
18
|
+
export const findSlashCommand = (name) => byName.get(name.toLowerCase());
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Ported from CVC Agent (https://github.com/NousResearch/cvc)
|
|
4
|
+
// Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
|
|
5
|
+
import { atom } from 'nanostores';
|
|
6
|
+
const HISTORY_LIMIT = 10;
|
|
7
|
+
export const $spawnHistory = atom([]);
|
|
8
|
+
export const $spawnDiff = atom(null);
|
|
9
|
+
export const getSpawnHistory = () => $spawnHistory.get();
|
|
10
|
+
export const getSpawnDiff = () => $spawnDiff.get();
|
|
11
|
+
export const clearSpawnHistory = () => $spawnHistory.set([]);
|
|
12
|
+
export const clearDiffPair = () => $spawnDiff.set(null);
|
|
13
|
+
export const setDiffPair = (pair) => $spawnDiff.set(pair);
|
|
14
|
+
/**
|
|
15
|
+
* Commit a finished turn's spawn tree to history. Keeps the last 10
|
|
16
|
+
* non-empty snapshots — empty turns (no subagents) are dropped.
|
|
17
|
+
*
|
|
18
|
+
* Why in-memory? The primary investigation loop is "I just ran a fan-out,
|
|
19
|
+
* it misbehaved, let me look at what happened" — same-session debugging.
|
|
20
|
+
* Disk persistence across process restarts is a natural extension but
|
|
21
|
+
* adds RPC surface for a less-common path.
|
|
22
|
+
*/
|
|
23
|
+
export const pushSnapshot = (subagents, meta) => {
|
|
24
|
+
if (!subagents.length) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const now = Date.now();
|
|
28
|
+
const started = meta.startedAt ?? Math.min(...subagents.map(s => s.startedAt ?? now));
|
|
29
|
+
const snap = {
|
|
30
|
+
finishedAt: now,
|
|
31
|
+
id: `snap-${now.toString(36)}`,
|
|
32
|
+
label: summarizeLabel(subagents),
|
|
33
|
+
sessionId: meta.sessionId ?? null,
|
|
34
|
+
startedAt: Number.isFinite(started) ? started : now,
|
|
35
|
+
subagents: subagents.map(item => ({ ...item }))
|
|
36
|
+
};
|
|
37
|
+
const next = [snap, ...$spawnHistory.get()].slice(0, HISTORY_LIMIT);
|
|
38
|
+
$spawnHistory.set(next);
|
|
39
|
+
};
|
|
40
|
+
function summarizeLabel(subagents) {
|
|
41
|
+
const top = subagents
|
|
42
|
+
.filter(s => s.parentId == null || subagents.every(o => o.id !== s.parentId))
|
|
43
|
+
.slice(0, 2)
|
|
44
|
+
.map(s => s.goal || 'subagent')
|
|
45
|
+
.join(' · ');
|
|
46
|
+
return top || `${subagents.length} agent${subagents.length === 1 ? '' : 's'}`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Push a disk-loaded snapshot onto the front of the history stack so the
|
|
50
|
+
* overlay can pick it up at index 1 via /replay load. Normalises the
|
|
51
|
+
* server payload (arbitrary list) into the same SubagentProgress shape
|
|
52
|
+
* used for live data — defensive against cross-version reads.
|
|
53
|
+
*/
|
|
54
|
+
export const pushDiskSnapshot = (r, path) => {
|
|
55
|
+
const raw = Array.isArray(r.subagents) ? r.subagents : [];
|
|
56
|
+
const normalised = raw.map(normaliseSubagent);
|
|
57
|
+
if (!normalised.length) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const snap = {
|
|
61
|
+
finishedAt: (r.finished_at ?? Date.now() / 1000) * 1000,
|
|
62
|
+
fromDisk: true,
|
|
63
|
+
id: `disk-${path}`,
|
|
64
|
+
label: r.label || `${normalised.length} subagents`,
|
|
65
|
+
path,
|
|
66
|
+
sessionId: r.session_id ?? null,
|
|
67
|
+
startedAt: (r.started_at ?? r.finished_at ?? Date.now() / 1000) * 1000,
|
|
68
|
+
subagents: normalised
|
|
69
|
+
};
|
|
70
|
+
const next = [snap, ...$spawnHistory.get()].slice(0, HISTORY_LIMIT);
|
|
71
|
+
$spawnHistory.set(next);
|
|
72
|
+
};
|
|
73
|
+
function normaliseSubagent(raw) {
|
|
74
|
+
const o = raw;
|
|
75
|
+
const s = (v) => (typeof v === 'string' ? v : undefined);
|
|
76
|
+
const n = (v) => (typeof v === 'number' ? v : undefined);
|
|
77
|
+
const arr = (v) => (Array.isArray(v) ? v : undefined);
|
|
78
|
+
return {
|
|
79
|
+
apiCalls: n(o.apiCalls),
|
|
80
|
+
costUsd: n(o.costUsd),
|
|
81
|
+
depth: typeof o.depth === 'number' ? o.depth : 0,
|
|
82
|
+
durationSeconds: n(o.durationSeconds),
|
|
83
|
+
filesRead: arr(o.filesRead),
|
|
84
|
+
filesWritten: arr(o.filesWritten),
|
|
85
|
+
goal: s(o.goal) ?? 'subagent',
|
|
86
|
+
id: s(o.id) ?? `sa-${Math.random().toString(36).slice(2, 8)}`,
|
|
87
|
+
index: typeof o.index === 'number' ? o.index : 0,
|
|
88
|
+
inputTokens: n(o.inputTokens),
|
|
89
|
+
iteration: n(o.iteration),
|
|
90
|
+
model: s(o.model),
|
|
91
|
+
notes: (arr(o.notes) ?? []).filter(x => typeof x === 'string'),
|
|
92
|
+
outputTail: arr(o.outputTail),
|
|
93
|
+
outputTokens: n(o.outputTokens),
|
|
94
|
+
parentId: s(o.parentId) ?? null,
|
|
95
|
+
reasoningTokens: n(o.reasoningTokens),
|
|
96
|
+
startedAt: n(o.startedAt),
|
|
97
|
+
status: s(o.status) ?? 'completed',
|
|
98
|
+
summary: s(o.summary),
|
|
99
|
+
taskCount: typeof o.taskCount === 'number' ? o.taskCount : 1,
|
|
100
|
+
thinking: (arr(o.thinking) ?? []).filter(x => typeof x === 'string'),
|
|
101
|
+
toolCount: typeof o.toolCount === 'number' ? o.toolCount : 0,
|
|
102
|
+
tools: (arr(o.tools) ?? []).filter(x => typeof x === 'string'),
|
|
103
|
+
toolsets: arr(o.toolsets)
|
|
104
|
+
};
|
|
105
|
+
}
|