@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/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.3';
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
- setProviderStep({ id: 'presetModel', provider: providerStep.provider });
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
- let models = [...preset.models];
395
- let defaultModel = preset.defaultModel;
396
- try {
397
- const controller = new AbortController();
398
- const timeout = setTimeout(() => controller.abort(), 2000);
399
- const resp = await fetch(preset.baseUrl + '/models', { signal: controller.signal });
400
- clearTimeout(timeout);
401
- if (resp.ok) {
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
- catch {
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) => setProviderStep({ id: 'endpoint', provider: { ...providerStep.provider, baseUrl: url.trim() } }) }) })), 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) => setProviderStep({
456
- id: 'newKey',
457
- provider: {
458
- name: providerStep.name,
459
- baseUrl: providerStep.url,
460
- apiKey: '',
461
- models: [model.trim()],
462
- defaultModel: model.trim(),
463
- },
464
- }) }) })), 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({
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(() => setScroll(0), [focus]);
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(6, bodyHeight - 2);
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 (also used by mouse wheel handler below).
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: Esc / PgUp-PgDn / Up-Down arrows.
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: !inputActive });
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 rendered = 0;
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
- if (rendered >= visibleRows)
671
- continue;
672
- rendered++;
673
- groupRows.push(_jsx(AgentRow, { agent: agent, logs: ctl.board.logsFor(agent.id, 8), cols: cols }, agent.id));
674
- }
675
- if (groupRows.length === 0)
676
- continue;
677
- if (rows.length > 0) {
678
- rows.push(_jsx(Text, { color: CHROME.separator, children: '─'.repeat(cols - 2) }, `sep-${group.title}`));
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
- rows.push(_jsx(Box, { flexDirection: "column", children: groupRows }, group.title));
703
+ if (full)
704
+ break;
681
705
  }
682
- const below = Math.max(0, agents.length - scroll - rendered);
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
  }
@@ -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
  }
@@ -14,12 +14,23 @@ const GROUP_LABEL = {
14
14
  git: 'Git/session',
15
15
  other: 'Other',
16
16
  };
17
- function modeHint(value) {
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
- if (!v.startsWith('/'))
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 CommandInput({ active, placeholder, mask, agentNames = [], agents = [], onSubmit, onEscape, notify }) {
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 cmdSuggestions = value.startsWith('/') && !value.includes(' ') ? matchCommands(value).slice(0, 10) : [];
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', ...agentNames].filter((n) => n.toLowerCase().startsWith(value.slice(1).toLowerCase())).slice(0, 8)
124
+ ? ['all', ...uniqueAgentNames].filter((n) => n.toLowerCase().startsWith(value.slice(1).toLowerCase())).slice(0, 8)
93
125
  : [];
94
- const suggestionCount = cmdSuggestions.length > 0 ? cmdSuggestions.length : agentSuggestions.length;
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))) })), 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 })] }))] })] }));
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
  }