dexto 1.5.3 → 1.5.4

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 (70) hide show
  1. package/dist/agents/coding-agent/coding-agent.yml +16 -0
  2. package/dist/cli/cli-subscriber.d.ts +4 -1
  3. package/dist/cli/cli-subscriber.d.ts.map +1 -1
  4. package/dist/cli/cli-subscriber.js +31 -1
  5. package/dist/cli/commands/interactive-commands/commands.d.ts.map +1 -1
  6. package/dist/cli/commands/interactive-commands/commands.js +2 -1
  7. package/dist/cli/commands/interactive-commands/general-commands.d.ts.map +1 -1
  8. package/dist/cli/commands/interactive-commands/general-commands.js +160 -3
  9. package/dist/cli/commands/interactive-commands/session/index.d.ts +2 -1
  10. package/dist/cli/commands/interactive-commands/session/index.d.ts.map +1 -1
  11. package/dist/cli/commands/interactive-commands/session/index.js +2 -1
  12. package/dist/cli/commands/interactive-commands/session/session-commands.d.ts +7 -0
  13. package/dist/cli/commands/interactive-commands/session/session-commands.d.ts.map +1 -1
  14. package/dist/cli/commands/interactive-commands/session/session-commands.js +16 -0
  15. package/dist/cli/ink-cli/InkCLIRefactored.d.ts +3 -1
  16. package/dist/cli/ink-cli/InkCLIRefactored.d.ts.map +1 -1
  17. package/dist/cli/ink-cli/InkCLIRefactored.js +36 -7
  18. package/dist/cli/ink-cli/components/StatusBar.d.ts +2 -1
  19. package/dist/cli/ink-cli/components/StatusBar.d.ts.map +1 -1
  20. package/dist/cli/ink-cli/components/StatusBar.js +12 -3
  21. package/dist/cli/ink-cli/components/chat/MessageItem.d.ts.map +1 -1
  22. package/dist/cli/ink-cli/components/chat/MessageItem.js +14 -3
  23. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.d.ts.map +1 -1
  24. package/dist/cli/ink-cli/components/modes/AlternateBufferCLI.js +1 -1
  25. package/dist/cli/ink-cli/components/modes/StaticCLI.d.ts.map +1 -1
  26. package/dist/cli/ink-cli/components/modes/StaticCLI.js +1 -1
  27. package/dist/cli/ink-cli/components/overlays/ContextStatsOverlay.d.ts +26 -0
  28. package/dist/cli/ink-cli/components/overlays/ContextStatsOverlay.d.ts.map +1 -0
  29. package/dist/cli/ink-cli/components/overlays/ContextStatsOverlay.js +240 -0
  30. package/dist/cli/ink-cli/components/overlays/SessionRenameOverlay.d.ts +21 -0
  31. package/dist/cli/ink-cli/components/overlays/SessionRenameOverlay.d.ts.map +1 -0
  32. package/dist/cli/ink-cli/components/overlays/SessionRenameOverlay.js +63 -0
  33. package/dist/cli/ink-cli/components/overlays/SessionSelectorRefactored.js +2 -2
  34. package/dist/cli/ink-cli/components/shared/MarkdownText.js +2 -2
  35. package/dist/cli/ink-cli/constants/processingPhrases.d.ts.map +1 -1
  36. package/dist/cli/ink-cli/constants/processingPhrases.js +14 -3
  37. package/dist/cli/ink-cli/constants/tips.d.ts.map +1 -1
  38. package/dist/cli/ink-cli/constants/tips.js +2 -0
  39. package/dist/cli/ink-cli/containers/InputContainer.d.ts.map +1 -1
  40. package/dist/cli/ink-cli/containers/InputContainer.js +8 -0
  41. package/dist/cli/ink-cli/containers/OverlayContainer.d.ts.map +1 -1
  42. package/dist/cli/ink-cli/containers/OverlayContainer.js +53 -2
  43. package/dist/cli/ink-cli/contexts/SoundContext.d.ts +23 -0
  44. package/dist/cli/ink-cli/contexts/SoundContext.d.ts.map +1 -0
  45. package/dist/cli/ink-cli/contexts/SoundContext.js +22 -0
  46. package/dist/cli/ink-cli/contexts/index.d.ts +1 -0
  47. package/dist/cli/ink-cli/contexts/index.d.ts.map +1 -1
  48. package/dist/cli/ink-cli/contexts/index.js +1 -0
  49. package/dist/cli/ink-cli/hooks/useCLIState.d.ts.map +1 -1
  50. package/dist/cli/ink-cli/hooks/useCLIState.js +1 -0
  51. package/dist/cli/ink-cli/services/processStream.d.ts +4 -0
  52. package/dist/cli/ink-cli/services/processStream.d.ts.map +1 -1
  53. package/dist/cli/ink-cli/services/processStream.js +76 -2
  54. package/dist/cli/ink-cli/state/initialState.d.ts.map +1 -1
  55. package/dist/cli/ink-cli/state/initialState.js +1 -0
  56. package/dist/cli/ink-cli/state/types.d.ts +8 -1
  57. package/dist/cli/ink-cli/state/types.d.ts.map +1 -1
  58. package/dist/cli/ink-cli/utils/commandOverlays.d.ts.map +1 -1
  59. package/dist/cli/ink-cli/utils/commandOverlays.js +4 -0
  60. package/dist/cli/ink-cli/utils/index.d.ts +1 -0
  61. package/dist/cli/ink-cli/utils/index.d.ts.map +1 -1
  62. package/dist/cli/ink-cli/utils/index.js +2 -0
  63. package/dist/cli/ink-cli/utils/messageFormatting.js +2 -2
  64. package/dist/cli/ink-cli/utils/soundNotification.d.ts +71 -0
  65. package/dist/cli/ink-cli/utils/soundNotification.d.ts.map +1 -0
  66. package/dist/cli/ink-cli/utils/soundNotification.js +203 -0
  67. package/dist/index.js +5 -0
  68. package/dist/webui/assets/{index-SGm5dxhp.js → index-Bh2aB65S.js} +161 -161
  69. package/dist/webui/index.html +1 -1
  70. package/package.json +7 -7
