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

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
@@ -0,0 +1,495 @@
1
+ /**
2
+ * BackgroundShellManager — session-scoped registry + lifecycle for
3
+ * fire-and-forget background shells (design §4).
4
+ *
5
+ * A background shell is a long-lived process (typically `npm run dev`)
6
+ * started via `Bash(run_in_background=true)`. Unlike the foreground Bash
7
+ * path (which awaits the command to completion), this:
8
+ *
9
+ * - spawns the command **detached** so it owns its own process group
10
+ * (so KillShell can reap the whole `sh → npm → node vite` tree, §难点2);
11
+ * - returns a `shell_id` immediately, never blocking the turn;
12
+ * - streams output to an in-memory ring buffer + a wrap-around disk file,
13
+ * stripping ANSI/progress noise only when handed to the agent (§难点4);
14
+ * - detects a served port best-effort from the output (§难点3);
15
+ * - is **completely separate** from `asyncAgentRegistry` so Engine.run's
16
+ * wait-for-background loop never waits on a dev server (§难点5);
17
+ * - on exit, enqueues a one-line completion notification (no output body,
18
+ * §难点6 / D6) keyed by sessionId.
19
+ *
20
+ * Lifetime is process-local and session-scoped: `killSession(sid)` on
21
+ * explicit session deletion, `killAll()` on app/worker exit. Crashing the
22
+ * worker leaks the detached children as orphans — pidfiles let a restarted
23
+ * worker discover and reap them (§难点1, `reapOrphansFromPidfiles`).
24
+ */
25
+ import { spawn } from "node:child_process";
26
+ import { homedir } from "node:os";
27
+ import { join } from "node:path";
28
+ import { mkdirSync, writeFileSync, rmSync, existsSync, readdirSync, readFileSync, } from "node:fs";
29
+ import { StringDecoder } from "node:string_decoder";
30
+ import { utf8SafeCutLength } from "./utf8-cut.js";
31
+ import { resolveSpawnTarget, buildSandboxEnv, mergeShellEnv, killProcessGroup, groupAlive, defaultShellBinary } from "./spawn-common.js";
32
+ import { RingFile } from "./ring-file.js";
33
+ import { cleanOutput } from "./output-clean.js";
34
+ import { notificationQueue } from "../tool-system/builtin/agent-notifications.js";
35
+ import { logger } from "../logging/logger.js";
36
+ /** Per-session soft cap on background shells (design §7 — fork-bomb guard). */
37
+ export const MAX_SHELLS_PER_SESSION = 16;
38
+ /** Disk ring-file cap: keep the most recent 8MB of raw output (design §6). */
39
+ const DISK_CAP_BYTES = 8 * 1024 * 1024;
40
+ /** BashOutput single-return cap (strip后尾部 ~16KB) so one read can't blow context. */
41
+ const READ_RETURN_CAP = 16 * 1024;
42
+ const KILL_GRACE_MS = 3000;
43
+ let shellCounter = 0;
44
+ function nextShellId() {
45
+ shellCounter += 1;
46
+ // Short, collision-resistant enough within a process: counter + base36 of
47
+ // a monotonic-ish value. Avoids Date.now()/Math.random() (banned in some
48
+ // contexts and unnecessary here).
49
+ return `bg_${shellCounter.toString(36)}${(shellCounter * 2654435761 % 0xffffff).toString(36)}`;
50
+ }
51
+ function bgShellsRoot() {
52
+ const base = process.env.CODE_SHELL_HOME ?? process.env.HOME ?? homedir();
53
+ return join(base, ".code-shell", "bg-shells");
54
+ }
55
+ const PORT_REGEXES = [
56
+ /localhost:(\d{2,5})/i,
57
+ /127\.0\.0\.1:(\d{2,5})/,
58
+ /0\.0\.0\.0:(\d{2,5})/,
59
+ /:(\d{4,5})\b/,
60
+ ];
61
+ function detectPort(text) {
62
+ for (const re of PORT_REGEXES) {
63
+ const m = re.exec(text);
64
+ if (m) {
65
+ const p = Number(m[1]);
66
+ if (p >= 1 && p <= 65535)
67
+ return p;
68
+ }
69
+ }
70
+ return undefined;
71
+ }
72
+ export class BackgroundShellManager {
73
+ shells = new Map();
74
+ spawnBackground(opts) {
75
+ // Defense-in-depth: sessionId becomes a directory under bgShellsRoot()
76
+ // (`join(root, sessionId)`, below). It's engine-generated/validated today,
77
+ // but a `..`/separator/empty id would write the shell log + record OUTSIDE
78
+ // the bg-shells root — refuse rather than trust the caller. Mirrors
79
+ // SessionManager.assertSafeSessionId without importing across the layer.
80
+ const sid = opts.sessionId;
81
+ if (typeof sid !== "string" || sid.length === 0 || sid.includes("..") || sid.includes("/") || sid.includes("\\")) {
82
+ return { ok: false, error: `invalid sessionId for background shell` };
83
+ }
84
+ // Per-session soft cap (fork-bomb guard, §7).
85
+ const live = [...this.shells.values()].filter((s) => s.sessionId === opts.sessionId && (s.status === "running" || s.status === "starting"));
86
+ if (live.length >= MAX_SHELLS_PER_SESSION) {
87
+ return {
88
+ ok: false,
89
+ error: `Too many background shells for this session (max ${MAX_SHELLS_PER_SESSION}). KillShell some first.`,
90
+ };
91
+ }
92
+ const shell = opts.shell ?? defaultShellBinary();
93
+ const { file, args } = resolveSpawnTarget(opts.command, {
94
+ cwd: opts.cwd,
95
+ shell,
96
+ sandbox: opts.sandbox,
97
+ });
98
+ const baseEnv = opts.sandbox && opts.sandbox.name !== "off" ? buildSandboxEnv() : { ...process.env };
99
+ const env = mergeShellEnv(baseEnv, opts.shellEnv);
100
+ let child;
101
+ try {
102
+ child = spawn(file, args, {
103
+ cwd: opts.cwd,
104
+ env,
105
+ // POSIX: detached → own process group so a negative-pid signal reaps
106
+ // the whole tree (npm → node → vite). Windows has no process groups;
107
+ // detached there spawns a separate console window instead, so we keep
108
+ // it attached and rely on `taskkill /T` (killProcessGroup's win32
109
+ // branch) to reap the tree. windowsHide stops a console flashing up.
110
+ detached: process.platform !== "win32",
111
+ windowsHide: true,
112
+ stdio: ["ignore", "pipe", "pipe"],
113
+ });
114
+ }
115
+ catch (err) {
116
+ return { ok: false, error: `Failed to spawn background shell: ${err.message}` };
117
+ }
118
+ if (child.pid === undefined) {
119
+ return { ok: false, error: "Failed to spawn background shell: no pid" };
120
+ }
121
+ const shellId = nextShellId();
122
+ const pgid = child.pid; // detached ⇒ pgid == pid
123
+ const dir = join(bgShellsRoot(), opts.sessionId);
124
+ const pidfilePath = join(dir, `${shellId}.json`);
125
+ const logPath = join(dir, `${shellId}.log`);
126
+ let ring;
127
+ try {
128
+ mkdirSync(dir, { recursive: true });
129
+ ring = new RingFile(logPath, DISK_CAP_BYTES);
130
+ }
131
+ catch {
132
+ // Disk unavailable — degrade to a memory-only ring at a temp-less path.
133
+ ring = new RingFile(join(dir, `${shellId}.log`), DISK_CAP_BYTES);
134
+ }
135
+ const startedAt = Date.now();
136
+ const rec = {
137
+ shellId,
138
+ pgid,
139
+ command: opts.command,
140
+ startedAt,
141
+ sessionId: opts.sessionId,
142
+ };
143
+ try {
144
+ writeFileSync(pidfilePath, JSON.stringify(rec), "utf8");
145
+ }
146
+ catch {
147
+ /* pidfile is best-effort; orphan reaping just won't see this one */
148
+ }
149
+ const sh = {
150
+ shellId,
151
+ sessionId: opts.sessionId,
152
+ command: opts.command,
153
+ cwd: opts.cwd,
154
+ pgid,
155
+ status: "running",
156
+ startedAt,
157
+ exitCode: null,
158
+ signal: null,
159
+ agentReadOffset: 0,
160
+ didWrap: false,
161
+ diskAvailable: ring.diskAvailable(),
162
+ child,
163
+ ring,
164
+ pidfilePath,
165
+ stdoutDec: new StringDecoder("utf-8"),
166
+ stderrDec: new StringDecoder("utf-8"),
167
+ portScanBuf: "",
168
+ };
169
+ this.shells.set(shellId, sh);
170
+ const onData = (chunk) => {
171
+ sh.ring.write(chunk);
172
+ sh.didWrap = sh.ring.didWrap();
173
+ if (sh.detectedPort === undefined) {
174
+ // Scan a bounded recent window of decoded text for a port.
175
+ sh.portScanBuf = (sh.portScanBuf + chunk.toString("utf8")).slice(-4096);
176
+ const p = detectPort(sh.portScanBuf);
177
+ if (p !== undefined) {
178
+ sh.detectedPort = p;
179
+ this.updatePidfilePort(sh, p);
180
+ }
181
+ }
182
+ };
183
+ child.stdout?.on("data", onData);
184
+ child.stderr?.on("data", onData);
185
+ child.on("error", (err) => {
186
+ logger.warn("bg_shell.child_error", { shellId, error: err.message });
187
+ });
188
+ child.on("exit", (code, sig) => {
189
+ // Only natural exits land here as "exited"; kill() sets "killed" first.
190
+ if (sh.status === "killed") {
191
+ this.finalize(sh, code, sig);
192
+ return;
193
+ }
194
+ sh.status = "exited";
195
+ this.finalize(sh, code, sig);
196
+ this.enqueueExitNotification(sh);
197
+ });
198
+ return { ok: true, shellId };
199
+ }
200
+ finalize(sh, code, sig) {
201
+ sh.exitCode = code;
202
+ sh.signal = sig;
203
+ sh.exitedAt = Date.now();
204
+ sh.child = null;
205
+ try {
206
+ sh.ring.close();
207
+ }
208
+ catch {
209
+ /* best-effort */
210
+ }
211
+ try {
212
+ rmSync(sh.pidfilePath, { force: true });
213
+ }
214
+ catch {
215
+ /* best-effort */
216
+ }
217
+ }
218
+ enqueueExitNotification(sh) {
219
+ const ok = sh.exitCode === 0;
220
+ const exitDesc = sh.signal
221
+ ? `signal ${sh.signal}`
222
+ : `exit ${sh.exitCode ?? "?"}`;
223
+ notificationQueue.enqueue({
224
+ agentId: sh.shellId,
225
+ // English description is the agent-facing wakeup text. The UI ignores
226
+ // it for the toast and uses workKind + command to build a localized
227
+ // label (see bgCompletionText in desktop renderer types.ts). No `name`
228
+ // so legacy UIs fall back to their own default rather than showing the
229
+ // English "background shell" brand string.
230
+ description: `Background shell exited (${exitDesc}): ${sh.command}`,
231
+ status: ok ? "completed" : "failed",
232
+ workKind: "shell",
233
+ command: sh.command,
234
+ error: ok ? undefined : `Background shell ${sh.shellId} exited with ${exitDesc}. Use BashOutput("${sh.shellId}") to inspect.`,
235
+ enqueuedAt: Date.now(),
236
+ }, sh.sessionId);
237
+ }
238
+ updatePidfilePort(sh, port) {
239
+ try {
240
+ const rec = {
241
+ shellId: sh.shellId,
242
+ pgid: sh.pgid,
243
+ command: sh.command,
244
+ port,
245
+ startedAt: sh.startedAt,
246
+ sessionId: sh.sessionId,
247
+ };
248
+ writeFileSync(sh.pidfilePath, JSON.stringify(rec), "utf8");
249
+ }
250
+ catch {
251
+ /* best-effort */
252
+ }
253
+ }
254
+ get(shellId) {
255
+ const sh = this.shells.get(shellId);
256
+ return sh ? this.toPublic(sh) : undefined;
257
+ }
258
+ /** Raw retained output (no cleaning) — test/UI helper. */
259
+ readOutputRaw(shellId) {
260
+ return this.shells.get(shellId)?.ring.readAll();
261
+ }
262
+ listForSession(sessionId) {
263
+ return [...this.shells.values()]
264
+ .filter((s) => s.sessionId === sessionId)
265
+ .map((s) => this.toPublic(s));
266
+ }
267
+ /**
268
+ * Read a shell's output. `mode=incremental` (default) returns text since the
269
+ * agent's last read; `mode=all` returns the full retained buffer. Output is
270
+ * ANSI-stripped and progress-folded, capped at the last READ_RETURN_CAP
271
+ * chars. `expectSessionId`, when given, enforces session ownership.
272
+ */
273
+ readOutput(shellId, mode = "incremental", expectSessionId) {
274
+ const sh = this.shells.get(shellId);
275
+ if (!sh)
276
+ return { ok: false, error: `Unknown shell_id: ${shellId}` };
277
+ if (expectSessionId !== undefined && sh.sessionId !== expectSessionId) {
278
+ return { ok: false, error: `Unknown shell_id: ${shellId}` };
279
+ }
280
+ let rawSlice;
281
+ let capped = false;
282
+ if (mode === "all") {
283
+ // Snapshot of the full retained buffer. This is explicitly a "show me the
284
+ // tail" view — when it exceeds the cap we keep the LAST 16KB (most recent
285
+ // output) and don't touch the incremental cursor.
286
+ rawSlice = sh.ring.readAll();
287
+ let text = cleanOutput(rawSlice);
288
+ if (text.length > READ_RETURN_CAP) {
289
+ text = text.slice(-READ_RETURN_CAP);
290
+ capped = true;
291
+ }
292
+ return { ok: true, text, header: this.buildHeader(sh, capped, "all") };
293
+ }
294
+ // Incremental: deliver bytes since the agent's last read WITHOUT dropping
295
+ // any. A single burst larger than the cap is paginated across successive
296
+ // reads — we consume only the leading READ_RETURN_CAP *raw* bytes this call
297
+ // and advance the absolute cursor by exactly that many, so the remainder is
298
+ // returned next read. (Truncating to the trailing 16KB while advancing the
299
+ // cursor to the end silently dropped the earliest bytes forever — #5.)
300
+ //
301
+ // Absolute stream cursor — survives ring wraparound (a window-relative
302
+ // offset would silently skip new bytes once the window slides).
303
+ const sliceBuf = sh.ring.sliceFromAbsolute(sh.agentReadOffset);
304
+ let consumedBytes = sliceBuf.length;
305
+ let rawBuf = sliceBuf;
306
+ if (rawBuf.length > READ_RETURN_CAP) {
307
+ // Cap on RAW bytes (not cleaned length): cleanOutput only ever shrinks,
308
+ // so the cleaned result of the first 16KB raw bytes is itself ≤ 16KB and
309
+ // won't need a second truncation. Advancing the cursor by exactly
310
+ // consumedBytes keeps the next read seamless.
311
+ // Back the cut off to the last complete UTF-8 char boundary so a
312
+ // multibyte char straddling the 16KB line isn't split into a `�` — and
313
+ // so consumedBytes lands on a boundary and the next read resumes cleanly.
314
+ // Guard against a pathological 0 (buffer starting mid-char) to guarantee
315
+ // forward progress.
316
+ const safeCut = utf8SafeCutLength(rawBuf, READ_RETURN_CAP) || READ_RETURN_CAP;
317
+ rawBuf = rawBuf.subarray(0, safeCut);
318
+ consumedBytes = safeCut;
319
+ capped = true;
320
+ }
321
+ rawSlice = rawBuf.toString("utf8");
322
+ sh.agentReadOffset += consumedBytes;
323
+ const text = cleanOutput(rawSlice);
324
+ return { ok: true, text, header: this.buildHeader(sh, capped, "incremental") };
325
+ }
326
+ /**
327
+ * Build the status header line for a readOutput result. `mode` shapes the cap
328
+ * note: `all` keeps the trailing 16KB (older output omitted from THIS view),
329
+ * while incremental paginates (more output available on the next read).
330
+ */
331
+ buildHeader(sh, capped, mode) {
332
+ const portPart = sh.detectedPort !== undefined ? ` port=${sh.detectedPort}` : "";
333
+ const exitPart = sh.status === "exited" || sh.status === "killed"
334
+ ? sh.signal
335
+ ? ` signal=${sh.signal}`
336
+ : ` exit=${sh.exitCode ?? "?"}`
337
+ : "";
338
+ const wrapNote = sh.didWrap ? " (older output discarded)" : "";
339
+ const capNote = capped
340
+ ? mode === "all"
341
+ ? " (showing last 16KB)"
342
+ : " (16KB this read; call again for more)"
343
+ : "";
344
+ const diskNote = sh.diskAvailable ? "" : " (disk buffer unavailable)";
345
+ return `[${sh.shellId} status=${sh.status}${portPart}${exitPart}]${wrapNote}${capNote}${diskNote}`;
346
+ }
347
+ /** Terminate a shell (its whole process group). Idempotent. */
348
+ async kill(shellId, expectSessionId) {
349
+ const sh = this.shells.get(shellId);
350
+ if (!sh)
351
+ return { ok: false, error: `Unknown shell_id: ${shellId}` };
352
+ if (expectSessionId !== undefined && sh.sessionId !== expectSessionId) {
353
+ return { ok: false, error: `Unknown shell_id: ${shellId}` };
354
+ }
355
+ if (sh.status === "exited" || sh.status === "killed") {
356
+ return { ok: true, alreadyExited: true, status: sh.status };
357
+ }
358
+ sh.status = "killed";
359
+ await killProcessGroup(sh.pgid, { graceMs: KILL_GRACE_MS });
360
+ // The 'exit' handler runs finalize(); if the child already detached/died
361
+ // without firing it, finalize defensively.
362
+ if (sh.exitedAt === undefined) {
363
+ this.finalize(sh, sh.exitCode, sh.signal);
364
+ }
365
+ return { ok: true, status: "killed" };
366
+ }
367
+ /** Kill every background shell belonging to `sessionId`. */
368
+ async killSession(sessionId) {
369
+ const ids = [...this.shells.values()]
370
+ .filter((s) => s.sessionId === sessionId && (s.status === "running" || s.status === "starting"))
371
+ .map((s) => s.shellId);
372
+ await Promise.all(ids.map((id) => this.kill(id)));
373
+ }
374
+ /** Kill every background shell (app/worker exit). */
375
+ async killAll() {
376
+ const ids = [...this.shells.values()]
377
+ .filter((s) => s.status === "running" || s.status === "starting")
378
+ .map((s) => s.shellId);
379
+ await Promise.all(ids.map((id) => this.kill(id)));
380
+ }
381
+ /**
382
+ * Scan pidfiles left by a previous (crashed) worker. For each: if the pgid
383
+ * is still alive it's an orphan (the dev server outlived its worker) — list
384
+ * it as `orphaned` so KillShell can clean it up; if dead, delete the stale
385
+ * pidfile. Returns the orphaned records found.
386
+ */
387
+ reapOrphansFromPidfiles() {
388
+ const root = bgShellsRoot();
389
+ if (!existsSync(root))
390
+ return [];
391
+ const orphans = [];
392
+ let sessionDirs;
393
+ try {
394
+ sessionDirs = readdirSync(root);
395
+ }
396
+ catch {
397
+ return [];
398
+ }
399
+ for (const sid of sessionDirs) {
400
+ const dir = join(root, sid);
401
+ let files;
402
+ try {
403
+ files = readdirSync(dir);
404
+ }
405
+ catch {
406
+ continue;
407
+ }
408
+ for (const f of files) {
409
+ if (!f.endsWith(".json"))
410
+ continue;
411
+ const path = join(dir, f);
412
+ let rec;
413
+ try {
414
+ rec = JSON.parse(readFileSync(path, "utf8"));
415
+ }
416
+ catch {
417
+ try {
418
+ rmSync(path, { force: true });
419
+ }
420
+ catch { /* ignore */ }
421
+ continue;
422
+ }
423
+ // Skip ones we already track in-process (this worker's own).
424
+ if (this.shells.has(rec.shellId))
425
+ continue;
426
+ if (groupAlive(rec.pgid)) {
427
+ orphans.push(rec);
428
+ this.registerOrphan(rec, path);
429
+ }
430
+ else {
431
+ try {
432
+ rmSync(path, { force: true });
433
+ }
434
+ catch { /* ignore */ }
435
+ }
436
+ }
437
+ }
438
+ return orphans;
439
+ }
440
+ registerOrphan(rec, pidfilePath) {
441
+ // A minimal entry: we have no live child/ring (different process), but
442
+ // ListShells should show it and KillShell should be able to reap it.
443
+ const sh = {
444
+ shellId: rec.shellId,
445
+ sessionId: rec.sessionId,
446
+ command: rec.command,
447
+ cwd: "",
448
+ pgid: rec.pgid,
449
+ status: "orphaned",
450
+ startedAt: rec.startedAt,
451
+ exitCode: null,
452
+ signal: null,
453
+ detectedPort: rec.port,
454
+ agentReadOffset: 0,
455
+ didWrap: false,
456
+ diskAvailable: false,
457
+ child: null,
458
+ // Read the orphan's ALREADY-captured output from its on-disk .log (the
459
+ // live process in the other worker owns/writes it). Opening it read-only
460
+ // surfaces the real tail instead of a fresh EMPTY .orphan ring that made
461
+ // a recovered shell always show "(无输出)". (#7)
462
+ ring: new RingFile(join(bgShellsRoot(), rec.sessionId, `${rec.shellId}.log`), DISK_CAP_BYTES, true),
463
+ pidfilePath,
464
+ stdoutDec: new StringDecoder("utf-8"),
465
+ stderrDec: new StringDecoder("utf-8"),
466
+ portScanBuf: "",
467
+ };
468
+ this.shells.set(rec.shellId, sh);
469
+ }
470
+ /** Test/reset helper — clears the registry without killing (used in tests). */
471
+ _clear() {
472
+ this.shells.clear();
473
+ }
474
+ toPublic(s) {
475
+ return {
476
+ shellId: s.shellId,
477
+ sessionId: s.sessionId,
478
+ command: s.command,
479
+ cwd: s.cwd,
480
+ pgid: s.pgid,
481
+ status: s.status,
482
+ startedAt: s.startedAt,
483
+ exitedAt: s.exitedAt,
484
+ exitCode: s.exitCode,
485
+ signal: s.signal,
486
+ detectedPort: s.detectedPort,
487
+ agentReadOffset: s.agentReadOffset,
488
+ totalBytes: s.ring.totalWritten(),
489
+ didWrap: s.didWrap,
490
+ diskAvailable: s.diskAvailable,
491
+ };
492
+ }
493
+ }
494
+ /** Process-local singleton, mirroring asyncAgentRegistry's lifetime contract. */
495
+ export const backgroundShellManager = new BackgroundShellManager();
@@ -0,0 +1,24 @@
1
+ /**
2
+ * output-clean — sanitize raw process output before it's handed to the LLM.
3
+ *
4
+ * Background dev servers (`npm run dev`, vite, webpack) emit two kinds of
5
+ * noise that are worthless — and token-expensive — in a model's context
6
+ * (design §难点4):
7
+ *
8
+ * - ANSI escape sequences (colors, cursor moves, line erases).
9
+ * - `\r`-driven progress redraws (`▕███░ 45%\r▕████ 100%`) where every
10
+ * frame but the last is stale.
11
+ *
12
+ * The *raw* bytes are still persisted to disk untouched (so a user `tail`ing
13
+ * the file keeps colors); this cleanup only applies to what `BashOutput`
14
+ * returns to the agent.
15
+ */
16
+ export declare function stripAnsi(s: string): string;
17
+ /**
18
+ * Collapse carriage-return progress redraws. Within a single line (text
19
+ * between `\n`s), `\r` means "redraw from column 0" — only the segment after
20
+ * the last `\r` survives. Newlines are preserved as line separators.
21
+ */
22
+ export declare function foldProgressLines(s: string): string;
23
+ /** Strip ANSI then fold progress frames — the full agent-facing cleanup. */
24
+ export declare function cleanOutput(s: string): string;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * output-clean — sanitize raw process output before it's handed to the LLM.
3
+ *
4
+ * Background dev servers (`npm run dev`, vite, webpack) emit two kinds of
5
+ * noise that are worthless — and token-expensive — in a model's context
6
+ * (design §难点4):
7
+ *
8
+ * - ANSI escape sequences (colors, cursor moves, line erases).
9
+ * - `\r`-driven progress redraws (`▕███░ 45%\r▕████ 100%`) where every
10
+ * frame but the last is stale.
11
+ *
12
+ * The *raw* bytes are still persisted to disk untouched (so a user `tail`ing
13
+ * the file keeps colors); this cleanup only applies to what `BashOutput`
14
+ * returns to the agent.
15
+ */
16
+ // Matches CSI sequences (ESC [ ... final-byte) plus a few common non-CSI
17
+ // escapes (ESC ] OSC, single-char escapes). Good enough for dev-server output;
18
+ // we deliberately don't pull in a full terminal parser.
19
+ // eslint-disable-next-line no-control-regex
20
+ const ANSI_REGEX = /[›][[\]()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PR-TZcf-ntqry=><~]/g;
21
+ export function stripAnsi(s) {
22
+ return s.replace(ANSI_REGEX, "");
23
+ }
24
+ /**
25
+ * Collapse carriage-return progress redraws. Within a single line (text
26
+ * between `\n`s), `\r` means "redraw from column 0" — only the segment after
27
+ * the last `\r` survives. Newlines are preserved as line separators.
28
+ */
29
+ export function foldProgressLines(s) {
30
+ return s
31
+ .split("\n")
32
+ .map((line) => {
33
+ const lastCr = line.lastIndexOf("\r");
34
+ return lastCr === -1 ? line : line.slice(lastCr + 1);
35
+ })
36
+ .join("\n");
37
+ }
38
+ /** Strip ANSI then fold progress frames — the full agent-facing cleanup. */
39
+ export function cleanOutput(s) {
40
+ return foldProgressLines(stripAnsi(s));
41
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * RingFile — a bounded, wrap-around output sink for a background shell's
3
+ * stdout/stderr (design §6 "落盘 8MB 环绕覆盖").
4
+ *
5
+ * Semantics: the logical stream is unbounded, but only the **last N bytes**
6
+ * are retained. When the stream exceeds the cap, the oldest bytes are
7
+ * dropped (`didWrap()` flips true). A dev server's startup banner is
8
+ * disposable; the recent tail is what the agent/user wants.
9
+ *
10
+ * Implementation: an in-memory tail Buffer is authoritative (so `readAll`
11
+ * and the manager's offset math never touch disk), mirrored to a file so a
12
+ * user can `tail`/open it with external tools. The disk mirror is
13
+ * best-effort — if the path is unwritable the in-memory buffer still works
14
+ * and the manager degrades to memory-only (design §7).
15
+ *
16
+ * The on-disk file holds the same retained tail as memory (rewritten on
17
+ * wrap, appended otherwise). We accept the cost of an occasional full
18
+ * rewrite on wrap; for line-oriented dev output at the 8MB cap this is rare
19
+ * and cheap relative to the process's own work.
20
+ */
21
+ export declare class RingFile {
22
+ private readonly path;
23
+ private readonly capBytes;
24
+ private buf;
25
+ private wrapped;
26
+ private fd;
27
+ private diskOk;
28
+ private readonlyMode;
29
+ /** Total bytes ever written to the logical stream (monotonic, NOT capped).
30
+ * The retained window is the last `min(total, cap)` of these. Readers track
31
+ * an ABSOLUTE position in this stream so incremental reads survive
32
+ * wraparound (a window-relative offset would silently skip new data once
33
+ * the window starts sliding). */
34
+ private total;
35
+ constructor(path: string, capBytes: number,
36
+ /** Read-only: load the EXISTING file's tail into memory and never truncate
37
+ * or write. Used to surface a recovered orphan shell's already-captured
38
+ * output (the live process owns the .log in another worker). */
39
+ readonlyExisting?: boolean);
40
+ /** True if the disk mirror is usable (false → memory-only degraded mode). */
41
+ diskAvailable(): boolean;
42
+ write(chunk: Buffer): void;
43
+ /** Whether any bytes have been discarded by wraparound. */
44
+ didWrap(): boolean;
45
+ /** The retained tail, decoded as UTF-8. */
46
+ readAll(): string;
47
+ /** Current retained byte length (for the agent read offset math). */
48
+ byteLength(): number;
49
+ /** Bytes from `offset` (within the retained window) to the end. */
50
+ sliceFrom(offset: number): Buffer;
51
+ /** Total bytes ever written to the logical stream (monotonic). Readers use
52
+ * this as their next cursor after a read. */
53
+ totalWritten(): number;
54
+ /**
55
+ * Bytes from an ABSOLUTE stream position to the current end. `absOffset` is
56
+ * a value previously returned by {@link totalWritten}. If it points before
57
+ * the retained window (the bytes were discarded by wraparound), returns the
58
+ * whole window. If it's at/after the end, returns empty.
59
+ */
60
+ sliceFromAbsolute(absOffset: number): Buffer;
61
+ close(): void;
62
+ private appendToDisk;
63
+ private rewriteDisk;
64
+ }