praxis-agent 0.7.0 → 0.9.0

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 (301) hide show
  1. package/README.md +8 -0
  2. package/dist/application/background-agent-manager.d.ts +5 -0
  3. package/dist/application/background-agent-manager.js +21 -0
  4. package/dist/application/background-bash-manager.d.ts +14 -0
  5. package/dist/application/background-bash-manager.js +42 -4
  6. package/dist/application/background-task-runtime.d.ts +34 -0
  7. package/dist/application/background-task-runtime.js +59 -0
  8. package/dist/application/session-service.d.ts +21 -1
  9. package/dist/application/session-service.js +188 -17
  10. package/dist/application/session-worktree.d.ts +1 -0
  11. package/dist/application/session-worktree.js +13 -2
  12. package/dist/application/subagent-service.d.ts +3 -0
  13. package/dist/application/subagent-service.js +6 -0
  14. package/dist/application/workflow-manager.d.ts +70 -2
  15. package/dist/application/workflow-manager.js +21 -4
  16. package/dist/cli/agents-dashboard.d.ts +9 -0
  17. package/dist/cli/agents-dashboard.js +16 -5
  18. package/dist/cli/interactive.d.ts +41 -3
  19. package/dist/cli/interactive.js +1281 -183
  20. package/dist/cli/tui/claude-style.d.ts +31 -3
  21. package/dist/cli/tui/claude-style.js +171 -54
  22. package/dist/cli/tui/config-dashboard.d.ts +63 -0
  23. package/dist/cli/tui/config-dashboard.js +150 -0
  24. package/dist/cli/tui/config-settings.d.ts +32 -0
  25. package/dist/cli/tui/config-settings.js +276 -0
  26. package/dist/cli/tui/custom-themes.d.ts +21 -0
  27. package/dist/cli/tui/custom-themes.js +284 -0
  28. package/dist/cli/tui/file-picker.d.ts +3 -1
  29. package/dist/cli/tui/file-picker.js +8 -2
  30. package/dist/cli/tui/keybindings.js +0 -1
  31. package/dist/cli/tui/mcp-panel-controller.d.ts +32 -0
  32. package/dist/cli/tui/mcp-panel-controller.js +80 -0
  33. package/dist/cli/tui/mcp-panel-projector.d.ts +79 -0
  34. package/dist/cli/tui/mcp-panel-projector.js +181 -0
  35. package/dist/cli/tui/mcp-panel.d.ts +8 -0
  36. package/dist/cli/tui/mcp-panel.js +99 -0
  37. package/dist/cli/tui/recently-denied.d.ts +15 -0
  38. package/dist/cli/tui/recently-denied.js +19 -0
  39. package/dist/cli/tui/runtime-interactions.d.ts +17 -0
  40. package/dist/cli/tui/runtime-interactions.js +100 -0
  41. package/dist/cli/tui/runtime-settings.d.ts +40 -0
  42. package/dist/cli/tui/runtime-settings.js +84 -0
  43. package/dist/cli/tui/slash-commands.js +10 -0
  44. package/dist/cli/tui/task-panel.d.ts +53 -0
  45. package/dist/cli/tui/task-panel.js +204 -0
  46. package/dist/cli/tui/terminal-notifications.d.ts +10 -0
  47. package/dist/cli/tui/terminal-notifications.js +48 -0
  48. package/dist/cli/tui/terminal-setup.d.ts +24 -0
  49. package/dist/cli/tui/terminal-setup.js +618 -0
  50. package/dist/cli/tui/theme-settings.d.ts +8 -0
  51. package/dist/cli/tui/theme-settings.js +132 -0
  52. package/dist/cli/tui/theme.d.ts +53 -0
  53. package/dist/cli/tui/theme.js +265 -0
  54. package/dist/cli.d.ts +9 -0
  55. package/dist/cli.js +97 -17
  56. package/dist/compatibility/claude/schema.js +2 -0
  57. package/dist/core/runtime.d.ts +12 -0
  58. package/dist/core/runtime.js +24 -0
  59. package/dist/maintenance/self-update.js +1 -1
  60. package/dist/mcp/claude-mcp-management.d.ts +6 -1
  61. package/dist/mcp/claude-mcp-management.js +191 -44
  62. package/dist/mcp/claude-mcp-oauth.js +2 -0
  63. package/dist/mcp/claude-mcp-tools.d.ts +31 -3
  64. package/dist/mcp/claude-mcp-tools.js +116 -34
  65. package/dist/permissions/claude-permission-resolver.d.ts +3 -0
  66. package/dist/permissions/claude-permission-resolver.js +42 -20
  67. package/dist/platform/claude-version.js +2 -1
  68. package/dist/plugins/claude-plugin-runtime.js +11 -3
  69. package/dist/tools/claude-interactive-tools.d.ts +7 -0
  70. package/dist/tools/claude-interactive-tools.js +21 -8
  71. package/dist/tools/claude-task-tools.d.ts +4 -0
  72. package/dist/tools/claude-task-tools.js +7 -0
  73. package/package.json +2 -1
  74. package/dist/application/background-agent-manager.d.ts.map +0 -1
  75. package/dist/application/background-agent-manager.js.map +0 -1
  76. package/dist/application/background-bash-manager.d.ts.map +0 -1
  77. package/dist/application/background-bash-manager.js.map +0 -1
  78. package/dist/application/background-task-id.d.ts.map +0 -1
  79. package/dist/application/background-task-id.js.map +0 -1
  80. package/dist/application/claude-project-purge.d.ts.map +0 -1
  81. package/dist/application/claude-project-purge.js.map +0 -1
  82. package/dist/application/managed-worktree.d.ts.map +0 -1
  83. package/dist/application/managed-worktree.js.map +0 -1
  84. package/dist/application/model-compactor.d.ts.map +0 -1
  85. package/dist/application/model-compactor.js.map +0 -1
  86. package/dist/application/scheduled-prompt-manager.d.ts.map +0 -1
  87. package/dist/application/scheduled-prompt-manager.js.map +0 -1
  88. package/dist/application/session-service.d.ts.map +0 -1
  89. package/dist/application/session-service.js.map +0 -1
  90. package/dist/application/session-worktree.d.ts.map +0 -1
  91. package/dist/application/session-worktree.js.map +0 -1
  92. package/dist/application/subagent-service.d.ts.map +0 -1
  93. package/dist/application/subagent-service.js.map +0 -1
  94. package/dist/application/tool-use-summary.d.ts.map +0 -1
  95. package/dist/application/tool-use-summary.js.map +0 -1
  96. package/dist/application/top-level-agent-manager.d.ts.map +0 -1
  97. package/dist/application/top-level-agent-manager.js.map +0 -1
  98. package/dist/application/workflow-manager.d.ts.map +0 -1
  99. package/dist/application/workflow-manager.js.map +0 -1
  100. package/dist/application/workflow-meta.d.ts.map +0 -1
  101. package/dist/application/workflow-meta.js.map +0 -1
  102. package/dist/application/workflow-runtime.d.ts.map +0 -1
  103. package/dist/application/workflow-runtime.js.map +0 -1
  104. package/dist/application/workflow-worktree.d.ts.map +0 -1
  105. package/dist/application/workflow-worktree.js.map +0 -1
  106. package/dist/cli/agents-dashboard.d.ts.map +0 -1
  107. package/dist/cli/agents-dashboard.js.map +0 -1
  108. package/dist/cli/controls.d.ts.map +0 -1
  109. package/dist/cli/controls.js.map +0 -1
  110. package/dist/cli/debug.d.ts.map +0 -1
  111. package/dist/cli/debug.js.map +0 -1
  112. package/dist/cli/interactive.d.ts.map +0 -1
  113. package/dist/cli/interactive.js.map +0 -1
  114. package/dist/cli/protocol.d.ts.map +0 -1
  115. package/dist/cli/protocol.js.map +0 -1
  116. package/dist/cli/tui/claude-style.d.ts.map +0 -1
  117. package/dist/cli/tui/claude-style.js.map +0 -1
  118. package/dist/cli/tui/clipboard.d.ts.map +0 -1
  119. package/dist/cli/tui/clipboard.js.map +0 -1
  120. package/dist/cli/tui/composer-editor.d.ts.map +0 -1
  121. package/dist/cli/tui/composer-editor.js.map +0 -1
  122. package/dist/cli/tui/composer-images.d.ts.map +0 -1
  123. package/dist/cli/tui/composer-images.js.map +0 -1
  124. package/dist/cli/tui/conversation-export.d.ts.map +0 -1
  125. package/dist/cli/tui/conversation-export.js.map +0 -1
  126. package/dist/cli/tui/external-editor.d.ts.map +0 -1
  127. package/dist/cli/tui/external-editor.js.map +0 -1
  128. package/dist/cli/tui/file-picker.d.ts.map +0 -1
  129. package/dist/cli/tui/file-picker.js.map +0 -1
  130. package/dist/cli/tui/git-diff.d.ts.map +0 -1
  131. package/dist/cli/tui/git-diff.js.map +0 -1
  132. package/dist/cli/tui/hook-settings.d.ts.map +0 -1
  133. package/dist/cli/tui/hook-settings.js.map +0 -1
  134. package/dist/cli/tui/keybindings.d.ts.map +0 -1
  135. package/dist/cli/tui/keybindings.js.map +0 -1
  136. package/dist/cli/tui/memory-files.d.ts.map +0 -1
  137. package/dist/cli/tui/memory-files.js.map +0 -1
  138. package/dist/cli/tui/permission-settings.d.ts.map +0 -1
  139. package/dist/cli/tui/permission-settings.js.map +0 -1
  140. package/dist/cli/tui/slash-commands.d.ts.map +0 -1
  141. package/dist/cli/tui/slash-commands.js.map +0 -1
  142. package/dist/cli/tui/terminal-suspend.d.ts.map +0 -1
  143. package/dist/cli/tui/terminal-suspend.js.map +0 -1
  144. package/dist/cli/tui/workspace-directories.d.ts.map +0 -1
  145. package/dist/cli/tui/workspace-directories.js.map +0 -1
  146. package/dist/cli.d.ts.map +0 -1
  147. package/dist/cli.js.map +0 -1
  148. package/dist/compatibility/claude/compaction.d.ts.map +0 -1
  149. package/dist/compatibility/claude/compaction.js.map +0 -1
  150. package/dist/compatibility/claude/context.d.ts.map +0 -1
  151. package/dist/compatibility/claude/context.js.map +0 -1
  152. package/dist/compatibility/claude/dynamic-context.d.ts.map +0 -1
  153. package/dist/compatibility/claude/dynamic-context.js.map +0 -1
  154. package/dist/compatibility/claude/file-history.d.ts.map +0 -1
  155. package/dist/compatibility/claude/file-history.js.map +0 -1
  156. package/dist/compatibility/claude/file-resources.d.ts.map +0 -1
  157. package/dist/compatibility/claude/file-resources.js.map +0 -1
  158. package/dist/compatibility/claude/fork.d.ts.map +0 -1
  159. package/dist/compatibility/claude/fork.js.map +0 -1
  160. package/dist/compatibility/claude/history.d.ts.map +0 -1
  161. package/dist/compatibility/claude/history.js.map +0 -1
  162. package/dist/compatibility/claude/ownership.d.ts.map +0 -1
  163. package/dist/compatibility/claude/ownership.js.map +0 -1
  164. package/dist/compatibility/claude/paths.d.ts.map +0 -1
  165. package/dist/compatibility/claude/paths.js.map +0 -1
  166. package/dist/compatibility/claude/pr-links.d.ts.map +0 -1
  167. package/dist/compatibility/claude/pr-links.js.map +0 -1
  168. package/dist/compatibility/claude/projection.d.ts.map +0 -1
  169. package/dist/compatibility/claude/projection.js.map +0 -1
  170. package/dist/compatibility/claude/schema.d.ts.map +0 -1
  171. package/dist/compatibility/claude/schema.js.map +0 -1
  172. package/dist/compatibility/claude/shared-resources.d.ts.map +0 -1
  173. package/dist/compatibility/claude/shared-resources.js.map +0 -1
  174. package/dist/compatibility/claude/sidechain.d.ts.map +0 -1
  175. package/dist/compatibility/claude/sidechain.js.map +0 -1
  176. package/dist/compatibility/claude/tool-links.d.ts.map +0 -1
  177. package/dist/compatibility/claude/tool-links.js.map +0 -1
  178. package/dist/compatibility/claude/translation.d.ts.map +0 -1
  179. package/dist/compatibility/claude/translation.js.map +0 -1
  180. package/dist/compatibility/claude/workflow-replay.d.ts.map +0 -1
  181. package/dist/compatibility/claude/workflow-replay.js.map +0 -1
  182. package/dist/compatibility/claude/workflow.d.ts.map +0 -1
  183. package/dist/compatibility/claude/workflow.js.map +0 -1
  184. package/dist/core/compaction.d.ts.map +0 -1
  185. package/dist/core/compaction.js.map +0 -1
  186. package/dist/core/context-budget.d.ts.map +0 -1
  187. package/dist/core/context-budget.js.map +0 -1
  188. package/dist/core/context.d.ts.map +0 -1
  189. package/dist/core/context.js.map +0 -1
  190. package/dist/core/runtime.d.ts.map +0 -1
  191. package/dist/core/runtime.js.map +0 -1
  192. package/dist/core/usage.d.ts.map +0 -1
  193. package/dist/core/usage.js.map +0 -1
  194. package/dist/extensions/claude-extension-tools.d.ts.map +0 -1
  195. package/dist/extensions/claude-extension-tools.js.map +0 -1
  196. package/dist/extensions/claude-extensions.d.ts.map +0 -1
  197. package/dist/extensions/claude-extensions.js.map +0 -1
  198. package/dist/hooks/claude-hook-tools.d.ts.map +0 -1
  199. package/dist/hooks/claude-hook-tools.js.map +0 -1
  200. package/dist/hooks/claude-hooks.d.ts.map +0 -1
  201. package/dist/hooks/claude-hooks.js.map +0 -1
  202. package/dist/maintenance/doctor.d.ts.map +0 -1
  203. package/dist/maintenance/doctor.js.map +0 -1
  204. package/dist/maintenance/self-update.d.ts.map +0 -1
  205. package/dist/maintenance/self-update.js.map +0 -1
  206. package/dist/mcp/claude-mcp-management.d.ts.map +0 -1
  207. package/dist/mcp/claude-mcp-management.js.map +0 -1
  208. package/dist/mcp/claude-mcp-oauth.d.ts.map +0 -1
  209. package/dist/mcp/claude-mcp-oauth.js.map +0 -1
  210. package/dist/mcp/claude-mcp-tools.d.ts.map +0 -1
  211. package/dist/mcp/claude-mcp-tools.js.map +0 -1
  212. package/dist/mcp/praxis-mcp-server.d.ts.map +0 -1
  213. package/dist/mcp/praxis-mcp-server.js.map +0 -1
  214. package/dist/permissions/claude-auto-classifier.d.ts.map +0 -1
  215. package/dist/permissions/claude-auto-classifier.js.map +0 -1
  216. package/dist/permissions/claude-permission-resolver.d.ts.map +0 -1
  217. package/dist/permissions/claude-permission-resolver.js.map +0 -1
  218. package/dist/persistence/claude-job-store.d.ts.map +0 -1
  219. package/dist/persistence/claude-job-store.js.map +0 -1
  220. package/dist/persistence/claude-scheduled-task-store.d.ts.map +0 -1
  221. package/dist/persistence/claude-scheduled-task-store.js.map +0 -1
  222. package/dist/persistence/claude-sidechain-store.d.ts.map +0 -1
  223. package/dist/persistence/claude-sidechain-store.js.map +0 -1
  224. package/dist/persistence/claude-task-store.d.ts.map +0 -1
  225. package/dist/persistence/claude-task-store.js.map +0 -1
  226. package/dist/persistence/claude-transcript-store.d.ts.map +0 -1
  227. package/dist/persistence/claude-transcript-store.js.map +0 -1
  228. package/dist/persistence/claude-workflow-store.d.ts.map +0 -1
  229. package/dist/persistence/claude-workflow-store.js.map +0 -1
  230. package/dist/persistence/in-memory-sidechain-store.d.ts.map +0 -1
  231. package/dist/persistence/in-memory-sidechain-store.js.map +0 -1
  232. package/dist/persistence/in-memory-transcript-store.d.ts.map +0 -1
  233. package/dist/persistence/in-memory-transcript-store.js.map +0 -1
  234. package/dist/platform/atomic-write.d.ts.map +0 -1
  235. package/dist/platform/atomic-write.js.map +0 -1
  236. package/dist/platform/bounded-process-runner.d.ts.map +0 -1
  237. package/dist/platform/bounded-process-runner.js.map +0 -1
  238. package/dist/platform/claude-version.d.ts.map +0 -1
  239. package/dist/platform/claude-version.js.map +0 -1
  240. package/dist/platform/command-shell.d.ts.map +0 -1
  241. package/dist/platform/command-shell.js.map +0 -1
  242. package/dist/platform/exclusive-file-lease.d.ts.map +0 -1
  243. package/dist/platform/exclusive-file-lease.js.map +0 -1
  244. package/dist/platform/sensitive-data.d.ts.map +0 -1
  245. package/dist/platform/sensitive-data.js.map +0 -1
  246. package/dist/platform/tmux-worktree.d.ts.map +0 -1
  247. package/dist/platform/tmux-worktree.js.map +0 -1
  248. package/dist/plugins/claude-plugin-eval-discovery.d.ts.map +0 -1
  249. package/dist/plugins/claude-plugin-eval-discovery.js.map +0 -1
  250. package/dist/plugins/claude-plugin-eval-graders.d.ts.map +0 -1
  251. package/dist/plugins/claude-plugin-eval-graders.js.map +0 -1
  252. package/dist/plugins/claude-plugin-eval-runner.d.ts.map +0 -1
  253. package/dist/plugins/claude-plugin-eval-runner.js.map +0 -1
  254. package/dist/plugins/claude-plugin-eval-schema.d.ts.map +0 -1
  255. package/dist/plugins/claude-plugin-eval-schema.js.map +0 -1
  256. package/dist/plugins/claude-plugin-eval.d.ts.map +0 -1
  257. package/dist/plugins/claude-plugin-eval.js.map +0 -1
  258. package/dist/plugins/claude-plugin-maintenance.d.ts.map +0 -1
  259. package/dist/plugins/claude-plugin-maintenance.js.map +0 -1
  260. package/dist/plugins/claude-plugin-marketplace.d.ts.map +0 -1
  261. package/dist/plugins/claude-plugin-marketplace.js.map +0 -1
  262. package/dist/plugins/claude-plugin-mcpb.d.ts.map +0 -1
  263. package/dist/plugins/claude-plugin-mcpb.js.map +0 -1
  264. package/dist/plugins/claude-plugin-runtime.d.ts.map +0 -1
  265. package/dist/plugins/claude-plugin-runtime.js.map +0 -1
  266. package/dist/providers/anthropic-compatible.d.ts.map +0 -1
  267. package/dist/providers/anthropic-compatible.js.map +0 -1
  268. package/dist/providers/environment.d.ts.map +0 -1
  269. package/dist/providers/environment.js.map +0 -1
  270. package/dist/providers/fallback-provider.d.ts.map +0 -1
  271. package/dist/providers/fallback-provider.js.map +0 -1
  272. package/dist/providers/openai-compatible.d.ts.map +0 -1
  273. package/dist/providers/openai-compatible.js.map +0 -1
  274. package/dist/tools/claude-interactive-tools.d.ts.map +0 -1
  275. package/dist/tools/claude-interactive-tools.js.map +0 -1
  276. package/dist/tools/claude-lsp-formatters.d.ts.map +0 -1
  277. package/dist/tools/claude-lsp-formatters.js.map +0 -1
  278. package/dist/tools/claude-lsp-tool.d.ts.map +0 -1
  279. package/dist/tools/claude-lsp-tool.js.map +0 -1
  280. package/dist/tools/claude-scheduled-tools.d.ts.map +0 -1
  281. package/dist/tools/claude-scheduled-tools.js.map +0 -1
  282. package/dist/tools/claude-task-tools.d.ts.map +0 -1
  283. package/dist/tools/claude-task-tools.js.map +0 -1
  284. package/dist/tools/claude-user-message.d.ts.map +0 -1
  285. package/dist/tools/claude-user-message.js.map +0 -1
  286. package/dist/tools/claude-workflow-tools.d.ts.map +0 -1
  287. package/dist/tools/claude-workflow-tools.js.map +0 -1
  288. package/dist/tools/claude-worktree-tools.d.ts.map +0 -1
  289. package/dist/tools/claude-worktree-tools.js.map +0 -1
  290. package/dist/tools/filtered-tool-registry.d.ts.map +0 -1
  291. package/dist/tools/filtered-tool-registry.js.map +0 -1
  292. package/dist/tools/glob.d.ts.map +0 -1
  293. package/dist/tools/glob.js.map +0 -1
  294. package/dist/tools/local-tools.d.ts.map +0 -1
  295. package/dist/tools/local-tools.js.map +0 -1
  296. package/dist/tools/notebook.d.ts.map +0 -1
  297. package/dist/tools/notebook.js.map +0 -1
  298. package/dist/tools/pdf.d.ts.map +0 -1
  299. package/dist/tools/pdf.js.map +0 -1
  300. package/dist/tools/web.d.ts.map +0 -1
  301. package/dist/tools/web.js.map +0 -1