@@ -8,6 +8,14 @@ image: '@dexto/image-local'
8
8
  # System prompt configuration - defines the agent's behavior as a coding assistant
9
9
  systemPrompt:
10
10
  contributors:
11
+ - id: date
12
+ type: dynamic
13
+ priority: 10
14
+ source: date
15
+ - id: env
16
+ type: dynamic
17
+ priority: 15
18
+ source: env
11
19
  - id: primary
12
20
  type: static
13
21
  priority: 0
@@ -103,6 +111,14 @@ toolConfirmation:
103
111
  # alwaysDeny:
104
112
  # - custom--process-tools--bash_exec--rm -rf* # Prevent recursive deletion
105
113
 
114
+ # Compaction configuration - automatically summarizes conversation when context is full
115
+ compaction:
116
+ type: reactive-overflow
117
+ enabled: true
118
+ # Uncomment to override model's context window (useful for testing or capping usage):
119
+ # maxContextTokens: 50000 # Cap at 50K tokens regardless of model's max
120
+ # thresholdPercent: 0.9 # Trigger at 90% of context window (leaves safety buffer)
121
+
106
122
  # Elicitation configuration - required for ask_user tool
107
123
  elicitation:
108
124
  enabled: true
@@ -8,7 +8,7 @@
8
8
  import { DextoAgent } from '@dexto/core';
9
9
  import { EventSubscriber } from '@dexto/server';
10
10
  import { AgentEventBus } from '@dexto/core';
11
- import type { SanitizedToolResult } from '@dexto/core';
11
+ import type { SanitizedToolResult, AgentEventMap } from '@dexto/core';
12
12
  /**
13
13
  * Event subscriber for CLI headless mode
14
14
  * Implements the standard EventSubscriber pattern used throughout the codebase
@@ -30,6 +30,9 @@ export declare class CLISubscriber implements EventSubscriber {
30
30
  onResponse(text: string): void;
31
31
  onError(error: Error): void;
32
32
  onConversationReset(): void;
33
+ onContextCompacting(payload: AgentEventMap['context:compacting']): void;
34
+ onContextCompacted(payload: AgentEventMap['context:compacted']): void;
35
+ onSessionContinued(payload: AgentEventMap['session:continued']): void;
33
36
  /**
34
37
  * Capture LLM token usage analytics
35
38
  */
@@ -1 +1 @@
1
- {"version":3,"file":"cli-subscriber.d.ts","sourceRoot":"","sources":["../../src/cli/cli-subscriber.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAU,UAAU,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,mBAAmB,EAAiB,MAAM,aAAa,CAAC;AAGtE;;;GAGG;AACH,qBAAa,aAAc,YAAW,eAAe;IACjD,OAAO,CAAC,gBAAgB,CAAc;IACtC,OAAO,CAAC,iBAAiB,CAAC,CAAa;IACvC,OAAO,CAAC,gBAAgB,CAAC,CAAyB;IAElD,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IA6BxC;;;OAGG;IACH,OAAO,IAAI,IAAI;IAcf,UAAU,IAAI,IAAI;IAIlB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM3B,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAK9C,YAAY,CACR,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,mBAAmB,EAC9B,SAAS,CAAC,EAAE,OAAO,EACnB,OAAO,CAAC,EAAE,OAAO,GAClB,IAAI;IAMP,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IA0B9B,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAyC3B,mBAAmB,IAAI,IAAI;IAM3B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoBzB;;;OAGG;IACG,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAsBxF"}
1
+ {"version":3,"file":"cli-subscriber.d.ts","sourceRoot":"","sources":["../../src/cli/cli-subscriber.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAU,UAAU,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGtE;;;GAGG;AACH,qBAAa,aAAc,YAAW,eAAe;IACjD,OAAO,CAAC,gBAAgB,CAAc;IACtC,OAAO,CAAC,iBAAiB,CAAC,CAAa;IACvC,OAAO,CAAC,gBAAgB,CAAC,CAAyB;IAElD,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IAgCxC;;;OAGG;IACH,OAAO,IAAI,IAAI;IAcf,UAAU,IAAI,IAAI;IAIlB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAM3B,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI;IAK9C,YAAY,CACR,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,mBAAmB,EAC9B,SAAS,CAAC,EAAE,OAAO,EACnB,OAAO,CAAC,EAAE,OAAO,GAClB,IAAI;IAMP,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IA0B9B,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAyC3B,mBAAmB,IAAI,IAAI;IAM3B,mBAAmB,CAAC,OAAO,EAAE,aAAa,CAAC,oBAAoB,CAAC,GAAG,IAAI;IAOvE,kBAAkB,CAAC,OAAO,EAAE,aAAa,CAAC,mBAAmB,CAAC,GAAG,IAAI;IAcrE,kBAAkB,CAAC,OAAO,EAAE,aAAa,CAAC,mBAAmB,CAAC,GAAG,IAAI;IAWrE;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA6BzB;;;OAGG;IACG,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAsBxF"}
@@ -37,6 +37,9 @@ export class CLISubscriber {
37
37
  });
38
38
  eventBus.on('llm:error', (payload) => this.onError(payload.error));
39
39
  eventBus.on('session:reset', this.onConversationReset.bind(this));
40
+ eventBus.on('context:compacting', this.onContextCompacting.bind(this));
41
+ eventBus.on('context:compacted', this.onContextCompacted.bind(this));
42
+ eventBus.on('session:continued', this.onSessionContinued.bind(this));
40
43
  }
