cvc-tui 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +498 -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 +629 -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 +121 -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 -71149
- 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/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,101 @@
|
|
|
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 { parseSlashCommand } from '../domain/slash.js';
|
|
6
|
+
import { asCommandDispatch, rpcErrorMessage } from '../lib/rpc.js';
|
|
7
|
+
import { findSlashCommand } from './slash/registry.js';
|
|
8
|
+
import { getUiState } from './uiStore.js';
|
|
9
|
+
export function createSlashHandler(ctx) {
|
|
10
|
+
const { gw } = ctx.gateway;
|
|
11
|
+
const { catalog } = ctx.local;
|
|
12
|
+
const { page, send, sys } = ctx.transcript;
|
|
13
|
+
const handler = (cmd) => {
|
|
14
|
+
const flight = ++ctx.slashFlightRef.current;
|
|
15
|
+
const ui = getUiState();
|
|
16
|
+
const sid = ui.sid;
|
|
17
|
+
const parsed = parseSlashCommand(cmd);
|
|
18
|
+
const argTail = parsed.arg ? ` ${parsed.arg}` : '';
|
|
19
|
+
const stale = () => flight !== ctx.slashFlightRef.current || getUiState().sid !== sid;
|
|
20
|
+
const guarded = (fn) => (r) => {
|
|
21
|
+
if (!stale() && r) {
|
|
22
|
+
fn(r);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const guardedErr = (e) => {
|
|
26
|
+
if (!stale()) {
|
|
27
|
+
sys(`error: ${rpcErrorMessage(e)}`);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const runCtx = { ...ctx, flight, guarded, guardedErr, sid, stale, ui };
|
|
31
|
+
const found = findSlashCommand(parsed.name);
|
|
32
|
+
if (found) {
|
|
33
|
+
found.run(parsed.arg, runCtx, cmd);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
if (catalog?.canon) {
|
|
37
|
+
const needle = `/${parsed.name}`.toLowerCase();
|
|
38
|
+
const exact = Object.entries(catalog.canon).find(([alias]) => alias.toLowerCase() === needle)?.[1];
|
|
39
|
+
if (exact) {
|
|
40
|
+
if (exact.toLowerCase() !== needle) {
|
|
41
|
+
return handler(`${exact}${argTail}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
const matches = [
|
|
46
|
+
...new Set(Object.entries(catalog.canon)
|
|
47
|
+
.filter(([alias]) => alias.startsWith(needle))
|
|
48
|
+
.map(([, canon]) => canon))
|
|
49
|
+
];
|
|
50
|
+
if (matches.length === 1 && matches[0].toLowerCase() !== needle) {
|
|
51
|
+
return handler(`${matches[0]}${argTail}`);
|
|
52
|
+
}
|
|
53
|
+
if (matches.length > 1) {
|
|
54
|
+
sys(`ambiguous command: ${matches.slice(0, 6).join(', ')}${matches.length > 6 ? ', …' : ''}`);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
gw.request('slash.exec', { command: cmd.slice(1), session_id: sid })
|
|
60
|
+
.then(r => {
|
|
61
|
+
if (stale()) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const body = r?.output || `/${parsed.name}: no output`;
|
|
65
|
+
const text = r?.warning ? `warning: ${r.warning}\n${body}` : body;
|
|
66
|
+
const long = text.length > 180 || text.split('\n').filter(Boolean).length > 2;
|
|
67
|
+
long ? page(text, parsed.name[0].toUpperCase() + parsed.name.slice(1)) : sys(text);
|
|
68
|
+
})
|
|
69
|
+
.catch(() => {
|
|
70
|
+
gw.request('command.dispatch', { arg: parsed.arg, name: parsed.name, session_id: sid })
|
|
71
|
+
.then((raw) => {
|
|
72
|
+
if (stale()) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const d = asCommandDispatch(raw);
|
|
76
|
+
if (!d) {
|
|
77
|
+
return sys('error: invalid response: command.dispatch');
|
|
78
|
+
}
|
|
79
|
+
if (d.type === 'exec' || d.type === 'plugin') {
|
|
80
|
+
return sys(d.output || '(no output)');
|
|
81
|
+
}
|
|
82
|
+
if (d.type === 'alias') {
|
|
83
|
+
return handler(`/${d.target}${argTail}`);
|
|
84
|
+
}
|
|
85
|
+
if (d.type === 'skill') {
|
|
86
|
+
sys(`⚡ loading skill: ${d.name}`);
|
|
87
|
+
return d.message?.trim() ? send(d.message) : sys(`/${parsed.name}: skill payload missing message`);
|
|
88
|
+
}
|
|
89
|
+
if (d.type === 'send') {
|
|
90
|
+
if (d.notice?.trim()) {
|
|
91
|
+
sys(d.notice);
|
|
92
|
+
}
|
|
93
|
+
return d.message?.trim() ? send(d.message) : sys(`/${parsed.name}: empty message`);
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
.catch(guardedErr);
|
|
97
|
+
});
|
|
98
|
+
return true;
|
|
99
|
+
};
|
|
100
|
+
return handler;
|
|
101
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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 buildState = () => ({
|
|
7
|
+
maxConcurrentChildren: null,
|
|
8
|
+
maxSpawnDepth: null,
|
|
9
|
+
paused: false,
|
|
10
|
+
updatedAt: null
|
|
11
|
+
});
|
|
12
|
+
export const $delegationState = atom(buildState());
|
|
13
|
+
export const getDelegationState = () => $delegationState.get();
|
|
14
|
+
export const patchDelegationState = (next) => $delegationState.set({ ...$delegationState.get(), ...next });
|
|
15
|
+
export const resetDelegationState = () => $delegationState.set(buildState());
|
|
16
|
+
// ── Overlay accordion open-state ──────────────────────────────────────
|
|
17
|
+
//
|
|
18
|
+
// Lifted out of OverlaySection's local useState so collapse choices
|
|
19
|
+
// survive:
|
|
20
|
+
// - navigating to a different subagent (Detail remounts)
|
|
21
|
+
// - switching list ↔ detail mode (Detail unmounts in list mode)
|
|
22
|
+
// - walking history (←/→)
|
|
23
|
+
// Keyed by section title; missing entries fall back to the section's
|
|
24
|
+
// `defaultOpen` prop.
|
|
25
|
+
export const $overlaySectionsOpen = atom({});
|
|
26
|
+
export const toggleOverlaySection = (title, defaultOpen) => {
|
|
27
|
+
const state = $overlaySectionsOpen.get();
|
|
28
|
+
const current = title in state ? state[title] : defaultOpen;
|
|
29
|
+
$overlaySectionsOpen.set({ ...state, [title]: !current });
|
|
30
|
+
};
|
|
31
|
+
export const getOverlaySectionOpen = (title, defaultOpen) => {
|
|
32
|
+
const state = $overlaySectionsOpen.get();
|
|
33
|
+
return title in state ? state[title] : defaultOpen;
|
|
34
|
+
};
|
|
35
|
+
/** Merge a raw RPC response into the store. Tolerant of partial/omitted fields. */
|
|
36
|
+
export const applyDelegationStatus = (r) => {
|
|
37
|
+
if (!r) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const patch = { updatedAt: Date.now() };
|
|
41
|
+
if (typeof r.max_spawn_depth === 'number') {
|
|
42
|
+
patch.maxSpawnDepth = r.max_spawn_depth;
|
|
43
|
+
}
|
|
44
|
+
if (typeof r.max_concurrent_children === 'number') {
|
|
45
|
+
patch.maxConcurrentChildren = r.max_concurrent_children;
|
|
46
|
+
}
|
|
47
|
+
if (typeof r.paused === 'boolean') {
|
|
48
|
+
patch.paused = r.paused;
|
|
49
|
+
}
|
|
50
|
+
patchDelegationState(patch);
|
|
51
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
// SPDX-License-Identifier: MIT
|
|
4
|
+
// Ported from CVC Agent (https://github.com/NousResearch/cvc)
|
|
5
|
+
// Original Copyright (c) 2025 Nous Research. CVC adaptations (c) 2026 Jai Kumar Meena.
|
|
6
|
+
import { createContext, useContext } from 'react';
|
|
7
|
+
const GatewayContext = createContext(null);
|
|
8
|
+
export function GatewayProvider({ children, value }) {
|
|
9
|
+
return _jsx(GatewayContext.Provider, { value: value, children: children });
|
|
10
|
+
}
|
|
11
|
+
export function useGateway() {
|
|
12
|
+
const value = useContext(GatewayContext);
|
|
13
|
+
if (!value) {
|
|
14
|
+
throw new Error('GatewayContext missing');
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
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
|
+
// History store — persists user input to ~/.cvc/agent_history.
|
|
6
|
+
// One entry per line, dedup against immediate previous, max 10000.
|
|
7
|
+
import { atom } from 'nanostores';
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import * as os from 'node:os';
|
|
11
|
+
export const HISTORY_MAX = 10000;
|
|
12
|
+
function defaultHistoryPath() {
|
|
13
|
+
return path.join(os.homedir(), '.cvc', 'agent_history');
|
|
14
|
+
}
|
|
15
|
+
let historyPath = defaultHistoryPath();
|
|
16
|
+
/** Used by tests to redirect persistence. */
|
|
17
|
+
export function setHistoryPath(p) {
|
|
18
|
+
historyPath = p;
|
|
19
|
+
}
|
|
20
|
+
export function getHistoryPath() {
|
|
21
|
+
return historyPath;
|
|
22
|
+
}
|
|
23
|
+
export const $history = atom([]);
|
|
24
|
+
/**
|
|
25
|
+
* Cursor pointing into history while user is browsing with Up/Down.
|
|
26
|
+
* `null` means "not browsing" (i.e. at the live edit buffer).
|
|
27
|
+
*/
|
|
28
|
+
export const $historyCursor = atom(null);
|
|
29
|
+
/** Snapshot of the live buffer so we can restore it after browsing. */
|
|
30
|
+
export const $historyDraft = atom('');
|
|
31
|
+
export function loadHistory() {
|
|
32
|
+
try {
|
|
33
|
+
if (!fs.existsSync(historyPath)) {
|
|
34
|
+
$history.set([]);
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
const raw = fs.readFileSync(historyPath, 'utf8');
|
|
38
|
+
// Drop empty trailing line, ignore blank lines.
|
|
39
|
+
const lines = raw.split('\n').filter((l) => l.length > 0);
|
|
40
|
+
$history.set(lines);
|
|
41
|
+
return lines;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
$history.set([]);
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** Append an entry, dedup-against-previous, cap at HISTORY_MAX, persist. */
|
|
49
|
+
export function pushHistory(entry) {
|
|
50
|
+
const trimmed = entry.replace(/\s+$/g, '');
|
|
51
|
+
if (!trimmed)
|
|
52
|
+
return;
|
|
53
|
+
const cur = $history.get();
|
|
54
|
+
if (cur.length > 0 && cur[cur.length - 1] === trimmed) {
|
|
55
|
+
// Dedup consecutive duplicates without rewriting the file.
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
let next = [...cur, trimmed];
|
|
59
|
+
if (next.length > HISTORY_MAX) {
|
|
60
|
+
next = next.slice(next.length - HISTORY_MAX);
|
|
61
|
+
}
|
|
62
|
+
$history.set(next);
|
|
63
|
+
persist();
|
|
64
|
+
}
|
|
65
|
+
export function persist() {
|
|
66
|
+
try {
|
|
67
|
+
fs.mkdirSync(path.dirname(historyPath), { recursive: true });
|
|
68
|
+
fs.writeFileSync(historyPath, $history.get().join('\n') + '\n', 'utf8');
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Best-effort — never crash the UI on history I/O failure.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Reverse-incremental search. Returns up to `limit` matches, most-recent-first.
|
|
76
|
+
*/
|
|
77
|
+
export function searchHistory(query, limit = 50) {
|
|
78
|
+
if (!query)
|
|
79
|
+
return [];
|
|
80
|
+
const q = query.toLowerCase();
|
|
81
|
+
const out = [];
|
|
82
|
+
const cur = $history.get();
|
|
83
|
+
for (let i = cur.length - 1; i >= 0 && out.length < limit; i--) {
|
|
84
|
+
if (cur[i].toLowerCase().includes(q))
|
|
85
|
+
out.push(cur[i]);
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
/** Move history cursor toward older entries; returns the entry text or null. */
|
|
90
|
+
export function historyPrev(currentDraft) {
|
|
91
|
+
const h = $history.get();
|
|
92
|
+
if (h.length === 0)
|
|
93
|
+
return null;
|
|
94
|
+
let idx = $historyCursor.get();
|
|
95
|
+
if (idx === null) {
|
|
96
|
+
$historyDraft.set(currentDraft);
|
|
97
|
+
idx = h.length - 1;
|
|
98
|
+
}
|
|
99
|
+
else if (idx > 0) {
|
|
100
|
+
idx -= 1;
|
|
101
|
+
}
|
|
102
|
+
$historyCursor.set(idx);
|
|
103
|
+
return h[idx];
|
|
104
|
+
}
|
|
105
|
+
/** Move history cursor toward newer entries; returns the entry text or null on fall-through. */
|
|
106
|
+
export function historyNext() {
|
|
107
|
+
const h = $history.get();
|
|
108
|
+
const idx = $historyCursor.get();
|
|
109
|
+
if (idx === null)
|
|
110
|
+
return null;
|
|
111
|
+
if (idx >= h.length - 1) {
|
|
112
|
+
// Fell off the end: restore draft.
|
|
113
|
+
$historyCursor.set(null);
|
|
114
|
+
return $historyDraft.get();
|
|
115
|
+
}
|
|
116
|
+
const next = idx + 1;
|
|
117
|
+
$historyCursor.set(next);
|
|
118
|
+
return h[next];
|
|
119
|
+
}
|
|
120
|
+
export function resetHistoryBrowse() {
|
|
121
|
+
$historyCursor.set(null);
|
|
122
|
+
$historyDraft.set('');
|
|
123
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
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
|
+
// Pure text-buffer helpers for the multi-line composer.
|
|
6
|
+
// Kept side-effect-free so they can be unit-tested without Ink/React.
|
|
7
|
+
export function emptyBuffer() {
|
|
8
|
+
return { text: '', cursor: 0 };
|
|
9
|
+
}
|
|
10
|
+
export function insert(buf, s) {
|
|
11
|
+
const text = buf.text.slice(0, buf.cursor) + s + buf.text.slice(buf.cursor);
|
|
12
|
+
return { text, cursor: buf.cursor + s.length };
|
|
13
|
+
}
|
|
14
|
+
export function backspace(buf) {
|
|
15
|
+
if (buf.cursor === 0)
|
|
16
|
+
return buf;
|
|
17
|
+
const text = buf.text.slice(0, buf.cursor - 1) + buf.text.slice(buf.cursor);
|
|
18
|
+
return { text, cursor: buf.cursor - 1 };
|
|
19
|
+
}
|
|
20
|
+
export function del(buf) {
|
|
21
|
+
if (buf.cursor >= buf.text.length)
|
|
22
|
+
return buf;
|
|
23
|
+
const text = buf.text.slice(0, buf.cursor) + buf.text.slice(buf.cursor + 1);
|
|
24
|
+
return { text, cursor: buf.cursor };
|
|
25
|
+
}
|
|
26
|
+
export function moveLeft(buf) {
|
|
27
|
+
return { ...buf, cursor: Math.max(0, buf.cursor - 1) };
|
|
28
|
+
}
|
|
29
|
+
export function moveRight(buf) {
|
|
30
|
+
return { ...buf, cursor: Math.min(buf.text.length, buf.cursor + 1) };
|
|
31
|
+
}
|
|
32
|
+
export function moveHome(buf) {
|
|
33
|
+
// Move to start of current logical line.
|
|
34
|
+
const before = buf.text.slice(0, buf.cursor);
|
|
35
|
+
const nl = before.lastIndexOf('\n');
|
|
36
|
+
return { ...buf, cursor: nl < 0 ? 0 : nl + 1 };
|
|
37
|
+
}
|
|
38
|
+
export function moveEnd(buf) {
|
|
39
|
+
const after = buf.text.slice(buf.cursor);
|
|
40
|
+
const nl = after.indexOf('\n');
|
|
41
|
+
return { ...buf, cursor: nl < 0 ? buf.text.length : buf.cursor + nl };
|
|
42
|
+
}
|
|
43
|
+
/** Lines split on \n (keeps empty trailing line). */
|
|
44
|
+
export function lines(text) {
|
|
45
|
+
return text.split('\n');
|
|
46
|
+
}
|
|
47
|
+
/** Convert cursor offset to {row, col} on logical lines. */
|
|
48
|
+
export function cursorRowCol(buf) {
|
|
49
|
+
const before = buf.text.slice(0, buf.cursor);
|
|
50
|
+
const ls = before.split('\n');
|
|
51
|
+
return { row: ls.length - 1, col: ls[ls.length - 1].length };
|
|
52
|
+
}
|
|
53
|
+
/** Move cursor up one logical line, preserving column when possible. */
|
|
54
|
+
export function moveUp(buf) {
|
|
55
|
+
const { row, col } = cursorRowCol(buf);
|
|
56
|
+
if (row === 0)
|
|
57
|
+
return buf;
|
|
58
|
+
const ls = lines(buf.text);
|
|
59
|
+
const targetCol = Math.min(col, ls[row - 1].length);
|
|
60
|
+
// Compute new offset: sum of lines 0..row-2 + their newlines + targetCol.
|
|
61
|
+
let off = 0;
|
|
62
|
+
for (let i = 0; i < row - 1; i++)
|
|
63
|
+
off += ls[i].length + 1;
|
|
64
|
+
off += targetCol;
|
|
65
|
+
return { ...buf, cursor: off };
|
|
66
|
+
}
|
|
67
|
+
export function moveDown(buf) {
|
|
68
|
+
const { row, col } = cursorRowCol(buf);
|
|
69
|
+
const ls = lines(buf.text);
|
|
70
|
+
if (row >= ls.length - 1)
|
|
71
|
+
return buf;
|
|
72
|
+
const targetCol = Math.min(col, ls[row + 1].length);
|
|
73
|
+
let off = 0;
|
|
74
|
+
for (let i = 0; i < row + 1; i++)
|
|
75
|
+
off += ls[i].length + 1;
|
|
76
|
+
off += targetCol;
|
|
77
|
+
return { ...buf, cursor: off };
|
|
78
|
+
}
|
|
79
|
+
/** True when the cursor is on the first logical line. */
|
|
80
|
+
export function onFirstLine(buf) {
|
|
81
|
+
return cursorRowCol(buf).row === 0;
|
|
82
|
+
}
|
|
83
|
+
/** True when the cursor is on the last logical line. */
|
|
84
|
+
export function onLastLine(buf) {
|
|
85
|
+
return cursorRowCol(buf).row === lines(buf.text).length - 1;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Word-wrap a logical line into visual rows of width `cols`.
|
|
89
|
+
* Returns array of strings (each ≤ cols chars). Tries to break at spaces.
|
|
90
|
+
*/
|
|
91
|
+
export function wrapLine(line, cols) {
|
|
92
|
+
if (cols <= 0)
|
|
93
|
+
return [line];
|
|
94
|
+
if (line.length <= cols)
|
|
95
|
+
return [line];
|
|
96
|
+
const out = [];
|
|
97
|
+
let rest = line;
|
|
98
|
+
while (rest.length > cols) {
|
|
99
|
+
let cut = cols;
|
|
100
|
+
// Look for a space to break at, within a small window.
|
|
101
|
+
const window = rest.slice(0, cols);
|
|
102
|
+
const lastSpace = window.lastIndexOf(' ');
|
|
103
|
+
if (lastSpace > Math.floor(cols * 0.5)) {
|
|
104
|
+
cut = lastSpace + 1;
|
|
105
|
+
}
|
|
106
|
+
out.push(rest.slice(0, cut));
|
|
107
|
+
rest = rest.slice(cut);
|
|
108
|
+
}
|
|
109
|
+
if (rest.length)
|
|
110
|
+
out.push(rest);
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
/** Wrap the full text, returning visual rows. */
|
|
114
|
+
export function wrapText(text, cols) {
|
|
115
|
+
return text.split('\n').flatMap((l) => (l.length === 0 ? [''] : wrapLine(l, cols)));
|
|
116
|
+
}
|
|
117
|
+
/** Stats for the dim status footer. */
|
|
118
|
+
export function bufferStats(text) {
|
|
119
|
+
return { chars: text.length, lineCount: text.split('\n').length };
|
|
120
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
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
|
+
export const $inputSelection = atom(null);
|
|
7
|
+
export const setInputSelection = (next) => $inputSelection.set(next);
|
|
8
|
+
export const getInputSelection = () => $inputSelection.get();
|
|
@@ -0,0 +1,28 @@
|
|
|
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
|
+
// Composer state — kept in a nanostore so any subsystem can read/write
|
|
6
|
+
// without prop-drilling. Holds the active text buffer + completion state.
|
|
7
|
+
import { atom } from 'nanostores';
|
|
8
|
+
import { emptyBuffer } from './inputBuffer.js';
|
|
9
|
+
export const $buffer = atom(emptyBuffer());
|
|
10
|
+
export const $completion = atom({
|
|
11
|
+
active: false,
|
|
12
|
+
prefix: '',
|
|
13
|
+
candidates: [],
|
|
14
|
+
index: 0,
|
|
15
|
+
start: 0,
|
|
16
|
+
});
|
|
17
|
+
export function resetCompletion() {
|
|
18
|
+
$completion.set({ active: false, prefix: '', candidates: [], index: 0, start: 0 });
|
|
19
|
+
}
|
|
20
|
+
export function setBuffer(b) {
|
|
21
|
+
$buffer.set(b);
|
|
22
|
+
// Any non-Tab edit should reset the completion cycle.
|
|
23
|
+
resetCompletion();
|
|
24
|
+
}
|
|
25
|
+
/** Direct setter that does not reset completion (used by the cycle itself). */
|
|
26
|
+
export function setBufferRaw(b) {
|
|
27
|
+
$buffer.set(b);
|
|
28
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Single source of truth for indicator style names. Union type is
|
|
2
|
+
// derived from this tuple so adding/removing a style only touches one
|
|
3
|
+
// line — `useConfigSync` (validation) and `session.ts` (slash arg
|
|
4
|
+
// validation + usage hint) both import it.
|
|
5
|
+
export const INDICATOR_STYLES = ['ascii', 'emoji', 'kaomoji', 'unicode'];
|
|
6
|
+
export const DEFAULT_INDICATOR_STYLE = 'kaomoji';
|
|
@@ -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
|
+
import { atom, computed } from 'nanostores';
|
|
6
|
+
const buildOverlayState = () => ({
|
|
7
|
+
agents: false,
|
|
8
|
+
agentsInitialHistoryIndex: 0,
|
|
9
|
+
approval: null,
|
|
10
|
+
clarify: null,
|
|
11
|
+
confirm: null,
|
|
12
|
+
modelPicker: false,
|
|
13
|
+
pager: null,
|
|
14
|
+
picker: false,
|
|
15
|
+
secret: null,
|
|
16
|
+
skillsHub: false,
|
|
17
|
+
sudo: null
|
|
18
|
+
});
|
|
19
|
+
export const $overlayState = atom(buildOverlayState());
|
|
20
|
+
export const $isBlocked = computed($overlayState, ({ agents, approval, clarify, confirm, modelPicker, pager, picker, secret, skillsHub, sudo }) => Boolean(agents || approval || clarify || confirm || modelPicker || pager || picker || secret || skillsHub || sudo));
|
|
21
|
+
export const getOverlayState = () => $overlayState.get();
|
|
22
|
+
export const patchOverlayState = (next) => $overlayState.set(typeof next === 'function' ? next($overlayState.get()) : { ...$overlayState.get(), ...next });
|
|
23
|
+
/** Full reset — used by session/turn teardown and tests. */
|
|
24
|
+
export const resetOverlayState = () => $overlayState.set(buildOverlayState());
|
|
25
|
+
/**
|
|
26
|
+
* Soft reset: drop FLOW-scoped overlays (approval / clarify / confirm / sudo
|
|
27
|
+
* / secret / pager) but PRESERVE user-toggled ones — agents dashboard, model
|
|
28
|
+
* picker, skills hub, session picker. Those are opened deliberately and
|
|
29
|
+
* shouldn't vanish when a turn ends. Called from turnController.idle() on
|
|
30
|
+
* every turn completion / interrupt; the old "reset everything" behaviour
|
|
31
|
+
* silently closed /agents the moment delegation finished.
|
|
32
|
+
*/
|
|
33
|
+
export const resetFlowOverlays = () => $overlayState.set({
|
|
34
|
+
...buildOverlayState(),
|
|
35
|
+
agents: $overlayState.get().agents,
|
|
36
|
+
agentsInitialHistoryIndex: $overlayState.get().agentsInitialHistoryIndex,
|
|
37
|
+
modelPicker: $overlayState.get().modelPicker,
|
|
38
|
+
picker: $overlayState.get().picker,
|
|
39
|
+
skillsHub: $overlayState.get().skillsHub
|
|
40
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
// Prompt store — confirmation + secret prompts requested by any subsystem.
|
|
6
|
+
// The composer hides while a prompt is active; the prompt overlay owns input.
|
|
7
|
+
import { atom } from 'nanostores';
|
|
8
|
+
export const $prompt = atom(null);
|
|
9
|
+
let counter = 0;
|
|
10
|
+
function nextId() {
|
|
11
|
+
counter += 1;
|
|
12
|
+
return `prompt_${Date.now()}_${counter}`;
|
|
13
|
+
}
|
|
14
|
+
/** Request a yes/no confirmation. Resolves with `true`/`false`, or `null` on Esc. */
|
|
15
|
+
export function requestConfirm(prompt, opts = {}) {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
$prompt.set({
|
|
18
|
+
id: nextId(),
|
|
19
|
+
kind: 'confirm',
|
|
20
|
+
prompt,
|
|
21
|
+
destructive: opts.destructive,
|
|
22
|
+
resolve: (v) => resolve(v),
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** Request a secret. Resolves with the entered string, or `null` on Esc. */
|
|
27
|
+
export function requestSecret(prompt, opts = {}) {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
$prompt.set({
|
|
30
|
+
id: nextId(),
|
|
31
|
+
kind: 'secret',
|
|
32
|
+
prompt,
|
|
33
|
+
mask: opts.mask !== false,
|
|
34
|
+
resolve: (v) => resolve(v),
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export function resolveActivePrompt(value) {
|
|
39
|
+
const cur = $prompt.get();
|
|
40
|
+
if (!cur)
|
|
41
|
+
return;
|
|
42
|
+
cur.resolve(value);
|
|
43
|
+
$prompt.set(null);
|
|
44
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
// Queue store — submissions made while a turn is streaming are queued
|
|
6
|
+
// and flushed when the active turn ends. Composer shows `[queued: N]`.
|
|
7
|
+
import { atom } from 'nanostores';
|
|
8
|
+
export const $queue = atom([]);
|
|
9
|
+
export function enqueue(text) {
|
|
10
|
+
const t = text.replace(/\s+$/g, '');
|
|
11
|
+
if (!t)
|
|
12
|
+
return;
|
|
13
|
+
$queue.set([...$queue.get(), t]);
|
|
14
|
+
}
|
|
15
|
+
export function dequeueAll() {
|
|
16
|
+
const cur = $queue.get();
|
|
17
|
+
$queue.set([]);
|
|
18
|
+
return cur;
|
|
19
|
+
}
|
|
20
|
+
export function queueLength() {
|
|
21
|
+
return $queue.get().length;
|
|
22
|
+
}
|
|
23
|
+
export function clearQueue() {
|
|
24
|
+
$queue.set([]);
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
function scrollBoundsForDelta(s, cur, delta) {
|
|
2
|
+
const viewport = Math.max(0, s.getViewportHeight());
|
|
3
|
+
const cachedHeight = Math.max(viewport, s.getScrollHeight());
|
|
4
|
+
let max = Math.max(0, cachedHeight - viewport);
|
|
5
|
+
// getScrollHeight() is render-time cached. After the streaming tail is
|
|
6
|
+
// committed into virtual history, the Yoga height can be fresher than the
|
|
7
|
+
// cached value; if we clamp only against the cached fake bottom, wheel-down
|
|
8
|
+
// becomes a no-op and no render is scheduled to reveal the real tail.
|
|
9
|
+
if (delta > 0 && cur + delta >= max - 1) {
|
|
10
|
+
const freshHeight = Math.max(viewport, s.getFreshScrollHeight());
|
|
11
|
+
max = Math.max(0, freshHeight - viewport);
|
|
12
|
+
}
|
|
13
|
+
return { max, viewport };
|
|
14
|
+
}
|
|
15
|
+
export function scrollWithSelectionBy(delta, { scrollRef, selection }) {
|
|
16
|
+
const s = scrollRef.current;
|
|
17
|
+
if (!s) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const cur = s.getScrollTop() + s.getPendingDelta();
|
|
21
|
+
const { max, viewport } = scrollBoundsForDelta(s, cur, delta);
|
|
22
|
+
const actual = Math.max(0, Math.min(max, cur + delta)) - cur;
|
|
23
|
+
if (actual === 0) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const sel = selection.getState();
|
|
27
|
+
const top = s.getViewportTop();
|
|
28
|
+
const bottom = top + viewport - 1;
|
|
29
|
+
if (sel?.anchor &&
|
|
30
|
+
sel.focus &&
|
|
31
|
+
sel.anchor.row >= top &&
|
|
32
|
+
sel.anchor.row <= bottom &&
|
|
33
|
+
(sel.isDragging || (sel.focus.row >= top && sel.focus.row <= bottom))) {
|
|
34
|
+
const shift = sel.isDragging ? selection.shiftAnchor : selection.shiftSelection;
|
|
35
|
+
if (actual > 0) {
|
|
36
|
+
selection.captureScrolledRows(top, top + actual - 1, 'above');
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
selection.captureScrolledRows(bottom + actual + 1, bottom, 'below');
|
|
40
|
+
}
|
|
41
|
+
shift(-actual, top, bottom);
|
|
42
|
+
}
|
|
43
|
+
s.scrollBy(actual);
|
|
44
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { patchUiState } from './uiStore.js';
|
|
2
|
+
export async function runExternalSetup({ args, ctx, done, launcher, suspend }) {
|
|
3
|
+
const { gateway, session, transcript } = ctx;
|
|
4
|
+
transcript.sys(`launching \`hermes ${args.join(' ')}\`…`);
|
|
5
|
+
patchUiState({ status: 'setup running…' });
|
|
6
|
+
let result = { code: null };
|
|
7
|
+
await suspend(async () => {
|
|
8
|
+
result = await launcher(args);
|
|
9
|
+
});
|
|
10
|
+
if (result.error) {
|
|
11
|
+
transcript.sys(`error launching hermes: ${result.error}`);
|
|
12
|
+
patchUiState({ status: 'setup required' });
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (result.code !== 0) {
|
|
16
|
+
transcript.sys(`hermes ${args[0]} exited with code ${result.code}`);
|
|
17
|
+
patchUiState({ status: 'setup required' });
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const setup = await gateway.rpc('setup.status', {});
|
|
21
|
+
if (setup?.provider_configured === false) {
|
|
22
|
+
transcript.sys('still no provider configured');
|
|
23
|
+
patchUiState({ status: 'setup required' });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
transcript.sys(done);
|
|
27
|
+
session.newSession();
|
|
28
|
+
}
|