dexto 1.5.7 → 1.5.8

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.
Files changed (86) hide show
  1. package/dist/agents/coding-agent/coding-agent.yml +2 -0
  2. package/dist/analytics/events.d.ts +1 -1
  3. package/dist/analytics/events.d.ts.map +1 -1
  4. package/dist/cli/auth/constants.d.ts +4 -0
  5. package/dist/cli/auth/constants.d.ts.map +1 -1
  6. package/dist/cli/auth/constants.js +4 -0
  7. package/dist/cli/commands/auth/logout.js +2 -2
  8. package/dist/cli/commands/billing/status.d.ts +3 -1
  9. package/dist/cli/commands/billing/status.d.ts.map +1 -1
  10. package/dist/cli/commands/billing/status.js +23 -1
  11. package/dist/cli/commands/interactive-commands/prompt-commands.d.ts.map +1 -1
  12. package/dist/cli/commands/interactive-commands/prompt-commands.js +18 -2
  13. package/dist/cli/commands/list-agents.d.ts.map +1 -1
  14. package/dist/cli/commands/list-agents.js +3 -2
  15. package/dist/cli/commands/setup.d.ts +5 -5
  16. package/dist/cli/commands/setup.d.ts.map +1 -1
  17. package/dist/cli/commands/setup.js +766 -207
  18. package/dist/cli/ink-cli/InkCLIRefactored.d.ts.map +1 -1
  19. package/dist/cli/ink-cli/InkCLIRefactored.js +11 -1
  20. package/dist/cli/ink-cli/components/BackgroundTasksPanel.d.ts +18 -0
  21. package/dist/cli/ink-cli/components/BackgroundTasksPanel.d.ts.map +1 -0
  22. package/dist/cli/ink-cli/components/BackgroundTasksPanel.js +48 -0
  23. package/dist/cli/ink-cli/components/ErrorBoundary.js +1 -1
  24. package/dist/cli/ink-cli/components/Footer.d.ts.map +1 -1
  25. package/dist/cli/ink-cli/components/Footer.js +4 -4
  26. package/dist/cli/ink-cli/components/ResourceAutocomplete.d.ts.map +1 -1
  27. package/dist/cli/ink-cli/components/ResourceAutocomplete.js +150 -41
  28. package/dist/cli/ink-cli/components/StatusBar.d.ts +3 -1
  29. package/dist/cli/ink-cli/components/StatusBar.d.ts.map +1 -1
  30. package/dist/cli/ink-cli/components/StatusBar.js +17 -1
  31. package/dist/cli/ink-cli/components/chat/MessageItem.d.ts.map +1 -1
  32. package/dist/cli/ink-cli/components/chat/MessageItem.js +9 -5
  33. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.d.ts.map +1 -1
  34. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.js +2 -1
  35. package/dist/cli/ink-cli/components/modes/StaticCLI.d.ts.map +1 -1
  36. package/dist/cli/ink-cli/components/modes/StaticCLI.js +2 -1
  37. package/dist/cli/ink-cli/components/overlays/ContextStatsOverlay.js +1 -1
  38. package/dist/cli/ink-cli/components/overlays/CustomModelWizard.d.ts.map +1 -1
  39. package/dist/cli/ink-cli/components/overlays/CustomModelWizard.js +8 -4
  40. package/dist/cli/ink-cli/components/overlays/LogLevelSelector.js +1 -1
  41. package/dist/cli/ink-cli/components/overlays/McpRemoveSelector.js +1 -1
  42. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts +1 -0
  43. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts.map +1 -1
  44. package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.js +144 -41
  45. package/dist/cli/ink-cli/components/overlays/ToolBrowser.d.ts +1 -0
  46. package/dist/cli/ink-cli/components/overlays/ToolBrowser.d.ts.map +1 -1
  47. package/dist/cli/ink-cli/components/overlays/ToolBrowser.js +281 -39
  48. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.d.ts +9 -1
  49. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.d.ts.map +1 -1
  50. package/dist/cli/ink-cli/components/overlays/custom-model-wizard/provider-config.js +35 -9
  51. package/dist/cli/ink-cli/containers/InputContainer.js +2 -2
  52. package/dist/cli/ink-cli/containers/OverlayContainer.d.ts.map +1 -1
  53. package/dist/cli/ink-cli/containers/OverlayContainer.js +134 -23
  54. package/dist/cli/ink-cli/hooks/useAgentEvents.d.ts.map +1 -1
  55. package/dist/cli/ink-cli/hooks/useAgentEvents.js +61 -0
  56. package/dist/cli/ink-cli/hooks/useCLIState.d.ts.map +1 -1
  57. package/dist/cli/ink-cli/hooks/useCLIState.js +3 -0
  58. package/dist/cli/ink-cli/hooks/useInputOrchestrator.d.ts.map +1 -1
  59. package/dist/cli/ink-cli/hooks/useInputOrchestrator.js +8 -0
  60. package/dist/cli/ink-cli/services/processStream.d.ts.map +1 -1
  61. package/dist/cli/ink-cli/services/processStream.js +50 -2
  62. package/dist/cli/ink-cli/state/initialState.d.ts.map +1 -1
  63. package/dist/cli/ink-cli/state/initialState.js +3 -0
  64. package/dist/cli/ink-cli/state/types.d.ts +9 -0
  65. package/dist/cli/ink-cli/state/types.d.ts.map +1 -1
  66. package/dist/cli/ink-cli/utils/llm-provider-display.d.ts +3 -0
  67. package/dist/cli/ink-cli/utils/llm-provider-display.d.ts.map +1 -0
  68. package/dist/cli/ink-cli/utils/llm-provider-display.js +22 -0
  69. package/dist/cli/ink-cli/utils/messageFormatting.d.ts +4 -6
  70. package/dist/cli/ink-cli/utils/messageFormatting.d.ts.map +1 -1
  71. package/dist/cli/ink-cli/utils/messageFormatting.js +77 -9
  72. package/dist/cli/utils/dexto-auth-check.d.ts +7 -7
  73. package/dist/cli/utils/dexto-auth-check.d.ts.map +1 -1
  74. package/dist/cli/utils/dexto-auth-check.js +16 -16
  75. package/dist/cli/utils/options.js +1 -1
  76. package/dist/cli/utils/provider-setup.d.ts +2 -2
  77. package/dist/cli/utils/provider-setup.d.ts.map +1 -1
  78. package/dist/cli/utils/provider-setup.js +10 -2
  79. package/dist/config/cli-overrides.js +1 -1
  80. package/dist/config/effective-llm.d.ts +4 -4
  81. package/dist/config/effective-llm.d.ts.map +1 -1
  82. package/dist/config/effective-llm.js +4 -4
  83. package/dist/index.js +12 -3
  84. package/dist/webui/assets/{index-Dl3mj53P.js → index-Cz2z7NQ8.js} +74 -74
  85. package/dist/webui/index.html +1 -1
  86. package/package.json +7 -7
