@cjhyy/code-shell-core 0.5.0-rc.2 → 0.6.0-rc.1

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 (387) hide show
  1. package/README.md +13 -10
  2. package/dist/agent/agent-definition-registry.d.ts +3 -0
  3. package/dist/agent/agent-definition-registry.js +3 -1
  4. package/dist/agent/agent-definition.d.ts +15 -0
  5. package/dist/agent/agent-definition.js +27 -3
  6. package/dist/arena/context/context-tools.js +47 -4
  7. package/dist/arena/ledger.js +9 -1
  8. package/dist/arena/strategies/utils.d.ts +1 -4
  9. package/dist/arena/strategies/utils.js +7 -84
  10. package/dist/automation/cron-expr.d.ts +10 -0
  11. package/dist/automation/cron-expr.js +23 -5
  12. package/dist/automation/runner.d.ts +21 -4
  13. package/dist/automation/runner.js +48 -12
  14. package/dist/automation/scheduler.d.ts +38 -0
  15. package/dist/automation/scheduler.js +89 -12
  16. package/dist/automation/store.js +5 -2
  17. package/dist/capability-control/disabled-lists.d.ts +25 -0
  18. package/dist/capability-control/disabled-lists.js +57 -0
  19. package/dist/capability-control/overlay.d.ts +15 -0
  20. package/dist/capability-control/overlay.js +27 -0
  21. package/dist/cc-orchestrator/agent-adapter.d.ts +50 -0
  22. package/dist/cc-orchestrator/agent-adapter.js +133 -0
  23. package/dist/cc-orchestrator/cc-capability.d.ts +19 -0
  24. package/dist/cc-orchestrator/cc-capability.js +53 -0
  25. package/dist/cc-orchestrator/codex-session-discovery.d.ts +24 -0
  26. package/dist/cc-orchestrator/codex-session-discovery.js +191 -0
  27. package/dist/cc-orchestrator/codex-session-history.d.ts +25 -0
  28. package/dist/cc-orchestrator/codex-session-history.js +187 -0
  29. package/dist/cc-orchestrator/external-agent-changes.d.ts +19 -0
  30. package/dist/cc-orchestrator/external-agent-changes.js +214 -0
  31. package/dist/cc-orchestrator/external-agent-driver.d.ts +18 -0
  32. package/dist/cc-orchestrator/external-agent-driver.js +69 -0
  33. package/dist/cc-orchestrator/index.d.ts +8 -0
  34. package/dist/cc-orchestrator/index.js +8 -0
  35. package/dist/cc-orchestrator/relevance-judge.d.ts +15 -0
  36. package/dist/cc-orchestrator/relevance-judge.js +29 -0
  37. package/dist/cc-orchestrator/session-discovery.d.ts +46 -0
  38. package/dist/cc-orchestrator/session-discovery.js +125 -0
  39. package/dist/cc-orchestrator/session-history.d.ts +19 -0
  40. package/dist/cc-orchestrator/session-history.js +67 -0
  41. package/dist/cli/agent-server-stdio.d.ts +15 -1
  42. package/dist/cli/agent-server-stdio.js +115 -17
  43. package/dist/cli/agent-server-tcp.js +40 -26
  44. package/dist/context/compaction.d.ts +56 -0
  45. package/dist/context/compaction.js +186 -0
  46. package/dist/context/manager.js +25 -4
  47. package/dist/cost-tracker.js +5 -61
  48. package/dist/credentials/cipher.d.ts +49 -0
  49. package/dist/credentials/cipher.js +45 -0
  50. package/dist/credentials/cookie-jar.d.ts +24 -0
  51. package/dist/credentials/cookie-jar.js +40 -0
  52. package/dist/credentials/index.d.ts +7 -0
  53. package/dist/credentials/index.js +5 -0
  54. package/dist/credentials/inject-credential-tool.d.ts +20 -0
  55. package/dist/credentials/inject-credential-tool.js +130 -0
  56. package/dist/credentials/store.d.ts +72 -0
  57. package/dist/credentials/store.js +184 -0
  58. package/dist/credentials/types.d.ts +56 -0
  59. package/dist/credentials/use-credential-tool.d.ts +29 -0
  60. package/dist/credentials/use-credential-tool.js +205 -0
  61. package/dist/credentials/use-gate.d.ts +56 -0
  62. package/dist/credentials/use-gate.js +52 -0
  63. package/dist/data/model-metadata.d.ts +77 -0
  64. package/dist/data/model-metadata.js +56 -0
  65. package/dist/data/model-metadata.json +216 -0
  66. package/dist/data/openrouter-models.d.ts +18 -7
  67. package/dist/data/openrouter-models.js +35 -8
  68. package/dist/engine/aux-key.d.ts +10 -0
  69. package/dist/engine/aux-key.js +11 -0
  70. package/dist/engine/dynamic-tool-defs.d.ts +19 -0
  71. package/dist/engine/dynamic-tool-defs.js +36 -0
  72. package/dist/engine/engine.d.ts +267 -138
  73. package/dist/engine/engine.js +957 -191
  74. package/dist/engine/friendly-error.d.ts +18 -0
  75. package/dist/engine/friendly-error.js +63 -0
  76. package/dist/engine/goal.d.ts +145 -0
  77. package/dist/engine/goal.js +144 -0
  78. package/dist/engine/image-policy.d.ts +13 -0
  79. package/dist/engine/image-policy.js +24 -0
  80. package/dist/engine/model-connections-pool.d.ts +17 -0
  81. package/dist/engine/model-connections-pool.js +66 -0
  82. package/dist/engine/model-facade.d.ts +10 -0
  83. package/dist/engine/model-facade.js +25 -0
  84. package/dist/engine/patch-orphaned-tools.js +3 -0
  85. package/dist/engine/resolve-llm-config.d.ts +16 -0
  86. package/dist/engine/resolve-llm-config.js +44 -0
  87. package/dist/engine/runtime.js +2 -1
  88. package/dist/engine/sandbox-cache-key.d.ts +10 -0
  89. package/dist/engine/sandbox-cache-key.js +9 -0
  90. package/dist/engine/sandbox-config.d.ts +31 -0
  91. package/dist/engine/sandbox-config.js +38 -0
  92. package/dist/engine/session-usage.d.ts +19 -0
  93. package/dist/engine/session-usage.js +25 -0
  94. package/dist/engine/steer-queue.d.ts +32 -0
  95. package/dist/engine/steer-queue.js +26 -0
  96. package/dist/engine/streaming-tool-queue.d.ts +12 -0
  97. package/dist/engine/streaming-tool-queue.js +50 -10
  98. package/dist/engine/turn-loop.d.ts +67 -3
  99. package/dist/engine/turn-loop.js +292 -41
  100. package/dist/engine/types.d.ts +175 -0
  101. package/dist/engine/types.js +13 -0
  102. package/dist/external-agents/config.d.ts +2 -0
  103. package/dist/external-agents/config.js +15 -0
  104. package/dist/external-agents/types.d.ts +31 -0
  105. package/dist/external-agents/types.js +1 -0
  106. package/dist/git/utils.js +11 -6
  107. package/dist/git/worktree.d.ts +48 -0
  108. package/dist/git/worktree.js +86 -10
  109. package/dist/hooks/goal-stop-hook.d.ts +28 -0
  110. package/dist/hooks/goal-stop-hook.js +186 -9
  111. package/dist/hooks/registry.d.ts +8 -0
  112. package/dist/hooks/registry.js +19 -0
  113. package/dist/hooks/shell-runner.js +10 -21
  114. package/dist/index.d.ts +49 -6
  115. package/dist/index.js +60 -4
  116. package/dist/llm/capabilities/param-specs.d.ts +14 -0
  117. package/dist/llm/capabilities/param-specs.js +62 -0
  118. package/dist/llm/capabilities/rules.js +4 -0
  119. package/dist/llm/capabilities/types.d.ts +10 -0
  120. package/dist/llm/client-base.d.ts +28 -1
  121. package/dist/llm/client-base.js +119 -13
  122. package/dist/llm/model-cache.js +4 -2
  123. package/dist/llm/model-pool.d.ts +14 -0
  124. package/dist/llm/model-pool.js +13 -0
  125. package/dist/llm/provider-auth.d.ts +41 -0
  126. package/dist/llm/provider-auth.js +76 -0
  127. package/dist/llm/provider-catalog.d.ts +4 -0
  128. package/dist/llm/providers/anthropic.js +64 -14
  129. package/dist/llm/providers/openai.d.ts +36 -0
  130. package/dist/llm/providers/openai.js +257 -51
  131. package/dist/llm/reasoning-setting.d.ts +3 -3
  132. package/dist/llm/reasoning-setting.js +9 -1
  133. package/dist/llm/stream-watchdog.js +5 -1
  134. package/dist/llm/token-counter.js +9 -2
  135. package/dist/llm/types.d.ts +4 -0
  136. package/dist/logging/sanitize-messages.js +22 -0
  137. package/dist/lsp/manager.d.ts +1 -1
  138. package/dist/lsp/manager.js +40 -10
  139. package/dist/model-catalog/builtin.d.ts +12 -0
  140. package/dist/model-catalog/builtin.js +407 -0
  141. package/dist/model-catalog/gen-connections.d.ts +20 -0
  142. package/dist/model-catalog/gen-connections.js +28 -0
  143. package/dist/model-catalog/index.d.ts +41 -0
  144. package/dist/model-catalog/index.js +90 -0
  145. package/dist/model-catalog/params.d.ts +20 -0
  146. package/dist/model-catalog/params.js +45 -0
  147. package/dist/model-catalog/resolve.d.ts +48 -0
  148. package/dist/model-catalog/resolve.js +33 -0
  149. package/dist/model-catalog/save-entry.d.ts +32 -0
  150. package/dist/model-catalog/save-entry.js +104 -0
  151. package/dist/model-catalog/types.d.ts +561 -0
  152. package/dist/model-catalog/types.js +93 -0
  153. package/dist/model-catalog/upsert.d.ts +9 -0
  154. package/dist/model-catalog/upsert.js +8 -0
  155. package/dist/onboarding.d.ts +12 -82
  156. package/dist/onboarding.js +61 -326
  157. package/dist/plugins/gitOps.d.ts +19 -0
  158. package/dist/plugins/gitOps.js +73 -4
  159. package/dist/plugins/installer/checkUpdate.d.ts +16 -0
  160. package/dist/plugins/installer/checkUpdate.js +32 -0
  161. package/dist/plugins/installer/codex/convertCommands.d.ts +19 -0
  162. package/dist/plugins/installer/codex/convertCommands.js +46 -0
  163. package/dist/plugins/installer/codex/convertMcp.d.ts +5 -2
  164. package/dist/plugins/installer/codex/convertMcp.js +45 -5
  165. package/dist/plugins/installer/install.js +25 -1
  166. package/dist/plugins/installer/installFromArchive.d.ts +43 -0
  167. package/dist/plugins/installer/installFromArchive.js +134 -0
  168. package/dist/plugins/installer/installFromSource.js +8 -2
  169. package/dist/plugins/installer/loadPluginAgents.js +6 -2
  170. package/dist/plugins/installer/loadPluginMcp.d.ts +9 -2
  171. package/dist/plugins/installer/loadPluginMcp.js +35 -2
  172. package/dist/plugins/installer/pruneDisabled.d.ts +24 -0
  173. package/dist/plugins/installer/pruneDisabled.js +73 -0
  174. package/dist/plugins/installer/types.d.ts +5 -2
  175. package/dist/plugins/installer/types.js +1 -0
  176. package/dist/plugins/installer/uninstall.js +4 -0
  177. package/dist/plugins/installer/unzip.d.ts +14 -0
  178. package/dist/plugins/installer/unzip.js +82 -0
  179. package/dist/plugins/installer/update.d.ts +14 -0
  180. package/dist/plugins/installer/update.js +53 -21
  181. package/dist/plugins/loadPluginHooks.d.ts +47 -1
  182. package/dist/plugins/loadPluginHooks.js +73 -1
  183. package/dist/plugins/marketplaceManager.d.ts +7 -0
  184. package/dist/plugins/marketplaceManager.js +20 -0
  185. package/dist/plugins/pluginCommandHook.js +4 -18
  186. package/dist/plugins/pluginContent.d.ts +30 -0
  187. package/dist/plugins/pluginContent.js +83 -0
  188. package/dist/plugins/pluginInstaller.d.ts +13 -0
  189. package/dist/plugins/pluginInstaller.js +42 -8
  190. package/dist/plugins/schemas.js +1 -0
  191. package/dist/plugins/types.d.ts +6 -0
  192. package/dist/preset/index.d.ts +7 -1
  193. package/dist/preset/index.js +96 -6
  194. package/dist/product/types.d.ts +1 -1
  195. package/dist/prompt/composer.d.ts +27 -0
  196. package/dist/prompt/composer.js +67 -21
  197. package/dist/prompt/instruction-scanner.js +5 -3
  198. package/dist/prompt/section-loader.js +1 -0
  199. package/dist/prompt/sections/base.md +1 -0
  200. package/dist/prompt/sections/browser.md +10 -0
  201. package/dist/prompt/sections/coding.md +4 -0
  202. package/dist/protocol/chat-session-manager.d.ts +8 -2
  203. package/dist/protocol/chat-session-manager.js +36 -0
  204. package/dist/protocol/chat-session.d.ts +65 -0
  205. package/dist/protocol/chat-session.js +94 -2
  206. package/dist/protocol/client.d.ts +27 -0
  207. package/dist/protocol/client.js +38 -0
  208. package/dist/protocol/server.d.ts +112 -0
  209. package/dist/protocol/server.js +586 -16
  210. package/dist/protocol/transport.js +3 -2
  211. package/dist/protocol/types.d.ts +46 -0
  212. package/dist/protocol/types.js +16 -0
  213. package/dist/quota/credentials.d.ts +3 -0
  214. package/dist/quota/credentials.js +80 -0
  215. package/dist/quota/index.d.ts +36 -0
  216. package/dist/quota/index.js +155 -0
  217. package/dist/quota/types.d.ts +48 -0
  218. package/dist/quota/types.js +13 -0
  219. package/dist/review/review-prompt.d.ts +28 -0
  220. package/dist/review/review-prompt.js +81 -0
  221. package/dist/run/FileRunStore.js +8 -3
  222. package/dist/run/RunApprovalBackend.js +25 -5
  223. package/dist/run/RunManager.d.ts +12 -0
  224. package/dist/run/RunManager.js +35 -0
  225. package/dist/run/factory.d.ts +1 -1
  226. package/dist/runtime/background-shell.d.ts +139 -0
  227. package/dist/runtime/background-shell.js +495 -0
  228. package/dist/runtime/output-clean.d.ts +24 -0
  229. package/dist/runtime/output-clean.js +41 -0
  230. package/dist/runtime/ring-file.d.ts +64 -0
  231. package/dist/runtime/ring-file.js +174 -0
  232. package/dist/runtime/safe-spawn.js +15 -34
  233. package/dist/runtime/spawn-common.d.ts +148 -0
  234. package/dist/runtime/spawn-common.js +303 -0
  235. package/dist/runtime/truncate-output.d.ts +22 -0
  236. package/dist/runtime/truncate-output.js +49 -0
  237. package/dist/runtime/utf8-cut.d.ts +11 -0
  238. package/dist/runtime/utf8-cut.js +23 -0
  239. package/dist/services/auto-dream.d.ts +4 -0
  240. package/dist/services/auto-dream.js +26 -26
  241. package/dist/services/diagnostics.d.ts +1 -2
  242. package/dist/services/diagnostics.js +12 -7
  243. package/dist/services/extract-memories.d.ts +14 -1
  244. package/dist/services/extract-memories.js +45 -6
  245. package/dist/services/memory-orchestrator.d.ts +21 -0
  246. package/dist/services/memory-orchestrator.js +125 -50
  247. package/dist/services/session-memory.js +24 -12
  248. package/dist/session/file-history.d.ts +124 -1
  249. package/dist/session/file-history.js +222 -6
  250. package/dist/session/memory.d.ts +116 -2
  251. package/dist/session/memory.js +250 -28
  252. package/dist/session/session-manager.d.ts +40 -0
  253. package/dist/session/session-manager.js +113 -3
  254. package/dist/session/simple-diff.d.ts +23 -0
  255. package/dist/session/simple-diff.js +84 -0
  256. package/dist/session/transcript.d.ts +25 -1
  257. package/dist/session/transcript.js +35 -2
  258. package/dist/session/undo-target.d.ts +67 -0
  259. package/dist/session/undo-target.js +144 -0
  260. package/dist/settings/disk-defaults.d.ts +9 -2
  261. package/dist/settings/disk-defaults.js +11 -2
  262. package/dist/settings/feature-flags.d.ts +64 -0
  263. package/dist/settings/feature-flags.js +61 -0
  264. package/dist/settings/manager.d.ts +68 -1
  265. package/dist/settings/manager.js +266 -29
  266. package/dist/settings/migrate-config.d.ts +45 -0
  267. package/dist/settings/migrate-config.js +125 -0
  268. package/dist/settings/schema-export.d.ts +25 -0
  269. package/dist/settings/schema-export.js +38 -0
  270. package/dist/settings/schema.d.ts +1186 -771
  271. package/dist/settings/schema.js +268 -97
  272. package/dist/skills/scanner.d.ts +9 -0
  273. package/dist/skills/scanner.js +30 -2
  274. package/dist/stt/resolve-transcribe.d.ts +31 -0
  275. package/dist/stt/resolve-transcribe.js +108 -0
  276. package/dist/stt/transcribe.d.ts +51 -0
  277. package/dist/stt/transcribe.js +65 -0
  278. package/dist/tool-system/browser-bridge.d.ts +226 -0
  279. package/dist/tool-system/browser-bridge.js +163 -0
  280. package/dist/tool-system/builtin/agent-heartbeat.d.ts +49 -0
  281. package/dist/tool-system/builtin/agent-heartbeat.js +89 -0
  282. package/dist/tool-system/builtin/agent-notifications.d.ts +12 -3
  283. package/dist/tool-system/builtin/agent-notifications.js +9 -3
  284. package/dist/tool-system/builtin/agent-output-file.d.ts +38 -0
  285. package/dist/tool-system/builtin/agent-output-file.js +72 -0
  286. package/dist/tool-system/builtin/agent-registry.d.ts +12 -0
  287. package/dist/tool-system/builtin/agent-registry.js +8 -0
  288. package/dist/tool-system/builtin/agent.d.ts +21 -1
  289. package/dist/tool-system/builtin/agent.js +489 -42
  290. package/dist/tool-system/builtin/apply-patch/applier.js +66 -8
  291. package/dist/tool-system/builtin/apply-patch/backup-targets.d.ts +10 -0
  292. package/dist/tool-system/builtin/apply-patch/backup-targets.js +30 -0
  293. package/dist/tool-system/builtin/apply-patch/index.js +0 -15
  294. package/dist/tool-system/builtin/background-jobs.d.ts +76 -0
  295. package/dist/tool-system/builtin/background-jobs.js +124 -0
  296. package/dist/tool-system/builtin/background-shell-tools.d.ts +20 -0
  297. package/dist/tool-system/builtin/background-shell-tools.js +108 -0
  298. package/dist/tool-system/builtin/background-work.d.ts +67 -0
  299. package/dist/tool-system/builtin/background-work.js +86 -0
  300. package/dist/tool-system/builtin/bash-output-style.d.ts +32 -0
  301. package/dist/tool-system/builtin/bash-output-style.js +40 -0
  302. package/dist/tool-system/builtin/bash.d.ts +5 -2
  303. package/dist/tool-system/builtin/bash.js +98 -62
  304. package/dist/tool-system/builtin/browser-tools.d.ts +33 -0
  305. package/dist/tool-system/builtin/browser-tools.js +312 -0
  306. package/dist/tool-system/builtin/cancel-goal.d.ts +31 -0
  307. package/dist/tool-system/builtin/cancel-goal.js +64 -0
  308. package/dist/tool-system/builtin/check-quota.d.ts +15 -0
  309. package/dist/tool-system/builtin/check-quota.js +34 -0
  310. package/dist/tool-system/builtin/config.js +7 -0
  311. package/dist/tool-system/builtin/cron.d.ts +7 -0
  312. package/dist/tool-system/builtin/cron.js +55 -3
  313. package/dist/tool-system/builtin/drive-claude-code.d.ts +30 -0
  314. package/dist/tool-system/builtin/drive-claude-code.js +157 -0
  315. package/dist/tool-system/builtin/edit-model-catalog.d.ts +3 -0
  316. package/dist/tool-system/builtin/edit-model-catalog.js +94 -0
  317. package/dist/tool-system/builtin/edit.js +20 -16
  318. package/dist/tool-system/builtin/eol.d.ts +29 -0
  319. package/dist/tool-system/builtin/eol.js +37 -0
  320. package/dist/tool-system/builtin/file-cache.d.ts +6 -0
  321. package/dist/tool-system/builtin/file-cache.js +8 -0
  322. package/dist/tool-system/builtin/generate-image.d.ts +35 -0
  323. package/dist/tool-system/builtin/generate-image.js +278 -50
  324. package/dist/tool-system/builtin/generate-video.d.ts +55 -0
  325. package/dist/tool-system/builtin/generate-video.js +361 -0
  326. package/dist/tool-system/builtin/glob.js +0 -7
  327. package/dist/tool-system/builtin/grep.js +6 -8
  328. package/dist/tool-system/builtin/image-providers.d.ts +86 -0
  329. package/dist/tool-system/builtin/image-providers.js +190 -0
  330. package/dist/tool-system/builtin/image-uploader.d.ts +33 -0
  331. package/dist/tool-system/builtin/image-uploader.js +74 -0
  332. package/dist/tool-system/builtin/index.d.ts +7 -1
  333. package/dist/tool-system/builtin/index.js +249 -25
  334. package/dist/tool-system/builtin/mcp-tools.js +23 -3
  335. package/dist/tool-system/builtin/memory.js +45 -7
  336. package/dist/tool-system/builtin/notebook-edit.js +0 -7
  337. package/dist/tool-system/builtin/powershell.js +4 -1
  338. package/dist/tool-system/builtin/read.js +10 -10
  339. package/dist/tool-system/builtin/repl.js +4 -1
  340. package/dist/tool-system/builtin/skill.js +9 -0
  341. package/dist/tool-system/builtin/sleep.js +3 -2
  342. package/dist/tool-system/builtin/tool-search.js +25 -7
  343. package/dist/tool-system/builtin/video-providers.d.ts +154 -0
  344. package/dist/tool-system/builtin/video-providers.js +235 -0
  345. package/dist/tool-system/builtin/web-fetch.js +12 -2
  346. package/dist/tool-system/builtin/web-search.js +21 -7
  347. package/dist/tool-system/builtin/worktree.d.ts +2 -1
  348. package/dist/tool-system/builtin/worktree.js +31 -4
  349. package/dist/tool-system/builtin/write.js +0 -6
  350. package/dist/tool-system/context.d.ts +154 -6
  351. package/dist/tool-system/executor.d.ts +3 -1
  352. package/dist/tool-system/executor.js +172 -88
  353. package/dist/tool-system/investigation-guard.js +1 -1
  354. package/dist/tool-system/mcp-manager.d.ts +42 -4
  355. package/dist/tool-system/mcp-manager.js +186 -20
  356. package/dist/tool-system/path-policy.d.ts +5 -0
  357. package/dist/tool-system/path-policy.js +307 -8
  358. package/dist/tool-system/permission.d.ts +36 -1
  359. package/dist/tool-system/permission.js +281 -69
  360. package/dist/tool-system/plan-mode-allowlist.d.ts +13 -2
  361. package/dist/tool-system/plan-mode-allowlist.js +24 -2
  362. package/dist/tool-system/registry.d.ts +1 -0
  363. package/dist/tool-system/registry.js +16 -2
  364. package/dist/tool-system/sandbox/index.d.ts +8 -0
  365. package/dist/tool-system/sandbox/index.js +7 -2
  366. package/dist/tool-system/validate-tool-metadata.d.ts +36 -0
  367. package/dist/tool-system/validate-tool-metadata.js +63 -0
  368. package/dist/types.d.ts +217 -6
  369. package/dist/updater.js +20 -9
  370. package/dist/utils/envUtils.d.ts +0 -9
  371. package/dist/utils/envUtils.js +3 -28
  372. package/dist/utils/exec.d.ts +40 -0
  373. package/dist/utils/exec.js +144 -0
  374. package/dist/utils/json.d.ts +12 -0
  375. package/dist/utils/json.js +92 -0
  376. package/dist/utils/theme.d.ts +1 -1
  377. package/dist/utils/theme.js +1 -1
  378. package/dist/utils/toolDisplay.js +0 -1
  379. package/package.json +13 -7
  380. package/dist/agent/coordinator.d.ts +0 -49
  381. package/dist/agent/coordinator.js +0 -77
  382. package/dist/settings/manager.test.js +0 -73
  383. package/dist/tool-system/builtin/remote-trigger.d.ts +0 -6
  384. package/dist/tool-system/builtin/remote-trigger.js +0 -54
  385. package/dist/tool-system/builtin/send-message.d.ts +0 -6
  386. package/dist/tool-system/builtin/send-message.js +0 -47
  387. /package/dist/{settings/manager.test.d.ts → credentials/types.js} +0 -0
