minimal-agent 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (573) hide show
  1. package/README.md +11 -3
  2. package/package.json +52 -5
  3. package/plugins/computer_use/.claude-plugin/plugin.json +6 -0
  4. package/plugins/computer_use/README.md +94 -0
  5. package/plugins/computer_use/app/_template.md +142 -0
  6. package/plugins/computer_use/app/chrome.md +165 -0
  7. package/plugins/computer_use/app/finder.md +76 -0
  8. package/plugins/computer_use/app/macos-system-settings.md +81 -0
  9. package/plugins/computer_use/app/microsoft-excel.md +80 -0
  10. package/plugins/computer_use/app/microsoft-powerpoint.md +80 -0
  11. package/plugins/computer_use/app/microsoft-word.md +79 -0
  12. package/plugins/computer_use/app/wechat.md +413 -0
  13. package/plugins/computer_use/app/weread.md +319 -0
  14. package/plugins/computer_use/commands/computeruse.md +25 -0
  15. package/plugins/computer_use/commands/cu.md +9 -0
  16. package/plugins/computer_use/plugin.js +26 -0
  17. package/plugins/computer_use/plugin.ts +30 -0
  18. package/plugins/computer_use/src/capture.js +90 -0
  19. package/plugins/computer_use/src/capture.ts +104 -0
  20. package/plugins/computer_use/src/commands.js +91 -0
  21. package/plugins/computer_use/src/commands.ts +103 -0
  22. package/plugins/computer_use/src/daemon.js +134 -0
  23. package/plugins/computer_use/src/daemon.ts +161 -0
  24. package/plugins/computer_use/src/manuals.js +135 -0
  25. package/plugins/computer_use/src/manuals.ts +159 -0
  26. package/plugins/computer_use/src/verify.js +168 -0
  27. package/plugins/computer_use/src/verify.ts +219 -0
  28. package/plugins/computer_use/test/commands.test.ts +73 -0
  29. package/plugins/cron-runner/plugin.ts +38 -0
  30. package/plugins/cron-runner/src/commands.ts +373 -0
  31. package/plugins/cron-runner/src/cronTime.ts +155 -0
  32. package/plugins/cron-runner/src/lock.ts +79 -0
  33. package/plugins/cron-runner/src/runs.ts +99 -0
  34. package/plugins/cron-runner/src/scheduleEngine.ts +76 -0
  35. package/plugins/cron-runner/src/scheduler.ts +376 -0
  36. package/plugins/cron-runner/src/schema.ts +139 -0
  37. package/plugins/cron-runner/src/service.ts +193 -0
  38. package/plugins/cron-runner/src/store.ts +333 -0
  39. package/plugins/cron-runner/src/types.ts +141 -0
  40. package/plugins/cron-runner/test/cronTime.test.ts +41 -0
  41. package/plugins/cron-runner/test/plugin-integration.test.ts +47 -0
  42. package/plugins/cron-runner/test/scheduleEngine.test.ts +41 -0
  43. package/plugins/cron-runner/test/service.test.ts +76 -0
  44. package/plugins/cron-runner/test/store-and-scheduler.test.ts +501 -0
  45. package/plugins/workflow-runner/plugin.js +56 -2
  46. package/plugins/workflow-runner/plugin.ts +113 -0
  47. package/plugins/workflow-runner/src/agentCommon.js +43 -4
  48. package/plugins/workflow-runner/src/agentCommon.ts +435 -0
  49. package/plugins/workflow-runner/src/controlFlow/branch.js +40 -0
  50. package/plugins/workflow-runner/src/controlFlow/branch.ts +46 -0
  51. package/plugins/workflow-runner/src/controlFlow/loop.js +211 -0
  52. package/plugins/workflow-runner/src/controlFlow/loop.ts +248 -0
  53. package/plugins/workflow-runner/src/controlFlow/pause.js +129 -0
  54. package/plugins/workflow-runner/src/controlFlow/pause.ts +141 -0
  55. package/plugins/workflow-runner/src/controlFlow/signal.js +21 -0
  56. package/plugins/workflow-runner/src/controlFlow/signal.ts +29 -0
  57. package/plugins/workflow-runner/src/controlFlow/types.js +18 -0
  58. package/plugins/workflow-runner/src/controlFlow/types.ts +63 -0
  59. package/plugins/workflow-runner/src/dslManifest.ts +127 -0
  60. package/plugins/workflow-runner/src/expressions.ts +622 -0
  61. package/plugins/workflow-runner/src/index.js +13 -4
  62. package/plugins/workflow-runner/src/index.ts +338 -0
  63. package/plugins/workflow-runner/src/jsonPayload.ts +84 -0
  64. package/plugins/workflow-runner/src/loader.js +23 -0
  65. package/plugins/workflow-runner/src/loader.ts +721 -0
  66. package/plugins/workflow-runner/src/multimodal.js +2 -1
  67. package/plugins/workflow-runner/src/multimodal.ts +64 -0
  68. package/plugins/workflow-runner/src/recovery.ts +149 -0
  69. package/plugins/workflow-runner/src/runner.js +75 -659
  70. package/plugins/workflow-runner/src/runner.ts +604 -0
  71. package/plugins/workflow-runner/src/stepExecutors/agent.ts +234 -0
  72. package/plugins/workflow-runner/src/stepExecutors/assert.ts +50 -0
  73. package/plugins/workflow-runner/src/stepExecutors/echo.js +31 -0
  74. package/plugins/workflow-runner/src/stepExecutors/echo.ts +38 -0
  75. package/plugins/workflow-runner/src/stepExecutors/llm.ts +133 -0
  76. package/plugins/workflow-runner/src/stepExecutors/set.js +49 -0
  77. package/plugins/workflow-runner/src/stepExecutors/set.ts +56 -0
  78. package/plugins/workflow-runner/src/stepExecutors/skill.js +5 -6
  79. package/plugins/workflow-runner/src/stepExecutors/skill.ts +140 -0
  80. package/plugins/workflow-runner/src/stepExecutors/tool.js +80 -8
  81. package/plugins/workflow-runner/src/stepExecutors/tool.ts +256 -0
  82. package/plugins/workflow-runner/src/stepExecutors/verify.ts +68 -0
  83. package/plugins/workflow-runner/src/stepPipeline.js +277 -0
  84. package/plugins/workflow-runner/src/stepPipeline.ts +366 -0
  85. package/plugins/workflow-runner/src/taskIdentity.ts +405 -0
  86. package/plugins/workflow-runner/src/toolRuntime.js +48 -0
  87. package/plugins/workflow-runner/src/toolRuntime.ts +64 -0
  88. package/plugins/workflow-runner/src/types.js +37 -1
  89. package/plugins/workflow-runner/src/types.ts +602 -0
  90. package/plugins/workflow-runner/src/workflowState.ts +733 -0
  91. package/plugins/workflow-runner/test/agent.test.ts +587 -0
  92. package/plugins/workflow-runner/test/agentLlmError.test.ts +373 -0
  93. package/plugins/workflow-runner/test/bashBlacklist.sweep.test.ts +65 -0
  94. package/plugins/workflow-runner/test/captureMisbind.test.ts +99 -0
  95. package/plugins/workflow-runner/test/cli.e2e.test.ts +147 -0
  96. package/plugins/workflow-runner/test/context-files.test.ts +320 -0
  97. package/plugins/workflow-runner/test/e2e.test.ts +370 -0
  98. package/plugins/workflow-runner/test/engineRobustness.test.ts +226 -0
  99. package/plugins/workflow-runner/test/expressions.test.ts +397 -0
  100. package/plugins/workflow-runner/test/foundation.test.ts +126 -0
  101. package/plugins/workflow-runner/test/graceful.test.ts +139 -0
  102. package/plugins/workflow-runner/test/greedyInputs.load.test.ts +47 -0
  103. package/plugins/workflow-runner/test/hostCapability.test.ts +108 -0
  104. package/plugins/workflow-runner/test/illustrate.load.test.ts +183 -0
  105. package/plugins/workflow-runner/test/jsonPayload.test.ts +83 -0
  106. package/plugins/workflow-runner/test/loader.test.ts +617 -0
  107. package/plugins/workflow-runner/test/maxTurnsRetryDelay.test.ts +33 -0
  108. package/plugins/workflow-runner/test/multimodal.test.ts +342 -0
  109. package/plugins/workflow-runner/test/output-schema.test.ts +213 -0
  110. package/plugins/workflow-runner/test/parseWorkflowArgs.test.ts +87 -0
  111. package/plugins/workflow-runner/test/pluginRunner.isolation.test.ts +269 -0
  112. package/plugins/workflow-runner/test/ppt.load.test.ts +1220 -0
  113. package/plugins/workflow-runner/test/ralphLoop.e2e.test.ts +220 -0
  114. package/plugins/workflow-runner/test/recovery.test.ts +265 -0
  115. package/plugins/workflow-runner/test/runner.test.ts +911 -0
  116. package/plugins/workflow-runner/test/runnerPreflight.test.ts +379 -0
  117. package/plugins/workflow-runner/test/skillTurnDone.test.ts +71 -0
  118. package/plugins/workflow-runner/test/stateLifecycle.test.ts +150 -0
  119. package/plugins/workflow-runner/test/stepPipeline.test.ts +114 -0
  120. package/plugins/workflow-runner/test/taskArbitration.test.ts +338 -0
  121. package/plugins/workflow-runner/test/taskIdentity.test.ts +237 -0
  122. package/plugins/workflow-runner/test/taskLifecycle.test.ts +270 -0
  123. package/plugins/workflow-runner/test/toolArtifactOverwrite.test.ts +221 -0
  124. package/plugins/workflow-runner/test/toolCapabilityBoundary.test.ts +29 -0
  125. package/plugins/workflow-runner/test/toolRuntimeIntegration.test.ts +168 -0
  126. package/plugins/workflow-runner/test/webppt.load.test.ts +250 -0
  127. package/plugins/workflow-runner/test/word.load.test.ts +208 -0
  128. package/plugins/workflow-runner/test/workflows.smoke.test.ts +76 -0
  129. package/plugins/workflow-runner/test/yamlGuardConditions.test.ts +207 -0
  130. package/skills/browser-automation/SKILL.md +9 -19
  131. package/skills/web-crawler/SKILL.md +9 -11
  132. package/src/askBridge.ts +249 -0
  133. package/src/bootstrap/cwdArg.js +10 -3
  134. package/src/bootstrap/cwdArg.ts +31 -0
  135. package/src/bootstrap/workingDir.ts +48 -0
  136. package/src/cli/args.js +70 -7
  137. package/src/cli/args.ts +146 -0
  138. package/src/cli/computerUse.js +185 -0
  139. package/src/cli/computerUse.ts +221 -0
  140. package/src/cli/configMenu.js +274 -202
  141. package/src/cli/configMenu.tsx +521 -0
  142. package/src/cli/memoryAccess.js +45 -0
  143. package/src/cli/memoryAccess.ts +53 -0
  144. package/src/cli/print.js +119 -5
  145. package/src/cli/print.ts +597 -0
  146. package/src/cli/quickModelSetup.js +60 -0
  147. package/src/cli/quickModelSetup.tsx +96 -0
  148. package/src/cli/streamJson.js +80 -4
  149. package/src/cli/streamJson.ts +433 -0
  150. package/src/cli/wizards/advisorWizard.js +1 -0
  151. package/src/cli/wizards/advisorWizard.tsx +195 -0
  152. package/src/cli/wizards/agentRuntimeWizard.js +84 -0
  153. package/src/cli/wizards/agentRuntimeWizard.tsx +134 -0
  154. package/src/cli/wizards/browserWizard.js +55 -0
  155. package/src/cli/wizards/browserWizard.tsx +77 -0
  156. package/src/cli/wizards/contextAdvisorWizard.js +84 -0
  157. package/src/cli/wizards/contextAdvisorWizard.tsx +124 -0
  158. package/src/cli/wizards/fieldForm.js +7 -4
  159. package/src/cli/wizards/fieldForm.tsx +250 -0
  160. package/src/cli/wizards/imageWizard.js +4 -5
  161. package/src/cli/wizards/imageWizard.tsx +96 -0
  162. package/src/cli/wizards/openRouterWizard.tsx +48 -0
  163. package/src/cli/wizards/providerPresets.js +1 -8
  164. package/src/cli/wizards/providerPresets.ts +106 -0
  165. package/src/cli/wizards/providerWizard.js +152 -205
  166. package/src/cli/wizards/providerWizard.tsx +266 -0
  167. package/src/cli/wizards/searchWizard.tsx +47 -0
  168. package/src/cli/wizards/ttsWizard.js +4 -3
  169. package/src/cli/wizards/ttsWizard.tsx +61 -0
  170. package/src/config/configDefaults.js +25 -0
  171. package/src/config/configDefaults.ts +33 -0
  172. package/src/config/configDiagnostics.js +117 -0
  173. package/src/config/configDiagnostics.ts +166 -0
  174. package/src/config/configFile.js +52 -32
  175. package/src/config/configFile.ts +633 -0
  176. package/src/config/configManagement.js +126 -0
  177. package/src/config/configManagement.ts +159 -0
  178. package/src/config/configService.js +66 -11
  179. package/src/config/configService.ts +554 -0
  180. package/src/config/numericLimits.js +61 -0
  181. package/src/config/numericLimits.ts +80 -0
  182. package/src/config.ts +109 -0
  183. package/src/context/agentHome.ts +11 -0
  184. package/src/context/archive.ts +144 -0
  185. package/src/context/budgetPlan.js +54 -0
  186. package/src/context/budgetPlan.ts +88 -0
  187. package/src/context/compact.js +179 -106
  188. package/src/context/compact.ts +584 -0
  189. package/src/context/contextBundle.js +119 -0
  190. package/src/context/contextBundle.ts +176 -0
  191. package/src/context/contextLock.ts +84 -0
  192. package/src/context/microCompactLite.ts +218 -0
  193. package/src/context/persistContext.ts +177 -0
  194. package/src/context/reactiveCompact.js +19 -4
  195. package/src/context/reactiveCompact.ts +240 -0
  196. package/src/context/recentDirs.ts +42 -0
  197. package/src/context/sessionPath.ts +206 -0
  198. package/src/context/sessionRegistry.js +8 -2
  199. package/src/context/sessionRegistry.ts +414 -0
  200. package/src/context/snipCompact.ts +163 -0
  201. package/src/context/tokenCounter.ts +106 -0
  202. package/src/context/workspaceIndex.js +2 -2
  203. package/src/context/workspaceIndex.ts +751 -0
  204. package/src/entry.compiled.tsx +20 -0
  205. package/src/harness/claimValidation.js +9 -0
  206. package/src/harness/claimValidation.ts +22 -0
  207. package/src/harness/evidenceLedger.js +92 -0
  208. package/src/harness/evidenceLedger.ts +96 -0
  209. package/src/harness/stallGuard.js +60 -0
  210. package/src/harness/stallGuard.ts +81 -0
  211. package/src/humanIntervention.js +129 -0
  212. package/src/humanIntervention.ts +216 -0
  213. package/src/infra/verification.ts +295 -0
  214. package/src/irreversible/rules.js +216 -0
  215. package/src/irreversible/rules.ts +261 -0
  216. package/src/llm/client.js +40 -3
  217. package/src/llm/client.ts +621 -0
  218. package/src/llm/promptCache.js +27 -0
  219. package/src/llm/promptCache.ts +41 -0
  220. package/src/llm/providers/anthropicFormat.js +41 -5
  221. package/src/llm/providers/anthropicFormat.ts +290 -0
  222. package/src/llm/providers/openaiFormat.js +186 -8
  223. package/src/llm/providers/openaiFormat.ts +428 -0
  224. package/src/loop.js +614 -63
  225. package/src/loop.ts +1454 -0
  226. package/src/main.js +67 -10
  227. package/src/main.tsx +344 -0
  228. package/src/memory/codec.js +204 -0
  229. package/src/memory/codec.ts +249 -0
  230. package/src/memory/constants.js +34 -0
  231. package/src/memory/constants.ts +50 -0
  232. package/src/memory/episodicMemory.js +684 -0
  233. package/src/memory/episodicMemory.ts +815 -0
  234. package/src/memory/fileStore.js +148 -0
  235. package/src/memory/fileStore.ts +184 -0
  236. package/src/memory/memoryService.js +570 -0
  237. package/src/memory/memoryService.ts +859 -0
  238. package/src/memory/migration.js +128 -0
  239. package/src/memory/migration.ts +160 -0
  240. package/src/memory/repository.js +789 -0
  241. package/src/memory/repository.ts +893 -0
  242. package/src/memory/retrieval.js +108 -0
  243. package/src/memory/retrieval.ts +145 -0
  244. package/src/memory/searchIndex.js +107 -0
  245. package/src/memory/searchIndex.ts +153 -0
  246. package/src/memory/topicView.js +82 -0
  247. package/src/memory/topicView.ts +95 -0
  248. package/src/memory/turnJournal.js +95 -0
  249. package/src/memory/turnJournal.ts +120 -0
  250. package/src/memory/types.js +2 -0
  251. package/src/memory/types.ts +186 -0
  252. package/src/messageQueue.ts +123 -0
  253. package/src/multimodal.js +128 -46
  254. package/src/multimodal.ts +349 -0
  255. package/src/plugin-sdk.js +21 -1
  256. package/src/plugin-sdk.ts +134 -0
  257. package/src/plugins/builtinPluginIds.js +47 -0
  258. package/src/plugins/builtinPluginIds.ts +51 -0
  259. package/src/plugins/builtinRegistry.ts +43 -0
  260. package/src/plugins/commandRouter.js +40 -26
  261. package/src/plugins/commandRouter.ts +261 -0
  262. package/src/plugins/hookEngine.ts +341 -0
  263. package/src/plugins/pluginApi.ts +133 -0
  264. package/src/plugins/pluginLifecycle.ts +34 -0
  265. package/src/plugins/pluginLoader.ts +147 -0
  266. package/src/plugins/pluginManifest.js +127 -0
  267. package/src/plugins/pluginManifest.ts +152 -0
  268. package/src/plugins/pluginRunner.js +47 -2
  269. package/src/plugins/pluginRunner.ts +207 -0
  270. package/src/plugins/transcript.ts +208 -0
  271. package/src/prompts/lint.js +169 -0
  272. package/src/prompts/lint.ts +220 -0
  273. package/src/prompts/pluginList.js +32 -16
  274. package/src/prompts/pluginList.ts +86 -0
  275. package/src/prompts/policyRegistry.js +318 -0
  276. package/src/prompts/policyRegistry.ts +358 -0
  277. package/src/prompts/projectInstructions.js +24 -0
  278. package/src/prompts/projectInstructions.ts +81 -0
  279. package/src/prompts/promptAssembly.js +311 -202
  280. package/src/prompts/promptAssembly.ts +510 -0
  281. package/src/prompts/skillList.js +106 -45
  282. package/src/prompts/skillList.ts +198 -0
  283. package/src/prompts/system.js +31 -6
  284. package/src/prompts/system.ts +81 -0
  285. package/src/runtime/agentRuntimeContext.js +53 -0
  286. package/src/runtime/agentRuntimeContext.ts +112 -0
  287. package/src/session/runAgentTurn.js +71 -12
  288. package/src/session/runAgentTurn.ts +181 -0
  289. package/src/session/runTurn.js +29 -0
  290. package/src/session/runTurn.ts +160 -0
  291. package/src/session/runtimeStore.js +149 -0
  292. package/src/session/runtimeStore.ts +197 -0
  293. package/src/session/sessionState.js +79 -3
  294. package/src/session/sessionState.ts +134 -0
  295. package/src/toolGate.ts +50 -0
  296. package/src/tools/advisor/advisor.js +76 -15
  297. package/src/tools/advisor/advisor.ts +512 -0
  298. package/src/tools/agent/agent.js +245 -29
  299. package/src/tools/agent/agent.ts +837 -0
  300. package/src/tools/agent/agentTaskService.js +60 -0
  301. package/src/tools/agent/agentTaskService.ts +113 -0
  302. package/src/tools/agent/agentTypes.js +20 -8
  303. package/src/tools/agent/agentTypes.ts +94 -0
  304. package/src/tools/agent/coordinator.ts +111 -0
  305. package/src/tools/agent/worktree.ts +296 -0
  306. package/src/tools/ask/ask.js +17 -0
  307. package/src/tools/ask/ask.ts +122 -0
  308. package/src/tools/bash/attachImage.js +223 -0
  309. package/src/tools/bash/attachImage.ts +281 -0
  310. package/src/tools/bash/bash.js +132 -14
  311. package/src/tools/bash/bash.ts +642 -0
  312. package/src/tools/bash/semantics.ts +103 -0
  313. package/src/tools/bash/warnings.js +17 -11
  314. package/src/tools/bash/warnings.ts +117 -0
  315. package/src/tools/capabilities.js +42 -0
  316. package/src/tools/capabilities.ts +69 -0
  317. package/src/tools/computeruse/accessibilityPermission.js +261 -0
  318. package/src/tools/computeruse/accessibilityPermission.ts +294 -0
  319. package/src/tools/computeruse/axSearch.js +96 -0
  320. package/src/tools/computeruse/axSearch.ts +120 -0
  321. package/src/tools/computeruse/axSnap.js +272 -0
  322. package/src/tools/computeruse/axSnap.ts +349 -0
  323. package/src/tools/computeruse/capabilityLabels.js +95 -0
  324. package/src/tools/computeruse/capabilityLabels.ts +115 -0
  325. package/src/tools/computeruse/computerUseService.js +2235 -0
  326. package/src/tools/computeruse/computerUseService.ts +2686 -0
  327. package/src/tools/computeruse/computeruse.js +2742 -0
  328. package/src/tools/computeruse/computeruse.ts +3265 -0
  329. package/src/tools/computeruse/diagnosticTrace.js +154 -0
  330. package/src/tools/computeruse/diagnosticTrace.ts +195 -0
  331. package/src/tools/computeruse/diagnostics.js +369 -0
  332. package/src/tools/computeruse/diagnostics.ts +453 -0
  333. package/src/tools/computeruse/grants.js +53 -0
  334. package/src/tools/computeruse/grants.ts +70 -0
  335. package/src/tools/computeruse/humanBoundary.js +159 -0
  336. package/src/tools/computeruse/humanBoundary.ts +199 -0
  337. package/src/tools/computeruse/intervention.js +77 -0
  338. package/src/tools/computeruse/intervention.ts +110 -0
  339. package/src/tools/computeruse/manualSchema.js +709 -0
  340. package/src/tools/computeruse/manualSchema.ts +916 -0
  341. package/src/tools/computeruse/observationImages.js +705 -0
  342. package/src/tools/computeruse/observationImages.ts +889 -0
  343. package/src/tools/computeruse/protocol.js +60 -0
  344. package/src/tools/computeruse/protocol.ts +515 -0
  345. package/src/tools/computeruse/telemetryLog.js +57 -0
  346. package/src/tools/computeruse/telemetryLog.ts +67 -0
  347. package/src/tools/computeruse/vision/appGuide.js +325 -0
  348. package/src/tools/computeruse/vision/appGuide.ts +482 -0
  349. package/src/tools/computeruse/vision/focus.js +291 -0
  350. package/src/tools/computeruse/vision/focus.ts +382 -0
  351. package/src/tools/computeruse/vision/frames.js +248 -0
  352. package/src/tools/computeruse/vision/frames.ts +329 -0
  353. package/src/tools/computeruse/vision/geometry.js +131 -0
  354. package/src/tools/computeruse/vision/geometry.ts +163 -0
  355. package/src/tools/computeruse/vision/imaging.js +201 -0
  356. package/src/tools/computeruse/vision/imaging.ts +281 -0
  357. package/src/tools/computeruse/vision/png.js +274 -0
  358. package/src/tools/computeruse/vision/png.ts +317 -0
  359. package/src/tools/computeruse/vision/raster.js +320 -0
  360. package/src/tools/computeruse/vision/raster.ts +408 -0
  361. package/src/tools/computeruse/vision/settle.js +131 -0
  362. package/src/tools/computeruse/vision/settle.ts +220 -0
  363. package/src/tools/computeruse/vision/telemetry.js +196 -0
  364. package/src/tools/computeruse/vision/telemetry.ts +241 -0
  365. package/src/tools/computeruse/visionOcr.js +115 -0
  366. package/src/tools/computeruse/visionOcr.ts +128 -0
  367. package/src/tools/computeruse/waitFor.js +237 -0
  368. package/src/tools/computeruse/waitFor.ts +291 -0
  369. package/src/tools/download/download.js +10 -6
  370. package/src/tools/download/download.ts +187 -0
  371. package/src/tools/edit/edit.js +30 -18
  372. package/src/tools/edit/edit.ts +349 -0
  373. package/src/tools/edit/multi-edit.js +25 -14
  374. package/src/tools/edit/multi-edit.ts +223 -0
  375. package/src/tools/effectiveToolSet.js +73 -0
  376. package/src/tools/effectiveToolSet.ts +91 -0
  377. package/src/tools/genimage/genimage.ts +257 -0
  378. package/src/tools/genspeech/genspeech.ts +83 -0
  379. package/src/tools/glob/glob.js +176 -29
  380. package/src/tools/glob/glob.ts +283 -0
  381. package/src/tools/grep/grep.js +61 -19
  382. package/src/tools/grep/grep.ts +239 -0
  383. package/src/tools/grep/rgPath.ts +206 -0
  384. package/src/tools/index.js +99 -5
  385. package/src/tools/index.ts +293 -0
  386. package/src/tools/lsp/config.js +113 -0
  387. package/src/tools/lsp/config.ts +113 -0
  388. package/src/tools/lsp/lsp.js +256 -0
  389. package/src/tools/lsp/lsp.ts +274 -0
  390. package/src/tools/lsp/service.js +303 -0
  391. package/src/tools/lsp/service.ts +296 -0
  392. package/src/tools/memory/memory.js +520 -0
  393. package/src/tools/memory/memory.ts +625 -0
  394. package/src/tools/memory/policy.js +53 -0
  395. package/src/tools/memory/policy.ts +72 -0
  396. package/src/tools/notebook/notebookEdit.js +114 -0
  397. package/src/tools/notebook/notebookEdit.ts +126 -0
  398. package/src/tools/powershell/powershell.js +210 -0
  399. package/src/tools/powershell/powershell.ts +239 -0
  400. package/src/tools/presentresult/presentresult.js +145 -0
  401. package/src/tools/presentresult/presentresult.ts +156 -0
  402. package/src/tools/read/read.js +371 -163
  403. package/src/tools/read/read.ts +558 -0
  404. package/src/tools/shared/diffSummary.js +44 -0
  405. package/src/tools/shared/diffSummary.ts +63 -0
  406. package/src/tools/shared/fileActivity.js +51 -0
  407. package/src/tools/shared/fileActivity.ts +66 -0
  408. package/src/tools/shared/fileMutationBus.js +13 -0
  409. package/src/tools/shared/fileMutationBus.ts +14 -0
  410. package/src/tools/shared/fileState.js +73 -65
  411. package/src/tools/shared/fileState.ts +145 -0
  412. package/src/tools/shared/fileUtils.js +81 -4
  413. package/src/tools/shared/fileUtils.ts +504 -0
  414. package/src/tools/shared/killTree.ts +50 -0
  415. package/src/tools/shared/permanentDeleteGuard.js +26 -0
  416. package/src/tools/shared/permanentDeleteGuard.ts +34 -0
  417. package/src/tools/shared/schemas.ts +20 -0
  418. package/src/tools/shared/toolOutputStore.js +97 -0
  419. package/src/tools/shared/toolOutputStore.ts +110 -0
  420. package/src/tools/skill/skill.js +86 -0
  421. package/src/tools/skill/skill.ts +94 -0
  422. package/src/tools/snip/snip.js +67 -0
  423. package/src/tools/snip/snip.ts +80 -0
  424. package/src/tools/tasks/backgroundTaskService.js +275 -0
  425. package/src/tools/tasks/backgroundTaskService.ts +369 -0
  426. package/src/tools/tasks/processEvidence.js +24 -0
  427. package/src/tools/tasks/processEvidence.ts +34 -0
  428. package/src/tools/tasks/taskManagement.js +163 -0
  429. package/src/tools/tasks/taskManagement.ts +180 -0
  430. package/src/tools/tasks/taskOutput.js +113 -0
  431. package/src/tools/tasks/taskOutput.ts +145 -0
  432. package/src/tools/tasks/taskStop.js +74 -0
  433. package/src/tools/tasks/taskStop.ts +99 -0
  434. package/src/tools/tasks/taskStore.js +46 -0
  435. package/src/tools/tasks/taskStore.ts +77 -0
  436. package/src/tools/terminal/terminalCapture.js +167 -0
  437. package/src/tools/terminal/terminalCapture.ts +179 -0
  438. package/src/tools/trash/trash.js +233 -0
  439. package/src/tools/trash/trash.ts +283 -0
  440. package/src/tools/types.js +1 -1
  441. package/src/tools/types.ts +24 -0
  442. package/src/tools/webbrowser/agentBrowserPath.ts +286 -0
  443. package/src/tools/webbrowser/browser.js +214 -11
  444. package/src/tools/webbrowser/browser.ts +293 -0
  445. package/src/tools/webbrowser/browserControl.js +153 -48
  446. package/src/tools/webbrowser/browserControl.ts +262 -0
  447. package/src/tools/webbrowser/browserDaemon.js +147 -39
  448. package/src/tools/webbrowser/browserDaemon.ts +270 -0
  449. package/src/tools/webbrowser/browserOutcome.js +273 -0
  450. package/src/tools/webbrowser/browserOutcome.ts +306 -0
  451. package/src/tools/webbrowser/browserRunRegistry.js +150 -0
  452. package/src/tools/webbrowser/browserRunRegistry.ts +179 -0
  453. package/src/tools/webbrowser/browserService.js +20 -6
  454. package/src/tools/webbrowser/browserService.ts +56 -0
  455. package/src/tools/webbrowser/browserStream.js +30 -1
  456. package/src/tools/webbrowser/browserStream.ts +216 -0
  457. package/src/tools/webbrowser/browserTabs.js +143 -0
  458. package/src/tools/webbrowser/browserTabs.ts +210 -0
  459. package/src/tools/webbrowser/webbrowser.js +831 -150
  460. package/src/tools/webbrowser/webbrowser.ts +1537 -0
  461. package/src/tools/webfetch/preapproved.ts +274 -0
  462. package/src/tools/webfetch/webfetch.js +24 -23
  463. package/src/tools/webfetch/webfetch.ts +383 -0
  464. package/src/tools/websearch/websearch.js +12 -11
  465. package/src/tools/websearch/websearch.ts +292 -0
  466. package/src/tools/worktree/sessionWorktree.js +128 -0
  467. package/src/tools/worktree/sessionWorktree.ts +158 -0
  468. package/src/tools/write/write.js +27 -44
  469. package/src/tools/write/write.ts +136 -0
  470. package/src/types.ts +960 -0
  471. package/src/ui/App.js +216 -12
  472. package/src/ui/App.tsx +879 -0
  473. package/src/ui/AskQuestion.tsx +300 -0
  474. package/src/ui/InputBox.js +77 -1
  475. package/src/ui/InputBox.tsx +567 -0
  476. package/src/ui/LiveArea.tsx +344 -0
  477. package/src/ui/MessageList.js +27 -6
  478. package/src/ui/MessageList.tsx +559 -0
  479. package/src/ui/ModelPicker.tsx +107 -0
  480. package/src/ui/QueuedMessagesBanner.tsx +51 -0
  481. package/src/ui/Root.js +6 -8
  482. package/src/ui/Root.tsx +100 -0
  483. package/src/ui/SessionPicker.tsx +169 -0
  484. package/src/ui/StatusLine.js +22 -1
  485. package/src/ui/StatusLine.tsx +145 -0
  486. package/src/ui/WorkspacePicker.tsx +84 -0
  487. package/src/ui/advisorCommand.js +54 -0
  488. package/src/ui/advisorCommand.ts +63 -0
  489. package/src/ui/computerCommand.js +19 -0
  490. package/src/ui/computerCommand.ts +35 -0
  491. package/src/ui/figures.ts +45 -0
  492. package/src/ui/hooks/askSlot.ts +112 -0
  493. package/src/ui/hooks/useChat.js +271 -7
  494. package/src/ui/hooks/useChat.ts +1578 -0
  495. package/src/ui/hooks/useInputHistory.ts +235 -0
  496. package/src/ui/hooks/usePasteHandler.ts +220 -0
  497. package/src/ui/hooks/useTerminalSize.ts +41 -0
  498. package/src/ui/hooks/useTextBuffer.ts +108 -0
  499. package/src/ui/hooks/useTokenUsage.ts +44 -0
  500. package/src/ui/liveViewport.ts +189 -0
  501. package/src/ui/markdown.ts +230 -0
  502. package/src/ui/textBuffer.ts +239 -0
  503. package/src/ui/theme.ts +35 -0
  504. package/src/utils/clipboardImage.js +358 -0
  505. package/src/utils/clipboardImage.ts +420 -0
  506. package/src/utils/debugLog.ts +50 -0
  507. package/src/utils/greenRoot.ts +35 -0
  508. package/src/utils/packageRoot.ts +40 -0
  509. package/src/utils/pathContains.js +28 -0
  510. package/src/utils/pathContains.ts +31 -0
  511. package/src/utils/resourcePaths.ts +71 -0
  512. package/src/utils/stripThink.ts +49 -0
  513. package/src/utils/zodToJson.ts +73 -0
  514. package/src/web/browserBridge.js +365 -66
  515. package/src/web/browserBridge.ts +672 -0
  516. package/src/web/protocol.ts +1804 -0
  517. package/vendor/computer-use/arm64-darwin/cu-helperd +0 -0
  518. package/workflows/ppt.yaml +13 -2
  519. package/workflows/wechat-reading-shots.yaml +315 -0
  520. package/workflows/word.yaml +11 -1
  521. package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
  522. package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
  523. package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
  524. package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
  525. package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
  526. package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
  527. package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
  528. package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
  529. package/skills/image-stats/scripts/__pycache__/image_stats.cpython-314.pyc +0 -0
  530. package/skills/image-stats/scripts/__pycache__/image_stats_test.cpython-314.pyc +0 -0
  531. package/skills/ui-design/scripts/__pycache__/assemble_material.cpython-314.pyc +0 -0
  532. package/skills/ui-design/scripts/__pycache__/assemble_material_test.cpython-314.pyc +0 -0
  533. package/skills/ui-design/scripts/__pycache__/chart_sanity_check.cpython-314.pyc +0 -0
  534. package/skills/ui-design/scripts/__pycache__/check_language_policy.cpython-314.pyc +0 -0
  535. package/skills/ui-design/scripts/__pycache__/check_ui_artifact.cpython-314.pyc +0 -0
  536. package/skills/ui-design/scripts/__pycache__/check_ui_artifact_pptx_test.cpython-314.pyc +0 -0
  537. package/skills/ui-design/scripts/__pycache__/deck_pages_check.cpython-314.pyc +0 -0
  538. package/skills/ui-design/scripts/__pycache__/deck_pages_check_test.cpython-314.pyc +0 -0
  539. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check.cpython-314.pyc +0 -0
  540. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check_test.cpython-314.pyc +0 -0
  541. package/skills/ui-design/scripts/__pycache__/design_budget_check.cpython-314.pyc +0 -0
  542. package/skills/ui-design/scripts/__pycache__/design_budget_check_test.cpython-314.pyc +0 -0
  543. package/skills/ui-design/scripts/__pycache__/design_repair_hints.cpython-314.pyc +0 -0
  544. package/skills/ui-design/scripts/__pycache__/design_repair_hints_test.cpython-314.pyc +0 -0
  545. package/skills/ui-design/scripts/__pycache__/design_style_check.cpython-314.pyc +0 -0
  546. package/skills/ui-design/scripts/__pycache__/design_style_check_test.cpython-314.pyc +0 -0
  547. package/skills/ui-design/scripts/__pycache__/draft_coverage_check.cpython-314.pyc +0 -0
  548. package/skills/ui-design/scripts/__pycache__/extract_source_assets.cpython-314.pyc +0 -0
  549. package/skills/ui-design/scripts/__pycache__/extract_source_assets_test.cpython-314.pyc +0 -0
  550. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect.cpython-314.pyc +0 -0
  551. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect_test.cpython-314.pyc +0 -0
  552. package/skills/ui-design/scripts/__pycache__/image_plan_lint.cpython-314.pyc +0 -0
  553. package/skills/ui-design/scripts/__pycache__/image_plan_lint_test.cpython-314.pyc +0 -0
  554. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite.cpython-314.pyc +0 -0
  555. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite_test.cpython-314.pyc +0 -0
  556. package/skills/ui-design/scripts/__pycache__/layout_balance.cpython-314.pyc +0 -0
  557. package/skills/ui-design/scripts/__pycache__/layout_balance_test.cpython-314.pyc +0 -0
  558. package/skills/ui-design/scripts/__pycache__/normalize_design_json.cpython-314.pyc +0 -0
  559. package/skills/ui-design/scripts/__pycache__/normalize_design_json_test.cpython-314.pyc +0 -0
  560. package/skills/ui-design/scripts/__pycache__/plan_source_layout.cpython-314.pyc +0 -0
  561. package/skills/ui-design/scripts/__pycache__/plan_source_layout_test.cpython-314.pyc +0 -0
  562. package/skills/ui-design/scripts/__pycache__/resolve_source_hint.cpython-314.pyc +0 -0
  563. package/skills/ui-design/scripts/__pycache__/resolve_source_hint_test.cpython-314.pyc +0 -0
  564. package/skills/ui-design/scripts/__pycache__/resolve_source_input.cpython-314.pyc +0 -0
  565. package/skills/ui-design/scripts/__pycache__/resolve_source_input_test.cpython-314.pyc +0 -0
  566. package/skills/ui-design/scripts/__pycache__/scaffold_ui_artifact.cpython-314.pyc +0 -0
  567. package/skills/ui-design/scripts/__pycache__/source_usage_check.cpython-314.pyc +0 -0
  568. package/skills/ui-design/scripts/__pycache__/source_usage_check_test.cpython-314.pyc +0 -0
  569. package/skills/ui-design/scripts/__pycache__/text_readability_pixel.cpython-314.pyc +0 -0
  570. package/skills/ui-design/scripts/__pycache__/visual_quality_gate.cpython-314.pyc +0 -0
  571. package/skills/ui-design/scripts/__pycache__/visual_quality_gate_test.cpython-314.pyc +0 -0
  572. /package/skills/ai-image/scripts/{generate_test.mjs → generate.selftest.mjs} +0 -0
  573. /package/skills/ai-image/scripts/{generate_from_plan_test.mjs → generate_from_plan.selftest.mjs} +0 -0
