@mseep/claudian 2.0.25

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 (687) hide show
  1. package/.env.local.example +2 -0
  2. package/.github/workflows/ci.yml +59 -0
  3. package/.github/workflows/claude-code-review.yml +57 -0
  4. package/.github/workflows/claude.yml +50 -0
  5. package/.github/workflows/duplicate-issues.yml +22 -0
  6. package/.github/workflows/release.yml +73 -0
  7. package/.github/workflows/stale.yml +21 -0
  8. package/.node-version +1 -0
  9. package/AGENTS.md +3 -0
  10. package/CLAUDE.md +80 -0
  11. package/LICENSE +21 -0
  12. package/README.md +190 -0
  13. package/assets/Preview.png +0 -0
  14. package/assets/sponsors/MOMA.png +0 -0
  15. package/bun.lock +1618 -0
  16. package/esbuild.config.mjs +195 -0
  17. package/eslint.config.mjs +143 -0
  18. package/jest.config.js +41 -0
  19. package/main.js +104915 -0
  20. package/manifest.json +10 -0
  21. package/package.json +65 -0
  22. package/scripts/build-css.mjs +119 -0
  23. package/scripts/build.mjs +19 -0
  24. package/scripts/postinstall.mjs +25 -0
  25. package/scripts/rendererSafeUnref.js +205 -0
  26. package/scripts/run-jest.js +19 -0
  27. package/scripts/sync-version.js +16 -0
  28. package/src/app/settings/ClaudianSettingsStorage.ts +435 -0
  29. package/src/app/settings/defaultSettings.ts +54 -0
  30. package/src/app/storage/SharedStorageService.ts +106 -0
  31. package/src/core/CLAUDE.md +84 -0
  32. package/src/core/auxiliary/AuxQueryRunner.ts +11 -0
  33. package/src/core/auxiliary/QueryBackedInlineEditService.ts +73 -0
  34. package/src/core/auxiliary/QueryBackedInstructionRefineService.ts +78 -0
  35. package/src/core/auxiliary/QueryBackedTitleGenerationService.ts +90 -0
  36. package/src/core/bootstrap/SessionStorage.ts +179 -0
  37. package/src/core/bootstrap/StoragePaths.ts +7 -0
  38. package/src/core/bootstrap/storage.ts +20 -0
  39. package/src/core/commands/builtInCommands.ts +141 -0
  40. package/src/core/mcp/McpConfigParser.ts +102 -0
  41. package/src/core/mcp/McpServerManager.ts +119 -0
  42. package/src/core/mcp/McpTester.ts +310 -0
  43. package/src/core/prompt/inlineEdit.ts +252 -0
  44. package/src/core/prompt/instructionRefine.ts +72 -0
  45. package/src/core/prompt/mainAgent.ts +213 -0
  46. package/src/core/prompt/titleGeneration.ts +44 -0
  47. package/src/core/providers/ProviderRegistry.ts +253 -0
  48. package/src/core/providers/ProviderSettingsCoordinator.ts +434 -0
  49. package/src/core/providers/ProviderWorkspaceRegistry.ts +119 -0
  50. package/src/core/providers/commands/ProviderCommandCatalog.ts +21 -0
  51. package/src/core/providers/commands/ProviderCommandEntry.ts +33 -0
  52. package/src/core/providers/commands/hiddenCommands.ts +74 -0
  53. package/src/core/providers/modelRouting.ts +17 -0
  54. package/src/core/providers/modelSelection.ts +72 -0
  55. package/src/core/providers/providerConfig.ts +34 -0
  56. package/src/core/providers/providerEnvironment.ts +364 -0
  57. package/src/core/providers/types.ts +544 -0
  58. package/src/core/runtime/ChatRuntime.ts +66 -0
  59. package/src/core/runtime/QueuedTurn.ts +97 -0
  60. package/src/core/runtime/types.ts +118 -0
  61. package/src/core/security/ApprovalManager.ts +142 -0
  62. package/src/core/storage/HomeFileAdapter.ts +75 -0
  63. package/src/core/storage/VaultFileAdapter.ts +132 -0
  64. package/src/core/tools/todo.ts +65 -0
  65. package/src/core/tools/toolIcons.ts +80 -0
  66. package/src/core/tools/toolInput.ts +119 -0
  67. package/src/core/tools/toolNames.ts +149 -0
  68. package/src/core/tools/toolResultContent.ts +26 -0
  69. package/src/core/types/agent.ts +28 -0
  70. package/src/core/types/chat.ts +177 -0
  71. package/src/core/types/diff.ts +31 -0
  72. package/src/core/types/index.ts +78 -0
  73. package/src/core/types/mcp.ts +97 -0
  74. package/src/core/types/plugins.ts +9 -0
  75. package/src/core/types/provider.ts +1 -0
  76. package/src/core/types/settings.ts +152 -0
  77. package/src/core/types/tools.ts +80 -0
  78. package/src/features/chat/CLAUDE.md +136 -0
  79. package/src/features/chat/ClaudianView.ts +762 -0
  80. package/src/features/chat/constants.ts +114 -0
  81. package/src/features/chat/controllers/BrowserSelectionController.ts +295 -0
  82. package/src/features/chat/controllers/CanvasSelectionController.ts +142 -0
  83. package/src/features/chat/controllers/ConversationController.ts +1103 -0
  84. package/src/features/chat/controllers/InputController.ts +1707 -0
  85. package/src/features/chat/controllers/NavigationController.ts +209 -0
  86. package/src/features/chat/controllers/SelectionController.ts +430 -0
  87. package/src/features/chat/controllers/StreamController.ts +1560 -0
  88. package/src/features/chat/controllers/contextRowVisibility.ts +18 -0
  89. package/src/features/chat/rendering/DiffRenderer.ts +134 -0
  90. package/src/features/chat/rendering/InlineAskUserQuestion.ts +702 -0
  91. package/src/features/chat/rendering/InlineExitPlanMode.ts +263 -0
  92. package/src/features/chat/rendering/InlinePlanApproval.ts +183 -0
  93. package/src/features/chat/rendering/MessageRenderer.ts +907 -0
  94. package/src/features/chat/rendering/SubagentRenderer.ts +678 -0
  95. package/src/features/chat/rendering/ThinkingBlockRenderer.ts +126 -0
  96. package/src/features/chat/rendering/TodoListRenderer.ts +5 -0
  97. package/src/features/chat/rendering/ToolCallRenderer.ts +1161 -0
  98. package/src/features/chat/rendering/WriteEditRenderer.ts +232 -0
  99. package/src/features/chat/rendering/collapsible.ts +101 -0
  100. package/src/features/chat/rendering/subagentLifecycleResolution.ts +25 -0
  101. package/src/features/chat/rendering/todoUtils.ts +29 -0
  102. package/src/features/chat/rewind.ts +31 -0
  103. package/src/features/chat/services/BangBashService.ts +56 -0
  104. package/src/features/chat/services/SubagentManager.ts +1107 -0
  105. package/src/features/chat/state/ChatState.ts +436 -0
  106. package/src/features/chat/state/types.ts +138 -0
  107. package/src/features/chat/tabs/Tab.ts +1886 -0
  108. package/src/features/chat/tabs/TabBar.ts +179 -0
  109. package/src/features/chat/tabs/TabManager.ts +1021 -0
  110. package/src/features/chat/tabs/providerResolution.ts +34 -0
  111. package/src/features/chat/tabs/types.ts +287 -0
  112. package/src/features/chat/ui/BangBashModeManager.ts +121 -0
  113. package/src/features/chat/ui/FileContext.ts +385 -0
  114. package/src/features/chat/ui/ImageContext.ts +366 -0
  115. package/src/features/chat/ui/InputToolbar.ts +1244 -0
  116. package/src/features/chat/ui/InstructionModeManager.ts +158 -0
  117. package/src/features/chat/ui/NavigationSidebar.ts +126 -0
  118. package/src/features/chat/ui/StatusPanel.ts +589 -0
  119. package/src/features/chat/ui/file-context/state/FileContextState.ts +83 -0
  120. package/src/features/chat/ui/file-context/view/FileChipsView.ts +70 -0
  121. package/src/features/chat/ui/textareaResize.ts +47 -0
  122. package/src/features/chat/utils/usageInfo.ts +26 -0
  123. package/src/features/inline-edit/ui/InlineEditModal.ts +895 -0
  124. package/src/features/inline-edit/ui/inlineEditMarkdownPreview.ts +55 -0
  125. package/src/features/settings/ClaudianSettings.ts +672 -0
  126. package/src/features/settings/keyboardNavigation.ts +60 -0
  127. package/src/features/settings/ui/EnvSnippetManager.ts +430 -0
  128. package/src/features/settings/ui/EnvironmentSettingsSection.ts +85 -0
  129. package/src/features/settings/ui/McpServerModal.ts +335 -0
  130. package/src/features/settings/ui/McpSettingsManager.ts +400 -0
  131. package/src/features/settings/ui/McpTestModal.ts +346 -0
  132. package/src/i18n/constants.ts +58 -0
  133. package/src/i18n/i18n.ts +140 -0
  134. package/src/i18n/locales/de.json +322 -0
  135. package/src/i18n/locales/en.json +322 -0
  136. package/src/i18n/locales/es.json +322 -0
  137. package/src/i18n/locales/fr.json +322 -0
  138. package/src/i18n/locales/ja.json +322 -0
  139. package/src/i18n/locales/ko.json +322 -0
  140. package/src/i18n/locales/pt.json +322 -0
  141. package/src/i18n/locales/ru.json +322 -0
  142. package/src/i18n/locales/zh-CN.json +322 -0
  143. package/src/i18n/locales/zh-TW.json +322 -0
  144. package/src/i18n/types.ts +248 -0
  145. package/src/main.ts +772 -0
  146. package/src/providers/acp/AcpClientConnection.ts +361 -0
  147. package/src/providers/acp/AcpJsonRpcTransport.ts +427 -0
  148. package/src/providers/acp/AcpSessionConfig.ts +139 -0
  149. package/src/providers/acp/AcpSessionUpdateNormalizer.ts +371 -0
  150. package/src/providers/acp/AcpSubprocess.ts +155 -0
  151. package/src/providers/acp/AcpToolStreamAdapter.ts +132 -0
  152. package/src/providers/acp/buildAcpUsageInfo.ts +41 -0
  153. package/src/providers/acp/index.ts +9 -0
  154. package/src/providers/acp/methodNames.ts +50 -0
  155. package/src/providers/acp/types.ts +566 -0
  156. package/src/providers/claude/CLAUDE.md +78 -0
  157. package/src/providers/claude/agents/AgentManager.ts +225 -0
  158. package/src/providers/claude/agents/AgentStorage.ts +101 -0
  159. package/src/providers/claude/app/ClaudeWorkspaceServices.ts +90 -0
  160. package/src/providers/claude/auxiliary/ClaudeInlineEditService.ts +122 -0
  161. package/src/providers/claude/auxiliary/ClaudeInstructionRefineService.ts +90 -0
  162. package/src/providers/claude/auxiliary/ClaudeTitleGenerationService.ts +129 -0
  163. package/src/providers/claude/auxiliary/extractAssistantText.ts +28 -0
  164. package/src/providers/claude/capabilities.ts +17 -0
  165. package/src/providers/claude/cli/findClaudeCLIPath.ts +261 -0
  166. package/src/providers/claude/commands/ClaudeCommandCatalog.ts +149 -0
  167. package/src/providers/claude/commands/probeRuntimeCommands.ts +86 -0
  168. package/src/providers/claude/env/ClaudeSettingsReconciler.ts +90 -0
  169. package/src/providers/claude/env/claudeModelEnv.ts +86 -0
  170. package/src/providers/claude/history/ClaudeConversationHistoryService.ts +446 -0
  171. package/src/providers/claude/history/ClaudeHistoryStore.ts +170 -0
  172. package/src/providers/claude/history/sdkAsyncSubagent.ts +92 -0
  173. package/src/providers/claude/history/sdkBranchFilter.ts +271 -0
  174. package/src/providers/claude/history/sdkHistoryTypes.ts +61 -0
  175. package/src/providers/claude/history/sdkMessageParsing.ts +413 -0
  176. package/src/providers/claude/history/sdkSessionPaths.ts +98 -0
  177. package/src/providers/claude/history/sdkSubagentSidecar.ts +261 -0
  178. package/src/providers/claude/hooks/SubagentHooks.ts +31 -0
  179. package/src/providers/claude/modelLabels.ts +77 -0
  180. package/src/providers/claude/modelOptions.ts +113 -0
  181. package/src/providers/claude/modelSelection.ts +17 -0
  182. package/src/providers/claude/plugins/PluginManager.ts +194 -0
  183. package/src/providers/claude/prompt/ClaudeTurnEncoder.ts +46 -0
  184. package/src/providers/claude/registration.ts +39 -0
  185. package/src/providers/claude/runtime/ClaudeApprovalHandler.ts +153 -0
  186. package/src/providers/claude/runtime/ClaudeChatRuntime.ts +1812 -0
  187. package/src/providers/claude/runtime/ClaudeCliResolver.ts +94 -0
  188. package/src/providers/claude/runtime/ClaudeDynamicUpdates.ts +164 -0
  189. package/src/providers/claude/runtime/ClaudeMessageChannel.ts +209 -0
  190. package/src/providers/claude/runtime/ClaudeQueryOptionsBuilder.ts +315 -0
  191. package/src/providers/claude/runtime/ClaudeRewindService.ts +220 -0
  192. package/src/providers/claude/runtime/ClaudeSessionManager.ts +92 -0
  193. package/src/providers/claude/runtime/ClaudeTaskResultInterpreter.ts +172 -0
  194. package/src/providers/claude/runtime/ClaudeUserMessageFactory.ts +83 -0
  195. package/src/providers/claude/runtime/claudeColdStartQuery.ts +152 -0
  196. package/src/providers/claude/runtime/customSpawn.ts +87 -0
  197. package/src/providers/claude/runtime/types.ts +134 -0
  198. package/src/providers/claude/sdk/messages.ts +17 -0
  199. package/src/providers/claude/sdk/toolResultContent.ts +4 -0
  200. package/src/providers/claude/sdk/typeGuards.ts +14 -0
  201. package/src/providers/claude/sdk/types.ts +15 -0
  202. package/src/providers/claude/security/ClaudePermissionUpdates.ts +44 -0
  203. package/src/providers/claude/settings.ts +138 -0
  204. package/src/providers/claude/storage/AgentVaultStorage.ts +101 -0
  205. package/src/providers/claude/storage/CCSettingsStorage.ts +153 -0
  206. package/src/providers/claude/storage/ClaudianSettingsStorage.ts +6 -0
  207. package/src/providers/claude/storage/McpStorage.ts +139 -0
  208. package/src/providers/claude/storage/SessionStorage.ts +5 -0
  209. package/src/providers/claude/storage/SkillStorage.ts +61 -0
  210. package/src/providers/claude/storage/SlashCommandStorage.ts +96 -0
  211. package/src/providers/claude/storage/StorageService.ts +185 -0
  212. package/src/providers/claude/stream/toolInputStreamState.ts +318 -0
  213. package/src/providers/claude/stream/transformClaudeMessage.ts +586 -0
  214. package/src/providers/claude/types/agent.ts +2 -0
  215. package/src/providers/claude/types/models.ts +168 -0
  216. package/src/providers/claude/types/plugins.ts +14 -0
  217. package/src/providers/claude/types/providerState.ts +16 -0
  218. package/src/providers/claude/types/settings.ts +98 -0
  219. package/src/providers/claude/ui/AgentSettings.ts +389 -0
  220. package/src/providers/claude/ui/ClaudeChatUIConfig.ts +113 -0
  221. package/src/providers/claude/ui/ClaudeSettingsTab.ts +407 -0
  222. package/src/providers/claude/ui/PluginSettingsManager.ts +149 -0
  223. package/src/providers/claude/ui/SlashCommandSettings.ts +527 -0
  224. package/src/providers/codex/CLAUDE.md +64 -0
  225. package/src/providers/codex/agents/CodexAgentMentionProvider.ts +33 -0
  226. package/src/providers/codex/app/CodexWorkspaceServices.ts +76 -0
  227. package/src/providers/codex/auxiliary/CodexInlineEditService.ts +9 -0
  228. package/src/providers/codex/auxiliary/CodexInstructionRefineService.ts +9 -0
  229. package/src/providers/codex/auxiliary/CodexTaskResultInterpreter.ts +29 -0
  230. package/src/providers/codex/auxiliary/CodexTitleGenerationService.ts +22 -0
  231. package/src/providers/codex/capabilities.ts +16 -0
  232. package/src/providers/codex/commands/CodexSkillCatalog.ts +180 -0
  233. package/src/providers/codex/env/CodexSettingsReconciler.ts +68 -0
  234. package/src/providers/codex/history/CodexConversationHistoryService.ts +212 -0
  235. package/src/providers/codex/history/CodexHistoryStore.ts +1672 -0
  236. package/src/providers/codex/modelOptions.ts +99 -0
  237. package/src/providers/codex/modelSelection.ts +17 -0
  238. package/src/providers/codex/normalization/codexSubagentNormalization.ts +227 -0
  239. package/src/providers/codex/normalization/codexToolNormalization.ts +390 -0
  240. package/src/providers/codex/prompt/encodeCodexTurn.ts +57 -0
  241. package/src/providers/codex/registration.ts +29 -0
  242. package/src/providers/codex/runtime/CodexAppServerProcess.ts +105 -0
  243. package/src/providers/codex/runtime/CodexAuxQueryRunner.ts +180 -0
  244. package/src/providers/codex/runtime/CodexBinaryLocator.ts +49 -0
  245. package/src/providers/codex/runtime/CodexChatRuntime.ts +1296 -0
  246. package/src/providers/codex/runtime/CodexCliResolver.ts +65 -0
  247. package/src/providers/codex/runtime/CodexExecutionTargetResolver.ts +104 -0
  248. package/src/providers/codex/runtime/CodexLaunchSpecBuilder.ts +85 -0
  249. package/src/providers/codex/runtime/CodexNotificationRouter.ts +1033 -0
  250. package/src/providers/codex/runtime/CodexPathMapper.ts +155 -0
  251. package/src/providers/codex/runtime/CodexRpcTransport.ts +171 -0
  252. package/src/providers/codex/runtime/CodexRuntimeContext.ts +109 -0
  253. package/src/providers/codex/runtime/CodexServerRequestRouter.ts +331 -0
  254. package/src/providers/codex/runtime/CodexSessionFileTail.ts +792 -0
  255. package/src/providers/codex/runtime/CodexSessionManager.ts +39 -0
  256. package/src/providers/codex/runtime/codexAppServerSupport.ts +58 -0
  257. package/src/providers/codex/runtime/codexAppServerTypes.ts +705 -0
  258. package/src/providers/codex/runtime/codexLaunchTypes.ts +30 -0
  259. package/src/providers/codex/settings.ts +236 -0
  260. package/src/providers/codex/skills/CodexSkillListingService.ts +173 -0
  261. package/src/providers/codex/storage/CodexSkillStorage.ts +250 -0
  262. package/src/providers/codex/storage/CodexSubagentStorage.ts +212 -0
  263. package/src/providers/codex/types/index.ts +16 -0
  264. package/src/providers/codex/types/models.ts +46 -0
  265. package/src/providers/codex/types/subagent.ts +23 -0
  266. package/src/providers/codex/ui/CodexChatUIConfig.ts +128 -0
  267. package/src/providers/codex/ui/CodexSettingsTab.ts +432 -0
  268. package/src/providers/codex/ui/CodexSkillSettings.ts +275 -0
  269. package/src/providers/codex/ui/CodexSubagentSettings.ts +400 -0
  270. package/src/providers/defaultProviderConfigs.ts +14 -0
  271. package/src/providers/index.ts +30 -0
  272. package/src/providers/opencode/agents/OpencodeAgentMentionProvider.ts +42 -0
  273. package/src/providers/opencode/app/OpencodeRuntimeCommandLoader.ts +67 -0
  274. package/src/providers/opencode/app/OpencodeWorkspaceServices.ts +55 -0
  275. package/src/providers/opencode/auxiliary/OpencodeInlineEditService.ts +13 -0
  276. package/src/providers/opencode/auxiliary/OpencodeInstructionRefineService.ts +12 -0
  277. package/src/providers/opencode/auxiliary/OpencodeTaskResultInterpreter.ts +29 -0
  278. package/src/providers/opencode/auxiliary/OpencodeTitleGenerationService.ts +27 -0
  279. package/src/providers/opencode/capabilities.ts +16 -0
  280. package/src/providers/opencode/commands/OpencodeCommandCatalog.ts +92 -0
  281. package/src/providers/opencode/discoveryState.ts +135 -0
  282. package/src/providers/opencode/env/OpencodeSettingsReconciler.ts +178 -0
  283. package/src/providers/opencode/history/OpencodeConversationHistoryService.ts +84 -0
  284. package/src/providers/opencode/history/OpencodeHistoryStore.ts +472 -0
  285. package/src/providers/opencode/history/OpencodeSqliteReader.ts +284 -0
  286. package/src/providers/opencode/internal/compareCollections.ts +72 -0
  287. package/src/providers/opencode/internal/providerProjection.ts +15 -0
  288. package/src/providers/opencode/models.ts +378 -0
  289. package/src/providers/opencode/modes.ts +150 -0
  290. package/src/providers/opencode/normalization/opencodeToolNormalization.ts +406 -0
  291. package/src/providers/opencode/registration.ts +27 -0
  292. package/src/providers/opencode/runtime/OpencodeAuxQueryRunner.ts +436 -0
  293. package/src/providers/opencode/runtime/OpencodeChatRuntime.ts +1603 -0
  294. package/src/providers/opencode/runtime/OpencodeCliResolver.ts +57 -0
  295. package/src/providers/opencode/runtime/OpencodeLaunchArtifacts.ts +231 -0
  296. package/src/providers/opencode/runtime/OpencodePaths.ts +113 -0
  297. package/src/providers/opencode/runtime/OpencodeRuntimeEnvironment.ts +18 -0
  298. package/src/providers/opencode/runtime/buildOpencodePrompt.ts +66 -0
  299. package/src/providers/opencode/settings.ts +427 -0
  300. package/src/providers/opencode/storage/OpencodeAgentStorage.ts +346 -0
  301. package/src/providers/opencode/types/agent.ts +37 -0
  302. package/src/providers/opencode/types/index.ts +9 -0
  303. package/src/providers/opencode/ui/OpencodeAgentSettings.ts +579 -0
  304. package/src/providers/opencode/ui/OpencodeChatUIConfig.ts +316 -0
  305. package/src/providers/opencode/ui/OpencodeSettingsTab.ts +674 -0
  306. package/src/providers/pi/app/PiRuntimeCommandLoader.ts +57 -0
  307. package/src/providers/pi/app/PiWorkspaceServices.ts +39 -0
  308. package/src/providers/pi/auxiliary/PiInlineEditService.ts +9 -0
  309. package/src/providers/pi/auxiliary/PiInstructionRefineService.ts +9 -0
  310. package/src/providers/pi/auxiliary/PiTaskResultInterpreter.ts +29 -0
  311. package/src/providers/pi/auxiliary/PiTitleGenerationService.ts +19 -0
  312. package/src/providers/pi/capabilities.ts +16 -0
  313. package/src/providers/pi/commands/PiCommandCatalog.ts +92 -0
  314. package/src/providers/pi/env/PiSettingsReconciler.ts +180 -0
  315. package/src/providers/pi/history/PiConversationHistoryService.ts +123 -0
  316. package/src/providers/pi/history/PiHistoryStore.ts +664 -0
  317. package/src/providers/pi/internal/compareCollections.ts +4 -0
  318. package/src/providers/pi/internal/providerProjection.ts +18 -0
  319. package/src/providers/pi/models.ts +302 -0
  320. package/src/providers/pi/normalizations/piEventNormalization.ts +211 -0
  321. package/src/providers/pi/normalizations/piToolNormalization.ts +97 -0
  322. package/src/providers/pi/registration.ts +30 -0
  323. package/src/providers/pi/runtime/PiAuxQueryRunner.ts +216 -0
  324. package/src/providers/pi/runtime/PiChatRuntime.ts +1064 -0
  325. package/src/providers/pi/runtime/PiCliResolver.ts +53 -0
  326. package/src/providers/pi/runtime/PiExtensionUiBridge.ts +161 -0
  327. package/src/providers/pi/runtime/PiJsonl.ts +71 -0
  328. package/src/providers/pi/runtime/PiLaunchSpec.ts +70 -0
  329. package/src/providers/pi/runtime/PiModelDiscoveryService.ts +92 -0
  330. package/src/providers/pi/runtime/PiRpcPayloads.ts +18 -0
  331. package/src/providers/pi/runtime/PiRpcTransport.ts +243 -0
  332. package/src/providers/pi/runtime/PiSubprocess.ts +159 -0
  333. package/src/providers/pi/runtime/buildPiPrompt.ts +62 -0
  334. package/src/providers/pi/runtime/buildPiUsageInfo.ts +69 -0
  335. package/src/providers/pi/settings.ts +468 -0
  336. package/src/providers/pi/types.ts +64 -0
  337. package/src/providers/pi/ui/ObsidianPiExtensionUiRenderer.ts +251 -0
  338. package/src/providers/pi/ui/PiChatUIConfig.ts +265 -0
  339. package/src/providers/pi/ui/PiExtensionUiRenderer.ts +12 -0
  340. package/src/providers/pi/ui/PiSettingsTab.ts +642 -0
  341. package/src/shared/components/ResumeSessionDropdown.ts +185 -0
  342. package/src/shared/components/SelectableDropdown.ts +140 -0
  343. package/src/shared/components/SelectionHighlight.ts +77 -0
  344. package/src/shared/components/SlashCommandDropdown.ts +421 -0
  345. package/src/shared/icons.ts +180 -0
  346. package/src/shared/mention/MentionDropdownController.ts +627 -0
  347. package/src/shared/mention/VaultMentionCache.ts +106 -0
  348. package/src/shared/mention/VaultMentionDataProvider.ts +51 -0
  349. package/src/shared/mention/types.ts +67 -0
  350. package/src/shared/modals/ConfirmModal.ts +60 -0
  351. package/src/shared/modals/ForkTargetModal.ts +47 -0
  352. package/src/shared/modals/InstructionConfirmModal.ts +281 -0
  353. package/src/style/CLAUDE.md +49 -0
  354. package/src/style/accessibility.css +40 -0
  355. package/src/style/base/animations.css +44 -0
  356. package/src/style/base/container.css +20 -0
  357. package/src/style/base/variables.css +46 -0
  358. package/src/style/base/visibility.css +15 -0
  359. package/src/style/components/code.css +97 -0
  360. package/src/style/components/context-footer.css +76 -0
  361. package/src/style/components/header.css +27 -0
  362. package/src/style/components/history.css +221 -0
  363. package/src/style/components/input.css +312 -0
  364. package/src/style/components/messages.css +262 -0
  365. package/src/style/components/nav-sidebar.css +58 -0
  366. package/src/style/components/status-panel.css +202 -0
  367. package/src/style/components/subagent.css +248 -0
  368. package/src/style/components/tabs.css +112 -0
  369. package/src/style/components/thinking.css +88 -0
  370. package/src/style/components/toolcalls.css +278 -0
  371. package/src/style/features/ask-user-question.css +315 -0
  372. package/src/style/features/diff.css +197 -0
  373. package/src/style/features/file-context.css +188 -0
  374. package/src/style/features/file-link.css +22 -0
  375. package/src/style/features/image-context.css +179 -0
  376. package/src/style/features/image-embed.css +40 -0
  377. package/src/style/features/image-modal.css +52 -0
  378. package/src/style/features/inline-edit.css +278 -0
  379. package/src/style/features/plan-mode.css +103 -0
  380. package/src/style/features/resume-session.css +119 -0
  381. package/src/style/features/slash-commands.css +91 -0
  382. package/src/style/index.css +63 -0
  383. package/src/style/modals/fork-target.css +21 -0
  384. package/src/style/modals/instruction.css +161 -0
  385. package/src/style/modals/mcp-modal.css +241 -0
  386. package/src/style/settings/agent-settings.css +2 -0
  387. package/src/style/settings/base.css +300 -0
  388. package/src/style/settings/env-snippets.css +366 -0
  389. package/src/style/settings/mcp-settings.css +211 -0
  390. package/src/style/settings/plugin-settings.css +164 -0
  391. package/src/style/settings/provider-model-picker.css +367 -0
  392. package/src/style/settings/slash-settings.css +16 -0
  393. package/src/style/toolbar/external-context.css +177 -0
  394. package/src/style/toolbar/mcp-selector.css +176 -0
  395. package/src/style/toolbar/mode-selector.css +19 -0
  396. package/src/style/toolbar/model-selector.css +99 -0
  397. package/src/style/toolbar/permission-toggle.css +56 -0
  398. package/src/style/toolbar/service-tier-toggle.css +39 -0
  399. package/src/style/toolbar/thinking-selector.css +83 -0
  400. package/src/types/smol-toml.d.ts +4 -0
  401. package/src/utils/agent.ts +50 -0
  402. package/src/utils/animationFrame.ts +46 -0
  403. package/src/utils/browser.ts +46 -0
  404. package/src/utils/canvas.ts +14 -0
  405. package/src/utils/cliBinaryLocator.ts +97 -0
  406. package/src/utils/context.ts +117 -0
  407. package/src/utils/contextMentionResolver.ts +154 -0
  408. package/src/utils/date.ts +31 -0
  409. package/src/utils/diff.ts +384 -0
  410. package/src/utils/editor.ts +104 -0
  411. package/src/utils/electronCompat.ts +53 -0
  412. package/src/utils/env.ts +465 -0
  413. package/src/utils/externalContext.ts +143 -0
  414. package/src/utils/externalContextScanner.ts +135 -0
  415. package/src/utils/fileLink.ts +263 -0
  416. package/src/utils/frontmatter.ts +194 -0
  417. package/src/utils/imageEmbed.ts +139 -0
  418. package/src/utils/inlineEdit.ts +22 -0
  419. package/src/utils/interrupt.ts +23 -0
  420. package/src/utils/markdown.ts +25 -0
  421. package/src/utils/markdownMath.ts +130 -0
  422. package/src/utils/mcp.ts +96 -0
  423. package/src/utils/obsidianCompat.ts +23 -0
  424. package/src/utils/path.ts +342 -0
  425. package/src/utils/session.ts +240 -0
  426. package/src/utils/slashCommand.ts +152 -0
  427. package/src/utils/subagentJsonl.ts +52 -0
  428. package/src/utils/windowsCmdShim.ts +98 -0
  429. package/tests/__mocks__/claude-agent-sdk.ts +317 -0
  430. package/tests/__mocks__/codex-sdk.ts +88 -0
  431. package/tests/__mocks__/obsidian.ts +434 -0
  432. package/tests/helpers/mockElement.ts +403 -0
  433. package/tests/helpers/sdkMessages.ts +291 -0
  434. package/tests/integration/core/agent/ClaudianService.test.ts +1845 -0
  435. package/tests/integration/core/mcp/mcp.test.ts +905 -0
  436. package/tests/integration/features/chat/imagePersistence.test.ts +38 -0
  437. package/tests/integration/main.test.ts +1701 -0
  438. package/tests/setupWindow.ts +26 -0
  439. package/tests/tsconfig.json +7 -0
  440. package/tests/unit/core/commands/builtInCommands.test.ts +239 -0
  441. package/tests/unit/core/mcp/McpServerManager.test.ts +405 -0
  442. package/tests/unit/core/mcp/McpTester.test.ts +282 -0
  443. package/tests/unit/core/mcp/createNodeFetch.test.ts +188 -0
  444. package/tests/unit/core/providers/ProviderRegistry.test.ts +275 -0
  445. package/tests/unit/core/providers/ProviderSettingsCoordinator.test.ts +490 -0
  446. package/tests/unit/core/providers/ProviderWorkspaceRegistry.test.ts +84 -0
  447. package/tests/unit/core/providers/modelRouting.test.ts +91 -0
  448. package/tests/unit/core/providers/modelSelection.test.ts +155 -0
  449. package/tests/unit/core/providers/providerEnvironment.test.ts +162 -0
  450. package/tests/unit/core/providers/tabLifecycle.test.ts +217 -0
  451. package/tests/unit/core/security/ApprovalManager.test.ts +152 -0
  452. package/tests/unit/core/storage/VaultFileAdapter.test.ts +535 -0
  453. package/tests/unit/core/tools/todo.test.ts +227 -0
  454. package/tests/unit/core/tools/toolIcons.test.ts +75 -0
  455. package/tests/unit/core/tools/toolInput.test.ts +350 -0
  456. package/tests/unit/core/tools/toolNames.test.ts +464 -0
  457. package/tests/unit/core/types/mcp.test.ts +115 -0
  458. package/tests/unit/features/chat/ClaudianView.test.ts +404 -0
  459. package/tests/unit/features/chat/controllers/BrowserSelectionController.test.ts +179 -0
  460. package/tests/unit/features/chat/controllers/CanvasSelectionController.test.ts +216 -0
  461. package/tests/unit/features/chat/controllers/ConversationController.test.ts +2764 -0
  462. package/tests/unit/features/chat/controllers/InputController.test.ts +3188 -0
  463. package/tests/unit/features/chat/controllers/NavigationController.test.ts +640 -0
  464. package/tests/unit/features/chat/controllers/SelectionController.test.ts +695 -0
  465. package/tests/unit/features/chat/controllers/StreamController.test.ts +2534 -0
  466. package/tests/unit/features/chat/controllers/contextRowVisibility.test.ts +46 -0
  467. package/tests/unit/features/chat/controllers/index.test.ts +16 -0
  468. package/tests/unit/features/chat/rendering/DiffRenderer.test.ts +355 -0
  469. package/tests/unit/features/chat/rendering/InlineAskUserQuestion.test.ts +1035 -0
  470. package/tests/unit/features/chat/rendering/InlineExitPlanMode.test.ts +191 -0
  471. package/tests/unit/features/chat/rendering/InlinePlanApproval.test.ts +126 -0
  472. package/tests/unit/features/chat/rendering/MessageRenderer.test.ts +2004 -0
  473. package/tests/unit/features/chat/rendering/SubagentRenderer.test.ts +917 -0
  474. package/tests/unit/features/chat/rendering/ThinkingBlockRenderer.test.ts +124 -0
  475. package/tests/unit/features/chat/rendering/TodoListRenderer.test.ts +173 -0
  476. package/tests/unit/features/chat/rendering/ToolCallRenderer.test.ts +909 -0
  477. package/tests/unit/features/chat/rendering/WriteEditRenderer.test.ts +474 -0
  478. package/tests/unit/features/chat/rendering/collapsible.test.ts +158 -0
  479. package/tests/unit/features/chat/rendering/todoUtils.test.ts +105 -0
  480. package/tests/unit/features/chat/rewind.test.ts +56 -0
  481. package/tests/unit/features/chat/services/BangBashService.test.ts +142 -0
  482. package/tests/unit/features/chat/services/InstructionRefineService.test.ts +371 -0
  483. package/tests/unit/features/chat/services/SubagentManager.test.ts +1759 -0
  484. package/tests/unit/features/chat/services/TitleGenerationService.test.ts +480 -0
  485. package/tests/unit/features/chat/state/ChatState.test.ts +581 -0
  486. package/tests/unit/features/chat/tabs/Tab.test.ts +4287 -0
  487. package/tests/unit/features/chat/tabs/TabBar.test.ts +357 -0
  488. package/tests/unit/features/chat/tabs/TabManager.test.ts +2962 -0
  489. package/tests/unit/features/chat/tabs/index.test.ts +11 -0
  490. package/tests/unit/features/chat/ui/BangBashModeManager.test.ts +321 -0
  491. package/tests/unit/features/chat/ui/ExternalContextSelector.test.ts +555 -0
  492. package/tests/unit/features/chat/ui/FileContextManager.test.ts +876 -0
  493. package/tests/unit/features/chat/ui/ImageContext.test.ts +777 -0
  494. package/tests/unit/features/chat/ui/InputToolbar.test.ts +1139 -0
  495. package/tests/unit/features/chat/ui/InstructionModeManager.test.ts +243 -0
  496. package/tests/unit/features/chat/ui/NavigationSidebar.test.ts +570 -0
  497. package/tests/unit/features/chat/ui/StatusPanel.test.ts +953 -0
  498. package/tests/unit/features/chat/ui/file-context/state/FileContextState.test.ts +155 -0
  499. package/tests/unit/features/chat/ui/textareaResize.test.ts +102 -0
  500. package/tests/unit/features/chat/utils/usageInfo.test.ts +56 -0
  501. package/tests/unit/features/inline-edit/InlineEditService.test.ts +1199 -0
  502. package/tests/unit/features/inline-edit/ui/InlineEditModal.openAndWait.test.ts +1482 -0
  503. package/tests/unit/features/inline-edit/ui/InlineEditModal.test.ts +495 -0
  504. package/tests/unit/features/inline-edit/ui/inlineEditMarkdownPreview.test.ts +92 -0
  505. package/tests/unit/features/settings/AgentSettings.test.ts +82 -0
  506. package/tests/unit/features/settings/keyboardNavigation.test.ts +73 -0
  507. package/tests/unit/features/settings/ui/CodexSkillSettings.test.ts +294 -0
  508. package/tests/unit/features/settings/ui/CodexSubagentSettings.test.ts +207 -0
  509. package/tests/unit/i18n/constants.test.ts +43 -0
  510. package/tests/unit/i18n/i18n.test.ts +244 -0
  511. package/tests/unit/i18n/locales.test.ts +134 -0
  512. package/tests/unit/providers/acp/AcpClientConnection.test.ts +248 -0
  513. package/tests/unit/providers/acp/AcpJsonRpcTransport.test.ts +186 -0
  514. package/tests/unit/providers/acp/AcpSessionConfig.test.ts +247 -0
  515. package/tests/unit/providers/acp/AcpSessionUpdateNormalizer.test.ts +145 -0
  516. package/tests/unit/providers/acp/AcpSubprocess.test.ts +105 -0
  517. package/tests/unit/providers/acp/buildAcpUsageInfo.test.ts +51 -0
  518. package/tests/unit/providers/claude/agents/AgentManager.test.ts +590 -0
  519. package/tests/unit/providers/claude/agents/AgentStorage.test.ts +434 -0
  520. package/tests/unit/providers/claude/agents/index.test.ts +10 -0
  521. package/tests/unit/providers/claude/commands/ClaudeCommandCatalog.test.ts +396 -0
  522. package/tests/unit/providers/claude/commands/probeRuntimeCommands.test.ts +92 -0
  523. package/tests/unit/providers/claude/env/ClaudeSettingsReconciler.test.ts +57 -0
  524. package/tests/unit/providers/claude/env/claudeModelEnv.test.ts +228 -0
  525. package/tests/unit/providers/claude/hooks/SubagentHooks.test.ts +83 -0
  526. package/tests/unit/providers/claude/plugins/PluginManager.test.ts +832 -0
  527. package/tests/unit/providers/claude/plugins/index.test.ts +7 -0
  528. package/tests/unit/providers/claude/prompt/ClaudeTurnEncoder.test.ts +145 -0
  529. package/tests/unit/providers/claude/prompt/instructionRefine.test.ts +185 -0
  530. package/tests/unit/providers/claude/prompt/systemPrompt.test.ts +163 -0
  531. package/tests/unit/providers/claude/prompt/titleGeneration.test.ts +20 -0
  532. package/tests/unit/providers/claude/runtime/ClaudeTaskResultInterpreter.test.ts +28 -0
  533. package/tests/unit/providers/claude/runtime/ClaudianService.test.ts +3796 -0
  534. package/tests/unit/providers/claude/runtime/MessageChannel.test.ts +421 -0
  535. package/tests/unit/providers/claude/runtime/QueryOptionsBuilder.test.ts +775 -0
  536. package/tests/unit/providers/claude/runtime/SessionManager.test.ts +182 -0
  537. package/tests/unit/providers/claude/runtime/claudeColdStartQuery.test.ts +331 -0
  538. package/tests/unit/providers/claude/runtime/customSpawn.test.ts +374 -0
  539. package/tests/unit/providers/claude/runtime/index.test.ts +13 -0
  540. package/tests/unit/providers/claude/runtime/types.test.ts +190 -0
  541. package/tests/unit/providers/claude/sdk/typeGuards.test.ts +50 -0
  542. package/tests/unit/providers/claude/security/ClaudePermissionUpdates.test.ts +198 -0
  543. package/tests/unit/providers/claude/storage/AgentVaultStorage.test.ts +413 -0
  544. package/tests/unit/providers/claude/storage/CCSettingsStorage.test.ts +408 -0
  545. package/tests/unit/providers/claude/storage/ClaudianSettingsStorage.test.ts +653 -0
  546. package/tests/unit/providers/claude/storage/McpStorage.test.ts +619 -0
  547. package/tests/unit/providers/claude/storage/SessionStorage.test.ts +680 -0
  548. package/tests/unit/providers/claude/storage/SkillStorage.test.ts +275 -0
  549. package/tests/unit/providers/claude/storage/SlashCommandStorage.test.ts +612 -0
  550. package/tests/unit/providers/claude/storage/storage.test.ts +360 -0
  551. package/tests/unit/providers/claude/storage/storageService.convenience.test.ts +447 -0
  552. package/tests/unit/providers/claude/stream/transformSDKMessage.test.ts +1729 -0
  553. package/tests/unit/providers/claude/types/types.test.ts +726 -0
  554. package/tests/unit/providers/claude/ui/ClaudeChatUIConfig.test.ts +173 -0
  555. package/tests/unit/providers/claude/ui/ClaudeSettingsTab.test.ts +466 -0
  556. package/tests/unit/providers/codex/agents/CodexAgentMentionProvider.test.ts +89 -0
  557. package/tests/unit/providers/codex/auxiliary/CodexInstructionRefineService.test.ts +81 -0
  558. package/tests/unit/providers/codex/capabilities.test.ts +39 -0
  559. package/tests/unit/providers/codex/commands/CodexSkillCatalog.test.ts +413 -0
  560. package/tests/unit/providers/codex/env/CodexSettingsReconciler.test.ts +106 -0
  561. package/tests/unit/providers/codex/fixtures/codex-session-abort.jsonl +9 -0
  562. package/tests/unit/providers/codex/fixtures/codex-session-agent-lifecycle.jsonl +12 -0
  563. package/tests/unit/providers/codex/fixtures/codex-session-persisted-tools.jsonl +15 -0
  564. package/tests/unit/providers/codex/fixtures/codex-session-simple.jsonl +10 -0
  565. package/tests/unit/providers/codex/fixtures/codex-session-tools.jsonl +12 -0
  566. package/tests/unit/providers/codex/fixtures/codex-session-websearch-persisted.jsonl +3 -0
  567. package/tests/unit/providers/codex/fixtures/codex-session-websearch.jsonl +7 -0
  568. package/tests/unit/providers/codex/history/CodexConversationHistoryService.test.ts +690 -0
  569. package/tests/unit/providers/codex/history/CodexHistoryStore.test.ts +2202 -0
  570. package/tests/unit/providers/codex/normalization/codexSubagentNormalization.test.ts +81 -0
  571. package/tests/unit/providers/codex/normalization/codexToolNormalization.test.ts +322 -0
  572. package/tests/unit/providers/codex/prompt/encodeCodexTurn.test.ts +168 -0
  573. package/tests/unit/providers/codex/runtime/CodexAppServerProcess.test.ts +255 -0
  574. package/tests/unit/providers/codex/runtime/CodexAuxQueryRunner.test.ts +130 -0
  575. package/tests/unit/providers/codex/runtime/CodexBinaryLocator.test.ts +90 -0
  576. package/tests/unit/providers/codex/runtime/CodexChatRuntime.test.ts +2445 -0
  577. package/tests/unit/providers/codex/runtime/CodexCliResolver.test.ts +103 -0
  578. package/tests/unit/providers/codex/runtime/CodexExecutionTargetResolver.test.ts +105 -0
  579. package/tests/unit/providers/codex/runtime/CodexLaunchSpecBuilder.test.ts +150 -0
  580. package/tests/unit/providers/codex/runtime/CodexNotificationRouter.test.ts +1248 -0
  581. package/tests/unit/providers/codex/runtime/CodexPathMapper.test.ts +51 -0
  582. package/tests/unit/providers/codex/runtime/CodexRpcTransport.test.ts +220 -0
  583. package/tests/unit/providers/codex/runtime/CodexRuntimeContext.test.ts +107 -0
  584. package/tests/unit/providers/codex/runtime/CodexServerRequestRouter.test.ts +537 -0
  585. package/tests/unit/providers/codex/runtime/CodexSessionFileTail.test.ts +1305 -0
  586. package/tests/unit/providers/codex/runtime/CodexSessionManager.test.ts +82 -0
  587. package/tests/unit/providers/codex/runtime/codexAppServerTypes.test.ts +336 -0
  588. package/tests/unit/providers/codex/settings.test.ts +189 -0
  589. package/tests/unit/providers/codex/skills/CodexSkillListingService.test.ts +178 -0
  590. package/tests/unit/providers/codex/storage/CodexSkillStorage.test.ts +342 -0
  591. package/tests/unit/providers/codex/storage/CodexSubagentStorage.test.ts +376 -0
  592. package/tests/unit/providers/codex/ui/CodexChatUIConfig.test.ts +211 -0
  593. package/tests/unit/providers/codex/ui/CodexSettingsTab.test.ts +578 -0
  594. package/tests/unit/providers/defaultProviderConfigs.test.ts +18 -0
  595. package/tests/unit/providers/opencode/OpencodeAuxQueryRunner.test.ts +355 -0
  596. package/tests/unit/providers/opencode/OpencodeChatRuntime.test.ts +808 -0
  597. package/tests/unit/providers/opencode/OpencodeCliResolver.test.ts +93 -0
  598. package/tests/unit/providers/opencode/OpencodeCommandCatalog.test.ts +75 -0
  599. package/tests/unit/providers/opencode/OpencodeConversationHistoryService.test.ts +103 -0
  600. package/tests/unit/providers/opencode/OpencodeHistoryStore.test.ts +418 -0
  601. package/tests/unit/providers/opencode/OpencodeLaunchArtifacts.test.ts +268 -0
  602. package/tests/unit/providers/opencode/OpencodePaths.test.ts +35 -0
  603. package/tests/unit/providers/opencode/OpencodeRuntimeCommandLoader.test.ts +129 -0
  604. package/tests/unit/providers/opencode/OpencodeSettingsReconciler.test.ts +96 -0
  605. package/tests/unit/providers/opencode/OpencodeSettingsTab.test.ts +649 -0
  606. package/tests/unit/providers/opencode/OpencodeSqliteReader.test.ts +185 -0
  607. package/tests/unit/providers/opencode/agents/OpencodeAgentMentionProvider.test.ts +56 -0
  608. package/tests/unit/providers/opencode/buildOpencodePrompt.test.ts +87 -0
  609. package/tests/unit/providers/opencode/capabilities.test.ts +39 -0
  610. package/tests/unit/providers/opencode/models.test.ts +273 -0
  611. package/tests/unit/providers/opencode/modes.test.ts +151 -0
  612. package/tests/unit/providers/opencode/opencodeToolNormalization.test.ts +197 -0
  613. package/tests/unit/providers/opencode/settings.test.ts +469 -0
  614. package/tests/unit/providers/opencode/storage/OpencodeAgentStorage.test.ts +377 -0
  615. package/tests/unit/providers/opencode/ui/OpencodeAgentSettings.test.ts +91 -0
  616. package/tests/unit/providers/pi/PiRuntimeCommandLoader.test.ts +149 -0
  617. package/tests/unit/providers/pi/capabilities.test.ts +20 -0
  618. package/tests/unit/providers/pi/commands/PiCommandCatalog.test.ts +69 -0
  619. package/tests/unit/providers/pi/env/PiSettingsReconciler.test.ts +61 -0
  620. package/tests/unit/providers/pi/history/PiConversationHistoryService.test.ts +147 -0
  621. package/tests/unit/providers/pi/history/PiHistoryStore.test.ts +523 -0
  622. package/tests/unit/providers/pi/models.test.ts +96 -0
  623. package/tests/unit/providers/pi/registration.test.ts +54 -0
  624. package/tests/unit/providers/pi/runtime/PiAuxQueryRunner.test.ts +172 -0
  625. package/tests/unit/providers/pi/runtime/PiChatRuntime.test.ts +830 -0
  626. package/tests/unit/providers/pi/runtime/PiCliResolver.test.ts +105 -0
  627. package/tests/unit/providers/pi/runtime/PiEventNormalization.test.ts +147 -0
  628. package/tests/unit/providers/pi/runtime/PiExtensionUiBridge.test.ts +98 -0
  629. package/tests/unit/providers/pi/runtime/PiJsonl.test.ts +42 -0
  630. package/tests/unit/providers/pi/runtime/PiLaunchSpec.test.ts +92 -0
  631. package/tests/unit/providers/pi/runtime/PiModelDiscoveryService.test.ts +135 -0
  632. package/tests/unit/providers/pi/runtime/PiRpcPayloads.test.ts +15 -0
  633. package/tests/unit/providers/pi/runtime/PiRpcTransport.test.ts +116 -0
  634. package/tests/unit/providers/pi/runtime/PiSubprocess.test.ts +151 -0
  635. package/tests/unit/providers/pi/runtime/buildPiPrompt.test.ts +84 -0
  636. package/tests/unit/providers/pi/runtime/buildPiUsageInfo.test.ts +69 -0
  637. package/tests/unit/providers/pi/settings.test.ts +253 -0
  638. package/tests/unit/providers/pi/ui/PiChatUIConfig.test.ts +161 -0
  639. package/tests/unit/providers/pi/ui/PiSettingsTab.test.ts +492 -0
  640. package/tests/unit/scripts/rendererSafeUnref.test.ts +101 -0
  641. package/tests/unit/shared/components/ResumeSessionDropdown.test.ts +356 -0
  642. package/tests/unit/shared/components/SelectableDropdown.test.ts +406 -0
  643. package/tests/unit/shared/components/SlashCommandDropdown.provider.test.ts +354 -0
  644. package/tests/unit/shared/components/SlashCommandDropdown.test.ts +508 -0
  645. package/tests/unit/shared/icons.test.ts +56 -0
  646. package/tests/unit/shared/index.test.ts +46 -0
  647. package/tests/unit/shared/mention/MentionDropdownController.test.ts +823 -0
  648. package/tests/unit/shared/mention/VaultFileCache.test.ts +195 -0
  649. package/tests/unit/shared/mention/VaultFolderCache.test.ts +143 -0
  650. package/tests/unit/shared/mention/VaultMentionDataProvider.test.ts +87 -0
  651. package/tests/unit/shared/modals/ConfirmModal.test.ts +111 -0
  652. package/tests/unit/shared/modals/ForkTargetModal.test.ts +101 -0
  653. package/tests/unit/shared/modals/InstructionConfirmModal.test.ts +305 -0
  654. package/tests/unit/utils/agent.test.ts +395 -0
  655. package/tests/unit/utils/animationFrame.test.ts +59 -0
  656. package/tests/unit/utils/browser.test.ts +73 -0
  657. package/tests/unit/utils/canvas.test.ts +54 -0
  658. package/tests/unit/utils/claudeCli.test.ts +294 -0
  659. package/tests/unit/utils/cliBinaryLocator.test.ts +33 -0
  660. package/tests/unit/utils/context.test.ts +288 -0
  661. package/tests/unit/utils/contextMentionResolver.test.ts +270 -0
  662. package/tests/unit/utils/date.test.ts +80 -0
  663. package/tests/unit/utils/diff.test.ts +291 -0
  664. package/tests/unit/utils/editor.test.ts +249 -0
  665. package/tests/unit/utils/electronCompat.test.ts +87 -0
  666. package/tests/unit/utils/env.test.ts +1240 -0
  667. package/tests/unit/utils/externalContext.test.ts +336 -0
  668. package/tests/unit/utils/externalContextScanner.test.ts +186 -0
  669. package/tests/unit/utils/fileLink.dom.test.ts +273 -0
  670. package/tests/unit/utils/fileLink.handler.test.ts +64 -0
  671. package/tests/unit/utils/fileLink.test.ts +233 -0
  672. package/tests/unit/utils/frontmatter.test.ts +434 -0
  673. package/tests/unit/utils/imageEmbed.test.ts +407 -0
  674. package/tests/unit/utils/inlineEdit.test.ts +63 -0
  675. package/tests/unit/utils/interrupt.test.ts +73 -0
  676. package/tests/unit/utils/markdown.test.ts +35 -0
  677. package/tests/unit/utils/markdownMath.test.ts +54 -0
  678. package/tests/unit/utils/mcp.test.ts +256 -0
  679. package/tests/unit/utils/obsidianCompat.test.ts +18 -0
  680. package/tests/unit/utils/path.test.ts +677 -0
  681. package/tests/unit/utils/sdkSession.test.ts +2359 -0
  682. package/tests/unit/utils/session.test.ts +971 -0
  683. package/tests/unit/utils/slashCommand.test.ts +778 -0
  684. package/tests/unit/utils/utils.test.ts +809 -0
  685. package/tsconfig.jest.json +8 -0
  686. package/tsconfig.json +26 -0
  687. package/versions.json +4 -0
