@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,1107 @@
1
+ import { existsSync, readFileSync, realpathSync } from 'fs';
2
+ import { tmpdir } from 'os';
3
+ import { isAbsolute, sep } from 'path';
4
+
5
+ import { ProviderRegistry } from '../../../core/providers/ProviderRegistry';
6
+ import type { ProviderTaskResultInterpreter } from '../../../core/providers/types';
7
+ import { TOOL_TASK } from '../../../core/tools/toolNames';
8
+ import { extractToolResultContent } from '../../../core/tools/toolResultContent';
9
+ import type {
10
+ SubagentInfo,
11
+ ToolCallInfo,
12
+ } from '../../../core/types';
13
+ import { extractFinalResultFromSubagentJsonl } from '../../../utils/subagentJsonl';
14
+ import {
15
+ addSubagentToolCall,
16
+ type AsyncSubagentState,
17
+ createAsyncSubagentBlock,
18
+ createSubagentBlock,
19
+ finalizeAsyncSubagent,
20
+ finalizeSubagentBlock,
21
+ markAsyncSubagentOrphaned,
22
+ type SubagentState,
23
+ updateAsyncSubagentRunning,
24
+ updateSubagentToolResult,
25
+ } from '../rendering/SubagentRenderer';
26
+ import type { PendingToolCall } from '../state/types';
27
+
28
+ export type SubagentStateChangeCallback = (subagent: SubagentInfo) => void;
29
+
30
+ export type HandleTaskResult =
31
+ | { action: 'buffered' }
32
+ | { action: 'created_sync'; subagentState: SubagentState }
33
+ | { action: 'created_async'; info: SubagentInfo; domState: AsyncSubagentState }
34
+ | { action: 'label_updated' };
35
+
36
+ export type RenderPendingResult =
37
+ | { mode: 'sync'; subagentState: SubagentState }
38
+ | { mode: 'async'; info: SubagentInfo; domState: AsyncSubagentState };
39
+
40
+ function isRecord(value: unknown): value is Record<string, unknown> {
41
+ return !!value && typeof value === 'object' && !Array.isArray(value);
42
+ }
43
+
44
+ function parseJsonRecord(value: string): Record<string, unknown> | null {
45
+ try {
46
+ const parsed: unknown = JSON.parse(value);
47
+ return isRecord(parsed) ? parsed : null;
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+
53
+ function parseJsonValue(value: string): unknown {
54
+ try {
55
+ const parsed: unknown = JSON.parse(value);
56
+ return parsed;
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+
62
+ export class SubagentManager {
63
+ private static readonly TRUSTED_OUTPUT_EXT = '.output';
64
+ private static readonly TRUSTED_TMP_ROOTS = SubagentManager.resolveTrustedTmpRoots();
65
+
66
+ private syncSubagents: Map<string, SubagentState> = new Map();
67
+ private pendingTasks: Map<string, PendingToolCall> = new Map();
68
+ private _spawnedThisStream = 0;
69
+
70
+ private activeAsyncSubagents: Map<string, SubagentInfo> = new Map();
71
+ private pendingAsyncSubagents: Map<string, SubagentInfo> = new Map();
72
+ private taskIdToAgentId: Map<string, string> = new Map();
73
+ private outputToolIdToAgentId: Map<string, string> = new Map();
74
+ private asyncDomStates: Map<string, AsyncSubagentState> = new Map();
75
+
76
+ private onStateChange: SubagentStateChangeCallback;
77
+ private taskResultInterpreter: ProviderTaskResultInterpreter;
78
+
79
+ constructor(
80
+ onStateChange: SubagentStateChangeCallback,
81
+ taskResultInterpreter: ProviderTaskResultInterpreter = ProviderRegistry.getTaskResultInterpreter(),
82
+ ) {
83
+ this.onStateChange = onStateChange;
84
+ this.taskResultInterpreter = taskResultInterpreter;
85
+ }
86
+
87
+ public setCallback(callback: SubagentStateChangeCallback): void {
88
+ this.onStateChange = callback;
89
+ }
90
+
91
+ public setTaskResultInterpreter(interpreter: ProviderTaskResultInterpreter): void {
92
+ this.taskResultInterpreter = interpreter;
93
+ }
94
+
95
+ // ============================================
96
+ // Unified Subagent Entry Point
97
+ // ============================================
98
+
99
+ /**
100
+ * Handles an Agent tool_use chunk with minimal buffering to determine sync vs async.
101
+ * Returns a typed result so StreamController can update messages accordingly.
102
+ */
103
+ public handleTaskToolUse(
104
+ taskToolId: string,
105
+ taskInput: Record<string, unknown>,
106
+ currentContentEl: HTMLElement | null
107
+ ): HandleTaskResult {
108
+ // Already rendered as sync → update label (no parentEl needed)
109
+ const existingSyncState = this.syncSubagents.get(taskToolId);
110
+ if (existingSyncState) {
111
+ this.updateSubagentLabel(existingSyncState.wrapperEl, existingSyncState.info, taskInput);
112
+ return { action: 'label_updated' };
113
+ }
114
+
115
+ // Already rendered as async → update label (no parentEl needed)
116
+ const existingAsyncState = this.asyncDomStates.get(taskToolId);
117
+ if (existingAsyncState) {
118
+ this.updateSubagentLabel(existingAsyncState.wrapperEl, existingAsyncState.info, taskInput);
119
+ // Sync to canonical SubagentInfo so status transitions don't revert updates
120
+ const canonical = this.getByTaskId(taskToolId);
121
+ if (canonical && canonical !== existingAsyncState.info) {
122
+ if (taskInput.description) canonical.description = taskInput.description as string;
123
+ if (taskInput.prompt) canonical.prompt = taskInput.prompt as string;
124
+ }
125
+ return { action: 'label_updated' };
126
+ }
127
+
128
+ // Already buffered → merge input and try to render
129
+ const pending = this.pendingTasks.get(taskToolId);
130
+ if (pending) {
131
+ const newInput = taskInput || {};
132
+ if (Object.keys(newInput).length > 0) {
133
+ pending.toolCall.input = { ...pending.toolCall.input, ...newInput };
134
+ }
135
+ if (currentContentEl) {
136
+ pending.parentEl = currentContentEl;
137
+ }
138
+
139
+ // Do not lock mode before run_in_background is explicitly known.
140
+ // Sync fallback is handled when child chunks/tool_result confirm sync.
141
+ if (this.resolveTaskMode(pending.toolCall.input)) {
142
+ const result = this.renderPendingTask(taskToolId, currentContentEl);
143
+ if (result) {
144
+ return result.mode === 'sync'
145
+ ? { action: 'created_sync', subagentState: result.subagentState }
146
+ : { action: 'created_async', info: result.info, domState: result.domState };
147
+ }
148
+ }
149
+ return { action: 'buffered' };
150
+ }
151
+
152
+ // New Task without a content element — buffer for later rendering
153
+ if (!currentContentEl) {
154
+ const toolCall: ToolCallInfo = {
155
+ id: taskToolId,
156
+ name: TOOL_TASK,
157
+ input: taskInput || {},
158
+ status: 'running',
159
+ isExpanded: false,
160
+ };
161
+ this.pendingTasks.set(taskToolId, { toolCall, parentEl: null });
162
+ return { action: 'buffered' };
163
+ }
164
+
165
+ const mode = this.resolveTaskMode(taskInput);
166
+ if (!mode) {
167
+ const toolCall: ToolCallInfo = {
168
+ id: taskToolId,
169
+ name: TOOL_TASK,
170
+ input: taskInput || {},
171
+ status: 'running',
172
+ isExpanded: false,
173
+ };
174
+ this.pendingTasks.set(taskToolId, { toolCall, parentEl: currentContentEl });
175
+ return { action: 'buffered' };
176
+ }
177
+
178
+ this._spawnedThisStream++;
179
+ if (mode === 'async') {
180
+ return this.createAsyncTask(taskToolId, taskInput, currentContentEl);
181
+ }
182
+ return this.createSyncTask(taskToolId, taskInput, currentContentEl);
183
+ }
184
+
185
+ // ============================================
186
+ // Pending Task Resolution
187
+ // ============================================
188
+
189
+ public hasPendingTask(toolId: string): boolean {
190
+ return this.pendingTasks.has(toolId);
191
+ }
192
+
193
+ /**
194
+ * Renders a buffered pending task. Called when a child chunk or tool_result
195
+ * confirms the task is sync, or when run_in_background becomes known.
196
+ * Uses the optional parentEl override, falling back to the stored parentEl.
197
+ */
198
+ public renderPendingTask(
199
+ toolId: string,
200
+ parentElOverride?: HTMLElement | null
201
+ ): RenderPendingResult | null {
202
+ const pending = this.pendingTasks.get(toolId);
203
+ if (!pending) return null;
204
+
205
+ const input = pending.toolCall.input;
206
+ const targetEl = parentElOverride ?? pending.parentEl;
207
+ if (!targetEl) return null;
208
+
209
+ this.pendingTasks.delete(toolId);
210
+
211
+ try {
212
+ if (input.run_in_background === true) {
213
+ const result = this.createAsyncTask(pending.toolCall.id, input, targetEl);
214
+ if (result.action === 'created_async') {
215
+ this._spawnedThisStream++;
216
+ return { mode: 'async', info: result.info, domState: result.domState };
217
+ }
218
+ } else {
219
+ const result = this.createSyncTask(pending.toolCall.id, input, targetEl);
220
+ if (result.action === 'created_sync') {
221
+ this._spawnedThisStream++;
222
+ return { mode: 'sync', subagentState: result.subagentState };
223
+ }
224
+ }
225
+ } catch {
226
+ // Non-fatal: task appears incomplete but doesn't crash the stream
227
+ }
228
+
229
+ return null;
230
+ }
231
+
232
+ /**
233
+ * Resolves a pending Task when its own tool_result arrives.
234
+ * If mode is still unknown, infer async from task result shape (agent_id/agentId),
235
+ * otherwise fall back to sync so it never remains pending indefinitely.
236
+ */
237
+ public renderPendingTaskFromTaskResult(
238
+ toolId: string,
239
+ taskResult: unknown,
240
+ isError: boolean,
241
+ parentElOverride?: HTMLElement | null,
242
+ taskToolUseResult?: unknown
243
+ ): RenderPendingResult | null {
244
+ const pending = this.pendingTasks.get(toolId);
245
+ if (!pending) return null;
246
+
247
+ const input = pending.toolCall.input;
248
+ const targetEl = parentElOverride ?? pending.parentEl;
249
+ if (!targetEl) return null;
250
+
251
+ const explicitMode = this.resolveTaskMode(input);
252
+ const taskResultText = extractToolResultContent(taskResult, { fallbackIndent: 2 });
253
+ const inferredMode = explicitMode
254
+ ?? this.inferModeFromTaskResult(taskResultText, isError, taskToolUseResult);
255
+
256
+ this.pendingTasks.delete(toolId);
257
+
258
+ try {
259
+ if (inferredMode === 'async') {
260
+ const result = this.createAsyncTask(pending.toolCall.id, input, targetEl);
261
+ if (result.action === 'created_async') {
262
+ this._spawnedThisStream++;
263
+ return { mode: 'async', info: result.info, domState: result.domState };
264
+ }
265
+ } else {
266
+ const result = this.createSyncTask(pending.toolCall.id, input, targetEl);
267
+ if (result.action === 'created_sync') {
268
+ this._spawnedThisStream++;
269
+ return { mode: 'sync', subagentState: result.subagentState };
270
+ }
271
+ }
272
+ } catch {
273
+ // Non-fatal: task appears incomplete but doesn't crash the stream
274
+ }
275
+
276
+ return null;
277
+ }
278
+
279
+ // ============================================
280
+ // Sync Subagent Operations
281
+ // ============================================
282
+
283
+ public getSyncSubagent(toolId: string): SubagentState | undefined {
284
+ return this.syncSubagents.get(toolId);
285
+ }
286
+
287
+ public addSyncToolCall(parentToolUseId: string, toolCall: ToolCallInfo): void {
288
+ const subagentState = this.syncSubagents.get(parentToolUseId);
289
+ if (!subagentState) return;
290
+ addSubagentToolCall(subagentState, toolCall);
291
+ }
292
+
293
+ public updateSyncToolResult(
294
+ parentToolUseId: string,
295
+ toolId: string,
296
+ toolCall: ToolCallInfo
297
+ ): void {
298
+ const subagentState = this.syncSubagents.get(parentToolUseId);
299
+ if (!subagentState) return;
300
+ updateSubagentToolResult(subagentState, toolId, toolCall);
301
+ }
302
+
303
+ public finalizeSyncSubagent(
304
+ toolId: string,
305
+ result: unknown,
306
+ isError: boolean,
307
+ toolUseResult?: unknown
308
+ ): SubagentInfo | null {
309
+ const subagentState = this.syncSubagents.get(toolId);
310
+ if (!subagentState) return null;
311
+
312
+ const resultText = extractToolResultContent(result, { fallbackIndent: 2 });
313
+ const extractedResult = this.extractAgentResult(resultText, '', toolUseResult);
314
+ finalizeSubagentBlock(subagentState, extractedResult, isError);
315
+ this.syncSubagents.delete(toolId);
316
+
317
+ return subagentState.info;
318
+ }
319
+
320
+ // ============================================
321
+ // Async Subagent Lifecycle
322
+ // ============================================
323
+
324
+ public handleTaskToolResult(
325
+ taskToolId: string,
326
+ result: unknown,
327
+ isError?: boolean,
328
+ toolUseResult?: unknown
329
+ ): void {
330
+ const subagent = this.pendingAsyncSubagents.get(taskToolId);
331
+ if (!subagent) return;
332
+ const resultText = extractToolResultContent(result, { fallbackIndent: 2 });
333
+
334
+ if (isError) {
335
+ this.transitionToError(subagent, taskToolId, resultText || 'Task failed to start');
336
+ return;
337
+ }
338
+
339
+ const agentId = this.taskResultInterpreter.extractAgentId(toolUseResult) ?? this.parseAgentId(resultText);
340
+
341
+ if (!agentId) {
342
+ const truncatedResult = resultText.length > 100 ? resultText.substring(0, 100) + '...' : resultText;
343
+ this.transitionToError(subagent, taskToolId, `Failed to parse agent_id. Result: ${truncatedResult}`);
344
+ return;
345
+ }
346
+
347
+ subagent.asyncStatus = 'running';
348
+ subagent.agentId = agentId;
349
+ subagent.startedAt = Date.now();
350
+
351
+ this.pendingAsyncSubagents.delete(taskToolId);
352
+ this.activeAsyncSubagents.set(agentId, subagent);
353
+ this.taskIdToAgentId.set(taskToolId, agentId);
354
+
355
+ this.updateAsyncDomState(subagent);
356
+ this.onStateChange(subagent);
357
+ }
358
+
359
+ public handleAgentOutputToolUse(toolCall: ToolCallInfo): void {
360
+ const agentId = this.extractAgentIdFromInput(toolCall.input);
361
+ if (!agentId) return;
362
+
363
+ const subagent = this.activeAsyncSubagents.get(agentId);
364
+ if (!subagent) return;
365
+
366
+ subagent.outputToolId = toolCall.id;
367
+ this.outputToolIdToAgentId.set(toolCall.id, agentId);
368
+ }
369
+
370
+ public handleAgentOutputToolResult(
371
+ toolId: string,
372
+ result: unknown,
373
+ isError: boolean,
374
+ toolUseResult?: unknown
375
+ ): SubagentInfo | undefined {
376
+ const resultText = extractToolResultContent(result, { fallbackIndent: 2 });
377
+ let agentId = this.outputToolIdToAgentId.get(toolId);
378
+ let subagent = agentId ? this.activeAsyncSubagents.get(agentId) : undefined;
379
+
380
+ if (!subagent) {
381
+ const inferredAgentId = this.inferAgentIdFromResult(resultText);
382
+ if (inferredAgentId) {
383
+ agentId = inferredAgentId;
384
+ subagent = this.activeAsyncSubagents.get(inferredAgentId);
385
+ }
386
+ }
387
+
388
+ if (!subagent) return undefined;
389
+
390
+ if (agentId) {
391
+ subagent.agentId = subagent.agentId || agentId;
392
+ this.outputToolIdToAgentId.set(toolId, agentId);
393
+ }
394
+
395
+ if (subagent.asyncStatus !== 'running') {
396
+ return undefined;
397
+ }
398
+
399
+ const stillRunning = this.isStillRunningResult(resultText, isError);
400
+ if (stillRunning) {
401
+ this.outputToolIdToAgentId.delete(toolId);
402
+ return subagent;
403
+ }
404
+
405
+ const extractedResult = this.extractAgentResult(resultText, agentId ?? '', toolUseResult);
406
+
407
+ // The chunk's is_error flag can be unreliable for async subagent results
408
+ // (SDK may set is_error on the content block even when the agent succeeded).
409
+ // Prefer the structured toolUseResult to determine actual error status.
410
+ const finalStatus = this.taskResultInterpreter.resolveTerminalStatus(
411
+ toolUseResult,
412
+ isError ? 'error' : 'completed',
413
+ );
414
+
415
+ subagent.asyncStatus = finalStatus;
416
+ subagent.status = finalStatus;
417
+ subagent.result = extractedResult;
418
+ subagent.completedAt = Date.now();
419
+
420
+ if (agentId) this.activeAsyncSubagents.delete(agentId);
421
+ this.outputToolIdToAgentId.delete(toolId);
422
+
423
+ this.updateAsyncDomState(subagent);
424
+ this.onStateChange(subagent);
425
+ return subagent;
426
+ }
427
+
428
+ public handleAsyncSubagentResult(
429
+ agentId: string,
430
+ status: 'completed' | 'error',
431
+ result?: string
432
+ ): SubagentInfo | undefined {
433
+ const subagent = this.activeAsyncSubagents.get(agentId);
434
+ if (!subagent || subagent.asyncStatus !== 'running') {
435
+ return undefined;
436
+ }
437
+
438
+ subagent.agentId = subagent.agentId || agentId;
439
+ subagent.asyncStatus = status;
440
+ subagent.status = status;
441
+ subagent.result = result?.trim() || (status === 'error' ? 'Background task failed.' : 'Background task completed.');
442
+ subagent.completedAt = Date.now();
443
+
444
+ this.activeAsyncSubagents.delete(agentId);
445
+ for (const [toolId, mappedAgentId] of this.outputToolIdToAgentId.entries()) {
446
+ if (mappedAgentId === agentId) {
447
+ this.outputToolIdToAgentId.delete(toolId);
448
+ }
449
+ }
450
+
451
+ this.updateAsyncDomState(subagent);
452
+ this.onStateChange(subagent);
453
+ return subagent;
454
+ }
455
+
456
+ public isPendingAsyncTask(taskToolId: string): boolean {
457
+ return this.pendingAsyncSubagents.has(taskToolId);
458
+ }
459
+
460
+ public isLinkedAgentOutputTool(toolId: string): boolean {
461
+ return this.outputToolIdToAgentId.has(toolId);
462
+ }
463
+
464
+ public getByTaskId(taskToolId: string): SubagentInfo | undefined {
465
+ const pending = this.pendingAsyncSubagents.get(taskToolId);
466
+ if (pending) return pending;
467
+
468
+ const agentId = this.taskIdToAgentId.get(taskToolId);
469
+ if (agentId) {
470
+ return this.activeAsyncSubagents.get(agentId);
471
+ }
472
+
473
+ return undefined;
474
+ }
475
+
476
+ /**
477
+ * Re-renders an async subagent after data-only updates (for example,
478
+ * hydrating tool calls from SDK sidecar files) without changing lifecycle state.
479
+ */
480
+ public refreshAsyncSubagent(subagent: SubagentInfo): void {
481
+ this.updateAsyncDomState(subagent);
482
+ this.onStateChange(subagent);
483
+ }
484
+
485
+ // ============================================
486
+ // Hook State
487
+ // ============================================
488
+
489
+ public hasRunningSubagents(): boolean {
490
+ // pendingAsyncSubagents: awaiting agent_id; activeAsyncSubagents: only holds running entries
491
+ return this.pendingAsyncSubagents.size > 0 || this.activeAsyncSubagents.size > 0;
492
+ }
493
+
494
+ // ============================================
495
+ // Lifecycle
496
+ // ============================================
497
+
498
+ public get subagentsSpawnedThisStream(): number {
499
+ return this._spawnedThisStream;
500
+ }
501
+
502
+ public resetSpawnedCount(): void {
503
+ this._spawnedThisStream = 0;
504
+ }
505
+
506
+ public resetStreamingState(): void {
507
+ this.syncSubagents.clear();
508
+ this.pendingTasks.clear();
509
+ }
510
+
511
+ public orphanAllActive(): SubagentInfo[] {
512
+ const orphaned: SubagentInfo[] = [];
513
+
514
+ for (const subagent of this.pendingAsyncSubagents.values()) {
515
+ this.markOrphaned(subagent);
516
+ orphaned.push(subagent);
517
+ }
518
+
519
+ for (const subagent of this.activeAsyncSubagents.values()) {
520
+ if (subagent.asyncStatus === 'running') {
521
+ this.markOrphaned(subagent);
522
+ orphaned.push(subagent);
523
+ }
524
+ }
525
+
526
+ this.pendingAsyncSubagents.clear();
527
+ this.activeAsyncSubagents.clear();
528
+ this.taskIdToAgentId.clear();
529
+ this.outputToolIdToAgentId.clear();
530
+
531
+ return orphaned;
532
+ }
533
+
534
+ public clear(): void {
535
+ this.syncSubagents.clear();
536
+ this.pendingTasks.clear();
537
+ this.pendingAsyncSubagents.clear();
538
+ this.activeAsyncSubagents.clear();
539
+ this.taskIdToAgentId.clear();
540
+ this.outputToolIdToAgentId.clear();
541
+ this.asyncDomStates.clear();
542
+ }
543
+
544
+ // ============================================
545
+ // Private: State Transitions
546
+ // ============================================
547
+
548
+ private markOrphaned(subagent: SubagentInfo): void {
549
+ subagent.asyncStatus = 'orphaned';
550
+ subagent.status = 'error';
551
+ subagent.result = 'Conversation ended before task completed';
552
+ subagent.completedAt = Date.now();
553
+ this.updateAsyncDomState(subagent);
554
+ this.onStateChange(subagent);
555
+ }
556
+
557
+ private transitionToError(subagent: SubagentInfo, taskToolId: string, errorResult: string): void {
558
+ subagent.asyncStatus = 'error';
559
+ subagent.status = 'error';
560
+ subagent.result = errorResult;
561
+ subagent.completedAt = Date.now();
562
+ this.pendingAsyncSubagents.delete(taskToolId);
563
+ this.updateAsyncDomState(subagent);
564
+ this.onStateChange(subagent);
565
+ }
566
+
567
+ // ============================================
568
+ // Private: Task Creation
569
+ // ============================================
570
+
571
+ private createSyncTask(
572
+ taskToolId: string,
573
+ taskInput: Record<string, unknown>,
574
+ parentEl: HTMLElement
575
+ ): HandleTaskResult {
576
+ const subagentState = createSubagentBlock(parentEl, taskToolId, taskInput);
577
+ this.syncSubagents.set(taskToolId, subagentState);
578
+ return { action: 'created_sync', subagentState };
579
+ }
580
+
581
+ private createAsyncTask(
582
+ taskToolId: string,
583
+ taskInput: Record<string, unknown>,
584
+ parentEl: HTMLElement
585
+ ): HandleTaskResult {
586
+ const description = (taskInput.description as string) || 'Background task';
587
+ const prompt = (taskInput.prompt as string) || '';
588
+
589
+ const info: SubagentInfo = {
590
+ id: taskToolId,
591
+ description,
592
+ prompt,
593
+ mode: 'async',
594
+ isExpanded: false,
595
+ status: 'running',
596
+ toolCalls: [],
597
+ asyncStatus: 'pending',
598
+ };
599
+
600
+ this.pendingAsyncSubagents.set(taskToolId, info);
601
+
602
+ const domState = createAsyncSubagentBlock(parentEl, taskToolId, taskInput);
603
+ this.asyncDomStates.set(taskToolId, domState);
604
+
605
+ return { action: 'created_async', info, domState };
606
+ }
607
+
608
+ // ============================================
609
+ // Private: Label Update
610
+ // ============================================
611
+
612
+ private updateSubagentLabel(
613
+ wrapperEl: HTMLElement,
614
+ info: SubagentInfo,
615
+ newInput: Record<string, unknown>
616
+ ): void {
617
+ if (!newInput || Object.keys(newInput).length === 0) return;
618
+ const description = (newInput.description as string) || '';
619
+ if (description) {
620
+ info.description = description;
621
+ const labelEl = wrapperEl.querySelector('.claudian-subagent-label');
622
+ if (labelEl) {
623
+ const truncated = description.length > 40 ? description.substring(0, 40) + '...' : description;
624
+ labelEl.setText(truncated);
625
+ }
626
+ }
627
+ const prompt = (newInput.prompt as string) || '';
628
+ if (prompt) {
629
+ info.prompt = prompt;
630
+ const promptEl = wrapperEl.querySelector('.claudian-subagent-prompt-text');
631
+ if (promptEl) {
632
+ promptEl.setText(prompt);
633
+ }
634
+ }
635
+ }
636
+
637
+ private resolveTaskMode(taskInput: Record<string, unknown>): 'sync' | 'async' | null {
638
+ if (!Object.prototype.hasOwnProperty.call(taskInput, 'run_in_background')) {
639
+ return null;
640
+ }
641
+ if (taskInput.run_in_background === true) {
642
+ return 'async';
643
+ }
644
+ if (taskInput.run_in_background === false) {
645
+ return 'sync';
646
+ }
647
+ return null;
648
+ }
649
+
650
+ private inferModeFromTaskResult(
651
+ taskResult: string,
652
+ isError: boolean,
653
+ taskToolUseResult?: unknown
654
+ ): 'sync' | 'async' {
655
+ if (isError) {
656
+ return 'sync';
657
+ }
658
+ if (this.taskResultInterpreter.hasAsyncLaunchMarker(taskToolUseResult)) {
659
+ return 'async';
660
+ }
661
+ // Only promote to async for launch-shaped payloads. Completed sync results
662
+ // can still contain agent metadata in the payload or final output text.
663
+ return this.parseAgentIdStrict(taskResult) ? 'async' : 'sync';
664
+ }
665
+
666
+ private parseAgentIdStrict(result: string): string | null {
667
+ const payload = this.unwrapTextPayload(result).trim();
668
+ if (!payload) {
669
+ return null;
670
+ }
671
+
672
+ const parsed = parseJsonRecord(payload);
673
+ if (parsed) {
674
+ if (this.hasTerminalTaskStatus(parsed)) {
675
+ return null;
676
+ }
677
+
678
+ const directAgentId = this.extractAgentIdFromRecord(parsed);
679
+ if (directAgentId) {
680
+ return directAgentId;
681
+ }
682
+
683
+ const taskRecord = parsed.task;
684
+ if (isRecord(taskRecord)) {
685
+ return this.extractAgentIdFromRecord(taskRecord);
686
+ }
687
+ }
688
+
689
+ const xmlStatus = this.taskResultInterpreter.extractTagValue(payload, 'retrieval_status')
690
+ ?? this.taskResultInterpreter.extractTagValue(payload, 'status');
691
+ if (this.isTerminalTaskStatusValue(xmlStatus)) {
692
+ return null;
693
+ }
694
+
695
+ const exactLineMatch = payload.match(/^\s*(?:agent_id|agentId)\s*[=:]\s*"?([a-zA-Z0-9_-]+)"?\s*$/i);
696
+ return exactLineMatch?.[1] ?? null;
697
+ }
698
+
699
+ private hasTerminalTaskStatus(value: unknown): boolean {
700
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
701
+ return false;
702
+ }
703
+
704
+ const record = value as Record<string, unknown>;
705
+ const rawStatus = record.retrieval_status ?? record.status;
706
+ return this.isTerminalTaskStatusValue(rawStatus);
707
+ }
708
+
709
+ private isTerminalTaskStatusValue(rawStatus: unknown): boolean {
710
+ if (typeof rawStatus !== 'string') {
711
+ return false;
712
+ }
713
+
714
+ const normalized = rawStatus.toLowerCase();
715
+ return normalized === 'completed' || normalized === 'success' || normalized === 'error';
716
+ }
717
+
718
+ private extractAgentIdFromRecord(record: Record<string, unknown>): string | null {
719
+ const direct = record.agent_id ?? record.agentId;
720
+ if (typeof direct === 'string' && direct.length > 0) {
721
+ return direct;
722
+ }
723
+
724
+ const data = record.data;
725
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
726
+ return null;
727
+ }
728
+
729
+ const nested = (data as Record<string, unknown>).agent_id ?? (data as Record<string, unknown>).agentId;
730
+ return typeof nested === 'string' && nested.length > 0 ? nested : null;
731
+ }
732
+
733
+ private extractAgentIdFromString(value: string): string | null {
734
+ const regexPatterns = [
735
+ /"agent_id"\s*:\s*"([^"]+)"/,
736
+ /"agentId"\s*:\s*"([^"]+)"/,
737
+ /agent_id[=:]\s*"?([a-zA-Z0-9_-]+)"?/i,
738
+ /agentId[=:]\s*"?([a-zA-Z0-9_-]+)"?/i,
739
+ ];
740
+
741
+ for (const pattern of regexPatterns) {
742
+ const match = value.match(pattern);
743
+ if (match && match[1]) {
744
+ return match[1];
745
+ }
746
+ }
747
+
748
+ return null;
749
+ }
750
+
751
+ // ============================================
752
+ // Private: Async DOM State Updates
753
+ // ============================================
754
+
755
+ private updateAsyncDomState(subagent: SubagentInfo): void {
756
+ // Find DOM state by task ID first, then by agentId
757
+ let asyncState = this.asyncDomStates.get(subagent.id);
758
+
759
+ if (!asyncState) {
760
+ for (const s of this.asyncDomStates.values()) {
761
+ if (s.info.agentId === subagent.agentId) {
762
+ asyncState = s;
763
+ break;
764
+ }
765
+ }
766
+ if (!asyncState) return;
767
+ }
768
+
769
+ asyncState.info = subagent;
770
+
771
+ switch (subagent.asyncStatus) {
772
+ case 'running':
773
+ updateAsyncSubagentRunning(asyncState, subagent.agentId || '');
774
+ break;
775
+
776
+ case 'completed':
777
+ case 'error':
778
+ finalizeAsyncSubagent(asyncState, subagent.result || '', subagent.asyncStatus === 'error');
779
+ break;
780
+
781
+ case 'orphaned':
782
+ markAsyncSubagentOrphaned(asyncState);
783
+ break;
784
+ }
785
+ }
786
+
787
+ // ============================================
788
+ // Private: Async Parsing Logic
789
+ // ============================================
790
+
791
+ private isStillRunningResult(result: string, isError: boolean): boolean {
792
+ const trimmed = result?.trim() || '';
793
+ const payload = this.unwrapTextPayload(trimmed);
794
+
795
+ if (isError) return false;
796
+ if (!trimmed) return false;
797
+
798
+ const parsed = parseJsonRecord(payload);
799
+ if (parsed) {
800
+ const status = parsed.retrieval_status ?? parsed.status;
801
+ const agents = isRecord(parsed.agents) ? parsed.agents : null;
802
+ const hasAgents = agents !== null && Object.keys(agents).length > 0;
803
+
804
+ if (status === 'not_ready' || status === 'running' || status === 'pending') {
805
+ return true;
806
+ }
807
+
808
+ if (hasAgents && agents) {
809
+ const agentStatuses = Object.values(agents)
810
+ .map((agent) => (isRecord(agent) && typeof agent.status === 'string') ? agent.status.toLowerCase() : '');
811
+ const anyRunning = agentStatuses.some(s =>
812
+ s === 'running' || s === 'pending' || s === 'not_ready'
813
+ );
814
+ if (anyRunning) return true;
815
+ return false;
816
+ }
817
+
818
+ if (status === 'success' || status === 'completed') {
819
+ return false;
820
+ }
821
+
822
+ return false;
823
+ }
824
+
825
+ const lowerResult = payload.toLowerCase();
826
+ if (lowerResult.includes('not_ready') || lowerResult.includes('not ready')) {
827
+ return true;
828
+ }
829
+
830
+ const xmlStatusMatch = lowerResult.match(/<status>([^<]+)<\/status>/);
831
+ if (xmlStatusMatch) {
832
+ const status = xmlStatusMatch[1].trim();
833
+ if (status === 'running' || status === 'pending' || status === 'not_ready') {
834
+ return true;
835
+ }
836
+ }
837
+
838
+ return false;
839
+ }
840
+
841
+ private extractAgentResult(result: string, agentId: string, toolUseResult?: unknown): string {
842
+ const structuredResult = this.taskResultInterpreter.extractStructuredResult(toolUseResult);
843
+ const normalizedStructuredResult = this.extractResultFromCandidateString(structuredResult);
844
+ if (normalizedStructuredResult) {
845
+ return normalizedStructuredResult;
846
+ }
847
+ if (structuredResult) {
848
+ return structuredResult;
849
+ }
850
+
851
+ const payload = this.unwrapTextPayload(result);
852
+
853
+ const parsed = parseJsonRecord(payload);
854
+ if (parsed) {
855
+ const taskResult = this.extractResultFromTaskObject(parsed.task);
856
+ if (taskResult) {
857
+ return taskResult;
858
+ }
859
+
860
+ const agents = isRecord(parsed.agents) ? parsed.agents : null;
861
+ const agentData = agents && agentId ? agents[agentId] : null;
862
+ if (isRecord(agentData)) {
863
+ const parsedResult = this.extractResultFromCandidateString(agentData.result);
864
+ if (parsedResult) {
865
+ return parsedResult;
866
+ }
867
+ const parsedOutput = this.extractResultFromCandidateString(agentData.output);
868
+ if (parsedOutput) {
869
+ return parsedOutput;
870
+ }
871
+ return JSON.stringify(agentData, null, 2);
872
+ }
873
+
874
+ if (agents) {
875
+ const agentIds = Object.keys(agents);
876
+ if (agentIds.length > 0) {
877
+ const firstAgent = agents[agentIds[0]];
878
+ if (isRecord(firstAgent)) {
879
+ const parsedResult = this.extractResultFromCandidateString(firstAgent.result);
880
+ if (parsedResult) {
881
+ return parsedResult;
882
+ }
883
+ const parsedOutput = this.extractResultFromCandidateString(firstAgent.output);
884
+ if (parsedOutput) {
885
+ return parsedOutput;
886
+ }
887
+ }
888
+ return JSON.stringify(firstAgent, null, 2);
889
+ }
890
+ }
891
+
892
+ const parsedResult = this.extractResultFromCandidateString(parsed.result);
893
+ if (parsedResult) {
894
+ return parsedResult;
895
+ }
896
+
897
+ const parsedOutput = this.extractResultFromCandidateString(parsed.output);
898
+ if (parsedOutput) {
899
+ return parsedOutput;
900
+ }
901
+ }
902
+
903
+ const taggedResult = this.extractResultFromTaggedPayload(payload);
904
+ if (taggedResult) {
905
+ return taggedResult;
906
+ }
907
+
908
+ return payload;
909
+ }
910
+
911
+ private extractResultFromTaskObject(task: unknown): string | null {
912
+ if (!task || typeof task !== 'object') {
913
+ return null;
914
+ }
915
+ const taskRecord = task as Record<string, unknown>;
916
+ return this.extractResultFromCandidateString(taskRecord.result)
917
+ ?? this.extractResultFromCandidateString(taskRecord.output);
918
+ }
919
+
920
+ private extractResultFromCandidateString(candidate: unknown): string | null {
921
+ if (typeof candidate !== 'string') {
922
+ return null;
923
+ }
924
+
925
+ const trimmed = candidate.trim();
926
+ if (!trimmed) {
927
+ return null;
928
+ }
929
+
930
+ const taggedResult = this.extractResultFromTaggedPayload(trimmed);
931
+ if (taggedResult) {
932
+ return taggedResult;
933
+ }
934
+
935
+ const jsonlResult = this.extractResultFromOutputJsonl(trimmed);
936
+ if (jsonlResult) {
937
+ return jsonlResult;
938
+ }
939
+
940
+ return trimmed;
941
+ }
942
+
943
+ private parseAgentId(result: string): string | null {
944
+ const regexPatterns = [
945
+ /"agent_id"\s*:\s*"([^"]+)"/,
946
+ /"agentId"\s*:\s*"([^"]+)"/,
947
+ /agent_id[=:]\s*"?([a-zA-Z0-9_-]+)"?/i,
948
+ /agentId[=:]\s*"?([a-zA-Z0-9_-]+)"?/i,
949
+ /\b([a-f0-9]{8})\b/,
950
+ ];
951
+
952
+ for (const pattern of regexPatterns) {
953
+ const match = result.match(pattern);
954
+ if (match && match[1]) {
955
+ return match[1];
956
+ }
957
+ }
958
+
959
+ const parsed = parseJsonRecord(result);
960
+ if (parsed) {
961
+ const agentId = parsed.agent_id || parsed.agentId;
962
+
963
+ if (typeof agentId === 'string' && agentId.length > 0) {
964
+ return agentId;
965
+ }
966
+
967
+ const data = parsed.data;
968
+ if (isRecord(data) && typeof data.agent_id === 'string') {
969
+ return data.agent_id;
970
+ }
971
+
972
+ if (parsed.id && typeof parsed.id === 'string') {
973
+ return parsed.id;
974
+ }
975
+ }
976
+
977
+ return null;
978
+ }
979
+
980
+ private inferAgentIdFromResult(result: string): string | null {
981
+ const parsed = parseJsonRecord(result);
982
+ if (parsed) {
983
+ const agents = isRecord(parsed.agents) ? parsed.agents : null;
984
+ if (agents) {
985
+ return Object.keys(agents)[0] ?? null;
986
+ }
987
+ }
988
+ return null;
989
+ }
990
+
991
+ private unwrapTextPayload(raw: string): string {
992
+ const parsed = parseJsonValue(raw);
993
+ if (parsed !== null) {
994
+ if (Array.isArray(parsed)) {
995
+ const textBlock = (parsed as unknown[]).find((block) => isRecord(block) && typeof block.text === 'string');
996
+ if (isRecord(textBlock) && typeof textBlock.text === 'string') return textBlock.text;
997
+ } else if (isRecord(parsed) && typeof parsed.text === 'string') {
998
+ return parsed.text;
999
+ }
1000
+ }
1001
+ return raw;
1002
+ }
1003
+
1004
+ private extractResultFromTaggedPayload(payload: string): string | null {
1005
+ const directResult = this.taskResultInterpreter.extractTagValue(payload, 'result');
1006
+ if (directResult) return directResult;
1007
+
1008
+ const outputContent = this.taskResultInterpreter.extractTagValue(payload, 'output');
1009
+ if (!outputContent) return null;
1010
+
1011
+ const extractedFromJsonl = this.extractResultFromOutputJsonl(outputContent);
1012
+ if (extractedFromJsonl) return extractedFromJsonl;
1013
+
1014
+ const nestedResult = this.taskResultInterpreter.extractTagValue(outputContent, 'result');
1015
+ if (nestedResult) return nestedResult;
1016
+
1017
+ const trimmed = outputContent.trim();
1018
+ return trimmed.length > 0 ? trimmed : null;
1019
+ }
1020
+
1021
+ private extractResultFromOutputJsonl(outputContent: string): string | null {
1022
+ const inlineResult = extractFinalResultFromSubagentJsonl(outputContent);
1023
+ if (inlineResult) {
1024
+ return inlineResult;
1025
+ }
1026
+
1027
+ const fullOutputPath = this.extractFullOutputPath(outputContent);
1028
+ if (!fullOutputPath) {
1029
+ return null;
1030
+ }
1031
+
1032
+ const fullOutput = this.readFullOutputFile(fullOutputPath);
1033
+ if (!fullOutput) {
1034
+ return null;
1035
+ }
1036
+
1037
+ return extractFinalResultFromSubagentJsonl(fullOutput);
1038
+ }
1039
+
1040
+ private extractFullOutputPath(content: string): string | null {
1041
+ const truncatedPattern = /\[Truncated\.\s*Full output:\s*([^\]\n]+)\]/i;
1042
+ const match = content.match(truncatedPattern);
1043
+ if (!match || !match[1]) {
1044
+ return null;
1045
+ }
1046
+
1047
+ const outputPath = match[1].trim();
1048
+ return outputPath.length > 0 ? outputPath : null;
1049
+ }
1050
+
1051
+ private readFullOutputFile(fullOutputPath: string): string | null {
1052
+ try {
1053
+ if (!this.isTrustedOutputPath(fullOutputPath)) {
1054
+ return null;
1055
+ }
1056
+
1057
+ if (!existsSync(fullOutputPath)) {
1058
+ return null;
1059
+ }
1060
+
1061
+ const fileContent = readFileSync(fullOutputPath, 'utf-8');
1062
+ const trimmed = fileContent.trim();
1063
+ return trimmed.length > 0 ? trimmed : null;
1064
+ } catch {
1065
+ return null;
1066
+ }
1067
+ }
1068
+
1069
+ private extractAgentIdFromInput(input: Record<string, unknown>): string | null {
1070
+ const agentId = (input.task_id as string) || (input.agentId as string) || (input.agent_id as string);
1071
+ return agentId || null;
1072
+ }
1073
+
1074
+ private static resolveTrustedTmpRoots(): string[] {
1075
+ const roots = new Set<string>();
1076
+ const candidates = [tmpdir(), '/tmp', '/private/tmp'];
1077
+ for (const candidate of candidates) {
1078
+ try {
1079
+ roots.add(realpathSync(candidate));
1080
+ } catch {
1081
+ // Ignore unavailable temp roots.
1082
+ }
1083
+ }
1084
+ return Array.from(roots);
1085
+ }
1086
+
1087
+ private isTrustedOutputPath(fullOutputPath: string): boolean {
1088
+ if (!isAbsolute(fullOutputPath)) {
1089
+ return false;
1090
+ }
1091
+
1092
+ if (!fullOutputPath.toLowerCase().endsWith(SubagentManager.TRUSTED_OUTPUT_EXT)) {
1093
+ return false;
1094
+ }
1095
+
1096
+ let resolvedPath: string;
1097
+ try {
1098
+ resolvedPath = realpathSync(fullOutputPath);
1099
+ } catch {
1100
+ return false;
1101
+ }
1102
+
1103
+ return SubagentManager.TRUSTED_TMP_ROOTS.some((root) =>
1104
+ resolvedPath === root || resolvedPath.startsWith(`${root}${sep}`)
1105
+ );
1106
+ }
1107
+ }