@@ -1 +1 @@
1
- {"version":3,"file":"InkCLIRefactored.d.ts","sourceRoot":"","sources":["../../../src/cli/ink-cli/InkCLIRefactored.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAWpD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAiB7E,UAAU,WAAW;IACjB,KAAK,EAAE,UAAU,CAAC;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,WAAW,CAAC;IACzB,YAAY,EAAE,wBAAwB,GAAG,IAAI,CAAC;CACjD;AA6CD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAC7B,KAAK,EACL,gBAAgB,EAChB,WAAW,EACX,YAAY,GACf,EAAE,WAAW,2CAgBb;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,kDAAkD;IAClD,UAAU,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACpF,wEAAwE;IACxE,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACvC,KAAK,EAAE,UAAU,EACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,EAC/B,OAAO,GAAE,aAAkB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAuEf"}
1
+ {"version":3,"file":"InkCLIRefactored.d.ts","sourceRoot":"","sources":["../../../src/cli/ink-cli/InkCLIRefactored.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAWpD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AAiB7E,UAAU,WAAW;IACjB,KAAK,EAAE,UAAU,CAAC;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,WAAW,CAAC;IACzB,YAAY,EAAE,wBAAwB,GAAG,IAAI,CAAC;CACjD;AA6CD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAC7B,KAAK,EACL,gBAAgB,EAChB,WAAW,EACX,YAAY,GACf,EAAE,WAAW,2CAgBb;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC1B,kDAAkD;IAClD,UAAU,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAC;IACpF,wEAAwE;IACxE,cAAc,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACvC,KAAK,EAAE,UAAU,EACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,EAC/B,OAAO,GAAE,aAAkB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAsFf"}
@@ -70,7 +70,7 @@ export async function startInkCliRefactored(agent, initialSessionId, options = {
70
70
  };
71
71
  // Initialize sound service from preferences
72
72
  const { SoundNotificationService } = await import('./utils/soundNotification.js');
73
- const { globalPreferencesExist, loadGlobalPreferences } = await import('@dexto/agent-management');
73
+ const { globalPreferencesExist, loadGlobalPreferences, agentPreferencesExist, loadAgentPreferences, } = await import('@dexto/agent-management');
74
74
  let soundService = null;
75
75
  // Initialize sound config with defaults (enabled by default even without preferences file)
76
76
  let soundConfig = {
@@ -97,6 +97,16 @@ export async function startInkCliRefactored(agent, initialSessionId, options = {
97
97
  if (soundConfig.enabled) {
98
98
  soundService = new SoundNotificationService(soundConfig);
99
99
  }
100
+ // Initialize tool preferences (per-agent)
101
+ if (agentPreferencesExist(agent.config.agentId)) {
102
+ try {
103
+ const preferences = await loadAgentPreferences(agent.config.agentId);
104
+ agent.setGlobalDisabledTools(preferences.tools?.disabled ?? []);
105
+ }
106
+ catch (error) {
107
+ agent.logger.debug(`Agent tool preferences could not be loaded: ${error instanceof Error ? error.message : String(error)}`);
108
+ }
109
+ }
100
110
  const inkApp = render(_jsx(InkCLIRefactored, { agent: agent, initialSessionId: initialSessionId, startupInfo: startupInfo, soundService: soundService }), {
101
111
  exitOnCtrlC: false,
102
112
  alternateBuffer: USE_ALTERNATE_BUFFER,
@@ -0,0 +1,18 @@
1
+ /**
2
+ * BackgroundTasksPanel Component
3
+ *
4
+ * Displays background task status in a compact table.
5
+ */
6
+ interface BackgroundTaskItem {
7
+ taskId: string;
8
+ status: 'running' | 'completed' | 'failed' | 'cancelled';
9
+ description?: string;
10
+ }
11
+ interface BackgroundTasksPanelProps {
12
+ tasks: BackgroundTaskItem[];
13
+ isExpanded: boolean;
14
+ isProcessing?: boolean;
15
+ }
16
+ export declare function BackgroundTasksPanel({ tasks, isExpanded, isProcessing, }: BackgroundTasksPanelProps): import("react/jsx-runtime").JSX.Element | null;
17
+ export {};
18
+ //# sourceMappingURL=BackgroundTasksPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackgroundTasksPanel.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/BackgroundTasksPanel.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,UAAU,kBAAkB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,UAAU,yBAAyB;IAC/B,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAsBD,wBAAgB,oBAAoB,CAAC,EACjC,KAAK,EACL,UAAU,EACV,YAAoB,GACvB,EAAE,yBAAyB,kDA+E3B"}
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ function padText(value, width) {
4
+ if (value.length >= width)
5
+ return value.slice(0, width - 1) + '…';
6
+ return value.padEnd(width, ' ');
7
+ }
8
+ function formatStatus(status) {
9
+ switch (status) {
10
+ case 'running':
11
+ return 'running';
12
+ case 'completed':
13
+ return 'done';
14
+ case 'failed':
15
+ return 'failed';
16
+ case 'cancelled':
17
+ return 'cancelled';
18
+ default:
19
+ return status;
20
+ }
21
+ }
22
+ export function BackgroundTasksPanel({ tasks, isExpanded, isProcessing = false, }) {
23
+ if (!isExpanded)
24
+ return null;
25
+ const sortedTasks = [...tasks].sort((a, b) => {
26
+ if (a.status === b.status)
27
+ return 0;
28
+ if (a.status === 'running')
29
+ return -1;
30
+ if (b.status === 'running')
31
+ return 1;
32
+ return a.status.localeCompare(b.status);
33
+ });
34
+ const runningCount = tasks.filter((task) => task.status === 'running').length;
35
+ const totalCount = tasks.length;
36
+ const headerText = `🧵 Background Tasks (${runningCount}/${totalCount} running)`;
37
+ if (totalCount === 0) {
38
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: isProcessing ? undefined : 'round', borderColor: "gray", paddingX: 1, marginX: 1, marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: headerText }), _jsx(Text, { color: "gray", dimColor: true, children: "No background tasks" })] }));
39
+ }
40
+ const statusWidth = 10;
41
+ const idWidth = 14;
42
+ const descWidth = 48;
43
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: isProcessing ? undefined : 'round', borderColor: "gray", paddingX: 1, marginX: 1, marginBottom: 1, children: [_jsxs(Box, { children: [_jsx(Text, { bold: true, color: "cyan", children: headerText }), _jsxs(Text, { color: "gray", dimColor: true, children: [' ', "\u00B7 ctrl+b to hide tasks"] })] }), _jsx(Box, { children: _jsxs(Text, { color: "gray", children: [padText('status', statusWidth), padText('task id', idWidth), padText('description', descWidth)] }) }), sortedTasks.map((task) => {
44
+ const status = formatStatus(task.status);
45
+ const desc = task.description ?? '';
46
+ return (_jsx(Box, { children: _jsxs(Text, { color: task.status === 'running' ? 'yellow' : 'gray', children: [padText(status, statusWidth), padText(task.taskId, idWidth), padText(desc, descWidth)] }) }, task.taskId));
47
+ })] }));
48
+ }
@@ -22,7 +22,7 @@ export class ErrorBoundary extends React.Component {
22
22
  }