@@ -0,0 +1,971 @@
1
+ import type { ChatMessage, ToolCallInfo } from '@/core/types';
2
+ import {
3
+ buildContextFromHistory,
4
+ buildPromptWithHistoryContext,
5
+ formatContextLine,
6
+ formatToolCallForContext,
7
+ getLastUserMessage,
8
+ isSessionExpiredError,
9
+ truncateToolResult,
10
+ } from '@/utils/session';
11
+
12
+ describe('session utilities', () => {
13
+ describe('isSessionExpiredError', () => {
14
+ it('returns true for "session expired" error', () => {
15
+ const error = new Error('Session expired');
16
+ expect(isSessionExpiredError(error)).toBe(true);
17
+ });
18
+
19
+ it('returns true for "session not found" error', () => {
20
+ const error = new Error('Session not found');
21
+ expect(isSessionExpiredError(error)).toBe(true);
22
+ });
23
+
24
+ it('returns true for "invalid session" error', () => {
25
+ const error = new Error('Invalid session');
26
+ expect(isSessionExpiredError(error)).toBe(true);
27
+ });
28
+
29
+ it('returns true for "session invalid" error', () => {
30
+ const error = new Error('Session invalid');
31
+ expect(isSessionExpiredError(error)).toBe(true);
32
+ });
33
+
34
+ it('returns true for "process exited with code" error', () => {
35
+ const error = new Error('Process exited with code 1');
36
+ expect(isSessionExpiredError(error)).toBe(true);
37
+ });
38
+
39
+ it('returns true for compound pattern "session" + "expired"', () => {
40
+ const error = new Error('The session has expired');
41
+ expect(isSessionExpiredError(error)).toBe(true);
42
+ });
43
+
44
+ it('returns true for compound pattern "resume" + "failed"', () => {
45
+ const error = new Error('Failed to resume session');
46
+ expect(isSessionExpiredError(error)).toBe(true);
47
+ });
48
+
49
+ it('returns true for compound pattern "resume" + "error"', () => {
50
+ const error = new Error('Resume error occurred');
51
+ expect(isSessionExpiredError(error)).toBe(true);
52
+ });
53
+
54
+ it('returns false for unrelated errors', () => {
55
+ const error = new Error('Network timeout');
56
+ expect(isSessionExpiredError(error)).toBe(false);
57
+ });
58
+
59
+ it('returns false for non-Error values', () => {
60
+ expect(isSessionExpiredError('string error')).toBe(false);
61
+ expect(isSessionExpiredError(null)).toBe(false);
62
+ expect(isSessionExpiredError(undefined)).toBe(false);
63
+ expect(isSessionExpiredError(42)).toBe(false);
64
+ });
65
+
66
+ it('is case-insensitive', () => {
67
+ const error = new Error('SESSION EXPIRED');
68
+ expect(isSessionExpiredError(error)).toBe(true);
69
+ });
70
+ });
71
+
72
+ describe('formatToolCallForContext', () => {
73
+ it('formats successful tool call with input but without result', () => {
74
+ const toolCall: ToolCallInfo = {
75
+ id: 'tool-1',
76
+ name: 'Read',
77
+ input: { file_path: '/path/to/file.md' },
78
+ status: 'completed',
79
+ result: 'File contents here - this should NOT be included',
80
+ };
81
+
82
+ const result = formatToolCallForContext(toolCall);
83
+
84
+ // Successful tools show input but no result (Claude can re-execute if needed)
85
+ expect(result).toBe('[Tool Read input: file_path=/path/to/file.md status=completed]');
86
+ expect(result).not.toContain('File contents');
87
+ });
88
+
89
+ it('formats tool call without input', () => {
90
+ const toolCall: ToolCallInfo = {
91
+ id: 'tool-1',
92
+ name: 'Read',
93
+ input: {},
94
+ status: 'completed',
95
+ };
96
+
97
+ const result = formatToolCallForContext(toolCall);
98
+
99
+ expect(result).toBe('[Tool Read status=completed]');
100
+ });
101
+
102
+ it('formats failed tool call with input and error message', () => {
103
+ const toolCall: ToolCallInfo = {
104
+ id: 'tool-1',
105
+ name: 'Read',
106
+ input: { file_path: '/path/to/missing.txt' },
107
+ status: 'error',
108
+ result: 'File not found',
109
+ };
110
+
111
+ const result = formatToolCallForContext(toolCall);
112
+
113
+ expect(result).toBe('[Tool Read input: file_path=/path/to/missing.txt status=error] error: File not found');
114
+ });
115
+
116
+ it('formats blocked tool call with input and error message', () => {
117
+ const toolCall: ToolCallInfo = {
118
+ id: 'tool-1',
119
+ name: 'Bash',
120
+ input: { command: 'rm -rf /' },
121
+ status: 'blocked',
122
+ result: 'Access denied by user approval',
123
+ };
124
+
125
+ const result = formatToolCallForContext(toolCall);
126
+
127
+ expect(result).toBe('[Tool Bash input: command=rm -rf / status=blocked] error: Access denied by user approval');
128
+ });
129
+
130
+ it('truncates long input values', () => {
131
+ const longPath = '/very/long/path/' + 'x'.repeat(150);
132
+ const toolCall: ToolCallInfo = {
133
+ id: 'tool-1',
134
+ name: 'Read',
135
+ input: { file_path: longPath },
136
+ status: 'completed',
137
+ };
138
+
139
+ const result = formatToolCallForContext(toolCall);
140
+
141
+ // Long values truncated to 100 chars (/very/long/path/ = 16 chars, so 84 x's + ...)
142
+ expect(result).toContain('file_path=/very/long/path/' + 'x'.repeat(84) + '...');
143
+ expect(result).not.toContain(longPath);
144
+ });
145
+
146
+ it('truncates long error messages to default 500 chars', () => {
147
+ const longError = 'x'.repeat(700);
148
+ const toolCall: ToolCallInfo = {
149
+ id: 'tool-1',
150
+ name: 'Bash',
151
+ input: {},
152
+ status: 'error',
153
+ result: longError,
154
+ };
155
+
156
+ const result = formatToolCallForContext(toolCall);
157
+
158
+ expect(result).toContain('x'.repeat(500));
159
+ expect(result).toContain('(truncated)');
160
+ });
161
+
162
+ it('truncates to custom max length for errors', () => {
163
+ const toolCall: ToolCallInfo = {
164
+ id: 'tool-1',
165
+ name: 'Bash',
166
+ input: {},
167
+ status: 'error',
168
+ result: 'x'.repeat(500),
169
+ };
170
+
171
+ const result = formatToolCallForContext(toolCall, 100);
172
+
173
+ expect(result).toContain('x'.repeat(100));
174
+ expect(result).toContain('(truncated)');
175
+ });
176
+
177
+ it('defaults to "completed" status when status is undefined', () => {
178
+ const toolCall = {
179
+ id: 'tool-1',
180
+ name: 'Write',
181
+ input: {},
182
+ status: 'completed',
183
+ } as ToolCallInfo;
184
+
185
+ const result = formatToolCallForContext(toolCall);
186
+
187
+ expect(result).toBe('[Tool Write status=completed]');
188
+ });
189
+
190
+ it('handles empty result string for successful tool', () => {
191
+ const toolCall: ToolCallInfo = {
192
+ id: 'tool-1',
193
+ name: 'Edit',
194
+ input: {},
195
+ status: 'completed',
196
+ result: '',
197
+ };
198
+
199
+ const result = formatToolCallForContext(toolCall);
200
+
201
+ expect(result).toBe('[Tool Edit status=completed]');
202
+ });
203
+
204
+ it('handles empty result string for failed tool', () => {
205
+ const toolCall: ToolCallInfo = {
206
+ id: 'tool-1',
207
+ name: 'Edit',
208
+ input: {},
209
+ status: 'error',
210
+ result: '',
211
+ };
212
+
213
+ const result = formatToolCallForContext(toolCall);
214
+
215
+ expect(result).toBe('[Tool Edit status=error]');
216
+ });
217
+
218
+ it('handles whitespace-only result for successful tool', () => {
219
+ const toolCall: ToolCallInfo = {
220
+ id: 'tool-1',
221
+ name: 'Glob',
222
+ input: {},
223
+ status: 'completed',
224
+ result: ' \n\t ',
225
+ };
226
+
227
+ const result = formatToolCallForContext(toolCall);
228
+
229
+ expect(result).toBe('[Tool Glob status=completed]');
230
+ });
231
+ });
232
+
233
+ describe('truncateToolResult', () => {
234
+ it('returns unchanged result when under max length', () => {
235
+ const result = truncateToolResult('short result', 100);
236
+ expect(result).toBe('short result');
237
+ });
238
+
239
+ it('returns unchanged result when exactly at max length', () => {
240
+ const result = truncateToolResult('x'.repeat(500), 500);
241
+ expect(result).toBe('x'.repeat(500));
242
+ });
243
+
244
+ it('truncates and adds indicator when over max length', () => {
245
+ const longResult = 'x'.repeat(700);
246
+ const result = truncateToolResult(longResult, 500);
247
+
248
+ expect(result).toBe('x'.repeat(500) + '... (truncated)');
249
+ });
250
+
251
+ it('uses default max length of 500', () => {
252
+ const longResult = 'x'.repeat(700);
253
+ const result = truncateToolResult(longResult);
254
+
255
+ expect(result).toBe('x'.repeat(500) + '... (truncated)');
256
+ });
257
+ });
258
+
259
+ describe('formatContextLine', () => {
260
+ it('returns formatted context line for message with currentNote', () => {
261
+ const message: ChatMessage = {
262
+ id: 'msg-1',
263
+ role: 'user',
264
+ content: 'Hello',
265
+ timestamp: Date.now(),
266
+ currentNote: 'notes/test.md',
267
+ };
268
+
269
+ const result = formatContextLine(message);
270
+
271
+ expect(result).toContain('notes/test.md');
272
+ });
273
+
274
+ it('returns null when currentNote is undefined', () => {
275
+ const message: ChatMessage = {
276
+ id: 'msg-1',
277
+ role: 'user',
278
+ content: 'Hello',
279
+ timestamp: Date.now(),
280
+ };
281
+
282
+ const result = formatContextLine(message);
283
+
284
+ expect(result).toBeNull();
285
+ });
286
+
287
+ it('returns null when currentNote is empty', () => {
288
+ const message: ChatMessage = {
289
+ id: 'msg-1',
290
+ role: 'user',
291
+ content: 'Hello',
292
+ timestamp: Date.now(),
293
+ currentNote: '',
294
+ };
295
+
296
+ const result = formatContextLine(message);
297
+
298
+ expect(result).toBeNull();
299
+ });
300
+ });
301
+
302
+ describe('buildContextFromHistory', () => {
303
+ it('builds context from simple user/assistant exchange', () => {
304
+ const messages: ChatMessage[] = [
305
+ { id: 'msg-1', role: 'user', content: 'Hello', timestamp: 1000 },
306
+ { id: 'msg-2', role: 'assistant', content: 'Hi there!', timestamp: 2000 },
307
+ ];
308
+
309
+ const result = buildContextFromHistory(messages);
310
+
311
+ expect(result).toContain('User: Hello');
312
+ expect(result).toContain('Assistant: Hi there!');
313
+ });
314
+
315
+ it('includes tool calls without results for successful tools', () => {
316
+ const messages: ChatMessage[] = [
317
+ { id: 'msg-1', role: 'user', content: 'Read file', timestamp: 1000 },
318
+ {
319
+ id: 'msg-2',
320
+ role: 'assistant',
321
+ content: 'Let me read that file.',
322
+ timestamp: 2000,
323
+ toolCalls: [
324
+ { id: 'tool-1', name: 'Read', input: {}, status: 'completed', result: 'file contents' },
325
+ ],
326
+ },
327
+ ];
328
+
329
+ const result = buildContextFromHistory(messages);
330
+
331
+ expect(result).toContain('User: Read file');
332
+ expect(result).toContain('Assistant: Let me read that file.');
333
+ expect(result).toContain('[Tool Read status=completed]');
334
+ // Successful tools don't include results (Claude can re-execute if needed)
335
+ expect(result).not.toContain('file contents');
336
+ });
337
+
338
+ it('includes error messages for failed tool calls', () => {
339
+ const messages: ChatMessage[] = [
340
+ { id: 'msg-1', role: 'user', content: 'Read file', timestamp: 1000 },
341
+ {
342
+ id: 'msg-2',
343
+ role: 'assistant',
344
+ content: 'Let me read that file.',
345
+ timestamp: 2000,
346
+ toolCalls: [
347
+ { id: 'tool-1', name: 'Read', input: {}, status: 'error', result: 'File not found' },
348
+ ],
349
+ },
350
+ ];
351
+
352
+ const result = buildContextFromHistory(messages);
353
+
354
+ expect(result).toContain('[Tool Read status=error] error: File not found');
355
+ });
356
+
357
+ it('includes currentNote context for user messages', () => {
358
+ const messages: ChatMessage[] = [
359
+ {
360
+ id: 'msg-1',
361
+ role: 'user',
362
+ content: 'Analyze this note',
363
+ timestamp: 1000,
364
+ currentNote: 'notes/important.md',
365
+ },
366
+ ];
367
+
368
+ const result = buildContextFromHistory(messages);
369
+
370
+ expect(result).toContain('notes/important.md');
371
+ expect(result).toContain('Analyze this note');
372
+ });
373
+
374
+ it('skips non-user/assistant messages', () => {
375
+ // buildContextFromHistory only processes 'user' and 'assistant' roles
376
+ const messages: ChatMessage[] = [
377
+ { id: 'msg-1', role: 'user', content: 'User message', timestamp: 2000 },
378
+ ];
379
+
380
+ const result = buildContextFromHistory(messages);
381
+
382
+ expect(result).toContain('User: User message');
383
+ });
384
+
385
+ it('skips assistant messages with no content and no tool results', () => {
386
+ const messages: ChatMessage[] = [
387
+ { id: 'msg-1', role: 'user', content: 'Hello', timestamp: 1000 },
388
+ { id: 'msg-2', role: 'assistant', content: '', timestamp: 2000 },
389
+ { id: 'msg-3', role: 'assistant', content: 'Response', timestamp: 3000 },
390
+ ];
391
+
392
+ const result = buildContextFromHistory(messages);
393
+
394
+ expect(result).toContain('User: Hello');
395
+ expect(result).toContain('Assistant: Response');
396
+ // Should not have an empty assistant entry
397
+ expect(result.match(/Assistant:/g)?.length).toBe(1);
398
+ });
399
+
400
+ it('includes assistant message with only tool results (no text content)', () => {
401
+ const messages: ChatMessage[] = [
402
+ { id: 'msg-1', role: 'user', content: 'Do something', timestamp: 1000 },
403
+ {
404
+ id: 'msg-2',
405
+ role: 'assistant',
406
+ content: '',
407
+ timestamp: 2000,
408
+ toolCalls: [
409
+ { id: 'tool-1', name: 'Bash', input: {}, status: 'completed', result: 'done' },
410
+ ],
411
+ },
412
+ ];
413
+
414
+ const result = buildContextFromHistory(messages);
415
+
416
+ expect(result).toContain('[Tool Bash status=completed]');
417
+ });
418
+
419
+ it('returns empty string for empty messages array', () => {
420
+ const result = buildContextFromHistory([]);
421
+ expect(result).toBe('');
422
+ });
423
+
424
+ it('handles messages with only whitespace content', () => {
425
+ const messages: ChatMessage[] = [
426
+ { id: 'msg-1', role: 'user', content: ' \n ', timestamp: 1000 },
427
+ { id: 'msg-2', role: 'assistant', content: ' \t ', timestamp: 2000 },
428
+ ];
429
+
430
+ const result = buildContextFromHistory(messages);
431
+
432
+ // Whitespace content should still be processed (trimmed)
433
+ expect(result).toContain('User:');
434
+ });
435
+
436
+ it('separates messages with double newlines', () => {
437
+ const messages: ChatMessage[] = [
438
+ { id: 'msg-1', role: 'user', content: 'First', timestamp: 1000 },
439
+ { id: 'msg-2', role: 'assistant', content: 'Second', timestamp: 2000 },
440
+ { id: 'msg-3', role: 'user', content: 'Third', timestamp: 3000 },
441
+ ];
442
+
443
+ const result = buildContextFromHistory(messages);
444
+
445
+ expect(result).toContain('\n\n');
446
+ });
447
+
448
+ it('shows all tool calls but only error results', () => {
449
+ const messages: ChatMessage[] = [
450
+ { id: 'msg-1', role: 'user', content: 'Test', timestamp: 1000 },
451
+ {
452
+ id: 'msg-2',
453
+ role: 'assistant',
454
+ content: 'Response',
455
+ timestamp: 2000,
456
+ toolCalls: [
457
+ { id: 'tool-1', name: 'Success', input: {}, status: 'completed', result: 'data' },
458
+ { id: 'tool-2', name: 'Failed', input: {}, status: 'error', result: 'error msg' },
459
+ ],
460
+ },
461
+ ];
462
+
463
+ const result = buildContextFromHistory(messages);
464
+
465
+ // Successful tool shows status only (no result)
466
+ expect(result).toContain('[Tool Success status=completed]');
467
+ expect(result).not.toContain('data');
468
+ // Failed tool shows error message
469
+ expect(result).toContain('[Tool Failed status=error] error: error msg');
470
+ });
471
+
472
+ it('includes thinking block summary', () => {
473
+ const messages: ChatMessage[] = [
474
+ { id: 'msg-1', role: 'user', content: 'Think about this', timestamp: 1000 },
475
+ {
476
+ id: 'msg-2',
477
+ role: 'assistant',
478
+ content: 'Here is my response',
479
+ timestamp: 2000,
480
+ contentBlocks: [
481
+ { type: 'thinking', content: 'Let me think...', durationSeconds: 5.5 },
482
+ { type: 'text', content: 'Here is my response' },
483
+ ],
484
+ },
485
+ ];
486
+
487
+ const result = buildContextFromHistory(messages);
488
+
489
+ expect(result).toContain('[Thinking: 1 block(s), 5.5s total]');
490
+ // Thinking content is NOT included (Claude will think anew)
491
+ expect(result).not.toContain('Let me think');
492
+ });
493
+
494
+ it('includes thinking summary for multiple blocks', () => {
495
+ const messages: ChatMessage[] = [
496
+ { id: 'msg-1', role: 'user', content: 'Complex problem', timestamp: 1000 },
497
+ {
498
+ id: 'msg-2',
499
+ role: 'assistant',
500
+ content: 'Response',
501
+ timestamp: 2000,
502
+ contentBlocks: [
503
+ { type: 'thinking', content: 'First thought', durationSeconds: 3.0 },
504
+ { type: 'thinking', content: 'Second thought', durationSeconds: 2.5 },
505
+ ],
506
+ },
507
+ ];
508
+
509
+ const result = buildContextFromHistory(messages);
510
+
511
+ expect(result).toContain('[Thinking: 2 block(s), 5.5s total]');
512
+ });
513
+
514
+ it('includes thinking summary without duration if not available', () => {
515
+ const messages: ChatMessage[] = [
516
+ { id: 'msg-1', role: 'user', content: 'Question', timestamp: 1000 },
517
+ {
518
+ id: 'msg-2',
519
+ role: 'assistant',
520
+ content: 'Answer',
521
+ timestamp: 2000,
522
+ contentBlocks: [
523
+ { type: 'thinking', content: 'Thinking...' },
524
+ ],
525
+ },
526
+ ];
527
+
528
+ const result = buildContextFromHistory(messages);
529
+
530
+ expect(result).toContain('[Thinking: 1 block(s)]');
531
+ expect(result).not.toContain('total]');
532
+ });
533
+
534
+ it('includes tool input in history', () => {
535
+ const messages: ChatMessage[] = [
536
+ { id: 'msg-1', role: 'user', content: 'Read my file', timestamp: 1000 },
537
+ {
538
+ id: 'msg-2',
539
+ role: 'assistant',
540
+ content: 'Let me read it',
541
+ timestamp: 2000,
542
+ toolCalls: [
543
+ { id: 'tool-1', name: 'Read', input: { file_path: '/notes/todo.md' }, status: 'completed', result: 'file contents' },
544
+ ],
545
+ },
546
+ ];
547
+
548
+ const result = buildContextFromHistory(messages);
549
+
550
+ expect(result).toContain('[Tool Read input: file_path=/notes/todo.md status=completed]');
551
+ });
552
+ });
553
+
554
+ describe('getLastUserMessage', () => {
555
+ it('returns last user message from history', () => {
556
+ const messages: ChatMessage[] = [
557
+ { id: 'msg-1', role: 'user', content: 'First', timestamp: 1000 },
558
+ { id: 'msg-2', role: 'assistant', content: 'Response', timestamp: 2000 },
559
+ { id: 'msg-3', role: 'user', content: 'Second', timestamp: 3000 },
560
+ { id: 'msg-4', role: 'assistant', content: 'Response 2', timestamp: 4000 },
561
+ ];
562
+
563
+ const result = getLastUserMessage(messages);
564
+
565
+ expect(result?.id).toBe('msg-3');
566
+ expect(result?.content).toBe('Second');
567
+ });
568
+
569
+ it('returns undefined when no user messages exist', () => {
570
+ const messages: ChatMessage[] = [
571
+ { id: 'msg-1', role: 'assistant', content: 'Response', timestamp: 1000 },
572
+ ];
573
+
574
+ const result = getLastUserMessage(messages);
575
+
576
+ expect(result).toBeUndefined();
577
+ });
578
+
579
+ it('returns undefined for empty messages array', () => {
580
+ const result = getLastUserMessage([]);
581
+
582
+ expect(result).toBeUndefined();
583
+ });
584
+
585
+ it('returns the only user message when there is just one', () => {
586
+ const messages: ChatMessage[] = [
587
+ { id: 'msg-1', role: 'assistant', content: 'Welcome', timestamp: 1000 },
588
+ { id: 'msg-2', role: 'user', content: 'Only user msg', timestamp: 2000 },
589
+ { id: 'msg-3', role: 'assistant', content: 'Response', timestamp: 3000 },
590
+ ];
591
+
592
+ const result = getLastUserMessage(messages);
593
+
594
+ expect(result?.id).toBe('msg-2');
595
+ });
596
+
597
+ it('finds user message among assistant messages', () => {
598
+ const messages: ChatMessage[] = [
599
+ { id: 'msg-1', role: 'assistant', content: 'Welcome', timestamp: 1000 },
600
+ { id: 'msg-2', role: 'user', content: 'User', timestamp: 2000 },
601
+ { id: 'msg-3', role: 'assistant', content: 'Response', timestamp: 3000 },
602
+ ];
603
+
604
+ const result = getLastUserMessage(messages);
605
+
606
+ expect(result?.id).toBe('msg-2');
607
+ });
608
+ });
609
+
610
+ describe('buildPromptWithHistoryContext', () => {
611
+ it('returns prompt unchanged when historyContext is null', () => {
612
+ const prompt = '<query>\nhello\n</query>';
613
+ const result = buildPromptWithHistoryContext(null, prompt, 'hello', []);
614
+
615
+ expect(result).toBe(prompt);
616
+ });
617
+
618
+ it('returns only history when actualPrompt matches last user message', () => {
619
+ const messages: ChatMessage[] = [
620
+ { id: 'msg-1', role: 'user', content: 'hello', timestamp: 1000 },
621
+ { id: 'msg-2', role: 'assistant', content: 'hi', timestamp: 2000 },
622
+ ];
623
+ const historyContext = 'User: hello\n\nAssistant: hi';
624
+ const prompt = '<query>\nhello\n</query>';
625
+ const actualPrompt = 'hello';
626
+
627
+ const result = buildPromptWithHistoryContext(historyContext, prompt, actualPrompt, messages);
628
+
629
+ // Should NOT append prompt since actualPrompt matches last user message
630
+ expect(result).toBe(historyContext);
631
+ });
632
+
633
+ it('appends prompt when actualPrompt differs from last user message', () => {
634
+ const messages: ChatMessage[] = [
635
+ { id: 'msg-1', role: 'user', content: 'first message', timestamp: 1000 },
636
+ { id: 'msg-2', role: 'assistant', content: 'response', timestamp: 2000 },
637
+ ];
638
+ const historyContext = 'User: first message\n\nAssistant: response';
639
+ const prompt = '<query>\nsecond message\n</query>';
640
+ const actualPrompt = 'second message';
641
+
642
+ const result = buildPromptWithHistoryContext(historyContext, prompt, actualPrompt, messages);
643
+
644
+ expect(result).toContain(historyContext);
645
+ expect(result).toContain('User: <query>');
646
+ expect(result).toContain('second message');
647
+ });
648
+
649
+ it('returns prompt unchanged when history context is empty string', () => {
650
+ const historyContext = '';
651
+ const prompt = '<query>\nhello\n</query>';
652
+
653
+ const result = buildPromptWithHistoryContext(historyContext, prompt, 'hello', []);
654
+
655
+ // Empty string is falsy, so returns original prompt
656
+ expect(result).toBe(prompt);
657
+ });
658
+
659
+ it('appends prompt when no user messages in history', () => {
660
+ const messages: ChatMessage[] = [
661
+ { id: 'msg-1', role: 'assistant', content: 'welcome', timestamp: 1000 },
662
+ ];
663
+ const historyContext = 'Assistant: welcome';
664
+ const prompt = '<query>\nhello\n</query>';
665
+ const actualPrompt = 'hello';
666
+
667
+ const result = buildPromptWithHistoryContext(historyContext, prompt, actualPrompt, messages);
668
+
669
+ expect(result).toContain(historyContext);
670
+ expect(result).toContain('User: <query>');
671
+ });
672
+
673
+ it('handles whitespace in comparison', () => {
674
+ const messages: ChatMessage[] = [
675
+ { id: 'msg-1', role: 'user', content: ' hello world ', timestamp: 1000 },
676
+ ];
677
+ const historyContext = 'User: hello world';
678
+ const prompt = '<query>\nhello world\n</query>';
679
+ const actualPrompt = 'hello world';
680
+
681
+ const result = buildPromptWithHistoryContext(historyContext, prompt, actualPrompt, messages);
682
+
683
+ // Should match after trimming
684
+ expect(result).toBe(historyContext);
685
+ });
686
+
687
+ it('avoids duplication when XML-wrapped content matches display content', () => {
688
+ const prompt = [
689
+ '<current_note>',
690
+ 'notes/file.md',
691
+ '</current_note>',
692
+ '',
693
+ '<editor_selection path="notes/file.md">',
694
+ 'selected text',
695
+ '</editor_selection>',
696
+ '',
697
+ '<query>',
698
+ 'Follow up',
699
+ '</query>',
700
+ ].join('\n');
701
+
702
+ const actualPrompt = [
703
+ '<editor_selection path="notes/file.md">',
704
+ 'selected text',
705
+ '</editor_selection>',
706
+ '',
707
+ '<query>',
708
+ 'Follow up',
709
+ '</query>',
710
+ ].join('\n');
711
+
712
+ const messages: ChatMessage[] = [
713
+ {
714
+ id: 'msg-1',
715
+ role: 'user',
716
+ content: prompt,
717
+ displayContent: 'Follow up',
718
+ timestamp: 1000,
719
+ },
720
+ ];
721
+
722
+ const historyContext = 'User: Follow up';
723
+
724
+ const result = buildPromptWithHistoryContext(historyContext, prompt, actualPrompt, messages);
725
+
726
+ expect(result).toBe(historyContext);
727
+ });
728
+
729
+ describe('new format (user content before XML context)', () => {
730
+ it('avoids duplication when actualPrompt matches last user message', () => {
731
+ const prompt = 'Explain this\n\n<current_note>\ntest.md\n</current_note>';
732
+ const actualPrompt = 'Explain this\n\n<current_note>\ntest.md\n</current_note>';
733
+ const messages: ChatMessage[] = [
734
+ {
735
+ id: 'msg-1',
736
+ role: 'user',
737
+ content: prompt,
738
+ displayContent: 'Explain this',
739
+ timestamp: 1000,
740
+ },
741
+ ];
742
+ const historyContext = 'User: Explain this';
743
+
744
+ const result = buildPromptWithHistoryContext(historyContext, prompt, actualPrompt, messages);
745
+
746
+ expect(result).toBe(historyContext);
747
+ });
748
+
749
+ it('appends prompt when actualPrompt differs from last user message', () => {
750
+ const oldPrompt = 'First question\n\n<current_note>\nold.md\n</current_note>';
751
+ const newPrompt = 'Second question\n\n<current_note>\nnew.md\n</current_note>';
752
+ const messages: ChatMessage[] = [
753
+ {
754
+ id: 'msg-1',
755
+ role: 'user',
756
+ content: oldPrompt,
757
+ displayContent: 'First question',
758
+ timestamp: 1000,
759
+ },
760
+ ];
761
+ const historyContext = 'User: First question\n\nAssistant: response';
762
+
763
+ const result = buildPromptWithHistoryContext(historyContext, newPrompt, newPrompt, messages);
764
+
765
+ expect(result).toContain(historyContext);
766
+ expect(result).toContain('User: Second question');
767
+ });
768
+
769
+ it('extracts user query from editor_selection format', () => {
770
+ const prompt = 'Refactor this\n\n<editor_selection path="src/main.ts">\ncode here\n</editor_selection>';
771
+ const messages: ChatMessage[] = [
772
+ {
773
+ id: 'msg-1',
774
+ role: 'user',
775
+ content: prompt,
776
+ displayContent: 'Refactor this',
777
+ timestamp: 1000,
778
+ },
779
+ ];
780
+ const historyContext = 'User: Refactor this';
781
+
782
+ const result = buildPromptWithHistoryContext(historyContext, prompt, prompt, messages);
783
+
784
+ expect(result).toBe(historyContext);
785
+ });
786
+
787
+ it('extracts user query from content with multiple XML context tags', () => {
788
+ const prompt = 'Update code\n\n<current_note>\ntest.md\n</current_note>\n\n<editor_selection path="test.md">\nselected\n</editor_selection>';
789
+ const messages: ChatMessage[] = [
790
+ {
791
+ id: 'msg-1',
792
+ role: 'user',
793
+ content: prompt,
794
+ displayContent: 'Update code',
795
+ timestamp: 1000,
796
+ },
797
+ ];
798
+ const historyContext = 'User: Update code';
799
+
800
+ const result = buildPromptWithHistoryContext(historyContext, prompt, prompt, messages);
801
+
802
+ expect(result).toBe(historyContext);
803
+ });
804
+
805
+ it('falls back to extractUserQuery when displayContent is not available', () => {
806
+ const prompt = 'Help me\n\n<current_note>\nfile.md\n</current_note>';
807
+ const messages: ChatMessage[] = [
808
+ {
809
+ id: 'msg-1',
810
+ role: 'user',
811
+ content: prompt,
812
+ // No displayContent - should extract from content
813
+ timestamp: 1000,
814
+ },
815
+ ];
816
+ const historyContext = 'User: Help me';
817
+
818
+ const result = buildPromptWithHistoryContext(historyContext, prompt, prompt, messages);
819
+
820
+ expect(result).toBe(historyContext);
821
+ });
822
+ });
823
+ });
824
+
825
+ describe('formatToolCallForContext edge cases', () => {
826
+ it('formats tool call with object input value', () => {
827
+ const toolCall: ToolCallInfo = {
828
+ id: 'tool-1',
829
+ name: 'Write',
830
+ input: { config: { nested: true }, count: 42 },
831
+ status: 'completed',
832
+ };
833
+
834
+ const result = formatToolCallForContext(toolCall);
835
+
836
+ expect(result).toContain('config=[object]');
837
+ expect(result).toContain('count=42');
838
+ });
839
+
840
+ it('skips null and undefined input values', () => {
841
+ const toolCall: ToolCallInfo = {
842
+ id: 'tool-1',
843
+ name: 'Read',
844
+ input: { path: '/file.md', optional: null, missing: undefined },
845
+ status: 'completed',
846
+ };
847
+
848
+ const result = formatToolCallForContext(toolCall);
849
+
850
+ expect(result).toContain('path=/file.md');
851
+ expect(result).not.toContain('optional');
852
+ expect(result).not.toContain('missing');
853
+ });
854
+
855
+ it('truncates long overall input string', () => {
856
+ const toolCall: ToolCallInfo = {
857
+ id: 'tool-1',
858
+ name: 'Bash',
859
+ input: {
860
+ a: 'x'.repeat(80),
861
+ b: 'y'.repeat(80),
862
+ c: 'z'.repeat(80),
863
+ },
864
+ status: 'completed',
865
+ };
866
+
867
+ const result = formatToolCallForContext(toolCall);
868
+
869
+ // Total input string truncated to 200 chars
870
+ expect(result).toContain('...');
871
+ });
872
+
873
+ it('formats whitespace-only result for failed tool without error detail', () => {
874
+ const toolCall: ToolCallInfo = {
875
+ id: 'tool-1',
876
+ name: 'Bash',
877
+ input: {},
878
+ status: 'error',
879
+ result: ' \n ',
880
+ };
881
+
882
+ const result = formatToolCallForContext(toolCall);
883
+
884
+ expect(result).toBe('[Tool Bash status=error]');
885
+ });
886
+ });
887
+
888
+ describe('buildContextFromHistory edge cases', () => {
889
+ it('skips interrupt messages', () => {
890
+ const messages: ChatMessage[] = [
891
+ { id: 'msg-1', role: 'user', content: 'Start task', timestamp: 1000 },
892
+ {
893
+ id: 'msg-2',
894
+ role: 'assistant',
895
+ content: 'Working on it...',
896
+ timestamp: 2000,
897
+ },
898
+ {
899
+ id: 'msg-3',
900
+ role: 'user',
901
+ content: '',
902
+ timestamp: 3000,
903
+ isInterrupt: true,
904
+ },
905
+ {
906
+ id: 'msg-4',
907
+ role: 'assistant',
908
+ content: 'Stopped.',
909
+ timestamp: 4000,
910
+ },
911
+ ];
912
+
913
+ const result = buildContextFromHistory(messages);
914
+
915
+ expect(result).toContain('User: Start task');
916
+ expect(result).toContain('Assistant: Working on it...');
917
+ expect(result).toContain('Assistant: Stopped.');
918
+ // Interrupt message should not appear as a user message
919
+ expect(result.match(/User:/g)?.length).toBe(1);
920
+ });
921
+
922
+ it('includes assistant message with only thinking blocks and no text', () => {
923
+ const messages: ChatMessage[] = [
924
+ { id: 'msg-1', role: 'user', content: 'Think hard', timestamp: 1000 },
925
+ {
926
+ id: 'msg-2',
927
+ role: 'assistant',
928
+ content: '',
929
+ timestamp: 2000,
930
+ contentBlocks: [
931
+ { type: 'thinking', content: 'Deep thought...', durationSeconds: 10 },
932
+ ],
933
+ },
934
+ ];
935
+
936
+ const result = buildContextFromHistory(messages);
937
+
938
+ expect(result).toContain('[Thinking: 1 block(s), 10.0s total]');
939
+ });
940
+
941
+ it('handles user message with currentNote but no content', () => {
942
+ const messages: ChatMessage[] = [
943
+ {
944
+ id: 'msg-1',
945
+ role: 'user',
946
+ content: '',
947
+ timestamp: 1000,
948
+ currentNote: 'notes/active.md',
949
+ },
950
+ ];
951
+
952
+ const result = buildContextFromHistory(messages);
953
+
954
+ expect(result).toContain('notes/active.md');
955
+ });
956
+
957
+ it('skips messages with unknown roles', () => {
958
+ const messages = [
959
+ { id: 'msg-1', role: 'user', content: 'Hello', timestamp: 1000 },
960
+ { id: 'msg-2', role: 'system' as any, content: 'System msg', timestamp: 1500 },
961
+ { id: 'msg-3', role: 'assistant', content: 'Response', timestamp: 2000 },
962
+ ] as ChatMessage[];
963
+
964
+ const result = buildContextFromHistory(messages);
965
+
966
+ expect(result).toContain('User: Hello');
967
+ expect(result).toContain('Assistant: Response');
968
+ expect(result).not.toContain('System msg');
969
+ });
970
+ });
971
+ });