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
@@ -0,0 +1,684 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { open, readFile, readdir, stat, unlink } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ import { agentStateDir } from '../context/agentHome.js';
5
+ import { containsForbiddenSecret, sha256 } from './codec.js';
6
+ import { acquireFileLock, appendPrivateLine, atomicWriteFile, ensurePrivateDir, pathExists, } from './fileStore.js';
7
+ import { memoryTerms } from './retrieval.js';
8
+ import { BM25_DOCUMENT_THRESHOLD, Bm25Index, recordSearchIndexBuild, recordSearchIndexCacheHit, recordSearchIndexFallback, recordSearchStrategy, } from './searchIndex.js';
9
+ const MAX_EPISODE_CHARS = 8_000;
10
+ const MAX_SEARCH_FILES = 200;
11
+ const MAX_SEARCH_LINES = 20_000;
12
+ const MAX_SNIPPET_CHARS = 500;
13
+ /**
14
+ * 已解析 episode 常驻内存的上限。
15
+ *
16
+ * ⚠️ `episodeFileCache` 缓存的是**整个文件解析出的全部 episode**,而日志本身是只增不减的
17
+ * (每条 user/assistant 消息 ≤8000 字符)。没有上限时,Desktop 这种长驻进程搜一次历史就把
18
+ * 几十 MB 对话原文永久钉在堆上。按 episode 条数做 LRU,逐出最久未用的文件。
19
+ */
20
+ const MAX_CACHED_EPISODES = 20_000;
21
+ /** 两次保留清理之间的最小间隔——清理是尽力而为的后台动作,不该每条消息都扫盘。 */
22
+ const RETENTION_SWEEP_INTERVAL_MS = 5 * 60_000;
23
+ const episodeFileCache = new Map();
24
+ const episodeIndexCache = new Map();
25
+ const episodeDiagnostics = {
26
+ fullFileReads: 0,
27
+ incrementalFileReads: 0,
28
+ unchangedFileHits: 0,
29
+ cacheEvictions: 0,
30
+ retentionSweeps: 0,
31
+ };
32
+ export function episodeSearchDiagnostics() {
33
+ return { ...episodeDiagnostics };
34
+ }
35
+ export function resetEpisodeSearchDiagnostics() {
36
+ episodeDiagnostics.fullFileReads = 0;
37
+ episodeDiagnostics.incrementalFileReads = 0;
38
+ episodeDiagnostics.unchangedFileHits = 0;
39
+ episodeDiagnostics.cacheEvictions = 0;
40
+ episodeDiagnostics.retentionSweeps = 0;
41
+ episodeFileCache.clear();
42
+ episodeIndexCache.clear();
43
+ cacheClock = 0;
44
+ retentionSweepStates.clear();
45
+ }
46
+ let cacheClock = 0;
47
+ const retentionSweepStates = new Map();
48
+ const MAX_RETENTION_SWEEP_STATES = 128;
49
+ function cachedEpisodeCount() {
50
+ let total = 0;
51
+ for (const entry of episodeFileCache.values())
52
+ total += entry.episodes.length;
53
+ return total;
54
+ }
55
+ /** 超出 MAX_CACHED_EPISODES 就按最久未用逐出整份文件缓存,直到回到上限之内。 */
56
+ function evictEpisodeCache() {
57
+ if (cachedEpisodeCount() <= MAX_CACHED_EPISODES)
58
+ return;
59
+ const byAge = [...episodeFileCache.entries()].sort((a, b) => a[1].usedAt - b[1].usedAt);
60
+ for (const [path] of byAge) {
61
+ if (cachedEpisodeCount() <= MAX_CACHED_EPISODES)
62
+ break;
63
+ episodeFileCache.delete(path);
64
+ episodeDiagnostics.cacheEvictions += 1;
65
+ }
66
+ // 文件缓存变了,基于它建的 BM25 语料签名也就失效了。
67
+ episodeIndexCache.clear();
68
+ }
69
+ function episodeRoot() {
70
+ return join(agentStateDir(), 'episodes');
71
+ }
72
+ function validPart(value) {
73
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}$/.test(value) ||
74
+ value.includes('..')) {
75
+ throw new Error('会话记忆标识无效');
76
+ }
77
+ return value;
78
+ }
79
+ function episodePath(workspaceId, sessionId) {
80
+ return join(episodeRoot(), `workspace-${validPart(workspaceId)}--${validPart(sessionId)}.jsonl`);
81
+ }
82
+ function backfillMarkerPath(workspaceId) {
83
+ return join(episodeRoot(), `.backfill-${validPart(workspaceId)}.json`);
84
+ }
85
+ function workspaceJournalLockPath(workspaceId) {
86
+ return join(episodeRoot(), `.workspace-${validPart(workspaceId)}.journal`);
87
+ }
88
+ export function sanitizeEpisodeContent(content) {
89
+ const normalized = content
90
+ .replace(/data:[^;\s]+;base64,[A-Za-z0-9+/=]{80,}/g, '[binary omitted]')
91
+ .replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/gi, '[secret omitted]')
92
+ .trim()
93
+ .slice(0, MAX_EPISODE_CHARS);
94
+ if (!normalized || containsForbiddenSecret(normalized))
95
+ return null;
96
+ return normalized;
97
+ }
98
+ export async function appendConversationEpisode(input) {
99
+ const content = sanitizeEpisodeContent(input.content);
100
+ if (!content)
101
+ return null;
102
+ const createdAt = input.createdAt ?? Date.now();
103
+ const messageId = input.messageId || randomUUID();
104
+ const episode = {
105
+ id: randomUUID(),
106
+ workspaceId: input.workspaceId,
107
+ sessionId: input.sessionId,
108
+ messageId,
109
+ role: input.role,
110
+ content,
111
+ createdAt,
112
+ contentHash: sha256(`${input.role}\0${content}`),
113
+ };
114
+ const path = episodePath(input.workspaceId, input.sessionId);
115
+ await ensurePrivateDir(dirname(path));
116
+ await appendPrivateLine(path, JSON.stringify(episode));
117
+ return episode;
118
+ }
119
+ /** 删除某工作区(缺省为全部)的会话原文日志。供 Desktop「清空记忆」一并调用。 */
120
+ export async function clearConversationEpisodes(workspaceId) {
121
+ const release = workspaceId
122
+ ? await acquireFileLock(workspaceJournalLockPath(workspaceId))
123
+ : null;
124
+ try {
125
+ return await clearConversationEpisodesUnlocked(workspaceId);
126
+ }
127
+ finally {
128
+ await release?.();
129
+ }
130
+ }
131
+ async function clearConversationEpisodesUnlocked(workspaceId) {
132
+ const files = await episodeFiles(workspaceId).catch(() => []);
133
+ let removed = 0;
134
+ for (const path of files) {
135
+ const result = await mutateEpisodeFile(path, () => ({ kind: 'delete' })).catch(() => null);
136
+ if (result?.changed)
137
+ removed += 1;
138
+ }
139
+ // ⚠️ backfill 标记必须**留下(没有就补写)**,绝不能删。
140
+ // backfillLegacyHistory 会从 `sessions/*.json` 一次性导入旧会话;标记一旦消失,
141
+ // 下一次 SearchConversations 就会把刚刚清掉的对话原文整批重新导回来 —— 用户点了
142
+ // "永久清空",转头又能搜到,等于清空是假的。
143
+ if (workspaceId) {
144
+ await atomicWriteFile(backfillMarkerPath(workspaceId), `${JSON.stringify({ version: 1, workspaceId, sources: 0, completedAt: Date.now(), reason: 'cleared-by-user' }, null, 2)}\n`).catch(() => undefined);
145
+ }
146
+ episodeIndexCache.clear();
147
+ return removed;
148
+ }
149
+ function sameEpisodeFile(left, right) {
150
+ return left.ino === right.ino && left.size === right.size && left.mtimeMs === right.mtimeMs;
151
+ }
152
+ async function readStableEpisodeFile(path) {
153
+ for (let attempt = 0; attempt < 2; attempt += 1) {
154
+ const before = await stat(path).catch((error) => {
155
+ if (error.code === 'ENOENT')
156
+ return null;
157
+ throw error;
158
+ });
159
+ if (!before)
160
+ return null;
161
+ const raw = await readFile(path, 'utf8');
162
+ const after = await stat(path).catch(() => null);
163
+ if (after
164
+ && before.ino === after.ino
165
+ && before.size === after.size
166
+ && before.mtimeMs === after.mtimeMs
167
+ && Buffer.byteLength(raw, 'utf8') === after.size) {
168
+ return { raw, ino: after.ino, size: after.size, mtimeMs: after.mtimeMs };
169
+ }
170
+ }
171
+ return null;
172
+ }
173
+ /**
174
+ * 与 appendPrivateLine 共用同一把 `<episode>.lock`。大文件的解析在锁外完成,
175
+ * 获锁后只验证文件代际并提交;如果这期间有追加,就重读重算,绝不覆盖新行。
176
+ */
177
+ async function mutateEpisodeFile(path, plan) {
178
+ for (let attempt = 0; attempt < 3; attempt += 1) {
179
+ const source = await readStableEpisodeFile(path);
180
+ if (!source)
181
+ return { changed: false, bytes: 0 };
182
+ const mutation = plan(source.raw);
183
+ if (mutation.kind === 'unchanged')
184
+ return { changed: false, bytes: source.size };
185
+ const release = await acquireFileLock(path);
186
+ try {
187
+ const live = await stat(path).catch(() => null);
188
+ if (!live || !sameEpisodeFile(source, live))
189
+ continue;
190
+ if (mutation.kind === 'delete')
191
+ await unlink(path);
192
+ else
193
+ await atomicWriteFile(path, mutation.content);
194
+ episodeFileCache.delete(path);
195
+ episodeIndexCache.clear();
196
+ return {
197
+ changed: true,
198
+ bytes: mutation.kind === 'write' ? Buffer.byteLength(mutation.content, 'utf8') : 0,
199
+ };
200
+ }
201
+ finally {
202
+ await release();
203
+ }
204
+ }
205
+ // 文件持续忙时跳过本轮维护;下次保留扫描会再处理。
206
+ const current = await stat(path).catch(() => null);
207
+ return { changed: false, bytes: current?.size ?? 0 };
208
+ }
209
+ /** 列出某工作区(或全部)的会话原文日志文件。 */
210
+ async function episodeFiles(workspaceId) {
211
+ const prefix = workspaceId ? `workspace-${validPart(workspaceId)}--` : 'workspace-';
212
+ try {
213
+ return (await readdir(episodeRoot(), { withFileTypes: true }))
214
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.jsonl') && entry.name.startsWith(prefix))
215
+ .map((entry) => join(episodeRoot(), entry.name));
216
+ }
217
+ catch (error) {
218
+ if (error.code === 'ENOENT')
219
+ return [];
220
+ throw error;
221
+ }
222
+ }
223
+ /** 按龄清理一个文件;返回清理后的字节数(文件被清空则删除并返回 0)。 */
224
+ async function pruneFileByAge(path, cutoff) {
225
+ const result = await mutateEpisodeFile(path, (raw) => {
226
+ const lines = raw.split('\n').filter((line) => line.trim().length > 0);
227
+ const kept = lines.filter((line) => {
228
+ const episode = parseEpisode(line);
229
+ // 解析不出来的行按"保留"处理:清理是维护动作,不该顺手吞掉看不懂的数据。
230
+ return episode ? episode.createdAt >= cutoff : true;
231
+ });
232
+ if (kept.length === lines.length)
233
+ return { kind: 'unchanged' };
234
+ if (kept.length === 0)
235
+ return { kind: 'delete' };
236
+ return { kind: 'write', content: `${kept.join('\n')}\n` };
237
+ });
238
+ return result.bytes;
239
+ }
240
+ /**
241
+ * 会话原文保留策略:先按龄清理,仍超体积上限就从**最旧的文件**整份删起。
242
+ *
243
+ * 全程 best-effort:清理失败绝不能影响正在进行的对话(这是维护动作,不是功能)。
244
+ * 与 records 的删除语义严格区分——records 只有用户能删,这里删的是为检索而留的对话副本。
245
+ */
246
+ export async function applyEpisodeRetention(workspaceId, retention, now = Date.now()) {
247
+ const files = await episodeFiles(workspaceId).catch(() => []);
248
+ if (!files.length)
249
+ return;
250
+ episodeDiagnostics.retentionSweeps += 1;
251
+ const cutoff = now - retention.episodeDays * 86_400_000;
252
+ const sized = [];
253
+ for (const path of files) {
254
+ const bytes = await pruneFileByAge(path, cutoff).catch(() => -1);
255
+ if (bytes <= 0)
256
+ continue;
257
+ const info = await stat(path).catch(() => null);
258
+ sized.push({ path, bytes, mtimeMs: info?.mtimeMs ?? 0 });
259
+ }
260
+ const limit = retention.episodeMaxMegabytes * 1024 * 1024;
261
+ let total = sized.reduce((sum, item) => sum + item.bytes, 0);
262
+ if (total <= limit)
263
+ return;
264
+ // 从最旧的文件开始按**条目**裁剪,而不是整份删除:
265
+ // 一个工作区常常只有一个长期会话文件,整份删会让全部历史在某次追加后突然消失(悬崖),
266
+ // 而按条目裁剪的结果是"最旧的对话先被丢掉",与按龄清理的语义一致。
267
+ for (const item of sized.sort((a, b) => a.mtimeMs - b.mtimeMs)) {
268
+ if (total <= limit)
269
+ break;
270
+ const keepBytes = Math.max(0, item.bytes - (total - limit));
271
+ const remaining = await trimFileToNewestBytes(item.path, keepBytes).catch(() => item.bytes);
272
+ total -= item.bytes - remaining;
273
+ }
274
+ episodeIndexCache.clear();
275
+ }
276
+ /** 只保留文件末尾(最新)能放进 keepBytes 的整行;keepBytes 为 0 时删除文件。返回剩余字节数。 */
277
+ async function trimFileToNewestBytes(path, keepBytes) {
278
+ const result = await mutateEpisodeFile(path, (raw) => {
279
+ if (keepBytes <= 0)
280
+ return { kind: 'delete' };
281
+ const lines = raw.split('\n').filter((line) => line.trim().length > 0);
282
+ const kept = [];
283
+ let bytes = 0;
284
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
285
+ const size = Buffer.byteLength(`${lines[index]}\n`, 'utf8');
286
+ if (bytes + size > keepBytes)
287
+ break;
288
+ kept.unshift(lines[index]);
289
+ bytes += size;
290
+ }
291
+ if (!kept.length)
292
+ return { kind: 'delete' };
293
+ if (kept.length === lines.length)
294
+ return { kind: 'unchanged' };
295
+ return { kind: 'write', content: `${kept.join('\n')}\n` };
296
+ });
297
+ return result.bytes;
298
+ }
299
+ /**
300
+ * 是否该跑一次保留清理(同一进程内每 RETENTION_SWEEP_INTERVAL_MS 最多一次)。
301
+ *
302
+ * 本模块**不自己去读设置**:那需要 `import('./memoryService.js')`,而 memoryService 正好
303
+ * 静态 import 本模块,绕一圈会变成动态 import 的循环依赖——dev 下能跑,但 `bun --compile`
304
+ * 的绿色版打包期解析动态 import 的行为另算,不值得赌(见 GREEN-BUILD 相关教训)。
305
+ * 由知道设置的那一层(turnJournal)来问「该扫了吗」,本模块只保留纯粹的存储职责。
306
+ */
307
+ export function shouldSweepRetention(workspaceId, now = Date.now()) {
308
+ const current = retentionSweepStates.get(workspaceId);
309
+ if (current?.inFlight || (current && now - current.lastStartedAt < RETENTION_SWEEP_INTERVAL_MS)) {
310
+ if (current)
311
+ current.lastUsedAt = now;
312
+ return false;
313
+ }
314
+ retentionSweepStates.set(workspaceId, { lastStartedAt: now, lastUsedAt: now, inFlight: true });
315
+ if (retentionSweepStates.size > MAX_RETENTION_SWEEP_STATES) {
316
+ const victims = [...retentionSweepStates.entries()]
317
+ .filter(([, state]) => !state.inFlight)
318
+ .sort((a, b) => a[1].lastUsedAt - b[1].lastUsedAt)
319
+ .slice(0, retentionSweepStates.size - MAX_RETENTION_SWEEP_STATES);
320
+ for (const [key] of victims)
321
+ retentionSweepStates.delete(key);
322
+ }
323
+ return true;
324
+ }
325
+ export function finishRetentionSweep(workspaceId) {
326
+ const state = retentionSweepStates.get(workspaceId);
327
+ if (state) {
328
+ state.inFlight = false;
329
+ state.lastUsedAt = Date.now();
330
+ }
331
+ }
332
+ function parseEpisode(line) {
333
+ try {
334
+ const value = JSON.parse(line);
335
+ if (typeof value.id !== 'string' ||
336
+ typeof value.workspaceId !== 'string' ||
337
+ typeof value.sessionId !== 'string' ||
338
+ typeof value.messageId !== 'string' ||
339
+ (value.role !== 'user' && value.role !== 'assistant') ||
340
+ typeof value.content !== 'string' ||
341
+ typeof value.createdAt !== 'number' ||
342
+ typeof value.contentHash !== 'string')
343
+ return null;
344
+ return value;
345
+ }
346
+ catch {
347
+ return null;
348
+ }
349
+ }
350
+ function parseEpisodeChunk(chunk, trailing = '') {
351
+ const combined = `${trailing}${chunk}`;
352
+ const lines = combined.split('\n');
353
+ const nextTrailing = lines.pop() ?? '';
354
+ return {
355
+ episodes: lines.flatMap((line) => {
356
+ const episode = line ? parseEpisode(line) : null;
357
+ return episode ? [episode] : [];
358
+ }),
359
+ trailing: nextTrailing,
360
+ };
361
+ }
362
+ async function readEpisodeBytes(path, offset, length) {
363
+ if (length <= 0)
364
+ return '';
365
+ const handle = await open(path, 'r');
366
+ try {
367
+ const buffer = Buffer.alloc(length);
368
+ const { bytesRead } = await handle.read(buffer, 0, length, offset);
369
+ return buffer.subarray(0, bytesRead).toString('utf8');
370
+ }
371
+ finally {
372
+ await handle.close();
373
+ }
374
+ }
375
+ async function loadEpisodeFile(path) {
376
+ const info = await stat(path);
377
+ const cached = episodeFileCache.get(path);
378
+ cacheClock += 1;
379
+ if (cached
380
+ && cached.ino === info.ino
381
+ && cached.size === info.size
382
+ && cached.mtimeMs === info.mtimeMs) {
383
+ cached.usedAt = cacheClock;
384
+ episodeDiagnostics.unchangedFileHits += 1;
385
+ return cached;
386
+ }
387
+ if (cached && cached.ino === info.ino && info.size > cached.size) {
388
+ const chunk = await readEpisodeBytes(path, cached.size, info.size - cached.size);
389
+ const parsed = parseEpisodeChunk(chunk, cached.trailing);
390
+ const next = {
391
+ ino: info.ino,
392
+ size: info.size,
393
+ mtimeMs: info.mtimeMs,
394
+ trailing: parsed.trailing,
395
+ episodes: [...cached.episodes, ...parsed.episodes],
396
+ usedAt: cacheClock,
397
+ };
398
+ episodeFileCache.set(path, next);
399
+ episodeDiagnostics.incrementalFileReads += 1;
400
+ evictEpisodeCache();
401
+ return next;
402
+ }
403
+ const raw = await readFile(path, 'utf8');
404
+ const parsed = parseEpisodeChunk(raw);
405
+ const next = {
406
+ ino: info.ino,
407
+ size: info.size,
408
+ mtimeMs: info.mtimeMs,
409
+ trailing: parsed.trailing,
410
+ episodes: parsed.episodes,
411
+ usedAt: cacheClock,
412
+ };
413
+ episodeFileCache.set(path, next);
414
+ episodeDiagnostics.fullFileReads += 1;
415
+ evictEpisodeCache();
416
+ return next;
417
+ }
418
+ function persistedMessageText(message) {
419
+ if (!message || typeof message !== 'object')
420
+ return null;
421
+ const item = message;
422
+ if (item.role !== 'user' && item.role !== 'assistant')
423
+ return null;
424
+ let content = '';
425
+ if (typeof item.content === 'string')
426
+ content = item.content;
427
+ else if (Array.isArray(item.content)) {
428
+ content = item.content
429
+ .filter((part) => Boolean(part &&
430
+ typeof part === 'object' &&
431
+ part.type === 'text' &&
432
+ typeof part.text === 'string'))
433
+ .map((part) => part.text)
434
+ .join('\n');
435
+ }
436
+ if (!content.trim())
437
+ return null;
438
+ return {
439
+ role: item.role,
440
+ content,
441
+ ...(typeof item.id === 'string' ? { messageId: item.id } : {}),
442
+ ...(typeof item.timestamp === 'number'
443
+ ? { createdAt: item.timestamp }
444
+ : {}),
445
+ };
446
+ }
447
+ async function workspaceArchiveDirs(workspaceId) {
448
+ try {
449
+ const index = JSON.parse(await readFile(join(agentStateDir(), 'workspace-index.json'), 'utf8'));
450
+ const workspace = index.workspaces?.find((item) => item.id === workspaceId);
451
+ if (!workspace)
452
+ return [];
453
+ const paths = [
454
+ workspace.canonicalPath,
455
+ workspace.displayPath,
456
+ ...(workspace.pathAliases ?? []),
457
+ ].filter((value) => typeof value === 'string');
458
+ return Array.from(new Set(paths.map((path) => join(agentStateDir(), 'archive', createHash('sha1').update(path).digest('hex').slice(0, 12)))));
459
+ }
460
+ catch {
461
+ return [];
462
+ }
463
+ }
464
+ async function backfillLegacyHistory(workspaceId) {
465
+ const release = await acquireFileLock(workspaceJournalLockPath(workspaceId));
466
+ try {
467
+ await backfillLegacyHistoryUnlocked(workspaceId);
468
+ }
469
+ finally {
470
+ await release();
471
+ }
472
+ }
473
+ async function backfillLegacyHistoryUnlocked(workspaceId) {
474
+ const marker = backfillMarkerPath(workspaceId);
475
+ if (await pathExists(marker))
476
+ return;
477
+ const sources = [];
478
+ const sessionsDir = join(agentStateDir(), 'sessions');
479
+ try {
480
+ for (const entry of await readdir(sessionsDir, { withFileTypes: true })) {
481
+ const prefix = `workspace-${validPart(workspaceId)}--`;
482
+ if (entry.isFile() &&
483
+ entry.name.startsWith(prefix) &&
484
+ entry.name.endsWith('.json')) {
485
+ sources.push({
486
+ path: join(sessionsDir, entry.name),
487
+ sessionId: entry.name.slice(prefix.length, -'.json'.length),
488
+ });
489
+ }
490
+ }
491
+ }
492
+ catch (error) {
493
+ if (error.code !== 'ENOENT')
494
+ throw error;
495
+ }
496
+ for (const dir of await workspaceArchiveDirs(workspaceId)) {
497
+ try {
498
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
499
+ if (entry.isFile() && entry.name.endsWith('.json')) {
500
+ sources.push({
501
+ path: join(dir, entry.name),
502
+ sessionId: `archive-${entry.name.slice(0, -5)}`,
503
+ });
504
+ }
505
+ }
506
+ }
507
+ catch (error) {
508
+ if (error.code !== 'ENOENT')
509
+ throw error;
510
+ }
511
+ }
512
+ for (const source of sources.slice(0, MAX_SEARCH_FILES)) {
513
+ let parsed;
514
+ try {
515
+ parsed = JSON.parse(await readFile(source.path, 'utf8'));
516
+ }
517
+ catch {
518
+ continue;
519
+ }
520
+ for (const message of parsed.messages ?? []) {
521
+ const extracted = persistedMessageText(message);
522
+ if (!extracted)
523
+ continue;
524
+ await appendConversationEpisode({
525
+ workspaceId,
526
+ sessionId: source.sessionId,
527
+ ...extracted,
528
+ });
529
+ }
530
+ }
531
+ await atomicWriteFile(marker, `${JSON.stringify({
532
+ version: 1,
533
+ workspaceId,
534
+ sources: sources.length,
535
+ completedAt: Date.now(),
536
+ }, null, 2)}\n`);
537
+ }
538
+ function searchScore(episode, query) {
539
+ const haystack = episode.content.toLocaleLowerCase();
540
+ const normalized = query.trim().toLocaleLowerCase();
541
+ const terms = memoryTerms(query);
542
+ const coverage = terms.length
543
+ ? terms.filter((term) => haystack.includes(term)).length / terms.length
544
+ : 0;
545
+ const phrase = normalized && haystack.includes(normalized) ? 35 : 0;
546
+ const roleWeight = episode.role === 'user' ? 4 : 0;
547
+ return phrase + coverage * 30 + roleWeight + episode.createdAt / 1e13;
548
+ }
549
+ export async function searchConversationEpisodes(query, options) {
550
+ if (!query.trim())
551
+ return [];
552
+ if (options.scope === 'all' && !options.explicitCrossWorkspace) {
553
+ throw new Error('跨工作区历史检索需要当前用户明确授权');
554
+ }
555
+ if (options.scope === 'all') {
556
+ try {
557
+ const index = JSON.parse(await readFile(join(agentStateDir(), 'workspace-index.json'), 'utf8'));
558
+ for (const workspace of index.workspaces ?? []) {
559
+ if (typeof workspace.id === 'string')
560
+ await backfillLegacyHistory(workspace.id);
561
+ }
562
+ }
563
+ catch {
564
+ await backfillLegacyHistory(options.workspaceId);
565
+ }
566
+ }
567
+ else {
568
+ await backfillLegacyHistory(options.workspaceId);
569
+ }
570
+ let names = [];
571
+ try {
572
+ names = (await readdir(episodeRoot(), { withFileTypes: true }))
573
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.jsonl'))
574
+ .map((entry) => entry.name)
575
+ .filter((name) => options.scope === 'all' ||
576
+ name.startsWith(`workspace-${validPart(options.workspaceId)}--`))
577
+ .slice(0, MAX_SEARCH_FILES);
578
+ }
579
+ catch (error) {
580
+ if (error.code === 'ENOENT')
581
+ return [];
582
+ throw error;
583
+ }
584
+ const files = [];
585
+ let linesRead = 0;
586
+ for (const name of names) {
587
+ if (linesRead >= MAX_SEARCH_LINES)
588
+ break;
589
+ const cached = await loadEpisodeFile(join(episodeRoot(), name)).catch(() => null);
590
+ if (!cached)
591
+ continue;
592
+ files.push({ name, cached });
593
+ linesRead += cached.episodes.length;
594
+ }
595
+ const dedupe = new Set();
596
+ const episodes = [];
597
+ let accepted = 0;
598
+ outer: for (const file of files) {
599
+ for (const episode of file.cached.episodes) {
600
+ if (accepted++ >= MAX_SEARCH_LINES)
601
+ break outer;
602
+ if (dedupe.has(episode.contentHash))
603
+ continue;
604
+ if (options.sessionId && episode.sessionId !== options.sessionId)
605
+ continue;
606
+ dedupe.add(episode.contentHash);
607
+ episodes.push(episode);
608
+ }
609
+ }
610
+ const limit = Math.min(20, Math.max(1, options.limit ?? 8));
611
+ if (episodes.length < BM25_DOCUMENT_THRESHOLD) {
612
+ recordSearchStrategy('lexical', episodes.length);
613
+ return episodes
614
+ .map((episode) => ({ episode, score: searchScore(episode, query) }))
615
+ .filter((item) => item.score >= 6)
616
+ .sort((a, b) => b.score - a.score)
617
+ .slice(0, limit)
618
+ .map(({ episode, score }) => conversationResult(episode, score));
619
+ }
620
+ try {
621
+ const corpusKey = JSON.stringify([
622
+ options.scope ?? 'workspace',
623
+ options.workspaceId,
624
+ options.sessionId ?? '',
625
+ ]);
626
+ const signature = files
627
+ .map(({ name, cached }) => `${name}:${cached.ino}:${cached.size}:${cached.mtimeMs}`)
628
+ .join('|');
629
+ let cachedIndex = episodeIndexCache.get(corpusKey);
630
+ if (!cachedIndex || cachedIndex.signature !== signature) {
631
+ cachedIndex = {
632
+ signature,
633
+ index: new Bm25Index(episodes.map((episode) => ({
634
+ id: episode.id,
635
+ fields: [{ text: episode.content, weight: 1 }],
636
+ }))),
637
+ };
638
+ episodeIndexCache.set(corpusKey, cachedIndex);
639
+ recordSearchIndexBuild();
640
+ }
641
+ else {
642
+ recordSearchIndexCacheHit();
643
+ }
644
+ recordSearchStrategy('bm25', episodes.length);
645
+ const scores = cachedIndex.index.search(query);
646
+ const normalized = query.trim().toLocaleLowerCase();
647
+ return episodes
648
+ .map((episode) => {
649
+ const bm25 = scores.get(episode.id) ?? 0;
650
+ const phrase = normalized && episode.content.toLocaleLowerCase().includes(normalized) ? 35 : 0;
651
+ const roleWeight = episode.role === 'user' ? 4 : 0;
652
+ return { episode, bm25, score: bm25 * 10 + phrase + roleWeight + episode.createdAt / 1e13 };
653
+ })
654
+ .filter((item) => item.bm25 > 0)
655
+ .sort((a, b) => b.score - a.score)
656
+ .slice(0, limit)
657
+ .map(({ episode, score }) => conversationResult(episode, score));
658
+ }
659
+ catch {
660
+ recordSearchIndexFallback();
661
+ recordSearchStrategy('lexical', episodes.length);
662
+ return episodes
663
+ .map((episode) => ({ episode, score: searchScore(episode, query) }))
664
+ .filter((item) => item.score >= 6)
665
+ .sort((a, b) => b.score - a.score)
666
+ .slice(0, limit)
667
+ .map(({ episode, score }) => conversationResult(episode, score));
668
+ }
669
+ }
670
+ function conversationResult(episode, score) {
671
+ return {
672
+ ...episode,
673
+ score,
674
+ snippet: episode.content.length > MAX_SNIPPET_CHARS
675
+ ? `${episode.content.slice(0, MAX_SNIPPET_CHARS)}…`
676
+ : episode.content,
677
+ };
678
+ }
679
+ export const episodicMemoryService = {
680
+ append: appendConversationEpisode,
681
+ search: searchConversationEpisodes,
682
+ clear: clearConversationEpisodes,
683
+ applyRetention: applyEpisodeRetention,
684
+ };