@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
@@ -4,7 +4,7 @@
4
4
  import { readFile, stat } from "node:fs/promises";
5
5
  import { existsSync } from "node:fs";
6
6
  import { fileCache } from "./file-cache.js";
7
- import { enforcePathPolicy } from "../path-policy.js";
7
+ import { toLf } from "./eol.js";
8
8
  export const readToolDef = {
9
9
  name: "Read",
10
10
  description: "Read a file from the local filesystem. Returns the file content with line numbers. " +
@@ -31,13 +31,6 @@ export async function readTool(args, ctx) {
31
31
  const filePath = args.file_path;
32
32
  if (!filePath)
33
33
  return "Error: file_path is required";
34
- // Path policy: in-workspace reads pass through; sensitive paths (~/.ssh,
35
- // .env, etc.) and outside-workspace reads are refused with an explanatory
36
- // error. existsSync check stays AFTER the policy gate — we don't want to
37
- // probe sensitive paths' existence as a side-channel.
38
- const blocked = enforcePathPolicy(filePath, "read", ctx?.cwd);
39
- if (blocked)
40
- return blocked;
41
34
  if (!existsSync(filePath))
42
35
  return `Error: File not found: ${filePath}`;
43
36
  try {
@@ -54,10 +47,17 @@ export async function readTool(args, ctx) {
54
47
  content = await readFile(filePath, "utf-8");
55
48
  fileCache.set(filePath, content, fileInfo.mtimeMs);
56
49
  }
57
- const lines = content.split("\n");
50
+ // Split on LF after normalizing CRLF → LF so a Windows (CRLF) file doesn't
51
+ // render a trailing ^M on every line. Display-only; the file isn't written.
52
+ const lines = toLf(content).split("\n");
58
53
  const totalLines = lines.length;
59
54
  const offset = Math.max(1, args.offset || 1);
60
- const limit = args.limit || 2000;
55
+ // A non-positive limit (a misbehaving caller passing 0 / -5 / NaN) falls back
56
+ // to the default page size — NOT through to `lines.slice(start, start+limit)`
57
+ // with a NEGATIVE end, which JS reads as "all but the last N" and would
58
+ // silently return the wrong window. `|| 2000` only caught 0/NaN, not negatives.
59
+ const rawLimit = args.limit;
60
+ const limit = typeof rawLimit === "number" && rawLimit > 0 ? rawLimit : 2000;
61
61
  const end = Math.min(totalLines, offset - 1 + limit);
62
62
  const selected = lines.slice(offset - 1, end);
63
63
  let numbered = selected.map((line, i) => `${offset + i}\t${line}`).join("\n");
@@ -34,7 +34,10 @@ const REPL_MAX_BUFFER = 1024 * 1024;
34
34
  export async function replTool(args, ctx) {
35
35
  const language = args.language;
36
36
  const code = args.code;
37
- const timeout = args.timeout ?? 30_000;
37
+ // `??` only catches null/undefined — a non-positive timeout (0/-5) would slip
38
+ // through to setTimeout (clamped to 0 → near-instant kill). Floor to default.
39
+ const rawTimeout = args.timeout;
40
+ const timeout = typeof rawTimeout === "number" && rawTimeout > 0 ? rawTimeout : 30_000;
38
41
  // A4: child process runs in the Engine's cwd, not the host process cwd.
39
42
  const cwd = ctx?.cwd ?? process.cwd();
40
43
  if (!code.trim()) {
@@ -42,6 +42,14 @@ export async function skillTool(args, ctx) {
42
42
  // ordering is observable but not load-bearing.
43
43
  const disabledSkills = ctx?.disabledSkills;
44
44
  const disabledPlugins = ctx?.disabledPlugins;
45
+ const skillAllowlist = ctx?.skillAllowlist;
46
+ // Sub-agent skill isolation: when this agent has an allowlist, a skill
47
+ // outside it must be refused — even invoking it by exact name. The scan
48
+ // below also drops it, but an explicit message beats a misleading "not
49
+ // found" for a skill that exists in the project but isn't this agent's.
50
+ if (skillAllowlist !== undefined && !skillAllowlist.includes(skillName)) {
51
+ return `Skill "${skillName}" is not available to this sub-agent. This agent's role restricts it to: ${skillAllowlist.length > 0 ? skillAllowlist.join(", ") : "(no skills)"}.`;
52
+ }
45
53
  if (disabledSkills && disabledSkills.includes(skillName)) {
46
54
  return `Skill "${skillName}" is disabled. Enable it in Customize or remove it from settings.disabledSkills.`;
47
55
  }
@@ -58,6 +66,7 @@ export async function skillTool(args, ctx) {
58
66
  const skills = scanSkills(ctx?.cwd ?? process.cwd(), {
59
67
  disabledSkills,
60
68
  disabledPlugins,
69
+ skillAllowlist,
61
70
  });
62
71
  const found = skills.find((s) => s.name === skillName);
63
72
  if (!found) {
@@ -3,8 +3,9 @@
3
3
  */
4
4
  export const sleepToolDef = {
5
5
  name: "Sleep",
6
- description: "Pause execution for a specified number of seconds. " +
7
- "Useful for polling workflows or waiting for external processes. " +
6
+ description: "Pause execution for a brief, deterministic wait (e.g. letting a just-started service settle for a few seconds). " +
7
+ "Do NOT use Sleep to poll for or wait on background work (background shells, async sub-agents, video generation): " +
8
+ "the system wakes you automatically when that work completes — just end your turn instead of looping Sleep. " +
8
9
  "Maximum duration is 300 seconds (5 minutes).",
9
10
  inputSchema: {
10
11
  type: "object",
@@ -31,20 +31,38 @@ export async function toolSearchTool(args, ctx) {
31
31
  return "Error: query is required";
32
32
  if (!ctx?.toolRegistry)
33
33
  return "Error: ToolSearch is not configured (no registry in ctx)";
34
- const maxResults = Math.min(args.max_results || 5, 20);
34
+ // Floor to default on non-positive: a negative max_results would reach
35
+ // `.slice(0, maxResults)` with a negative end (= all but the last N), silently
36
+ // returning the wrong tool set. `|| 5` only caught 0/NaN, not negatives.
37
+ const rawMax = args.max_results;
38
+ const maxResults = Math.min(typeof rawMax === "number" && rawMax > 0 ? rawMax : 5, 20);
39
+ // The tool registry is worker-SHARED (B1): it holds MCP tools registered by
40
+ // every session, including servers another project enabled. ToolSearch is a
41
+ // side door around the per-turn toolDefs filter — without this gate it would
42
+ // surface (and let the model `select:` into) MCP tools from a server THIS
43
+ // session never enabled, which is exactly the chrome-devtools-in-writeflow
44
+ // leak. Mirror the engine's toolDefs visibility: keep an MCP tool only when
45
+ // its server is in this session's allowedMcpServers. Undefined set = no
46
+ // gating (sub-agents / hosts that don't populate it).
47
+ const visible = (tool) => {
48
+ if (tool.source !== "mcp")
49
+ return true;
50
+ const allowed = ctx.allowedMcpServers;
51
+ return !allowed || allowed.has(tool.serverName ?? "");
52
+ };
35
53
  // "select:Name1,Name2" → exact match
36
54
  if (query.startsWith("select:")) {
37
55
  const names = query.slice(7).split(",").map((n) => n.trim());
38
- return matchExact(ctx.toolRegistry, names);
56
+ return matchExact(ctx.toolRegistry, names, visible);
39
57
  }
40
58
  // Keyword search
41
- return searchByKeyword(ctx.toolRegistry, query, maxResults);
59
+ return searchByKeyword(ctx.toolRegistry, query, maxResults, visible);
42
60
  }
43
- function matchExact(registry, names) {
61
+ function matchExact(registry, names, visible) {
44
62
  const results = [];
45
63
  for (const name of names) {
46
64
  const tool = registry.getTool(name);
47
- if (tool) {
65
+ if (tool && visible(tool)) {
48
66
  results.push(formatTool(tool));
49
67
  }
50
68
  else {
@@ -53,8 +71,8 @@ function matchExact(registry, names) {
53
71
  }
54
72
  return results.join("\n\n---\n\n");
55
73
  }
56
- function searchByKeyword(registry, query, maxResults) {
57
- const allTools = registry.listToolsDetailed();
74
+ function searchByKeyword(registry, query, maxResults, visible) {
75
+ const allTools = registry.listToolsDetailed().filter(visible);
58
76
  const queryLower = query.toLowerCase();
59
77
  const keywords = queryLower.split(/\s+/);
60
78
  // Score each tool
@@ -0,0 +1,154 @@
1
+ /**
2
+ * VideoProvider — adapter interface for text-to-video generation (TODO 7.1).
3
+ *
4
+ * Unlike images (one request → bytes), video generation is asynchronous and
5
+ * long: SUBMIT a job → POLL its status until it finishes → DOWNLOAD the bytes.
6
+ * The interface captures exactly those three steps so the GenerateVideo tool's
7
+ * background polling loop is provider-agnostic, and concrete adapters
8
+ * (Seedance/Kling — filled in once their private API docs are available) only
9
+ * implement submit/poll/download.
10
+ *
11
+ * Credentials are resolved by the tool layer from settings; adapters are pure
12
+ * "creds + request → job lifecycle". `fetch` is injected for testability.
13
+ */
14
+ export interface VideoProviderCreds {
15
+ baseUrl: string;
16
+ apiKey: string;
17
+ }
18
+ export interface VideoSubmitRequest {
19
+ prompt: string;
20
+ model: string;
21
+ creds: VideoProviderCreds;
22
+ /** Single image URL (http/https) — image-to-video. Back-compat. */
23
+ image?: string;
24
+ /** Multiple image URLs — ≥2 triggers reference-to-video. Takes precedence over `image`. */
25
+ images?: string[];
26
+ /** Reference/continuation video URLs (http/https) — passed through to fal's `video_urls`. Reference them in the prompt as @Video1/@Video2. Forces reference-to-video. */
27
+ videos?: string[];
28
+ signal?: AbortSignal;
29
+ }
30
+ export type VideoSubmitResult = {
31
+ ok: true;
32
+ jobId: string;
33
+ } | {
34
+ ok: false;
35
+ error: string;
36
+ };
37
+ export type VideoPollResult = {
38
+ ok: true;
39
+ status: "running";
40
+ } | {
41
+ ok: true;
42
+ status: "succeeded";
43
+ } | {
44
+ ok: true;
45
+ status: "failed";
46
+ error: string;
47
+ } | {
48
+ ok: false;
49
+ error: string;
50
+ };
51
+ export type VideoDownloadResult = {
52
+ ok: true;
53
+ bytes: Uint8Array;
54
+ ext: string;
55
+ url?: string;
56
+ } | {
57
+ ok: false;
58
+ error: string;
59
+ };
60
+ export interface VideoProvider {
61
+ readonly kind: string;
62
+ submit(req: VideoSubmitRequest): Promise<VideoSubmitResult>;
63
+ poll(req: {
64
+ jobId: string;
65
+ creds: VideoProviderCreds;
66
+ signal?: AbortSignal;
67
+ }): Promise<VideoPollResult>;
68
+ download(req: {
69
+ jobId: string;
70
+ creds: VideoProviderCreds;
71
+ signal?: AbortSignal;
72
+ }): Promise<VideoDownloadResult>;
73
+ }
74
+ /**
75
+ * Deterministic in-memory adapter for tests and local dev — no network. Models
76
+ * the three-step lifecycle: submit mints a job; poll returns `running` for the
77
+ * first N calls then `succeeded` (or `failed` after M); download returns the
78
+ * configured bytes. Lets the GenerateVideo background loop be tested end-to-end
79
+ * before any real (Seedance/Kling) adapter exists.
80
+ */
81
+ export declare class FakeVideoProvider implements VideoProvider {
82
+ private readonly opts;
83
+ readonly kind = "fake";
84
+ private polls;
85
+ private counter;
86
+ constructor(opts?: {
87
+ succeedAfterPolls?: number;
88
+ failAfterPolls?: number;
89
+ failMessage?: string;
90
+ bytes?: string;
91
+ });
92
+ submit(_req: VideoSubmitRequest): Promise<VideoSubmitResult>;
93
+ poll(req: {
94
+ jobId: string;
95
+ creds?: VideoProviderCreds;
96
+ signal?: AbortSignal;
97
+ }): Promise<VideoPollResult>;
98
+ download(_req: {
99
+ jobId: string;
100
+ creds?: VideoProviderCreds;
101
+ signal?: AbortSignal;
102
+ }): Promise<VideoDownloadResult>;
103
+ }
104
+ /**
105
+ * fal.ai video adapter — submit/poll/download against the fal queue API
106
+ * (https://queue.fal.run). model id selects the underlying model
107
+ * (e.g. fal-ai/kling-video/v3/pro/text-to-video). When `image` is present we
108
+ * switch a `...text-to-video` model to `...image-to-video` and send image_url,
109
+ * so callers only need to pass an image to get i2v.
110
+ *
111
+ * IMPORTANT: fal's status/result URLs use a model-FAMILY prefix
112
+ * (`fal-ai/kling-video/requests/<id>`), NOT the full submit path
113
+ * (`.../v3/pro/text-to-video/requests/<id>` → HTTP 405). So we never
114
+ * reconstruct those URLs; we encode the `status_url` + `response_url` that
115
+ * submit returns into the jobId (`<status_url>|<response_url>`) and use them
116
+ * verbatim in poll/download. Verified against the live API 2026-06-10.
117
+ */
118
+ export declare class FalVideoProvider implements VideoProvider {
119
+ private readonly fetchImpl;
120
+ readonly kind = "fal";
121
+ constructor(fetchImpl?: typeof fetch);
122
+ /** Effective image list: images[] wins; else single image; else []. */
123
+ private imageList;
124
+ /**
125
+ * Does this model family support reference-to-video (multi-image)? Verified
126
+ * 2026-06-10 against the live API: Seedance has it; Kling does NOT (routing a
127
+ * Kling model to `.../reference-to-video` 404s at result fetch). Keep this a
128
+ * conservative allowlist — only families confirmed to expose the endpoint.
129
+ */
130
+ private supportsReference;
131
+ /** Route model suffix by image count: 0→text, 1→image, ≥2→reference. */
132
+ private resolveModel;
133
+ /** jobId = `${statusUrl}|${responseUrl}` (fal returns both from submit). */
134
+ private split;
135
+ submit(req: VideoSubmitRequest): Promise<VideoSubmitResult>;
136
+ poll(req: {
137
+ jobId: string;
138
+ creds: VideoProviderCreds;
139
+ signal?: AbortSignal;
140
+ }): Promise<VideoPollResult>;
141
+ download(req: {
142
+ jobId: string;
143
+ creds: VideoProviderCreds;
144
+ signal?: AbortSignal;
145
+ }): Promise<VideoDownloadResult>;
146
+ }
147
+ /** Default model per video provider kind (filled in with real adapters). */
148
+ export declare const DEFAULT_VIDEO_MODEL: Record<string, string>;
149
+ /**
150
+ * Registry of video-provider adapters, keyed by `kind`. Only `fake` exists
151
+ * today (test/dev). Real adapters (seedance, kling) are intentionally absent
152
+ * until their private API contracts are confirmed — see TODO 7.1 块3.
153
+ */
154
+ export declare function getVideoProvider(kind: string, fetchImpl?: typeof fetch): VideoProvider | null;
@@ -0,0 +1,235 @@
1
+ /**
2
+ * VideoProvider — adapter interface for text-to-video generation (TODO 7.1).
3
+ *
4
+ * Unlike images (one request → bytes), video generation is asynchronous and
5
+ * long: SUBMIT a job → POLL its status until it finishes → DOWNLOAD the bytes.
6
+ * The interface captures exactly those three steps so the GenerateVideo tool's
7
+ * background polling loop is provider-agnostic, and concrete adapters
8
+ * (Seedance/Kling — filled in once their private API docs are available) only
9
+ * implement submit/poll/download.
10
+ *
11
+ * Credentials are resolved by the tool layer from settings; adapters are pure
12
+ * "creds + request → job lifecycle". `fetch` is injected for testability.
13
+ */
14
+ /**
15
+ * Deterministic in-memory adapter for tests and local dev — no network. Models
16
+ * the three-step lifecycle: submit mints a job; poll returns `running` for the
17
+ * first N calls then `succeeded` (or `failed` after M); download returns the
18
+ * configured bytes. Lets the GenerateVideo background loop be tested end-to-end
19
+ * before any real (Seedance/Kling) adapter exists.
20
+ */
21
+ export class FakeVideoProvider {
22
+ opts;
23
+ kind = "fake";
24
+ polls = new Map();
25
+ counter = 0;
26
+ constructor(opts = {}) {
27
+ this.opts = opts;
28
+ }
29
+ async submit(_req) {
30
+ this.counter += 1;
31
+ const jobId = `fakejob-${this.counter}`;
32
+ this.polls.set(jobId, 0);
33
+ return { ok: true, jobId };
34
+ }
35
+ async poll(req) {
36
+ const n = this.polls.get(req.jobId) ?? 0;
37
+ this.polls.set(req.jobId, n + 1);
38
+ if (this.opts.failAfterPolls !== undefined && n >= this.opts.failAfterPolls) {
39
+ return { ok: true, status: "failed", error: this.opts.failMessage ?? "job failed" };
40
+ }
41
+ if (this.opts.succeedAfterPolls !== undefined && n >= this.opts.succeedAfterPolls) {
42
+ return { ok: true, status: "succeeded" };
43
+ }
44
+ return { ok: true, status: "running" };
45
+ }
46
+ async download(_req) {
47
+ return { ok: true, bytes: Buffer.from(this.opts.bytes ?? "FAKE_VIDEO"), ext: "mp4" };
48
+ }
49
+ }
50
+ /**
51
+ * fal.ai video adapter — submit/poll/download against the fal queue API
52
+ * (https://queue.fal.run). model id selects the underlying model
53
+ * (e.g. fal-ai/kling-video/v3/pro/text-to-video). When `image` is present we
54
+ * switch a `...text-to-video` model to `...image-to-video` and send image_url,
55
+ * so callers only need to pass an image to get i2v.
56
+ *
57
+ * IMPORTANT: fal's status/result URLs use a model-FAMILY prefix
58
+ * (`fal-ai/kling-video/requests/<id>`), NOT the full submit path
59
+ * (`.../v3/pro/text-to-video/requests/<id>` → HTTP 405). So we never
60
+ * reconstruct those URLs; we encode the `status_url` + `response_url` that
61
+ * submit returns into the jobId (`<status_url>|<response_url>`) and use them
62
+ * verbatim in poll/download. Verified against the live API 2026-06-10.
63
+ */
64
+ export class FalVideoProvider {
65
+ fetchImpl;
66
+ kind = "fal";
67
+ constructor(fetchImpl = fetch) {
68
+ this.fetchImpl = fetchImpl;
69
+ }
70
+ /** Effective image list: images[] wins; else single image; else []. */
71
+ imageList(req) {
72
+ if (req.images && req.images.length)
73
+ return req.images;
74
+ if (req.image)
75
+ return [req.image];
76
+ return [];
77
+ }
78
+ /**
79
+ * Does this model family support reference-to-video (multi-image)? Verified
80
+ * 2026-06-10 against the live API: Seedance has it; Kling does NOT (routing a
81
+ * Kling model to `.../reference-to-video` 404s at result fetch). Keep this a
82
+ * conservative allowlist — only families confirmed to expose the endpoint.
83
+ */
84
+ supportsReference(model) {
85
+ return /seedance/i.test(model);
86
+ }
87
+ /** Route model suffix by image count: 0→text, 1→image, ≥2→reference. */
88
+ resolveModel(model, imageCount) {
89
+ const target = imageCount >= 2 ? "reference-to-video" : imageCount === 1 ? "image-to-video" : null;
90
+ if (target && /(text|image|reference)-to-video$/.test(model)) {
91
+ return model.replace(/(text|image|reference)-to-video$/, target);
92
+ }
93
+ return model;
94
+ }
95
+ /** jobId = `${statusUrl}|${responseUrl}` (fal returns both from submit). */
96
+ split(jobId) {
97
+ const idx = jobId.indexOf("|");
98
+ if (idx < 0)
99
+ return { statusUrl: jobId, responseUrl: jobId };
100
+ return { statusUrl: jobId.slice(0, idx), responseUrl: jobId.slice(idx + 1) };
101
+ }
102
+ async submit(req) {
103
+ const base = req.creds.baseUrl.replace(/\/+$/, "");
104
+ const imgs = this.imageList(req);
105
+ const vids = req.videos ?? [];
106
+ // Multi-image OR any reference video needs the reference-to-video endpoint,
107
+ // which only some families (Seedance) expose. Routing e.g. Kling there
108
+ // 404s at result fetch — fail fast with a clear message instead.
109
+ const needsReference = imgs.length >= 2 || vids.length > 0;
110
+ if (needsReference && !this.supportsReference(req.model)) {
111
+ return {
112
+ ok: false,
113
+ error: `model "${req.model}" does not support reference-to-video (multiple images or reference videos). Use a Seedance model, or pass a single image / no video.`,
114
+ };
115
+ }
116
+ // imageCount alone can't express "reference needed"; bump to 2 when a video
117
+ // forces the reference endpoint so resolveModel picks reference-to-video.
118
+ const refImageCount = needsReference ? Math.max(imgs.length, 2) : imgs.length;
119
+ const model = this.resolveModel(req.model, refImageCount);
120
+ const body = { prompt: req.prompt };
121
+ if (imgs.length >= 2)
122
+ body.image_urls = imgs;
123
+ else if (imgs.length === 1)
124
+ body.image_url = imgs[0];
125
+ if (vids.length > 0)
126
+ body.video_urls = vids;
127
+ try {
128
+ const r = await this.fetchImpl(`${base}/${model}`, {
129
+ method: "POST",
130
+ headers: { Authorization: `Key ${req.creds.apiKey}`, "Content-Type": "application/json" },
131
+ body: JSON.stringify(body),
132
+ signal: req.signal,
133
+ });
134
+ if (!r.ok) {
135
+ const t = await r.text().catch(() => "");
136
+ return { ok: false, error: `fal submit failed: HTTP ${r.status} ${t}`.trim() };
137
+ }
138
+ const j = (await r.json());
139
+ if (!j.request_id)
140
+ return { ok: false, error: "fal submit: no request_id in response" };
141
+ // Prefer the URLs fal hands back; fall back to family-prefix reconstruction
142
+ // only if they're absent (defensive — live API always returns them).
143
+ const statusUrl = j.status_url ?? "";
144
+ const responseUrl = j.response_url ?? "";
145
+ if (!statusUrl || !responseUrl) {
146
+ return { ok: false, error: "fal submit: missing status_url/response_url" };
147
+ }
148
+ return { ok: true, jobId: `${statusUrl}|${responseUrl}` };
149
+ }
150
+ catch (err) {
151
+ return { ok: false, error: `fal submit error: ${err.message}` };
152
+ }
153
+ }
154
+ async poll(req) {
155
+ const { statusUrl } = this.split(req.jobId);
156
+ try {
157
+ const r = await this.fetchImpl(statusUrl, {
158
+ method: "GET",
159
+ headers: { Authorization: `Key ${req.creds.apiKey}` },
160
+ signal: req.signal,
161
+ });
162
+ // fal returns 200/202 for valid in-progress/queued states; only treat a
163
+ // hard client/server error (4xx except 202, 5xx) as a transport failure.
164
+ if (!r.ok && r.status !== 202) {
165
+ const t = await r.text().catch(() => "");
166
+ return { ok: false, error: `fal status failed: HTTP ${r.status} ${t}`.trim() };
167
+ }
168
+ const j = (await r.json());
169
+ switch (j.status) {
170
+ case "IN_QUEUE":
171
+ case "IN_PROGRESS":
172
+ return { ok: true, status: "running" };
173
+ case "COMPLETED":
174
+ return { ok: true, status: "succeeded" };
175
+ default:
176
+ return { ok: true, status: "failed", error: `fal status: ${j.status ?? "unknown"}` };
177
+ }
178
+ }
179
+ catch (err) {
180
+ return { ok: false, error: `fal poll error: ${err.message}` };
181
+ }
182
+ }
183
+ async download(req) {
184
+ const { responseUrl } = this.split(req.jobId);
185
+ try {
186
+ // hop 1: result JSON
187
+ const r = await this.fetchImpl(responseUrl, {
188
+ method: "GET",
189
+ headers: { Authorization: `Key ${req.creds.apiKey}` },
190
+ signal: req.signal,
191
+ });
192
+ if (!r.ok) {
193
+ const t = await r.text().catch(() => "");
194
+ return { ok: false, error: `fal result failed: HTTP ${r.status} ${t}`.trim() };
195
+ }
196
+ const j = (await r.json());
197
+ const videoUrl = j.video?.url;
198
+ if (!videoUrl)
199
+ return { ok: false, error: "fal result: no video.url" };
200
+ // hop 2: video bytes
201
+ const vr = await this.fetchImpl(videoUrl, { method: "GET", signal: req.signal });
202
+ if (!vr.ok) {
203
+ return { ok: false, error: `fal video download failed: HTTP ${vr.status}` };
204
+ }
205
+ const buf = new Uint8Array(await vr.arrayBuffer());
206
+ const m = /\.([a-z0-9]{2,4})(?:\?|$)/i.exec(videoUrl);
207
+ const ext = m ? m[1].toLowerCase() : "mp4";
208
+ // Surface the fal-hosted URL so the model can reuse it for video extension
209
+ // (videos[] → video_urls) without re-uploading. Note: signed/expiring.
210
+ return { ok: true, bytes: buf, ext, url: videoUrl };
211
+ }
212
+ catch (err) {
213
+ return { ok: false, error: `fal download error: ${err.message}` };
214
+ }
215
+ }
216
+ }
217
+ /** Default model per video provider kind (filled in with real adapters). */
218
+ export const DEFAULT_VIDEO_MODEL = {
219
+ fal: "fal-ai/kling-video/v3/pro/text-to-video",
220
+ };
221
+ /**
222
+ * Registry of video-provider adapters, keyed by `kind`. Only `fake` exists
223
+ * today (test/dev). Real adapters (seedance, kling) are intentionally absent
224
+ * until their private API contracts are confirmed — see TODO 7.1 块3.
225
+ */
226
+ export function getVideoProvider(kind, fetchImpl = fetch) {
227
+ switch (kind) {
228
+ case "fake":
229
+ return new FakeVideoProvider({ succeedAfterPolls: 0 });
230
+ case "fal":
231
+ return new FalVideoProvider(fetchImpl);
232
+ default:
233
+ return null;
234
+ }
235
+ }
@@ -176,7 +176,12 @@ export async function webFetchTool(args) {
176
176
  catch {
177
177
  return `Error: invalid URL "${url}"`;
178
178
  }
179
- const maxLength = Math.min(args.max_length || DEFAULT_MAX, MAX_OUTPUT);
179
+ // A non-positive max_length must NOT reach `text.slice(0, maxLength)` below
180
+ // with a negative end — JS reads that as "all but the last N", silently
181
+ // returning the WRONG bytes (+ a misleading "truncated" suffix). `|| DEFAULT`
182
+ // only caught 0/NaN, not negatives. Floor to the default page size first.
183
+ const rawMaxLength = args.max_length;
184
+ const maxLength = Math.min(typeof rawMaxLength === "number" && rawMaxLength > 0 ? rawMaxLength : DEFAULT_MAX, MAX_OUTPUT);
180
185
  const rawHeaders = args.headers ?? {};
181
186
  const baseHeaders = {
182
187
  "User-Agent": "Mozilla/5.0 (compatible; code-shell/0.1; +https://github.com/nicepkg/code-shell)",
@@ -192,7 +197,12 @@ export async function webFetchTool(args) {
192
197
  // host, and DNS-resolved IPs against the block lists. fetch() runs
193
198
  // with redirect: "manual" so the runtime cannot silently follow a
194
199
  // redirect into private/loopback/metadata space.
200
+ // Combine the 30s fetch timeout with the run's abort signal (registry injects
201
+ // __signal) so a user Stop cancels an in-flight fetch promptly, not only on
202
+ // the 30s timeout. Timeout alone was the previous behavior.
195
203
  const timeoutSignal = AbortSignal.timeout(30_000);
204
+ const userSignal = args.__signal;
205
+ const fetchSignal = userSignal ? AbortSignal.any([userSignal, timeoutSignal]) : timeoutSignal;
196
206
  let currentUrl = parsed;
197
207
  let currentHeaders = { ...baseHeaders };
198
208
  const originalOrigin = parsed;
@@ -206,7 +216,7 @@ export async function webFetchTool(args) {
206
216
  const res = await fetch(currentUrl, {
207
217
  headers: currentHeaders,
208
218
  redirect: "manual",
209
- signal: timeoutSignal,
219
+ signal: fetchSignal,
210
220
  });
211
221
  // Not a redirect: return body (or HTTP error).
212
222
  if (res.status < 300 || res.status >= 400 || !res.headers.get("location")) {
@@ -119,23 +119,34 @@ export async function webSearchTool(args, ctx) {
119
119
  const query = args.query;
120
120
  if (!query)
121
121
  return "Error: query is required";
122
- const numResults = Math.min(args.num_results || 10, 20);
122
+ // Floor to default on non-positive: a negative num_results would reach
123
+ // `results.slice(0, num)` with a negative end (= all but the last N), silently
124
+ // returning the wrong result set. `|| 10` only caught 0/NaN, not negatives.
125
+ const rawNum = args.num_results;
126
+ const numResults = Math.min(typeof rawNum === "number" && rawNum > 0 ? rawNum : 10, 20);
123
127
  // Use the run's cwd (e.g. an automation job's project dir) so per-project
124
128
  // settings resolve correctly; falls back to process.cwd() when absent.
125
129
  const config = resolveSearchConfig(ctx?.cwd);
126
130
  if (config.source === "none") {
127
131
  return "Error: No search provider configured. Open desktop 设置 → 连接 to add credentials, or set SERPER_API_KEY / TAVILY_API_KEY / SEARXNG_URL.";
128
132
  }
133
+ // A search provider's fetch had NO timeout and NO abort wiring — a hung/slow
134
+ // provider blocked the tool indefinitely and a user Stop couldn't cancel it.
135
+ // Give every provider call a 20s timeout, combined with the run's abort
136
+ // signal (registry injects __signal) so Stop is prompt.
137
+ const userSignal = args.__signal;
138
+ const timeoutSignal = AbortSignal.timeout(20_000);
139
+ const signal = userSignal ? AbortSignal.any([userSignal, timeoutSignal]) : timeoutSignal;
129
140
  try {
130
141
  let results;
131
142
  if (config.provider === "serper" && config.apiKey) {
132
- results = await searchSerper(query, numResults, config.apiKey);
143
+ results = await searchSerper(query, numResults, config.apiKey, signal);
133
144
  }
134
145
  else if (config.provider === "tavily" && config.apiKey) {
135
- results = await searchTavily(query, numResults, config.apiKey);
146
+ results = await searchTavily(query, numResults, config.apiKey, signal);
136
147
  }
137
148
  else if (config.provider === "searxng" && config.baseUrl) {
138
- results = await searchSearXNG(query, numResults, config.baseUrl);
149
+ results = await searchSearXNG(query, numResults, config.baseUrl, signal);
139
150
  }
140
151
  else {
141
152
  return `Error: provider "${config.provider}" missing ${config.provider === "searxng" ? "base URL" : "API key"}`;
@@ -150,7 +161,7 @@ export async function webSearchTool(args, ctx) {
150
161
  return `Search error: ${err.message}`;
151
162
  }
152
163
  }
153
- async function searchSerper(query, num, apiKey) {
164
+ async function searchSerper(query, num, apiKey, signal) {
154
165
  const res = await fetch("https://google.serper.dev/search", {
155
166
  method: "POST",
156
167
  headers: {
@@ -158,6 +169,7 @@ async function searchSerper(query, num, apiKey) {
158
169
  "Content-Type": "application/json",
159
170
  },
160
171
  body: JSON.stringify({ q: query, num }),
172
+ signal,
161
173
  });
162
174
  if (!res.ok)
163
175
  throw new Error(`Serper API error: ${res.status} ${res.statusText}`);
@@ -168,7 +180,7 @@ async function searchSerper(query, num, apiKey) {
168
180
  snippet: item.snippet ?? "",
169
181
  }));
170
182
  }
171
- async function searchTavily(query, num, apiKey) {
183
+ async function searchTavily(query, num, apiKey, signal) {
172
184
  const res = await fetch("https://api.tavily.com/search", {
173
185
  method: "POST",
174
186
  headers: { "Content-Type": "application/json" },
@@ -178,6 +190,7 @@ async function searchTavily(query, num, apiKey) {
178
190
  max_results: num,
179
191
  include_answer: false,
180
192
  }),
193
+ signal,
181
194
  });
182
195
  if (!res.ok)
183
196
  throw new Error(`Tavily API error: ${res.status} ${res.statusText}`);
@@ -188,13 +201,14 @@ async function searchTavily(query, num, apiKey) {
188
201
  snippet: item.content ?? "",
189
202
  }));
190
203
  }
191
- async function searchSearXNG(query, num, baseUrl) {
204
+ async function searchSearXNG(query, num, baseUrl, signal) {
192
205
  const url = new URL("/search", baseUrl);
193
206
  url.searchParams.set("q", query);
194
207
  url.searchParams.set("format", "json");
195
208
  url.searchParams.set("pageno", "1");
196
209
  const res = await fetch(url.toString(), {
197
210
  headers: { Accept: "application/json" },
211
+ signal,
198
212
  });
199
213
  if (!res.ok)
200
214
  throw new Error(`SearXNG error: ${res.status} ${res.statusText}`);