23
23
  render() {
24
24
  if (this.state.hasError) {
25
- return (_jsxs(Box, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "red", children: [_jsx(Text, { color: "red", bold: true, children: "\u274C CLI Error" }), _jsx(Text, { color: "red", children: this.state.error?.message || 'Unknown error' }), _jsx(Text, { color: "yellowBright", children: "Press Ctrl+C to exit" })] }));
25
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, borderStyle: "round", borderColor: "red", children: [_jsx(Text, { color: "red", bold: true, children: "CLI Error" }), _jsx(Text, { color: "red", children: this.state.error?.message || 'Unknown error' }), _jsx(Text, { color: "yellowBright", children: "Press Ctrl+C to exit" })] }));
26
26
  }
27
27
  return this.props.children;
28
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Footer.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/Footer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAGnE,UAAU,WAAW;IACjB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB;AAOD;;GAEG;AACH,wBAAgB,MAAM,CAAC,EACnB,KAAK,EACL,SAAS,EACT,SAAS,EACT,GAAG,EACH,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,WAAW,GACd,EAAE,WAAW,2CAkHb"}
1
+ {"version":3,"file":"Footer.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/Footer.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAGnE,UAAU,WAAW;IACjB,KAAK,EAAE,UAAU,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kEAAkE;IAClE,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB;AAOD;;GAEG;AACH,wBAAgB,MAAM,CAAC,EACnB,KAAK,EACL,SAAS,EACT,SAAS,EACT,GAAG,EACH,UAAU,EACV,gBAAgB,EAChB,cAAc,EACd,WAAW,GACd,EAAE,WAAW,2CAiHb"}
@@ -7,7 +7,7 @@ import { useEffect, useState } from 'react';
7
7
  import path from 'node:path';
8
8
  import { Box, Text } from 'ink';
9
9
  import { getModelDisplayName } from '@dexto/core';
10
- import { isDextoAuthEnabled } from '@dexto/agent-management';
10
+ import { getLLMProviderDisplayName } from '../utils/llm-provider-display.js';
11
11
  function getDirectoryName(cwd) {
12
12
  const base = path.basename(cwd);
13
13
  return base || cwd;
@@ -20,8 +20,8 @@ export function Footer({ agent, sessionId, modelName, cwd, branchName, autoAppro
20
20
  const displayModelName = getModelDisplayName(modelName);
21
21
  const [contextLeft, setContextLeft] = useState(null);
22
22
  // Provider is session-scoped because /model can switch LLM per session.
23
- const viaDexto = isDextoAuthEnabled() &&
24
- agent.getCurrentLLMConfig(sessionId ?? undefined).provider === 'dexto';
23
+ const provider = sessionId ? agent.getCurrentLLMConfig(sessionId).provider : null;
24
+ const providerLabel = provider ? getLLMProviderDisplayName(provider) : null;
25
25
  useEffect(() => {
26
26
  if (!sessionId) {
27
27
  setContextLeft(null);
@@ -73,5 +73,5 @@ export function Footer({ agent, sessionId, modelName, cwd, branchName, autoAppro
73
73
  }, [agent, sessionId]);
74
74
  // Shell mode changes the path color to yellow as indicator
75
75
  const pathColor = isShellMode ? 'yellow' : 'blue';
76
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: pathColor, children: displayPath }), branchName && _jsxs(Text, { color: "gray", children: [" (", branchName, ")"] })] }), _jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: displayModelName }), viaDexto && _jsx(Text, { color: "gray", children: " via Dexto" })] })] }), contextLeft && (_jsx(Box, { children: _jsxs(Text, { color: "gray", children: [contextLeft.percentLeft, "% context left"] }) })), isShellMode && (_jsxs(Box, { children: [_jsx(Text, { color: "yellow", bold: true, children: "!" }), _jsx(Text, { color: "gray", children: " for shell mode" })] })), planModeActive && !isShellMode && (_jsxs(Box, { children: [_jsx(Text, { color: "magentaBright", children: "plan mode" }), _jsx(Text, { color: "gray", children: " (shift + tab to cycle)" })] })), autoApproveEdits && !planModeActive && !isShellMode && (_jsxs(Box, { children: [_jsx(Text, { color: "yellowBright", children: "accept edits" }), _jsx(Text, { color: "gray", children: " (shift + tab to cycle)" })] }))] }));
76
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Box, { flexDirection: "row", justifyContent: "space-between", children: [_jsxs(Box, { children: [_jsx(Text, { color: pathColor, children: displayPath }), branchName && _jsxs(Text, { color: "gray", children: [" (", branchName, ")"] })] }), _jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: displayModelName }), providerLabel && _jsxs(Text, { color: "gray", children: [" (", providerLabel, ")"] })] })] }), contextLeft && (_jsx(Box, { children: _jsxs(Text, { color: "gray", children: [contextLeft.percentLeft, "% context left"] }) })), isShellMode && (_jsxs(Box, { children: [_jsx(Text, { color: "yellow", bold: true, children: "!" }), _jsx(Text, { color: "gray", children: " for shell mode" })] })), planModeActive && !isShellMode && (_jsxs(Box, { children: [_jsx(Text, { color: "magentaBright", children: "plan mode" }), _jsx(Text, { color: "gray", children: " (shift + tab to cycle)" })] })), autoApproveEdits && !planModeActive && !isShellMode && (_jsxs(Box, { children: [_jsx(Text, { color: "yellowBright", children: "accept edits" }), _jsx(Text, { color: "gray", children: " (shift + tab to cycle)" })] }))] }));
77
77
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ResourceAutocomplete.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/ResourceAutocomplete.tsx"],"names":[],"mappings":"AAAA,OAAO,KAQN,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kCAAkC,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,WAAW,0BAA0B;IACvC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAED,UAAU,yBAAyB;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACvD,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;CACrB;AA6ED;;GAEG;AACH,QAAA,MAAM,yBAAyB,8GAoQ9B,CAAC;AAEF;;;GAGG;AACH,QAAA,MAAM,oBAAoB,EAErB,OAAO,yBAAyB,CAAC;AAEtC,eAAe,oBAAoB,CAAC"}
1
+ {"version":3,"file":"ResourceAutocomplete.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/ResourceAutocomplete.tsx"],"names":[],"mappings":"AAAA,OAAO,KAQN,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kCAAkC,CAAC;AAC5D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,MAAM,WAAW,0BAA0B;IACvC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAED,UAAU,yBAAyB;IAC/B,SAAS,EAAE,OAAO,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,CAAC;IACvD,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,UAAU,CAAC;CACrB;AAsFD;;GAEG;AACH,QAAA,MAAM,yBAAyB,8GA6W9B,CAAC;AAEF;;;GAGG;AACH,QAAA,MAAM,oBAAoB,EAErB,OAAO,yBAAyB,CAAC;AAEtC,eAAe,oBAAoB,CAAC"}
@@ -1,6 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React, { useState, useEffect, useRef, useMemo, useCallback, forwardRef, useImperativeHandle, } from 'react';
3
3
  import { Box, Text } from 'ink';