@@ -1,14 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { randomUUID } from 'node:crypto';
2
3
  import { homedir } from 'node:os';
3
4
  import { resolve } from 'node:path';
4
5
  import { useEffect, useMemo, useRef, useState } from 'react';
5
6
  import { Box, Text, render, useApp, useInput } from 'ink';
6
7
  import { AgentRunCancelledError } from '../core/runtime.js';
8
+ import { claudePermissionActionKey, } from '../permissions/claude-permission-resolver.js';
7
9
  import { redactSensitiveText, sensitiveEnvironmentValues, } from '../platform/sensitive-data.js';
8
- import { CommandPalette, BtwPanel, Composer, DiffDashboard, DialogFrame, ExternalEditorWait, HelpMenu, HookDashboard, ListDashboard, MemoryDashboard, MentionPicker, PermissionDashboard, SelectionMenu, SessionPicker, StatusDashboard, Transcript, WelcomePanel, useTerminalWidth, } from './tui/claude-style.js';
10
+ import { CommandPalette, BtwPanel, Composer, DiffDashboard, DialogFrame, ExternalEditorWait, HelpMenu, HookDashboard, ListDashboard, MemoryDashboard, MentionPicker, PermissionDashboard, SelectionMenu, SessionPicker, StatusDashboard, ThemePicker, CustomThemeEditor, Transcript, WelcomePanel, useTerminalWidth, } from './tui/claude-style.js';
9
11
  import { loadTuiMemoryFiles, openTuiMemoryFolder, } from './tui/memory-files.js';
10
12
  import { loadGitDiff, visiblePatchLines, } from './tui/git-diff.js';
11
13
  import { addTuiPermissionRule, loadTuiPermissionRules, removeTuiPermissionRule, } from './tui/permission-settings.js';
14
+ import { createRecentlyDeniedStore, } from './tui/recently-denied.js';
12
15
  import { filterTuiSlashCommands, mergeTuiSlashCommands, slashCommandQuery, } from './tui/slash-commands.js';
13
16
  import { createComposerEditor, deleteComposerToEnd, deleteComposerToStart, deleteComposerWordBackward, insertComposerText, moveComposerCursorByWord, } from './tui/composer-editor.js';
14
17
  import { applyMentionReference, fileReferenceAtCursor, filterTuiMentionEntries, loadTuiFileEntries, } from './tui/file-picker.js';
@@ -19,6 +22,20 @@ import { composerImageIds, deleteComposerImageBackward, deleteComposerImageForwa
19
22
  import { defaultTuiKeybindings, ensureTuiKeybindingsFile, hasTuiKeybindingPrefix, loadTuiKeybindings, resolveTuiKeybinding, tuiKeyChord, } from './tui/keybindings.js';
20
23
  import { completeTuiWorkspaceDirectory, resolveTuiWorkspaceDirectory, } from './tui/workspace-directories.js';
21
24
  import { conversationExportPath, conversationExportText, defaultConversationExportFilename, writeConversationExport, } from './tui/conversation-export.js';
25
+ import { loadTuiThemeSettings, saveTuiThemeSettings, themeSettingsWithCustomTheme, } from './tui/theme-settings.js';
26
+ import { DEFAULT_TUI_THEME_SETTINGS, TUI_THEMES, TuiThemeProvider, tuiPalette, } from './tui/theme.js';
27
+ import { CUSTOM_THEME_TOKENS, createTuiCustomTheme, deleteTuiCustomTheme, loadTuiCustomThemes, updateTuiCustomTheme, } from './tui/custom-themes.js';
28
+ import { setupTuiTerminal, terminalSetupTuiSlashCommand, } from './tui/terminal-setup.js';
29
+ import { ConfigDashboard, projectConfigRows } from './tui/config-dashboard.js';
30
+ import { loadConfigSettings, saveConfigSetting, configSettingDefinition, resolveConfigSettingsLocation, } from './tui/config-settings.js';
31
+ import { projectRuntimeSettings, loadRuntimeSettings, } from './tui/runtime-settings.js';
32
+ import { autoUpdateTarget, copyCandidates, externalEditorInitialContent, formatTurnDuration, questionTimeoutMilliseconds, sessionRecap, shouldShowCopyPicker, spinnerTip, } from './tui/runtime-interactions.js';
33
+ import { notifyTerminal, } from './tui/terminal-notifications.js';
34
+ import { McpPanel } from './tui/mcp-panel.js';
35
+ import { McpPanelController, mcpRuntimeFromSession, } from './tui/mcp-panel-controller.js';
36
+ import { initialTuiMcpPanelState, } from './tui/mcp-panel-projector.js';
37
+ import { TaskPanel, initialTuiTaskPanelState, projectTuiTasks, reconcileTuiTaskPanelState, updateTuiTaskPanelState, } from './tui/task-panel.js';
38
+ import { ClaudeMcpManagement } from '../mcp/claude-mcp-management.js';
22
39
  const EMPTY_SLASH_COMMANDS = [];
23
40
  const EMPTY_AGENTS = [];
24
41
  const EFFORT_OPTIONS = ['low', 'medium', 'high', 'xhigh', 'max'];
@@ -54,6 +71,11 @@ const PERMISSION_OPTIONS = [
54
71
  description: 'Restrict changes to the plan file until approval.',
55
72
  },
56
73
  ];