41
44
  /**
42
45
  * Clean up internal state
@@ -134,14 +137,39 @@ export class CLISubscriber {
134
137
  this.streamingContent = '';
135
138
  logger.info('🔄 Conversation history cleared.', null, 'blue');
136
139
  }
140
+ onContextCompacting(payload) {
141
+ // Output to stderr (doesn't interfere with stdout response stream)
142
+ process.stderr.write(`[📦 Compacting context (~${payload.estimatedTokens.toLocaleString()} tokens)...]\n`);
143
+ }
144
+ onContextCompacted(payload) {
145
+ const { originalTokens, compactedTokens, originalMessages, compactedMessages, reason } = payload;
146
+ const reductionPercent = originalTokens > 0
147
+ ? Math.round(((originalTokens - compactedTokens) / originalTokens) * 100)
148
+ : 0;
149
+ // Output to stderr (doesn't interfere with stdout response stream)
150
+ process.stderr.write(`[📦 Context compacted (${reason}): ${originalTokens.toLocaleString()} → ~${compactedTokens.toLocaleString()} tokens (${reductionPercent}% reduction), ${originalMessages} → ${compactedMessages} messages]\n`);
151
+ }
152
+ onSessionContinued(payload) {
153
+ const { previousSessionId, newSessionId, summaryTokens, originalMessages, reason } = payload;
154
+ // Output to stderr (doesn't interfere with stdout response stream)
155
+ process.stderr.write(`[📦 Context compacted → Continuing in new session\n` +
156
+ ` ${previousSessionId.slice(0, 8)}... → ${newSessionId.slice(0, 8)}...\n` +
157
+ ` ${originalMessages} messages → ~${summaryTokens.toLocaleString()} token summary (${reason})]\n`);
158
+ }
137
159
  /**
138
160
  * Capture LLM token usage analytics
139
161
  */