package/src/ui/App.tsx ADDED
@@ -0,0 +1,879 @@
1
+ /**
2
+ * ============================================================
3
+ * src/ui/App.tsx —— TUI 顶层组件
4
+ * ------------------------------------------------------------
5
+ * 组合所有 UI 子组件,把 useChat 暴露的状态映射到各组件 props。
6
+ * 不直接处理键盘输入,那是 InputBox 的事。
7
+ * ============================================================
8
+ */
9
+
10
+ import React, { useDeferredValue } from 'react';
11
+ import { Box, Text, useApp } from 'ink';
12
+
13
+ import { getWorkingDir } from '../bootstrap/workingDir.js';
14
+ import { ConfigMenu } from '../cli/configMenu.js';
15
+ import { configService } from '../config/configService.js';
16
+ import { listArchives, restoreArchive } from '../context/archive.js';
17
+ import { saveContext } from '../context/persistContext.js';
18
+ import { sessionFileFor } from '../context/sessionPath.js';
19
+ import { workspaceIndexService, type WorkspaceRecord } from '../context/workspaceIndex.js';
20
+ import { memoryService } from '../memory/memoryService.js';
21
+ import {
22
+ createSession,
23
+ listSessionSummaries,
24
+ updateSessionMetadata,
25
+ type SessionSummary,
26
+ } from '../context/sessionRegistry.js';
27
+ import type { Message, Provider } from '../types.js';
28
+ import type { ModelConfig } from '../config/configFile.js';
29
+ import { activatePlugins } from '../plugins/pluginLifecycle.js';
30
+ import { runAgentTurn } from '../session/runAgentTurn.js';
31
+ import { writeClipboardText } from '../utils/clipboardImage.js';
32
+ import {
33
+ createCliComputerRun,
34
+ readRecentComputerUseAudit,
35
+ } from '../cli/computerUse.js';
36
+ import { computerUseService } from '../tools/computeruse/computerUseService.js';
37
+ import { advisorHelpText, parseAdvisorCommand } from './advisorCommand.js';
38
+ import { AskQuestion } from './AskQuestion.js';
39
+ import { InputBox } from './InputBox.js';
40
+ import { LiveArea } from './LiveArea.js';
41
+ import { QueuedMessagesBanner } from './QueuedMessagesBanner.js';
42
+ import { MessageList } from './MessageList.js';
43
+ import { ModelPicker } from './ModelPicker.js';
44
+ import { SessionPicker } from './SessionPicker.js';
45
+ import { WorkspacePicker } from './WorkspacePicker.js';
46
+ import { StatusLine } from './StatusLine.js';
47
+ import { theme } from './theme.js';
48
+ import { parseComputerCommand } from './computerCommand.js';
49
+ import { useChat } from './hooks/useChat.js';
50
+ import { useTerminalSize } from './hooks/useTerminalSize.js';
51
+
52
+ /**
53
+ * ANSI escape: 清屏 + 光标归位。
54
+ *
55
+ * 注意:项目里仅这两处保留 process.stdout.write 旁路 Ink:
56
+ * 1) onCompactDone(压缩完)
57
+ * 2) /new 清空历史后
58
+ * CLEAR_SCREEN 是**一次性**写入 + 整屏清空的语义,不存在"反复写新内容到 live 区"
59
+ * 的问题;ESC[2J 清屏后 ink 下次 onRender 会重发完整 lastOutput,lineCount 残留
60
+ * 也不会留下脏行(写在空白处)。其它"用户看到的非消息内容"必须走 chat.ephemeralLine
61
+ * 走 Ink 渲染管线,否则会破坏 log-update 计数。
62
+ */
63
+ const CLEAR_SCREEN = '\x1b[2J\x1b[H';
64
+
65
+ function formatProviderName(apiFormat: string): string {
66
+ if (apiFormat === 'anthropic') return 'Anthropic';
67
+ if (apiFormat === 'openai') return 'OpenAI兼容';
68
+ return apiFormat;
69
+ }
70
+
71
+ interface Props {
72
+ provider: Provider;
73
+ /** 启动时的历史(含 system prompt);可能是从 last-context.json 加载来的旧对话。 */
74
+ initialHistory: Message[];
75
+ }
76
+
77
+ export function App({ provider, initialHistory }: Props): JSX.Element {
78
+ // v2 multi-session:持久化路径完全由 useChat 内部管控(按 activeSessionId 选文件)。
79
+ // App 不再传 onPersist —— 之前 App 端调 saveContext(messages) 会污染 default session。
80
+ const chat = useChat({
81
+ provider,
82
+ initialHistory,
83
+ onCompactDone: () => process.stdout.write(CLEAR_SCREEN),
84
+ });
85
+
86
+ // 终端尺寸感知:rows → MessageList 决定窄终端是否折叠 reasoning + LiveArea 收敛预览高;
87
+ // cols → LiveArea 逐行截断防换行
88
+ const { rows: terminalRows, cols: terminalCols } = useTerminalSize();
89
+
90
+ React.useEffect(() => {
91
+ const controller = new AbortController();
92
+ let cleanup: (() => void) | undefined;
93
+ void activatePlugins({
94
+ provider,
95
+ signal: controller.signal,
96
+ runAgentTurn,
97
+ }).then((fn) => {
98
+ if (controller.signal.aborted) {
99
+ fn();
100
+ return;
101
+ }
102
+ cleanup = fn;
103
+ }).catch((err) => {
104
+ console.warn(
105
+ `[minimal-agent] plugin activation failed: ${
106
+ err instanceof Error ? err.message : String(err)
107
+ }`,
108
+ );
109
+ });
110
+ return () => {
111
+ controller.abort();
112
+ cleanup?.();
113
+ };
114
+ }, [provider]);
115
+
116
+ /**
117
+ * 用 useDeferredValue 把 history 推后到低优先级渲染队列。
118
+ *
119
+ * 流式期间 transient state 高频 setState 是高优先级,
120
+ * 而 MessageList 重算 turn meta + Static reconcile 比较重,可以"等空闲再做"。
121
+ * React 在繁忙时会让 deferredHistory 保持旧值跳过 MessageList rerender,
122
+ * 直到 streamingText 稳定下来再 commit 新 history。
123
+ *
124
+ * 注意:assistant_message 落定时 history 增长 + streamingText 清空是同一次
125
+ * runQuery yield —— useDeferredValue 不会无限延迟,最终一定会 catch up,
126
+ * Static commit 新 message 进 scrollback 不会丢。
127
+ */
128
+ const deferredHistory = useDeferredValue(chat.history);
129
+
130
+ // /new:内存 reset + 磁盘 clearContext 全部由 chat.clearHistory() 内部完成,await 之后再清屏。
131
+ // ★ deps 用精细字段 chat.clearHistory,不是整个 chat。
132
+ // 理由:useChat 返回的 chat 对象每次 useChat 重渲都是新对象字面量引用。
133
+ // 如果 deps=[chat],流式期 reasoningPreview/streamingText 每 100ms flush 触发
134
+ // useChat rerender → chat 新引用 → handleClear 新引用 → InputBox 收到新 onClear
135
+ // prop → 强制重渲 → 整个 live 区被 Ink 重写一次(看起来就是 spinner 每帧打新行)。
136
+ const handleClear = React.useCallback(async () => {
137
+ await chat.clearHistory();
138
+ process.stdout.write(CLEAR_SCREEN);
139
+ }, [chat.clearHistory]);
140
+
141
+ // history ref:在 handleSubmit 闭包里读最新 chat.history,避免每次流式 flush
142
+ // 重建 callback;同时避免把 chat.history 放进 deps 导致 InputBox 频繁 rerender。
143
+ const historyRef = React.useRef<Message[]>(chat.history);
144
+ React.useEffect(() => {
145
+ historyRef.current = chat.history;
146
+ }, [chat.history]);
147
+
148
+ // /session /sessions /resume(裸命令)打开的选择器数据;非 null 时顶替 InputBox。
149
+ // 纯本地 UI 态,不需要塞进 useChat——不像 pendingAsk 那样由 askBridge 从外部注入,
150
+ // 这里完全由 handleSubmit 自己的分支触发/清空。
151
+ const [sessionPicker, setSessionPicker] = React.useState<SessionSummary[] | null>(null);
152
+ const [workspacePicker, setWorkspacePicker] = React.useState<WorkspaceRecord[] | null>(null);
153
+
154
+ // /config —— 硬编码菜单覆盖层,纯本地 UI 态(与 sessionPicker 同构)。
155
+ const [configMenuOpen, setConfigMenuOpen] = React.useState(false);
156
+
157
+ // /model —— 基座模型选择器;/advisor select —— 顾问模型选择器
158
+ const [baseModelPicker, setBaseModelPicker] = React.useState<{ models: ModelConfig[]; currentId: string | null } | null>(null);
159
+ const [advisorModelPicker, setAdvisorModelPicker] = React.useState<{ models: ModelConfig[]; currentId: string | null } | null>(null);
160
+
161
+ // 强制刷新计数,用于切换模型后触发 StatusLine 重渲染
162
+ const [, forceRerender] = React.useState(0);
163
+
164
+ const handleSessionSelect = React.useCallback(
165
+ async (id: string) => {
166
+ setSessionPicker(null);
167
+ await chat.switchSession(id);
168
+ chat.ephemeralLine(`[/session] 已切换到 session "${id}"`);
169
+ },
170
+ [chat.switchSession, chat.ephemeralLine],
171
+ );
172
+
173
+ const handleSessionCreate = React.useCallback(
174
+ async (title: string) => {
175
+ setSessionPicker(null);
176
+ const summary = await createSession(getWorkingDir(), title ? { title } : {});
177
+ await chat.switchSession(summary.id);
178
+ chat.ephemeralLine(`[/session] 已新建并切换到 session "${summary.id}"(${summary.title})`);
179
+ },
180
+ [chat.switchSession, chat.ephemeralLine],
181
+ );
182
+
183
+ const openWorkspacePicker = React.useCallback(async () => {
184
+ if (chat.isLoading) return;
185
+ setSessionPicker(null);
186
+ setConfigMenuOpen(false);
187
+ setWorkspacePicker(await workspaceIndexService.list());
188
+ }, [chat.isLoading]);
189
+
190
+ const handleWorkspaceSelect = React.useCallback(async (cwd: string, sessionId: string) => {
191
+ const switched = await chat.switchWorkspace(cwd, sessionId);
192
+ setWorkspacePicker(null);
193
+ if (switched) {
194
+ chat.ephemeralLine(`[/workspaces] 已切换到 ${cwd} · ${sessionId}`);
195
+ }
196
+ }, [chat.switchWorkspace, chat.ephemeralLine]);
197
+
198
+ const handleBaseModelSelect = React.useCallback(
199
+ async (modelId: string | null) => {
200
+ setBaseModelPicker(null);
201
+ if (!modelId) return;
202
+ const snap = await configService.getSnapshot();
203
+ const model = snap.config?.models.find((m) => m.id === modelId);
204
+ if (!model) {
205
+ chat.ephemeralLine(`[/model] 未找到模型 "${modelId}"`);
206
+ return;
207
+ }
208
+ const newProvider = await configService.providerForModel(modelId);
209
+ if (newProvider) {
210
+ await updateSessionMetadata(getWorkingDir(), chat.activeSessionId, { modelId });
211
+ chat.setProvider(newProvider);
212
+ forceRerender((v) => v + 1);
213
+ chat.ephemeralLine(`[/model] 当前 Session 已切换为 ${model.displayName || model.model}(${formatProviderName(model.apiFormat)}),下一轮对话生效。`);
214
+ }
215
+ },
216
+ [chat.activeSessionId, chat.setProvider, chat.ephemeralLine],
217
+ );
218
+
219
+ React.useEffect(() => {
220
+ let cancelled = false;
221
+ void listSessionSummaries(getWorkingDir()).then(async (sessions) => {
222
+ const modelId = sessions.find((session) => session.id === chat.activeSessionId)?.modelId;
223
+ const provider = await configService.providerForModel(modelId ?? null)
224
+ ?? await configService.providerForModel(null);
225
+ if (!cancelled && provider) chat.setProvider(provider);
226
+ }).catch(() => {});
227
+ return () => { cancelled = true; };
228
+ }, [chat.activeSessionId, chat.setProvider]);
229
+
230
+ const handleAdvisorModelSelect = React.useCallback(
231
+ async (modelId: string | null) => {
232
+ setAdvisorModelPicker(null);
233
+ if (modelId === null) {
234
+ await configService.savePatch({ advisor: { modelId: null, enabled: false } });
235
+ chat.ephemeralLine('[/advisor] 已关闭顾问模型。');
236
+ return;
237
+ }
238
+ const snap = await configService.getSnapshot();
239
+ const model = snap.config?.models.find((m) => m.id === modelId);
240
+ if (!model) {
241
+ chat.ephemeralLine(`[/advisor] 未找到模型 "${modelId}"`);
242
+ return;
243
+ }
244
+ await configService.savePatch({ advisor: { modelId, enabled: true } });
245
+ chat.ephemeralLine(`[/advisor] 已设置顾问模型为 ${model.displayName || model.model}(${formatProviderName(model.apiFormat)})。`);
246
+ },
247
+ [chat.ephemeralLine],
248
+ );
249
+
250
+ /**
251
+ * v2 slash 命令拦截层。
252
+ *
253
+ * InputBox 已经拦截了 /exit /quit /new /compact 不走 onSubmit;
254
+ * 其他 / 开头命令落到这里。命中命令走 chat.ephemeralLine() 把提示作为
255
+ * _ephemeral 的 system 消息推进 history → MessageList <Static> 把它 commit
256
+ * 进 scrollback;不调 chat.submit 避免被 LLM 当成用户输入。
257
+ *
258
+ * 旧方案是 process.stdout.write 旁路 Ink,但那样会破坏 ink log-update 的
259
+ * previousLineCount 计数,下一帧 eraseLines 用过期 N 去清会留下脏行。
260
+ *
261
+ * 支持:
262
+ * /session /sessions /resume(裸命令) — 列出当前 cwd 下所有 session,弹选择器
263
+ * /session <id> /resume <id> — 直接切换到指定 session
264
+ * /session new [title] — 新建 session 并切过去
265
+ * /list-archive — 列归档
266
+ * /restore <id> — 恢复归档为当前 session 历史(覆盖+重读)
267
+ * /show <id-suffix> — 打印 tool_call_id 末尾匹配的完整 tool result
268
+ */
269
+ const handleSubmit = React.useCallback(
270
+ async (text: string) => {
271
+ const trimmed = text.trim();
272
+ const isBareSessionCmd =
273
+ trimmed === '/session' || trimmed === '/sessions' || trimmed === '/resume';
274
+ const sessionArgMatch = /^\/(?:session|resume)\s+(.+)$/.exec(trimmed);
275
+
276
+ if (trimmed === '/workspaces') {
277
+ await openWorkspacePicker();
278
+ return;
279
+ }
280
+
281
+ if (trimmed === '/copy') {
282
+ const latest = [...historyRef.current].reverse().find(
283
+ (message) => message.role === 'assistant' && typeof message.content === 'string' && message.content.length > 0,
284
+ );
285
+ if (!latest || latest.role !== 'assistant' || typeof latest.content !== 'string') {
286
+ chat.ephemeralLine('[/copy] 当前会话还没有可复制的 AI 最终回复。');
287
+ return;
288
+ }
289
+ try {
290
+ await writeClipboardText(latest.content);
291
+ chat.ephemeralLine('[/copy] 已复制最近一条 AI 最终回复。');
292
+ } catch (error) {
293
+ chat.ephemeralLine(`[/copy] ${error instanceof Error ? error.message : String(error)}`);
294
+ }
295
+ return;
296
+ }
297
+
298
+ const computerCommand = parseComputerCommand(trimmed);
299
+ if (computerCommand.matched && !computerCommand.invalid) {
300
+ const sub = computerCommand.command ?? 'help';
301
+
302
+ if (sub === 'help') {
303
+ const helperAvailable = computerUseService.available();
304
+ let helperBackend = '未知';
305
+ let modeLabel = chat.computerUseMode;
306
+ try {
307
+ const workspace = await workspaceIndexService
308
+ .findByPath(getWorkingDir())
309
+ .catch(() => null);
310
+ const context = createCliComputerRun({
311
+ mode: 'observe',
312
+ surface: 'tui',
313
+ conversationKey: `workspace:${workspace?.id ?? getWorkingDir()}:session:${chat.activeSessionId}`,
314
+ userInput: '',
315
+ allowedFileRoots: [getWorkingDir()],
316
+ supervised: true,
317
+ });
318
+ const resp = await computerUseService.request(context, { action: 'status' });
319
+ if (resp.ok && resp.state?.backend) {
320
+ helperBackend = resp.state.backend;
321
+ }
322
+ } catch {
323
+ // 探测失败就保持 "未知"
324
+ }
325
+ const modeDesc: Record<string, string> = {
326
+ disabled: '已关闭(默认)',
327
+ observe: '观察模式(只看不操作)',
328
+ interactive: '交互模式(可点击/输入)',
329
+ };
330
+ chat.ephemeralLine(
331
+ [
332
+ '[/computer] 电脑操控命令帮助',
333
+ '',
334
+ ` 当前模式:${modeLabel} — ${modeDesc[modeLabel] ?? modeLabel}`,
335
+ ` Helper 状态:${helperAvailable ? '✅ 可用' : '❌ 不可用'} backend:${helperBackend}`,
336
+ '',
337
+ ' 可用子命令:',
338
+ ' /computer help 显示本帮助信息',
339
+ ' /computer status 显示当前模式与 helper 状态',
340
+ ' /computer permissions 检查辅助功能与屏幕录制权限',
341
+ ' /computer observe 进入观察模式(AI 只能看屏幕,不能操作)',
342
+ ' /computer interactive 进入交互模式(AI 可点击、输入、滚动等)',
343
+ ' /computer stop 关闭电脑操控,回到普通对话',
344
+ ' /computer takeover 同 interactive(受监督交互)',
345
+ ' /computer resume 恢复上一次电脑操控会话',
346
+ ' /computer cancel 取消当前电脑操控任务',
347
+ ' /computer apps 列出当前可见应用',
348
+ ' /computer audit 查看最近 10 条电脑操控审计记录',
349
+ '',
350
+ ' 使用说明:',
351
+ ' 1. 首次使用请先运行 /computer permissions 检查权限',
352
+ ' 2. 输入 /computer observe 或 /computer interactive 开启',
353
+ ' 3. 开启后,直接描述你想让 AI 做的事即可',
354
+ ' 4. 随时可输入 /computer stop 关闭',
355
+ ].join('\n'),
356
+ );
357
+ return;
358
+ }
359
+
360
+ if (sub === 'interactive' || sub === 'takeover') {
361
+ chat.setComputerUseMode('interactive');
362
+ chat.ephemeralLine(
363
+ '[/computer] ✅ 已切换到交互模式(interactive)。\n' +
364
+ ' AI 可以观察屏幕并执行鼠标点击、键盘输入、滚动等操作。\n' +
365
+ ' 直接描述你想做的事情即可开始。',
366
+ );
367
+ return;
368
+ }
369
+
370
+ if (sub === 'observe') {
371
+ chat.setComputerUseMode('observe');
372
+ chat.ephemeralLine(
373
+ '[/computer] ✅ 已切换到观察模式(observe)。\n' +
374
+ ' AI 只能观察屏幕画面,不会执行任何操作。\n' +
375
+ ' 直接描述你想让 AI 看什么即可。',
376
+ );
377
+ return;
378
+ }
379
+
380
+ if (sub === 'stop') {
381
+ chat.ephemeralLine(`[/computer] ${await chat.stopComputerUse()}`);
382
+ return;
383
+ }
384
+
385
+ if (sub === 'audit') {
386
+ const records = await readRecentComputerUseAudit(10);
387
+ chat.ephemeralLine(
388
+ records.length
389
+ ? `[/computer] 最近审计:\n${records.map((line) => ` ${line}`).join('\n')}`
390
+ : '[/computer] 暂无本机 Computer Use 审计记录。',
391
+ );
392
+ return;
393
+ }
394
+
395
+ if (sub === 'resume') {
396
+ chat.ephemeralLine(`[/computer] ${await chat.resumeComputerUse()}`);
397
+ return;
398
+ }
399
+
400
+ if (sub === 'cancel') {
401
+ chat.ephemeralLine(`[/computer] ${await chat.cancelComputerUse()}`);
402
+ return;
403
+ }
404
+
405
+ const workspace = await workspaceIndexService
406
+ .findByPath(getWorkingDir())
407
+ .catch(() => null);
408
+ const context = createCliComputerRun({
409
+ mode: 'observe',
410
+ surface: 'tui',
411
+ conversationKey: `workspace:${workspace?.id ?? getWorkingDir()}:session:${chat.activeSessionId}`,
412
+ userInput: '',
413
+ allowedFileRoots: [getWorkingDir()],
414
+ supervised: true,
415
+ });
416
+
417
+ const action =
418
+ sub === 'apps'
419
+ ? 'list_apps'
420
+ : sub === 'permissions'
421
+ ? 'permissions'
422
+ : 'status';
423
+ const response = await computerUseService.request(context, { action });
424
+ if (!response.ok) {
425
+ chat.ephemeralLine(
426
+ `[/computer] ${response.error?.message ?? 'Computer Use 不可用'}${response.error?.hint ? `\n ${response.error.hint}` : ''}`,
427
+ );
428
+ return;
429
+ }
430
+ if (sub === 'apps') {
431
+ const apps = response.state?.windows
432
+ .map((window) => window.appId)
433
+ .filter((id, index, all) => all.indexOf(id) === index);
434
+ chat.ephemeralLine(
435
+ `[/computer] 可见应用:${apps?.length ? apps.join('、') : response.content ?? '当前没有可见应用'}`,
436
+ );
437
+ return;
438
+ }
439
+ if (sub === 'permissions') {
440
+ chat.ephemeralLine(
441
+ `[/computer] 权限:${response.content ?? '原生服务已连接;详细权限状态未报告。'}`,
442
+ );
443
+ return;
444
+ }
445
+ // status
446
+ chat.ephemeralLine(
447
+ `[/computer] 状态:\n` +
448
+ ` 模式:${chat.computerUseMode}\n` +
449
+ ` Helper:${computerUseService.available() ? '✅ 可用' : '❌ 不可用'}\n` +
450
+ ` Backend:${response.state?.backend ?? '未知'}\n` +
451
+ ` 提示:输入 /computer help 查看所有可用命令`,
452
+ );
453
+ return;
454
+ }
455
+
456
+ if (computerCommand.matched) {
457
+ chat.ephemeralLine(
458
+ '[/computer] 用法:/computer status|apps|permissions|takeover|resume|cancel|stop|audit',
459
+ );
460
+ return;
461
+ }
462
+
463
+ if (trimmed === '/memories') {
464
+ const workspace = await workspaceIndexService.findByPath(getWorkingDir()).catch(() => null);
465
+ const records = await memoryService.recall('', workspace?.id, 100);
466
+ if (!records.length) {
467
+ chat.ephemeralLine('[/memories] 当前没有本机长期记忆。');
468
+ return;
469
+ }
470
+ chat.ephemeralLine(`[/memories] 本机长期记忆:\n${records.map((record) =>
471
+ ` ${record.id} · ${record.scope === 'personal' ? '个人' : '工作区'} · ${record.subject}: ${record.statement}`).join('\n')}\n使用 /forget-memory <id> 删除一条记忆。`);
472
+ return;
473
+ }
474
+
475
+ if (trimmed.startsWith('/forget-memory ')) {
476
+ const id = trimmed.slice('/forget-memory '.length).trim();
477
+ const workspace = await workspaceIndexService.findByPath(getWorkingDir()).catch(() => null);
478
+ const records = await memoryService.recall('', workspace?.id, 100);
479
+ const record = records.find((item) => item.id === id);
480
+ if (!record) {
481
+ chat.ephemeralLine(`[/forget-memory] 未找到记忆 ${id},未做修改。`);
482
+ return;
483
+ }
484
+ await memoryService.forget(record.id, record.scope, record.workspaceId);
485
+ chat.ephemeralLine(`[/forget-memory] 已删除“${record.subject}”。`);
486
+ return;
487
+ }
488
+
489
+ if (isBareSessionCmd) {
490
+ const sessions = await listSessionSummaries(getWorkingDir());
491
+ setSessionPicker(sessions);
492
+ return;
493
+ }
494
+
495
+ if (sessionArgMatch) {
496
+ const arg = sessionArgMatch[1]!.trim();
497
+ // "new" 必须是独立词(后面跟空格或到此为止),避免 "/session newton" 被误
498
+ // 拆成 "new" + 标题 "ton"——真实 session id 恰好以 "new" 开头时会撞上。
499
+ if (arg === 'new' || arg.startsWith('new ')) {
500
+ const title = arg.slice('new'.length).trim();
501
+ await handleSessionCreate(title);
502
+ return;
503
+ }
504
+ await handleSessionSelect(arg);
505
+ return;
506
+ }
507
+
508
+ // /list-archive
509
+ if (text.trim() === '/list-archive') {
510
+ const entries = await listArchives();
511
+ if (entries.length === 0) {
512
+ chat.ephemeralLine('[/list-archive] 当前 cwd 下无归档。');
513
+ return;
514
+ }
515
+ const lines = entries
516
+ .slice(0, 30)
517
+ .map(
518
+ (e) =>
519
+ ` ${e.id} · ${e.msgCount} msgs · ${e.firstUserPreview || '(empty)'}`,
520
+ )
521
+ .join('\n');
522
+ const tail =
523
+ entries.length > 30 ? `\n ...(共 ${entries.length} 条,仅显示前 30)` : '';
524
+ chat.ephemeralLine(
525
+ `[/list-archive] 归档列表(最新优先):\n${lines}${tail}`,
526
+ );
527
+ return;
528
+ }
529
+
530
+ // /restore <id>
531
+ if (text.startsWith('/restore ')) {
532
+ const id = text.slice('/restore '.length).trim();
533
+ if (id.length === 0) {
534
+ chat.ephemeralLine('[/restore] 用法:/restore <archive-id>');
535
+ return;
536
+ }
537
+ const messages = await restoreArchive(id);
538
+ if (!messages) {
539
+ chat.ephemeralLine(`[/restore] 未找到 archive id "${id}"`);
540
+ return;
541
+ }
542
+ // 覆盖当前 active session 文件;强制 reload 让 history 替换
543
+ const targetFile = sessionFileFor(getWorkingDir(), chat.activeSessionId);
544
+ await saveContext(messages, targetFile);
545
+ await chat.switchSession(chat.activeSessionId, { force: true });
546
+ chat.ephemeralLine(
547
+ `[/restore] 已恢复 archive ${id} 到 session "${chat.activeSessionId}"(${messages.length} 条消息)`,
548
+ );
549
+ return;
550
+ }
551
+
552
+ // /show <id-suffix>
553
+ if (text.startsWith('/show ')) {
554
+ const suffix = text.slice('/show '.length).trim();
555
+ if (suffix.length === 0) {
556
+ chat.ephemeralLine('[/show] 用法:/show <tool_call_id 末尾 8 字符>');
557
+ return;
558
+ }
559
+ const hit = historyRef.current.find(
560
+ (m) => m.role === 'tool' && m.tool_call_id.endsWith(suffix),
561
+ );
562
+ if (!hit || hit.role !== 'tool') {
563
+ chat.ephemeralLine(
564
+ `[/show] 未找到 tool_call_id 末尾匹配 "${suffix}" 的 tool result`,
565
+ );
566
+ return;
567
+ }
568
+ chat.ephemeralLine(
569
+ `[tool result · #${hit.tool_call_id.slice(-8)} · 完整 ${hit.content.split('\n').length} 行]\n${hit.content}`,
570
+ );
571
+ return;
572
+ }
573
+
574
+ // /plan <目标> —— 便捷别名,等价 /workflow plan(跑 plan 工作流:
575
+ // 调研 → AskUserQuestion 选择 → 产出 ./PLAN.md)。改写后透传给 pluginRunner。
576
+ // 目标里的引号剥掉,避免破坏 workflow 参数 tokenizer("..." 整体当一个位置参数)。
577
+ if (text.startsWith('/plan ') || text.trim() === '/plan') {
578
+ const goal = text.slice('/plan'.length).trim().replace(/"/g, '');
579
+ if (goal.length === 0) {
580
+ chat.ephemeralLine(
581
+ '[/plan] 用法:/plan <目标>(= /workflow plan:调研→提问选择→产出 ./PLAN.md)',
582
+ );
583
+ return;
584
+ }
585
+ void chat.submit(`/workflow plan "${goal}"`);
586
+ return;
587
+ }
588
+
589
+ // /model —— 列出已配置的基座模型并切换选择器;/model <id> 直接切换
590
+ const modelArgMatch = /^\/model(?:\s+(.+))?$/.exec(trimmed);
591
+ if (modelArgMatch) {
592
+ const arg = modelArgMatch[1]?.trim();
593
+ const models = await configService.models();
594
+ const enabledModels = models.filter((m) => m.enabled && m.apiKey);
595
+ if (enabledModels.length === 0) {
596
+ chat.ephemeralLine('[/model] 尚未配置任何模型,请先通过 Desktop 设置或 /config 添加模型。');
597
+ return;
598
+ }
599
+ if (arg) {
600
+ // 直接切换到指定模型(支持精确匹配 id 或模糊匹配 displayName/model)
601
+ const target = enabledModels.find((m) => m.id === arg || m.model === arg || m.displayName === arg);
602
+ if (!target) {
603
+ chat.ephemeralLine(`[/model] 未找到模型 "${arg}",可用模型:\n${enabledModels.map((m) => ` ${m.id} · ${m.displayName || m.model}`).join('\n')}`);
604
+ return;
605
+ }
606
+ await handleBaseModelSelect(target.id);
607
+ return;
608
+ }
609
+ // 无参数:打开交互选择器
610
+ const currentSession = (await listSessionSummaries(getWorkingDir())).find((session) => session.id === chat.activeSessionId);
611
+ const snap = await configService.getSnapshot();
612
+ setBaseModelPicker({ models: enabledModels, currentId: currentSession?.modelId ?? snap.config?.baseModelId ?? null });
613
+ return;
614
+ }
615
+
616
+ // /advisor [help|status|on|off|ask|select] —— 与 Desktop 共用统一配置。
617
+ // 解析在 ui/advisorCommand.ts(纯函数可测):认不得的子命令一律当咨询内容,
618
+ // 不再静默掉进 chat.submit() 被当成聊天发给 LLM。
619
+ const advisorCommand = parseAdvisorCommand(trimmed);
620
+ if (advisorCommand.matched) {
621
+ const sub = advisorCommand.command;
622
+ const note = advisorCommand.note;
623
+
624
+ // help 先于配置读取返回:configService.advisor() 每次都做 fresh reload
625
+ // (跨进程一致性的有意设计),纯文案命令没必要为此读一次盘。
626
+ if (sub === 'help') {
627
+ chat.ephemeralLine(advisorHelpText());
628
+ return;
629
+ }
630
+
631
+ const advisor = await configService.advisor();
632
+
633
+ if (sub === 'status') {
634
+ if (!advisor) {
635
+ chat.ephemeralLine(
636
+ '[/advisor] 未配置顾问模型,请在 Desktop 设置 → 模型中配置。',
637
+ );
638
+ } else {
639
+ chat.ephemeralLine(
640
+ `[/advisor] ${advisor.provider.name}/${advisor.provider.model} · ${advisor.enabled ? '开启' : '关闭'} · ${advisor.mode === 'auto' ? '智能自动' : '仅手动'}。`,
641
+ );
642
+ }
643
+ return;
644
+ }
645
+
646
+ if (sub === 'on') {
647
+ if (!advisor) {
648
+ chat.ephemeralLine(
649
+ '[/advisor] 未配置顾问模型,无法开启。',
650
+ );
651
+ } else {
652
+ await configService.savePatch({ advisor: { enabled: true } });
653
+ chat.ephemeralLine(`[/advisor] 顾问模型已开启(${advisor.provider.name}/${advisor.provider.model})。`);
654
+ }
655
+ return;
656
+ }
657
+
658
+ if (sub === 'off') {
659
+ if (advisor) await configService.savePatch({ advisor: { enabled: false } });
660
+ chat.ephemeralLine('[/advisor] 顾问模型已关闭。');
661
+ return;
662
+ }
663
+
664
+ if (sub === 'select') {
665
+ const models = await configService.models();
666
+ const enabledModels = models.filter((m) => m.enabled && m.apiKey);
667
+ if (enabledModels.length === 0) {
668
+ chat.ephemeralLine('[/advisor] 尚未配置任何模型,请先添加模型。');
669
+ return;
670
+ }
671
+ if (note) {
672
+ // /advisor select <id> 直接指定模型
673
+ const target = enabledModels.find((m) => m.id === note || m.model === note || m.displayName === note);
674
+ if (!target) {
675
+ chat.ephemeralLine(`[/advisor] 未找到模型 "${note}",可用模型:\n${enabledModels.map((m) => ` ${m.id} · ${m.displayName || m.model}`).join('\n')}`);
676
+ return;
677
+ }
678
+ await configService.savePatch({ advisor: { modelId: target.id, enabled: true } });
679
+ chat.ephemeralLine(`[/advisor] 已设置顾问模型为 ${target.displayName || target.model}(${formatProviderName(target.apiFormat)})。`);
680
+ } else {
681
+ // 无参数:打开交互选择器(含"关闭"选项)
682
+ const snap = await configService.getSnapshot();
683
+ setAdvisorModelPicker({ models: enabledModels, currentId: snap.config?.advisor?.modelId ?? null });
684
+ }
685
+ return;
686
+ }
687
+
688
+ // sub === 'ask':立即请求顾问意见。走 chat.runManualAdvisor 而不是裸 runAdvisor——
689
+ // 它负责过程显示(LiveArea 进度行)与 ESC 中断,否则最长 180 秒 TUI 全静默。
690
+ if (!advisor) {
691
+ chat.ephemeralLine('[/advisor] 未配置顾问模型:先用 /advisor select 选一个,或在 Desktop 设置 → 模型中配置。');
692
+ return;
693
+ }
694
+ if (!advisor.enabled) {
695
+ chat.ephemeralLine('[/advisor] 顾问模型当前已关闭;/advisor on 开启后再咨询。');
696
+ return;
697
+ }
698
+ const result = await chat.runManualAdvisor(note);
699
+ chat.ephemeralLine(
700
+ result.ok
701
+ ? `[/advisor] ${advisor.provider.model} 的意见:\n${result.content}`
702
+ : `[/advisor] ${result.error}`,
703
+ );
704
+ return;
705
+ }
706
+
707
+ // /config —— 硬编码配置菜单(取代旧版 LLM 驱动的 skills/config/SKILL.md)。
708
+ // 精确匹配,不接受多余文字(与 /advisor 等硬拦截命令同规则)。仅交互式 TUI
709
+ // 支持;-p 非交互模式没有 Ink 树可渲染菜单,此命令在那里就是普通聊天文本。
710
+ if (trimmed === '/config') {
711
+ setConfigMenuOpen(true);
712
+ return;
713
+ }
714
+
715
+ // 非命令 → 走正常 LLM 流程
716
+ void chat.submit(text);
717
+ },
718
+ // ★ 精细 deps:用 chat 的具体方法/字段,**不是**整个 chat 对象。
719
+ // useChat 每次重渲都返回新的 chat 对象字面量;用 [chat] 会让 handleSubmit
720
+ // 随每次流式 flush(100ms)重建一次 → InputBox 收到新 onSubmit → 强制重渲
721
+ // → Ink 重写 live 区 → spinner 看起来每帧打新行。
722
+ [
723
+ chat.ephemeralLine,
724
+ chat.switchSession,
725
+ chat.activeSessionId,
726
+ chat.submit,
727
+ chat.runManualAdvisor,
728
+ chat.setProvider,
729
+ chat.computerUseMode,
730
+ chat.setComputerUseMode,
731
+ chat.stopComputerUse,
732
+ chat.resumeComputerUse,
733
+ chat.cancelComputerUse,
734
+ handleSessionSelect,
735
+ handleSessionCreate,
736
+ openWorkspacePicker,
737
+ handleBaseModelSelect,
738
+ handleAdvisorModelSelect,
739
+ ],
740
+ );
741
+
742
+ // banner 改作为 Static items[0] 走 MessageList。原因:Ink 5 的 Static 约定
743
+ // 它必须是兄弟节点中第一个,否则 above-sibling 会跟 Static commit 一起被重打
744
+ // 到 stdout(实测看到 banner 反复打印 N 次)。
745
+ // 使用 getCurrentProvider() 获取最新 provider(/model 切换后实时显示)
746
+ const currentProvider = chat.getCurrentProvider();
747
+ const bannerText = `minimal-agent · ${currentProvider.name}/${currentProvider.model} · /model 切模型 · /computer 控制桌面 · /copy 复制回复 · /paste-image 添加图片 · /new 清空 · /compact 压缩 · Ctrl+C 中断`;
748
+
749
+ return (
750
+ <Box flexDirection="column">
751
+ {/* 消息历史 + banner(banner 作为 Static items[0] 进 scrollback,避免重打) */}
752
+ {/* deferredHistory:流式期间让 MessageList 重算"等空闲再做",避免高频抢渲染 */}
753
+ <MessageList
754
+ history={deferredHistory}
755
+ compactGeneration={chat.compactGeneration}
756
+ bannerText={bannerText}
757
+ terminalRows={terminalRows}
758
+ />
759
+
760
+ {/* ★ live 区整体钳到 cols-1:保证下面任何一行都不触及终端最后一列。
761
+ 根因(已用 node_modules/ink/build/log-update.js 源码确认):stock ink 用
762
+ `previousLineCount = output.split('\n').length` 决定下一帧 eraseLines(N),
763
+ 完全不补偿"满宽行被终端自动折行"。满宽行(如 InputBox 的 ╭──╮ 边框)在
764
+ Windows 下光标到末列、遇 \n 会双重换行 → 实际物理行 > \n 数 → eraseLines
765
+ 擦少 → 它上方的 live 行(reasoning)每帧 strand 进 scrollback 刷屏。
766
+ 留出最后一列即可从源头规避。MessageList(<Static>) 是 write-once、不参与每帧
767
+ log-update,无需约束(满宽 committed 行至多留一次性空行,不会持续刷屏)。 */}
768
+ <Box flexDirection="column" width={Math.max(1, terminalCols - 1)}>
769
+ {/* LiveArea:Static 下方唯一 live 区。有界多行(状态行 + 内容区)。
770
+ streamingText 让答案在 live 区流式逐行出现(落定后由 <Static> 接管完整呈现);
771
+ terminalRows 用于 contentMaxLines 自适应(满足 INV-2:live 区总高 < 终端行数)。 */}
772
+ <LiveArea
773
+ progressStage={chat.progressStage}
774
+ reasoningPreview={chat.reasoningPreview}
775
+ streamingText={chat.streamingText}
776
+ runningTools={chat.runningTools}
777
+ pluginProgress={chat.pluginProgress}
778
+ transientError={chat.transientError}
779
+ terminalCols={terminalCols}
780
+ terminalRows={terminalRows}
781
+ />
782
+
783
+ {/* 排队消息列表(AI工作中用户插话时显示) */}
784
+ <QueuedMessagesBanner items={chat.queuedItems} />
785
+
786
+ {chat.pendingComputerIntervention && (
787
+ <Box flexDirection="column" borderStyle="round" borderColor={theme.warning} paddingX={1} marginBottom={1}>
788
+ <Text color={theme.warning}>需要人工操作:{chat.pendingComputerIntervention.sanitizedSummary}</Text>
789
+ <Text dimColor>
790
+ 请直接在目标应用中处理;敏感内容不会进入终端或模型。完成后输入 /computer resume,取消输入 /computer cancel。
791
+ </Text>
792
+ </Box>
793
+ )}
794
+
795
+ {/* 错误提示 */}
796
+ {chat.error && (
797
+ <Box marginBottom={1}>
798
+ <Text color={theme.error}>⚠ {chat.error}</Text>
799
+ </Box>
800
+ )}
801
+
802
+ {/* 中断提示 */}
803
+ {chat.interrupted && (
804
+ <Box marginBottom={1}>
805
+ <Text color={theme.warning}>⚡ 操作被用户中断,等待新的任务输入...</Text>
806
+ <Text dimColor> 调整上一次计划:直接说调整内容 / 重新跑 /plan 目标 / 新任务直接说</Text>
807
+ </Box>
808
+ )}
809
+
810
+ {/* 输入区:/config 菜单覆盖层、/session /sessions /resume 打开的选择器、
811
+ /model /advisor select 打开的模型选择器、AskUserQuestion 待答、
812
+ 或普通 InputBox——都用 useInput,同时挂会抢键 → 必须互斥渲染。
813
+ 选完/取消/关闭后各自状态归位,InputBox 自动回归。 */}
814
+ {configMenuOpen ? (
815
+ <ConfigMenu mode="reconfigure" onDone={() => setConfigMenuOpen(false)} />
816
+ ) : baseModelPicker ? (
817
+ <ModelPicker
818
+ models={baseModelPicker.models}
819
+ currentModelId={baseModelPicker.currentId}
820
+ title="选择基座模型"
821
+ onSelect={handleBaseModelSelect}
822
+ onCancel={() => setBaseModelPicker(null)}
823
+ />
824
+ ) : advisorModelPicker ? (
825
+ <ModelPicker
826
+ models={advisorModelPicker.models}
827
+ currentModelId={advisorModelPicker.currentId}
828
+ title="选择顾问模型"
829
+ nullOptionLabel="○ 不使用顾问模型"
830
+ onSelect={handleAdvisorModelSelect}
831
+ onCancel={() => setAdvisorModelPicker(null)}
832
+ />
833
+ ) : workspacePicker ? (
834
+ <WorkspacePicker
835
+ workspaces={workspacePicker}
836
+ currentPath={getWorkingDir()}
837
+ currentSessionId={chat.activeSessionId}
838
+ onSelect={handleWorkspaceSelect}
839
+ onCancel={() => setWorkspacePicker(null)}
840
+ />
841
+ ) : sessionPicker ? (
842
+ <SessionPicker
843
+ sessions={sessionPicker}
844
+ activeSessionId={chat.activeSessionId}
845
+ onSelect={handleSessionSelect}
846
+ onCreate={handleSessionCreate}
847
+ onCancel={() => setSessionPicker(null)}
848
+ />
849
+ ) : chat.pendingAsk ? (
850
+ <AskQuestion
851
+ questions={chat.pendingAsk}
852
+ source={chat.pendingAskSource}
853
+ queuedCount={chat.pendingAskQueueCount}
854
+ onAnswer={chat.answerAsk}
855
+ onCancel={chat.cancelAsk}
856
+ />
857
+ ) : (
858
+ /* handleSubmit 包装做 slash 命令拦截,非命令透传 chat.submit */
859
+ <InputBox
860
+ onSubmit={handleSubmit}
861
+ disabled={chat.isLoading && !chat.pendingComputerIntervention}
862
+ onAbort={chat.abort}
863
+ onClear={handleClear}
864
+ onCompact={chat.compactNow}
865
+ onWorkspaces={openWorkspacePicker}
866
+ />
867
+ )}
868
+
869
+ {/* 底部状态栏 */}
870
+ <StatusLine
871
+ provider={currentProvider}
872
+ history={chat.history}
873
+ pluginProgress={chat.pluginProgress}
874
+ contextUsage={chat.contextUsage}
875
+ />
876
+ </Box>
877
+ </Box>
878
+ );
879
+ }