@codex-infinity/pi-infinity 0.60.1 → 0.61.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.
Files changed (263) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/bun/cli.d.ts.map +1 -1
  3. package/dist/bun/cli.js +1 -0
  4. package/dist/bun/cli.js.map +1 -1
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/cli/config-selector.js.map +1 -1
  7. package/dist/cli/file-processor.js.map +1 -1
  8. package/dist/cli/initial-message.js.map +1 -1
  9. package/dist/cli/list-models.js.map +1 -1
  10. package/dist/cli/session-picker.d.ts.map +1 -1
  11. package/dist/cli/session-picker.js +2 -1
  12. package/dist/cli/session-picker.js.map +1 -1
  13. package/dist/cli.d.ts.map +1 -1
  14. package/dist/cli.js +1 -0
  15. package/dist/cli.js.map +1 -1
  16. package/dist/config.js.map +1 -1
  17. package/dist/core/agent-session.d.ts +14 -0
  18. package/dist/core/agent-session.d.ts.map +1 -1
  19. package/dist/core/agent-session.js +107 -72
  20. package/dist/core/agent-session.js.map +1 -1
  21. package/dist/core/auth-storage.js +4 -8
  22. package/dist/core/auth-storage.js.map +1 -1
  23. package/dist/core/bash-executor.js.map +1 -1
  24. package/dist/core/compaction/branch-summarization.js.map +1 -1
  25. package/dist/core/compaction/compaction.js.map +1 -1
  26. package/dist/core/compaction/utils.js.map +1 -1
  27. package/dist/core/event-bus.js.map +1 -1
  28. package/dist/core/exec.d.ts.map +1 -1
  29. package/dist/core/exec.js +7 -3
  30. package/dist/core/exec.js.map +1 -1
  31. package/dist/core/export-html/ansi-to-html.js.map +1 -1
  32. package/dist/core/export-html/index.js.map +1 -1
  33. package/dist/core/export-html/template.css +43 -13
  34. package/dist/core/export-html/template.html +1 -0
  35. package/dist/core/export-html/template.js +107 -0
  36. package/dist/core/export-html/tool-renderer.js.map +1 -1
  37. package/dist/core/extensions/index.d.ts +1 -1
  38. package/dist/core/extensions/index.d.ts.map +1 -1
  39. package/dist/core/extensions/index.js.map +1 -1
  40. package/dist/core/extensions/loader.d.ts.map +1 -1
  41. package/dist/core/extensions/loader.js +4 -4
  42. package/dist/core/extensions/loader.js.map +1 -1
  43. package/dist/core/extensions/runner.d.ts +1 -1
  44. package/dist/core/extensions/runner.d.ts.map +1 -1
  45. package/dist/core/extensions/runner.js +62 -56
  46. package/dist/core/extensions/runner.js.map +1 -1
  47. package/dist/core/extensions/types.d.ts +4 -3
  48. package/dist/core/extensions/types.d.ts.map +1 -1
  49. package/dist/core/extensions/types.js.map +1 -1
  50. package/dist/core/extensions/wrapper.js.map +1 -1
  51. package/dist/core/footer-data-provider.d.ts +9 -2
  52. package/dist/core/footer-data-provider.d.ts.map +1 -1
  53. package/dist/core/footer-data-provider.js +92 -20
  54. package/dist/core/footer-data-provider.js.map +1 -1
  55. package/dist/core/index.d.ts +1 -1
  56. package/dist/core/index.d.ts.map +1 -1
  57. package/dist/core/index.js.map +1 -1
  58. package/dist/core/keybindings.d.ts +268 -51
  59. package/dist/core/keybindings.d.ts.map +1 -1
  60. package/dist/core/keybindings.js +220 -143
  61. package/dist/core/keybindings.js.map +1 -1
  62. package/dist/core/messages.js.map +1 -1
  63. package/dist/core/model-registry.d.ts +1 -0
  64. package/dist/core/model-registry.d.ts.map +1 -1
  65. package/dist/core/model-registry.js +29 -21
  66. package/dist/core/model-registry.js.map +1 -1
  67. package/dist/core/model-resolver.d.ts.map +1 -1
  68. package/dist/core/model-resolver.js +4 -4
  69. package/dist/core/model-resolver.js.map +1 -1
  70. package/dist/core/package-manager.js +0 -6
  71. package/dist/core/package-manager.js.map +1 -1
  72. package/dist/core/prompt-templates.js.map +1 -1
  73. package/dist/core/resolve-config-value.js.map +1 -1
  74. package/dist/core/resource-loader.js +0 -37
  75. package/dist/core/resource-loader.js.map +1 -1
  76. package/dist/core/sdk.d.ts +2 -2
  77. package/dist/core/sdk.d.ts.map +1 -1
  78. package/dist/core/sdk.js +4 -4
  79. package/dist/core/sdk.js.map +1 -1
  80. package/dist/core/session-manager.d.ts +5 -0
  81. package/dist/core/session-manager.d.ts.map +1 -1
  82. package/dist/core/session-manager.js +8 -12
  83. package/dist/core/session-manager.js.map +1 -1
  84. package/dist/core/settings-manager.js +7 -16
  85. package/dist/core/settings-manager.js.map +1 -1
  86. package/dist/core/skills.js.map +1 -1
  87. package/dist/core/slash-commands.d.ts.map +1 -1
  88. package/dist/core/slash-commands.js +2 -1
  89. package/dist/core/slash-commands.js.map +1 -1
  90. package/dist/core/system-prompt.js.map +1 -1
  91. package/dist/core/timings.js.map +1 -1
  92. package/dist/core/tools/bash.d.ts.map +1 -1
  93. package/dist/core/tools/bash.js +12 -10
  94. package/dist/core/tools/bash.js.map +1 -1
  95. package/dist/core/tools/edit-diff.js.map +1 -1
  96. package/dist/core/tools/edit.d.ts.map +1 -1
  97. package/dist/core/tools/edit.js +3 -2
  98. package/dist/core/tools/edit.js.map +1 -1
  99. package/dist/core/tools/file-mutation-queue.d.ts +6 -0
  100. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -0
  101. package/dist/core/tools/file-mutation-queue.js +37 -0
  102. package/dist/core/tools/file-mutation-queue.js.map +1 -0
  103. package/dist/core/tools/find.js.map +1 -1
  104. package/dist/core/tools/grep.js.map +1 -1
  105. package/dist/core/tools/index.d.ts +1 -0
  106. package/dist/core/tools/index.d.ts.map +1 -1
  107. package/dist/core/tools/index.js +1 -0
  108. package/dist/core/tools/index.js.map +1 -1
  109. package/dist/core/tools/ls.js.map +1 -1
  110. package/dist/core/tools/path-utils.js.map +1 -1
  111. package/dist/core/tools/read.js.map +1 -1
  112. package/dist/core/tools/truncate.js.map +1 -1
  113. package/dist/core/tools/write.d.ts.map +1 -1
  114. package/dist/core/tools/write.js +6 -3
  115. package/dist/core/tools/write.js.map +1 -1
  116. package/dist/index.d.ts +3 -3
  117. package/dist/index.d.ts.map +1 -1
  118. package/dist/index.js +2 -2
  119. package/dist/index.js.map +1 -1
  120. package/dist/main.d.ts.map +1 -1
  121. package/dist/main.js +10 -5
  122. package/dist/main.js.map +1 -1
  123. package/dist/migrations.js.map +1 -1
  124. package/dist/modes/interactive/components/armin.js +6 -10
  125. package/dist/modes/interactive/components/armin.js.map +1 -1
  126. package/dist/modes/interactive/components/assistant-message.js +0 -4
  127. package/dist/modes/interactive/components/assistant-message.js.map +1 -1
  128. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  129. package/dist/modes/interactive/components/bash-execution.js +8 -14
  130. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  131. package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
  132. package/dist/modes/interactive/components/bordered-loader.js +1 -4
  133. package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
  134. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  135. package/dist/modes/interactive/components/branch-summary-message.js +3 -5
  136. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  137. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  138. package/dist/modes/interactive/components/compaction-summary-message.js +3 -5
  139. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  140. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  141. package/dist/modes/interactive/components/config-selector.js +14 -23
  142. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  143. package/dist/modes/interactive/components/countdown-timer.js +0 -5
  144. package/dist/modes/interactive/components/countdown-timer.js.map +1 -1
  145. package/dist/modes/interactive/components/custom-editor.d.ts +3 -3
  146. package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
  147. package/dist/modes/interactive/components/custom-editor.js +7 -14
  148. package/dist/modes/interactive/components/custom-editor.js.map +1 -1
  149. package/dist/modes/interactive/components/custom-message.js +1 -6
  150. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  151. package/dist/modes/interactive/components/daxnuts.js +6 -8
  152. package/dist/modes/interactive/components/daxnuts.js.map +1 -1
  153. package/dist/modes/interactive/components/diff.js.map +1 -1
  154. package/dist/modes/interactive/components/dynamic-border.js +0 -1
  155. package/dist/modes/interactive/components/dynamic-border.js.map +1 -1
  156. package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  157. package/dist/modes/interactive/components/extension-editor.js +10 -15
  158. package/dist/modes/interactive/components/extension-editor.js.map +1 -1
  159. package/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  160. package/dist/modes/interactive/components/extension-input.js +7 -13
  161. package/dist/modes/interactive/components/extension-input.js.map +1 -1
  162. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  163. package/dist/modes/interactive/components/extension-selector.js +9 -16
  164. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  165. package/dist/modes/interactive/components/footer.js +1 -3
  166. package/dist/modes/interactive/components/footer.js.map +1 -1
  167. package/dist/modes/interactive/components/index.d.ts +1 -1
  168. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  169. package/dist/modes/interactive/components/index.js +1 -1
  170. package/dist/modes/interactive/components/index.js.map +1 -1
  171. package/dist/modes/interactive/components/keybinding-hints.d.ts +3 -36
  172. package/dist/modes/interactive/components/keybinding-hints.d.ts.map +1 -1
  173. package/dist/modes/interactive/components/keybinding-hints.js +5 -44
  174. package/dist/modes/interactive/components/keybinding-hints.js.map +1 -1
  175. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  176. package/dist/modes/interactive/components/login-dialog.js +9 -15
  177. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  178. package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  179. package/dist/modes/interactive/components/model-selector.js +20 -28
  180. package/dist/modes/interactive/components/model-selector.js.map +1 -1
  181. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  182. package/dist/modes/interactive/components/oauth-selector.js +8 -13
  183. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  184. package/dist/modes/interactive/components/scoped-models-selector.d.ts.map +1 -1
  185. package/dist/modes/interactive/components/scoped-models-selector.js +13 -17
  186. package/dist/modes/interactive/components/scoped-models-selector.js.map +1 -1
  187. package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
  188. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  189. package/dist/modes/interactive/components/session-selector.js +65 -93
  190. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  191. package/dist/modes/interactive/components/settings-selector.js +0 -2
  192. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  193. package/dist/modes/interactive/components/show-images-selector.js +0 -1
  194. package/dist/modes/interactive/components/show-images-selector.js.map +1 -1
  195. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  196. package/dist/modes/interactive/components/skill-invocation-message.js +3 -5
  197. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  198. package/dist/modes/interactive/components/theme-selector.js +0 -2
  199. package/dist/modes/interactive/components/theme-selector.js.map +1 -1
  200. package/dist/modes/interactive/components/thinking-selector.js +0 -1
  201. package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
  202. package/dist/modes/interactive/components/tool-execution.d.ts +7 -0
  203. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  204. package/dist/modes/interactive/components/tool-execution.js +57 -28
  205. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  206. package/dist/modes/interactive/components/tree-selector.d.ts.map +1 -1
  207. package/dist/modes/interactive/components/tree-selector.js +32 -46
  208. package/dist/modes/interactive/components/tree-selector.js.map +1 -1
  209. package/dist/modes/interactive/components/user-message-selector.d.ts.map +1 -1
  210. package/dist/modes/interactive/components/user-message-selector.js +9 -12
  211. package/dist/modes/interactive/components/user-message-selector.js.map +1 -1
  212. package/dist/modes/interactive/components/user-message.js.map +1 -1
  213. package/dist/modes/interactive/components/visual-truncate.js.map +1 -1
  214. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  215. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  216. package/dist/modes/interactive/interactive-mode.js +210 -170
  217. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  218. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  219. package/dist/modes/interactive/theme/theme.js +49 -42
  220. package/dist/modes/interactive/theme/theme.js.map +1 -1
  221. package/dist/modes/print-mode.js.map +1 -1
  222. package/dist/modes/rpc/jsonl.js.map +1 -1
  223. package/dist/modes/rpc/rpc-client.js +6 -7
  224. package/dist/modes/rpc/rpc-client.js.map +1 -1
  225. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  226. package/dist/modes/rpc/rpc-mode.js +4 -1
  227. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  228. package/dist/utils/changelog.js.map +1 -1
  229. package/dist/utils/child-process.d.ts +11 -0
  230. package/dist/utils/child-process.d.ts.map +1 -0
  231. package/dist/utils/child-process.js +78 -0
  232. package/dist/utils/child-process.js.map +1 -0
  233. package/dist/utils/clipboard-image.js.map +1 -1
  234. package/dist/utils/clipboard-native.d.ts +1 -0
  235. package/dist/utils/clipboard-native.d.ts.map +1 -1
  236. package/dist/utils/clipboard-native.js.map +1 -1
  237. package/dist/utils/clipboard.d.ts +1 -1
  238. package/dist/utils/clipboard.d.ts.map +1 -1
  239. package/dist/utils/clipboard.js +11 -1
  240. package/dist/utils/clipboard.js.map +1 -1
  241. package/dist/utils/exif-orientation.js.map +1 -1
  242. package/dist/utils/frontmatter.js.map +1 -1
  243. package/dist/utils/git.js.map +1 -1
  244. package/dist/utils/image-convert.js.map +1 -1
  245. package/dist/utils/image-resize.js.map +1 -1
  246. package/dist/utils/mime.js.map +1 -1
  247. package/dist/utils/photon.js.map +1 -1
  248. package/dist/utils/shell.js.map +1 -1
  249. package/dist/utils/sleep.js.map +1 -1
  250. package/dist/utils/tools-manager.js.map +1 -1
  251. package/docs/extensions.md +42 -5
  252. package/docs/keybindings.md +101 -112
  253. package/examples/extensions/antigravity-image-gen.ts +5 -3
  254. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  255. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  256. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  257. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  258. package/examples/extensions/subagent/index.ts +7 -5
  259. package/examples/extensions/tool-override.ts +9 -7
  260. package/examples/extensions/truncated-tool.ts +6 -3
  261. package/examples/extensions/with-deps/package-lock.json +2 -2
  262. package/examples/extensions/with-deps/package.json +1 -1
  263. package/package.json +4 -4