@@ -22,13 +22,32 @@
22
22
  import { mkdirSync, existsSync, readFileSync, writeFileSync, readdirSync, renameSync, statSync, } from "node:fs";
23
23
  import { join } from "node:path";
24
24
  import { homedir } from "node:os";
25
+ /**
26
+ * Drop memories older than `maxAgeDays` (by file mtime) for context injection
27
+ * (TODO 8.1). A non-positive/undefined maxAge means no filtering. Entries with
28
+ * an unknown mtime (updatedAt 0/undefined) are KEPT — we never hide a memory
29
+ * just because we couldn't read its timestamp. Pure + testable.
30
+ */
31
+ export function filterByAge(entries, maxAgeDays, now = Date.now()) {
32
+ if (!maxAgeDays || maxAgeDays <= 0)
33
+ return entries;
34
+ const cutoff = now - maxAgeDays * 24 * 60 * 60 * 1000;
35
+ // Pinned memories never age out of injection — that's the point of the pin.
36
+ return entries.filter((e) => e.pinned || !e.updatedAt || e.updatedAt >= cutoff);
37
+ }
25
38
  export function resolveMemoryBaseDir(override) {
26
39
  if (override)
27
40
  return override;
28
41
  const fromEnv = process.env.CODE_SHELL_HOME;
29
42
  if (fromEnv && fromEnv.length > 0)
30
43
  return fromEnv;
31
- return join(homedir(), ".code-shell");
44
+ // `process.env.HOME ?? homedir()` (NOT raw homedir()): bun caches homedir()
45
+ // at process start and never re-reads it, so a test's HOME override — and any
46
+ // code that relies on $HOME — would be ignored, and memory would read the
47
+ // REAL ~/.code-shell (leaking global memories into isolated tests). See the
48
+ // homedir()-cache trap; mirrors settings/manager.ts userHome().
49
+ const home = process.env.HOME ?? homedir();
50
+ return join(home, ".code-shell");
32
51
  }
