dexto 1.6.0 → 1.6.1
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/agents/coding-agent/coding-agent.yml +3 -1
- package/dist/cli/assets/sounds/SOURCES.md +35 -0
- package/dist/cli/assets/sounds/boot.wav +0 -0
- package/dist/cli/assets/sounds/chime.wav +0 -0
- package/dist/cli/assets/sounds/coin.wav +0 -0
- package/dist/cli/assets/sounds/confirm.wav +0 -0
- package/dist/cli/assets/sounds/levelup.wav +0 -0
- package/dist/cli/assets/sounds/ping.wav +0 -0
- package/dist/cli/assets/sounds/powerup.wav +0 -0
- package/dist/cli/assets/sounds/startup.wav +0 -0
- package/dist/cli/assets/sounds/success.wav +0 -0
- package/dist/cli/assets/sounds/treasure.wav +0 -0
- package/dist/cli/assets/sounds/win.wav +0 -0
- package/dist/cli/commands/interactive-commands/exit-handler.d.ts +12 -0
- package/dist/cli/commands/interactive-commands/exit-handler.d.ts.map +1 -0
- package/dist/cli/commands/interactive-commands/exit-handler.js +20 -0
- package/dist/cli/commands/interactive-commands/exit-stats.d.ts +24 -0
- package/dist/cli/commands/interactive-commands/exit-stats.d.ts.map +1 -0
- package/dist/cli/commands/interactive-commands/exit-stats.js +17 -0
- package/dist/cli/commands/interactive-commands/general-commands.d.ts.map +1 -1
- package/dist/cli/commands/interactive-commands/general-commands.js +53 -3
- package/dist/cli/commands/interactive-commands/prompt-commands.d.ts.map +1 -1
- package/dist/cli/commands/interactive-commands/prompt-commands.js +12 -67
- package/dist/cli/commands/interactive-commands/session/session-commands.d.ts.map +1 -1
- package/dist/cli/commands/interactive-commands/session/session-commands.js +0 -2
- package/dist/cli/commands/interactive-commands/system/system-commands.d.ts +1 -13
- package/dist/cli/commands/interactive-commands/system/system-commands.d.ts.map +1 -1
- package/dist/cli/commands/interactive-commands/system/system-commands.js +45 -54
- package/dist/cli/ink-cli/InkCLIRefactored.d.ts.map +1 -1
- package/dist/cli/ink-cli/InkCLIRefactored.js +132 -21
- package/dist/cli/ink-cli/components/ApprovalPrompt.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/ApprovalPrompt.js +74 -20
- package/dist/cli/ink-cli/components/ElicitationForm.d.ts +5 -3
- package/dist/cli/ink-cli/components/ElicitationForm.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/ElicitationForm.js +414 -180
- package/dist/cli/ink-cli/components/ResourceAutocomplete.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/ResourceAutocomplete.js +20 -11
- package/dist/cli/ink-cli/components/SlashCommandAutocomplete.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/SlashCommandAutocomplete.js +47 -67
- package/dist/cli/ink-cli/components/StatusBar.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/StatusBar.js +10 -4
- package/dist/cli/ink-cli/components/base/BaseSelector.d.ts +2 -1
- package/dist/cli/ink-cli/components/base/BaseSelector.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/base/BaseSelector.js +37 -27
- package/dist/cli/ink-cli/components/chat/Header.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/chat/Header.js +1 -1
- package/dist/cli/ink-cli/components/chat/MessageItem.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/chat/MessageItem.js +3 -1
- package/dist/cli/ink-cli/components/chat/ToolIcon.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/chat/ToolIcon.js +5 -15
- package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/chat/styled-boxes/LogConfigBox.js +1 -1
- package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.js +4 -2
- package/dist/cli/ink-cli/components/modes/StaticCLI.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/modes/StaticCLI.js +9 -2
- package/dist/cli/ink-cli/components/overlays/CommandOutputOverlay.d.ts +13 -0
- package/dist/cli/ink-cli/components/overlays/CommandOutputOverlay.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/CommandOutputOverlay.js +60 -0
- package/dist/cli/ink-cli/components/overlays/LogLevelSelector.js +1 -1
- package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/ModelSelectorRefactored.js +213 -100
- package/dist/cli/ink-cli/components/overlays/PromptList.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/PromptList.js +12 -16
- package/dist/cli/ink-cli/components/overlays/SoundsSelector.d.ts +21 -0
- package/dist/cli/ink-cli/components/overlays/SoundsSelector.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/overlays/SoundsSelector.js +566 -0
- package/dist/cli/ink-cli/components/overlays/ToolBrowser.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/ToolBrowser.js +94 -39
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/overlays/custom-model-wizard/LocalModelWizard.js +8 -13
- package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.d.ts +3 -3
- package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/renderers/FilePreviewRenderer.js +6 -5
- package/dist/cli/ink-cli/components/renderers/FileRenderer.d.ts +3 -1
- package/dist/cli/ink-cli/components/renderers/FileRenderer.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/renderers/FileRenderer.js +18 -7
- package/dist/cli/ink-cli/components/renderers/ShellRenderer.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/renderers/ShellRenderer.js +7 -17
- package/dist/cli/ink-cli/components/renderers/index.d.ts.map +1 -1
- package/dist/cli/ink-cli/components/renderers/index.js +1 -1
- package/dist/cli/ink-cli/components/shared/FocusOverlayFrame.d.ts +7 -0
- package/dist/cli/ink-cli/components/shared/FocusOverlayFrame.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/shared/FocusOverlayFrame.js +8 -0
- package/dist/cli/ink-cli/components/shared/HintBar.d.ts +6 -0
- package/dist/cli/ink-cli/components/shared/HintBar.d.ts.map +1 -0
- package/dist/cli/ink-cli/components/shared/HintBar.js +6 -0
- package/dist/cli/ink-cli/constants/spinnerFrames.d.ts +2 -0
- package/dist/cli/ink-cli/constants/spinnerFrames.d.ts.map +1 -0
- package/dist/cli/ink-cli/constants/spinnerFrames.js +1 -0
- package/dist/cli/ink-cli/constants/tips.d.ts.map +1 -1
- package/dist/cli/ink-cli/constants/tips.js +1 -0
- package/dist/cli/ink-cli/containers/InputContainer.d.ts.map +1 -1
- package/dist/cli/ink-cli/containers/InputContainer.js +19 -15
- package/dist/cli/ink-cli/containers/OverlayContainer.d.ts.map +1 -1
- package/dist/cli/ink-cli/containers/OverlayContainer.js +21 -5
- package/dist/cli/ink-cli/hooks/useAnimationTick.d.ts +11 -0
- package/dist/cli/ink-cli/hooks/useAnimationTick.d.ts.map +1 -0
- package/dist/cli/ink-cli/hooks/useAnimationTick.js +54 -0
- package/dist/cli/ink-cli/hooks/useCLIState.d.ts.map +1 -1
- package/dist/cli/ink-cli/hooks/useCLIState.js +1 -0
- package/dist/cli/ink-cli/services/processStream.d.ts.map +1 -1
- package/dist/cli/ink-cli/services/processStream.js +17 -8
- package/dist/cli/ink-cli/state/initialState.d.ts.map +1 -1
- package/dist/cli/ink-cli/state/initialState.js +1 -0
- package/dist/cli/ink-cli/state/types.d.ts +13 -1
- package/dist/cli/ink-cli/state/types.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/commandOverlays.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/commandOverlays.js +1 -0
- package/dist/cli/ink-cli/utils/elicitationSchema.d.ts +11 -0
- package/dist/cli/ink-cli/utils/elicitationSchema.d.ts.map +1 -0
- package/dist/cli/ink-cli/utils/elicitationSchema.js +80 -0
- package/dist/cli/ink-cli/utils/index.d.ts +1 -1
- package/dist/cli/ink-cli/utils/index.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/index.js +1 -1
- package/dist/cli/ink-cli/utils/messageFormatting.d.ts +2 -17
- package/dist/cli/ink-cli/utils/messageFormatting.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/messageFormatting.js +22 -128
- package/dist/cli/ink-cli/utils/overlayPresentation.d.ts +19 -0
- package/dist/cli/ink-cli/utils/overlayPresentation.d.ts.map +1 -0
- package/dist/cli/ink-cli/utils/overlayPresentation.js +33 -0
- package/dist/cli/ink-cli/utils/overlaySizing.d.ts +19 -0
- package/dist/cli/ink-cli/utils/overlaySizing.d.ts.map +1 -0
- package/dist/cli/ink-cli/utils/overlaySizing.js +11 -0
- package/dist/cli/ink-cli/utils/soundNotification.d.ts +19 -13
- package/dist/cli/ink-cli/utils/soundNotification.d.ts.map +1 -1
- package/dist/cli/ink-cli/utils/soundNotification.js +120 -97
- package/dist/utils/session-logger-factory.d.ts.map +1 -1
- package/dist/utils/session-logger-factory.js +17 -2
- package/dist/webui/assets/{index-DwtueA8l.js → index-CKhumsZA.js} +135 -135
- package/dist/webui/index.html +1 -1
- package/package.json +11 -11
|
@@ -25,6 +25,13 @@ import { getStartupInfo } from './utils/messageFormatting.js';
|
|
|
25
25
|
// Toggle this to switch between modes for testing
|
|
26
26
|
//const USE_ALTERNATE_BUFFER = true;
|
|
27
27
|
const USE_ALTERNATE_BUFFER = false;
|
|
28
|
+
function formatCost(c) {
|
|
29
|
+
if (c < 0.01)
|
|
30
|
+
return `$${c.toFixed(4)}`;
|
|
31
|
+
if (c < 1)
|
|
32
|
+
return `$${c.toFixed(3)}`;
|
|
33
|
+
return `$${c.toFixed(2)}`;
|
|
34
|
+
}
|
|
28
35
|
/**
|
|
29
36
|
* Inner component that wraps the mode-specific component with providers
|
|
30
37
|
*/
|
|
@@ -68,45 +75,74 @@ export async function startInkCliRefactored(agent, initialSessionId, options = {
|
|
|
68
75
|
updateInfo: options.updateInfo,
|
|
69
76
|
needsAgentSync: options.needsAgentSync,
|
|
70
77
|
};
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
// Load preferences helpers (non-fatal if unavailable)
|
|
79
|
+
let globalPreferencesExistFn = () => false;
|
|
80
|
+
let loadGlobalPreferencesFn = null;
|
|
81
|
+
let agentPreferencesExistFn = () => false;
|
|
82
|
+
let loadAgentPreferencesFn = null;
|
|
83
|
+
try {
|
|
84
|
+
const agentManagement = await import('@dexto/agent-management');
|
|
85
|
+
globalPreferencesExistFn = agentManagement.globalPreferencesExist;
|
|
86
|
+
loadGlobalPreferencesFn = agentManagement.loadGlobalPreferences;
|
|
87
|
+
agentPreferencesExistFn = agentManagement.agentPreferencesExist;
|
|
88
|
+
loadAgentPreferencesFn = agentManagement.loadAgentPreferences;
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
agent.logger.debug(`Preferences module could not be loaded: ${error instanceof Error ? error.message : String(error)}`);
|
|
92
|
+
}
|
|
74
93
|
let soundService = null;
|
|
75
94
|
// Initialize sound config with defaults (enabled by default even without preferences file)
|
|
76
95
|
let soundConfig = {
|
|
77
96
|
enabled: true,
|
|
97
|
+
onStartup: false,
|
|
98
|
+
startupSoundFile: undefined,
|
|
78
99
|
onApprovalRequired: true,
|
|
100
|
+
approvalSoundFile: undefined,
|
|
79
101
|
onTaskComplete: true,
|
|
102
|
+
completeSoundFile: undefined,
|
|
80
103
|
};
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
104
|
+
// Initialize sound service from preferences (non-fatal)
|
|
105
|
+
try {
|
|
106
|
+
const { SoundNotificationService: SoundNotificationServiceImpl } = await import('./utils/soundNotification.js');
|
|
107
|
+
// Override with user preferences if they exist
|
|
108
|
+
if (globalPreferencesExistFn() && loadGlobalPreferencesFn) {
|
|
109
|
+
try {
|
|
110
|
+
const preferences = await loadGlobalPreferencesFn();
|
|
111
|
+
soundConfig = {
|
|
112
|
+
enabled: preferences.sounds?.enabled ?? soundConfig.enabled,
|
|
113
|
+
onStartup: preferences.sounds?.onStartup ?? soundConfig.onStartup,
|
|
114
|
+
startupSoundFile: preferences.sounds?.startupSoundFile ?? soundConfig.startupSoundFile,
|
|
115
|
+
onApprovalRequired: preferences.sounds?.onApprovalRequired ?? soundConfig.onApprovalRequired,
|
|
116
|
+
approvalSoundFile: preferences.sounds?.approvalSoundFile ?? soundConfig.approvalSoundFile,
|
|
117
|
+
onTaskComplete: preferences.sounds?.onTaskComplete ?? soundConfig.onTaskComplete,
|
|
118
|
+
completeSoundFile: preferences.sounds?.completeSoundFile ?? soundConfig.completeSoundFile,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
// Continue with default sounds - this is non-critical functionality
|
|
123
|
+
agent.logger.debug(`Sound preferences could not be loaded: ${error instanceof Error ? error.message : String(error)}`);
|
|
124
|
+
}
|
|
95
125
|
}
|
|
126
|
+
// Always create the service so sound settings can be toggled at runtime (service gates playback by config)
|
|
127
|
+
soundService = new SoundNotificationServiceImpl(soundConfig);
|
|
128
|
+
soundService.playStartupSound();
|
|
96
129
|
}
|
|
97
|
-
|
|
98
|
-
|
|
130
|
+
catch (error) {
|
|
131
|
+
agent.logger.debug(`Sound initialization failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
132
|
+
soundService = null;
|
|
99
133
|
}
|
|
100
134
|
// Initialize tool preferences (per-agent)
|
|
101
|
-
if (
|
|
135
|
+
if (agentPreferencesExistFn(agent.config.agentId) && loadAgentPreferencesFn) {
|
|
102
136
|
try {
|
|
103
|
-
const preferences = await
|
|
137
|
+
const preferences = await loadAgentPreferencesFn(agent.config.agentId);
|
|
104
138
|
agent.setGlobalDisabledTools(preferences.tools?.disabled ?? []);
|
|
105
139
|
}
|
|
106
140
|
catch (error) {
|
|
107
141
|
agent.logger.debug(`Agent tool preferences could not be loaded: ${error instanceof Error ? error.message : String(error)}`);
|
|
108
142
|
}
|
|
109
143
|
}
|
|
144
|
+
// Import exit handler before render to avoid race condition
|
|
145
|
+
const { registerExitHandler } = await import('../commands/interactive-commands/exit-handler.js');
|
|
110
146
|
const inkApp = render(_jsx(InkCLIRefactored, { agent: agent, initialSessionId: initialSessionId, initialPrompt: options.initialPrompt, startupInfo: startupInfo, soundService: soundService, configFilePath: options.configFilePath ?? null }), {
|
|
111
147
|
exitOnCtrlC: false,
|
|
112
148
|
alternateBuffer: USE_ALTERNATE_BUFFER,
|
|
@@ -114,7 +150,82 @@ export async function startInkCliRefactored(agent, initialSessionId, options = {
|
|
|
114
150
|
// Static pattern doesn't need it (and may work better without)
|
|
115
151
|
incrementalRendering: USE_ALTERNATE_BUFFER,
|
|
116
152
|
});
|
|
153
|
+
// Register exit handler immediately after render (synchronous, before any await)
|
|
154
|
+
registerExitHandler(() => inkApp.unmount());
|
|
117
155
|
await inkApp.waitUntilExit();
|
|
118
156
|
// Disable bracketed paste mode to restore normal terminal behavior
|
|
119
157
|
disableBracketedPaste();
|
|
158
|
+
// Display session stats if available (after Ink has unmounted)
|
|
159
|
+
const chalk = (await import('chalk')).default;
|
|
160
|
+
const { getExitStats, clearExitStats } = await import('../commands/interactive-commands/exit-stats.js');
|
|
161
|
+
const exitStats = getExitStats();
|
|
162
|
+
if (exitStats) {
|
|
163
|
+
// Add visual separation - clear space like Gemini CLI does
|
|
164
|
+
// This creates a clean slate showing only the exit command and summary
|
|
165
|
+
process.stdout.write('\n'.repeat(1));
|
|
166
|
+
process.stdout.write(chalk.bold.cyan('📊 Session Summary') + '\n');
|
|
167
|
+
process.stdout.write(chalk.dim('─'.repeat(50)) + '\n');
|
|
168
|
+
// Session ID
|
|
169
|
+
if (exitStats.sessionId) {
|
|
170
|
+
process.stdout.write(chalk.gray(` Session ID: ${exitStats.sessionId}`) + '\n');
|
|
171
|
+
}
|
|
172
|
+
// Duration
|
|
173
|
+
if (exitStats.duration) {
|
|
174
|
+
process.stdout.write(chalk.gray(` Duration: ${exitStats.duration}`) + '\n');
|
|
175
|
+
}
|
|
176
|
+
// Message count
|
|
177
|
+
if (exitStats.messageCount.total > 0) {
|
|
178
|
+
process.stdout.write(chalk.gray(` Messages: ${exitStats.messageCount.total} total (${exitStats.messageCount.user} user, ${exitStats.messageCount.assistant} assistant)`) + '\n');
|
|
179
|
+
}
|
|
180
|
+
// Multi-model breakdown (if multiple models were used)
|
|
181
|
+
if (exitStats.modelStats && exitStats.modelStats.length > 1) {
|
|
182
|
+
process.stdout.write(chalk.gray('\n Models Used:') + '\n');
|
|
183
|
+
for (const modelStat of exitStats.modelStats) {
|
|
184
|
+
const modelLabel = `${modelStat.model} (${modelStat.messageCount} msgs)`;
|
|
185
|
+
process.stdout.write(chalk.gray(` • ${modelLabel}`) + '\n');
|
|
186
|
+
// Detailed token breakdown per model
|
|
187
|
+
const tokens = modelStat.tokenUsage;
|
|
188
|
+
process.stdout.write(chalk.gray(` Input tokens: ${tokens.inputTokens.toLocaleString()}`) +
|
|
189
|
+
'\n');
|
|
190
|
+
process.stdout.write(chalk.gray(` Output tokens: ${tokens.outputTokens.toLocaleString()}`) + '\n');
|
|
191
|
+
process.stdout.write(chalk.gray(` Reasoning tokens: ${tokens.reasoningTokens.toLocaleString()}`) + '\n');
|
|
192
|
+
process.stdout.write(chalk.gray(` Cache read tokens: ${tokens.cacheReadTokens.toLocaleString()}`) + '\n');
|
|
193
|
+
process.stdout.write(chalk.gray(` Cache write tokens: ${tokens.cacheWriteTokens.toLocaleString()}`) + '\n');
|
|
194
|
+
process.stdout.write(chalk.gray(` Total tokens: ${tokens.totalTokens.toLocaleString()}`) +
|
|
195
|
+
'\n');
|
|
196
|
+
if (modelStat.estimatedCost !== undefined) {
|
|
197
|
+
process.stdout.write(chalk.gray(` Cost: ${formatCost(modelStat.estimatedCost)}`) + '\n');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Token usage - label depends on whether multi-model was shown
|
|
202
|
+
if (exitStats.tokenUsage) {
|
|
203
|
+
const { inputTokens, outputTokens, reasoningTokens, cacheReadTokens, cacheWriteTokens, totalTokens, } = exitStats.tokenUsage;
|
|
204
|
+
// Calculate cache savings percentage
|
|
205
|
+
const totalInputWithCache = inputTokens + cacheReadTokens;
|
|
206
|
+
const cacheSavingsPercent = totalInputWithCache > 0
|
|
207
|
+
? ((cacheReadTokens / totalInputWithCache) * 100).toFixed(1)
|
|
208
|
+
: '0.0';
|
|
209
|
+
const tokenSectionLabel = exitStats.modelStats && exitStats.modelStats.length > 1
|
|
210
|
+
? '\n Total Token Usage:'
|
|
211
|
+
: '\n Token Usage:';
|
|
212
|
+
process.stdout.write(chalk.gray(tokenSectionLabel) + '\n');
|
|
213
|
+
process.stdout.write(chalk.gray(` Input tokens: ${inputTokens.toLocaleString()}`) + '\n');
|
|
214
|
+
process.stdout.write(chalk.gray(` Output tokens: ${outputTokens.toLocaleString()}`) + '\n');
|
|
215
|
+
process.stdout.write(chalk.gray(` Reasoning tokens: ${reasoningTokens.toLocaleString()}`) + '\n');
|
|
216
|
+
const cacheReadLabel = cacheReadTokens > 0
|
|
217
|
+
? `${cacheReadTokens.toLocaleString()} (💰 ${cacheSavingsPercent}% savings)`
|
|
218
|
+
: cacheReadTokens.toLocaleString();
|
|
219
|
+
process.stdout.write(chalk.gray(` Cache read tokens: ${cacheReadLabel}`) + '\n');
|
|
220
|
+
process.stdout.write(chalk.gray(` Cache write tokens: ${cacheWriteTokens.toLocaleString()}`) + '\n');
|
|
221
|
+
process.stdout.write(chalk.gray(` Total tokens: ${totalTokens.toLocaleString()}`) + '\n');
|
|
222
|
+
}
|
|
223
|
+
// Estimated cost
|
|
224
|
+
if (exitStats.estimatedCost !== undefined) {
|
|
225
|
+
process.stdout.write(chalk.green(`\n Estimated Cost: ${formatCost(exitStats.estimatedCost)}`) + '\n');
|
|
226
|
+
}
|
|
227
|
+
clearExitStats();
|
|
228
|
+
}
|
|
229
|
+
process.stdout.write(chalk.dim('─'.repeat(50)) + '\n');
|
|
230
|
+
process.stdout.write('\n' + chalk.rgb(255, 165, 0)('Exiting Dexto CLI. Goodbye!') + '\n');
|
|
120
231
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApprovalPrompt.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/ApprovalPrompt.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kCAAkC,CAAC;AAM5D,MAAM,WAAW,eAAe;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,oBAAoB;IACjC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B,oEAAoE;IACpE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mDAAmD;IACnD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,gFAAgF;IAChF,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,UAAU,mBAAmB;IACzB,QAAQ,EAAE,eAAe,CAAC;IAC1B,SAAS,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAC9C,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACxB;
|
|
1
|
+
{"version":3,"file":"ApprovalPrompt.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/ApprovalPrompt.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kCAAkC,CAAC;AAM5D,MAAM,WAAW,eAAe;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,oBAAoB;IACjC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B,oEAAoE;IACpE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mDAAmD;IACnD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,gFAAgF;IAChF,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,gDAAgD;IAChD,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,UAAU,mBAAmB;IACzB,QAAQ,EAAE,eAAe,CAAC;IAC1B,SAAS,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAC;IAC9C,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,QAAQ,EAAE,MAAM,IAAI,CAAC;CACxB;AAiBD;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,kGAwe1B,CAAC"}
|
|
@@ -4,7 +4,7 @@ import { Box, Text } from 'ink';
|
|
|
4
4
|
import { ElicitationForm } from './ElicitationForm.js';
|
|
5
5
|
import { DiffPreview, CreateFilePreview } from './renderers/index.js';
|
|
6
6
|
import { isEditWriteTool } from '../utils/toolUtils.js';
|
|
7
|
-
import { formatToolHeader } from '../utils/messageFormatting.js';
|
|
7
|
+
import { formatPathForDisplay, formatToolHeader } from '../utils/messageFormatting.js';
|
|
8
8
|
/**
|
|
9
9
|
* Compact approval prompt component that displays above the input area
|
|
10
10
|
* Shows options based on approval type:
|
|
@@ -16,10 +16,31 @@ export const ApprovalPrompt = forwardRef(({ approval, onApprove, onDeny, onCance
|
|
|
16
16
|
const isCommandConfirmation = approval.type === 'command_confirmation';
|
|
17
17
|
const isElicitation = approval.type === 'elicitation';
|
|
18
18
|
const isDirectoryAccess = approval.type === 'directory_access';
|
|
19
|
+
const directoryAccess = approval.metadata.directoryAccess;
|
|
20
|
+
const hasToolDirectoryAccess = approval.type === 'tool_confirmation' &&
|
|
21
|
+
directoryAccess !== undefined &&
|
|
22
|
+
typeof directoryAccess === 'object' &&
|
|
23
|
+
directoryAccess !== null;
|
|
24
|
+
const directoryAccessParentDir = hasToolDirectoryAccess && typeof directoryAccess.parentDir === 'string'
|
|
25
|
+
? directoryAccess.parentDir
|
|
26
|
+
: null;
|
|
19
27
|
// Extract tool metadata
|
|
20
28
|
const toolName = approval.metadata.toolName;
|
|
21
29
|
const toolArgs = approval.metadata.args || {};
|
|
22
|
-
const
|
|
30
|
+
const callDescriptionRaw = typeof approval.metadata.description === 'string'
|
|
31
|
+
? approval.metadata.description
|
|
32
|
+
: undefined;
|
|
33
|
+
const callDescription = useMemo(() => {
|
|
34
|
+
if (!callDescriptionRaw)
|
|
35
|
+
return null;
|
|
36
|
+
const singleLine = callDescriptionRaw.replace(/\s+/g, ' ').trim();
|
|
37
|
+
if (!singleLine)
|
|
38
|
+
return null;
|
|
39
|
+
const maxLength = 120;
|
|
40
|
+
if (singleLine.length <= maxLength)
|
|
41
|
+
return singleLine;
|
|
42
|
+
return `${singleLine.slice(0, Math.max(0, maxLength - 1))}…`;
|
|
43
|
+
}, [callDescriptionRaw]);
|
|
23
44
|
// Check if this is a plan_review tool (shows custom approval options)
|
|
24
45
|
const isPlanReview = toolName === 'plan_review';
|
|
25
46
|
// Extract suggested patterns for tools that support pattern-based approvals
|
|
@@ -28,15 +49,26 @@ export const ApprovalPrompt = forwardRef(({ approval, onApprove, onDeny, onCance
|
|
|
28
49
|
// Check if this is an edit/write file tool
|
|
29
50
|
const isEditOrWriteTool = isEditWriteTool(toolName);
|
|
30
51
|
// Format tool header using shared utility (same format as tool messages)
|
|
52
|
+
const presentationSnapshot = useMemo(() => {
|
|
53
|
+
const raw = approval.metadata
|
|
54
|
+
.presentationSnapshot;
|
|
55
|
+
if (typeof raw !== 'object' || raw === null) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
if (raw.version !== 1) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return raw;
|
|
62
|
+
}, [approval.metadata]);
|
|
31
63
|
const formattedTool = useMemo(() => {
|
|
32
64
|
if (!toolName)
|
|
33
65
|
return null;
|
|
34
66
|
return formatToolHeader({
|
|
35
67
|
toolName,
|
|
36
68
|
args: toolArgs,
|
|
37
|
-
...(
|
|
69
|
+
...(presentationSnapshot !== undefined && { presentationSnapshot }),
|
|
38
70
|
});
|
|
39
|
-
}, [toolName,
|
|
71
|
+
}, [toolName, toolArgs, presentationSnapshot]);
|
|
40
72
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
41
73
|
// State for plan review feedback input
|
|
42
74
|
const [showFeedbackInput, setShowFeedbackInput] = useState(false);
|
|
@@ -53,6 +85,15 @@ export const ApprovalPrompt = forwardRef(({ approval, onApprove, onDeny, onCance
|
|
|
53
85
|
options.push({ id: 'plan-approve-accept-edits', label: 'Approve + Accept All Edits' });
|
|
54
86
|
// Third "option" is the feedback input (handled specially in render)
|
|
55
87
|
}
|
|
88
|
+
else if (hasToolDirectoryAccess) {
|
|
89
|
+
// Tool approval that includes directory access - offer session-scoped dir allow
|
|
90
|
+
const dirLabel = directoryAccessParentDir
|
|
91
|
+
? ` ${formatPathForDisplay(directoryAccessParentDir)}`
|
|
92
|
+
: '';
|
|
93
|
+
options.push({ id: 'yes', label: 'Yes (once)' });
|
|
94
|
+
options.push({ id: 'yes-directory-session', label: `Yes, allow${dirLabel} (session)` });
|
|
95
|
+
options.push({ id: 'no', label: 'No' });
|
|
96
|
+
}
|
|
56
97
|
else if (hasSuggestedPatterns) {
|
|
57
98
|
// Tool with pattern suggestions
|
|
58
99
|
options.push({ id: 'yes', label: 'Yes (once)' });
|
|
@@ -73,7 +114,7 @@ export const ApprovalPrompt = forwardRef(({ approval, onApprove, onDeny, onCance
|
|
|
73
114
|
else if (isDirectoryAccess) {
|
|
74
115
|
// Directory access - offer session-scoped access
|
|
75
116
|
const parentDir = approval.metadata.parentDir;
|
|
76
|
-
const dirLabel = parentDir ? `
|
|
117
|
+
const dirLabel = parentDir ? ` ${formatPathForDisplay(parentDir)}` : '';
|
|
77
118
|
options.push({ id: 'yes', label: 'Yes (once)' });
|
|
78
119
|
options.push({ id: 'yes-session', label: `Yes, allow${dirLabel} (session)` });
|
|
79
120
|
options.push({ id: 'no', label: 'No' });
|
|
@@ -169,6 +210,9 @@ export const ApprovalPrompt = forwardRef(({ approval, onApprove, onDeny, onCance
|
|
|
169
210
|
onApprove({ rememberChoice: true });
|
|
170
211
|
}
|
|
171
212
|
}
|
|
213
|
+
else if (option.id === 'yes-directory-session') {
|
|
214
|
+
onApprove({ rememberDirectory: true });
|
|
215
|
+
}
|
|
172
216
|
else if (option.id === 'yes-accept-edits') {
|
|
173
217
|
// Approve and enable "accept all edits" mode
|
|
174
218
|
onApprove({ enableAcceptEditsMode: true });
|
|
@@ -204,6 +248,7 @@ export const ApprovalPrompt = forwardRef(({ approval, onApprove, onDeny, onCance
|
|
|
204
248
|
isElicitation,
|
|
205
249
|
isEditOrWriteTool,
|
|
206
250
|
isDirectoryAccess,
|
|
251
|
+
hasToolDirectoryAccess,
|
|
207
252
|
isPlanReview,
|
|
208
253
|
options,
|
|
209
254
|
suggestedPatterns,
|
|
@@ -225,24 +270,16 @@ export const ApprovalPrompt = forwardRef(({ approval, onApprove, onDeny, onCance
|
|
|
225
270
|
if (!displayPreview)
|
|
226
271
|
return null;
|
|
227
272
|
switch (displayPreview.type) {
|
|
228
|
-
case 'diff':
|
|
229
|
-
|
|
230
|
-
return (_jsx(DiffPreview, { data: displayPreview, headerType: isOverwrite ? 'overwrite' : 'edit' }));
|
|
231
|
-
}
|
|
273
|
+
case 'diff':
|
|
274
|
+
return _jsx(DiffPreview, { data: displayPreview });
|
|
232
275
|
case 'shell':
|
|
233
|
-
// For shell preview,
|
|
234
|
-
return (_jsxs(Box, { marginBottom: 1,
|
|
276
|
+
// For shell preview, show the command + optional description (no output yet)
|
|
277
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Box, { marginBottom: 0, children: _jsx(Text, { color: "cyan", bold: true, children: displayPreview.title ?? 'Bash' }) }), _jsxs(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [displayPreview.command.split('\n').map((line, i) => (_jsxs(Text, { color: "yellowBright", wrap: "wrap", children: [i === 0 ? '$ ' : ' ', line] }, i))), displayPreview.isBackground && (_jsx(Text, { color: "gray", children: "(background)" }))] }), callDescription && (_jsx(Box, { marginTop: 0, children: _jsx(Text, { color: "gray", children: callDescription }) }))] }));
|
|
235
278
|
case 'file':
|
|
236
|
-
//
|
|
237
|
-
if (displayPreview.
|
|
279
|
+
// If content is provided, show the full content (create/review flows)
|
|
280
|
+
if (displayPreview.content) {
|
|
238
281
|
return _jsx(CreateFilePreview, { data: displayPreview });
|
|
239
282
|
}
|
|
240
|
-
// For plan_review (read operation with content), show full content for review
|
|
241
|
-
if (displayPreview.operation === 'read' &&
|
|
242
|
-
displayPreview.content &&
|
|
243
|
-
isPlanReview) {
|
|
244
|
-
return _jsx(CreateFilePreview, { data: displayPreview, header: "Review plan" });
|
|
245
|
-
}
|
|
246
283
|
// Fallback for other file operations
|
|
247
284
|
return (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "gray", children: [displayPreview.operation === 'read' &&
|
|
248
285
|
`Read ${displayPreview.lineCount ?? 'file'} lines`, displayPreview.operation === 'write' &&
|
|
@@ -256,7 +293,24 @@ export const ApprovalPrompt = forwardRef(({ approval, onApprove, onDeny, onCance
|
|
|
256
293
|
const directoryPath = approval.metadata.path;
|
|
257
294
|
const parentDir = approval.metadata.parentDir;
|
|
258
295
|
const operation = approval.metadata.operation;
|
|
259
|
-
|
|
296
|
+
const directoryAccessTitle = !isDirectoryAccess
|
|
297
|
+
? null
|
|
298
|
+
: operation === 'read'
|
|
299
|
+
? 'Read file'
|
|
300
|
+
: operation === 'write'
|
|
301
|
+
? 'Write file'
|
|
302
|
+
: operation === 'edit'
|
|
303
|
+
? 'Edit file'
|
|
304
|
+
: 'Directory access';
|
|
305
|
+
const directoryAccessToolHeader = !isDirectoryAccess || !toolName
|
|
306
|
+
? null
|
|
307
|
+
: formatToolHeader({
|
|
308
|
+
toolName,
|
|
309
|
+
args: (directoryPath ?? parentDir) ? { path: directoryPath ?? parentDir } : {},
|
|
310
|
+
...(presentationSnapshot !== undefined && { presentationSnapshot }),
|
|
311
|
+
}).header;
|
|
312
|
+
const showHeaderBlock = isDirectoryAccess || isCommandConfirmation || !displayPreview;
|
|
313
|
+
return (_jsxs(Box, { paddingX: 0, paddingY: 0, flexDirection: "column", children: [showHeaderBlock && (_jsx(Box, { flexDirection: "column", marginBottom: 0, children: isDirectoryAccess ? (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 0, children: _jsx(Text, { color: "cyan", bold: true, children: directoryAccessTitle ?? 'Directory access' }) }), directoryAccessToolHeader && (_jsx(Box, { marginBottom: 0, children: _jsx(Text, { wrap: "wrap", children: directoryAccessToolHeader }) }))] })) : isCommandConfirmation ? (_jsxs(_Fragment, { children: [_jsx(Box, { flexDirection: "row", children: _jsxs(Text, { color: "yellowBright", bold: true, children: ["Confirm command:", ' '] }) }), command && (_jsx(Box, { flexDirection: "column", marginTop: 0, children: _jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: command.split('\n').map((line, i) => (_jsx(Text, { color: "red", wrap: "wrap", children: line }, i))) }) }))] })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: "yellowBright", bold: true, children: ["Confirm:", ' '] }), formattedTool && (_jsx(Text, { color: "cyan", children: formattedTool.header }))] }), callDescription && (_jsxs(Box, { flexDirection: "row", marginTop: 0, children: [_jsx(Text, { color: "gray", children: ' ' }), _jsx(Text, { color: "gray", children: callDescription })] }))] })) })), renderPreview(), _jsxs(Box, { flexDirection: "column", marginTop: 0, children: [options.map((option, index) => {
|
|
260
314
|
const isSelected = index === selectedIndex;
|
|
261
315
|
const isNo = option.id === 'no';
|
|
262
316
|
return (_jsx(Box, { children: isSelected ? (_jsxs(Text, { color: isNo ? 'red' : 'green', bold: true, children: [' ▶ ', option.label] })) : (_jsxs(Text, { color: "gray", children: [' ', option.label] })) }, option.id));
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ElicitationForm Component
|
|
3
|
-
* Renders a form for ask_user/elicitation requests in the CLI
|
|
4
|
-
*
|
|
3
|
+
* Renders a form for ask_user/elicitation requests in the CLI.
|
|
4
|
+
*
|
|
5
|
+
* Uses a wizard flow (one question at a time) to avoid huge modals and improve
|
|
6
|
+
* usability on small terminals.
|
|
5
7
|
*/
|
|
6
8
|
import React from 'react';
|
|
7
|
-
import type { Key } from '../hooks/useInputOrchestrator.js';
|
|
8
9
|
import type { ElicitationMetadata } from '@dexto/core';
|
|
10
|
+
import type { Key } from '../hooks/useInputOrchestrator.js';
|
|
9
11
|
export interface ElicitationFormHandle {
|
|
10
12
|
handleInput: (input: string, key: Key) => boolean;
|
|
11
13
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ElicitationForm.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/ElicitationForm.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ElicitationForm.d.ts","sourceRoot":"","sources":["../../../../src/cli/ink-cli/components/ElicitationForm.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA0E,MAAM,OAAO,CAAC;AAG/F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACvD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,kCAAkC,CAAC;AAI5D,MAAM,WAAW,qBAAqB;IAClC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;CACrD;AAED,UAAU,oBAAoB;IAC1B,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtD,QAAQ,EAAE,MAAM,IAAI,CAAC;CACxB;AAkBD;;GAEG;AACH,eAAO,MAAM,eAAe,oGA6uB3B,CAAC"}
|