4
+ import path from 'path';
5
+ import { centerTruncatePath } from '../utils/messageFormatting.js';
4
6
  /**
5
7
  * Get match score for resource: 0 = no match, 1 = description/URI match, 2 = name includes, 3 = name starts with
6
8
  * Prioritizes name matches over description/URI matches
@@ -11,7 +13,7 @@ function getResourceMatchScore(resource, query) {
11
13
  const lowerQuery = query.toLowerCase();
12
14
  const name = (resource.name || '').toLowerCase();
13
15
  const uri = resource.uri.toLowerCase();
14
- const uriFilename = uri.split('/').pop()?.toLowerCase() || '';
16
+ const uriFilename = uri.split(/[\\/]/).pop()?.toLowerCase() || '';
15
17
  const description = (resource.description || '').toLowerCase();
16
18
  // Highest priority: name starts with query
17
19
  if (name.startsWith(lowerQuery)) {
@@ -139,27 +141,127 @@ const ResourceAutocompleteInner = forwardRef(function ResourceAutocomplete({ isV
139
141
  }
140
142
  return '';
141
143
  }, [searchQuery]);
142
- // Filter and sort resources (no limit - scrolling handles it)
143
- const filteredResources = useMemo(() => {
144
- const matched = resources.filter((r) => matchesQuery(r, mentionQuery));
145
- return sortResources(matched, mentionQuery);
144
+ // Extract directories and create display items (hybrid search)
145
+ const displayItems = useMemo(() => {
146
+ const items = [];
147
+ const directories = new Set();
148
+ // Process each resource to extract paths and directories
149
+ resources.forEach((resource) => {
150
+ // Convert URI to relative path
151
+ let relativePath = resource.uri;
152
+ const rawUri = relativePath.replace(/^(fs|file):\/\//, '');
153
+ if (path.isAbsolute(rawUri)) {
154
+ try {
155
+ const relPath = path.relative(process.cwd(), rawUri);
156
+ if (relPath && !relPath.startsWith('..')) {
157
+ relativePath = relPath;
158
+ }
159
+ else {
160
+ // Outside cwd, use name as fallback
161
+ const uriParts = resource.uri.split(/[\\/]/);
162
+ relativePath =
163
+ resource.name || uriParts[uriParts.length - 1] || resource.uri;
164
+ }
165
+ }
166
+ catch {
167
+ return; // Skip if path conversion fails
168
+ }
169
+ }
170
+ // Add file item
171
+ items.push({
172
+ path: relativePath,
173
+ isDirectory: false,
174
+ resource,
175
+ });
176
+ // Extract all parent directories (1-2 levels deep)
177
+ const segments = relativePath.split(path.sep).filter(Boolean);
178
+ for (let i = 0; i < Math.min(segments.length - 1, 2); i++) {
179
+ const dirPath = segments.slice(0, i + 1).join(path.sep) + path.sep;
180
+ directories.add(dirPath);
181
+ }
182
+ });
183
+ // Add directory items
184
+ directories.forEach((dirPath) => {
185
+ items.push({
186
+ path: dirPath,
187
+ isDirectory: true,
188
+ });
189
+ });
190
+ // Filter by query
191
+ const filtered = items.filter((item) => {
192
+ if (!mentionQuery)
193
+ return true; // Show all when no query
194
+ const lowerQuery = mentionQuery.toLowerCase();
195
+ const lowerPath = item.path.toLowerCase();
196
+ const pathParts = item.path.split(path.sep).filter(Boolean);
197
+ const lastSegment = pathParts[pathParts.length - 1]?.toLowerCase() || '';
198
+ // Match against filename/dirname or full path
199
+ if (lastSegment.includes(lowerQuery) || lowerPath.includes(lowerQuery)) {
200
+ return true;
201
+ }
202
+ // Also match against resource name and description (for files)
203
+ if (item.resource) {
204
+ const lowerName = (item.resource.name || '').toLowerCase();
205
+ const lowerDescription = (item.resource.description || '').toLowerCase();
206
+ if (lowerName.includes(lowerQuery) || lowerDescription.includes(lowerQuery)) {
207
+ return true;
208
+ }
209
+ }
210
+ return false;
211
+ });
212
+ // Sort by relevance
213
+ return filtered.sort((a, b) => {
214
+ if (!mentionQuery) {
215
+ // No query: directories first, then alphabetically
216
+ if (a.isDirectory !== b.isDirectory) {
217
+ return a.isDirectory ? -1 : 1;
218
+ }
219
+ return a.path.localeCompare(b.path);
220
+ }
221
+ const lowerQuery = mentionQuery.toLowerCase();
222
+ const aPathParts = a.path.split(path.sep).filter(Boolean);
223
+ const bPathParts = b.path.split(path.sep).filter(Boolean);
224
+ const aLastSegment = aPathParts[aPathParts.length - 1]?.toLowerCase() || '';
225
+ const bLastSegment = bPathParts[bPathParts.length - 1]?.toLowerCase() || '';
226
+ // Score by match quality
227
+ const aStartsWith = aLastSegment.startsWith(lowerQuery);
228
+ const bStartsWith = bLastSegment.startsWith(lowerQuery);
229
+ const aIncludes = aLastSegment.includes(lowerQuery);
230
+ const bIncludes = bLastSegment.includes(lowerQuery);
231
+ // Priority 1: Prefix matches
232
+ if (aStartsWith && !bStartsWith)
233
+ return -1;
234
+ if (!aStartsWith && bStartsWith)
235
+ return 1;
236
+ // Priority 2: Substring matches
237
+ if (aIncludes && !bIncludes)
238
+ return -1;
239
+ if (!aIncludes && bIncludes)
240
+ return 1;
241
+ // Priority 3: Shallower paths first
242
+ const depthDiff = aPathParts.length - bPathParts.length;
243
+ if (depthDiff !== 0)
244
+ return depthDiff;
245
+ // Priority 4: Alphabetically
246
+ return a.path.localeCompare(b.path);
247
+ });
146
248
  }, [resources, mentionQuery]);
147
249
  // Track items length for reset detection
148
- const prevItemsLengthRef = useRef(filteredResources.length);
149
- const itemsChanged = filteredResources.length !== prevItemsLengthRef.current;
250
+ const prevItemsLengthRef = useRef(displayItems.length);
251
+ const itemsChanged = displayItems.length !== prevItemsLengthRef.current;
150
252
  // Derive clamped selection values during render (always valid, no setState needed)
151
253
  // This prevents the double-render that was causing flickering
152
254
  const selectedIndex = itemsChanged
153
255
  ? 0
154
- : Math.min(selection.index, Math.max(0, filteredResources.length - 1));
256
+ : Math.min(selection.index, Math.max(0, displayItems.length - 1));
155
257
  const scrollOffset = itemsChanged
156
258
  ? 0
157
- : Math.min(selection.offset, Math.max(0, filteredResources.length - MAX_VISIBLE_ITEMS));
259
+ : Math.min(selection.offset, Math.max(0, displayItems.length - MAX_VISIBLE_ITEMS));
158
260
  // Sync state only when items actually changed AND state differs
159
261
  // This effect runs AFTER render, updating state for next user interaction
160
262
  useEffect(() => {
161
263
  if (itemsChanged) {
162
- prevItemsLengthRef.current = filteredResources.length;
264
+ prevItemsLengthRef.current = displayItems.length;
163
265
  // Only setState if values actually differ (prevents unnecessary re-render)
164
266
  if (selection.index !== 0 || selection.offset !== 0) {
165
267
  selectedIndexRef.current = 0;
@@ -169,11 +271,11 @@ const ResourceAutocompleteInner = forwardRef(function ResourceAutocomplete({ isV
169
271
  selectedIndexRef.current = 0;
170
272
  }
171
273
  }
172
- }, [itemsChanged, filteredResources.length, selection.index, selection.offset]);
274
+ }, [itemsChanged, displayItems.length, selection.index, selection.offset]);
173
275
  // Calculate visible items based on scroll offset
174
276
  const visibleResources = useMemo(() => {
175
- return filteredResources.slice(scrollOffset, scrollOffset + MAX_VISIBLE_ITEMS);
176
- }, [filteredResources, scrollOffset, MAX_VISIBLE_ITEMS]);
277
+ return displayItems.slice(scrollOffset, scrollOffset + MAX_VISIBLE_ITEMS);
278
+ }, [displayItems, scrollOffset, MAX_VISIBLE_ITEMS]);
177
279
  // Expose handleInput method via ref
178
280
  useImperativeHandle(ref, () => ({
179
281
  handleInput: (_input, key) => {
@@ -184,7 +286,7 @@ const ResourceAutocompleteInner = forwardRef(function ResourceAutocomplete({ isV
184
286
  onClose();
185
287
  return true;
186
288
  }
187
- const itemsLength = filteredResources.length;
289
+ const itemsLength = displayItems.length;
188
290
  if (itemsLength === 0)
189
291
  return false;
190
292
  if (key.upArrow) {
@@ -195,41 +297,50 @@ const ResourceAutocompleteInner = forwardRef(function ResourceAutocomplete({ isV
195
297
  updateSelection((prev) => (prev + 1) % itemsLength);
196
298
  return true;
197
299
  }
198
- // Tab to load into input (for editing before selection)
300
+ // Tab to load into input (for editing/browsing)
199
301
  if (key.tab) {
200
- const resource = filteredResources[selectedIndexRef.current];
201
- if (!resource)
302
+ const item = displayItems[selectedIndexRef.current];
303
+ if (!item)
202
304
  return false;
203
- // Get the @ position and construct the text to load
204
305
  const atIndex = searchQuery.lastIndexOf('@');
306
+ const reference = item.path; // Already a relative path
205
307
  if (atIndex >= 0) {
206
308
  const before = searchQuery.slice(0, atIndex + 1);
207
- const uriParts = resource.uri.split('/');
208
- const reference = resource.name || uriParts[uriParts.length - 1] || resource.uri;
209
309
  onLoadIntoInput?.(`${before}${reference}`);
210
310
  }
211
311
  else {
212
- // Fallback: just append @resource
213
- const uriParts = resource.uri.split('/');
214
- const reference = resource.name || uriParts[uriParts.length - 1] || resource.uri;
215
312
  onLoadIntoInput?.(`${searchQuery}@${reference}`);
216
313
  }
217
314
  return true;
218
315
  }
219
- // Enter to select
316
+ // Enter to select (directories drill down, files select)
220
317
  if (key.return) {
221
- const resource = filteredResources[selectedIndexRef.current];
222
- if (resource) {
223
- onSelectResource(resource);
224
- return true;
318
+ const item = displayItems[selectedIndexRef.current];
319
+ if (!item)
320
+ return false;
321
+ if (item.isDirectory) {
322
+ // Drill down into directory
323
+ const atIndex = searchQuery.lastIndexOf('@');
324
+ if (atIndex >= 0) {
325
+ const before = searchQuery.slice(0, atIndex + 1);
326
+ onLoadIntoInput?.(`${before}${item.path}`);
327
+ }
328
+ else {
329
+ onLoadIntoInput?.(`${searchQuery}@${item.path}`);
330
+ }
225
331
  }
332
+ else if (item.resource) {
333
+ // Select the file resource
334
+ onSelectResource(item.resource);
335
+ }
336
+ return true;
226
337
  }
227
338
  // Don't consume other keys (typing, backspace, etc.)
228
339
  return false;
229
340
  },
230
341
  }), [
231
342
  isVisible,
232
- filteredResources,
343
+ displayItems,
233
344
  selectedIndexRef,
234
345
  searchQuery,
235
346
  onClose,
@@ -242,22 +353,20 @@ const ResourceAutocompleteInner = forwardRef(function ResourceAutocomplete({ isV
242
353
  if (isLoading) {
243
354
  return (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: "Loading resources..." }) }));
244
355
  }
245
- if (filteredResources.length === 0) {
356
+ if (displayItems.length === 0) {
246
357
  return (_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsx(Text, { color: "gray", children: mentionQuery
247
358
  ? `No resources match "${mentionQuery}"`
248
359
  : 'No resources available. Connect an MCP server or enable internal resources.' }) }));
249
360
  }
250
- const totalItems = filteredResources.length;
251
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { paddingX: 0, paddingY: 0, children: _jsxs(Text, { color: "yellowBright", bold: true, children: ["Resources (", selectedIndex + 1, "/", totalItems, ") - \u2191\u2193 navigate, Tab load, Enter select, Esc close"] }) }), visibleResources.map((resource, visibleIndex) => {
252
- const actualIndex = scrollOffset + visibleIndex;
253
- const isSelected = actualIndex === selectedIndex;
254
- const uriParts = resource.uri.split('/');
255
- const displayName = resource.name || uriParts[uriParts.length - 1] || resource.uri;
256
- const isImage = (resource.mimeType || '').startsWith('image/');
257
- // Truncate URI for display (show last 40 chars with ellipsis)
258
- const truncatedUri = resource.uri.length > 50 ? '…' + resource.uri.slice(-49) : resource.uri;
259
- return (_jsxs(Box, { children: [isImage && _jsx(Text, { color: isSelected ? 'cyan' : 'gray', children: "\uD83D\uDDBC\uFE0F " }), _jsx(Text, { color: isSelected ? 'cyan' : 'white', bold: isSelected, children: displayName }), resource.serverName && (_jsxs(Text, { color: "gray", children: [" [", resource.serverName, "]"] })), _jsxs(Text, { color: "gray", children: [" ", truncatedUri] })] }, resource.uri));
260
- })] }));
361
+ return (_jsx(Box, { flexDirection: "column", paddingLeft: 2, children: visibleResources.map((item, visibleIndex) => {
362
+ const actualIndex = scrollOffset + visibleIndex;
363
+ const isSelected = actualIndex === selectedIndex;
364
+ // Use center truncation for long paths
365
+ const displayPath = centerTruncatePath(item.path, 60);
366
+ // Check if it's an image file
367
+ const isImage = item.resource?.mimeType?.startsWith('image/');
368
+ return (_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? 'cyan' : 'gray', children: isSelected ? '❯ ' : ' ' }), _jsxs(Text, { color: isSelected ? 'cyan' : 'white', bold: isSelected, children: [isImage && '🖼️ ', displayPath, item.resource?.serverName && ` [${item.resource.serverName}]`] })] }, item.path));
369
+ }) }));
261
370
  });
262
371
  /**
263
372
  * Export with React.memo to prevent unnecessary re-renders from parent
@@ -25,6 +25,8 @@ interface StatusBarProps {
25
25
  planModeActive?: boolean;
26
26
  /** Whether accept all edits mode is active */