@@ -6,7 +6,7 @@ import * as crypto from "node:crypto";
6
6
  import * as fs from "node:fs";
7
7
  import * as os from "node:os";
8
8
  import * as path from "node:path";
9
- import { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, Text, TruncatedText, TUI, visibleWidth, } from "@mariozechner/pi-tui";
9
+ import { CombinedAutocompleteProvider, Container, fuzzyFilter, Loader, Markdown, matchesKey, ProcessTerminal, Spacer, setKeybindings, Text, TruncatedText, TUI, visibleWidth, } from "@mariozechner/pi-tui";
10
10
  import { spawn, spawnSync } from "child_process";
11
11
  import { APP_NAME, getAgentDir, getAuthPath, getDebugLogPath, getShareViewerUrl, getUpdateInstruction, VERSION, } from "../../config.js";
12
12
  import { parseSkillBlock } from "../../core/agent-session.js";
@@ -35,7 +35,7 @@ import { ExtensionEditorComponent } from "./components/extension-editor.js";
35
35
  import { ExtensionInputComponent } from "./components/extension-input.js";
36
36
  import { ExtensionSelectorComponent } from "./components/extension-selector.js";
37
37
  import { FooterComponent } from "./components/footer.js";
38
- import { appKey, appKeyHint, editorKey, keyHint, rawKeyHint } from "./components/keybinding-hints.js";
38
+ import { keyHint, keyText, rawKeyHint } from "./components/keybinding-hints.js";
39
39
  import { LoginDialogComponent } from "./components/login-dialog.js";