74
+ function selectionPrefix(selected, screenReader) {
75
+ if (selected)
76
+ return screenReader ? 'Selected: ' : '❯ ';
77
+ return screenReader ? '' : ' ';
78
+ }
57
79
  function rewindActions(point) {
58
80
  const shared = [
59
81
  { action: 'summarize-from', label: 'Summarize from here' },
@@ -143,6 +165,12 @@ function describeTool(call, sensitiveValues) {
143
165
  const name = redactSensitiveText(call.name, sensitiveValues);
144
166
  return `${name} ${describeToolInput(call, sensitiveValues)}`;
145
167
  }
168
+ function describeRecentlyDeniedAction(call, sensitiveValues) {
169
+ const description = call.input.description;
170
+ return typeof description === 'string' && description.trim()
171
+ ? redactSensitiveText(description.trim(), sensitiveValues)
172
+ : describeTool(call, sensitiveValues);
173
+ }
146
174
  function redactToolCall(call, sensitiveValues) {
147
175
  return {
148
176
  ...call,
@@ -187,22 +215,47 @@ function ordinal(value) {
187
215
  return `${value}th`;
188
216
  }
189
217
  }
190
- export function InteractiveApp({ factory, initialSessions, initialPrompt, initialHistory = [], signal, onCancel, onTurnChange, onCleanup, onBackground, axScreenReader = false, allowNewSession = true, resume, display = { version: 'dev', cwd: process.cwd() }, terminalWidth, slashCommands = EMPTY_SLASH_COMMANDS, agents = EMPTY_AGENTS, allowDangerouslySkipPermissions = false, additionalDirectories = [], diffLoader, fileLoader, externalEditor = editTuiPrompt, keybindingsConfigRoot, keybindingsFile = ensureTuiKeybindingsFile, keybindingsLoader = loadTuiKeybindings, keybindingsEditor = openTuiEditorFile, memoryFilesLoader = (configRoot, cwd) => loadTuiMemoryFiles({ configRoot, cwd }), memoryEditor = openTuiEditorFile, memoryFolderOpener = openTuiMemoryFolder, suspendProcess = suspendTuiProcess, clipboardReader = readTuiClipboard, clipboardWriter = writeTuiClipboard, sideQuestionClipboardWriter = writeTuiOsc52Clipboard, exportWriter = writeConversationExport, permissionRuleStore, workspaceDirectoryResolver = resolveTuiWorkspaceDirectory, workspaceDirectoryCompleter = completeTuiWorkspaceDirectory, }) {
218
+ export function InteractiveApp({ factory, initialSessions, initialPrompt, initialHistory = [], signal, onCancel, onTurnChange, onCleanup, onRendererChange, onBackground, axScreenReader = false, allowNewSession = true, resume, display = { version: 'dev', cwd: process.cwd() }, terminalWidth, slashCommands = EMPTY_SLASH_COMMANDS, agents = EMPTY_AGENTS, allowDangerouslySkipPermissions = false, additionalDirectories = [], diffLoader, fileLoader, externalEditor = editTuiPrompt, keybindingsConfigRoot, keybindingsFile = ensureTuiKeybindingsFile, keybindingsLoader = loadTuiKeybindings, keybindingsEditor = openTuiEditorFile, memoryFilesLoader = (configRoot, cwd) => loadTuiMemoryFiles({ configRoot, cwd }), memoryEditor = openTuiEditorFile, memoryFolderOpener = openTuiMemoryFolder, suspendProcess = suspendTuiProcess, clipboardReader = readTuiClipboard, clipboardWriter = writeTuiClipboard, sideQuestionClipboardWriter = writeTuiOsc52Clipboard, exportWriter = writeConversationExport, permissionRuleStore, recentlyDeniedStore: suppliedRecentlyDeniedStore, terminalSetup: terminalSetupOverride, themeStore, initialThemeSettings, initialThemeLoadError, workspaceDirectoryResolver = resolveTuiWorkspaceDirectory, workspaceDirectoryCompleter = completeTuiWorkspaceDirectory, runtimeSettings: suppliedRuntimeSettings, runtimeSettingsTarget, notificationWriter, }) {
191
219
  const { exit, suspendTerminal, waitUntilRenderFlush } = useApp();
192
220
  const width = useTerminalWidth(terminalWidth);
193
221
  const keybindingsRoot = useMemo(() => resolve(keybindingsConfigRoot ??
194
222
  process.env.CLAUDE_CONFIG_DIR ??
195
223
  resolve(homedir(), '.claude')), [keybindingsConfigRoot]);
224
+ const configTarget = useMemo(() => resolveConfigSettingsLocation(runtimeSettingsTarget), [runtimeSettingsTarget]);
196
225
  const sensitiveValues = useMemo(() => sensitiveEnvironmentValues(process.env), []);
197
226
  const [runtimeCwd, setRuntimeCwd] = useState(display.cwd);
198
227
  const runtimeCwdRef = useRef(display.cwd);
228
+ const runtimeGitignoreRef = useRef(true);
199
229
  const loadDiffSnapshot = useMemo(() => diffLoader ?? (() => loadGitDiff(runtimeCwd)), [diffLoader, runtimeCwd]);
200
- const loadFiles = useMemo(() => fileLoader ?? (() => loadTuiFileEntries(runtimeCwd)), [fileLoader, runtimeCwd]);
230
+ const loadFiles = useMemo(() => fileLoader ??
231
+ (() => loadTuiFileEntries(runtimeCwd, {
232
+ respectGitignore: runtimeGitignoreRef.current,
233
+ })), [fileLoader, runtimeCwd]);
201
234
  const permissionStore = useMemo(() => permissionRuleStore ?? {
202
235
  load: () => loadTuiPermissionRules(runtimeCwd),
203
236
  add: (input) => addTuiPermissionRule({ cwd: runtimeCwd, ...input }),
204
237
  remove: removeTuiPermissionRule,
205
238
  }, [permissionRuleStore, runtimeCwd]);
239
+ const recentDeniedStore = useMemo(() => suppliedRecentlyDeniedStore ?? createRecentlyDeniedStore(), [suppliedRecentlyDeniedStore]);
240
+ const presentationThemeStore = useMemo(() => themeStore ?? {
241
+ load: loadTuiThemeSettings,
242
+ save: saveTuiThemeSettings,
243
+ loadCustomThemes: () => loadTuiCustomThemes(),
244
+ createCustomTheme: (input) => createTuiCustomTheme(input),
245
+ updateCustomTheme: (theme, token, value) => updateTuiCustomTheme(theme, token, value),
246
+ deleteCustomTheme: (theme) => deleteTuiCustomTheme(theme),
247
+ }, [themeStore]);
248
+ const [customThemes, setCustomThemes] = useState([]);
249
+ const [themeSettings, setThemeSettings] = useState(initialThemeSettings ?? DEFAULT_TUI_THEME_SETTINGS);
250
+ const [runtimeSettings, setRuntimeSettings] = useState(suppliedRuntimeSettings ??
251
+ projectRuntimeSettings({ settings: {}, state: {} }));
252
+ const runtimeSettingsRef = useRef(runtimeSettings);
253
+ runtimeSettingsRef.current = runtimeSettings;
254
+ runtimeGitignoreRef.current = runtimeSettings.gitignore;
255
+ const [vimInsertMode, setVimInsertMode] = useState(true);
256
+ const activePalette = tuiPalette(TUI_THEMES.includes(themeSettings.theme)
257
+ ? themeSettings.theme
258
+ : 'dark', themeSettings.syntaxHighlightingDisabled, process.env, themeSettings.customTheme);
206
259
  const choices = useMemo(() => allowNewSession ? [null, ...initialSessions] : initialSessions, [allowNewSession, initialSessions]);
207
260
  const [pickerIncludesNewSession, setPickerIncludesNewSession] = useState(allowNewSession);
208
261
  const pickerIncludesNewSessionRef = useRef(allowNewSession);
@@ -216,6 +269,11 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
216
269
  const [selectedIndex, setSelectedIndex] = useState(0);
217
270
  const selectedIndexRef = useRef(0);
218
271
  const [sessionId, setSessionId] = useState(resume?.sessionId ?? null);
272
+ const sessionIdRef = useRef(resume?.sessionId ?? null);
273
+ sessionIdRef.current = sessionId;
274
+ const [activeSessionSummary, setActiveSessionSummary] = useState(() => resume?.sessionId
275
+ ? initialSessions.find((session) => session.sessionId === resume.sessionId)
276
+ : undefined);
219
277
  const [pendingFork, setPendingFork] = useState(resume?.forkSession === true);
220
278
  const [input, setInput] = useState('');
221
279
  const inputRef = useRef('');
@@ -250,6 +308,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
250
308
  const [processSuspendRequested, setProcessSuspendRequested] = useState(false);
251
309
  const processSuspendRequestedRef = useRef(false);
252
310
  const [editorFooterMessage, setEditorFooterMessage] = useState();
311
+ const terminalSetupCommand = useMemo(() => terminalSetupTuiSlashCommand(process.env, process.platform), []);
253
312
  const [availableSlashCommands, setAvailableSlashCommands] = useState(slashCommands);
254
313
  const [availableAgents, setAvailableAgents] = useState(agents);
255
314
  const [btwHistory, setBtwHistory] = useState([]);
@@ -261,6 +320,8 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
261
320
  const [menu, setMenu] = useState(null);
262
321
  const menuRef = useRef(null);
263
322
  const [busy, setBusy] = useState(false);
323
+ const taskEntriesRef = useRef([]);
324
+ const mcpControllerRef = useRef(null);
264
325
  const [compactProgress, setCompactProgress] = useState(0);
265
326
  const initialPromptRef = useRef(initialPrompt?.trim() ?? '');
266
327
  const [initialPromptPending, setInitialPromptPending] = useState(initialPromptRef.current.length > 0);
@@ -268,6 +329,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
268
329
  const [activeText, setActiveText] = useState('');
269
330
  const [activeThinking, setActiveThinking] = useState('');
270
331
  const [thinkingExpanded, setThinkingExpanded] = useState(false);
332
+ const [turnDuration, setTurnDuration] = useState();
271
333
  const [usage, setUsage] = useState();
272
334
  const [costUsd, setCostUsd] = useState();
273
335
  const [contextWindowTokens, setContextWindowTokens] = useState(display.contextWindowTokens);
@@ -277,7 +339,11 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
277
339
  const turnNumberRef = useRef(0);
278
340
  const turnMutatedFilesRef = useRef(false);
279
341
  const permissionCallsRef = useRef(new Map());
342
+ const approvedSessionActionsRef = useRef(new Map());
280
343
  const [recentDenied, setRecentDenied] = useState([]);
344
+ const [retryingDeniedId, setRetryingDeniedId] = useState(null);
345
+ const recentDeniedRef = useRef([]);
346
+ recentDeniedRef.current = recentDenied;
281
347
  const [exitConfirmation, setExitConfirmation] = useState(false);
282
348
  const exitConfirmationTimerRef = useRef(null);
283
349
  const [runtimePreferences, setRuntimePreferences] = useState(() => ({
@@ -292,6 +358,8 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
292
358
  const elicitationRef = useRef(null);
293
359
  const [question, setQuestion] = useState(null);
294
360
  const questionRef = useRef(null);
361
+ const questionTimeoutRef = useRef(null);
362
+ const recapShownRef = useRef(false);
295
363
  const [planApproval, setPlanApproval] = useState(null);
296
364
  const planApprovalRef = useRef(null);
297
365
  const serviceRef = useRef(null);
@@ -303,7 +371,10 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
303
371
  const serviceRetirementRef = useRef(null);
304
372
  const serviceEpochRef = useRef(0);
305
373
  const serviceCreationEpochRef = useRef(null);
306
- const allSlashCommands = useMemo(() => mergeTuiSlashCommands(availableSlashCommands), [availableSlashCommands]);
374
+ const allSlashCommands = useMemo(() => mergeTuiSlashCommands([
375
+ ...(terminalSetupCommand === null ? [] : [terminalSetupCommand]),
376
+ ...availableSlashCommands,
377
+ ]), [availableSlashCommands, terminalSetupCommand]);
307
378
  const builtinSlashCommands = useMemo(() => allSlashCommands.filter((command) => command.source === 'builtin'), [allSlashCommands]);
308
379
  const customSlashCommands = useMemo(() => allSlashCommands.filter((command) => command.source !== 'builtin'), [allSlashCommands]);
309
380
  const commandQuery = commandPaletteOpen ? slashCommandQuery(input) : null;
@@ -451,6 +522,88 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
451
522
  void closing.catch(() => undefined);
452
523
  }, []);
453
524
  const append = (line) => setHistory((current) => [...current, line]);
525
+ useEffect(() => {
526
+ if (initialThemeSettings !== undefined) {
527
+ if (initialThemeLoadError)
528
+ append({ kind: 'warning', text: initialThemeLoadError });
529
+ return;
530
+ }
531
+ let cancelled = false;
532
+ void presentationThemeStore.load().then((loaded) => {
533
+ if (!cancelled)
534
+ setThemeSettings(themeSettingsWithCustomTheme(loaded, customThemes));
535
+ }, (error) => {
536
+ if (!cancelled)
537
+ append({
538
+ kind: 'warning',
539
+ text: `Unable to load theme settings: ${error instanceof Error ? error.message : String(error)}`,
540
+ });
541
+ });
542
+ return () => {
543
+ cancelled = true;
544
+ };
545
+ }, [
546
+ customThemes,
547
+ initialThemeLoadError,
548
+ initialThemeSettings,
549
+ presentationThemeStore,
550
+ ]);
551
+ useEffect(() => {
552
+ let cancelled = false;
553
+ void Promise.resolve(presentationThemeStore.loadCustomThemes?.() ?? []).then((themes) => {
554
+ if (!cancelled && themes)
555
+ setCustomThemes(themes);
556
+ }, (error) => {
557
+ if (!cancelled)
558
+ append({
559
+ kind: 'warning',
560
+ text: `Unable to load custom themes: ${error instanceof Error ? error.message : String(error)}`,
561
+ });
562
+ });
563
+ return () => {
564
+ cancelled = true;
565
+ };
566
+ }, [presentationThemeStore]);
567
+ useEffect(() => {
568
+ if (suppliedRuntimeSettings !== undefined)
569
+ return;
570
+ let cancelled = false;
571
+ void loadConfigSettings(configTarget).then((snapshot) => {
572
+ if (!cancelled)
573
+ setRuntimeSettings(projectRuntimeSettings(snapshot));
574
+ }, (error) => {
575
+ if (!cancelled)
576
+ append({
577
+ kind: 'warning',
578
+ text: `Unable to load runtime settings: ${error instanceof Error ? error.message : String(error)}`,
579
+ });
580
+ });
581
+ return () => {
582
+ cancelled = true;
583
+ };
584
+ }, [configTarget, suppliedRuntimeSettings]);
585
+ useEffect(() => {
586
+ if (runtimeSettings.recap &&
587
+ resume?.sessionId &&
588
+ initialHistory.length > 0) {
589
+ const recap = sessionRecap(initialHistory);
590
+ if (recap && !recapShownRef.current) {
591
+ recapShownRef.current = true;
592
+ append({ kind: 'notice', text: recap });
593
+ }
594
+ }
595
+ }, [initialHistory, resume?.sessionId, runtimeSettings.recap]);
596
+ useEffect(() => {
597
+ if (!runtimeSettings.defaultToAgentsView || availableAgents.length === 0)
598
+ return;
599
+ if (selectingSession || menuRef.current !== null)
600
+ return;
601
+ updateMenu({ kind: 'agents', agents: availableAgents, selectedIndex: 0 });
602
+ }, [
603
+ availableAgents.length,
604
+ runtimeSettings.defaultToAgentsView,
605
+ selectingSession,
606
+ ]);
454
607
  useEffect(() => {
455
608
  let cancelled = false;
456
609
  void keybindingsLoader(keybindingsRoot).then((loaded) => {
@@ -542,7 +695,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
542
695
  await waitUntilRenderFlush();
543
696
  let result;
544
697
  await suspendTerminal(async () => {
545
- result = await externalEditor(externalEditorRequest.prompt, {
698
+ result = await externalEditor(externalEditorInitialContent(externalEditorRequest.prompt, history, runtimeSettingsRef.current.externalEditorContext), {
546
699
  cwd: runtimeCwdRef.current,
547
700
  ...(signal === undefined ? {} : { signal }),
548
701
  });
@@ -567,7 +720,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
567
720
  })();
568
721
  onTurnChange?.(editing);
569
722
  void editing.finally(() => onTurnChange?.(null));
570
- }, [externalEditorRequest]);
723
+ }, [externalEditorRequest, history]);
571
724
  useEffect(() => {
572
725
  if (!keybindingsEditing)
573
726
  return;
@@ -665,6 +818,27 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
665
818
  menuRef.current = next;
666
819
  setMenu(next);
667
820
  };
821
+ const refreshTasks = async (existing) => {
822
+ const commands = await service();
823
+ if (!sessionId || !commands.taskSnapshots) {
824
+ const rows = workflowRows(commands.workflows?.() ?? []);
825
+ updateMenu({
826
+ kind: 'list',
827
+ title: 'Background',
828
+ rows,
829
+ emptyText: 'No tasks currently running',
830
+ selectedIndex: 0,
831
+ });
832
+ return;
833
+ }
834
+ const snapshot = await commands.taskSnapshots(sessionId);
835
+ const tasks = projectTuiTasks(snapshot);
836
+ const state = existing
837
+ ? reconcileTuiTaskPanelState(existing.state, taskEntriesRef.current, tasks)
838
+ : initialTuiTaskPanelState(tasks);
839
+ taskEntriesRef.current = tasks;
840
+ updateMenu({ kind: 'tasks', tasks, state });
841
+ };
668
842
  const updateBtwHistory = (updater) => {
669
843
  const next = updater(btwHistoryRef.current);
670
844
  btwHistoryRef.current = next;
@@ -782,7 +956,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
782
956
  case 'tool-call':
783
957
  if (['Edit', 'Write', 'NotebookEdit'].includes(event.call.name))
784
958
  turnMutatedFilesRef.current = true;
785
- permissionCallsRef.current.set(event.call.id, describeTool(event.call, sensitiveValues));
959
+ permissionCallsRef.current.set(event.call.id, event.call);
786
960
  append({
787
961
  kind: 'tool',
788
962
  call: redactToolCall(event.call, sensitiveValues),
@@ -790,9 +964,21 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
790
964
  });
791
965
  break;
792
966
  case 'permission-decision':
793
- if (event.behavior === 'deny') {
794
- const denied = permissionCallsRef.current.get(event.callId) ?? event.callId;
795
- setRecentDenied((current) => [denied, ...current.filter((item) => item !== denied)].slice(0, 20));
967
+ if (event.behavior === 'deny' &&
968
+ event.source === 'auto-classifier' &&
969
+ event.autoModeOutcome === 'blocked') {
970
+ const denied = permissionCallsRef.current.get(event.callId);
971
+ const deniedSessionId = sessionIdRef.current;
972
+ if (denied && deniedSessionId) {
973
+ const action = {
974
+ id: `${event.callId}:${randomUUID()}`,
975
+ call: denied,
976
+ display: describeRecentlyDeniedAction(denied, sensitiveValues),
977
+ reason: event.reason ?? '',
978
+ sessionId: deniedSessionId,
979
+ };
980
+ void recentDeniedStore.record(action).then((entries) => setRecentDenied(entries), (error) => warn(error));
981
+ }
796
982
  }
797
983
  append({
798
984
  kind: event.behavior === 'deny' ? 'warning' : 'notice',
@@ -939,9 +1125,13 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
939
1125
  return;
940
1126
  settled = true;
941
1127
  signal?.removeEventListener('abort', abort);
942
- if (questionRef.current === pending)
1128
+ if (questionTimeoutRef.current) {
1129
+ clearTimeout(questionTimeoutRef.current);
1130
+ questionTimeoutRef.current = null;
1131
+ }
1132
+ if (questionRef.current?.resolve === pending.resolve)
943
1133
  questionRef.current = null;
944
- setQuestion((current) => (current === pending ? null : current));
1134
+ setQuestion((current) => current?.resolve === pending.resolve ? null : current);
945
1135
  resolveResult(result);
946
1136
  },
947
1137
  };
@@ -953,6 +1143,13 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
953
1143
  clearComposerInput();
954
1144
  questionRef.current = pending;
955
1145
  setQuestion(pending);
1146
+ const timeout = questionTimeoutMilliseconds(runtimeSettingsRef.current.askUserQuestionTimeout);
1147
+ if (timeout !== undefined) {
1148
+ questionTimeoutRef.current = setTimeout(() => {
1149
+ questionTimeoutRef.current = null;
1150
+ questionRef.current?.resolve(null);
1151
+ }, timeout);
1152
+ }
956
1153
  });
957
1154
  const approvePlan = (request, signal) => new Promise((resolveApproval) => {
958
1155
  let settled = false;
@@ -1005,6 +1202,14 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1005
1202
  setRuntimePreferences(next);
1006
1203
  return next;
1007
1204
  };
1205
+ const isSessionActionApproved = (call) => {
1206
+ const activeSessionId = sessionIdRef.current;
1207
+ return activeSessionId
1208
+ ? (approvedSessionActionsRef.current
1209
+ .get(activeSessionId)
1210
+ ?.has(claudePermissionActionKey(call)) ?? false)
1211
+ : false;
1212
+ };
1008
1213
  const service = async () => {
1009
1214
  if (serviceRef.current)
1010
1215
  return serviceRef.current;
@@ -1028,6 +1233,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1028
1233
  : { model: preferences.model }),
1029
1234
  effort: preferences.effort,
1030
1235
  permissionMode: preferences.permissionMode,
1236
+ isSessionActionApproved,
1031
1237
  additionalDirectories: preferences.additionalDirectories,
1032
1238
  cwd: runtimeCwdRef.current,
1033
1239
  ...(signal ? { signal } : {}),
@@ -1066,6 +1272,13 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1066
1272
  }
1067
1273
  }
1068
1274
  };
1275
+ const reloadRuntimeSettings = async (snapshot) => {
1276
+ const current = snapshot ?? (await loadConfigSettings(configTarget));
1277
+ const projected = projectRuntimeSettings(current);
1278
+ runtimeSettingsRef.current = projected;
1279
+ setRuntimeSettings(projected);
1280
+ return projected;
1281
+ };
1069
1282
  const withLocalCommands = async (operation) => {
1070
1283
  if (serviceRef.current)
1071
1284
  return operation(serviceRef.current);
@@ -1076,6 +1289,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1076
1289
  ...(preferences.model === undefined ? {} : { model: preferences.model }),
1077
1290
  effort: preferences.effort,
1078
1291
  permissionMode: preferences.permissionMode,
1292
+ isSessionActionApproved,
1079
1293
  additionalDirectories: preferences.additionalDirectories,
1080
1294
  cwd: runtimeCwdRef.current,
1081
1295
  ...(signal ? { signal } : {}),
@@ -1088,7 +1302,11 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1088
1302
  }
1089
1303
  };
1090
1304
  const openSession = (nextSessionId) => {
1305
+ sessionIdRef.current = nextSessionId;
1091
1306
  setSessionId(nextSessionId);
1307
+ setActiveSessionSummary(nextSessionId === null
1308
+ ? undefined
1309
+ : initialSessions.find((session) => session.sessionId === nextSessionId));
1092
1310
  const loadId = sessionLoadRef.current + 1;
1093
1311
  sessionLoadRef.current = loadId;
1094
1312
  if (nextSessionId === null) {
@@ -1173,14 +1391,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1173
1391
  setBusy(true);
1174
1392
  setStatus('loading tasks');
1175
1393
  try {
1176
- const rows = workflowRows((await service()).workflows?.() ?? []);
1177
- updateMenu({
1178
- kind: 'list',
1179
- title: 'Background',
1180
- rows,
1181
- emptyText: 'No tasks currently running',
1182
- selectedIndex: 0,
1183
- });
1394
+ await refreshTasks();
1184
1395
  }
1185
1396
  catch (error) {
1186
1397
  warn(error);
@@ -1224,6 +1435,15 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1224
1435
  });
1225
1436
  return;
1226
1437
  }
1438
+ if (!runtimeSettingsRef.current.copyFullResponse &&
1439
+ shouldShowCopyPicker(response.text)) {
1440
+ updateMenu({
1441
+ kind: 'copy',
1442
+ candidates: copyCandidates(response.text),
1443
+ selectedIndex: 0,
1444
+ });
1445
+ return;
1446
+ }
1227
1447
  const copying = clipboardWriter(response.text).then(() => append({
1228
1448
  kind: 'local-result',
1229
1449
  text: `Copied ${position === 1 ? 'last' : `${ordinal(position)}-latest`} response to clipboard.`,
@@ -1297,16 +1517,43 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1297
1517
  setBusy(true);
1298
1518
  setStatus('loading MCP servers');
1299
1519
  try {
1300
- const servers = (await service()).runtimeInfo?.().mcpServers ?? [];
1520
+ const commands = await service();
1521
+ if (!commands.mcpInspect || !commands.mcpTools) {
1522
+ const servers = commands.runtimeInfo?.().mcpServers ?? [];
1523
+ updateMenu({
1524
+ kind: 'list',
1525
+ title: 'MCP servers',
1526
+ rows: servers.map((server) => ({
1527
+ label: server.name,
1528
+ description: server.status,
1529
+ })),
1530
+ emptyText: 'No MCP servers configured',
1531
+ selectedIndex: 0,
1532
+ });
1533
+ return;
1534
+ }
1535
+ const configRoot = process.env.CLAUDE_CONFIG_DIR;
1536
+ const management = new ClaudeMcpManagement({
1537
+ cwd: runtimeCwdRef.current,
1538
+ ...(configRoot ? { configRoot } : {}),
1539
+ });
1540
+ const controller = new McpPanelController({
1541
+ cwd: runtimeCwdRef.current,
1542
+ management,
1543
+ runtime: mcpRuntimeFromSession({
1544
+ mcpInspect: commands.mcpInspect,
1545
+ mcpReconnect: commands.mcpReconnect ?? (async () => { }),
1546
+ mcpAuthenticate: commands.mcpAuthenticate ?? (async () => { }),
1547
+ mcpReload: commands.mcpReload ?? (async () => { }),
1548
+ mcpTools: commands.mcpTools,
1549
+ }),
1550
+ });
1551
+ mcpControllerRef.current = controller;
1552
+ const model = await controller.open();
1301
1553
  updateMenu({
1302
- kind: 'list',
1303
- title: 'MCP servers',
1304
- rows: servers.map((server) => ({
1305
- label: server.name,
1306
- description: server.status,
1307
- })),
1308
- emptyText: 'No MCP servers configured',
1309
- selectedIndex: 0,
1554
+ kind: 'mcp',
1555
+ model,
1556
+ state: initialTuiMcpPanelState(model),
1310
1557
  });
1311
1558
  }
1312
1559
  catch (error) {
@@ -1814,6 +2061,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1814
2061
  };
1815
2062
  const submit = async (prompt, shellCommand, images = []) => {
1816
2063
  const turnNumber = turnNumberRef.current + 1;
2064
+ const turnStartedAt = Date.now();
1817
2065
  turnNumberRef.current = turnNumber;
1818
2066
  turnMutatedFilesRef.current = false;
1819
2067
  scheduledWaitRef.current?.abort();
@@ -1825,10 +2073,14 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1825
2073
  ? AbortSignal.any([signal, turnController.signal])
1826
2074
  : turnController.signal;
1827
2075
  setBusy(true);
2076
+ setTurnDuration(undefined);
1828
2077
  setCommandPaletteOpen(false);
1829
2078
  setStatus('assembling-context');
1830
2079
  setActiveText('');
1831
2080
  setActiveThinking('');
2081
+ if (runtimeSettingsRef.current.tips) {
2082
+ setStatus(spinnerTip(runtimeSettingsRef.current) ?? 'assembling-context');
2083
+ }
1832
2084
  if (shellCommand === undefined)
1833
2085
  append({ kind: 'user', text: prompt });
1834
2086
  else
@@ -1838,6 +2090,11 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1838
2090
  commands = await service();
1839
2091
  let activeSessionId = sessionId;
1840
2092
  const startedNewSession = activeSessionId === null;
2093
+ if (activeSessionId === null) {
2094
+ activeSessionId = randomUUID();
2095
+ sessionIdRef.current = activeSessionId;
2096
+ setSessionId(activeSessionId);
2097
+ }
1841
2098
  if (activeSessionId && pendingFork) {
1842
2099
  const fork = await commands.fork(activeSessionId, resume?.forkSessionId);
1843
2100
  activeSessionId = fork.sessionId;
@@ -1845,20 +2102,21 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1845
2102
  setPendingFork(false);
1846
2103
  }
1847
2104
  const result = shellCommand === undefined
1848
- ? activeSessionId
1849
- ? await commands.resume(activeSessionId, prompt, turnSignal, undefined, images)
1850
- : await commands.run(prompt, turnSignal, undefined, undefined, images)
1851
- : activeSessionId
1852
- ? commands.resumeShell
1853
- ? await commands.resumeShell(activeSessionId, shellCommand, turnSignal)
2105
+ ? startedNewSession
2106
+ ? await commands.run(prompt, turnSignal, activeSessionId, undefined, images)
2107
+ : await commands.resume(activeSessionId, prompt, turnSignal, undefined, images)
2108
+ : startedNewSession
2109
+ ? commands.runShell
2110
+ ? await commands.runShell(shellCommand, turnSignal, activeSessionId)
1854
2111
  : (() => {
1855
2112
  throw new Error('Interactive shell mode is unavailable');
1856
2113
  })()
1857
- : commands.runShell
1858
- ? await commands.runShell(shellCommand, turnSignal)
2114
+ : commands.resumeShell
2115
+ ? await commands.resumeShell(activeSessionId, shellCommand, turnSignal)
1859
2116
  : (() => {
1860
2117
  throw new Error('Interactive shell mode is unavailable');
1861
2118
  })();
2119
+ sessionIdRef.current = result.sessionId;
1862
2120
  setSessionId(result.sessionId);
1863
2121
  if (startedNewSession &&
1864
2122
  runtimePreferencesRef.current.permissionMode !== 'default') {
@@ -1887,6 +2145,17 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
1887
2145
  setActiveText('');
1888
2146
  setActiveThinking('');
1889
2147
  setStatus('ready');
2148
+ setTurnDuration(Date.now() - turnStartedAt);
2149
+ if (runtimeSettingsRef.current.notifChannel !== 'notifications_disabled') {
2150
+ notifyTerminal({
2151
+ channel: runtimeSettingsRef.current.notifChannel,
2152
+ title: 'Praxis',
2153
+ message: 'Turn complete',
2154
+ ...(notificationWriter === undefined
2155
+ ? {}
2156
+ : { write: notificationWriter }),
2157
+ });
2158
+ }
1890
2159
  }
1891
2160
  catch (error) {
1892
2161
  if (turnController.signal.aborted && !signal?.aborted) {
@@ -2059,9 +2328,11 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
2059
2328
  ? ['Help', 'Tabs']
2060
2329
  : menuRef.current.kind === 'permission-dashboard'
2061
2330
  ? ['Settings', 'Tabs']
2062
- : menuRef.current.kind === 'model'
2063
- ? ['ModelPicker', 'Select']
2064
- : ['Select']
2331
+ : menuRef.current.kind === 'theme'
2332
+ ? ['ThemePicker', 'Select']
2333
+ : menuRef.current.kind === 'model'
2334
+ ? ['ModelPicker', 'Select']
2335
+ : ['Select']
2065
2336
  : commandPaletteVisible || filePickerVisible
2066
2337
  ? ['Autocomplete', 'Chat']
2067
2338
  : permission || planApproval
@@ -2091,6 +2362,24 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
2091
2362
  return;
2092
2363
  }
2093
2364
  const isKeybinding = (action) => keybindingAction === action;
2365
+ if (runtimeSettingsRef.current.editor === 'vim' &&
2366
+ !permission &&
2367
+ !planApproval &&
2368
+ !question &&
2369
+ !elicitation &&
2370
+ menuRef.current === null) {
2371
+ if (!vimInsertMode) {
2372
+ if (value === 'i' || value === 'a')
2373
+ setVimInsertMode(true);
2374
+ else if (key.leftArrow || key.rightArrow)
2375
+ editComposer();
2376
+ return;
2377
+ }
2378
+ if (key.escape || value === '\u001B') {
2379
+ setVimInsertMode(false);
2380
+ return;
2381
+ }
2382
+ }
2094
2383
  if (permission) {
2095
2384
  if (lower === 'y' || value === '1') {
2096
2385
  permission.resolve(true);
@@ -2233,6 +2522,255 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
2233
2522
  return;
2234
2523
  }
2235
2524
  const earlyMenu = menuRef.current;
2525
+ if (earlyMenu?.kind === 'copy') {
2526
+ if (key.escape || value === '\u001B') {
2527
+ updateMenu(null);
2528
+ return;
2529
+ }
2530
+ if (key.upArrow || key.downArrow) {
2531
+ updateMenu({
2532
+ ...earlyMenu,
2533
+ selectedIndex: Math.max(0, Math.min(earlyMenu.candidates.length - 1, earlyMenu.selectedIndex + (key.upArrow ? -1 : 1))),
2534
+ });
2535
+ return;
2536
+ }
2537
+ if (/^[1-9]$/u.test(value)) {
2538
+ updateMenu({
2539
+ ...earlyMenu,
2540
+ selectedIndex: Math.min(earlyMenu.candidates.length - 1, Number(value) - 1),
2541
+ });
2542
+ return;
2543
+ }
2544
+ if (key.return) {
2545
+ const candidate = earlyMenu.candidates[earlyMenu.selectedIndex];
2546
+ if (!candidate)
2547
+ return;
2548
+ const copying = clipboardWriter(candidate.text).then(() => {
2549
+ updateMenu(null);
2550
+ append({
2551
+ kind: 'local-result',
2552
+ text: `Copied ${candidate.label.toLowerCase()} to clipboard.`,
2553
+ });
2554
+ }, (error) => warn(error));
2555
+ onTurnChange?.(copying);
2556
+ void copying.finally(() => onTurnChange?.(null));
2557
+ }
2558
+ return;
2559
+ }
2560
+ if (earlyMenu?.kind === 'agents') {
2561
+ if (key.escape || value === '\u001B') {
2562
+ updateMenu(null);
2563
+ return;
2564
+ }
2565
+ if (key.upArrow || key.downArrow) {
2566
+ updateMenu({
2567
+ ...earlyMenu,
2568
+ selectedIndex: Math.max(0, Math.min(earlyMenu.agents.length - 1, earlyMenu.selectedIndex + (key.upArrow ? -1 : 1))),
2569
+ });
2570
+ }
2571
+ else if (key.return) {
2572
+ const agent = earlyMenu.agents[earlyMenu.selectedIndex];
2573
+ if (agent) {
2574
+ updateComposerInput(`@${agent.name} `);
2575
+ updateMenu(null);
2576
+ }
2577
+ }
2578
+ return;
2579
+ }
2580
+ if (earlyMenu?.kind === 'tasks') {
2581
+ if (key.escape || value === '\u001B') {
2582
+ updateMenu(null);
2583
+ return;
2584
+ }
2585
+ const selected = earlyMenu.tasks[earlyMenu.state.selectedIndex];
2586
+ if ((value.toLowerCase() === 'x' || value === 'X') &&
2587
+ selected?.status === 'running') {
2588
+ const stopping = (async () => {
2589
+ setBusy(true);
2590
+ try {
2591
+ const commands = await service();
2592
+ if (!sessionId || !commands.stopTask)
2593
+ throw new Error('Task controls are unavailable.');
2594
+ await commands.stopTask(sessionId, selected.id);
2595
+ await refreshTasks(earlyMenu);
2596
+ }
2597
+ catch (error) {
2598
+ warn(error);
2599
+ }
2600
+ finally {
2601
+ setBusy(false);
2602
+ }
2603
+ })();
2604
+ onTurnChange?.(stopping);
2605
+ void stopping.finally(() => onTurnChange?.(null));
2606
+ return;
2607
+ }
2608
+ if (key.upArrow ||
2609
+ key.downArrow ||
2610
+ key.return ||
2611
+ key.leftArrow ||
2612
+ key.rightArrow) {
2613
+ const action = key.upArrow
2614
+ ? { type: 'move', delta: -1 }
2615
+ : key.downArrow
2616
+ ? { type: 'move', delta: 1 }
2617
+ : key.return
2618
+ ? { type: 'open' }
2619
+ : { type: 'back' };
2620
+ const state = updateTuiTaskPanelState(earlyMenu.state, earlyMenu.tasks, action);
2621
+ updateMenu({ ...earlyMenu, state });
2622
+ }
2623
+ return;
2624
+ }
2625
+ if (earlyMenu?.kind === 'mcp') {
2626
+ if (key.escape || value === '\u001B') {
2627
+ const transition = mcpControllerRef.current
2628
+ ? { state: earlyMenu.state, command: { type: 'close' } }
2629
+ : undefined;
2630
+ if (transition?.command?.type === 'close')
2631
+ updateMenu(null);
2632
+ return;
2633
+ }
2634
+ const input = key.upArrow
2635
+ ? { type: 'up' }
2636
+ : key.downArrow
2637
+ ? { type: 'down' }
2638
+ : key.return
2639
+ ? { type: 'confirm' }
2640
+ : key.leftArrow
2641
+ ? { type: 'back' }
2642
+ : null;
2643
+ if (input && mcpControllerRef.current) {
2644
+ const dispatch = mcpControllerRef.current.dispatch(earlyMenu.model, earlyMenu.state, input);
2645
+ const operation = (async () => {
2646
+ setBusy(true);
2647
+ try {
2648
+ const result = await dispatch;
2649
+ if (result.closed)
2650
+ updateMenu(null);
2651
+ else
2652
+ updateMenu({
2653
+ kind: 'mcp',
2654
+ model: result.model,
2655
+ state: result.state,
2656
+ });
2657
+ if (result.error)
2658
+ warn(result.error);
2659
+ }
2660
+ catch (error) {
2661
+ warn(error);
2662
+ }
2663
+ finally {
2664
+ setBusy(false);
2665
+ }
2666
+ })();
2667
+ onTurnChange?.(operation);
2668
+ void operation.finally(() => onTurnChange?.(null));
2669
+ }
2670
+ return;
2671
+ }
2672
+ if (earlyMenu?.kind === 'config') {
2673
+ if (key.escape || value === '\u001B') {
2674
+ if (earlyMenu.searchFocused && earlyMenu.query) {
2675
+ updateMenu({ ...earlyMenu, query: '' });
2676
+ }
2677
+ else {
2678
+ updateMenu(null);
2679
+ }
2680
+ return;
2681
+ }
2682
+ const rows = projectConfigRows(earlyMenu.snapshot, earlyMenu.query);
2683
+ if (key.leftArrow || key.rightArrow || key.tab) {
2684
+ const tabs = [
2685
+ 'settings',
2686
+ 'status',
2687
+ 'config',
2688
+ 'usage',
2689
+ 'stats',
2690
+ ];
2691
+ const index = tabs.indexOf(earlyMenu.tab);
2692
+ const direction = key.leftArrow || (key.tab && key.shift) ? -1 : 1;
2693
+ const tab = tabs[Math.max(0, Math.min(tabs.length - 1, index + direction))] ??
2694
+ 'config';
2695
+ updateMenu({
2696
+ ...earlyMenu,
2697
+ tab,
2698
+ selectedIndex: 0,
2699
+ searchFocused: tab === 'config',
2700
+ });
2701
+ return;
2702
+ }
2703
+ if (earlyMenu.tab !== 'config')
2704
+ return;
2705
+ if (key.upArrow || key.downArrow) {
2706
+ updateMenu({
2707
+ ...earlyMenu,
2708
+ searchFocused: false,
2709
+ selectedIndex: Math.max(0, Math.min(Math.max(0, rows.length - 1), earlyMenu.selectedIndex + (key.upArrow ? -1 : 1))),
2710
+ });
2711
+ return;
2712
+ }
2713
+ if (value === '/' && !earlyMenu.searchFocused) {
2714
+ updateMenu({ ...earlyMenu, searchFocused: true });
2715
+ return;
2716
+ }
2717
+ if (earlyMenu.searchFocused && key.backspace) {
2718
+ updateMenu({ ...earlyMenu, query: earlyMenu.query.slice(0, -1) });
2719
+ return;
2720
+ }
2721
+ if (earlyMenu.searchFocused &&
2722
+ !key.ctrl &&
2723
+ !key.meta &&
2724
+ value &&
2725
+ printable) {
2726
+ updateMenu({
2727
+ ...earlyMenu,
2728
+ query: earlyMenu.query + value,
2729
+ selectedIndex: 0,
2730
+ });
2731
+ return;
2732
+ }
2733
+ if (key.return || value === ' ') {
2734
+ const row = rows[earlyMenu.selectedIndex];
2735
+ if (!row)
2736
+ return;
2737
+ const definition = configSettingDefinition(row.definition.id);
2738
+ if (!definition || definition.values === 'language') {
2739
+ append({
2740
+ kind: 'warning',
2741
+ text: 'This setting requires a typed value and is not yet editable in the panel.',
2742
+ });
2743
+ return;
2744
+ }
2745
+ const values = definition.values;
2746
+ const currentIndex = values.indexOf(row.value);
2747
+ const value = values[(currentIndex + 1) % values.length];
2748
+ if (value === undefined)
2749
+ return;
2750
+ const saving = (async () => {
2751
+ setBusy(true);
2752
+ try {
2753
+ const snapshot = await saveConfigSetting(definition.id, value, configTarget);
2754
+ await reloadRuntimeSettings(snapshot);
2755
+ await retireService();
2756
+ updateMenu({ ...earlyMenu, snapshot });
2757
+ append({
2758
+ kind: 'local-result',
2759
+ text: `${definition.label} set to ${String(value)}`,
2760
+ });
2761
+ }
2762
+ catch (error) {
2763
+ warn(error);
2764
+ }
2765
+ finally {
2766
+ setBusy(false);
2767
+ }
2768
+ })();
2769
+ onTurnChange?.(saving);
2770
+ void saving.finally(() => onTurnChange?.(null));
2771
+ }
2772
+ return;
2773
+ }
2236
2774
  if (earlyMenu?.kind === 'memory') {
2237
2775
  if (key.escape || value === '\u001B') {
2238
2776
  updateMenu(null);
@@ -2792,6 +3330,68 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
2792
3330
  selectedIndex: -1,
2793
3331
  });
2794
3332
  }
3333
+ else if (activeMenu.tabIndex === 0 &&
3334
+ activeMenu.selectedIndex >= 0 &&
3335
+ (key.return || value.toLowerCase() === 'r')) {
3336
+ const action = recentDenied[activeMenu.selectedIndex];
3337
+ if (!action)
3338
+ return;
3339
+ const operation = (async () => {
3340
+ setBusy(true);
3341
+ const approvals = approvedSessionActionsRef.current.get(action.sessionId) ??
3342
+ new Set();
3343
+ const actionKey = claudePermissionActionKey(action.call);
3344
+ const wasApproved = approvals.has(actionKey);
3345
+ approvals.add(actionKey);
3346
+ approvedSessionActionsRef.current.set(action.sessionId, approvals);
3347
+ try {
3348
+ const commands = await service();
3349
+ if (value.toLowerCase() === 'r') {
3350
+ if (!commands.retryRecentlyDenied) {
3351
+ throw new Error('Recently denied retry is unavailable.');
3352
+ }
3353
+ setRetryingDeniedId(action.id);
3354
+ const result = await commands.retryRecentlyDenied(action.sessionId, action.display, signal);
3355
+ sessionIdRef.current = result.sessionId;
3356
+ setSessionId(result.sessionId);
3357
+ setUsage(result.usage);
3358
+ setCostUsd(result.costUsd);
3359
+ append({ kind: 'assistant', text: result.text });
3360
+ updateMenu(null);
3361
+ }
3362
+ else {
3363
+ if (!commands.approveRecentlyDenied) {
3364
+ throw new Error('Recently denied approval is unavailable.');
3365
+ }
3366
+ await commands.approveRecentlyDenied(action.sessionId, action.display);
3367
+ const entries = await recentDeniedStore.remove(action.id);
3368
+ setRecentDenied(entries);
3369
+ append({
3370
+ kind: 'notice',
3371
+ text: `Approved ${action.display}`,
3372
+ });
3373
+ updateMenu({
3374
+ kind: 'permission-dashboard',
3375
+ tabIndex: entries.length > 0 ? 0 : 1,
3376
+ selectedIndex: entries.length > 0 ? 0 : -1,
3377
+ query: '',
3378
+ rules: activeMenu.rules,
3379
+ });
3380
+ }
3381
+ }
3382
+ catch (error) {
3383
+ if (!wasApproved)
3384
+ approvals.delete(actionKey);
3385
+ warn(error);
3386
+ }
3387
+ finally {
3388
+ setRetryingDeniedId(null);
3389
+ setBusy(false);
3390
+ }
3391
+ })();
3392
+ onTurnChange?.(operation);
3393
+ void operation.finally(() => onTurnChange?.(null));
3394
+ }
2795
3395
  else if (key.return && behavior && activeMenu.selectedIndex === 0) {
2796
3396
  clearComposerInput();
2797
3397
  updateMenu({
@@ -3089,6 +3689,317 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
3089
3689
  }
3090
3690
  return;
3091
3691
  }
3692
+ if (activeMenu.kind === 'theme') {
3693
+ const themeOptionCount = TUI_THEMES.length + customThemes.length + 1;
3694
+ const lastThemeOptionIndex = themeOptionCount - 1;
3695
+ if (key.escape || value === '\u001B') {
3696
+ updateMenu(null);
3697
+ }
3698
+ else if (isKeybinding('theme:editCustom') || controlKey('e')) {
3699
+ const options = [
3700
+ ...TUI_THEMES,
3701
+ ...customThemes.map((theme) => `custom:${theme.slug}`),
3702
+ ];
3703
+ const selected = options[activeMenu.selectedIndex];
3704
+ const theme = selected?.startsWith('custom:')
3705
+ ? customThemes.find((entry) => `custom:${entry.slug}` === selected)
3706
+ : undefined;
3707
+ if (!theme) {
3708
+ append({
3709
+ kind: 'warning',
3710
+ text: 'Only custom themes can be edited.',
3711
+ });
3712
+ }
3713
+ else {
3714
+ updateMenu({
3715
+ kind: 'custom-theme-editor',
3716
+ theme,
3717
+ selectedIndex: 0,
3718
+ query: '',
3719
+ });
3720
+ }
3721
+ }
3722
+ else if (isKeybinding('theme:toggleSyntaxHighlighting')) {
3723
+ const syntaxHighlightingDisabled = !themeSettings.syntaxHighlightingDisabled;
3724
+ const saving = (async () => {
3725
+ setBusy(true);
3726
+ try {
3727
+ const committed = await presentationThemeStore.save({
3728
+ syntaxHighlightingDisabled,
3729
+ });
3730
+ setThemeSettings(committed);
3731
+ }
3732
+ catch (error) {
3733
+ warn(error);
3734
+ }
3735
+ finally {
3736
+ setBusy(false);
3737
+ }
3738
+ })();
3739
+ onTurnChange?.(saving);
3740
+ void saving.finally(() => onTurnChange?.(null));
3741
+ }
3742
+ else if (key.upArrow || key.downArrow) {
3743
+ updateMenu({
3744
+ ...activeMenu,
3745
+ selectedIndex: Math.max(0, Math.min(lastThemeOptionIndex, activeMenu.selectedIndex + (key.upArrow ? -1 : 1))),
3746
+ });
3747
+ }
3748
+ else if (/^[1-9]$/u.test(value)) {
3749
+ updateMenu({
3750
+ ...activeMenu,
3751
+ selectedIndex: Math.min(lastThemeOptionIndex, Number(value) - 1),
3752
+ });
3753
+ }
3754
+ else if (key.return) {
3755
+ const options = [
3756
+ ...TUI_THEMES,
3757
+ ...customThemes.map((theme) => `custom:${theme.slug}`),
3758
+ '__new__',
3759
+ ];
3760
+ const selected = options[activeMenu.selectedIndex];
3761
+ if (!selected)
3762
+ return;
3763
+ if (selected === '__new__') {
3764
+ const base = themeSettings.theme.startsWith('custom:')
3765
+ ? (themeSettings.customTheme?.base ?? 'dark')
3766
+ : themeSettings.theme === 'auto'
3767
+ ? 'dark'
3768
+ : themeSettings.theme;
3769
+ updateMenu({ kind: 'custom-theme-create', base });
3770
+ return;
3771
+ }
3772
+ const saving = (async () => {
3773
+ setBusy(true);
3774
+ try {
3775
+ const customTheme = selected.startsWith('custom:')
3776
+ ? customThemes.find((theme) => `custom:${theme.slug}` === selected)
3777
+ : undefined;
3778
+ const committed = await presentationThemeStore.save({
3779
+ theme: selected,
3780
+ ...(customTheme === undefined ? {} : { customTheme }),
3781
+ });
3782
+ setThemeSettings(committed);
3783
+ updateMenu(null);
3784
+ append({
3785
+ kind: 'local-result',
3786
+ text: `Theme set to ${customTheme?.name ?? selected}`,
3787
+ });
3788
+ }
3789
+ catch (error) {
3790
+ warn(error);
3791
+ }
3792
+ finally {
3793
+ setBusy(false);
3794
+ }
3795
+ })();
3796
+ onTurnChange?.(saving);
3797
+ void saving.finally(() => onTurnChange?.(null));
3798
+ }
3799
+ return;
3800
+ }
3801
+ if (activeMenu.kind === 'custom-theme-create') {
3802
+ if (key.escape || value === '\u001B') {
3803
+ clearComposerInput();
3804
+ updateMenu(null);
3805
+ }
3806
+ else if (key.return) {
3807
+ const name = inputRef.current.trim() || 'my-theme';
3808
+ const creating = (async () => {
3809
+ setBusy(true);
3810
+ try {
3811
+ const create = presentationThemeStore.createCustomTheme;
3812
+ if (!create)
3813
+ throw new Error('Custom theme controls are unavailable.');
3814
+ const theme = await create({ name, base: activeMenu.base });
3815
+ setCustomThemes((current) => [...current, theme].sort((left, right) => left.name.localeCompare(right.name)));
3816
+ const committed = await presentationThemeStore.save({
3817
+ theme: `custom:${theme.slug}`,
3818
+ customTheme: theme,
3819
+ });
3820
+ setThemeSettings(committed);
3821
+ clearComposerInput();
3822
+ updateMenu(null);
3823
+ append({
3824
+ kind: 'local-result',
3825
+ text: `Using custom theme "${theme.name}"`,
3826
+ });
3827
+ }
3828
+ catch (error) {
3829
+ warn(error);
3830
+ }
3831
+ finally {
3832
+ setBusy(false);
3833
+ }
3834
+ })();
3835
+ onTurnChange?.(creating);
3836
+ void creating.finally(() => onTurnChange?.(null));
3837
+ }
3838
+ else {
3839
+ editComposer();
3840
+ }
3841
+ return;
3842
+ }
3843
+ if (activeMenu.kind === 'custom-theme-editor') {
3844
+ const filteredTokens = CUSTOM_THEME_TOKENS.filter((token) => token.toLowerCase().includes(activeMenu.query.toLowerCase()));
3845
+ if (key.escape || value === '\u001B') {
3846
+ clearComposerInput();
3847
+ updateMenu(null);
3848
+ }
3849
+ else if (key.upArrow || key.downArrow) {
3850
+ updateMenu({
3851
+ ...activeMenu,
3852
+ selectedIndex: Math.max(0, Math.min(Math.max(0, filteredTokens.length - 1), activeMenu.selectedIndex + (key.upArrow ? -1 : 1))),
3853
+ });
3854
+ }
3855
+ else if (key.return) {
3856
+ const token = filteredTokens[activeMenu.selectedIndex];
3857
+ if (token) {
3858
+ const current = activeMenu.theme.overrides[token] ?? '';
3859
+ clearComposerInput();
3860
+ updateMenu({
3861
+ kind: 'custom-theme-token',
3862
+ theme: activeMenu.theme,
3863
+ token,
3864
+ });
3865
+ updateComposerInput(current);
3866
+ }
3867
+ }
3868
+ else if (key.tab) {
3869
+ const token = filteredTokens[activeMenu.selectedIndex];
3870
+ if (token && activeMenu.theme.overrides[token] !== undefined) {
3871
+ const resetting = (async () => {
3872
+ setBusy(true);
3873
+ try {
3874
+ const update = presentationThemeStore.updateCustomTheme;
3875
+ if (!update)
3876
+ throw new Error('Custom theme controls are unavailable.');
3877
+ const next = await update(activeMenu.theme, token, undefined);
3878
+ setCustomThemes((current) => current.map((entry) => entry.slug === next.slug ? next : entry));
3879
+ setThemeSettings((current) => ({
3880
+ ...current,
3881
+ theme: `custom:${next.slug}`,
3882
+ customTheme: next,
3883
+ }));
3884
+ updateMenu({ ...activeMenu, theme: next });
3885
+ }
3886
+ catch (error) {
3887
+ warn(error);
3888
+ }
3889
+ finally {
3890
+ setBusy(false);
3891
+ }
3892
+ })();
3893
+ onTurnChange?.(resetting);
3894
+ void resetting.finally(() => onTurnChange?.(null));
3895
+ }
3896
+ }
3897
+ else if (key.backspace || key.delete) {
3898
+ updateMenu({
3899
+ ...activeMenu,
3900
+ query: activeMenu.query.slice(0, -1),
3901
+ selectedIndex: 0,
3902
+ });
3903
+ }
3904
+ else if (!key.ctrl && !key.meta && value && printable) {
3905
+ updateMenu({
3906
+ ...activeMenu,
3907
+ query: activeMenu.query + value,
3908
+ selectedIndex: 0,
3909
+ });
3910
+ }
3911
+ else if (controlKey('d')) {
3912
+ updateMenu({
3913
+ kind: 'custom-theme-delete',
3914
+ theme: activeMenu.theme,
3915
+ selectedIndex: 0,
3916
+ });
3917
+ }
3918
+ return;
3919
+ }
3920
+ if (activeMenu.kind === 'custom-theme-token') {
3921
+ if (key.escape || value === '\u001B') {
3922
+ clearComposerInput();
3923
+ updateMenu({
3924
+ kind: 'custom-theme-editor',
3925
+ theme: activeMenu.theme,
3926
+ selectedIndex: 0,
3927
+ query: '',
3928
+ });
3929
+ }
3930
+ else if (key.return) {
3931
+ const valueToSave = inputRef.current.trim();
3932
+ const saving = (async () => {
3933
+ setBusy(true);
3934
+ try {
3935
+ const update = presentationThemeStore.updateCustomTheme;
3936
+ if (!update)
3937
+ throw new Error('Custom theme controls are unavailable.');
3938
+ const next = await update(activeMenu.theme, activeMenu.token, valueToSave);
3939
+ setCustomThemes((current) => current.map((entry) => entry.slug === next.slug ? next : entry));
3940
+ setThemeSettings((current) => ({
3941
+ ...current,
3942
+ theme: `custom:${next.slug}`,
3943
+ customTheme: next,
3944
+ }));
3945
+ clearComposerInput();
3946
+ updateMenu({
3947
+ kind: 'custom-theme-editor',
3948
+ theme: next,
3949
+ selectedIndex: 0,
3950
+ query: '',
3951
+ });
3952
+ }
3953
+ catch (error) {
3954
+ warn(error);
3955
+ }
3956
+ finally {
3957
+ setBusy(false);
3958
+ }
3959
+ })();
3960
+ onTurnChange?.(saving);
3961
+ void saving.finally(() => onTurnChange?.(null));
3962
+ }
3963
+ else {
3964
+ editComposer();
3965
+ }
3966
+ return;
3967
+ }
3968
+ if (activeMenu.kind === 'custom-theme-delete') {
3969
+ if (key.escape || value === '\u001B') {
3970
+ updateMenu(null);
3971
+ }
3972
+ else if (key.return || value.toLowerCase() === 'y') {
3973
+ const deleting = (async () => {
3974
+ setBusy(true);
3975
+ try {
3976
+ const remove = presentationThemeStore.deleteCustomTheme;
3977
+ if (!remove)
3978
+ throw new Error('Custom theme controls are unavailable.');
3979
+ await remove(activeMenu.theme);
3980
+ setCustomThemes((current) => current.filter((entry) => entry.slug !== activeMenu.theme.slug));
3981
+ const committed = await presentationThemeStore.save({
3982
+ theme: activeMenu.theme.base,
3983
+ });
3984
+ setThemeSettings(committed);
3985
+ updateMenu(null);
3986
+ append({
3987
+ kind: 'local-result',
3988
+ text: `Deleted custom theme "${activeMenu.theme.name}"`,
3989
+ });
3990
+ }
3991
+ catch (error) {
3992
+ warn(error);
3993
+ }
3994
+ finally {
3995
+ setBusy(false);
3996
+ }
3997
+ })();
3998
+ onTurnChange?.(deleting);
3999
+ void deleting.finally(() => onTurnChange?.(null));
4000
+ }
4001
+ return;
4002
+ }
3092
4003
  if (activeMenu.kind === 'compact-progress') {
3093
4004
  if (key.escape || value === '\u001B' || isKeybinding('chat:cancel')) {
3094
4005
  turnControllerRef.current?.abort();
@@ -3099,6 +4010,11 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
3099
4010
  updateMenu(null);
3100
4011
  return;
3101
4012
  }
4013
+ if (activeMenu.kind === 'mcp' ||
4014
+ activeMenu.kind === 'tasks' ||
4015
+ activeMenu.kind === 'config') {
4016
+ return;
4017
+ }
3102
4018
  const optionCount = activeMenu.kind === 'model'
3103
4019
  ? modelOptions.length
3104
4020
  : EFFORT_OPTIONS.length;
@@ -3194,6 +4110,13 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
3194
4110
  if (key.tab)
3195
4111
  return;
3196
4112
  }
4113
+ if (runtimeSettingsRef.current.leftArrowOpensAgents &&
4114
+ key.leftArrow &&
4115
+ inputRef.current.length === 0 &&
4116
+ availableAgents.length > 0) {
4117
+ updateMenu({ kind: 'agents', agents: availableAgents, selectedIndex: 0 });
4118
+ return;
4119
+ }
3197
4120
  if (isKeybinding('chat:cancel')) {
3198
4121
  const now = Date.now();
3199
4122
  if (now - lastEscapeAtRef.current <= 500)
@@ -3236,7 +4159,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
3236
4159
  return;
3237
4160
  }
3238
4161
  }
3239
- const implicitShiftNewline = key.return && key.shift && keybindingAction === undefined;
4162
+ const implicitShiftNewline = key.return && (key.shift || key.meta) && keybindingAction === undefined;
3240
4163
  if (isKeybinding('chat:submit') ||
3241
4164
  isKeybinding('chat:newline') ||
3242
4165
  implicitShiftNewline) {
@@ -3257,6 +4180,7 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
3257
4180
  if (!prompt || prompt === '!')
3258
4181
  return;
3259
4182
  const copyCommand = /^\/copy(?:\s+(\d+))?$/u.exec(prompt);
4183
+ const tuiCommand = /^\/tui(?:\s+(default|fullscreen))?$/u.exec(prompt);
3260
4184
  const renameCommand = /^\/rename(?:\s+(.+))?$/u.exec(prompt);
3261
4185
  const cdCommand = /^\/cd(?:\s+(.+))?$/u.exec(prompt);
3262
4186
  const btwCommand = /^\/btw(?:\s+([\s\S]+))?$/u.exec(prompt);
@@ -3294,6 +4218,34 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
3294
4218
  selectedIndex: EFFORT_OPTIONS.indexOf(runtimePreferences.effort),
3295
4219
  });
3296
4220
  }
4221
+ else if (prompt === '/theme') {
4222
+ updateMenu({
4223
+ kind: 'theme',
4224
+ selectedIndex: Math.max(0, themeSettings.theme.startsWith('custom:')
4225
+ ? TUI_THEMES.length +
4226
+ customThemes.findIndex((theme) => `custom:${theme.slug}` === themeSettings.theme)
4227
+ : TUI_THEMES.indexOf(themeSettings.theme)),
4228
+ });
4229
+ }
4230
+ else if (prompt === '/terminal-setup') {
4231
+ const setup = (async () => {
4232
+ setBusy(true);
4233
+ setStatus('configuring terminal');
4234
+ try {
4235
+ const result = await (terminalSetupOverride ?? setupTuiTerminal)();
4236
+ append({ kind: 'local-result', text: result });
4237
+ }
4238
+ catch (error) {
4239
+ warn(error);
4240
+ }
4241
+ finally {
4242
+ setBusy(false);
4243
+ setStatus('ready');
4244
+ }
4245
+ })();
4246
+ onTurnChange?.(setup);
4247
+ void setup.finally(() => onTurnChange?.(null));
4248
+ }
3297
4249
  else if (prompt === '/keybindings') {
3298
4250
  setKeybindingsEditing(true);
3299
4251
  }
@@ -3382,10 +4334,12 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
3382
4334
  const loading = (async () => {
3383
4335
  setBusy(true);
3384
4336
  try {
4337
+ const entries = await recentDeniedStore.load();
4338
+ setRecentDenied(entries);
3385
4339
  updateMenu({
3386
4340
  kind: 'permission-dashboard',
3387
- tabIndex: 1,
3388
- selectedIndex: -1,
4341
+ tabIndex: entries.length > 0 ? 0 : 1,
4342
+ selectedIndex: entries.length > 0 ? 0 : -1,
3389
4343
  query: '',
3390
4344
  rules: await permissionStore.load(),
3391
4345
  });
@@ -3511,11 +4465,69 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
3511
4465
  updateMenu({ kind: 'status', tabIndex: 1 });
3512
4466
  }
3513
4467
  else if (prompt === '/config') {
3514
- updateMenu({ kind: 'status', tabIndex: 2 });
4468
+ const initial = {
4469
+ kind: 'config',
4470
+ snapshot: { settings: {}, state: {} },
4471
+ tab: 'config',
4472
+ selectedIndex: 0,
4473
+ query: '',
4474
+ searchFocused: true,
4475
+ };
4476
+ updateMenu(initial);
4477
+ const loading = (async () => {
4478
+ setBusy(true);
4479
+ try {
4480
+ const snapshot = await loadConfigSettings(configTarget);
4481
+ if (menuRef.current?.kind === 'config')
4482
+ updateMenu({ ...menuRef.current, snapshot });
4483
+ }
4484
+ catch (error) {
4485
+ warn(error);
4486
+ }
4487
+ finally {
4488
+ setBusy(false);
4489
+ }
4490
+ })();
4491
+ onTurnChange?.(loading);
4492
+ void loading.finally(() => onTurnChange?.(null));
4493
+ }
4494
+ else if (tuiCommand) {
4495
+ const mode = tuiCommand[1];
4496
+ if (!mode) {
4497
+ append({
4498
+ kind: 'local-result',
4499
+ text: `TUI renderer: ${runtimeSettingsRef.current.tui}. Usage: /tui default|fullscreen`,
4500
+ });
4501
+ }
4502
+ else {
4503
+ const saving = (async () => {
4504
+ setBusy(true);
4505
+ try {
4506
+ const snapshot = await saveConfigSetting('tui', mode, configTarget);
4507
+ await reloadRuntimeSettings(snapshot);
4508
+ onRendererChange?.(mode, sessionId);
4509
+ exit();
4510
+ }
4511
+ catch (error) {
4512
+ warn(error);
4513
+ }
4514
+ finally {
4515
+ setBusy(false);
4516
+ }
4517
+ })();
4518
+ onTurnChange?.(saving);
4519
+ void saving.finally(() => onTurnChange?.(null));
4520
+ }
3515
4521
  }
3516
4522
  else if (prompt === '/usage') {
3517
4523
  updateMenu({ kind: 'status', tabIndex: 3 });
3518
4524
  }
4525
+ else if (prompt === '/update') {
4526
+ append({
4527
+ kind: 'local-result',
4528
+ text: `Configured auto-update channel: ${autoUpdateTarget(runtimeSettingsRef.current.autoUpdatesChannel)}. Run praxis update to update now.`,
4529
+ });
4530
+ }
3519
4531
  else if (prompt === '/skills' || prompt === '/skill') {
3520
4532
  updateMenu({
3521
4533
  kind: 'list',
@@ -3586,161 +4598,247 @@ export function InteractiveApp({ factory, initialSessions, initialPrompt, initia
3586
4598
  }
3587
4599
  editComposer();
3588
4600
  });
3589
- return (_jsx(Box, { flexDirection: "column", children: selectingSession ? (_jsx(SessionPicker, { sessions: filteredPickerChoices, selectedIndex: selectedIndex, screenReader: axScreenReader, query: sessionSearch })) : (_jsxs(_Fragment, { children: [!axScreenReader && history.length === 0 && !sessionId ? (_jsx(WelcomePanel, { display: runtimeDisplay, width: width })) : null, sessionId ? (_jsxs(Text, { dimColor: true, children: ["Session ", sessionId.slice(0, 8)] })) : null, _jsx(Transcript, { items: history, activeText: activeText, activeThinking: activeThinking, thinkingExpanded: thinkingExpanded, detailedTranscript: thinkingExpanded, screenReader: axScreenReader }), externalEditorRequest !== null ||
3590
- keybindingsEditing ||
3591
- memoryEditorRequest !== null ? (_jsx(ExternalEditorWait, { screenReader: axScreenReader })) : permission ? (_jsxs(DialogFrame, { title: permission.kind === 'recovery'
3592
- ? `Retry interrupted ${permission.call.name}?`
3593
- : `Allow ${permission.call.name}?`, screenReader: axScreenReader, children: [_jsx(Text, { bold: true, children: describeTool(permission.call, sensitiveValues) }), _jsx(Text, { children: "\u276F 1. Yes" }), _jsx(Text, { children: " 2. No" }), _jsx(Text, { dimColor: true, children: "Enter/Esc declines \u00B7 y/n quick response" })] })) : planApproval ? (_jsxs(DialogFrame, { title: "Approve this plan and begin implementation?", screenReader: axScreenReader, children: [_jsx(Text, { dimColor: true, children: planApproval.request.planPath }), planApproval.request.plan ? (_jsx(Box, { marginY: 1, children: _jsx(Text, { children: planApproval.request.plan }) })) : null, _jsx(Text, { children: "\u276F 1. Yes, implement the plan" }), _jsx(Text, { children: " 2. No, keep planning" })] })) : question ? (_jsxs(DialogFrame, { title: `${question.questions[question.index]?.header}: ${question.questions[question.index]?.question}`, screenReader: axScreenReader, children: [question.questions[question.index]?.options.map((option, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [index === 0 ? '❯ ' : ' ', index + 1, ". ", option.label, " \u2014 ", option.description] }), option.preview ? (_jsx(Text, { dimColor: true, children: option.preview })) : null] }, `${index}-${option.label}`))), _jsxs(Text, { children: ["\u203A ", input] }), _jsx(Text, { dimColor: true, children: question.questions[question.index]?.multiSelect
3594
- ? 'Enter comma-separated option numbers or custom text · Esc cancels'
3595
- : 'Enter one option number or custom text · Esc cancels' })] })) : elicitation ? (_jsxs(DialogFrame, { title: `MCP elicitation (${elicitation.request.serverName})`, screenReader: axScreenReader, children: [_jsx(Text, { children: elicitation.request.message }), elicitation.request.url ? (_jsx(Text, { children: elicitation.request.url })) : null, elicitation.request.requestedSchema ? (_jsx(Text, { dimColor: true, children: JSON.stringify(elicitation.request.requestedSchema) })) : null, _jsxs(Text, { children: ["\u203A ", input] }), _jsx(Text, { dimColor: true, children: "Enter JSON object to accept \u00B7 Esc to cancel" })] })) : menu?.kind === 'model-input' ? (_jsxs(DialogFrame, { title: "Enter model ID", screenReader: axScreenReader, children: [_jsx(Text, { dimColor: true, children: "Enter a model ID supported by the configured provider." }), _jsxs(Text, { children: ["\u203A ", input] }), _jsx(Text, { dimColor: true, children: "Enter confirms \u00B7 Esc cancels" })] })) : menu?.kind === 'export-filename' ? (_jsxs(DialogFrame, { title: "Enter filename:", screenReader: axScreenReader, children: [_jsxs(Text, { children: ["> ", input] }), _jsx(Text, { dimColor: true, children: "Enter to save \u00B7 Esc to go back" })] })) : menu?.kind === 'compact-progress' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "\u273B Compacting conversation\u2026" }), _jsxs(Text, { children: ['▰'.repeat(Math.floor(compactProgress / 2)), '▱'.repeat(50 - Math.floor(compactProgress / 2)), ' ', compactProgress, "%"] })] })) : menu?.kind === 'btw' ? (_jsx(BtwPanel, { entries: btwHistory, selectedIndex: menu.selectedIndex, scrollOffset: menu.scrollOffset, copied: btwCopied, width: width, screenReader: axScreenReader })) : menu?.kind === 'rewind' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: " Rewind" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [' ', "Restore the code and/or conversation to the point before\u2026"] }), _jsx(Text, { children: " " }), (() => {
3596
- const window = rewindPointWindow(menu.points, menu.selectedIndex);
3597
- return (_jsxs(_Fragment, { children: [window.start > 0 ? (_jsxs(Text, { dimColor: true, children: [" \u2191 ", window.start, " more above"] })) : null, menu.points
3598
- .slice(window.start, window.end)
3599
- .map((point, offset) => {
3600
- const index = window.start + offset;
3601
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { inverse: menu.selectedIndex === index, children: [menu.selectedIndex === index ? ' ❯ ' : ' ', point.prompt.replace(/\s+/gu, ' ').slice(0, 72)] }), _jsxs(Text, { dimColor: true, children: [' ', point.fileChanges.length > 0
3602
- ? point.fileChanges
3603
- .map((path) => path.startsWith(`${runtimeCwd}/`)
3604
- ? path.slice(runtimeCwd.length + 1)
3605
- : path)
3606
- .join(', ')
3607
- : point.fileRestoreAvailable
3608
- ? 'No code changes'
3609
- : '⚠ No code restore'] })] }, point.messageId));
3610
- }), window.end < menu.points.length ? (_jsxs(Text, { dimColor: true, children: [' ', "\u2193 ", menu.points.length - window.end, " more below"] })) : null] }));
3611
- })(), _jsxs(Text, { inverse: menu.selectedIndex === menu.points.length, children: [menu.selectedIndex === menu.points.length ? ' ❯ ' : ' ', "(current)"] }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: " Enter to continue \u00B7 Esc to cancel" })] })) : menu?.kind === 'rewind-confirm' ? (_jsxs(DialogFrame, { title: "Confirm restore point", screenReader: axScreenReader, children: [_jsxs(Text, { children: ["\u2502 ", menu.point.prompt] }), _jsx(Text, { children: " " }), _jsx(Text, { children: "The conversation will be forked." }), _jsxs(Text, { children: ["The code will", ' ', menu.point.fileChanges.length > 0
3612
- ? `restore ${menu.point.fileChanges.join(', ')}.`
3613
- : 'be unchanged.'] }), _jsx(Text, { children: " " }), rewindActions(menu.point).map((option, index) => (_jsxs(Text, { inverse: menu.selectedIndex === index, children: [menu.selectedIndex === index ? '❯ ' : ' ', index + 1, ". ", option.label] }, option.action))), _jsx(Text, { children: " " }), _jsx(Text, { color: "yellow", children: "\u26A0 Rewinding does not affect files edited manually or via bash." })] })) : menu?.kind === 'rewind-context' ? (_jsxs(DialogFrame, { title: `Summarize ${menu.direction === 'from' ? 'from' : 'up to'} here`, screenReader: axScreenReader, children: [_jsx(Text, { children: menu.direction === 'from'
3614
- ? 'Messages after this point will be summarized.'
3615
- : 'Messages up to this point will be summarized.' }), _jsx(Text, { children: " " }), _jsxs(Text, { children: ["add context (optional): ", input] }), _jsx(Text, { dimColor: true, children: "Enter to summarize \u00B7 Esc to go back" })] })) : menu?.kind === 'permission-rule-input' ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: ["Add ", menu.behavior, " permission rule"] }), _jsx(Text, { children: "Permission rules are a tool name, optionally followed by a specifier in parentheses." }), _jsx(Text, { children: "e.g., WebFetch or Bash(ls *)" }), _jsx(Text, { children: " " }), _jsx(Box, { borderStyle: axScreenReader ? undefined : 'round', paddingX: axScreenReader ? 0 : 1, children: _jsx(Text, { ...(input ? {} : { dimColor: true }), children: input || 'Enter permission rule…' }) }), _jsx(Text, { dimColor: true, children: "Enter to submit \u00B7 Esc to cancel" })] })) : menu?.kind === 'workspace-directory-input' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Add directory to workspace" }), _jsx(Text, { children: "Praxis Code will be able to read files in this directory and make edits when auto-accept edits is on." }), _jsx(Text, { children: " " }), _jsx(Text, { children: "Enter the path to the directory:" }), _jsx(Box, { borderStyle: axScreenReader ? undefined : 'round', paddingX: axScreenReader ? 0 : 1, children: _jsx(Text, { ...(input ? {} : { dimColor: true }), children: input || 'Directory path…' }) }), _jsx(Text, { dimColor: true, children: "Tab to complete \u00B7 Enter to add \u00B7 Esc to cancel" })] })) : menu?.kind === 'permission-delete' ? (_jsxs(DialogFrame, { title: permissionDeleteTitle(menu.rule.behavior), screenReader: axScreenReader, children: [_jsx(Text, { children: menu.rule.rule }), permissionRuleDescription(menu.rule.rule) ? (_jsx(Text, { dimColor: true, children: permissionRuleDescription(menu.rule.rule) })) : null, _jsx(Text, { dimColor: true, children: permissionScopeLabel(menu.rule.scope) }), _jsx(Text, { children: " " }), _jsx(Text, { children: "Are you sure you want to delete this permission rule?" }), _jsx(Text, { children: " " }), _jsxs(Text, { inverse: menu.selectedIndex === 0, children: [menu.selectedIndex === 0 ? '❯ ' : ' ', "1. Yes"] }), _jsxs(Text, { inverse: menu.selectedIndex === 1, children: [menu.selectedIndex === 1 ? '❯ ' : ' ', "2. No"] }), _jsx(Text, { dimColor: true, children: "Esc to cancel" })] })) : menu?.kind === 'workspace-directory-delete' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Remove directory from workspace?" }), _jsxs(Text, { children: [" ", menu.path] }), _jsx(Text, { children: " " }), _jsx(Text, { children: "Praxis Code will no longer have access to files in this directory." }), _jsx(Text, { children: " " }), _jsxs(Text, { inverse: menu.selectedIndex === 0, children: [menu.selectedIndex === 0 ? '❯ ' : ' ', "1. Yes"] }), _jsxs(Text, { inverse: menu.selectedIndex === 1, children: [menu.selectedIndex === 1 ? '❯ ' : ' ', "2. No"] }), _jsx(Text, { dimColor: true, children: "Enter to confirm \u00B7 Esc to cancel" })] })) : menu ? (menu.kind === 'help' ? (_jsx(HelpMenu, { tabIndex: menu.tabIndex, selectedIndex: menu.selectedIndex, builtinCommands: builtinSlashCommands, customCommands: customSlashCommands, width: width, screenReader: axScreenReader })) : menu.kind === 'diff' ? (_jsx(DiffDashboard, { snapshots: menu.snapshots, sourceIndex: menu.sourceIndex, selectedIndex: menu.selectedIndex, viewingFile: menu.viewingFile, scrollOffset: menu.scrollOffset, width: width, screenReader: axScreenReader })) : menu.kind === 'permission-dashboard' ? (_jsx(PermissionDashboard, { tabIndex: menu.tabIndex, selectedIndex: menu.selectedIndex, query: menu.query, rules: menu.rules, recentDenied: recentDenied, workspaceDirectories: workspaceDirectories, width: width, screenReader: axScreenReader })) : menu.kind === 'permission-scope' ? (_jsx(SelectionMenu, { title: "Where should this rule be saved?", description: `${menu.rule}${permissionRuleDescription(menu.rule)
3616
- ? ` · ${permissionRuleDescription(menu.rule)}`
3617
- : ''}`, options: [
3618
- {
3619
- label: 'Project settings (local)',
3620
- description: 'Saved in .claude/settings.local.json',
3621
- },
3622
- {
3623
- label: 'Project settings',
3624
- description: 'Checked in at .claude/settings.json',
3625
- },
3626
- {
3627
- label: 'User settings',
3628
- description: 'Saved in at ~/.claude/settings.json',
3629
- },
3630
- ], selectedIndex: menu.selectedIndex, footer: "Enter to confirm \u00B7 Esc to cancel", width: width, screenReader: axScreenReader })) : menu.kind === 'status' ? (_jsx(StatusDashboard, { tabIndex: menu.tabIndex, version: runtimeDisplay.version, sessionId: sessionId, display: runtimeDisplay, ...(usage === undefined ? {} : { usage }), ...(costUsd === undefined ? {} : { costUsd }), turnCount: turnNumberRef.current, toolCount: history.filter((item) => item.kind === 'tool').length, commandCount: allSlashCommands.length, detailedTranscript: thinkingExpanded, width: width, screenReader: axScreenReader })) : menu.kind === 'memory' ? (_jsx(MemoryDashboard, { autoMemoryEnabled: menu.autoMemoryEnabled, entries: menu.entries, selectedIndex: menu.selectedIndex, openedIndex: menu.openedIndex, loading: menu.loading, width: width, screenReader: axScreenReader })) : menu.kind === 'hooks' ? (_jsx(HookDashboard, { configuration: menu.configuration, depth: menu.depth, eventIndex: menu.eventIndex, matcherIndex: menu.matcherIndex, hookIndex: menu.hookIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'list' ? (_jsx(ListDashboard, { title: menu.title, rows: menu.rows, emptyText: menu.emptyText, selectedIndex: menu.selectedIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'model' ? (_jsx(SelectionMenu, { title: "Select model", description: `Effort: ${runtimePreferences.effort}`, options: modelOptions, selectedIndex: menu.selectedIndex, footer: "\u2191/\u2193 select \u00B7 \u2190/\u2192 effort \u00B7 Enter applies to this session \u00B7 Esc cancels", width: width, screenReader: axScreenReader })) : menu.kind === 'effort' ? (_jsx(SelectionMenu, { title: "Select effort", description: "Controls how much reasoning effort the provider should use.", options: EFFORT_OPTIONS.map((option) => ({
3631
- label: option,
3632
- description: option === 'low'
3633
- ? 'Fastest and least deliberative.'
3634
- : option === 'max'
3635
- ? 'Highest available reasoning effort.'
3636
- : 'Use this effort for the next session turns.',
3637
- selected: option === runtimePreferences.effort,
3638
- })), selectedIndex: menu.selectedIndex, footer: "\u2191/\u2193 select \u00B7 Enter applies to this session \u00B7 Esc cancels", width: width, screenReader: axScreenReader })) : menu.kind === 'export' ? (_jsx(SelectionMenu, { title: "Export conversation", description: "Select export method", options: [
3639
- {
3640
- label: 'Copy to clipboard',
3641
- description: 'Copy the conversation to your system clipboard',
3642
- },
3643
- {
3644
- label: 'Save to file',
3645
- description: 'Save the conversation to a file in the current directory',
3646
- },
3647
- ], selectedIndex: menu.selectedIndex, footer: "Esc to cancel", width: width, screenReader: axScreenReader })) : null) : (_jsxs(_Fragment, { children: [commandPaletteVisible ? (_jsx(CommandPalette, { commands: matchingSlashCommands, selectedIndex: selectedSlashCommandIndex, width: width, screenReader: axScreenReader })) : null, filePickerVisible ? (_jsx(MentionPicker, { entries: matchingMentionEntries, selectedIndex: selectedFileIndex, width: width, screenReader: axScreenReader })) : null, exitConfirmation ? (_jsx(Text, { color: "yellow", children: "Press Ctrl-C again to exit" })) : null, _jsx(Composer, { input: shellMode ? input.slice(1) : input, cursor: shellMode ? Math.max(0, inputCursor - 1) : inputCursor, shellMode: shellMode, busy: busy, clipboardBusy: clipboardBusy, status: status, display: runtimeDisplay, ...(usage === undefined ? {} : { usage }), ...(costUsd === undefined ? {} : { costUsd }), width: width, screenReader: axScreenReader, hasThinking: hasDetailedTranscript, thinkingExpanded: thinkingExpanded, shortcutsVisible: shortcutsVisible, ...(editorFooterMessage === undefined
3648
- ? {}
3649
- : { footerMessage: editorFooterMessage }) })] }))] })) }));
4601
+ return (_jsx(TuiThemeProvider, { settings: themeSettings, children: _jsx(Box, { flexDirection: "column", children: selectingSession ? (_jsx(SessionPicker, { sessions: filteredPickerChoices, selectedIndex: selectedIndex, screenReader: axScreenReader, query: sessionSearch })) : (_jsxs(_Fragment, { children: [!axScreenReader && history.length === 0 && !sessionId ? (_jsx(WelcomePanel, { display: runtimeDisplay, width: width })) : null, sessionId ? (_jsxs(Text, { dimColor: true, children: ["Session ", sessionId.slice(0, 8)] })) : null, _jsx(Transcript, { items: history, activeText: activeText, activeThinking: activeThinking, thinkingExpanded: thinkingExpanded, detailedTranscript: thinkingExpanded || runtimeSettings.verbose, screenReader: axScreenReader }), externalEditorRequest !== null ||
4602
+ keybindingsEditing ||
4603
+ memoryEditorRequest !== null ? (_jsx(ExternalEditorWait, { screenReader: axScreenReader })) : permission ? (_jsxs(DialogFrame, { title: permission.kind === 'recovery'
4604
+ ? `Retry interrupted ${permission.call.name}?`
4605
+ : `Allow ${permission.call.name}?`, screenReader: axScreenReader, children: [_jsx(Text, { bold: true, children: describeTool(permission.call, sensitiveValues) }), _jsxs(Text, { children: [selectionPrefix(true, axScreenReader), "1. Yes"] }), _jsx(Text, { children: " 2. No" }), _jsx(Text, { dimColor: true, children: "Enter/Esc declines \u00B7 y/n quick response" })] })) : planApproval ? (_jsxs(DialogFrame, { title: "Approve this plan and begin implementation?", screenReader: axScreenReader, children: [_jsx(Text, { dimColor: true, children: planApproval.request.planPath }), planApproval.request.plan ? (_jsx(Box, { marginY: 1, children: _jsx(Text, { children: planApproval.request.plan }) })) : null, _jsxs(Text, { children: [selectionPrefix(true, axScreenReader), "1. Yes, implement the plan"] }), _jsx(Text, { children: " 2. No, keep planning" })] })) : question ? (_jsxs(DialogFrame, { title: `${question.questions[question.index]?.header}: ${question.questions[question.index]?.question}`, screenReader: axScreenReader, children: [question.questions[question.index]?.options.map((option, index) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: [index + 1, ". ", option.label, " \u2014 ", option.description] }), option.preview ? (_jsx(Text, { dimColor: true, children: option.preview })) : null] }, `${index}-${option.label}`))), _jsxs(Text, { children: [axScreenReader ? 'Current answer: ' : '› ', input || (axScreenReader ? '(empty)' : '')] }), _jsx(Text, { dimColor: true, children: question.questions[question.index]?.multiSelect
4606
+ ? 'Enter comma-separated option numbers or custom text · Esc cancels'
4607
+ : 'Enter one option number or custom text · Esc cancels' })] })) : elicitation ? (_jsxs(DialogFrame, { title: `MCP elicitation (${elicitation.request.serverName})`, screenReader: axScreenReader, children: [_jsx(Text, { children: elicitation.request.message }), elicitation.request.url ? (_jsx(Text, { children: elicitation.request.url })) : null, elicitation.request.requestedSchema ? (_jsx(Text, { dimColor: true, children: JSON.stringify(elicitation.request.requestedSchema) })) : null, _jsxs(Text, { children: ["\u203A ", input] }), _jsx(Text, { dimColor: true, children: "Enter JSON object to accept \u00B7 Esc to cancel" })] })) : menu?.kind === 'model-input' ? (_jsxs(DialogFrame, { title: "Enter model ID", screenReader: axScreenReader, children: [_jsx(Text, { dimColor: true, children: "Enter a model ID supported by the configured provider." }), _jsxs(Text, { children: ["\u203A ", input] }), _jsx(Text, { dimColor: true, children: "Enter confirms \u00B7 Esc cancels" })] })) : menu?.kind === 'export-filename' ? (_jsxs(DialogFrame, { title: "Enter filename:", screenReader: axScreenReader, children: [_jsxs(Text, { children: ["> ", input] }), _jsx(Text, { dimColor: true, children: "Enter to save \u00B7 Esc to go back" })] })) : menu?.kind === 'compact-progress' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "\u273B Compacting conversation\u2026" }), _jsxs(Text, { children: ['▰'.repeat(Math.floor(compactProgress / 2)), '▱'.repeat(50 - Math.floor(compactProgress / 2)), ' ', compactProgress, "%"] })] })) : menu?.kind === 'btw' ? (_jsx(BtwPanel, { entries: btwHistory, selectedIndex: menu.selectedIndex, scrollOffset: menu.scrollOffset, copied: btwCopied, width: width, screenReader: axScreenReader })) : menu?.kind === 'rewind' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: " Rewind" }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [' ', "Restore the code and/or conversation to the point before\u2026"] }), _jsx(Text, { children: " " }), (() => {
4608
+ const window = rewindPointWindow(menu.points, menu.selectedIndex);
4609
+ return (_jsxs(_Fragment, { children: [window.start > 0 ? (_jsxs(Text, { dimColor: true, children: [" \u2191 ", window.start, " more above"] })) : null, menu.points
4610
+ .slice(window.start, window.end)
4611
+ .map((point, offset) => {
4612
+ const index = window.start + offset;
4613
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { inverse: menu.selectedIndex === index, children: [menu.selectedIndex === index ? ' ❯ ' : ' ', point.prompt
4614
+ .replace(/\s+/gu, ' ')
4615
+ .slice(0, 72)] }), _jsxs(Text, { dimColor: true, children: [' ', point.fileChanges.length > 0
4616
+ ? point.fileChanges
4617
+ .map((path) => path.startsWith(`${runtimeCwd}/`)
4618
+ ? path.slice(runtimeCwd.length + 1)
4619
+ : path)
4620
+ .join(', ')
4621
+ : point.fileRestoreAvailable
4622
+ ? 'No code changes'
4623
+ : '⚠ No code restore'] })] }, point.messageId));
4624
+ }), window.end < menu.points.length ? (_jsxs(Text, { dimColor: true, children: [' ', "\u2193 ", menu.points.length - window.end, " more below"] })) : null] }));
4625
+ })(), _jsxs(Text, { inverse: menu.selectedIndex === menu.points.length, children: [menu.selectedIndex === menu.points.length ? ' ❯ ' : ' ', "(current)"] }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: " Enter to continue \u00B7 Esc to cancel" })] })) : menu?.kind === 'rewind-confirm' ? (_jsxs(DialogFrame, { title: "Confirm restore point", screenReader: axScreenReader, children: [_jsxs(Text, { children: ["\u2502 ", menu.point.prompt] }), _jsx(Text, { children: " " }), _jsx(Text, { children: "The conversation will be forked." }), _jsxs(Text, { children: ["The code will", ' ', menu.point.fileChanges.length > 0
4626
+ ? `restore ${menu.point.fileChanges.join(', ')}.`
4627
+ : 'be unchanged.'] }), _jsx(Text, { children: " " }), rewindActions(menu.point).map((option, index) => (_jsxs(Text, { inverse: menu.selectedIndex === index, children: [menu.selectedIndex === index ? '❯ ' : ' ', index + 1, ". ", option.label] }, option.action))), _jsx(Text, { children: " " }), _jsx(Text, { color: activePalette.warning, children: "\u26A0 Rewinding does not affect files edited manually or via bash." })] })) : menu?.kind === 'rewind-context' ? (_jsxs(DialogFrame, { title: `Summarize ${menu.direction === 'from' ? 'from' : 'up to'} here`, screenReader: axScreenReader, children: [_jsx(Text, { children: menu.direction === 'from'
4628
+ ? 'Messages after this point will be summarized.'
4629
+ : 'Messages up to this point will be summarized.' }), _jsx(Text, { children: " " }), _jsxs(Text, { children: ["add context (optional): ", input] }), _jsx(Text, { dimColor: true, children: "Enter to summarize \u00B7 Esc to go back" })] })) : menu?.kind === 'permission-rule-input' ? (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: ["Add ", menu.behavior, " permission rule"] }), _jsx(Text, { children: "Permission rules are a tool name, optionally followed by a specifier in parentheses." }), _jsx(Text, { children: "e.g., WebFetch or Bash(ls *)" }), _jsx(Text, { children: " " }), _jsx(Box, { borderStyle: axScreenReader ? undefined : 'round', paddingX: axScreenReader ? 0 : 1, children: _jsx(Text, { ...(input ? {} : { dimColor: true }), children: input || 'Enter permission rule…' }) }), _jsx(Text, { dimColor: true, children: "Enter to submit \u00B7 Esc to cancel" })] })) : menu?.kind === 'workspace-directory-input' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Add directory to workspace" }), _jsx(Text, { children: "Praxis Code will be able to read files in this directory and make edits when auto-accept edits is on." }), _jsx(Text, { children: " " }), _jsx(Text, { children: "Enter the path to the directory:" }), _jsx(Box, { borderStyle: axScreenReader ? undefined : 'round', paddingX: axScreenReader ? 0 : 1, children: _jsx(Text, { ...(input ? {} : { dimColor: true }), children: input || 'Directory path…' }) }), _jsx(Text, { dimColor: true, children: "Tab to complete \u00B7 Enter to add \u00B7 Esc to cancel" })] })) : menu?.kind === 'permission-delete' ? (_jsxs(DialogFrame, { title: permissionDeleteTitle(menu.rule.behavior), screenReader: axScreenReader, children: [_jsx(Text, { children: menu.rule.rule }), permissionRuleDescription(menu.rule.rule) ? (_jsx(Text, { dimColor: true, children: permissionRuleDescription(menu.rule.rule) })) : null, _jsx(Text, { dimColor: true, children: permissionScopeLabel(menu.rule.scope) }), _jsx(Text, { children: " " }), _jsx(Text, { children: "Are you sure you want to delete this permission rule?" }), _jsx(Text, { children: " " }), _jsxs(Text, { inverse: !axScreenReader && menu.selectedIndex === 0, children: [selectionPrefix(menu.selectedIndex === 0, axScreenReader), "1. Yes"] }), _jsxs(Text, { inverse: !axScreenReader && menu.selectedIndex === 1, children: [selectionPrefix(menu.selectedIndex === 1, axScreenReader), "2. No"] }), _jsx(Text, { dimColor: true, children: "Esc to cancel" })] })) : menu?.kind === 'workspace-directory-delete' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Remove directory from workspace?" }), _jsxs(Text, { children: [" ", menu.path] }), _jsx(Text, { children: " " }), _jsx(Text, { children: "Praxis Code will no longer have access to files in this directory." }), _jsx(Text, { children: " " }), _jsxs(Text, { inverse: !axScreenReader && menu.selectedIndex === 0, children: [selectionPrefix(menu.selectedIndex === 0, axScreenReader), "1. Yes"] }), _jsxs(Text, { inverse: !axScreenReader && menu.selectedIndex === 1, children: [selectionPrefix(menu.selectedIndex === 1, axScreenReader), "2. No"] }), _jsx(Text, { dimColor: true, children: "Enter to confirm \u00B7 Esc to cancel" })] })) : menu ? (menu.kind === 'help' ? (_jsx(HelpMenu, { tabIndex: menu.tabIndex, selectedIndex: menu.selectedIndex, builtinCommands: builtinSlashCommands, customCommands: customSlashCommands, width: width, screenReader: axScreenReader })) : menu.kind === 'diff' ? (_jsx(DiffDashboard, { snapshots: menu.snapshots, sourceIndex: menu.sourceIndex, selectedIndex: menu.selectedIndex, viewingFile: menu.viewingFile, scrollOffset: menu.scrollOffset, width: width, screenReader: axScreenReader })) : menu.kind === 'permission-dashboard' ? (_jsx(PermissionDashboard, { tabIndex: menu.tabIndex, selectedIndex: menu.selectedIndex, query: menu.query, rules: menu.rules, recentDenied: recentDenied, retryingDeniedId: retryingDeniedId, workspaceDirectories: workspaceDirectories, width: width, screenReader: axScreenReader })) : menu.kind === 'permission-scope' ? (_jsx(SelectionMenu, { title: "Where should this rule be saved?", description: `${menu.rule}${permissionRuleDescription(menu.rule)
4630
+ ? ` · ${permissionRuleDescription(menu.rule)}`
4631
+ : ''}`, options: [
4632
+ {
4633
+ label: 'Project settings (local)',
4634
+ description: 'Saved in .claude/settings.local.json',
4635
+ },
4636
+ {
4637
+ label: 'Project settings',
4638
+ description: 'Checked in at .claude/settings.json',
4639
+ },
4640
+ {
4641
+ label: 'User settings',
4642
+ description: 'Saved in at ~/.claude/settings.json',
4643
+ },
4644
+ ], selectedIndex: menu.selectedIndex, footer: "Enter to confirm \u00B7 Esc to cancel", width: width, screenReader: axScreenReader })) : menu.kind === 'status' ? (_jsx(StatusDashboard, { tabIndex: menu.tabIndex, version: runtimeDisplay.version, sessionId: sessionId, display: runtimeDisplay, ...(usage === undefined ? {} : { usage }), ...(costUsd === undefined ? {} : { costUsd }), turnCount: turnNumberRef.current, toolCount: history.filter((item) => item.kind === 'tool').length, commandCount: allSlashCommands.length, detailedTranscript: thinkingExpanded, width: width, screenReader: axScreenReader })) : menu.kind === 'copy' ? (_jsx(SelectionMenu, { title: "Copy response", description: "Choose the response content to copy.", options: menu.candidates.map((candidate) => ({
4645
+ label: candidate.label,
4646
+ description: candidate.description,
4647
+ })), selectedIndex: menu.selectedIndex, footer: "\u2191/\u2193 select \u00B7 Enter copies \u00B7 Esc cancels", width: width, screenReader: axScreenReader })) : menu.kind === 'agents' ? (_jsx(ListDashboard, { title: "Agents", rows: menu.agents.map((agent) => ({
4648
+ label: agent.name,
4649
+ description: agent.description,
4650
+ })), emptyText: "No agents configured", selectedIndex: menu.selectedIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'config' ? (_jsx(ConfigDashboard, { tab: menu.tab, snapshot: menu.snapshot, query: menu.query, selectedIndex: menu.selectedIndex, searchFocused: menu.searchFocused, settings: [
4651
+ {
4652
+ label: 'Provider',
4653
+ value: runtimeDisplay.model ?? 'default',
4654
+ },
4655
+ {
4656
+ label: 'Permission mode',
4657
+ value: runtimeDisplay.permissionMode ?? 'default',
4658
+ },
4659
+ ], status: {
4660
+ version: runtimeDisplay.version,
4661
+ sessionId: sessionId ?? 'new',
4662
+ cwd: runtimeCwd,
4663
+ model: runtimeDisplay.model ?? 'default',
4664
+ settingSources: ['user', 'project', 'local'],
4665
+ }, usage: {
4666
+ costUsd: costUsd ?? 0,
4667
+ apiDurationMs: 0,
4668
+ wallDurationMs: 0,
4669
+ linesAdded: 0,
4670
+ linesRemoved: 0,
4671
+ usage: usage ?? { inputTokens: 0, outputTokens: 0 },
4672
+ }, stats: [], width: width, screenReader: axScreenReader })) : menu.kind === 'mcp' ? (_jsx(McpPanel, { model: menu.model, state: menu.state, width: width, screenReader: axScreenReader })) : menu.kind === 'tasks' ? (_jsx(TaskPanel, { tasks: menu.tasks, state: menu.state, width: width, screenReader: axScreenReader })) : menu.kind === 'memory' ? (_jsx(MemoryDashboard, { autoMemoryEnabled: menu.autoMemoryEnabled, entries: menu.entries, selectedIndex: menu.selectedIndex, openedIndex: menu.openedIndex, loading: menu.loading, width: width, screenReader: axScreenReader })) : menu.kind === 'hooks' ? (_jsx(HookDashboard, { configuration: menu.configuration, depth: menu.depth, eventIndex: menu.eventIndex, matcherIndex: menu.matcherIndex, hookIndex: menu.hookIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'list' ? (_jsx(ListDashboard, { title: menu.title, rows: menu.rows, emptyText: menu.emptyText, selectedIndex: menu.selectedIndex, width: width, screenReader: axScreenReader })) : menu.kind === 'model' ? (_jsx(SelectionMenu, { title: "Select model", description: `Effort: ${runtimePreferences.effort}`, options: modelOptions, selectedIndex: menu.selectedIndex, footer: "\u2191/\u2193 select \u00B7 \u2190/\u2192 effort \u00B7 Enter applies to this session \u00B7 Esc cancels", width: width, screenReader: axScreenReader })) : menu.kind === 'effort' ? (_jsx(SelectionMenu, { title: "Select effort", description: "Controls how much reasoning effort the provider should use.", options: EFFORT_OPTIONS.map((option) => ({
4673
+ label: option,
4674
+ description: option === 'low'
4675
+ ? 'Fastest and least deliberative.'
4676
+ : option === 'max'
4677
+ ? 'Highest available reasoning effort.'
4678
+ : 'Use this effort for the next session turns.',
4679
+ selected: option === runtimePreferences.effort,
4680
+ })), selectedIndex: menu.selectedIndex, footer: "\u2191/\u2193 select \u00B7 Enter applies to this session \u00B7 Esc cancels", width: width, screenReader: axScreenReader })) : menu.kind === 'theme' ? (_jsx(ThemePicker, { currentTheme: themeSettings.theme, selectedIndex: menu.selectedIndex, customThemes: customThemes, syntaxHighlightingDisabled: themeSettings.syntaxHighlightingDisabled, width: width, screenReader: axScreenReader })) : menu.kind === 'custom-theme-create' ? (_jsxs(DialogFrame, { title: "New custom theme", screenReader: axScreenReader, children: [_jsxs(Text, { children: ["Name: ", input || 'my-theme'] }), _jsxs(Text, { children: ["based on ", menu.base, " \u00B7 saved to ~/.claude/themes/theme.json"] }), _jsx(Text, { dimColor: true, children: "Enter to create \u00B7 Esc to cancel" })] })) : menu.kind === 'custom-theme-editor' ? (_jsx(CustomThemeEditor, { theme: menu.theme, width: width, screenReader: axScreenReader, value: menu.query, tokens: CUSTOM_THEME_TOKENS.filter((token) => token.toLowerCase().includes(menu.query.toLowerCase())), selectedIndex: menu.selectedIndex, query: menu.query })) : menu.kind === 'custom-theme-token' ? (_jsx(CustomThemeEditor, { theme: menu.theme, token: menu.token, width: width, screenReader: axScreenReader, value: input })) : menu.kind === 'custom-theme-delete' ? (_jsxs(DialogFrame, { title: "Delete custom theme", screenReader: axScreenReader, children: [_jsxs(Text, { children: ["Delete ", menu.theme.name, " permanently?"] }), _jsx(Text, { dimColor: true, children: "Enter to confirm \u00B7 Esc to cancel" })] })) : menu.kind === 'export' ? (_jsx(SelectionMenu, { title: "Export conversation", description: "Select export method", options: [
4681
+ {
4682
+ label: 'Copy to clipboard',
4683
+ description: 'Copy the conversation to your system clipboard',
4684
+ },
4685
+ {
4686
+ label: 'Save to file',
4687
+ description: 'Save the conversation to a file in the current directory',
4688
+ },
4689
+ ], selectedIndex: menu.selectedIndex, footer: "Esc to cancel", width: width, screenReader: axScreenReader })) : null) : (_jsxs(_Fragment, { children: [commandPaletteVisible ? (_jsx(CommandPalette, { commands: matchingSlashCommands, selectedIndex: selectedSlashCommandIndex, width: width, screenReader: axScreenReader })) : null, filePickerVisible ? (_jsx(MentionPicker, { entries: matchingMentionEntries, selectedIndex: selectedFileIndex, width: width, screenReader: axScreenReader })) : null, exitConfirmation ? (_jsx(Text, { color: activePalette.warning, children: "Press Ctrl-C again to exit" })) : null, _jsx(Composer, { input: shellMode ? input.slice(1) : input, cursor: shellMode ? Math.max(0, inputCursor - 1) : inputCursor, shellMode: shellMode, busy: busy, clipboardBusy: clipboardBusy, status: status, display: runtimeDisplay, ...(usage === undefined ? {} : { usage }), ...(costUsd === undefined ? {} : { costUsd }), width: width, screenReader: axScreenReader, hasThinking: hasDetailedTranscript, thinkingExpanded: thinkingExpanded, reduceMotion: runtimeSettings.reduceMotion, progressBar: runtimeSettings.progressBar, ...(runtimeSettings.turnDuration
4690
+ ? (() => {
4691
+ const duration = formatTurnDuration(turnDuration);
4692
+ return duration === undefined
4693
+ ? {}
4694
+ : { turnDuration: duration };
4695
+ })()
4696
+ : {}), editorMode: runtimeSettings.editor, ...(runtimeSettings.prStatus &&
4697
+ activeSessionSummary?.prNumber
4698
+ ? { prStatus: `PR #${activeSessionSummary.prNumber}` }
4699
+ : {}), shortcutsVisible: shortcutsVisible, ...(editorFooterMessage === undefined
4700
+ ? {}
4701
+ : { footerMessage: editorFooterMessage }) })] }))] })) }) }));
3650
4702
  }