33
52
  export class MemoryManager {
34
53
  baseDir;
@@ -72,10 +91,29 @@ export class MemoryManager {
72
91
  save(entry) {
73
92
  const fileName = this.slugify(entry.name) + ".md";
74
93
  const filePath = join(this.memoryDir, fileName);
94
+ const nowIso = new Date().toISOString();
95
+ // Overwrite = UPDATE: preserve the original `created` and accumulated
96
+ // `usageCount` of the file being replaced (only `created` is load-bearing
97
+ // for TTL; carrying usageCount avoids resetting a popular memory's score).
98
+ // explicit entry fields win over the existing file (lets callers force a value).
99
+ const existing = existsSync(filePath) ? this.loadFile(fileName) : null;
100
+ const created = entry.created ?? existing?.created ?? nowIso;
101
+ const usageCount = entry.usageCount ?? existing?.usageCount ?? 0;
102
+ const lastUsed = entry.lastUsed ?? existing?.lastUsed ?? created;
103
+ const originProject = entry.originProject ?? existing?.originProject;
104
+ // pinned/origin are written only when meaningful so legacy-shaped files
105
+ // stay byte-identical for unpinned manual saves. Lifecycle fields are
106
+ // always written now (created/lastUsed/usageCount) — they're how TTL works.
75
107
  const content = `---\n` +
76
108
  `name: ${entry.name}\n` +
77
109
  `description: ${entry.description}\n` +
78
110
  `type: ${entry.type}\n` +
111
+ (entry.pinned ? `pinned: true\n` : "") +
112
+ (entry.origin ? `origin: ${entry.origin}\n` : "") +
113
+ (originProject ? `originProject: ${originProject}\n` : "") +
114
+ `created: ${created}\n` +
115
+ `lastUsed: ${lastUsed}\n` +
116
+ `usageCount: ${usageCount}\n` +
79
117
  `---\n\n` +
80
118
  `${entry.content}\n`;
81
119
  writeFileSync(filePath, content, "utf-8");
@@ -107,6 +145,17 @@ export class MemoryManager {
107
145
  */
108
146
  loadFile(fileName) {
109
147
  const filePath = join(this.memoryDir, fileName);
148
+ if (!existsSync(filePath))
149
+ return null;
150
+ return this.loadFileFromDir(this.memoryDir, fileName, this.scope);
151
+ }
152
+ /**
153
+ * Parse a memory file's frontmatter + body into a MemoryEntry. Shared by both
154
+ * loadFile (own scope) and loadScope (cross-scope) so the parsing of every
155
+ * field — including the lifecycle fields — lives in exactly one place.
156
+ * Returns null on missing file / unparseable frontmatter.
157
+ */
158
+ parseMemoryFile(filePath, fileName, scope) {
110
159
  if (!existsSync(filePath))
111
160
  return null;
112
161
  try {
@@ -119,7 +168,30 @@ export class MemoryManager {
119
168
  const name = frontmatter.match(/name:\s*(.+)/)?.[1]?.trim() ?? fileName;
120
169
  const description = frontmatter.match(/description:\s*(.+)/)?.[1]?.trim() ?? "";
121
170
  const type = (frontmatter.match(/type:\s*(.+)/)?.[1]?.trim() ?? "project");
122
- return { name, description, type, content, fileName, scope: this.scope };
171
+ const pinned = frontmatter.match(/pinned:\s*(.+)/)?.[1]?.trim() === "true";
172
+ // Anchor with ^…/m so `origin:` doesn't accidentally match the
173
+ // `originProject:` line (and vice-versa).
174
+ const originRaw = frontmatter.match(/^origin:\s*(.+)/m)?.[1]?.trim();
175
+ const origin = originRaw === "auto" || originRaw === "manual" ? originRaw : undefined;
176
+ const originProject = frontmatter.match(/^originProject:\s*(.+)/m)?.[1]?.trim() || undefined;
177
+ let updatedAt = 0;
178
+ try {
179
+ updatedAt = statSync(filePath).mtimeMs;
180
+ }
181
+ catch {
182
+ // mtime best-effort; 0 = unknown (never filtered out by maxAge).
183
+ }
184
+ // Lifecycle fields. Legacy files lack them → fall back to mtime so a TTL
185
+ // sweep treats them as "last used = file age" rather than "never used".
186
+ const mtimeIso = updatedAt > 0 ? new Date(updatedAt).toISOString() : undefined;
187
+ const created = frontmatter.match(/created:\s*(.+)/)?.[1]?.trim() || mtimeIso;
188
+ const lastUsed = frontmatter.match(/lastUsed:\s*(.+)/)?.[1]?.trim() || created;
189
+ const usageRaw = frontmatter.match(/usageCount:\s*(\d+)/)?.[1];
190
+ const usageCount = usageRaw ? parseInt(usageRaw, 10) : 0;
191
+ return {
192
+ name, description, type, content, fileName, scope,
193
+ updatedAt, pinned, origin, usageCount, lastUsed, created, originProject,
194
+ };
123
195
  }
124
196
  catch {
125
197
  return null;
@@ -149,6 +221,129 @@ export class MemoryManager {
149
221
  return false;
150
222
  }
151
223
  }
224
+ /**
225
+ * Record a recall: a MemoryRead hit this entry. Bumps usageCount and sets
226
+ * lastUsed=now, rewriting only the frontmatter (content untouched). This is
227
+ * the signal that drives recall-based TTL — a memory that keeps getting read
228
+ * never ages out. Idempotent-safe: missing entry → false, no throw.
229
+ */
230
+ recordRecall(nameOrFile, now = new Date()) {
231
+ const entry = this.loadAll().find((e) => e.name === nameOrFile || e.fileName === nameOrFile);
232
+ if (!entry)
233
+ return false;
234
+ try {
235
+ this.save({
236
+ name: entry.name,
237
+ description: entry.description,
238
+ type: entry.type,
239
+ content: entry.content,
240
+ pinned: entry.pinned,
241
+ origin: entry.origin,
242
+ created: entry.created,
243
+ usageCount: (entry.usageCount ?? 0) + 1,
244
+ lastUsed: now.toISOString(),
245
+ });
246
+ return true;
247
+ }
248
+ catch {
249
+ return false;
250
+ }
251
+ }
252
+ /**
253
+ * Recall-based TTL sweep. Soft-deletes `project`-type memories not read for
254
+ * more than `ttlDays` (by lastUsed). Stable types (user/feedback/reference)
255
+ * and pinned entries are NEVER pruned — only ephemeral project events age out.
256
+ * Returns the names pruned. ttlDays<=0 disables the sweep.
257
+ */
258
+ pruneByRecall(ttlDays, now = new Date()) {
259
+ if (!ttlDays || ttlDays <= 0)
260
+ return [];
261
+ const cutoff = now.getTime() - ttlDays * 24 * 60 * 60 * 1000;
262
+ const pruned = [];
263
+ for (const e of this.loadAll()) {
264
+ if (e.type !== "project")
265
+ continue; // stable types are exempt
266
+ if (e.pinned)
267
+ continue; // pinned exempt
268
+ const lastUsedMs = e.lastUsed ? new Date(e.lastUsed).getTime() : NaN;
269
+ // Unknown lastUsed (unparseable) → keep, never prune on missing data.
270
+ if (!Number.isFinite(lastUsedMs))
271
+ continue;
272
+ if (lastUsedMs < cutoff) {
273
+ if (this.delete(e.name))
274
+ pruned.push(e.name);
275
+ }
276
+ }
277
+ return pruned;
278
+ }
279
+ /**
280
+ * Approve a pending memory: move it from the pending scope into the user scope
281
+ * of the SAME memory root (pending only ever exists at the global root — the
282
+ * approval门 only gates global writes). Must be called on a pending-scope
283
+ * manager. Returns the new user filename, or null if not found / wrong scope.
284
+ *
285
+ * 审批门 (用户拍板): the only path that moves an auto-extracted memory into the
286
+ * curated, injected global user store.
287
+ */
288
+ approvePending(nameOrFile) {
289
+ return this.movePending(nameOrFile, "global");
290
+ }
291
+ /**
292
+ * Demote a pending memory: 不批准升全局,但它仍是有用记忆 → 落回它来源项目的
293
+ * user store (originProject)。无 originProject 时落全局 user(兜底,不丢)。
294
+ */
295
+ demotePending(nameOrFile) {
296
+ return this.movePending(nameOrFile, "project");
297
+ }
298
+ /** Shared move for approve(→global user)/demote(→origin-project user). */
299
+ movePending(nameOrFile, dest) {
300
+ if (this.scope !== "pending")
301
+ return null;
302
+ const entry = this.loadAll().find((e) => e.name === nameOrFile || e.fileName === nameOrFile);
303
+ if (!entry)
304
+ return null;
305
+ const targetMgr = dest === "project" && entry.originProject
306
+ ? new MemoryManager({ baseDir: this.baseDir, projectDir: entry.originProject, scope: "user" })
307
+ : new MemoryManager({ baseDir: this.baseDir, scope: "user" }); // global, or fallback
308
+ const fileName = targetMgr.save({
309
+ name: entry.name,
310
+ description: entry.description,
311
+ type: entry.type,
312
+ content: entry.content,
313
+ origin: entry.origin ?? "auto",
314
+ created: entry.created,
315
+ lastUsed: entry.lastUsed,
316
+ usageCount: entry.usageCount,
317
+ // originProject is no longer needed once it has landed in a real store.
318
+ });
319
+ this.delete(entry.name); // soft-delete the pending copy
320
+ return fileName;
321
+ }
322
+ /**
323
+ * Promote a project-level user memory to the GLOBAL user store (用户手动点
324
+ * "提升到全局")。Must be called on a project-scoped user manager. Copies the
325
+ * entry into global user/ and soft-deletes the project copy. Returns the new
326
+ * global filename, or null if not found.
327
+ */
328
+ promoteToGlobal(nameOrFile) {
329
+ const entry = this.loadAll().find((e) => e.name === nameOrFile || e.fileName === nameOrFile);
330
+ if (!entry)
331
+ return null;
332
+ const globalMgr = new MemoryManager({ baseDir: this.baseDir, scope: "user" });
333
+ const fileName = globalMgr.save({
334
+ name: entry.name,
335
+ description: entry.description,
336
+ type: entry.type,
337
+ content: entry.content,
338
+ origin: entry.origin,
339
+ pinned: entry.pinned,
340
+ created: entry.created,
341
+ lastUsed: entry.lastUsed,
342
+ usageCount: entry.usageCount,
343
+ });
344
+ this.delete(entry.name); // remove from the project store
345
+ return fileName;
346
+ }
152
347
  /**
153
348
  * Get the MEMORY.md index content for injection into prompts.
154
349
  */
@@ -164,20 +359,20 @@ export class MemoryManager {
164
359
  * tagged with their origin so the model can tell user-owned from
165
360
  * dream-generated.
166
361
  */
167
- buildMemoryContext() {
168
- const userEntries = this.scope === "user"
169
- ? this.loadAll()
170
- : this.loadScope("user");
171
- const dreamEntries = this.scope === "dream"
172
- ? this.loadAll()
173
- : this.loadScope("dream");
362
+ buildMemoryContext(opts) {
363
+ const userEntries = filterByAge(this.scope === "user" ? this.loadAll() : this.loadScope("user"), opts?.maxAgeDays, opts?.now);
364
+ const dreamEntries = filterByAge(this.scope === "dream" ? this.loadAll() : this.loadScope("dream"), opts?.maxAgeDays, opts?.now);
174
365
  if (userEntries.length === 0 && dreamEntries.length === 0)
175
366
  return "";
367
+ // Pinned memories lead the list so the user's hand-picked context is what
368
+ // the model reads first (sort is stable — unpinned keep their order).
369
+ const pinnedFirst = (a, b) => Number(b.pinned ?? false) - Number(a.pinned ?? false);
370
+ userEntries.sort(pinnedFirst);
176
371
  const lines = [];
177
372
  if (userEntries.length > 0) {
178
373
  lines.push("## User memories (you own these — needs permission to modify)");
179
374
  for (const e of userEntries) {
180
- lines.push(`- [${e.type}] ${e.name}: ${e.description}`);
375
+ lines.push(`- ${e.pinned ? "[pinned] " : ""}[${e.type}] ${e.name}: ${e.description}`);
181
376
  }
182
377
  }
183
378
  if (dreamEntries.length > 0) {
@@ -193,6 +388,50 @@ export class MemoryManager {
193
388
  lines.join("\n") +
194
389
  `\n\nTo read a specific memory, look under ${this.memoryRoot}/{user,dream}/`);
195
390
  }
391
+ /**
392
+ * Two-layer injection index (用户拍板). Merges GLOBAL (cross-project
393
+ * experience) + PROJECT (this repo's facts) memories into a compact index —
394
+ * one line per entry, name + description only, NO body. The model reads a
395
+ * specific entry's full content on demand via MemoryRead (which records a
396
+ * recall → drives TTL + UI visibility).
397
+ *
398
+ * This is what fixes "global memory never shows up": global memories are now
399
+ * injected alongside project ones, every session, regardless of cwd.
400
+ *
401
+ * Static because it must construct two managers (global = no projectDir,
402
+ * project = with projectDir). `projectDir` undefined → only global is shown.
403
+ */
404
+ static buildInjectionIndex(opts) {
405
+ const global = new MemoryManager({ baseDir: opts.baseDir });
406
+ const project = opts.projectDir
407
+ ? new MemoryManager({ baseDir: opts.baseDir, projectDir: opts.projectDir })
408
+ : null;
409
+ const pinnedFirst = (a, b) => Number(b.pinned ?? false) - Number(a.pinned ?? false);
410
+ const collect = (mm) => filterByAge([...mm.loadScope("user"), ...mm.loadScope("dream")], opts.maxAgeDays, opts.now).sort(pinnedFirst);
411
+ const globalEntries = collect(global);
412
+ const projectEntries = project ? collect(project) : [];
413
+ if (globalEntries.length === 0 && projectEntries.length === 0)
414
+ return "";
415
+ const fmt = (e) => `- ${e.pinned ? "[pinned] " : ""}[${e.type}] ${e.name}: ${e.description}`;
416
+ const lines = [];
417
+ if (globalEntries.length > 0) {
418
+ lines.push("## Global memories (apply across all projects)");
419
+ for (const e of globalEntries)
420
+ lines.push(fmt(e));
421
+ }
422
+ if (projectEntries.length > 0) {
423
+ if (lines.length > 0)
424
+ lines.push("");
425
+ lines.push("## Project memories (this repo)");
426
+ for (const e of projectEntries)
427
+ lines.push(fmt(e));
428
+ }
429
+ return (`# Persistent Memory (index)\n\n` +
430
+ `These are summaries of memories from previous sessions. Only the summary is shown here.\n` +
431
+ `When a memory looks relevant to the current task, read its full content with the ` +
432
+ `MemoryRead tool (scope = user or dream; location = global or project) before relying on it.\n\n` +
433
+ lines.join("\n"));
434
+ }
196
435
  /**
197
436
  * Load every entry belonging to the given scope, without changing the
198
437
  * manager's own scope. Used by buildMemoryContext to merge user + dream
@@ -212,24 +451,7 @@ export class MemoryManager {
212
451
  return entries;
213
452
  }
214
453
  loadFileFromDir(dir, fileName, scope) {
215
- const filePath = join(dir, fileName);
216
- if (!existsSync(filePath))
217
- return null;
218
- try {
219
- const raw = readFileSync(filePath, "utf-8");
220
- const frontmatterMatch = raw.match(/^---\n([\s\S]*?)\n---\n\n?([\s\S]*)$/);
221
- if (!frontmatterMatch)
222
- return null;
223
- const frontmatter = frontmatterMatch[1];
224
- const content = frontmatterMatch[2].trim();
225
- const name = frontmatter.match(/name:\s*(.+)/)?.[1]?.trim() ?? fileName;
226
- const description = frontmatter.match(/description:\s*(.+)/)?.[1]?.trim() ?? "";
227
- const type = (frontmatter.match(/type:\s*(.+)/)?.[1]?.trim() ?? "project");
228
- return { name, description, type, content, fileName, scope };
229
- }
230
- catch {
231
- return null;
232
- }
454
+ return this.parseMemoryFile(join(dir, fileName), fileName, scope);
233
455
  }
234
456
  /**
235
457
  * Move pre-scope flat-layout entries (entries that lived directly under
@@ -46,6 +46,46 @@ export declare class SessionManager {
46
46
  * without catching SessionError.
47
47
  */
48
48
  exists(sessionId: string): boolean;
49
+ /**
50
+ * Cheap "what cwd is this session bound to?" probe — reads only state.json,
51
+ * NOT the transcript (unlike resume()). engine.run uses this to recover a
52
+ * resumed session's project directory when the caller omits options.cwd
53
+ * (e.g. a desktop host whose sidebar repo selection has drifted to null),
54
+ * so a project-bound session keeps loading its own agents/settings/memory
55
+ * instead of falling back to process.cwd(). Returns undefined for an
56
+ * unknown, malformed, or traversal-shaped id rather than throwing — the
57
+ * caller treats "no recoverable cwd" the same as "not given".
58
+ */
59
+ readCwd(sessionId: string): string | undefined;
60
+ /**
61
+ * Cheap "does this session have a persisted goal?" probe — reads only
62
+ * state.json, NOT the transcript (like readCwd). A persistent goal lives ONLY
63
+ * in state.activeGoal; it is never appended to the transcript as an event, so
64
+ * a session rebuilt from disk (e.g. localStorage wiped) can't recover the
65
+ * goal from its messages. The desktop host calls this on session load to
66
+ * re-surface the active-goal block + its Cancel button, which would otherwise
67
+ * be invisible (and thus uncancellable) after a reload of an aborted goal
68
+ * run. Returns undefined for an unknown / malformed / traversal-shaped id, or
69
+ * a session with no active goal — never throws.
70
+ */
71
+ readActiveGoal(sessionId: string): import("../engine/goal.js").GoalConfig | undefined;
72
+ /**
73
+ * Disk-only "wipe this session's persistent goal" — the counterpart to
74
+ * readActiveGoal. Reads state.json, removes state.activeGoal, and rewrites it
75
+ * atomically (via saveState). Returns true only if a goal was actually
76
+ * present (idempotent — clearing a session with no goal is a no-op returning
77
+ * false). Never throws on an unknown / malformed / traversal-shaped id
78
+ * (returns false), matching readActiveGoal's tolerance.
79
+ *
80
+ * Why it lives here and not only on Engine: a persistent goal can strand a
81
+ * session whose worker is NOT live (aborted/reloaded) — Engine.clearGoal
82
+ * needs a resumed in-RAM session and a matching in-flight hook, neither of
83
+ * which exists once the worker has exited. Hosts (the desktop bridge) call
84
+ * THIS to clear such a goal off disk without spinning up a full Engine.
85
+ * Engine.clearGoal also delegates its disk write here so the wipe logic lives
86
+ * in exactly one place.
87
+ */
88
+ clearActiveGoal(sessionId: string): boolean;
49
89
  resume(sessionId: string): SessionBundle;
50
90
  saveState(state: SessionState): void;
51
91
  /**
@@ -93,7 +93,12 @@ export class SessionManager {
93
93
  parentSessionId: parentSessionId ?? null,
94
94
  ...(origin ? { origin } : {}),
95
95
  };
96
- writeFileSync(join(sessionDir, "state.json"), JSON.stringify(state, null, 2), "utf-8");
96
+ // Atomic write (tmp+rename) like saveState, so a crash during this one-time
97
+ // create can't leave a torn state.json that resume() then fails to parse.
98
+ const stateTarget = join(sessionDir, "state.json");
99
+ const stateTmp = `${stateTarget}.${process.pid}.${Date.now()}.create.tmp`;
100
+ writeFileSync(stateTmp, JSON.stringify(state, null, 2), "utf-8");
101
+ renameSync(stateTmp, stateTarget);
97
102
  const transcript = new Transcript(join(sessionDir, "transcript.jsonl"));
98
103
  transcript.append("session_meta", {
99
104
  sessionId,
@@ -121,6 +126,102 @@ export class SessionManager {
121
126
  }
122
127
  return existsSync(join(this.sessionsDir, sessionId));
123
128
  }
129
+ /**
130
+ * Cheap "what cwd is this session bound to?" probe — reads only state.json,
131
+ * NOT the transcript (unlike resume()). engine.run uses this to recover a
132
+ * resumed session's project directory when the caller omits options.cwd
133
+ * (e.g. a desktop host whose sidebar repo selection has drifted to null),
134
+ * so a project-bound session keeps loading its own agents/settings/memory
135
+ * instead of falling back to process.cwd(). Returns undefined for an
136
+ * unknown, malformed, or traversal-shaped id rather than throwing — the
137
+ * caller treats "no recoverable cwd" the same as "not given".
138
+ */
139
+ readCwd(sessionId) {
140
+ try {
141
+ assertSafeSessionId(sessionId);
142
+ }
143
+ catch {
144
+ return undefined;
145
+ }
146
+ const stateFile = join(this.sessionsDir, sessionId, "state.json");
147
+ if (!existsSync(stateFile))
148
+ return undefined;
149
+ try {
150
+ const state = JSON.parse(readFileSync(stateFile, "utf-8"));
151
+ return typeof state.cwd === "string" && state.cwd.length > 0 ? state.cwd : undefined;
152
+ }
153
+ catch {
154
+ return undefined;
155
+ }
156
+ }
157
+ /**
158
+ * Cheap "does this session have a persisted goal?" probe — reads only
159
+ * state.json, NOT the transcript (like readCwd). A persistent goal lives ONLY
160
+ * in state.activeGoal; it is never appended to the transcript as an event, so
161
+ * a session rebuilt from disk (e.g. localStorage wiped) can't recover the
162
+ * goal from its messages. The desktop host calls this on session load to
163
+ * re-surface the active-goal block + its Cancel button, which would otherwise
164
+ * be invisible (and thus uncancellable) after a reload of an aborted goal
165
+ * run. Returns undefined for an unknown / malformed / traversal-shaped id, or
166
+ * a session with no active goal — never throws.
167
+ */
168
+ readActiveGoal(sessionId) {
169
+ try {
170
+ assertSafeSessionId(sessionId);
171
+ }
172
+ catch {
173
+ return undefined;
174
+ }
175
+ const stateFile = join(this.sessionsDir, sessionId, "state.json");
176
+ if (!existsSync(stateFile))
177
+ return undefined;
178
+ try {
179
+ const state = JSON.parse(readFileSync(stateFile, "utf-8"));
180
+ return state.activeGoal;
181
+ }
182
+ catch {
183
+ return undefined;
184
+ }
185
+ }
186
+ /**
187
+ * Disk-only "wipe this session's persistent goal" — the counterpart to
188
+ * readActiveGoal. Reads state.json, removes state.activeGoal, and rewrites it
189
+ * atomically (via saveState). Returns true only if a goal was actually
190
+ * present (idempotent — clearing a session with no goal is a no-op returning
191
+ * false). Never throws on an unknown / malformed / traversal-shaped id
192
+ * (returns false), matching readActiveGoal's tolerance.
193
+ *
194
+ * Why it lives here and not only on Engine: a persistent goal can strand a
195
+ * session whose worker is NOT live (aborted/reloaded) — Engine.clearGoal
196
+ * needs a resumed in-RAM session and a matching in-flight hook, neither of
197
+ * which exists once the worker has exited. Hosts (the desktop bridge) call
198
+ * THIS to clear such a goal off disk without spinning up a full Engine.
199
+ * Engine.clearGoal also delegates its disk write here so the wipe logic lives
200
+ * in exactly one place.
201
+ */
202
+ clearActiveGoal(sessionId) {
203
+ try {
204
+ assertSafeSessionId(sessionId);
205
+ }
206
+ catch {
207
+ return false;
208
+ }
209
+ const stateFile = join(this.sessionsDir, sessionId, "state.json");
210
+ if (!existsSync(stateFile))
211
+ return false;
212
+ let state;
213
+ try {
214
+ state = JSON.parse(readFileSync(stateFile, "utf-8"));
215
+ }
216
+ catch {
217
+ return false;
218
+ }
219
+ if (state.activeGoal === undefined)
220
+ return false;
221
+ state.activeGoal = undefined;
222
+ this.saveState(state);
223
+ return true;
224
+ }
124
225
  resume(sessionId) {
125
226
  assertSafeSessionId(sessionId);
126
227
  const sessionDir = join(this.sessionsDir, sessionId);
@@ -131,7 +232,16 @@ export class SessionManager {
131
232
  if (!existsSync(stateFile)) {
132
233
  throw new SessionError(`Session state file not found: ${sessionId}`);
133
234
  }
134
- const state = JSON.parse(readFileSync(stateFile, "utf-8"));
235
+ let state;
236
+ try {
237
+ state = JSON.parse(readFileSync(stateFile, "utf-8"));
238
+ }
239
+ catch (err) {
240
+ // A corrupt state.json (external tampering, disk corruption, or a crash
241
+ // during the one-time create() write) must surface as a clean SessionError
242
+ // — not a raw SyntaxError that escapes callers expecting SessionError.
243
+ throw new SessionError(`Session state is corrupt for ${sessionId}: ${err instanceof Error ? err.message : String(err)}`);
244
+ }
135
245
  const transcriptFile = join(sessionDir, "transcript.jsonl");
136
246
  const transcript = Transcript.loadFromFile(transcriptFile);
137
247
  state.status = "active";
@@ -165,7 +275,7 @@ export class SessionManager {
165
275
  newBundle.state.parentSessionId = sourceSessionId;
166
276
  // Copy events up to the fork point
167
277
  for (const event of events) {
168
- if (event.type === "turn_boundary" && event.data.turnNumber > forkTurn) {
278
+ if (event.type === "turn_boundary" && (event.data.turnNumber ?? -1) > forkTurn) {
169
279
  break;
170
280
  }
171
281
  newBundle.transcript.append(event.type, event.data);
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Minimal, dependency-free unified-diff for human-readable previews (e.g. the
3
+ * /undo confirmation). NOT a patch generator — it's for showing a user "here's
4
+ * what restoring will change" before they confirm. Uses a standard LCS so the
5
+ * output reads like a normal diff (unchanged context, -removed, +added).
6
+ *
7
+ * `from` is the content currently on disk; `to` is the snapshot we'd restore.
8
+ * So the preview answers "if I undo, these lines change."
9
+ */
10
+ /** A single diff line with its marker. */
11
+ export interface DiffLine {
12
+ marker: " " | "-" | "+";
13
+ text: string;
14
+ }
15
+ /** Longest-common-subsequence table → minimal -/+ line diff (Myers-equivalent
16
+ * result via classic LCS; fine for preview-sized files). */
17
+ export declare function diffLines(from: string, to: string): DiffLine[];
18
+ /**
19
+ * Render a compact unified-diff string for a preview. Collapses long runs of
20
+ * unchanged context to at most `context` lines around each change so a small
21
+ * edit in a big file doesn't print the whole file. Returns "" when identical.
22
+ */
23
+ export declare function renderDiffPreview(from: string, to: string, context?: number): string;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Minimal, dependency-free unified-diff for human-readable previews (e.g. the
3
+ * /undo confirmation). NOT a patch generator — it's for showing a user "here's
4
+ * what restoring will change" before they confirm. Uses a standard LCS so the
5
+ * output reads like a normal diff (unchanged context, -removed, +added).
6
+ *
7
+ * `from` is the content currently on disk; `to` is the snapshot we'd restore.
8
+ * So the preview answers "if I undo, these lines change."
9
+ */
10
+ /** Longest-common-subsequence table → minimal -/+ line diff (Myers-equivalent
11
+ * result via classic LCS; fine for preview-sized files). */
12
+ export function diffLines(from, to) {
13
+ // Normalize CRLF → LF before splitting so a Windows (CRLF) snapshot vs. an
14
+ // LF edit doesn't render as every-line-changed in the /undo preview.
15
+ const a = from.replace(/\r\n/g, "\n").split("\n");
16
+ const b = to.replace(/\r\n/g, "\n").split("\n");
17
+ const n = a.length;
18
+ const m = b.length;
19
+ // LCS length table.
20
+ const lcs = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
21
+ for (let i = n - 1; i >= 0; i--) {
22
+ for (let j = m - 1; j >= 0; j--) {
23
+ lcs[i][j] = a[i] === b[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1]);
24
+ }
25
+ }
26
+ const out = [];
27
+ let i = 0;
28
+ let j = 0;
29
+ while (i < n && j < m) {
30
+ if (a[i] === b[j]) {
31
+ out.push({ marker: " ", text: a[i] });
32
+ i++;
33
+ j++;
34
+ }
35
+ else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
36
+ out.push({ marker: "-", text: a[i] });
37
+ i++;
38
+ }
39
+ else {
40
+ out.push({ marker: "+", text: b[j] });
41
+ j++;
42
+ }
43
+ }
44
+ while (i < n)
45
+ out.push({ marker: "-", text: a[i++] });
46
+ while (j < m)
47
+ out.push({ marker: "+", text: b[j++] });
48
+ return out;
49
+ }
50
+ /**
51
+ * Render a compact unified-diff string for a preview. Collapses long runs of
52
+ * unchanged context to at most `context` lines around each change so a small
53
+ * edit in a big file doesn't print the whole file. Returns "" when identical.
54
+ */
55
+ export function renderDiffPreview(from, to, context = 3) {
56
+ const lines = diffLines(from, to);
57
+ if (lines.every((l) => l.marker === " "))
58
+ return "";
59
+ // Mark which lines are within `context` of a change; drop the rest, inserting
60
+ // a "⋯" gap marker where we elide.
61
+ const keep = new Array(lines.length).fill(false);
62
+ for (let k = 0; k < lines.length; k++) {
63
+ if (lines[k].marker !== " ") {
64
+ for (let d = -context; d <= context; d++) {
65
+ const idx = k + d;
66
+ if (idx >= 0 && idx < lines.length)
67
+ keep[idx] = true;
68
+ }
69
+ }
70
+ }
71
+ const rows = [];
72
+ let elided = false;
73
+ for (let k = 0; k < lines.length; k++) {
74
+ if (keep[k]) {
75
+ rows.push(`${lines[k].marker}${lines[k].text}`);
76
+ elided = false;
77
+ }
78
+ else if (!elided) {
79
+ rows.push("⋯");
80
+ elided = true;
81
+ }
82
+ }
83
+ return rows.join("\n");
84
+ }