40
40
  import { ModelSelectorComponent } from "./components/model-selector.js";
41
41
  import { OAuthSelectorComponent } from "./components/oauth-selector.js";
@@ -52,79 +52,6 @@ function isExpandable(obj) {
52
52
  return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
53
53
  }
54
54
  export class InteractiveMode {
55
- options;
56
- session;
57
- ui;
58
- chatContainer;
59
- pendingMessagesContainer;
60
- statusContainer;
61
- defaultEditor;
62
- editor;
63
- autocompleteProvider;
64
- fdPath;
65
- editorContainer;
66
- footer;
67
- footerDataProvider;
68
- keybindings;
69
- version;
70
- isInitialized = false;
71
- onInputCallback;
72
- loadingAnimation = undefined;
73
- pendingWorkingMessage = undefined;
74
- defaultWorkingMessage = "Working...";
75
- lastSigintTime = 0;
76
- lastEscapeTime = 0;
77
- changelogMarkdown = undefined;
78
- // Status line tracking (for mutating immediately-sequential status updates)
79
- lastStatusSpacer = undefined;
80
- lastStatusText = undefined;
81
- // Streaming message tracking
82
- streamingComponent = undefined;
83
- streamingMessage = undefined;
84
- // Tool execution tracking: toolCallId -> component
85
- pendingTools = new Map();
86
- // Tool output expansion state
87
- toolOutputExpanded = false;
88
- // Thinking block visibility state
89
- hideThinkingBlock = false;
90
- // Skill commands: command name -> skill file path
91
- skillCommands = new Map();
92
- // Agent subscription unsubscribe function
93
- unsubscribe;
94
- // Track if editor is in bash mode (text starts with !)
95
- isBashMode = false;
96
- // Track current bash execution component
97
- bashComponent = undefined;
98
- // Track pending bash components (shown in pending area, moved to chat on submit)
99
- pendingBashComponents = [];
100
- // Auto-compaction state
101
- autoCompactionLoader = undefined;
102
- autoCompactionEscapeHandler;
103
- // Auto-retry state
104
- retryLoader = undefined;
105
- retryEscapeHandler;
106
- // Messages queued while compaction is running
107
- compactionQueuedMessages = [];
108
- // Shutdown state
109
- shutdownRequested = false;
110
- // Extension UI state
111
- extensionSelector = undefined;
112
- extensionInput = undefined;
113
- extensionEditor = undefined;
114
- extensionTerminalInputUnsubscribers = new Set();
115
- // Extension widgets (components rendered above/below the editor)
116
- extensionWidgetsAbove = new Map();
117
- extensionWidgetsBelow = new Map();
118
- widgetContainerAbove;
119
- widgetContainerBelow;
120
- // Custom footer from extension (undefined = use built-in footer)
121
- customFooter = undefined;
122
- // Header container that holds the built-in or custom header
123
- headerContainer;
124
- // Built-in header (logo + keybinding hints + changelog)
125
- builtInHeader = undefined;
126
- // Custom header from extension (undefined = use built-in header)
127
- customHeader = undefined;
128
55
  // Convenience accessors
129
56
  get agent() {
130
57
  return this.session.agent;
@@ -137,6 +64,60 @@ export class InteractiveMode {
137
64
  }
138
65
  constructor(session, options = {}) {
139
66
  this.options = options;
67
+ this.isInitialized = false;
68
+ this.loadingAnimation = undefined;
69
+ this.pendingWorkingMessage = undefined;
70
+ this.defaultWorkingMessage = "Working...";
71
+ this.lastSigintTime = 0;
72
+ this.lastEscapeTime = 0;
73
+ this.changelogMarkdown = undefined;
74
+ // Status line tracking (for mutating immediately-sequential status updates)
75
+ this.lastStatusSpacer = undefined;
76
+ this.lastStatusText = undefined;
77
+ // Streaming message tracking
78
+ this.streamingComponent = undefined;
79
+ this.streamingMessage = undefined;
80
+ // Tool execution tracking: toolCallId -> component
81
+ this.pendingTools = new Map();
82
+ // Tool output expansion state
83
+ this.toolOutputExpanded = false;
84
+ // Thinking block visibility state
85
+ this.hideThinkingBlock = false;
86
+ // Skill commands: command name -> skill file path
87
+ this.skillCommands = new Map();
88
+ // Track if editor is in bash mode (text starts with !)
89
+ this.isBashMode = false;
90
+ // Track current bash execution component
91
+ this.bashComponent = undefined;
92
+ // Track pending bash components (shown in pending area, moved to chat on submit)
93
+ this.pendingBashComponents = [];
94
+ // Auto-compaction state
95
+ this.autoCompactionLoader = undefined;
96
+ // Auto-retry state
97
+ this.retryLoader = undefined;
98
+ // Messages queued while compaction is running
99
+ this.compactionQueuedMessages = [];
100
+ // Shutdown state
101
+ this.shutdownRequested = false;
102
+ // Extension UI state
103
+ this.extensionSelector = undefined;
104
+ this.extensionInput = undefined;
105
+ this.extensionEditor = undefined;
106
+ this.extensionTerminalInputUnsubscribers = new Set();
107
+ // Extension widgets (components rendered above/below the editor)
108
+ this.extensionWidgetsAbove = new Map();
109
+ this.extensionWidgetsBelow = new Map();
110
+ // Custom footer from extension (undefined = use built-in footer)
111
+ this.customFooter = undefined;
112
+ // Built-in header (logo + keybinding hints + changelog)
113
+ this.builtInHeader = undefined;
114
+ // Custom header from extension (undefined = use built-in header)
115
+ this.customHeader = undefined;
116
+ /**
117
+ * Gracefully shutdown the agent.
118
+ * Emits shutdown event to extensions, then exits.
119
+ */
120
+ this.isShuttingDown = false;
140
121
  this.session = session;
141
122
  this.version = VERSION;
142
123
  this.ui = new TUI(new ProcessTerminal(), this.settingsManager.getShowHardwareCursor());
@@ -148,6 +129,7 @@ export class InteractiveMode {
148
129
  this.widgetContainerAbove = new Container();
149
130
  this.widgetContainerBelow = new Container();
150
131
  this.keybindings = KeybindingsManager.create();
132
+ setKeybindings(this.keybindings);
151
133
  const editorPaddingX = this.settingsManager.getEditorPaddingX();
152
134
  const autocompleteMaxVisible = this.settingsManager.getAutocompleteMaxVisible();
153
135
  this.defaultEditor = new CustomEditor(this.ui, getEditorTheme(), this.keybindings, {
@@ -242,27 +224,26 @@ export class InteractiveMode {
242
224
  if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
243
225
  const logo = theme.bold(theme.fg("accent", APP_NAME)) + theme.fg("dim", ` v${this.version}`);
244
226
  // Build startup instructions using keybinding hint helpers
245
- const kb = this.keybindings;
246
- const hint = (action, desc) => appKeyHint(kb, action, desc);
227
+ const hint = (keybinding, description) => keyHint(keybinding, description);
247
228
  const instructions = [
248
- hint("interrupt", "to interrupt"),
249
- hint("clear", "to clear"),
250
- rawKeyHint(`${appKey(kb, "clear")} twice`, "to exit"),
251
- hint("exit", "to exit (empty)"),
252
- hint("suspend", "to suspend"),
253
- keyHint("deleteToLineEnd", "to delete to end"),
254
- hint("cycleThinkingLevel", "to cycle thinking level"),
255
- rawKeyHint(`${appKey(kb, "cycleModelForward")}/${appKey(kb, "cycleModelBackward")}`, "to cycle models"),
256
- hint("selectModel", "to select model"),
257
- hint("expandTools", "to expand tools"),
258
- hint("toggleThinking", "to expand thinking"),
259
- hint("externalEditor", "for external editor"),
229
+ hint("app.interrupt", "to interrupt"),
230
+ hint("app.clear", "to clear"),
231
+ rawKeyHint(`${keyText("app.clear")} twice`, "to exit"),
232
+ hint("app.exit", "to exit (empty)"),
233
+ hint("app.suspend", "to suspend"),
234
+ keyHint("tui.editor.deleteToLineEnd", "to delete to end"),
235
+ hint("app.thinking.cycle", "to cycle thinking level"),
236
+ rawKeyHint(`${keyText("app.model.cycleForward")}/${keyText("app.model.cycleBackward")}`, "to cycle models"),
237
+ hint("app.model.select", "to select model"),
238
+ hint("app.tools.expand", "to expand tools"),
239
+ hint("app.thinking.toggle", "to expand thinking"),
240
+ hint("app.editor.external", "for external editor"),
260
241
  rawKeyHint("/", "for commands"),
261
242
  rawKeyHint("!", "to run bash"),
262
243
  rawKeyHint("!!", "to run bash (no context)"),
263
- hint("followUp", "to queue follow-up"),
264
- hint("dequeue", "to edit all queued messages"),
265
- hint("pasteImage", "to paste image"),
244
+ hint("app.message.followUp", "to queue follow-up"),
245
+ hint("app.message.dequeue", "to edit all queued messages"),
246
+ hint("app.clipboard.pasteImage", "to paste image"),
266
247
  rawKeyHint("drop files", "to attach"),
267
248
  ].join("\n");
268
249
  this.builtInHeader = new Text(`${logo}\n${instructions}`, 1, 0);
@@ -1049,11 +1030,11 @@ export class InteractiveMode {
1049
1030
  this.defaultEditor.onExtensionShortcut = undefined;
1050
1031
  this.updateTerminalTitle();
1051
1032
  if (this.loadingAnimation) {
1052
- this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`);
1033
+ this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
1053
1034
  }
1054
1035
  }
1055
1036
  // Maximum total widget lines to prevent viewport overflow
1056
- static MAX_WIDGET_LINES = 10;
1037
+ static { this.MAX_WIDGET_LINES = 10; }
1057
1038
  /**
1058
1039
  * Render all extension widgets to the widget container.
1059
1040
  */
@@ -1172,7 +1153,7 @@ export class InteractiveMode {
1172
1153
  this.loadingAnimation.setMessage(message);
1173
1154
  }
1174
1155
  else {
1175
- this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${appKey(this.keybindings, "interrupt")} to interrupt)`);
1156
+ this.loadingAnimation.setMessage(`${this.defaultWorkingMessage} (${keyText("app.interrupt")} to interrupt)`);
1176
1157
  }
1177
1158
  }
1178
1159
  else {
@@ -1530,24 +1511,24 @@ export class InteractiveMode {
1530
1511
  }
1531
1512
  };
1532
1513
  // Register app action handlers
1533
- this.defaultEditor.onAction("clear", () => this.handleCtrlC());
1514
+ this.defaultEditor.onAction("app.clear", () => this.handleCtrlC());
1534
1515
  this.defaultEditor.onCtrlD = () => this.handleCtrlD();
1535
- this.defaultEditor.onAction("suspend", () => this.handleCtrlZ());
1536
- this.defaultEditor.onAction("cycleThinkingLevel", () => this.cycleThinkingLevel());
1537
- this.defaultEditor.onAction("cycleModelForward", () => this.cycleModel("forward"));
1538
- this.defaultEditor.onAction("cycleModelBackward", () => this.cycleModel("backward"));
1516
+ this.defaultEditor.onAction("app.suspend", () => this.handleCtrlZ());
1517
+ this.defaultEditor.onAction("app.thinking.cycle", () => this.cycleThinkingLevel());
1518
+ this.defaultEditor.onAction("app.model.cycleForward", () => this.cycleModel("forward"));
1519
+ this.defaultEditor.onAction("app.model.cycleBackward", () => this.cycleModel("backward"));
1539
1520
  // Global debug handler on TUI (works regardless of focus)
1540
1521
  this.ui.onDebug = () => this.handleDebugCommand();
1541
- this.defaultEditor.onAction("selectModel", () => this.showModelSelector());
1542
- this.defaultEditor.onAction("expandTools", () => this.toggleToolOutputExpansion());
1543
- this.defaultEditor.onAction("toggleThinking", () => this.toggleThinkingBlockVisibility());
1544
- this.defaultEditor.onAction("externalEditor", () => this.openExternalEditor());
1545
- this.defaultEditor.onAction("followUp", () => this.handleFollowUp());
1546
- this.defaultEditor.onAction("dequeue", () => this.handleDequeue());
1547
- this.defaultEditor.onAction("newSession", () => this.handleClearCommand());
1548
- this.defaultEditor.onAction("tree", () => this.showTreeSelector());
1549
- this.defaultEditor.onAction("fork", () => this.showUserMessageSelector());
1550
- this.defaultEditor.onAction("resume", () => this.showSessionSelector());
1522
+ this.defaultEditor.onAction("app.model.select", () => this.showModelSelector());
1523
+ this.defaultEditor.onAction("app.tools.expand", () => this.toggleToolOutputExpansion());
1524
+ this.defaultEditor.onAction("app.thinking.toggle", () => this.toggleThinkingBlockVisibility());
1525
+ this.defaultEditor.onAction("app.editor.external", () => this.openExternalEditor());
1526
+ this.defaultEditor.onAction("app.message.followUp", () => this.handleFollowUp());
1527
+ this.defaultEditor.onAction("app.message.dequeue", () => this.handleDequeue());
1528
+ this.defaultEditor.onAction("app.session.new", () => this.handleClearCommand());
1529
+ this.defaultEditor.onAction("app.session.tree", () => this.showTreeSelector());
1530
+ this.defaultEditor.onAction("app.session.fork", () => this.showUserMessageSelector());
1531
+ this.defaultEditor.onAction("app.session.resume", () => this.showSessionSelector());
1551
1532
  this.defaultEditor.onChange = (text) => {
1552
1533
  const wasBashMode = this.isBashMode;
1553
1534
  this.isBashMode = text.trimStart().startsWith("!");
@@ -1607,13 +1588,18 @@ export class InteractiveMode {
1607
1588
  this.editor.setText("");
1608
1589
  return;
1609
1590
  }
1591
+ if (text.startsWith("/import")) {
1592
+ await this.handleImportCommand(text);
1593
+ this.editor.setText("");
1594
+ return;
1595
+ }
1610
1596
  if (text === "/share") {
1611
1597
  await this.handleShareCommand();
1612
1598
  this.editor.setText("");
1613
1599
  return;
1614
1600
  }
1615
1601
  if (text === "/copy") {
1616
- this.handleCopyCommand();
1602
+ await this.handleCopyCommand();
1617
1603
  this.editor.setText("");
1618
1604
  return;
1619
1605
  }
@@ -1861,15 +1847,17 @@ export class InteractiveMode {
1861
1847
  this.ui.requestRender();
1862
1848
  break;
1863
1849
  case "tool_execution_start": {
1864
- if (!this.pendingTools.has(event.toolCallId)) {
1865
- const component = new ToolExecutionComponent(event.toolName, event.args, {
1850
+ let component = this.pendingTools.get(event.toolCallId);
1851
+ if (!component) {
1852
+ component = new ToolExecutionComponent(event.toolName, event.args, {
1866
1853
  showImages: this.settingsManager.getShowImages(),
1867
1854
  }, this.getRegisteredToolDefinition(event.toolName), this.ui);
1868
1855
  component.setExpanded(this.toolOutputExpanded);
1869
1856
  this.chatContainer.addChild(component);
1870
1857
  this.pendingTools.set(event.toolCallId, component);
1871
- this.ui.requestRender();
1872
1858
  }
1859
+ component.markExecutionStarted();
1860
+ this.ui.requestRender();
1873
1861
  break;
1874
1862
  }
1875
1863
  case "tool_execution_update": {
@@ -1914,7 +1902,7 @@ export class InteractiveMode {
1914
1902
  // Show compacting indicator with reason
1915
1903
  this.statusContainer.clear();
1916
1904
  const reasonText = event.reason === "overflow" ? "Context overflow detected, " : "";
1917
- this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `${reasonText}Auto-compacting... (${appKey(this.keybindings, "interrupt")} to cancel)`);
1905
+ this.autoCompactionLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `${reasonText}Auto-compacting... (${keyText("app.interrupt")} to cancel)`);
1918
1906
  this.statusContainer.addChild(this.autoCompactionLoader);
1919
1907
  this.ui.requestRender();
1920
1908
  break;
@@ -1966,7 +1954,7 @@ export class InteractiveMode {
1966
1954
  // Show retry indicator
1967
1955
  this.statusContainer.clear();
1968
1956
  const delaySeconds = Math.round(event.delayMs / 1000);
1969
- this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), `Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s... (${appKey(this.keybindings, "interrupt")} to cancel)`);
1957
+ this.retryLoader = new Loader(this.ui, (spinner) => theme.fg("warning", spinner), (text) => theme.fg("muted", text), `Retrying (${event.attempt}/${event.maxAttempts}) in ${delaySeconds}s... (${keyText("app.interrupt")} to cancel)`);
1970
1958
  this.statusContainer.addChild(this.retryLoader);
1971
1959
  this.ui.requestRender();
1972
1960
  break;
@@ -2201,11 +2189,6 @@ export class InteractiveMode {
2201
2189
  // Only called when editor is empty (enforced by CustomEditor)
2202
2190
  void this.shutdown();
2203
2191
  }
2204
- /**
2205
- * Gracefully shutdown the agent.
2206
- * Emits shutdown event to extensions, then exits.
2207
- */
2208
- isShuttingDown = false;
2209
2192
  async shutdown() {
2210
2193
  if (this.isShuttingDown)
2211
2194
  return;
@@ -2235,20 +2218,32 @@ export class InteractiveMode {
2235
2218
  await this.shutdown();
2236
2219
  }
2237
2220
  handleCtrlZ() {
2221
+ // Keep the event loop alive while suspended. Without this, stopping the TUI
2222
+ // can leave Node with no ref'ed handles, causing the process to exit on fg
2223
+ // before the SIGCONT handler gets a chance to restore the terminal.
2224
+ const suspendKeepAlive = setInterval(() => { }, 2 ** 30);
2238
2225
  // Ignore SIGINT while suspended so Ctrl+C in the terminal does not
2239
2226
  // kill the backgrounded process. The handler is removed on resume.
2240
2227
  const ignoreSigint = () => { };
2241
2228
  process.on("SIGINT", ignoreSigint);
2242
2229
  // Set up handler to restore TUI when resumed
2243
2230
  process.once("SIGCONT", () => {
2231
+ clearInterval(suspendKeepAlive);
2244
2232
  process.removeListener("SIGINT", ignoreSigint);
2245
2233
  this.ui.start();
2246
2234
  this.ui.requestRender(true);
2247
2235
  });
2248
- // Stop the TUI (restore terminal to normal mode)
2249
- this.ui.stop();
2250
- // Send SIGTSTP to process group (pid=0 means all processes in group)
2251
- process.kill(0, "SIGTSTP");
2236
+ try {
2237
+ // Stop the TUI (restore terminal to normal mode)
2238
+ this.ui.stop();
2239
+ // Send SIGTSTP to process group (pid=0 means all processes in group)
2240
+ process.kill(0, "SIGTSTP");
2241
+ }
2242
+ catch (error) {
2243
+ clearInterval(suspendKeepAlive);
2244
+ process.removeListener("SIGINT", ignoreSigint);
2245
+ throw error;
2246
+ }
2252
2247
  }
2253
2248
  async handleFollowUp() {
2254
2249
  const text = (this.editor.getExpandedText?.() ?? this.editor.getText()).trim();
@@ -2481,7 +2476,7 @@ export class InteractiveMode {
2481
2476
  const text = theme.fg("dim", `Follow-up: ${message}`);
2482
2477
  this.pendingMessagesContainer.addChild(new TruncatedText(text, 1, 0));
2483
2478
  }
2484
- const dequeueHint = this.getAppKeyDisplay("dequeue");
2479
+ const dequeueHint = this.getAppKeyDisplay("app.message.dequeue");
2485
2480
  const hintText = theme.fg("dim", `↳ ${dequeueHint} to edit all queued messages`);
2486
2481
  this.pendingMessagesContainer.addChild(new TruncatedText(hintText, 1, 0));
2487
2482
  }
@@ -3003,7 +2998,7 @@ export class InteractiveMode {
3003
2998
  this.session.abortBranchSummary();
3004
2999
  };
3005
3000
  this.chatContainer.addChild(new Spacer(1));
3006
- summaryLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `Summarizing branch... (${appKey(this.keybindings, "interrupt")} to cancel)`);
3001
+ summaryLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), `Summarizing branch... (${keyText("app.interrupt")} to cancel)`);
3007
3002
  this.statusContainer.addChild(summaryLoader);
3008
3003
  this.ui.requestRender();
3009
3004
  }
@@ -3285,13 +3280,58 @@ export class InteractiveMode {
3285
3280
  const parts = text.split(/\s+/);
3286
3281
  const outputPath = parts.length > 1 ? parts[1] : undefined;
3287
3282
  try {
3288
- const filePath = await this.session.exportToHtml(outputPath);
3289
- this.showStatus(`Session exported to: ${filePath}`);
3283
+ if (outputPath?.endsWith(".jsonl")) {
3284
+ const filePath = this.session.exportToJsonl(outputPath);
3285
+ this.showStatus(`Session exported to: ${filePath}`);
3286
+ }
3287
+ else {
3288
+ const filePath = await this.session.exportToHtml(outputPath);
3289
+ this.showStatus(`Session exported to: ${filePath}`);
3290
+ }
3290
3291
  }
3291
3292
  catch (error) {
3292
3293
  this.showError(`Failed to export session: ${error instanceof Error ? error.message : "Unknown error"}`);
3293
3294
  }
3294
3295
  }
3296
+ async handleImportCommand(text) {
3297
+ const parts = text.split(/\s+/);
3298
+ if (parts.length < 2 || !parts[1]) {
3299
+ this.showError("Usage: /import <path.jsonl>");
3300
+ return;
3301
+ }
3302
+ const inputPath = parts[1];
3303
+ const confirmed = await this.showExtensionConfirm("Import session", `Replace current session with ${inputPath}?`);
3304
+ if (!confirmed) {
3305
+ this.showStatus("Import cancelled");
3306
+ return;
3307
+ }
3308
+ try {
3309
+ // Stop loading animation
3310
+ if (this.loadingAnimation) {
3311
+ this.loadingAnimation.stop();
3312
+ this.loadingAnimation = undefined;
3313
+ }
3314
+ this.statusContainer.clear();
3315
+ // Clear UI state
3316
+ this.pendingMessagesContainer.clear();
3317
+ this.compactionQueuedMessages = [];
3318
+ this.streamingComponent = undefined;
3319
+ this.streamingMessage = undefined;
3320
+ this.pendingTools.clear();
3321
+ const success = await this.session.importFromJsonl(inputPath);
3322
+ if (!success) {
3323
+ this.showWarning("Import cancelled");
3324
+ return;
3325
+ }
3326
+ // Clear and re-render the chat
3327
+ this.chatContainer.clear();
3328
+ this.renderInitialMessages();
3329
+ this.showStatus(`Session imported from: ${inputPath}`);
3330
+ }
3331
+ catch (error) {
3332
+ this.showError(`Failed to import session: ${error instanceof Error ? error.message : "Unknown error"}`);
3333
+ }
3334
+ }
3295
3335
  async handleShareCommand() {
3296
3336
  // Check if gh is available and logged in
3297
3337
  try {
@@ -3379,14 +3419,14 @@ export class InteractiveMode {
3379
3419
  }
3380
3420
  }
3381
3421
  }
3382
- handleCopyCommand() {
3422
+ async handleCopyCommand() {
3383
3423
  const text = this.session.getLastAssistantText();
3384
3424
  if (!text) {
3385
3425
  this.showError("No agent messages to copy yet.");
3386
3426
  return;
3387
3427
  }
3388
3428
  try {
3389
- copyToClipboard(text);
3429
+ await copyToClipboard(text);
3390
3430
  this.showStatus("Copied last agent message to clipboard");
3391
3431
  }
3392
3432
  catch (error) {
@@ -3479,54 +3519,54 @@ export class InteractiveMode {
3479
3519
  * Get capitalized display string for an app keybinding action.
3480
3520
  */
3481
3521
  getAppKeyDisplay(action) {
3482
- return this.capitalizeKey(appKey(this.keybindings, action));
3522
+ return this.capitalizeKey(keyText(action));
3483
3523
  }
3484
3524
  /**
3485
3525
  * Get capitalized display string for an editor keybinding action.
3486
3526
  */
3487
3527
  getEditorKeyDisplay(action) {
3488
- return this.capitalizeKey(editorKey(action));
3528
+ return this.capitalizeKey(keyText(action));
3489
3529
  }
3490
3530
  handleHotkeysCommand() {
3491
3531
  // Navigation keybindings
3492
- const cursorUp = this.getEditorKeyDisplay("cursorUp");
3493
- const cursorDown = this.getEditorKeyDisplay("cursorDown");
3494
- const cursorLeft = this.getEditorKeyDisplay("cursorLeft");
3495
- const cursorRight = this.getEditorKeyDisplay("cursorRight");
3496
- const cursorWordLeft = this.getEditorKeyDisplay("cursorWordLeft");
3497
- const cursorWordRight = this.getEditorKeyDisplay("cursorWordRight");
3498
- const cursorLineStart = this.getEditorKeyDisplay("cursorLineStart");
3499
- const cursorLineEnd = this.getEditorKeyDisplay("cursorLineEnd");
3500
- const jumpForward = this.getEditorKeyDisplay("jumpForward");
3501
- const jumpBackward = this.getEditorKeyDisplay("jumpBackward");
3502
- const pageUp = this.getEditorKeyDisplay("pageUp");
3503
- const pageDown = this.getEditorKeyDisplay("pageDown");
3532
+ const cursorUp = this.getEditorKeyDisplay("tui.editor.cursorUp");
3533
+ const cursorDown = this.getEditorKeyDisplay("tui.editor.cursorDown");
3534
+ const cursorLeft = this.getEditorKeyDisplay("tui.editor.cursorLeft");
3535
+ const cursorRight = this.getEditorKeyDisplay("tui.editor.cursorRight");
3536
+ const cursorWordLeft = this.getEditorKeyDisplay("tui.editor.cursorWordLeft");
3537
+ const cursorWordRight = this.getEditorKeyDisplay("tui.editor.cursorWordRight");
3538
+ const cursorLineStart = this.getEditorKeyDisplay("tui.editor.cursorLineStart");
3539
+ const cursorLineEnd = this.getEditorKeyDisplay("tui.editor.cursorLineEnd");
3540
+ const jumpForward = this.getEditorKeyDisplay("tui.editor.jumpForward");
3541
+ const jumpBackward = this.getEditorKeyDisplay("tui.editor.jumpBackward");
3542
+ const pageUp = this.getEditorKeyDisplay("tui.editor.pageUp");
3543
+ const pageDown = this.getEditorKeyDisplay("tui.editor.pageDown");
3504
3544
  // Editing keybindings
3505
- const submit = this.getEditorKeyDisplay("submit");
3506
- const newLine = this.getEditorKeyDisplay("newLine");
3507
- const deleteWordBackward = this.getEditorKeyDisplay("deleteWordBackward");
3508
- const deleteWordForward = this.getEditorKeyDisplay("deleteWordForward");
3509
- const deleteToLineStart = this.getEditorKeyDisplay("deleteToLineStart");
3510
- const deleteToLineEnd = this.getEditorKeyDisplay("deleteToLineEnd");
3511
- const yank = this.getEditorKeyDisplay("yank");
3512
- const yankPop = this.getEditorKeyDisplay("yankPop");
3513
- const undo = this.getEditorKeyDisplay("undo");
3514
- const tab = this.getEditorKeyDisplay("tab");
3545
+ const submit = this.getEditorKeyDisplay("tui.input.submit");
3546
+ const newLine = this.getEditorKeyDisplay("tui.input.newLine");
3547
+ const deleteWordBackward = this.getEditorKeyDisplay("tui.editor.deleteWordBackward");
3548
+ const deleteWordForward = this.getEditorKeyDisplay("tui.editor.deleteWordForward");
3549
+ const deleteToLineStart = this.getEditorKeyDisplay("tui.editor.deleteToLineStart");
3550
+ const deleteToLineEnd = this.getEditorKeyDisplay("tui.editor.deleteToLineEnd");
3551
+ const yank = this.getEditorKeyDisplay("tui.editor.yank");
3552
+ const yankPop = this.getEditorKeyDisplay("tui.editor.yankPop");
3553
+ const undo = this.getEditorKeyDisplay("tui.editor.undo");
3554
+ const tab = this.getEditorKeyDisplay("tui.input.tab");
3515
3555
  // App keybindings
3516
- const interrupt = this.getAppKeyDisplay("interrupt");
3517
- const clear = this.getAppKeyDisplay("clear");
3518
- const exit = this.getAppKeyDisplay("exit");
3519
- const suspend = this.getAppKeyDisplay("suspend");
3520
- const cycleThinkingLevel = this.getAppKeyDisplay("cycleThinkingLevel");
3521
- const cycleModelForward = this.getAppKeyDisplay("cycleModelForward");
3522
- const selectModel = this.getAppKeyDisplay("selectModel");
3523
- const expandTools = this.getAppKeyDisplay("expandTools");
3524
- const toggleThinking = this.getAppKeyDisplay("toggleThinking");
3525
- const externalEditor = this.getAppKeyDisplay("externalEditor");
3526
- const cycleModelBackward = this.getAppKeyDisplay("cycleModelBackward");
3527
- const followUp = this.getAppKeyDisplay("followUp");
3528
- const dequeue = this.getAppKeyDisplay("dequeue");
3529
- const pasteImage = this.getAppKeyDisplay("pasteImage");
3556
+ const interrupt = this.getAppKeyDisplay("app.interrupt");
3557
+ const clear = this.getAppKeyDisplay("app.clear");
3558
+ const exit = this.getAppKeyDisplay("app.exit");
3559
+ const suspend = this.getAppKeyDisplay("app.suspend");
3560
+ const cycleThinkingLevel = this.getAppKeyDisplay("app.thinking.cycle");
3561
+ const cycleModelForward = this.getAppKeyDisplay("app.model.cycleForward");
3562
+ const selectModel = this.getAppKeyDisplay("app.model.select");
3563
+ const expandTools = this.getAppKeyDisplay("app.tools.expand");
3564
+ const toggleThinking = this.getAppKeyDisplay("app.thinking.toggle");
3565
+ const externalEditor = this.getAppKeyDisplay("app.editor.external");
3566
+ const cycleModelBackward = this.getAppKeyDisplay("app.model.cycleBackward");
3567
+ const followUp = this.getAppKeyDisplay("app.message.followUp");
3568
+ const dequeue = this.getAppKeyDisplay("app.message.dequeue");
3569
+ const pasteImage = this.getAppKeyDisplay("app.clipboard.pasteImage");
3530
3570
  let hotkeys = `
3531
3571
  **Navigation**
3532
3572
  | Key | Action |
@@ -3751,7 +3791,7 @@ export class InteractiveMode {
3751
3791
  };
3752
3792
  // Show compacting status
3753
3793
  this.chatContainer.addChild(new Spacer(1));
3754
- const cancelHint = `(${appKey(this.keybindings, "interrupt")} to cancel)`;
3794
+ const cancelHint = `(${keyText("app.interrupt")} to cancel)`;
3755
3795
  const label = isAuto ? `Auto-compacting context... ${cancelHint}` : `Compacting context... ${cancelHint}`;
3756
3796
  const compactingLoader = new Loader(this.ui, (spinner) => theme.fg("accent", spinner), (text) => theme.fg("muted", text), label);
3757
3797
  this.statusContainer.addChild(compactingLoader);