140
162
  captureTokenUsage(payload) {
141
- const { tokenUsage, provider, model, sessionId } = payload;
163
+ const { tokenUsage, provider, model, sessionId, estimatedInputTokens } = payload;
142
164
  if (!tokenUsage || (!tokenUsage.inputTokens && !tokenUsage.outputTokens)) {
143
165
  return;
144
166
  }
167
+ // Calculate estimate accuracy if both estimate and actual are available
168
+ let estimateAccuracyPercent;
169
+ if (estimatedInputTokens !== undefined && tokenUsage.inputTokens) {
170
+ const diff = estimatedInputTokens - tokenUsage.inputTokens;
171
+ estimateAccuracyPercent = Math.round((diff / tokenUsage.inputTokens) * 100);
172
+ }
145
173
  capture('dexto_llm_tokens_consumed', {
146
174
  source: 'cli',
147
175
  sessionId,
@@ -153,6 +181,8 @@ export class CLISubscriber {
153
181
  totalTokens: tokenUsage.totalTokens,
154
182
  cacheReadTokens: tokenUsage.cacheReadTokens,
155
183
  cacheWriteTokens: tokenUsage.cacheWriteTokens,
184
+ estimatedInputTokens,
185
+ estimateAccuracyPercent,
156
186
  });
157
187
  }
158
188
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/interactive-commands/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAYnF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,YAAY,EAAE,iBAAiB,EAAO,CAAC;AAqCpD;;;;;GAKG;AACH,wBAAsB,cAAc,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,KAAK,EAAE,UAAU,EACjB,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,oBAAoB,CAAC,CAyD/B;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,iBAAiB,EAAE,CAEpD"}
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/interactive-commands/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAYnF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,YAAY,EAAE,iBAAiB,EAAO,CAAC;AAsCpD;;;;;GAKG;AACH,wBAAsB,cAAc,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,KAAK,EAAE,UAAU,EACjB,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,oBAAoB,CAAC,CAyD/B;AAED;;;GAGG;AACH,wBAAgB,cAAc,IAAI,iBAAiB,EAAE,CAEpD"}
@@ -20,7 +20,7 @@
20
20
  */
21
21
  // Import modular command definitions
22
22
  import { generalCommands, createHelpCommand } from './general-commands.js';
23
- import { searchCommand, resumeCommand } from './session/index.js';
23
+ import { searchCommand, resumeCommand, renameCommand } from './session/index.js';
24
24
  import { modelCommands } from './model/index.js';
25
25
  import { mcpCommands } from './mcp/index.js';
26
26
  import { systemCommands } from './system/index.js';
@@ -51,6 +51,7 @@ const baseCommands = [
51
51
  // Session management
52
52
  searchCommand, // /search - opens search overlay
53
53
  resumeCommand, // /resume - opens session selector overlay
54
+ renameCommand, // /rename <title> - rename current session
54
55
  // Model management
55
56
  modelCommands, // /model - opens model selector overlay
56
57
  // MCP server management
@@ -1 +1 @@
1
- {"version":3,"file":"general-commands.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/interactive-commands/general-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAwC,MAAM,qBAAqB,CAAC;AAoDnG;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,cAAc,EAAE,MAAM,iBAAiB,EAAE,GAAG,iBAAiB,CAgC9F;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,iBAAiB,EAyO9C,CAAC"}
1
+ {"version":3,"file":"general-commands.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/interactive-commands/general-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAwC,MAAM,qBAAqB,CAAC;AAgGnG;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,cAAc,EAAE,MAAM,iBAAiB,EAAE,GAAG,iBAAiB,CAgC9F;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,iBAAiB,EA8X9C,CAAC"}
@@ -15,17 +15,57 @@ import { formatForInkCli } from './utils/format-output.js';
15
15
  import { CommandOutputHelper } from './utils/command-output.js';
16
16
  import { writeToClipboard } from '../../ink-cli/utils/clipboardUtils.js';
17
17
  /**
18
- * Execute a shell command and return the output
18
+ * Get the shell rc file path for the given shell
19
19
  */
20
+ function getShellRcFile(shell) {
21
+ const home = process.env.HOME;
22
+ if (!home)
23
+ return null;
24
+ if (shell.includes('zsh')) {
25
+ return `${home}/.zshrc`;
26
+ }
27
+ else if (shell.includes('bash')) {
28
+ return `${home}/.bashrc`;
29
+ }
30
+ return null;
31
+ }
32
+ /**
33
+ * Wrap command to source shell rc file for alias support
34
+ * This mimics how Claude Code handles shell aliases - by sourcing the rc file
35
+ * before executing the command, we get aliases without using -i flag.
36
+ * We use eval to force the command to be re-parsed after sourcing, since
37
+ * aliases are expanded at parse time, not execution time.
38
+ */
39
+ function wrapCommandWithRcSource(command, shell) {
40
+ const rcFile = getShellRcFile(shell);
41
+ if (!rcFile) {
42
+ return command;
43
+ }
44
+ // Escape single quotes in the command for safe eval
45
+ const escapedCommand = command.replace(/'/g, "'\\''");
46
+ // Source the rc file (suppressing errors if it doesn't exist), then eval the command
47
+ // eval forces re-parsing after sourcing, allowing aliases to expand
48
+ // For bash, we also need to enable expand_aliases
49
+ if (shell.includes('bash')) {
50
+ return `source "${rcFile}" 2>/dev/null; shopt -s expand_aliases 2>/dev/null; eval '${escapedCommand}'`;
51
+ }
52
+ return `source "${rcFile}" 2>/dev/null; eval '${escapedCommand}'`;
53
+ }
20
54
  async function executeShellCommand(command, cwd, timeoutMs = 30000) {
21
55
  return new Promise((resolve) => {
22
56
  // Use user's default shell from SHELL env var, fallback to /bin/sh
23
- // Use -i flag for interactive mode to load aliases from shell config
57
+ // Avoid -i (interactive) as it sets up job control which causes SIGTTIN
58
+ // when the parent process tries to read stdin while shell runs.
59
+ // Instead, source the shell's rc file to get aliases (similar to Claude Code).
60
+ // Use detached: true to create a new process group, preventing the child
61
+ // from interfering with the parent's terminal control.
24
62
  const userShell = process.env.SHELL || '/bin/sh';
25
- const child = spawn(userShell, ['-ic', command], {
63
+ const wrappedCommand = wrapCommandWithRcSource(command, userShell);
64
+ const child = spawn(userShell, ['-c', wrappedCommand], {
26
65
  cwd,
27
66
  stdio: ['ignore', 'pipe', 'pipe'],
28
67
  env: { ...process.env },
68
+ detached: true,
29
69
  });
30
70
  let stdout = '';
31
71
  let stderr = '';
@@ -148,6 +188,123 @@ export const generalCommands = [
148
188
  }
149
189
  },
150
190
  },
191
+ {
192
+ name: 'compact',
193
+ description: 'Compact context by summarizing older messages',
194
+ usage: '/compact',
195
+ category: 'General',
196
+ aliases: ['summarize'],
197
+ handler: async (_args, agent, ctx) => {
198
+ try {
199
+ const { sessionId } = ctx;
200
+ if (!sessionId) {
201
+ return formatForInkCli('⚠️ No active session to compact');
202
+ }
203
+ // Compact context - generates summary and hides older messages
204
+ const result = await agent.compactContext(sessionId);
205
+ if (!result) {
206
+ return formatForInkCli('💡 Nothing to compact - history is too short or compaction is not configured.');
207
+ }
208
+ return formatForInkCli(`📦 Context compacted → Continuing in new session\n` +
209
+ ` ${result.previousSessionId.slice(0, 8)}... → ${result.newSessionId.slice(0, 8)}...\n` +
210
+ ` ${result.originalMessages} messages → ~${result.summaryTokens.toLocaleString()} token summary\n` +
211
+ `💡 New session created with conversation summary. Old session preserved.`);
212
+ }
213
+ catch (error) {
214
+ const errorMsg = `Failed to compact context: ${error instanceof Error ? error.message : String(error)}`;
215
+ agent.logger.error(errorMsg);
216
+ return formatForInkCli(`❌ ${errorMsg}`);
217
+ }
218
+ },
219
+ },
220
+ {
221
+ name: 'context',
222
+ description: 'Show context window usage statistics',
223
+ usage: '/context',
224
+ category: 'General',
225
+ aliases: ['ctx', 'tokens'],
226
+ handler: async (_args, agent, ctx) => {
227
+ try {
228
+ const { sessionId } = ctx;
229
+ if (!sessionId) {
230
+ return formatForInkCli('⚠️ No active session');
231
+ }
232
+ const stats = await agent.getContextStats(sessionId);
233
+ // Create a visual progress bar (clamped to 0-100% for display)
234
+ const barWidth = 20;
235
+ const displayPercent = Math.min(Math.max(stats.usagePercent, 0), 100);
236
+ const filledWidth = Math.round((displayPercent / 100) * barWidth);
237
+ const emptyWidth = barWidth - filledWidth;
238
+ const progressBar = '█'.repeat(filledWidth) + '░'.repeat(emptyWidth);
239
+ // Color based on usage
240
+ let usageColor = chalk.green;
241
+ if (stats.usagePercent > 80)
242
+ usageColor = chalk.red;
243
+ else if (stats.usagePercent > 60)
244
+ usageColor = chalk.yellow;
245
+ // Helper to format token counts
246
+ const formatTokens = (tokens) => {
247
+ if (tokens >= 1000) {
248
+ return `${(tokens / 1000).toFixed(1)}k`;
249
+ }
250
+ return tokens.toLocaleString();
251
+ };
252
+ // Helper to calculate percentage of max
253
+ const pct = (tokens) => {
254
+ const percent = stats.maxContextTokens > 0
255
+ ? ((tokens / stats.maxContextTokens) * 100).toFixed(1)
256
+ : '0.0';
257
+ return `${percent}%`;
258
+ };
259
+ const overflowWarning = stats.usagePercent > 100 ? ' ⚠️ OVERFLOW' : '';
260
+ const { breakdown } = stats;
261
+ // Show actual tokens if available, otherwise estimate with indicator
262
+ const tokenDisplay = stats.actualTokens !== null
263
+ ? `${formatTokens(stats.actualTokens)}`
264
+ : `~${formatTokens(stats.estimatedTokens)}`;
265
+ // Calculate auto compact buffer (reserved space before compaction triggers)
266
+ // maxContextTokens already has thresholdPercent applied, so we need to derive
267
+ // the buffer as: maxContextTokens * (1 - thresholdPercent) / thresholdPercent
268
+ const autoCompactBuffer = stats.thresholdPercent > 0 && stats.thresholdPercent < 1.0
269
+ ? Math.floor((stats.maxContextTokens * (1 - stats.thresholdPercent)) /
270
+ stats.thresholdPercent)
271
+ : 0;
272
+ const bufferPercent = Math.round((1 - stats.thresholdPercent) * 100);
273
+ const bufferLabel = bufferPercent > 0
274
+ ? `Auto compact buffer (${bufferPercent}%)`
275
+ : 'Auto compact buffer';
276
+ const lines = [
277
+ `📊 Context Usage`,
278
+ ` ${usageColor(progressBar)} ${stats.usagePercent}%${overflowWarning}`,
279
+ ` ${chalk.dim(stats.modelDisplayName)} · ${tokenDisplay} / ${formatTokens(stats.maxContextTokens)} tokens`,
280
+ ``,
281
+ ` ${chalk.cyan('Breakdown:')} ${stats.actualTokens === null ? chalk.dim('(estimated)') : ''}`,
282
+ ` ├─ System prompt: ${formatTokens(breakdown.systemPrompt)} (${pct(breakdown.systemPrompt)})`,
283
+ ` ├─ Tools: ${formatTokens(breakdown.tools.total)} (${pct(breakdown.tools.total)})`,
284
+ ` ├─ Messages: ${formatTokens(breakdown.messages)} (${pct(breakdown.messages)})`,
285
+ ` └─ ${bufferLabel}: ${formatTokens(autoCompactBuffer)} (reserved)`,
286
+ ``,
287
+ ` Messages: ${stats.filteredMessageCount} visible (${stats.messageCount} total)`,
288
+ ];
289
+ // Show pruned tool count if any
290
+ if (stats.prunedToolCount > 0) {
291
+ lines.push(` 🗑️ ${stats.prunedToolCount} tool output(s) pruned`);
292
+ }
293
+ if (stats.hasSummary) {
294
+ lines.push(` 📦 Context has been compacted`);
295
+ }
296
+ if (stats.usagePercent > 100) {
297
+ lines.push(` 💡 Use /compact to manually compact, or send a message to trigger auto-compaction`);
298
+ }
299
+ return formatForInkCli(lines.join('\n'));
300
+ }
301
+ catch (error) {
302
+ const errorMsg = `Failed to get context stats: ${error instanceof Error ? error.message : String(error)}`;
303
+ agent.logger.error(errorMsg);
304
+ return formatForInkCli(`❌ ${errorMsg}`);
305
+ }
306
+ },
307
+ },
151
308
  {
152
309
  name: 'copy',
153
310
  description: 'Copy the last assistant response to clipboard',
@@ -7,8 +7,9 @@
7
7
  * Exports:
8
8
  * - searchCommand: Opens interactive search overlay
9
9
  * - resumeCommand: Shows interactive session selector
10
+ * - renameCommand: Rename the current session
10
11
  *
11
12
  * Note: For headless CLI session management, see src/cli/commands/session-commands.ts
12
13
  */
13
- export { searchCommand, resumeCommand } from './session-commands.js';
14
+ export { searchCommand, resumeCommand, renameCommand } from './session-commands.js';
14
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/interactive-commands/session/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/interactive-commands/session/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC"}
@@ -7,7 +7,8 @@
7
7
  * Exports:
8
8
  * - searchCommand: Opens interactive search overlay
9
9
  * - resumeCommand: Shows interactive session selector
10
+ * - renameCommand: Rename the current session
10
11
  *
11
12
  * Note: For headless CLI session management, see src/cli/commands/session-commands.ts
12
13
  */
13
- export { searchCommand, resumeCommand } from './session-commands.js';
14
+ export { searchCommand, resumeCommand, renameCommand } from './session-commands.js';
@@ -7,6 +7,7 @@
7
7
  * Commands:
8
8
  * - resume: Shows interactive session selector
9
9
  * - search: Opens interactive search overlay
10
+ * - rename: Rename the current session
10
11
  *
11
12
  * Note: For headless CLI session management (list, history, delete),
12
13
  * see src/cli/commands/session-commands.ts
@@ -22,4 +23,10 @@ export declare const resumeCommand: CommandDefinition;
22
23
  * Standalone search command - opens interactive search overlay
23
24
  */
24
25
  export declare const searchCommand: CommandDefinition;
26
+ /**
27
+ * Rename command - rename the current session
28
+ * In interactive CLI, this shows the rename overlay.
29
+ * The overlay is triggered via commandOverlays.ts registry.
30
+ */
31
+ export declare const renameCommand: CommandDefinition;
25
32
  //# sourceMappingURL=session-commands.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-commands.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/interactive-commands/session/session-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAkB,MAAM,sBAAsB,CAAC;AAE9E;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,iBAsB3B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,iBAc3B,CAAC"}
1
+ {"version":3,"file":"session-commands.d.ts","sourceRoot":"","sources":["../../../../../src/cli/commands/interactive-commands/session/session-commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAkB,MAAM,sBAAsB,CAAC;AAE9E;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,iBAsB3B,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,iBAc3B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,aAAa,EAAE,iBAa3B,CAAC"}
@@ -7,6 +7,7 @@
7
7
  * Commands:
8
8
  * - resume: Shows interactive session selector
9
9
  * - search: Opens interactive search overlay
10
+ * - rename: Rename the current session
10
11
  *
11
12
  * Note: For headless CLI session management (list, history, delete),
12
13
  * see src/cli/commands/session-commands.ts
@@ -49,3 +50,18 @@ export const searchCommand = {
49
50
  return true;
50
51
  },
51
52
  };
53
+ /**
54
+ * Rename command - rename the current session
55
+ * In interactive CLI, this shows the rename overlay.
56
+ * The overlay is triggered via commandOverlays.ts registry.
57
+ */
58
+ export const renameCommand = {
59
+ name: 'rename',
60
+ description: 'Rename the current session',
61
+ usage: '/rename',
62
+ category: 'General',
63
+ handler: async (_args, _agent, _ctx) => {
64
+ // Interactive overlay handles everything - just return success
65
+ return true;
66
+ },
67
+ };
@@ -9,10 +9,12 @@
9
9
  */
10
10
  import type { DextoAgent } from '@dexto/core';
11
11
  import type { StartupInfo } from './state/types.js';
12
+ import type { SoundNotificationService } from './utils/soundNotification.js';
12
13
  interface InkCLIProps {
13
14
  agent: DextoAgent;
14
15
  initialSessionId: string | null;
15
16
  startupInfo: StartupInfo;
17
+ soundService: SoundNotificationService | null;
16
18
  }
17
19
  /**
18
20
  * Modern CLI interface using React Ink
@@ -22,7 +24,7 @@ interface InkCLIProps {
22
24
  * - KeypressProvider for unified keyboard input
23
25
  * - MouseProvider (only in alternate buffer mode)
24
26
  */
25
- export declare function InkCLIRefactored({ agent, initialSessionId, startupInfo }: InkCLIProps): import("react/jsx-runtime").JSX.Element;
27
+ export declare function InkCLIRefactored({ agent, initialSessionId, startupInfo, soundService, }: InkCLIProps): import("react/jsx-runtime").JSX.Element;
26
28
  /**
27
29
  * Start the modern Ink-based CLI
28
30
  */
@@ -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;AAoBpD,UAAU,WAAW;IACjB,KAAK,EAAE,UAAU,CAAC;IAClB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,WAAW,CAAC;CAC5B;AAyCD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,EAAE,WAAW,2CAerF;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CACvC,KAAK,EAAE,UAAU,EACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
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,wBAAsB,qBAAqB,CACvC,KAAK,EAAE,UAAU,EACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAChC,OAAO,CAAC,IAAI,CAAC,CAgEf"}
@@ -13,7 +13,7 @@ import { render } from 'ink';
13
13
  import { registerGracefulShutdown } from '../../utils/graceful-shutdown.js';
14
14
  import { enableBracketedPaste, disableBracketedPaste } from './utils/bracketedPaste.js';
15
15
  // Contexts (keyboard/mouse providers)
16
- import { KeypressProvider, MouseProvider, ScrollProvider } from './contexts/index.js';
16
+ import { KeypressProvider, MouseProvider, ScrollProvider, SoundProvider, } from './contexts/index.js';
17
17
  // Components
18
18
  import { ErrorBoundary } from './components/ErrorBoundary.js';
19
19
  import { AlternateBufferCLI, StaticCLI } from './components/modes/index.js';
@@ -28,7 +28,7 @@ const USE_ALTERNATE_BUFFER = false;
28
28
  /**
29
29
  * Inner component that wraps the mode-specific component with providers
30
30
  */
31
- function InkCLIInner({ agent, initialSessionId, startupInfo }) {
31
+ function InkCLIInner({ agent, initialSessionId, startupInfo, soundService }) {
32
32
  // Selection hint callback for alternate buffer mode
33
33
  const [, setSelectionHintShown] = useState(false);
34
34
  // Streaming mode - can be toggled via /stream command
@@ -37,10 +37,10 @@ function InkCLIInner({ agent, initialSessionId, startupInfo }) {
37
37
  setSelectionHintShown(true);
38
38
  }, []);
39
39
  if (USE_ALTERNATE_BUFFER) {
40
- return (_jsx(ScrollProvider, { onSelectionAttempt: handleSelectionAttempt, children: _jsx(AlternateBufferCLI, { agent: agent, initialSessionId: initialSessionId, startupInfo: startupInfo, onSelectionAttempt: handleSelectionAttempt, useStreaming: streaming }) }));
40
+ return (_jsx(SoundProvider, { soundService: soundService, children: _jsx(ScrollProvider, { onSelectionAttempt: handleSelectionAttempt, children: _jsx(AlternateBufferCLI, { agent: agent, initialSessionId: initialSessionId, startupInfo: startupInfo, onSelectionAttempt: handleSelectionAttempt, useStreaming: streaming }) }) }));
41
41
  }
42
42
  // Static mode - no ScrollProvider needed
43
- return (_jsx(StaticCLI, { agent: agent, initialSessionId: initialSessionId, startupInfo: startupInfo, useStreaming: streaming }));
43
+ return (_jsx(SoundProvider, { soundService: soundService, children: _jsx(StaticCLI, { agent: agent, initialSessionId: initialSessionId, startupInfo: startupInfo, useStreaming: streaming }) }));
44
44
  }
45
45
  /**
46
46
  * Modern CLI interface using React Ink
@@ -50,8 +50,8 @@ function InkCLIInner({ agent, initialSessionId, startupInfo }) {
50
50
  * - KeypressProvider for unified keyboard input
51
51
  * - MouseProvider (only in alternate buffer mode)
52
52
  */
53
- export function InkCLIRefactored({ agent, initialSessionId, startupInfo }) {
54
- return (_jsx(ErrorBoundary, { children: _jsx(KeypressProvider, { children: _jsx(MouseProvider, { mouseEventsEnabled: USE_ALTERNATE_BUFFER, children: _jsx(InkCLIInner, { agent: agent, initialSessionId: initialSessionId, startupInfo: startupInfo }) }) }) }));
53
+ export function InkCLIRefactored({ agent, initialSessionId, startupInfo, soundService, }) {
54
+ return (_jsx(ErrorBoundary, { children: _jsx(KeypressProvider, { children: _jsx(MouseProvider, { mouseEventsEnabled: USE_ALTERNATE_BUFFER, children: _jsx(InkCLIInner, { agent: agent, initialSessionId: initialSessionId, startupInfo: startupInfo, soundService: soundService }) }) }) }));
55
55
  }
56
56
  /**
57
57
  * Start the modern Ink-based CLI
@@ -62,7 +62,36 @@ export async function startInkCliRefactored(agent, initialSessionId) {
62
62
  // This wraps pastes with escape sequences that our KeypressContext handles
63
63
  enableBracketedPaste();
64
64
  const startupInfo = await getStartupInfo(agent);
65
- const inkApp = render(_jsx(InkCLIRefactored, { agent: agent, initialSessionId: initialSessionId, startupInfo: startupInfo }), {
65
+ // Initialize sound service from preferences
66
+ const { SoundNotificationService } = await import('./utils/soundNotification.js');
67
+ const { globalPreferencesExist, loadGlobalPreferences } = await import('@dexto/agent-management');
68
+ let soundService = null;
69
+ // Initialize sound config with defaults (enabled by default even without preferences file)
70
+ let soundConfig = {
71
+ enabled: true,
72
+ onApprovalRequired: true,
73
+ onTaskComplete: true,
74
+ };
75
+ // Override with user preferences if they exist
76
+ if (globalPreferencesExist()) {
77
+ try {
78
+ const preferences = await loadGlobalPreferences();
79
+ soundConfig = {
80
+ enabled: preferences.sounds?.enabled ?? soundConfig.enabled,
81
+ onApprovalRequired: preferences.sounds?.onApprovalRequired ?? soundConfig.onApprovalRequired,
82
+ onTaskComplete: preferences.sounds?.onTaskComplete ?? soundConfig.onTaskComplete,
83
+ };
84
+ }
85
+ catch (error) {
86
+ // Log at debug level to help troubleshoot sound configuration issues
87
+ // Continue with default sounds - this is non-critical functionality
88
+ agent.logger.debug(`Sound preferences could not be loaded: ${error instanceof Error ? error.message : String(error)}`);
89
+ }
90
+ }
91
+ if (soundConfig.enabled) {
92
+ soundService = new SoundNotificationService(soundConfig);
93
+ }
94
+ const inkApp = render(_jsx(InkCLIRefactored, { agent: agent, initialSessionId: initialSessionId, startupInfo: startupInfo, soundService: soundService }), {
66
95
  exitOnCtrlC: false,
67
96
  alternateBuffer: USE_ALTERNATE_BUFFER,
68
97
  // Incremental rendering works better with VirtualizedList
@@ -12,6 +12,7 @@ interface StatusBarProps {
12
12
  agent: DextoAgent;
13
13
  isProcessing: boolean;
14
14
  isThinking: boolean;
15
+ isCompacting: boolean;
15
16
  approvalQueueCount: number;
16
17
  copyModeEnabled?: boolean;
17
18
  /** Whether an approval prompt is currently shown */
@@ -25,6 +26,6 @@ interface StatusBarProps {
25
26
  * - Hide spinner during approval wait (user is reviewing, not waiting)
26
27
  * - Only show elapsed time after 30s (avoid visual noise for fast operations)
27
28
  */
28
- export declare function StatusBar({ agent, isProcessing, isThinking, approvalQueueCount, copyModeEnabled, isAwaitingApproval, }: StatusBarProps): import("react/jsx-runtime").JSX.Element | null;
29
+ export declare function StatusBar({ agent, isProcessing, isThinking, isCompacting, approvalQueueCount, copyModeEnabled, isAwaitingApproval, }: StatusBarProps): import("react/jsx-runtime").JSX.Element | null;
29
30
  export {};
30
31
  //# 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,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,oDAAoD;IACpD,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,YAAY,EACZ,UAAU,EACV,kBAAkB,EAClB,eAAuB,EACvB,kBAA0B,GAC7B,EAAE,cAAc,kDAqFhB"}
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;CAChC;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EACtB,KAAK,EACL,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,kBAAkB,EAClB,eAAuB,EACvB,kBAA0B,GAC7B,EAAE,cAAc,kDA6GhB"}
@@ -21,9 +21,9 @@ 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, approvalQueueCount, copyModeEnabled = false, isAwaitingApproval = false, }) {
25
- // Cycle through witty phrases while processing
26
- const { phrase } = usePhraseCycler({ isActive: isProcessing });
24
+ export function StatusBar({ agent, isProcessing, isThinking, isCompacting, approvalQueueCount, copyModeEnabled = false, isAwaitingApproval = false, }) {
25
+ // Cycle through witty phrases while processing (not during compacting)
26
+ const { phrase } = usePhraseCycler({ isActive: isProcessing && !isCompacting });
27
27
  // Track elapsed time during processing
28
28
  const { formatted: elapsedTime, elapsedMs } = useElapsedTime({ isActive: isProcessing });
29
29
  // Track token usage during processing
@@ -42,6 +42,15 @@ export function StatusBar({ agent, isProcessing, isThinking, approvalQueueCount,
42
42
  if (isAwaitingApproval) {
43
43
  return null;
44
44
  }
45
+ // Show compacting state - yellow/orange color to indicate context management
46
+ if (isCompacting) {
47
+ const metaParts = [];
48
+ if (showTime)
49
+ metaParts.push(`(${elapsedTime})`);
50
+ metaParts.push('Esc to cancel');
51
+ const metaContent = metaParts.join(' • ');
52
+ return (_jsxs(Box, { paddingX: 1, marginTop: 1, marginBottom: 1, flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", alignItems: "center", children: [_jsx(Text, { color: "yellow", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { color: "yellow", children: " \uD83D\uDCE6 Compacting context..." })] }), _jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", children: metaContent }) })] }));
53
+ }
45
54
  // Show initial processing state (before streaming starts) - green/teal color
46
55
  // TODO: Rename this event/state to "reasoning" and associate it with actual reasoning tokens
47
56
  // Currently "thinking" event fires before any response, not during reasoning token generation
@@ -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;AAuC9B,UAAU,gBAAgB;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,mEACc,gBAAgB,6CAkNrD,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;AAuC9B,UAAU,gBAAgB;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,2DAA2D;IAC3D,aAAa,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,mEACc,gBAAgB,6CA6NrD,CAAC"}
@@ -113,7 +113,13 @@ export const MessageItem = memo(({ message, terminalWidth = 80 }) => {
113
113
  const toolArgs = parenIndex > 0 ? message.content.slice(parenIndex) : '';
114
114
  // Build the full tool header text for wrapping
115
115
  // Don't include status suffix if we have sub-agent progress (it shows its own status)
116
- const statusSuffix = subAgentProgress ? '' : isRunning ? ' Running...' : isPending ? ' Waiting...' : '';
116
+ const statusSuffix = subAgentProgress
117
+ ? ''
118
+ : isRunning
119
+ ? ' Running...'
120
+ : isPending
121
+ ? ' Waiting...'
122
+ : '';
117
123
  const fullToolText = `${toolName}${toolArgs}${statusSuffix}`;
118
124
  // ToolIcon takes 2 chars ("● "), so available width is terminalWidth - 2
119
125
  const iconWidth = 2;
@@ -124,7 +130,10 @@ export const MessageItem = memo(({ message, terminalWidth = 80 }) => {
124
130
  trim: false,
125
131
  });
126
132
  const toolLines = wrappedToolText.split('\n');
127
- 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] }) })), hasStructuredDisplay ? (_jsx(ToolResultRenderer, { display: message.toolDisplayData, content: message.toolContent })) : (message.toolResult && (_jsx(Box, { flexDirection: "column", children: _jsxs(Text, { color: "gray", children: [" \u23BF ", message.toolResult] }) })))] }));
133
+ 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 &&
134
+ subAgentProgress.tokenUsage.total > 0
135
+ ? ` | ${subAgentProgress.tokenUsage.total.toLocaleString()} tokens`
136
+ : ''] }) })), hasStructuredDisplay ? (_jsx(ToolResultRenderer, { display: message.toolDisplayData, content: message.toolContent })) : (message.toolResult && (_jsx(Box, { flexDirection: "column", children: _jsxs(Text, { color: "gray", children: [" \u23BF ", message.toolResult] }) })))] }));
128
137
  }
129
138
  // System message: Compact gray text
130
139
  return (_jsx(Box, { flexDirection: "column", marginBottom: 1, width: terminalWidth, children: _jsx(Text, { color: "gray", children: message.content }) }));
@@ -147,6 +156,8 @@ export const MessageItem = memo(({ message, terminalWidth = 80 }) => {
147
156
  prev.message.subAgentProgress?.toolsCalled ===
148
157
  next.message.subAgentProgress?.toolsCalled &&
149
158
  prev.message.subAgentProgress?.currentTool ===
150
- next.message.subAgentProgress?.currentTool);
159
+ next.message.subAgentProgress?.currentTool &&
160
+ prev.message.subAgentProgress?.tokenUsage?.total ===
161
+ next.message.subAgentProgress?.tokenUsage?.total);
151
162
  });
152
163
  MessageItem.displayName = 'MessageItem';
@@ -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;AA2BjE,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,2CAiTzB"}
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;AA2BjE,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,2CAkTzB"}