3651
4703
  export async function runInteractive(options) {
3652
4704
  const controller = new AbortController();
3653
4705
  const signal = options.signal
3654
4706
  ? AbortSignal.any([options.signal, controller.signal])
3655
4707
  : controller.signal;
3656
- const listing = await options.factory.createService({
3657
- eventSink: () => undefined,
3658
- requireProvider: false,
3659
- signal,
3660
- });
3661
- let initialSessions;
3662
- let initialSlashCommands;
3663
- let initialAgents;
4708
+ let currentResume = options.resume;
4709
+ let currentInitialPrompt = options.initialPrompt;
4710
+ let currentRuntimeSettings = options.runtimeSettings ?? (await loadRuntimeSettings());
4711
+ let currentRenderer = currentRuntimeSettings.tui;
4712
+ let rendererChange = null;
4713
+ let rendererNotice;
4714
+ const readRendererChange = () => rendererChange;
4715
+ let initialThemeSettings = DEFAULT_TUI_THEME_SETTINGS;
4716
+ let initialThemeLoadError;
3664
4717
  try {
3665
- const sessions = await listing.sessions();
3666
- initialSessions = options.sessionFilter
3667
- ? sessions.filter(options.sessionFilter)
3668
- : sessions;
3669
- initialSlashCommands = listing.slashCommands?.() ?? [];
3670
- initialAgents = listing.agentDefinitions?.() ?? [];
3671
- if (options.requireSession && initialSessions.length === 0) {
3672
- throw new Error(options.missingSessionMessage ??
3673
- 'No conversation linked to a pull request in this project');
3674
- }
4718
+ initialThemeSettings = await loadTuiThemeSettings();
3675
4719
  }
3676
4720
  catch (error) {
4721
+ initialThemeLoadError = `Unable to load theme settings: ${error instanceof Error ? error.message : String(error)}`;
4722
+ }
4723
+ while (true) {
4724
+ const listing = await options.factory.createService({
4725
+ eventSink: () => undefined,
4726
+ requireProvider: false,
4727
+ signal,
4728
+ });
4729
+ let initialSessions;
4730
+ let initialSlashCommands;
4731
+ let initialAgents;
3677
4732
  try {
3678
- await listing.close?.();
4733
+ const sessions = await listing.sessions();
4734
+ initialSessions = options.sessionFilter
4735
+ ? sessions.filter(options.sessionFilter)
4736
+ : sessions;
4737
+ initialSlashCommands = listing.slashCommands?.() ?? [];
4738
+ initialAgents = listing.agentDefinitions?.() ?? [];
4739
+ if (options.requireSession && initialSessions.length === 0) {
4740
+ throw new Error(options.missingSessionMessage ??
4741
+ 'No conversation linked to a pull request in this project');
4742
+ }
3679
4743
  }
3680
- catch {
3681
- // Preserve the session-listing failure as the primary error.
4744
+ catch (error) {
4745
+ try {
4746
+ await listing.close?.();
4747
+ }
4748
+ catch {
4749
+ // Preserve the session-listing failure as the primary error.
4750
+ }
4751
+ throw error;
3682
4752
  }
3683
- throw error;
3684
- }
3685
- const canonicalResumeSession = options.resume?.sessionId === undefined
3686
- ? undefined
3687
- : initialSessions.find((session) => session.sessionId.toLowerCase() ===
3688
- options.resume?.sessionId?.toLowerCase());
3689
- const resume = canonicalResumeSession
3690
- ? { ...options.resume, sessionId: canonicalResumeSession.sessionId }
3691
- : options.resume;
3692
- let initialHistory = [];
3693
- try {
3694
- initialHistory =
3695
- resume?.sessionId === undefined || listing.transcript === undefined
3696
- ? []
3697
- : await listing.transcript(resume.sessionId);
3698
- }
3699
- catch (error) {
4753
+ const canonicalResumeSession = currentResume?.sessionId === undefined
4754
+ ? undefined
4755
+ : initialSessions.find((session) => session.sessionId.toLowerCase() ===
4756
+ currentResume?.sessionId?.toLowerCase());
4757
+ const resume = canonicalResumeSession
4758
+ ? { ...currentResume, sessionId: canonicalResumeSession.sessionId }
4759
+ : currentResume;
4760
+ let initialHistory = [];
3700
4761
  try {
3701
- await listing.close?.();
4762
+ initialHistory =
4763
+ resume?.sessionId === undefined || listing.transcript === undefined
4764
+ ? []
4765
+ : await listing.transcript(resume.sessionId);
3702
4766
  }
3703
- catch {
3704
- // Preserve the transcript-loading failure as the primary error.
4767
+ catch (error) {
4768
+ try {
4769
+ await listing.close?.();
4770
+ }
4771
+ catch {
4772
+ // Preserve the transcript-loading failure as the primary error.
4773
+ }
4774
+ throw error;
3705
4775
  }
3706
- throw error;
4776
+ await listing.close?.();
4777
+ if (signal.aborted)
4778
+ break;
4779
+ const history = rendererNotice === undefined
4780
+ ? initialHistory
4781
+ : [
4782
+ ...initialHistory,
4783
+ { kind: 'local-result', text: rendererNotice },
4784
+ ];
4785
+ rendererNotice = undefined;
4786
+ let activeTurn = null;
4787
+ let cleanup = null;
4788
+ let backgrounded;
4789
+ rendererChange = null;
4790
+ const instance = render(_jsx(InteractiveApp, { factory: options.factory, initialSessions: initialSessions, slashCommands: initialSlashCommands, agents: initialAgents, initialHistory: history, runtimeSettings: currentRuntimeSettings, initialThemeSettings: initialThemeSettings, ...(initialThemeLoadError === undefined
4791
+ ? {}
4792
+ : { initialThemeLoadError }), ...(currentInitialPrompt === undefined
4793
+ ? {}
4794
+ : { initialPrompt: currentInitialPrompt }), signal: signal, onCancel: () => controller.abort(), onTurnChange: (turn) => {
4795
+ activeTurn = turn;
4796
+ }, onCleanup: (closing) => {
4797
+ cleanup = closing;
4798
+ }, onRendererChange: (mode, sessionId) => {
4799
+ rendererChange = { mode, sessionId };
4800
+ currentRenderer = mode;
4801
+ currentRuntimeSettings = { ...currentRuntimeSettings, tui: mode };
4802
+ }, ...(options.onBackground === undefined
4803
+ ? {}
4804
+ : {
4805
+ onBackground: async (request) => {
4806
+ const result = await options.onBackground?.(request);
4807
+ if (!result)
4808
+ throw new Error('Background launch returned no job');
4809
+ backgrounded = result;
4810
+ return result;
4811
+ },
4812
+ }), allowNewSession: !options.requireSession, ...(options.additionalDirectories === undefined
4813
+ ? {}
4814
+ : { additionalDirectories: options.additionalDirectories }), ...(resume === undefined ? {} : { resume }), ...(options.display === undefined ? {} : { display: options.display }), ...(options.axScreenReader ? { axScreenReader: true } : {}), ...(options.allowDangerouslySkipPermissions
4815
+ ? { allowDangerouslySkipPermissions: true }
4816
+ : {}) }), {
4817
+ exitOnCtrlC: false,
4818
+ incrementalRendering: !options.axScreenReader,
4819
+ interactive: true,
4820
+ alternateScreen: currentRenderer === 'fullscreen',
4821
+ });
4822
+ await instance.waitUntilExit();
4823
+ if (activeTurn)
4824
+ await activeTurn;
4825
+ if (cleanup)
4826
+ await cleanup;
4827
+ if (backgrounded)
4828
+ options.onBackgrounded?.(backgrounded);
4829
+ const change = readRendererChange();
4830
+ if (!change || signal.aborted)
4831
+ break;
4832
+ currentInitialPrompt = undefined;
4833
+ currentRenderer = change.mode;
4834
+ rendererNotice =
4835
+ change.mode === 'fullscreen'
4836
+ ? 'Using flicker-free rendering'
4837
+ : 'Switched back to the classic renderer';
4838
+ currentResume = change.sessionId
4839
+ ? { sessionId: change.sessionId }
4840
+ : undefined;
3707
4841
  }
3708
- await listing.close?.();
3709
- let activeTurn = null;
3710
- let cleanup = null;
3711
- let backgrounded;
3712
- const instance = render(_jsx(InteractiveApp, { factory: options.factory, initialSessions: initialSessions, slashCommands: initialSlashCommands, agents: initialAgents, initialHistory: initialHistory, ...(options.initialPrompt === undefined
3713
- ? {}
3714
- : { initialPrompt: options.initialPrompt }), signal: signal, onCancel: () => controller.abort(), onTurnChange: (turn) => {
3715
- activeTurn = turn;
3716
- }, onCleanup: (closing) => {
3717
- cleanup = closing;
3718
- }, ...(options.onBackground === undefined
3719
- ? {}
3720
- : {
3721
- onBackground: async (request) => {
3722
- const result = await options.onBackground?.(request);
3723
- if (!result)
3724
- throw new Error('Background launch returned no job');
3725
- backgrounded = result;
3726
- return result;
3727
- },
3728
- }), allowNewSession: !options.requireSession, ...(options.additionalDirectories === undefined
3729
- ? {}
3730
- : { additionalDirectories: options.additionalDirectories }), ...(resume === undefined ? {} : { resume }), ...(options.display === undefined ? {} : { display: options.display }), ...(options.axScreenReader ? { axScreenReader: true } : {}), ...(options.allowDangerouslySkipPermissions
3731
- ? { allowDangerouslySkipPermissions: true }
3732
- : {}) }), {
3733
- exitOnCtrlC: false,
3734
- incrementalRendering: !options.axScreenReader,
3735
- interactive: true,
3736
- });
3737
- await instance.waitUntilExit();
3738
- if (activeTurn)
3739
- await activeTurn;
3740
- if (cleanup)
3741
- await cleanup;
3742
- if (backgrounded)
3743
- options.onBackgrounded?.(backgrounded);
3744
4842
  return signal.aborted ? 130 : 0;
3745
4843
  }
3746
4844
  //# sourceMappingURL=interactive.js.map