@parallel-cli/parallel 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/CHANGELOG.md +60 -2
- package/README.md +112 -99
- package/dist/agents/agent.js +2 -2
- package/dist/agents/tools.js +105 -7
- package/dist/commands.js +104 -18
- package/dist/config.js +31 -1
- package/dist/controller.js +68 -14
- package/dist/coordination/blackboard.js +75 -0
- package/dist/i18n.js +140 -32
- package/dist/index.js +3 -3
- package/dist/server.js +10 -0
- package/dist/ui/AgentPanel.js +22 -7
- package/dist/ui/App.js +86 -62
- package/dist/ui/AttachApp.js +13 -3
- package/dist/ui/CommandInput.js +60 -9
- package/dist/ui/SettingsPanel.js +86 -18
- package/dist/ui/Timeline.js +11 -9
- package/dist/ui/views.js +39 -15
- package/package.json +1 -1
package/dist/ui/App.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Box, Text, useApp, useInput, useStdout } from 'ink';
|
|
|
6
6
|
import { Controller } from '../controller.js';
|
|
7
7
|
import { startSessionServer } from '../server.js';
|
|
8
8
|
import { executeInput } from '../commands.js';
|
|
9
|
-
import { PROVIDER_PRESETS, getProvider, providerNeedsApiKey, providerReady, rememberFolder, saveConfig } from '../config.js';
|
|
9
|
+
import { PROVIDER_PRESETS, detectProviderModels, getProvider, isLocalProvider, isPlaceholderModel, providerNeedsApiKey, providerReady, rememberFolder, saveConfig, } from '../config.js';
|
|
10
10
|
import { LANGS, setLang, t } from '../i18n.js';
|
|
11
11
|
import { AgentRow, AgentTranscript } from './AgentPanel.js';
|
|
12
12
|
import { ApprovalPrompt } from './ApprovalPrompt.js';
|
|
@@ -18,7 +18,7 @@ import { SelectList, WizardStep } from './Wizard.js';
|
|
|
18
18
|
import { BRAND, CHROME, STATE, STATE_META, UI, middleTruncate } from './tokens.js';
|
|
19
19
|
const LOGO = 'Parallel';
|
|
20
20
|
// Version from package.json. Hardcoded — rootDir: "src" prevents importing ../../package.json.
|
|
21
|
-
const VERSION = '0.4.
|
|
21
|
+
const VERSION = '0.4.5';
|
|
22
22
|
function usableProvider(config) {
|
|
23
23
|
const p = getProvider(config);
|
|
24
24
|
return p && providerReady(p) && (p.defaultModel || p.models[0]) ? p : undefined;
|
|
@@ -60,7 +60,7 @@ export function App({ config, initialFolder }) {
|
|
|
60
60
|
const [modelCustom, setModelCustom] = useState(false);
|
|
61
61
|
const ctlRef = useRef(directFolder ? new Controller(config, directFolder) : null);
|
|
62
62
|
// ---------- main state ----------
|
|
63
|
-
const [, setTick] = useState(0);
|
|
63
|
+
const [tick, setTick] = useState(0);
|
|
64
64
|
const [view, setView] = useState('agents');
|
|
65
65
|
// Focus mode (/focus <agent>): plain input is routed to that agent.
|
|
66
66
|
const [focus, setFocus] = useState(null);
|
|
@@ -107,6 +107,12 @@ export function App({ config, initialFolder }) {
|
|
|
107
107
|
clearInterval(timer);
|
|
108
108
|
};
|
|
109
109
|
}, [ctl]);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (!ctl || !focus)
|
|
112
|
+
return;
|
|
113
|
+
if (!ctl.board.getAgentByName(focus))
|
|
114
|
+
setFocus(null);
|
|
115
|
+
}, [ctl, focus, tick]);
|
|
110
116
|
// Session server: lets `parallel attach <agent>` open per-agent terminals.
|
|
111
117
|
useEffect(() => {
|
|
112
118
|
if (!ctl)
|
|
@@ -228,7 +234,10 @@ export function App({ config, initialFolder }) {
|
|
|
228
234
|
setProviderStep({ id: 'pick' });
|
|
229
235
|
}
|
|
230
236
|
else if (providerStep.id === 'endpoint') {
|
|
231
|
-
|
|
237
|
+
const isPreset = PROVIDER_PRESETS.some((p) => p.name.toLowerCase() === providerStep.provider.name.toLowerCase());
|
|
238
|
+
setProviderStep(isPreset
|
|
239
|
+
? { id: 'presetModel', provider: providerStep.provider }
|
|
240
|
+
: { id: 'customModel', name: providerStep.provider.name, url: providerStep.provider.baseUrl });
|
|
232
241
|
}
|
|
233
242
|
else if (providerStep.id === 'editEndpoint') {
|
|
234
243
|
setProviderStep({ id: 'endpoint', provider: providerStep.provider });
|
|
@@ -391,27 +400,16 @@ export function App({ config, initialFolder }) {
|
|
|
391
400
|
...ls.slice(-5),
|
|
392
401
|
{ text: t('wiz.provider.ollama.checking', { url: preset.baseUrl }), level: 'info' },
|
|
393
402
|
]);
|
|
394
|
-
|
|
395
|
-
let
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
const data = (await resp.json());
|
|
403
|
-
const detected = data?.data?.map((m) => m.id).filter(Boolean) ?? [];
|
|
404
|
-
if (detected.length > 0) {
|
|
405
|
-
models = detected;
|
|
406
|
-
defaultModel = detected[0];
|
|
407
|
-
setSystemLines((ls) => [
|
|
408
|
-
...ls.slice(-5),
|
|
409
|
-
{ text: t('wiz.provider.ollama.found', { n: detected.length }), level: 'ok' },
|
|
410
|
-
]);
|
|
411
|
-
}
|
|
412
|
-
}
|
|
403
|
+
const detected = await detectProviderModels(preset);
|
|
404
|
+
let models = detected?.models ?? [...preset.models];
|
|
405
|
+
let defaultModel = detected?.defaultModel ?? preset.defaultModel;
|
|
406
|
+
if (detected) {
|
|
407
|
+
setSystemLines((ls) => [
|
|
408
|
+
...ls.slice(-5),
|
|
409
|
+
{ text: t('wiz.provider.ollama.found', { n: detected.models.length }), level: 'ok' },
|
|
410
|
+
]);
|
|
413
411
|
}
|
|
414
|
-
|
|
412
|
+
else {
|
|
415
413
|
setSystemLines((ls) => [
|
|
416
414
|
...ls.slice(-5),
|
|
417
415
|
{ text: t('wiz.provider.ollama.notFound', { url: preset.baseUrl }), level: 'warn' },
|
|
@@ -452,16 +450,35 @@ export function App({ config, initialFolder }) {
|
|
|
452
450
|
if (providerNeedsApiKey(providerStep.provider))
|
|
453
451
|
return setProviderStep({ id: 'key', provider: providerStep.provider });
|
|
454
452
|
finishProvider(providerStep.provider);
|
|
455
|
-
} })] })), phase === 'provider' && providerStep.id === 'editEndpoint' && (_jsx(WizardStep, { step: 4, total: totalSteps, title: t('wiz.provider.url.title'), footer: t('wiz.footer.type'), children: _jsx(SelectList, { items: [], height: wizardListHeight, allowInput: true, inputPlaceholder: providerStep.provider.baseUrl, onBack: wizardBack, onInput: (url) =>
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
453
|
+
} })] })), phase === 'provider' && providerStep.id === 'editEndpoint' && (_jsx(WizardStep, { step: 4, total: totalSteps, title: t('wiz.provider.url.title'), footer: t('wiz.footer.type'), children: _jsx(SelectList, { items: [], height: wizardListHeight, allowInput: true, inputPlaceholder: providerStep.provider.baseUrl, onBack: wizardBack, onInput: (url) => {
|
|
454
|
+
const baseUrl = url.trim();
|
|
455
|
+
setProviderStep({
|
|
456
|
+
id: 'endpoint',
|
|
457
|
+
provider: {
|
|
458
|
+
...providerStep.provider,
|
|
459
|
+
baseUrl,
|
|
460
|
+
requiresApiKey: !isLocalProvider({ baseUrl }),
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
} }) })), phase === 'provider' && providerStep.id === 'key' && (_jsxs(WizardStep, { step: 4, total: totalSteps, title: t('wiz.provider.key.title', { name: providerStep.provider.name }), footer: t('wiz.provider.key.footer'), children: [_jsx(Text, { color: "gray", children: providerStep.provider.baseUrl }), _jsx(SelectList, { items: [], height: wizardListHeight, allowInput: true, mask: true, inputPlaceholder: "sk-\u2026", onBack: wizardBack, onInput: (k) => finishProvider({ ...providerStep.provider, apiKey: k.trim() }) })] })), phase === 'provider' && providerStep.id === 'name' && (_jsx(WizardStep, { step: 4, total: totalSteps, title: t('wiz.provider.name.title'), footer: t('wiz.footer.type'), children: _jsx(SelectList, { items: [], height: wizardListHeight, allowInput: true, inputPlaceholder: t('wiz.provider.name.ph'), onBack: wizardBack, onInput: (name) => setProviderStep({ id: 'url', name }) }) })), phase === 'provider' && providerStep.id === 'url' && (_jsx(WizardStep, { step: 4, total: totalSteps, title: t('wiz.provider.url.title'), footer: t('wiz.footer.type'), children: _jsx(SelectList, { items: [], height: wizardListHeight, allowInput: true, inputPlaceholder: t('wiz.provider.url.ph'), onBack: wizardBack, onInput: (url) => setProviderStep({ id: 'customModel', name: providerStep.name, url }) }) })), phase === 'provider' && providerStep.id === 'customModel' && (_jsx(WizardStep, { step: 4, total: totalSteps, title: t('wiz.provider.model.title'), footer: t('wiz.footer.type'), children: _jsx(SelectList, { items: [], height: wizardListHeight, allowInput: true, inputPlaceholder: t('wiz.provider.model.ph'), onBack: wizardBack, onInput: (model) => {
|
|
464
|
+
const trimmed = model.trim();
|
|
465
|
+
if (isPlaceholderModel(trimmed)) {
|
|
466
|
+
setSystemLines((ls) => [...ls.slice(-5), { text: t('set.modelPlaceholder'), level: 'warn' }]);
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const local = isLocalProvider({ baseUrl: providerStep.url });
|
|
470
|
+
setProviderStep({
|
|
471
|
+
id: 'endpoint',
|
|
472
|
+
provider: {
|
|
473
|
+
name: providerStep.name,
|
|
474
|
+
baseUrl: providerStep.url,
|
|
475
|
+
apiKey: '',
|
|
476
|
+
models: [trimmed],
|
|
477
|
+
defaultModel: trimmed,
|
|
478
|
+
requiresApiKey: !local,
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
} }) })), phase === 'provider' && providerStep.id === 'newKey' && (_jsx(WizardStep, { step: 4, total: totalSteps, title: t('wiz.provider.key.title', { name: providerStep.provider.name }), footer: t('wiz.provider.key.footer'), children: _jsx(SelectList, { items: [], height: wizardListHeight, allowInput: true, mask: true, inputPlaceholder: "sk-\u2026", onBack: wizardBack, onInput: (key) => finishProvider({
|
|
465
482
|
...providerStep.provider,
|
|
466
483
|
apiKey: key.trim(),
|
|
467
484
|
}) }) })), phase === 'model' && !modelCustom && sessionProvider && (_jsxs(WizardStep, { step: 5, total: totalSteps, title: t('wiz.model.title'), children: [_jsx(Text, { color: "gray", children: t('wiz.model.provider', { name: sessionProvider.name, url: sessionProvider.baseUrl }) }), _jsx(SelectList, { items: [
|
|
@@ -538,7 +555,10 @@ function MainScreen({ ctl, folder, view, focus, rawLogs, systemLines, agentNames
|
|
|
538
555
|
: undefined;
|
|
539
556
|
const [scroll, setScroll] = useState(0);
|
|
540
557
|
const [focusFollowTail, setFocusFollowTail] = useState(true);
|
|
541
|
-
useEffect(() =>
|
|
558
|
+
useEffect(() => {
|
|
559
|
+
setScroll(0);
|
|
560
|
+
setFocusFollowTail(true);
|
|
561
|
+
}, [focus]);
|
|
542
562
|
const FOCUS_LOGS = Math.max(8, bodyHeight - 1);
|
|
543
563
|
const focusedLogs = focused ? ctl.board.logs.filter((l) => l.agentId === focused.id) : [];
|
|
544
564
|
const maxScroll = Math.max(0, focusedLogs.length - FOCUS_LOGS);
|
|
@@ -548,8 +568,8 @@ function MainScreen({ ctl, folder, view, focus, rawLogs, systemLines, agentNames
|
|
|
548
568
|
: [];
|
|
549
569
|
const [hubScroll, setHubScroll] = useState(0);
|
|
550
570
|
const [hubFollowTail, setHubFollowTail] = useState(true);
|
|
551
|
-
const hubRows = Math.max(
|
|
552
|
-
const maxHubScroll = Math.max(0, agents.length - hubRows);
|
|
571
|
+
const hubRows = Math.max(3, bodyHeight - 2);
|
|
572
|
+
const maxHubScroll = Math.max(0, agents.length - Math.max(1, Math.floor(hubRows / 2)));
|
|
553
573
|
const clampedHub = Math.min(hubScroll, maxHubScroll);
|
|
554
574
|
const logSeq = ctl.board.logs.length > 0 ? ctl.board.logs[ctl.board.logs.length - 1].seq ?? ctl.board.logs.length : 0;
|
|
555
575
|
useEffect(() => {
|
|
@@ -560,7 +580,7 @@ function MainScreen({ ctl, folder, view, focus, rawLogs, systemLines, agentNames
|
|
|
560
580
|
if (hubFollowTail)
|
|
561
581
|
setHubScroll(0);
|
|
562
582
|
}, [logSeq, agents.length, hubFollowTail]);
|
|
563
|
-
// Scroll helpers
|
|
583
|
+
// Scroll helpers.
|
|
564
584
|
const scrollFocusUp = () => {
|
|
565
585
|
setFocusFollowTail(false);
|
|
566
586
|
setScroll((s) => Math.min(s + 1, maxScroll));
|
|
@@ -585,25 +605,23 @@ function MainScreen({ ctl, folder, view, focus, rawLogs, systemLines, agentNames
|
|
|
585
605
|
return next;
|
|
586
606
|
});
|
|
587
607
|
};
|
|
588
|
-
// Keyboard:
|
|
589
|
-
// When CommandInput is NOT focused, Up/Down scroll the hub or focused agent;
|
|
590
|
-
// when it IS focused, CommandInput's own useInput sees them first (history nav).
|
|
608
|
+
// Keyboard: PgUp/PgDn always scroll hub/focus. Up/Down only scroll when input is inactive.
|
|
591
609
|
useInput((_input, key) => {
|
|
592
|
-
if (key.escape)
|
|
610
|
+
if (key.escape && !settingsOpen)
|
|
593
611
|
onEscape();
|
|
594
612
|
if (focused) {
|
|
595
|
-
if (key.pageUp || key.upArrow)
|
|
613
|
+
if (key.pageUp || (!inputActive && key.upArrow))
|
|
596
614
|
scrollFocusUp();
|
|
597
|
-
if (key.pageDown || key.downArrow)
|
|
615
|
+
if (key.pageDown || (!inputActive && key.downArrow))
|
|
598
616
|
scrollFocusDown();
|
|
599
617
|
}
|
|
600
618
|
else if (view === 'agents') {
|
|
601
|
-
if (key.pageUp || key.upArrow)
|
|
619
|
+
if (key.pageUp || (!inputActive && key.upArrow))
|
|
602
620
|
scrollHubUp();
|
|
603
|
-
if (key.pageDown || key.downArrow)
|
|
621
|
+
if (key.pageDown || (!inputActive && key.downArrow))
|
|
604
622
|
scrollHubDown();
|
|
605
623
|
}
|
|
606
|
-
}, { isActive: !
|
|
624
|
+
}, { isActive: !approval && !question });
|
|
607
625
|
const idleCount = agents.filter((a) => a.state === 'idle').length;
|
|
608
626
|
const workingCount = agents.filter((a) => ['working', 'thinking', 'listening'].includes(a.state)).length;
|
|
609
627
|
const doneCount = agents.filter((a) => a.state === 'done').length;
|
|
@@ -627,7 +645,7 @@ function MainScreen({ ctl, folder, view, focus, rawLogs, systemLines, agentNames
|
|
|
627
645
|
specialists: 'specialists',
|
|
628
646
|
};
|
|
629
647
|
const viewLabel = VIEW_LABEL[view] ?? 'control room';
|
|
630
|
-
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: CHROME.muted, children: ["\u256D", '─'.repeat(cols - 2), "\u256E"] }), _jsxs(Box, { flexDirection: "row", width: cols, children: [_jsx(Text, { color: CHROME.muted, children: "\u2502 " }), _jsxs(Box, { flexDirection: "row", width: cols - 4, justifyContent: "space-between", children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { bold: true, color: BRAND.primary, children: "PARALLEL" }), _jsx(Text, { color: globalDotColor, children: " \u25CF" }), _jsxs(Text, { color: view === 'agents' ? CHROME.muted : BRAND.muted, children: [" ", viewLabel] }), rawLogs ? _jsx(Text, { color: UI.warn, children: " [RAW]" }) : null] }), _jsx(Text, { color: CHROME.muted, children: middleTruncate(folder, folderMax) })] }), _jsx(Text, { color: CHROME.muted, children: " \u2502" })] }), _jsxs(Box, { flexDirection: "row", width: cols, children: [_jsx(Text, { color: CHROME.muted, children: "\u2502 " }), _jsxs(Box, { flexDirection: "row", width: cols - 4, justifyContent: agents.length > 0 ? 'space-between' : 'flex-end', children: [agents.length > 0 ? (_jsx(Box, { flexDirection: "row", children: _jsxs(Text, { children: [_jsxs(Text, { color: CHROME.muted, children: ["\u25C7 ", idleCount, " idle"] }), ' · ', _jsxs(Text, { color: workingCount > 0 ? STATE.working : CHROME.muted, children: ["\u25CF ", workingCount, " active"] }), ' · ', _jsxs(Text, { color: doneCount > 0 ? STATE.done : CHROME.muted, children: ["\u2713 ", doneCount, " done"] }), ' · ', _jsxs(Text, { color: errorCount > 0 ? STATE.error : CHROME.muted, children: ["\u2717 ", errorCount, " err"] })] }) })) : null, _jsxs(Text, { color: CHROME.muted, children: ["v", VERSION] })] }), _jsx(Text, { color: CHROME.muted, children: " \u2502" })] }), _jsxs(Text, { color: CHROME.muted, children: ["\u2570", '─'.repeat(cols - 2), "\u256F"] })] }), _jsx(Text, { children: " " }), _jsx(Box, { height: bodyHeight, overflow: "hidden", flexDirection: "column", children: view === 'settings' ? (_jsx(SettingsPanel, { ctl: ctl, scope: "global", height: bodyHeight, onClose: onEscape })) : view === 'settings-session' ? (_jsx(SettingsPanel, { ctl: ctl, scope: "session", height: bodyHeight, onClose: onEscape })) : view === 'board' ? (_jsx(BoardView, { board: ctl.board })) : view === 'notes' ? (_jsx(NotesView, { board: ctl.board })) : view === 'sessions' ? (_jsx(SessionsView, { projectRoot: ctl.projectRoot })) : view === 'diff' ? (_jsx(DiffView, { board: ctl.board })) : view === 'cost' ? (_jsx(CostView, { board: ctl.board })) : view === 'skills' ? (_jsx(SkillsView, { skills: ctl.getSkills() })) : view === 'specialists' ? (_jsx(SpecialistsView, { specialists: ctl.getSpecialists() })) : view === 'help' ? (_jsx(HelpView, { bodyHeight: bodyHeight })) : agents.length === 0 ? (_jsx(Box, { borderStyle: "single", borderColor: "gray", flexDirection: "column", paddingX: 1, children: _jsx(Text, { color: "gray", children: t('main.empty') }) })) : focused ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(AgentTranscript, { agent: focused, logs: visibleLogs, raw: rawLogs, scrolled: clampedScroll }), !focusFollowTail ? _jsx(Text, { color: UI.warn, children: "Viewing older \u00B7 PgDn to latest" }) : null] })) : (_jsx(AgentHub, { agents: agents, ctl: ctl, cols: cols, scroll: clampedHub, visibleRows: hubRows })) }), systemLines.length > 0 && !settingsOpen && (_jsxs(Box, { flexDirection: "column", children: [agents.length > 0 ? _jsx(Text, { color: UI.muted, bold: true, children: "Session" }) : null, (agents.length > 0
|
|
648
|
+
return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: CHROME.muted, children: ["\u256D", '─'.repeat(cols - 2), "\u256E"] }), _jsxs(Box, { flexDirection: "row", width: cols, children: [_jsx(Text, { color: CHROME.muted, children: "\u2502 " }), _jsxs(Box, { flexDirection: "row", width: cols - 4, justifyContent: "space-between", children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(Text, { bold: true, color: BRAND.primary, children: "PARALLEL" }), _jsx(Text, { color: globalDotColor, children: " \u25CF" }), _jsxs(Text, { color: view === 'agents' ? CHROME.muted : BRAND.muted, children: [" ", viewLabel] }), rawLogs && focused ? _jsx(Text, { color: UI.warn, children: " [RAW]" }) : null] }), _jsx(Text, { color: CHROME.muted, children: middleTruncate(folder, folderMax) })] }), _jsx(Text, { color: CHROME.muted, children: " \u2502" })] }), _jsxs(Box, { flexDirection: "row", width: cols, children: [_jsx(Text, { color: CHROME.muted, children: "\u2502 " }), _jsxs(Box, { flexDirection: "row", width: cols - 4, justifyContent: agents.length > 0 ? 'space-between' : 'flex-end', children: [agents.length > 0 ? (_jsx(Box, { flexDirection: "row", children: _jsxs(Text, { children: [_jsxs(Text, { color: CHROME.muted, children: ["\u25C7 ", idleCount, " idle"] }), ' · ', _jsxs(Text, { color: workingCount > 0 ? STATE.working : CHROME.muted, children: ["\u25CF ", workingCount, " active"] }), ' · ', _jsxs(Text, { color: doneCount > 0 ? STATE.done : CHROME.muted, children: ["\u2713 ", doneCount, " done"] }), ' · ', _jsxs(Text, { color: errorCount > 0 ? STATE.error : CHROME.muted, children: ["\u2717 ", errorCount, " err"] })] }) })) : null, _jsxs(Text, { color: CHROME.muted, children: ["v", VERSION] })] }), _jsx(Text, { color: CHROME.muted, children: " \u2502" })] }), _jsxs(Text, { color: CHROME.muted, children: ["\u2570", '─'.repeat(cols - 2), "\u256F"] })] }), _jsx(Text, { children: " " }), _jsx(Box, { height: bodyHeight, overflow: "hidden", flexDirection: "column", children: view === 'settings' ? (_jsx(SettingsPanel, { ctl: ctl, scope: "global", height: bodyHeight, onClose: onEscape })) : view === 'settings-session' ? (_jsx(SettingsPanel, { ctl: ctl, scope: "session", height: bodyHeight, onClose: onEscape })) : view === 'board' ? (_jsx(BoardView, { board: ctl.board, bodyHeight: bodyHeight })) : view === 'notes' ? (_jsx(NotesView, { board: ctl.board, bodyHeight: bodyHeight })) : view === 'sessions' ? (_jsx(SessionsView, { projectRoot: ctl.projectRoot, bodyHeight: bodyHeight })) : view === 'diff' ? (_jsx(DiffView, { board: ctl.board, bodyHeight: bodyHeight })) : view === 'cost' ? (_jsx(CostView, { board: ctl.board, bodyHeight: bodyHeight })) : view === 'skills' ? (_jsx(SkillsView, { skills: ctl.getSkills(), bodyHeight: bodyHeight })) : view === 'specialists' ? (_jsx(SpecialistsView, { specialists: ctl.getSpecialists(), bodyHeight: bodyHeight })) : view === 'help' ? (_jsx(HelpView, { bodyHeight: bodyHeight })) : agents.length === 0 ? (_jsx(Box, { borderStyle: "single", borderColor: "gray", flexDirection: "column", paddingX: 1, children: _jsx(Text, { color: "gray", children: t('main.empty') }) })) : focused ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(AgentTranscript, { agent: focused, logs: visibleLogs, raw: rawLogs, scrolled: clampedScroll, cols: cols }), !focusFollowTail ? _jsx(Text, { color: UI.warn, children: "Viewing older \u00B7 PgDn to latest" }) : null] })) : (_jsx(AgentHub, { agents: agents, ctl: ctl, cols: cols, scroll: clampedHub, visibleRows: hubRows })) }), systemLines.length > 0 && !settingsOpen && (_jsxs(Box, { flexDirection: "column", children: [agents.length > 0 ? _jsx(Text, { color: UI.muted, bold: true, children: "Session" }) : null, (agents.length > 0
|
|
631
649
|
? systemLines
|
|
632
650
|
.filter((l) => !/^Ready|^Type a task|^⚡ Ready|^Default \/task|^Agent .* launched/.test(l.text))
|
|
633
651
|
.slice(-2)
|
|
@@ -639,7 +657,7 @@ function MainScreen({ ctl, folder, view, focus, rawLogs, systemLines, agentNames
|
|
|
639
657
|
// Split on \n so multiline i18n messages render correctly (Ink <Text> doesn't interpret \n).
|
|
640
658
|
const lines = l.text.split('\n');
|
|
641
659
|
return lines.map((line, j) => (_jsx(Text, { color: levelColor, wrap: "truncate-end", children: line }, `${i}-${j}`)));
|
|
642
|
-
})] })), approval && (_jsx(ApprovalPrompt, { request: approval, pendingCount: ctl.approvals.length, onAnswer: (id, ok, always) => ctl.answerApproval(id, ok, always) })), question && (_jsx(QuestionPrompt, { question: question, pendingCount: ctl.questions.length, onAnswer: (id, answer, auto) => ctl.answerQuestion(id, answer, auto) }, question.id)), _jsx(CommandInput, { active: inputActive, placeholder: focus ? `Message ${focus} or /command` : 'Task mode: describe work to run · /ask question · /plan proposal · / for commands', agentNames: agentNames, agents: agents, onSubmit: onInput, onEscape: onEscape, notify: notify }), _jsx(Text, { children: " " }), _jsxs(Box, { flexDirection: "column", children: [agents.length === 0 ? (_jsxs(Text, { children: [_jsx(Text, { color: BRAND.muted, children: "/ask /task /plan" }), _jsx(Text, { color: CHROME.muted, children: " \u00B7 Tab autocompletes \u00B7 Esc clears" })] })) : null, _jsxs(Text, { children: [_jsx(Text, { color: CHROME.muted, children: "\u2318 Parallel" }), _jsx(Text, { color: CHROME.muted, children: " \u00B7 Shell " }), _jsx(Text, { color: ctl.session.approvalMode === 'ask' ? UI.warn :
|
|
660
|
+
})] })), approval && (_jsx(ApprovalPrompt, { request: approval, pendingCount: ctl.approvals.length, onAnswer: (id, ok, always) => ctl.answerApproval(id, ok, always) })), question && (_jsx(QuestionPrompt, { question: question, pendingCount: ctl.questions.length, onAnswer: (id, answer, auto) => ctl.answerQuestion(id, answer, auto) }, question.id)), _jsx(CommandInput, { active: inputActive, placeholder: focus ? `Message ${focus} or /command` : 'Task mode: describe work to run · /ask question · /plan proposal · / for commands', context: focus ? 'focus' : 'hub', targetAgent: focused?.name, modelLabel: ctl.sessionProvider() ? `${ctl.sessionProvider()?.name}:${ctl.session.model}` : undefined, agentNames: agentNames, agents: agents, onSubmit: onInput, onEscape: onEscape, notify: notify }), _jsx(Text, { children: " " }), _jsxs(Box, { flexDirection: "column", children: [agents.length === 0 ? (_jsxs(Text, { children: [_jsx(Text, { color: BRAND.muted, children: "/ask /task /plan" }), _jsx(Text, { color: CHROME.muted, children: " \u00B7 Tab autocompletes \u00B7 Esc clears" })] })) : null, _jsxs(Text, { children: [_jsx(Text, { color: CHROME.muted, children: "\u2318 Parallel" }), _jsx(Text, { color: CHROME.muted, children: " \u00B7 Shell " }), _jsx(Text, { color: ctl.session.approvalMode === 'ask' ? UI.warn :
|
|
643
661
|
ctl.session.approvalMode === 'yolo' ? UI.danger :
|
|
644
662
|
UI.ok, children: ctl.session.approvalMode === 'auto-safe' ? 'auto' : ctl.session.approvalMode }), _jsxs(Text, { color: CHROME.muted, children: [" \u00B7 Sessions: ", Controller.listSessions(ctl.projectRoot).length] }), ctl.questions.length > 0 ? (_jsxs(Text, { color: UI.warn, children: [" \u00B7 \u2753", ctl.questions.length] })) : null, ctl.approvals.length > 0 ? (_jsxs(Text, { color: UI.warn, children: [" \u00B7 \u23F3", ctl.approvals.length] })) : null, focused ? (_jsxs(Text, { color: BRAND.muted, children: [" \u00B7 \uD83C\uDFAF ", focused.name] })) : null] })] })] }));
|
|
645
663
|
}
|
|
@@ -658,27 +676,33 @@ function groupAgents(agents) {
|
|
|
658
676
|
function AgentHub({ agents, ctl, cols, scroll, visibleRows, }) {
|
|
659
677
|
const groups = groupAgents([...agents].sort((a, b) => STATE_META[a.state].rank - STATE_META[b.state].rank || a.startedAt - b.startedAt));
|
|
660
678
|
let skipped = scroll;
|
|
661
|
-
let
|
|
679
|
+
let renderedAgents = 0;
|
|
680
|
+
let renderedLines = 0;
|
|
662
681
|
const rows = [];
|
|
682
|
+
let full = false;
|
|
663
683
|
for (const group of groups) {
|
|
664
|
-
const groupRows = [];
|
|
665
684
|
for (const agent of group.agents) {
|
|
666
685
|
if (skipped > 0) {
|
|
667
686
|
skipped--;
|
|
668
687
|
continue;
|
|
669
688
|
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
689
|
+
const needsSeparator = rows.length > 0;
|
|
690
|
+
const neededLines = 2 + (needsSeparator ? 1 : 0);
|
|
691
|
+
if (renderedLines + neededLines > visibleRows) {
|
|
692
|
+
full = true;
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
if (needsSeparator) {
|
|
696
|
+
rows.push(_jsx(Text, { color: CHROME.separator, children: '─'.repeat(cols - 2) }, `sep-${group.title}-${agent.id}`));
|
|
697
|
+
renderedLines++;
|
|
698
|
+
}
|
|
699
|
+
renderedAgents++;
|
|
700
|
+
renderedLines += 2;
|
|
701
|
+
rows.push(_jsx(AgentRow, { agent: agent, logs: ctl.board.logsFor(agent.id, 8), cols: cols }, agent.id));
|
|
679
702
|
}
|
|
680
|
-
|
|
703
|
+
if (full)
|
|
704
|
+
break;
|
|
681
705
|
}
|
|
682
|
-
const below = Math.max(0, agents.length - scroll -
|
|
706
|
+
const below = Math.max(0, agents.length - scroll - renderedAgents);
|
|
683
707
|
return (_jsxs(Box, { flexDirection: "column", children: [scroll > 0 ? _jsxs(Text, { color: CHROME.muted, children: ["\u25B2 ", scroll, " older \u00B7 PgDn to latest"] }) : null, rows, below > 0 ? _jsxs(Text, { color: CHROME.muted, children: ["\u25BC ", below, " more \u00B7 PgUp"] }) : null] }));
|
|
684
708
|
}
|
package/dist/ui/AttachApp.js
CHANGED
|
@@ -22,6 +22,12 @@ export function parseAttachCommand(text) {
|
|
|
22
22
|
return { type: 'detach' };
|
|
23
23
|
if (v === '/raw')
|
|
24
24
|
return { type: 'raw' };
|
|
25
|
+
const at = v.match(/^@(\S+)\s+(.+)$/s);
|
|
26
|
+
if (at)
|
|
27
|
+
return { type: 'send', target: at[1], text: at[2].trim() };
|
|
28
|
+
const send = v.match(/^\/send\s+(\S+)\s+(.+)$/s);
|
|
29
|
+
if (send)
|
|
30
|
+
return { type: 'send', target: send[1], text: send[2].trim() };
|
|
25
31
|
const m = v.match(/^\/(ask|a|task|t|plan|p)\s+(.+)$/s);
|
|
26
32
|
if (m) {
|
|
27
33
|
const mode = m[1] === 'ask' || m[1] === 'a' ? 'ask' : m[1] === 'plan' || m[1] === 'p' ? 'plan' : 'task';
|
|
@@ -122,6 +128,10 @@ export function AttachApp({ agentRef, sock }) {
|
|
|
122
128
|
wire({ type: 'spawn', text: cmd.text, mode: cmd.mode });
|
|
123
129
|
return;
|
|
124
130
|
}
|
|
131
|
+
if (cmd.type === 'send') {
|
|
132
|
+
wire({ type: 'send', target: cmd.target, text: cmd.text });
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
125
135
|
wire({ type: 'input', agent: agentRef, text: cmd.text });
|
|
126
136
|
};
|
|
127
137
|
const st = info ? STATE_META[info.state] : null;
|
|
@@ -134,18 +144,18 @@ export function AttachApp({ agentRef, sock }) {
|
|
|
134
144
|
* what used to leave stray blank lines in the native scrollback. */
|
|
135
145
|
_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: info.color, bold: true, children: info.alias || info.name }), ' ', _jsxs(Text, { color: st.color, bold: true, children: [st.mark, " ", st.label] }), ' ', _jsx(Spinner, { color: info.color }), _jsxs(Text, { color: UI.muted, children: [' ', "\u00B7 ", elapsed(info.startedAt), " \u00B7 ", info.steps, " st \u00B7", ' ', Math.round((info.tokensIn + info.tokensOut) / 1000), "k \u00B7", ' '] }), _jsx(Text, { color: UI.ok, children: info.cost === null ? '$-' : fmtCost(info.cost) })] }), info.currentAction ? (_jsxs(Text, { color: info.color, wrap: "truncate-end", children: ["Current ", truncate(info.currentAction, 120)] })) : null, others.length > 0 ? (_jsxs(Text, { color: UI.muted, wrap: "truncate-end", children: ["Others ", ' ', others
|
|
136
146
|
.map((o) => `${o.name} [${stateLabel(o.state)}] ${truncate(o.currentAction || o.task, 40)}`)
|
|
137
|
-
.join(' · ')] })) : null, !raw ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: UI.muted, bold: true, children: t('timeline.activity') }), _jsx(Timeline, { logs: lines.map((l) => l.log) })] })) : null] })) : (
|
|
147
|
+
.join(' · ')] })) : null, !raw ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: UI.muted, bold: true, children: t('timeline.activity') }), _jsx(Timeline, { logs: lines.map((l) => l.log), cols: process.stdout.columns || 100 })] })) : null] })) : (
|
|
138
148
|
/* FULL panel when idle / waiting / done — repaints are rare here. */
|
|
139
149
|
_jsxs(Box, { borderStyle: "single", borderColor: info?.color ?? 'gray', flexDirection: "column", paddingX: 1, marginTop: 1, children: [info && st ? (_jsxs(_Fragment, { children: [_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Box, { children: [banner, _jsxs(Text, { color: st.color, bold: true, children: [' ', st.mark, " ", st.label] })] }), _jsxs(Text, { color: UI.muted, wrap: "truncate-end", children: [middleTruncate(info.model, 18), " \u00B7 ", elapsed(info.startedAt), " \u00B7 ", info.steps, " st \u00B7", ' ', Math.round((info.tokensIn + info.tokensOut) / 1000), "k \u00B7", ' ', info.ctxPct !== undefined ? (_jsxs(Text, { color: info.ctxPct >= 90 ? UI.danger : info.ctxPct >= 70 ? UI.warn : UI.muted, children: [info.ctxPct, "% \u00B7", ' '] })) : null, _jsx(Text, { color: UI.ok, children: info.cost === null ? '$-' : fmtCost(info.cost) })] })] }), _jsxs(Text, { color: UI.muted, wrap: "wrap", children: ["Task ", _jsx(Text, { color: UI.text, children: info.task })] }), info.currentAction ? (_jsxs(Text, { color: info.color, wrap: "truncate-end", children: ["Current ", truncate(info.currentAction, 160)] })) : null, others.length > 0 ? (
|
|
140
150
|
// The session's shared awareness, visible here too: what the
|
|
141
151
|
// OTHER agents are doing right now (live, same feed the agents get).
|
|
142
152
|
_jsxs(Text, { color: UI.muted, wrap: "truncate-end", children: ["Others ", ' ', others
|
|
143
153
|
.map((o) => `${o.name} [${stateLabel(o.state)}] ${truncate(o.currentAction || o.task, 40)}`)
|
|
144
|
-
.join(' · ')] })) : null, !raw ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: UI.muted, bold: true, children: t('timeline.activity') }), _jsx(Timeline, { logs: lines.map((l) => l.log) })] })) : null, info.lastResult && (info.state === 'done' || info.state === 'error' || info.state === 'stopped') ? (_jsxs(Box, { borderStyle: "single", borderColor: "gray", flexDirection: "column", paddingX: 1, marginTop: 1, children: [_jsx(Text, { color: UI.ok, bold: true, children: "Result" }), _jsx(Md, { text: info.lastResult })] })) : null] })) : (_jsx(Text, { color: "gray", children: gone ? t('attach.gone') : t('attach.waiting', { agent: agentRef }) })), gone && info ? _jsx(Text, { color: UI.danger, children: t('attach.gone') }) : null] })), approval ? (_jsx(ApprovalPrompt, { request: { ...approval, agentId: info?.id ?? '', resolve: noop }, pendingCount: 1, onAnswer: (id, ok, always) => {
|
|
154
|
+
.join(' · ')] })) : null, !raw ? (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: UI.muted, bold: true, children: t('timeline.activity') }), _jsx(Timeline, { logs: lines.map((l) => l.log), cols: process.stdout.columns || 100 })] })) : null, info.lastResult && (info.state === 'done' || info.state === 'error' || info.state === 'stopped') ? (_jsxs(Box, { borderStyle: "single", borderColor: "gray", flexDirection: "column", paddingX: 1, marginTop: 1, children: [_jsx(Text, { color: UI.ok, bold: true, children: "Result" }), _jsx(Md, { text: info.lastResult })] })) : null] })) : (_jsx(Text, { color: "gray", children: gone ? t('attach.gone') : t('attach.waiting', { agent: agentRef }) })), gone && info ? _jsx(Text, { color: UI.danger, children: t('attach.gone') }) : null] })), approval ? (_jsx(ApprovalPrompt, { request: { ...approval, agentId: info?.id ?? '', resolve: noop }, pendingCount: 1, onAnswer: (id, ok, always) => {
|
|
145
155
|
wire({ type: 'approve', id, approved: ok, always: !!always });
|
|
146
156
|
setApproval(null);
|
|
147
157
|
} })) : question ? (_jsx(QuestionPrompt, { question: { ...question, agentId: info?.id ?? '', resolve: noop }, pendingCount: 1, onAnswer: (id, answer) => {
|
|
148
158
|
wire({ type: 'answer', id, text: answer });
|
|
149
159
|
setQuestion(null);
|
|
150
|
-
} }, question.id)) : null, _jsx(CommandInput, { active: !gone && !interacting, placeholder: t('attach.placeholder', { agent: info?.name ?? agentRef }), onSubmit: send, onEscape: () => exit() }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { color: "yellowBright", wrap: "truncate-end", children: formatAttachFooter(info) }) })] }));
|
|
160
|
+
} }, question.id)) : null, _jsx(CommandInput, { active: !gone && !interacting, placeholder: t('attach.placeholder', { agent: info?.name ?? agentRef }), context: "attach", targetAgent: info?.name ?? agentRef, modelLabel: info?.model, agentNames: [info?.alias, info?.name, ...others.flatMap((o) => [o.alias, o.name])].filter((n) => Boolean(n)), agents: info ? [info] : [], onSubmit: send, onEscape: () => exit() }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { color: "yellowBright", wrap: "truncate-end", children: formatAttachFooter(info) }) })] }));
|
|
151
161
|
}
|
package/dist/ui/CommandInput.js
CHANGED
|
@@ -14,12 +14,23 @@ const GROUP_LABEL = {
|
|
|
14
14
|
git: 'Git/session',
|
|
15
15
|
other: 'Other',
|
|
16
16
|
};
|
|
17
|
-
|
|
17
|
+
const AGENT_ARG_COMMANDS = new Set(['/focus', '/send', '/attach', '/pause', '/resume', '/stop', '/restore', '/commit']);
|
|
18
|
+
function modeHint(value, context, targetAgent) {
|
|
18
19
|
const v = value.trimStart().toLowerCase();
|
|
19
|
-
if (!v)
|
|
20
|
+
if (!v) {
|
|
21
|
+
if (context === 'focus')
|
|
22
|
+
return `Message ${targetAgent ?? 'focused agent'} · / for hub commands · PgUp/PgDn scroll`;
|
|
23
|
+
if (context === 'attach')
|
|
24
|
+
return `Steer ${targetAgent ?? 'agent'} · /task spawns · @all broadcasts · /quit detaches`;
|
|
20
25
|
return 'Default /task · Tab/→ autocomplete · / for commands';
|
|
21
|
-
|
|
26
|
+
}
|
|
27
|
+
if (!v.startsWith('/')) {
|
|
28
|
+
if (context === 'focus')
|
|
29
|
+
return `Will message ${targetAgent ?? 'focused agent'}`;
|
|
30
|
+
if (context === 'attach')
|
|
31
|
+
return `Will steer ${targetAgent ?? 'attached agent'}`;
|
|
22
32
|
return 'Will launch /task';
|
|
33
|
+
}
|
|
23
34
|
if (v.startsWith('/ask') || v === '/a')
|
|
24
35
|
return 'Ask mode · advice only · no edits';
|
|
25
36
|
if (v.startsWith('/task') || v === '/t')
|
|
@@ -38,7 +49,25 @@ export function bestCommandCompletion(value) {
|
|
|
38
49
|
const cmd = matchCommands(value)[0];
|
|
39
50
|
return cmd ? `${cmd.name} ` : null;
|
|
40
51
|
}
|
|
41
|
-
export function
|
|
52
|
+
export function commandNamesForContext(context) {
|
|
53
|
+
if (context !== 'attach')
|
|
54
|
+
return undefined;
|
|
55
|
+
return ['/ask', '/a', '/task', '/t', '/plan', '/p', '/send', '/raw', '/quit', '/exit', '/detach'];
|
|
56
|
+
}
|
|
57
|
+
export function agentArgCommand(value) {
|
|
58
|
+
const m = value.match(/^(\/\S+)\s+([^\s]*)$/);
|
|
59
|
+
if (!m)
|
|
60
|
+
return null;
|
|
61
|
+
const cmd = m[1].toLowerCase();
|
|
62
|
+
return AGENT_ARG_COMMANDS.has(cmd) ? cmd : null;
|
|
63
|
+
}
|
|
64
|
+
export function completeAgentArgument(value, agent) {
|
|
65
|
+
const cmd = agentArgCommand(value);
|
|
66
|
+
if (!cmd)
|
|
67
|
+
return value;
|
|
68
|
+
return `${cmd} ${agent} `;
|
|
69
|
+
}
|
|
70
|
+
export function CommandInput({ active, placeholder, mask, context = 'hub', targetAgent, modelLabel, commandNames, agentNames = [], agents = [], onSubmit, onEscape, notify, }) {
|
|
42
71
|
const [value, setValue] = useState('');
|
|
43
72
|
const [attachments, setAttachments] = useState([]);
|
|
44
73
|
const [history, setHistory] = useState([]);
|
|
@@ -87,11 +116,26 @@ export function CommandInput({ active, placeholder, mask, agentNames = [], agent
|
|
|
87
116
|
setAttachments((arr) => [...arr, { kind: 'image', n, dataUri: img.dataUri, label: img.label }]);
|
|
88
117
|
notify?.(t('input.imageAdded'));
|
|
89
118
|
};
|
|
90
|
-
const
|
|
119
|
+
const uniqueAgentNames = [...new Set(agentNames.filter(Boolean))];
|
|
120
|
+
const allowedCommands = commandNames ?? commandNamesForContext(context);
|
|
121
|
+
const commandAllowed = (c) => !allowedCommands || allowedCommands.includes(c.name) || c.aliases?.some((a) => allowedCommands.includes(a));
|
|
122
|
+
const cmdSuggestions = value.startsWith('/') && !value.includes(' ') ? matchCommands(value).filter(commandAllowed).slice(0, 10) : [];
|
|
91
123
|
const agentSuggestions = value.startsWith('@') && !value.includes(' ')
|
|
92
|
-
? ['all', ...
|
|
124
|
+
? ['all', ...uniqueAgentNames].filter((n) => n.toLowerCase().startsWith(value.slice(1).toLowerCase())).slice(0, 8)
|
|
93
125
|
: [];
|
|
94
|
-
const
|
|
126
|
+
const argCommand = agentArgCommand(value);
|
|
127
|
+
const argPrefix = argCommand ? value.split(/\s+/)[1] ?? '' : '';
|
|
128
|
+
const argSuggestions = argCommand
|
|
129
|
+
? [
|
|
130
|
+
...(argCommand === '/send' || argCommand === '/pause' || argCommand === '/resume' || argCommand === '/stop' || argCommand === '/commit'
|
|
131
|
+
? ['all']
|
|
132
|
+
: []),
|
|
133
|
+
...uniqueAgentNames,
|
|
134
|
+
]
|
|
135
|
+
.filter((n) => n.toLowerCase().startsWith(argPrefix.toLowerCase()))
|
|
136
|
+
.slice(0, 8)
|
|
137
|
+
: [];
|
|
138
|
+
const suggestionCount = cmdSuggestions.length > 0 ? cmdSuggestions.length : agentSuggestions.length > 0 ? agentSuggestions.length : argSuggestions.length;
|
|
95
139
|
const hasSuggestions = suggestionCount > 0;
|
|
96
140
|
const exactCommand = cmdSuggestions.some((c) => c.name === value.toLowerCase() || c.aliases?.some((a) => a === value.toLowerCase()));
|
|
97
141
|
useEffect(() => {
|
|
@@ -112,6 +156,11 @@ export function CommandInput({ active, placeholder, mask, agentNames = [], agent
|
|
|
112
156
|
setValue('@' + agent + ' ');
|
|
113
157
|
return true;
|
|
114
158
|
}
|
|
159
|
+
if (argSuggestions.length > 0) {
|
|
160
|
+
const agent = argSuggestions[Math.min(selectedSuggestion, argSuggestions.length - 1)];
|
|
161
|
+
setValue(completeAgentArgument(value, agent));
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
115
164
|
return false;
|
|
116
165
|
};
|
|
117
166
|
useInput((input, key) => {
|
|
@@ -211,10 +260,12 @@ export function CommandInput({ active, placeholder, mask, agentNames = [], agent
|
|
|
211
260
|
const shown = mask ? '•'.repeat(value.length) : value;
|
|
212
261
|
const byName = new Map(agents.flatMap((a) => [[a.name, a], [a.alias, a]]));
|
|
213
262
|
const commandIndexes = new Map(cmdSuggestions.map((c, i) => [c.name, i]));
|
|
214
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "gray", wrap: "truncate-end", children: modeHint(value) }), cmdSuggestions.length > 0 && (_jsx(Box, { borderStyle: "single", borderColor: "gray", flexDirection: "column", paddingX: 1, children: groupedCommands(cmdSuggestions).map(([group, commands]) => (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "gray", bold: true, children: GROUP_LABEL[group] ?? group }), commands.map((c) => ((() => {
|
|
263
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", gap: 1, children: [_jsx(Text, { color: "gray", wrap: "truncate-end", children: modeHint(value, context, targetAgent) }), _jsxs(Text, { color: "cyan", children: ["[", context, "]"] }), targetAgent ? _jsxs(Text, { color: "magenta", children: ["[", targetAgent, "]"] }) : null, modelLabel ? _jsxs(Text, { color: "yellow", children: ["[", modelLabel, "]"] }) : null] }), cmdSuggestions.length > 0 && (_jsx(Box, { borderStyle: "single", borderColor: "gray", flexDirection: "column", paddingX: 1, children: groupedCommands(cmdSuggestions).map(([group, commands]) => (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "gray", bold: true, children: GROUP_LABEL[group] ?? group }), commands.map((c) => ((() => {
|
|
215
264
|
const selected = commandIndexes.get(c.name) === selectedSuggestion;
|
|
216
265
|
return (_jsxs(Text, { children: [_jsxs(Text, { color: selected ? 'cyanBright' : 'cyan', bold: true, children: [selected ? '› ' : ' ', c.name.padEnd(14)] }), _jsx(Text, { color: "yellow", children: c.args.padEnd(22) }), _jsx(Text, { color: "gray", children: t(c.descKey) })] }, c.name));
|
|
217
266
|
})()))] }, group))) })), agentSuggestions.length > 0 && (_jsx(Box, { borderStyle: "single", borderColor: "gray", flexDirection: "column", paddingX: 1, children: agentSuggestions.map((n, i) => (_jsxs(Text, { children: [_jsxs(Text, { color: i === selectedSuggestion ? 'cyanBright' : 'cyan', bold: true, children: [i === selectedSuggestion ? '› ' : ' ', "@", n.padEnd(10)] }), _jsx(Text, { color: "gray", children: n === 'all'
|
|
218
267
|
? t('input.atAll')
|
|
219
|
-
: `${byName.get(n)?.state ?? ''} ${byName.get(n)?.mode ? `/${byName.get(n)?.mode}` : ''}` })] }, n))) })),
|
|
268
|
+
: `${byName.get(n)?.state ?? ''} ${byName.get(n)?.mode ? `/${byName.get(n)?.mode}` : ''}` })] }, n))) })), argSuggestions.length > 0 && (_jsxs(Box, { borderStyle: "single", borderColor: "gray", flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: "gray", bold: true, children: "Agents" }), argSuggestions.map((n, i) => (_jsxs(Text, { children: [_jsxs(Text, { color: i === selectedSuggestion ? 'cyanBright' : 'cyan', bold: true, children: [i === selectedSuggestion ? '› ' : ' ', n.padEnd(12)] }), _jsx(Text, { color: "gray", children: n === 'all'
|
|
269
|
+
? t('input.atAll')
|
|
270
|
+
: `${byName.get(n)?.state ?? ''} ${byName.get(n)?.mode ? `/${byName.get(n)?.mode}` : ''}` })] }, n)))] })), attachments.length > 0 && (_jsx(Box, { flexDirection: "row", gap: 1, paddingX: 1, children: attachments.map((a) => (_jsxs(Text, { color: "cyan", backgroundColor: "gray", children: [' ', a.kind === 'paste' ? t('input.attPaste', { n: a.n, lines: a.lines }) : t('input.attImage', { n: a.n, file: a.label }), ' '] }, a.n))) })), _jsxs(Box, { borderStyle: "single", borderColor: active ? 'cyan' : 'gray', paddingX: 1, children: [_jsxs(Text, { color: "cyanBright", bold: true, children: ["\u203A", ' '] }), shown ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: shown }), active && _jsx(Text, { color: "cyanBright", children: "\u2588" })] })) : (_jsxs(_Fragment, { children: [active && _jsx(Text, { color: "cyanBright", children: "\u2588" }), _jsx(Text, { color: "gray", children: placeholder })] }))] })] }));
|
|
220
271
|
}
|