27
27
  autoApproveEdits?: boolean;
28
+ /** Number of running background tasks */
29
+ backgroundTasksRunning?: number;
28
30
  }
29
31
  /**
30
32
  * Status bar that shows processing state above input area
@@ -34,6 +36,6 @@ interface StatusBarProps {
34
36
  * - Hide spinner during approval wait (user is reviewing, not waiting)
35
37
  * - Only show elapsed time after 30s (avoid visual noise for fast operations)
36
38
  */
37
- export declare function StatusBar({ agent, isProcessing, isThinking, isCompacting, approvalQueueCount, copyModeEnabled, isAwaitingApproval, todoExpanded, hasTodos, planModeActive, autoApproveEdits, }: StatusBarProps): import("react/jsx-runtime").JSX.Element | null;
39
+ export declare function StatusBar({ agent, isProcessing, isThinking, isCompacting, approvalQueueCount, copyModeEnabled, isAwaitingApproval, todoExpanded, hasTodos, planModeActive, autoApproveEdits, backgroundTasksRunning, }: StatusBarProps): import("react/jsx-runtime").JSX.Element | null;
38
40
  export {};
39
41
  //# sourceMappingURL=StatusBar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"StatusBar.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/StatusBar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK9C,UAAU,cAAc;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,oDAAoD;IACpD,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,wCAAwC;IACxC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kCAAkC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,kBAAkB,EAClB,eAAuB,EACvB,kBAA0B,EAC1B,YAAmB,EACnB,QAAgB,EAChB,cAAsB,EACtB,gBAAwB,GAC3B,EAAE,cAAc,kDAuHhB"}
1
+ {"version":3,"file":"StatusBar.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/StatusBar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAK9C,UAAU,cAAc;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,oDAAoD;IACpD,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,wCAAwC;IACxC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,kCAAkC;IAClC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,8CAA8C;IAC9C,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,yCAAyC;IACzC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACnC;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,kBAAkB,EAClB,eAAuB,EACvB,kBAA0B,EAC1B,YAAmB,EACnB,QAAgB,EAChB,cAAsB,EACtB,gBAAwB,EACxB,sBAA0B,GAC7B,EAAE,cAAc,kDAyIhB"}
@@ -21,7 +21,7 @@ import { useTokenCounter } from '../hooks/useTokenCounter.js';
21
21
  * - Hide spinner during approval wait (user is reviewing, not waiting)
22
22
  * - Only show elapsed time after 30s (avoid visual noise for fast operations)
23
23
  */
24
- export function StatusBar({ agent, isProcessing, isThinking, isCompacting, approvalQueueCount, copyModeEnabled = false, isAwaitingApproval = false, todoExpanded = true, hasTodos = false, planModeActive = false, autoApproveEdits = false, }) {
24
+ export function StatusBar({ agent, isProcessing, isThinking, isCompacting, approvalQueueCount, copyModeEnabled = false, isAwaitingApproval = false, todoExpanded = true, hasTodos = false, planModeActive = false, autoApproveEdits = false, backgroundTasksRunning = 0, }) {
25
25
  // Cycle through witty phrases while processing (not during compacting)
26
26
  const { phrase } = usePhraseCycler({ isActive: isProcessing && !isCompacting });
27
27
  // Track elapsed time during processing
@@ -48,11 +48,17 @@ export function StatusBar({ agent, isProcessing, isThinking, isCompacting, appro
48
48
  ? 'ctrl+t to hide tasks'
49
49
  : 'ctrl+t to show tasks'
50
50
  : null;
51
+ const backgroundHint = backgroundTasksRunning > 0 ? 'ctrl+b to view bg tasks' : null;
51
52
  // Show compacting state - yellow/orange color to indicate context management
52
53
  if (isCompacting) {
53
54
  const metaParts = [];
54
55
  if (showTime)
55
56
  metaParts.push(`(${elapsedTime})`);
57
+ if (backgroundTasksRunning > 0) {
58
+ metaParts.push(`${backgroundTasksRunning} bg task${backgroundTasksRunning > 1 ? 's' : ''}`);
59
+ }
60
+ if (backgroundHint)
61
+ metaParts.push(backgroundHint);
56
62
  metaParts.push('Esc to cancel');
57
63
  if (todoHint)
58
64
  metaParts.push(todoHint);
@@ -68,6 +74,11 @@ export function StatusBar({ agent, isProcessing, isThinking, isCompacting, appro
68
74
  metaParts.push(`(${elapsedTime})`);
69
75
  if (tokenCount)
70
76
  metaParts.push(tokenCount);
77
+ if (backgroundTasksRunning > 0) {
78
+ metaParts.push(`${backgroundTasksRunning} bg task${backgroundTasksRunning > 1 ? 's' : ''}`);
79
+ }
80
+ if (backgroundHint)
81
+ metaParts.push(backgroundHint);
71
82
  metaParts.push('Esc to cancel');
72
83
  if (todoHint)
73
84
  metaParts.push(todoHint);
@@ -82,6 +93,11 @@ export function StatusBar({ agent, isProcessing, isThinking, isCompacting, appro
82
93
  metaParts.push(`(${elapsedTime})`);
83
94
  if (tokenCount)
84
95
  metaParts.push(tokenCount);
96
+ if (backgroundTasksRunning > 0) {
97
+ metaParts.push(`${backgroundTasksRunning} bg task${backgroundTasksRunning > 1 ? 's' : ''}`);
98
+ }
99
+ if (backgroundHint)
100
+ metaParts.push(backgroundHint);
85
101
  metaParts.push('Esc to cancel');
86
102
  if (todoHint)
87
103
  metaParts.push(todoHint);
@@ -1 +1 @@
1
- {"version":3,"file":"MessageItem.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/chat/MessageItem.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EACR,OAAO,EAUV,MAAM,sBAAsB,CAAC;AAmD9B,UAAU,gBAAgB;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,mEACc,gBAAgB,6CA+NrD,CAAC"}
1
+ {"version":3,"file":"MessageItem.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/chat/MessageItem.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EACR,OAAO,EAUV,MAAM,sBAAsB,CAAC;AAoD9B,UAAU,gBAAgB;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,mEACc,gBAAgB,6CAyOrD,CAAC"}
@@ -11,6 +11,7 @@ import { ConfigBox, StatsBox, HelpBox, SessionListBox, SessionHistoryBox, LogCon
11
11
  import { ToolResultRenderer } from '../renderers/index.js';
12
12
  import { MarkdownText } from '../shared/MarkdownText.js';
13
13
  import { ToolIcon } from './ToolIcon.js';
14
+ import { formatToolResultPreview } from '../../utils/messageFormatting.js';
14
15
  /**
15
16
  * Strip <plan-mode>...</plan-mode> tags from content.
16
17
  * Plan mode instructions are injected for the LLM but should not be shown in the UI.
@@ -120,10 +121,13 @@ export const MessageItem = memo(({ message, terminalWidth = 80 }) => {
120
121
  const isPending = message.toolStatus === 'pending' || message.toolStatus === 'pending_approval';
121
122
  // Check for sub-agent progress data
122
123
  const subAgentProgress = message.subAgentProgress;
124
+ const contentLines = message.content.split('\n');
125
+ const headerLine = contentLines[0] ?? '';
126
+ const subHeaderLine = contentLines.length > 1 ? contentLines[1] : '';
123
127
  // Parse tool name and args for bold formatting: "ToolName(args)" → bold name + normal args
124
- const parenIndex = message.content.indexOf('(');
125
- const toolName = parenIndex > 0 ? message.content.slice(0, parenIndex) : message.content;
126
- const toolArgs = parenIndex > 0 ? message.content.slice(parenIndex) : '';
128
+ const parenIndex = headerLine.indexOf('(');
129
+ const toolName = parenIndex > 0 ? headerLine.slice(0, parenIndex) : headerLine;
130
+ const toolArgs = parenIndex > 0 ? headerLine.slice(parenIndex) : '';
127
131
  // Build the full tool header text for wrapping
128
132
  // Don't include status suffix if we have sub-agent progress (it shows its own status)
129
133
  const statusSuffix = subAgentProgress
@@ -143,10 +147,10 @@ export const MessageItem = memo(({ message, terminalWidth = 80 }) => {
143
147
  trim: false,
144
148
  });
145
149
  const toolLines = wrappedToolText.split('\n');
146
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, width: terminalWidth, children: [toolLines.map((line, i) => (_jsxs(Box, { flexDirection: "row", children: [i === 0 ? (_jsx(ToolIcon, { status: message.toolStatus || 'finished', isError: message.isError ?? false })) : (_jsx(Text, { children: ' ' })), _jsx(Text, { children: i === 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: line.slice(0, toolName.length) }), _jsx(Text, { children: line.slice(toolName.length) })] })) : (line) })] }, i))), subAgentProgress && isRunning && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: "gray", children: ["\u2514\u2500 ", subAgentProgress.toolsCalled, " tool", subAgentProgress.toolsCalled !== 1 ? 's' : '', " called | Current:", ' ', subAgentProgress.currentTool, subAgentProgress.tokenUsage &&
150
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, width: terminalWidth, children: [toolLines.map((line, i) => (_jsxs(Box, { flexDirection: "row", children: [i === 0 ? (_jsx(ToolIcon, { status: message.toolStatus || 'finished', isError: message.isError ?? false })) : (_jsx(Text, { children: ' ' })), _jsx(Text, { children: i === 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: line.slice(0, toolName.length) }), _jsx(Text, { children: line.slice(toolName.length) })] })) : (line) })] }, i))), subHeaderLine && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", children: subHeaderLine }) })), subAgentProgress && isRunning && (_jsx(Box, { marginLeft: 2, children: _jsxs(Text, { color: "gray", children: ["\u2514\u2500 ", subAgentProgress.toolsCalled, " tool", subAgentProgress.toolsCalled !== 1 ? 's' : '', " called | Current:", ' ', subAgentProgress.currentTool, subAgentProgress.tokenUsage &&
147
151
  subAgentProgress.tokenUsage.total > 0
148
152
  ? ` | ${subAgentProgress.tokenUsage.total.toLocaleString()} tokens`
149
- : ''] }) })), hasStructuredDisplay ? (_jsx(ToolResultRenderer, { display: message.toolDisplayData, content: message.toolContent })) : (message.toolResult && (_jsx(Box, { flexDirection: "column", children: _jsxs(Text, { color: "gray", children: [" \u23BF ", message.toolResult] }) })))] }));
153
+ : ''] }) })), hasStructuredDisplay ? (_jsx(ToolResultRenderer, { display: message.toolDisplayData, content: message.toolContent })) : (message.toolResult && (_jsx(Box, { flexDirection: "column", children: _jsxs(Text, { color: "gray", children: ["\u23BF ", formatToolResultPreview(message.toolResult)] }) })))] }));
150
154
  }
151
155
  // System message: Compact gray text
152
156
  return (_jsx(Box, { flexDirection: "column", marginBottom: 1, width: terminalWidth, children: _jsx(Text, { color: "gray", children: message.content }) }));
@@ -1 +1 @@
1
- {"version":3,"file":"AlternateBufferCLI.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/modes/AlternateBufferCLI.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;AA6BjE,UAAU,uBAAuB;IAC7B,KAAK,EAAE,UAAU,CAAC;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,WAAW,CAAC;IACzB,2EAA2E;IAC3E,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,EAC/B,KAAK,EACL,gBAAgB,EAChB,WAAW,EACX,kBAAkB,EAClB,YAAmB,GACtB,EAAE,uBAAuB,2CAiVzB"}
1
+ {"version":3,"file":"AlternateBufferCLI.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/modes/AlternateBufferCLI.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,OAAO,KAAK,EAAW,WAAW,EAAE,MAAM,sBAAsB,CAAC;AA8BjE,UAAU,uBAAuB;IAC7B,KAAK,EAAE,UAAU,CAAC;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,WAAW,CAAC;IACzB,2EAA2E;IAC3E,kBAAkB,CAAC,EAAE,MAAM,IAAI,CAAC;IAChC,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,kBAAkB,CAAC,EAC/B,KAAK,EACL,gBAAgB,EAChB,WAAW,EACX,kBAAkB,EAClB,YAAmB,GACtB,EAAE,uBAAuB,2CAyVzB"}
@@ -25,6 +25,7 @@ import { StatusBar } from '../StatusBar.js';
25
25
  import { HistorySearchBar } from '../HistorySearchBar.js';
26
26
  import { Footer } from '../Footer.js';
27
27
  import { TodoPanel } from '../TodoPanel.js';
28
+ import { BackgroundTasksPanel } from '../BackgroundTasksPanel.js';
28
29
  import { VirtualizedList, SCROLL_TO_ITEM_END, } from '../shared/VirtualizedList.js';
29
30
  // Containers
30
31
  import { InputContainer } from '../../containers/InputContainer.js';
@@ -175,5 +176,5 @@ export function AlternateBufferCLI({ agent, initialSessionId, startupInfo, onSel
175
176
  return 'header';
176
177
  return item.message.id;
177
178
  }, []);
178
- return (_jsxs(Box, { flexDirection: "column", height: terminalHeight, children: [_jsx(Box, { ref: listContainerRef, flexGrow: 1, flexShrink: 1, minHeight: 0, children: _jsx(VirtualizedList, { ref: listRef, data: listData, renderItem: renderListItem, estimatedItemHeight: estimateItemHeight, keyExtractor: getItemKey, initialScrollIndex: SCROLL_TO_ITEM_END, initialScrollOffsetInIndex: SCROLL_TO_ITEM_END }) }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsx(StatusBar, { agent: agent, isProcessing: ui.isProcessing, isThinking: ui.isThinking, isCompacting: ui.isCompacting, approvalQueueCount: approvalQueue.length, copyModeEnabled: ui.copyModeEnabled, isAwaitingApproval: approval !== null, todoExpanded: ui.todoExpanded, hasTodos: todos.some((t) => t.status !== 'completed'), planModeActive: ui.planModeActive, autoApproveEdits: ui.autoApproveEdits }), _jsx(TodoPanel, { todos: todos, isExpanded: ui.todoExpanded, isProcessing: ui.isProcessing }), selectionHintVisible && (_jsx(Box, { paddingX: 1, children: _jsx(Text, { color: "yellowBright", children: "\uD83D\uDCA1 Tip: Hold Option (\u2325) and click to select text, or press Ctrl+S to toggle copy mode" }) })), _jsx(QueuedMessagesDisplay, { messages: queuedMessages }), _jsx(InputContainer, { ref: inputContainerRef, buffer: buffer, input: input, ui: ui, session: session, approval: approval, queuedMessages: queuedMessages, setInput: setInput, setUi: setUi, setSession: setSession, setMessages: setMessages, setPendingMessages: setPendingMessages, setDequeuedBuffer: setDequeuedBuffer, setQueuedMessages: setQueuedMessages, setApproval: setApproval, setApprovalQueue: setApprovalQueue, setTodos: setTodos, agent: agent, inputService: inputService, onKeyboardScroll: handleKeyboardScroll, useStreaming: useStreaming }), _jsx(OverlayContainer, { ref: overlayContainerRef, ui: ui, input: input, session: session, approval: approval, setInput: setInput, setUi: setUi, setSession: setSession, setMessages: setMessages, setApproval: setApproval, setApprovalQueue: setApprovalQueue, agent: agent, inputService: inputService, buffer: buffer, onSubmitPromptCommand: handleSubmitPromptCommand }), ui.exitWarningShown && (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { color: "yellowBright", bold: true, children: "\u26A0 Press Ctrl+C again to exit" }), _jsx(Text, { color: "gray", children: " (or press any key to cancel)" })] })), _jsx(Footer, { agent: agent, sessionId: session.id, modelName: session.modelName, cwd: process.cwd(), ...(branchName ? { branchName } : {}), autoApproveEdits: ui.autoApproveEdits, planModeActive: ui.planModeActive, isShellMode: buffer.text.startsWith('!') }), ui.historySearch.isActive && (_jsx(HistorySearchBar, { query: ui.historySearch.query, hasMatch: historySearchHasMatch }))] })] }));
179
+ return (_jsxs(Box, { flexDirection: "column", height: terminalHeight, children: [_jsx(Box, { ref: listContainerRef, flexGrow: 1, flexShrink: 1, minHeight: 0, children: _jsx(VirtualizedList, { ref: listRef, data: listData, renderItem: renderListItem, estimatedItemHeight: estimateItemHeight, keyExtractor: getItemKey, initialScrollIndex: SCROLL_TO_ITEM_END, initialScrollOffsetInIndex: SCROLL_TO_ITEM_END }) }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [_jsx(StatusBar, { agent: agent, isProcessing: ui.isProcessing, isThinking: ui.isThinking, isCompacting: ui.isCompacting, approvalQueueCount: approvalQueue.length, copyModeEnabled: ui.copyModeEnabled, isAwaitingApproval: approval !== null, todoExpanded: ui.todoExpanded, hasTodos: todos.some((t) => t.status !== 'completed'), planModeActive: ui.planModeActive, autoApproveEdits: ui.autoApproveEdits, backgroundTasksRunning: ui.backgroundTasksRunning }), _jsx(BackgroundTasksPanel, { tasks: ui.backgroundTasks, isExpanded: ui.backgroundTasksExpanded, isProcessing: ui.isProcessing }), _jsx(TodoPanel, { todos: todos, isExpanded: ui.todoExpanded, isProcessing: ui.isProcessing }), selectionHintVisible && (_jsx(Box, { paddingX: 1, children: _jsx(Text, { color: "yellowBright", children: "\uD83D\uDCA1 Tip: Hold Option (\u2325) and click to select text, or press Ctrl+S to toggle copy mode" }) })), _jsx(QueuedMessagesDisplay, { messages: queuedMessages }), _jsx(InputContainer, { ref: inputContainerRef, buffer: buffer, input: input, ui: ui, session: session, approval: approval, queuedMessages: queuedMessages, setInput: setInput, setUi: setUi, setSession: setSession, setMessages: setMessages, setPendingMessages: setPendingMessages, setDequeuedBuffer: setDequeuedBuffer, setQueuedMessages: setQueuedMessages, setApproval: setApproval, setApprovalQueue: setApprovalQueue, setTodos: setTodos, agent: agent, inputService: inputService, onKeyboardScroll: handleKeyboardScroll, useStreaming: useStreaming }), _jsx(OverlayContainer, { ref: overlayContainerRef, ui: ui, input: input, session: session, approval: approval, setInput: setInput, setUi: setUi, setSession: setSession, setMessages: setMessages, setApproval: setApproval, setApprovalQueue: setApprovalQueue, agent: agent, inputService: inputService, buffer: buffer, onSubmitPromptCommand: handleSubmitPromptCommand }), ui.exitWarningShown && (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { color: "yellowBright", bold: true, children: "\u26A0 Press Ctrl+C again to exit" }), _jsx(Text, { color: "gray", children: " (or press any key to cancel)" })] })), _jsx(Footer, { agent: agent, sessionId: session.id, modelName: session.modelName, cwd: process.cwd(), ...(branchName ? { branchName } : {}), autoApproveEdits: ui.autoApproveEdits, planModeActive: ui.planModeActive, isShellMode: buffer.text.startsWith('!') }), ui.historySearch.isActive && (_jsx(HistorySearchBar, { query: ui.historySearch.query, hasMatch: historySearchHasMatch }))] })] }));
179
180
  }
@@ -1 +1 @@
1
- {"version":3,"file":"StaticCLI.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/modes/StaticCLI.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAM9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAoBxD,UAAU,cAAc;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,WAAW,CAAC;IACzB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,gBAAgB,EAChB,WAAW,EACX,YAAmB,GACtB,EAAE,cAAc,2CA2OhB"}
1
+ {"version":3,"file":"StaticCLI.d.ts","sourceRoot":"","sources":["../../../../../src/cli/ink-cli/components/modes/StaticCLI.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAM9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAqBxD,UAAU,cAAc;IACpB,KAAK,EAAE,UAAU,CAAC;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,WAAW,CAAC;IACzB,6DAA6D;IAC7D,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,gBAAgB,EAChB,WAAW,EACX,YAAmB,GACtB,EAAE,cAAc,2CAmPhB"}