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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (387) hide show
  1. package/README.md +13 -10
  2. package/dist/agent/agent-definition-registry.d.ts +3 -0
  3. package/dist/agent/agent-definition-registry.js +3 -1
  4. package/dist/agent/agent-definition.d.ts +15 -0
  5. package/dist/agent/agent-definition.js +27 -3
  6. package/dist/arena/context/context-tools.js +47 -4
  7. package/dist/arena/ledger.js +9 -1
  8. package/dist/arena/strategies/utils.d.ts +1 -4
  9. package/dist/arena/strategies/utils.js +7 -84
  10. package/dist/automation/cron-expr.d.ts +10 -0
  11. package/dist/automation/cron-expr.js +23 -5
  12. package/dist/automation/runner.d.ts +21 -4
  13. package/dist/automation/runner.js +48 -12
  14. package/dist/automation/scheduler.d.ts +38 -0
  15. package/dist/automation/scheduler.js +89 -12
  16. package/dist/automation/store.js +5 -2
  17. package/dist/capability-control/disabled-lists.d.ts +25 -0
  18. package/dist/capability-control/disabled-lists.js +57 -0
  19. package/dist/capability-control/overlay.d.ts +15 -0
  20. package/dist/capability-control/overlay.js +27 -0
  21. package/dist/cc-orchestrator/agent-adapter.d.ts +50 -0
  22. package/dist/cc-orchestrator/agent-adapter.js +133 -0
  23. package/dist/cc-orchestrator/cc-capability.d.ts +19 -0
  24. package/dist/cc-orchestrator/cc-capability.js +53 -0
  25. package/dist/cc-orchestrator/codex-session-discovery.d.ts +24 -0
  26. package/dist/cc-orchestrator/codex-session-discovery.js +191 -0
  27. package/dist/cc-orchestrator/codex-session-history.d.ts +25 -0
  28. package/dist/cc-orchestrator/codex-session-history.js +187 -0
  29. package/dist/cc-orchestrator/external-agent-changes.d.ts +19 -0
  30. package/dist/cc-orchestrator/external-agent-changes.js +214 -0
  31. package/dist/cc-orchestrator/external-agent-driver.d.ts +18 -0
  32. package/dist/cc-orchestrator/external-agent-driver.js +69 -0
  33. package/dist/cc-orchestrator/index.d.ts +8 -0
  34. package/dist/cc-orchestrator/index.js +8 -0
  35. package/dist/cc-orchestrator/relevance-judge.d.ts +15 -0
  36. package/dist/cc-orchestrator/relevance-judge.js +29 -0
  37. package/dist/cc-orchestrator/session-discovery.d.ts +46 -0
  38. package/dist/cc-orchestrator/session-discovery.js +125 -0
  39. package/dist/cc-orchestrator/session-history.d.ts +19 -0
  40. package/dist/cc-orchestrator/session-history.js +67 -0
  41. package/dist/cli/agent-server-stdio.d.ts +15 -1
  42. package/dist/cli/agent-server-stdio.js +115 -17
  43. package/dist/cli/agent-server-tcp.js +40 -26
  44. package/dist/context/compaction.d.ts +56 -0
  45. package/dist/context/compaction.js +186 -0
  46. package/dist/context/manager.js +25 -4
  47. package/dist/cost-tracker.js +5 -61
  48. package/dist/credentials/cipher.d.ts +49 -0
  49. package/dist/credentials/cipher.js +45 -0
  50. package/dist/credentials/cookie-jar.d.ts +24 -0
  51. package/dist/credentials/cookie-jar.js +40 -0
  52. package/dist/credentials/index.d.ts +7 -0
  53. package/dist/credentials/index.js +5 -0
  54. package/dist/credentials/inject-credential-tool.d.ts +20 -0
  55. package/dist/credentials/inject-credential-tool.js +130 -0
  56. package/dist/credentials/store.d.ts +72 -0
  57. package/dist/credentials/store.js +184 -0
  58. package/dist/credentials/types.d.ts +56 -0
  59. package/dist/credentials/use-credential-tool.d.ts +29 -0
  60. package/dist/credentials/use-credential-tool.js +205 -0
  61. package/dist/credentials/use-gate.d.ts +56 -0
  62. package/dist/credentials/use-gate.js +52 -0
  63. package/dist/data/model-metadata.d.ts +77 -0
  64. package/dist/data/model-metadata.js +56 -0
  65. package/dist/data/model-metadata.json +216 -0
  66. package/dist/data/openrouter-models.d.ts +18 -7
  67. package/dist/data/openrouter-models.js +35 -8
  68. package/dist/engine/aux-key.d.ts +10 -0
  69. package/dist/engine/aux-key.js +11 -0
  70. package/dist/engine/dynamic-tool-defs.d.ts +19 -0
  71. package/dist/engine/dynamic-tool-defs.js +36 -0
  72. package/dist/engine/engine.d.ts +267 -138
  73. package/dist/engine/engine.js +957 -191
  74. package/dist/engine/friendly-error.d.ts +18 -0
  75. package/dist/engine/friendly-error.js +63 -0
  76. package/dist/engine/goal.d.ts +145 -0
  77. package/dist/engine/goal.js +144 -0
  78. package/dist/engine/image-policy.d.ts +13 -0
  79. package/dist/engine/image-policy.js +24 -0
  80. package/dist/engine/model-connections-pool.d.ts +17 -0
  81. package/dist/engine/model-connections-pool.js +66 -0
  82. package/dist/engine/model-facade.d.ts +10 -0
  83. package/dist/engine/model-facade.js +25 -0
  84. package/dist/engine/patch-orphaned-tools.js +3 -0
  85. package/dist/engine/resolve-llm-config.d.ts +16 -0
  86. package/dist/engine/resolve-llm-config.js +44 -0
  87. package/dist/engine/runtime.js +2 -1
  88. package/dist/engine/sandbox-cache-key.d.ts +10 -0
  89. package/dist/engine/sandbox-cache-key.js +9 -0
  90. package/dist/engine/sandbox-config.d.ts +31 -0
  91. package/dist/engine/sandbox-config.js +38 -0
  92. package/dist/engine/session-usage.d.ts +19 -0
  93. package/dist/engine/session-usage.js +25 -0
  94. package/dist/engine/steer-queue.d.ts +32 -0
  95. package/dist/engine/steer-queue.js +26 -0
  96. package/dist/engine/streaming-tool-queue.d.ts +12 -0
  97. package/dist/engine/streaming-tool-queue.js +50 -10
  98. package/dist/engine/turn-loop.d.ts +67 -3
  99. package/dist/engine/turn-loop.js +292 -41
  100. package/dist/engine/types.d.ts +175 -0
  101. package/dist/engine/types.js +13 -0
  102. package/dist/external-agents/config.d.ts +2 -0
  103. package/dist/external-agents/config.js +15 -0
  104. package/dist/external-agents/types.d.ts +31 -0
  105. package/dist/external-agents/types.js +1 -0
  106. package/dist/git/utils.js +11 -6
  107. package/dist/git/worktree.d.ts +48 -0
  108. package/dist/git/worktree.js +86 -10
  109. package/dist/hooks/goal-stop-hook.d.ts +28 -0
  110. package/dist/hooks/goal-stop-hook.js +186 -9
  111. package/dist/hooks/registry.d.ts +8 -0
  112. package/dist/hooks/registry.js +19 -0
  113. package/dist/hooks/shell-runner.js +10 -21
  114. package/dist/index.d.ts +49 -6
  115. package/dist/index.js +60 -4
  116. package/dist/llm/capabilities/param-specs.d.ts +14 -0
  117. package/dist/llm/capabilities/param-specs.js +62 -0
  118. package/dist/llm/capabilities/rules.js +4 -0
  119. package/dist/llm/capabilities/types.d.ts +10 -0
  120. package/dist/llm/client-base.d.ts +28 -1
  121. package/dist/llm/client-base.js +119 -13
  122. package/dist/llm/model-cache.js +4 -2
  123. package/dist/llm/model-pool.d.ts +14 -0
  124. package/dist/llm/model-pool.js +13 -0
  125. package/dist/llm/provider-auth.d.ts +41 -0
  126. package/dist/llm/provider-auth.js +76 -0
  127. package/dist/llm/provider-catalog.d.ts +4 -0
  128. package/dist/llm/providers/anthropic.js +64 -14
  129. package/dist/llm/providers/openai.d.ts +36 -0
  130. package/dist/llm/providers/openai.js +257 -51
  131. package/dist/llm/reasoning-setting.d.ts +3 -3
  132. package/dist/llm/reasoning-setting.js +9 -1
  133. package/dist/llm/stream-watchdog.js +5 -1
  134. package/dist/llm/token-counter.js +9 -2
  135. package/dist/llm/types.d.ts +4 -0
  136. package/dist/logging/sanitize-messages.js +22 -0
  137. package/dist/lsp/manager.d.ts +1 -1
  138. package/dist/lsp/manager.js +40 -10
  139. package/dist/model-catalog/builtin.d.ts +12 -0
  140. package/dist/model-catalog/builtin.js +407 -0
  141. package/dist/model-catalog/gen-connections.d.ts +20 -0
  142. package/dist/model-catalog/gen-connections.js +28 -0
  143. package/dist/model-catalog/index.d.ts +41 -0
  144. package/dist/model-catalog/index.js +90 -0
  145. package/dist/model-catalog/params.d.ts +20 -0
  146. package/dist/model-catalog/params.js +45 -0
  147. package/dist/model-catalog/resolve.d.ts +48 -0
  148. package/dist/model-catalog/resolve.js +33 -0
  149. package/dist/model-catalog/save-entry.d.ts +32 -0
  150. package/dist/model-catalog/save-entry.js +104 -0
  151. package/dist/model-catalog/types.d.ts +561 -0
  152. package/dist/model-catalog/types.js +93 -0
  153. package/dist/model-catalog/upsert.d.ts +9 -0
  154. package/dist/model-catalog/upsert.js +8 -0
  155. package/dist/onboarding.d.ts +12 -82
  156. package/dist/onboarding.js +61 -326
  157. package/dist/plugins/gitOps.d.ts +19 -0
  158. package/dist/plugins/gitOps.js +73 -4
  159. package/dist/plugins/installer/checkUpdate.d.ts +16 -0
  160. package/dist/plugins/installer/checkUpdate.js +32 -0
  161. package/dist/plugins/installer/codex/convertCommands.d.ts +19 -0
  162. package/dist/plugins/installer/codex/convertCommands.js +46 -0
  163. package/dist/plugins/installer/codex/convertMcp.d.ts +5 -2
  164. package/dist/plugins/installer/codex/convertMcp.js +45 -5
  165. package/dist/plugins/installer/install.js +25 -1
  166. package/dist/plugins/installer/installFromArchive.d.ts +43 -0
  167. package/dist/plugins/installer/installFromArchive.js +134 -0
  168. package/dist/plugins/installer/installFromSource.js +8 -2
  169. package/dist/plugins/installer/loadPluginAgents.js +6 -2
  170. package/dist/plugins/installer/loadPluginMcp.d.ts +9 -2
  171. package/dist/plugins/installer/loadPluginMcp.js +35 -2
  172. package/dist/plugins/installer/pruneDisabled.d.ts +24 -0
  173. package/dist/plugins/installer/pruneDisabled.js +73 -0
  174. package/dist/plugins/installer/types.d.ts +5 -2
  175. package/dist/plugins/installer/types.js +1 -0
  176. package/dist/plugins/installer/uninstall.js +4 -0
  177. package/dist/plugins/installer/unzip.d.ts +14 -0
  178. package/dist/plugins/installer/unzip.js +82 -0
  179. package/dist/plugins/installer/update.d.ts +14 -0
  180. package/dist/plugins/installer/update.js +53 -21
  181. package/dist/plugins/loadPluginHooks.d.ts +47 -1
  182. package/dist/plugins/loadPluginHooks.js +73 -1
  183. package/dist/plugins/marketplaceManager.d.ts +7 -0
  184. package/dist/plugins/marketplaceManager.js +20 -0
  185. package/dist/plugins/pluginCommandHook.js +4 -18
  186. package/dist/plugins/pluginContent.d.ts +30 -0
  187. package/dist/plugins/pluginContent.js +83 -0
  188. package/dist/plugins/pluginInstaller.d.ts +13 -0
  189. package/dist/plugins/pluginInstaller.js +42 -8
  190. package/dist/plugins/schemas.js +1 -0
  191. package/dist/plugins/types.d.ts +6 -0
  192. package/dist/preset/index.d.ts +7 -1
  193. package/dist/preset/index.js +96 -6
  194. package/dist/product/types.d.ts +1 -1
  195. package/dist/prompt/composer.d.ts +27 -0
  196. package/dist/prompt/composer.js +67 -21
  197. package/dist/prompt/instruction-scanner.js +5 -3
  198. package/dist/prompt/section-loader.js +1 -0
  199. package/dist/prompt/sections/base.md +1 -0
  200. package/dist/prompt/sections/browser.md +10 -0
  201. package/dist/prompt/sections/coding.md +4 -0
  202. package/dist/protocol/chat-session-manager.d.ts +8 -2
  203. package/dist/protocol/chat-session-manager.js +36 -0
  204. package/dist/protocol/chat-session.d.ts +65 -0
  205. package/dist/protocol/chat-session.js +94 -2
  206. package/dist/protocol/client.d.ts +27 -0
  207. package/dist/protocol/client.js +38 -0
  208. package/dist/protocol/server.d.ts +112 -0
  209. package/dist/protocol/server.js +586 -16
  210. package/dist/protocol/transport.js +3 -2
  211. package/dist/protocol/types.d.ts +46 -0
  212. package/dist/protocol/types.js +16 -0
  213. package/dist/quota/credentials.d.ts +3 -0
  214. package/dist/quota/credentials.js +80 -0
  215. package/dist/quota/index.d.ts +36 -0
  216. package/dist/quota/index.js +155 -0
  217. package/dist/quota/types.d.ts +48 -0
  218. package/dist/quota/types.js +13 -0
  219. package/dist/review/review-prompt.d.ts +28 -0
  220. package/dist/review/review-prompt.js +81 -0
  221. package/dist/run/FileRunStore.js +8 -3
  222. package/dist/run/RunApprovalBackend.js +25 -5
  223. package/dist/run/RunManager.d.ts +12 -0
  224. package/dist/run/RunManager.js +35 -0
  225. package/dist/run/factory.d.ts +1 -1
  226. package/dist/runtime/background-shell.d.ts +139 -0
  227. package/dist/runtime/background-shell.js +495 -0
  228. package/dist/runtime/output-clean.d.ts +24 -0
  229. package/dist/runtime/output-clean.js +41 -0
  230. package/dist/runtime/ring-file.d.ts +64 -0
  231. package/dist/runtime/ring-file.js +174 -0
  232. package/dist/runtime/safe-spawn.js +15 -34
  233. package/dist/runtime/spawn-common.d.ts +148 -0
  234. package/dist/runtime/spawn-common.js +303 -0
  235. package/dist/runtime/truncate-output.d.ts +22 -0
  236. package/dist/runtime/truncate-output.js +49 -0
  237. package/dist/runtime/utf8-cut.d.ts +11 -0
  238. package/dist/runtime/utf8-cut.js +23 -0
  239. package/dist/services/auto-dream.d.ts +4 -0
  240. package/dist/services/auto-dream.js +26 -26
  241. package/dist/services/diagnostics.d.ts +1 -2
  242. package/dist/services/diagnostics.js +12 -7
  243. package/dist/services/extract-memories.d.ts +14 -1
  244. package/dist/services/extract-memories.js +45 -6
  245. package/dist/services/memory-orchestrator.d.ts +21 -0
  246. package/dist/services/memory-orchestrator.js +125 -50
  247. package/dist/services/session-memory.js +24 -12
  248. package/dist/session/file-history.d.ts +124 -1
  249. package/dist/session/file-history.js +222 -6
  250. package/dist/session/memory.d.ts +116 -2
  251. package/dist/session/memory.js +250 -28
  252. package/dist/session/session-manager.d.ts +40 -0
  253. package/dist/session/session-manager.js +113 -3
  254. package/dist/session/simple-diff.d.ts +23 -0
  255. package/dist/session/simple-diff.js +84 -0
  256. package/dist/session/transcript.d.ts +25 -1
  257. package/dist/session/transcript.js +35 -2
  258. package/dist/session/undo-target.d.ts +67 -0
  259. package/dist/session/undo-target.js +144 -0
  260. package/dist/settings/disk-defaults.d.ts +9 -2
  261. package/dist/settings/disk-defaults.js +11 -2
  262. package/dist/settings/feature-flags.d.ts +64 -0
  263. package/dist/settings/feature-flags.js +61 -0
  264. package/dist/settings/manager.d.ts +68 -1
  265. package/dist/settings/manager.js +266 -29
  266. package/dist/settings/migrate-config.d.ts +45 -0
  267. package/dist/settings/migrate-config.js +125 -0
  268. package/dist/settings/schema-export.d.ts +25 -0
  269. package/dist/settings/schema-export.js +38 -0
  270. package/dist/settings/schema.d.ts +1186 -771
  271. package/dist/settings/schema.js +268 -97
  272. package/dist/skills/scanner.d.ts +9 -0
  273. package/dist/skills/scanner.js +30 -2
  274. package/dist/stt/resolve-transcribe.d.ts +31 -0
  275. package/dist/stt/resolve-transcribe.js +108 -0
  276. package/dist/stt/transcribe.d.ts +51 -0
  277. package/dist/stt/transcribe.js +65 -0
  278. package/dist/tool-system/browser-bridge.d.ts +226 -0
  279. package/dist/tool-system/browser-bridge.js +163 -0
  280. package/dist/tool-system/builtin/agent-heartbeat.d.ts +49 -0
  281. package/dist/tool-system/builtin/agent-heartbeat.js +89 -0
  282. package/dist/tool-system/builtin/agent-notifications.d.ts +12 -3
  283. package/dist/tool-system/builtin/agent-notifications.js +9 -3
  284. package/dist/tool-system/builtin/agent-output-file.d.ts +38 -0
  285. package/dist/tool-system/builtin/agent-output-file.js +72 -0
  286. package/dist/tool-system/builtin/agent-registry.d.ts +12 -0
  287. package/dist/tool-system/builtin/agent-registry.js +8 -0
  288. package/dist/tool-system/builtin/agent.d.ts +21 -1
  289. package/dist/tool-system/builtin/agent.js +489 -42
  290. package/dist/tool-system/builtin/apply-patch/applier.js +66 -8
  291. package/dist/tool-system/builtin/apply-patch/backup-targets.d.ts +10 -0
  292. package/dist/tool-system/builtin/apply-patch/backup-targets.js +30 -0
  293. package/dist/tool-system/builtin/apply-patch/index.js +0 -15
  294. package/dist/tool-system/builtin/background-jobs.d.ts +76 -0
  295. package/dist/tool-system/builtin/background-jobs.js +124 -0
  296. package/dist/tool-system/builtin/background-shell-tools.d.ts +20 -0
  297. package/dist/tool-system/builtin/background-shell-tools.js +108 -0
  298. package/dist/tool-system/builtin/background-work.d.ts +67 -0
  299. package/dist/tool-system/builtin/background-work.js +86 -0
  300. package/dist/tool-system/builtin/bash-output-style.d.ts +32 -0
  301. package/dist/tool-system/builtin/bash-output-style.js +40 -0
  302. package/dist/tool-system/builtin/bash.d.ts +5 -2
  303. package/dist/tool-system/builtin/bash.js +98 -62
  304. package/dist/tool-system/builtin/browser-tools.d.ts +33 -0
  305. package/dist/tool-system/builtin/browser-tools.js +312 -0
  306. package/dist/tool-system/builtin/cancel-goal.d.ts +31 -0
  307. package/dist/tool-system/builtin/cancel-goal.js +64 -0
  308. package/dist/tool-system/builtin/check-quota.d.ts +15 -0
  309. package/dist/tool-system/builtin/check-quota.js +34 -0
  310. package/dist/tool-system/builtin/config.js +7 -0
  311. package/dist/tool-system/builtin/cron.d.ts +7 -0
  312. package/dist/tool-system/builtin/cron.js +55 -3
  313. package/dist/tool-system/builtin/drive-claude-code.d.ts +30 -0
  314. package/dist/tool-system/builtin/drive-claude-code.js +157 -0
  315. package/dist/tool-system/builtin/edit-model-catalog.d.ts +3 -0
  316. package/dist/tool-system/builtin/edit-model-catalog.js +94 -0
  317. package/dist/tool-system/builtin/edit.js +20 -16
  318. package/dist/tool-system/builtin/eol.d.ts +29 -0
  319. package/dist/tool-system/builtin/eol.js +37 -0
  320. package/dist/tool-system/builtin/file-cache.d.ts +6 -0
  321. package/dist/tool-system/builtin/file-cache.js +8 -0
  322. package/dist/tool-system/builtin/generate-image.d.ts +35 -0
  323. package/dist/tool-system/builtin/generate-image.js +278 -50
  324. package/dist/tool-system/builtin/generate-video.d.ts +55 -0
  325. package/dist/tool-system/builtin/generate-video.js +361 -0
  326. package/dist/tool-system/builtin/glob.js +0 -7
  327. package/dist/tool-system/builtin/grep.js +6 -8
  328. package/dist/tool-system/builtin/image-providers.d.ts +86 -0
  329. package/dist/tool-system/builtin/image-providers.js +190 -0
  330. package/dist/tool-system/builtin/image-uploader.d.ts +33 -0
  331. package/dist/tool-system/builtin/image-uploader.js +74 -0
  332. package/dist/tool-system/builtin/index.d.ts +7 -1
  333. package/dist/tool-system/builtin/index.js +249 -25
  334. package/dist/tool-system/builtin/mcp-tools.js +23 -3
  335. package/dist/tool-system/builtin/memory.js +45 -7
  336. package/dist/tool-system/builtin/notebook-edit.js +0 -7
  337. package/dist/tool-system/builtin/powershell.js +4 -1
  338. package/dist/tool-system/builtin/read.js +10 -10
  339. package/dist/tool-system/builtin/repl.js +4 -1
  340. package/dist/tool-system/builtin/skill.js +9 -0
  341. package/dist/tool-system/builtin/sleep.js +3 -2
  342. package/dist/tool-system/builtin/tool-search.js +25 -7
  343. package/dist/tool-system/builtin/video-providers.d.ts +154 -0
  344. package/dist/tool-system/builtin/video-providers.js +235 -0
  345. package/dist/tool-system/builtin/web-fetch.js +12 -2
  346. package/dist/tool-system/builtin/web-search.js +21 -7
  347. package/dist/tool-system/builtin/worktree.d.ts +2 -1
  348. package/dist/tool-system/builtin/worktree.js +31 -4
  349. package/dist/tool-system/builtin/write.js +0 -6
  350. package/dist/tool-system/context.d.ts +154 -6
  351. package/dist/tool-system/executor.d.ts +3 -1
  352. package/dist/tool-system/executor.js +172 -88
  353. package/dist/tool-system/investigation-guard.js +1 -1
  354. package/dist/tool-system/mcp-manager.d.ts +42 -4
  355. package/dist/tool-system/mcp-manager.js +186 -20
  356. package/dist/tool-system/path-policy.d.ts +5 -0
  357. package/dist/tool-system/path-policy.js +307 -8
  358. package/dist/tool-system/permission.d.ts +36 -1
  359. package/dist/tool-system/permission.js +281 -69
  360. package/dist/tool-system/plan-mode-allowlist.d.ts +13 -2
  361. package/dist/tool-system/plan-mode-allowlist.js +24 -2
  362. package/dist/tool-system/registry.d.ts +1 -0
  363. package/dist/tool-system/registry.js +16 -2
  364. package/dist/tool-system/sandbox/index.d.ts +8 -0
  365. package/dist/tool-system/sandbox/index.js +7 -2
  366. package/dist/tool-system/validate-tool-metadata.d.ts +36 -0
  367. package/dist/tool-system/validate-tool-metadata.js +63 -0
  368. package/dist/types.d.ts +217 -6
  369. package/dist/updater.js +20 -9
  370. package/dist/utils/envUtils.d.ts +0 -9
  371. package/dist/utils/envUtils.js +3 -28
  372. package/dist/utils/exec.d.ts +40 -0
  373. package/dist/utils/exec.js +144 -0
  374. package/dist/utils/json.d.ts +12 -0
  375. package/dist/utils/json.js +92 -0
  376. package/dist/utils/theme.d.ts +1 -1
  377. package/dist/utils/theme.js +1 -1
  378. package/dist/utils/toolDisplay.js +0 -1
  379. package/package.json +13 -7
  380. package/dist/agent/coordinator.d.ts +0 -49
  381. package/dist/agent/coordinator.js +0 -77
  382. package/dist/settings/manager.test.js +0 -73
  383. package/dist/tool-system/builtin/remote-trigger.d.ts +0 -6
  384. package/dist/tool-system/builtin/remote-trigger.js +0 -54
  385. package/dist/tool-system/builtin/send-message.d.ts +0 -6
  386. package/dist/tool-system/builtin/send-message.js +0 -47
  387. /package/dist/{settings/manager.test.d.ts → credentials/types.js} +0 -0
@@ -5,6 +5,32 @@ function pluginNameFromKey(key) {
5
5
  const at = key.lastIndexOf("@");
6
6
  return at > 0 ? key.slice(0, at) : key;
7
7
  }
8
+ /**
9
+ * Layer a user override's supplement fields onto a plugin server config.
10
+ *
11
+ * Only env/credential fields are picked — command/args/url/transport are
12
+ * deliberately NOT carried, because those are the plugin's identity and must
13
+ * keep coming from the plugin manifest so a plugin update can change them
14
+ * without the user being stuck on a stale shadow copy. This explicit pick is
15
+ * defense-in-depth on top of the schema's `.strict()`, so a hand-edited
16
+ * settings file can never smuggle those fields in.
17
+ */
18
+ function applyOverride(pluginConfig, override) {
19
+ if (!override)
20
+ return pluginConfig;
21
+ const supplement = {};
22
+ if (override.env !== undefined)
23
+ supplement.env = override.env;
24
+ if (override.envVars !== undefined)
25
+ supplement.envVars = override.envVars;
26
+ if (override.credentialRef !== undefined)
27
+ supplement.credentialRef = override.credentialRef;
28
+ if (override.bearerTokenEnvVar !== undefined)
29
+ supplement.bearerTokenEnvVar = override.bearerTokenEnvVar;
30
+ if (override.envHeaders !== undefined)
31
+ supplement.envHeaders = override.envHeaders;
32
+ return { ...pluginConfig, ...supplement };
33
+ }
8
34
  /**
9
35
  * Read a single plugin's MCP servers, already keyed `<plugin>:<server>`.
10
36
  *
@@ -52,8 +78,15 @@ function readPluginMcp(installPath, pluginName) {
52
78
  * Merge each registered plugin's MCP servers into a copy of `base`.
53
79
  * Plugin keys are `<plugin>:<server>`. A key already present in `base`
54
80
  * (user-configured) is NOT overwritten. Disabled plugins are skipped.
81
+ *
82
+ * `overrides` lets the user *supplement* a plugin server's env/credential
83
+ * fields without editing the plugin's manifest: for a plugin-sourced server,
84
+ * the override's {@link OVERRIDE_FIELDS} are layered on top (override wins),
85
+ * while command/args/url/transport stay from the plugin. Overrides do NOT
86
+ * apply to user-added (base) servers — those are edited via `mcpServers`
87
+ * directly — and an override for an unknown/disabled server has no effect.
55
88
  */
56
- export function mergePluginMcpServers(base, disabledPlugins = []) {
89
+ export function mergePluginMcpServers(base, disabledPlugins = [], overrides = {}) {
57
90
  const disabled = new Set(disabledPlugins);
58
91
  const merged = { ...base };
59
92
  const data = readInstalledPlugins();
@@ -66,7 +99,7 @@ export function mergePluginMcpServers(base, disabledPlugins = []) {
66
99
  for (const [k, cfg] of Object.entries(servers)) {
67
100
  if (k in merged)
68
101
  continue; // user / earlier wins
69
- merged[k] = cfg;
102
+ merged[k] = applyOverride(cfg, overrides[k]);
70
103
  }
71
104
  }
72
105
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Prune a plugin's orphaned entries from a settings object's `disabledSkills`
3
+ * (entries shaped `name:skill`) and `disabledPlugins` (bare `name`). Called on
4
+ * uninstall so a removed plugin leaves no dangling disable flags behind.
5
+ *
6
+ * Pure + immutable: returns a NEW object (shallow clone) with the two arrays
7
+ * replaced by filtered copies. Settings lacking these fields pass through
8
+ * unchanged. A plugin name that is a prefix of another (`mimi-video` vs
9
+ * `mimi-video-pro`) only matches its own exact name / `name:` prefix.
10
+ */
11
+ export interface PrunableSettings {
12
+ disabledSkills?: string[];
13
+ disabledPlugins?: string[];
14
+ [key: string]: unknown;
15
+ }
16
+ export declare function pruneDisabledEntriesForPlugin<T extends PrunableSettings>(settings: T, pluginName: string): T;
17
+ /**
18
+ * Read the user settings.json (under {@link userHome}, so HOME-isolated tests
19
+ * are honored), prune the plugin's orphaned disable entries, and write back —
20
+ * only when something actually changed. Never throws: a missing/corrupt file or
21
+ * any IO error is swallowed (uninstall already succeeded; settings cleanup is
22
+ * best-effort). Atomic write via .tmp + rename mirrors SettingsManager.
23
+ */
24
+ export declare function pruneDisabledSettingsForPlugin(pluginName: string): void;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Prune a plugin's orphaned entries from a settings object's `disabledSkills`
3
+ * (entries shaped `name:skill`) and `disabledPlugins` (bare `name`). Called on
4
+ * uninstall so a removed plugin leaves no dangling disable flags behind.
5
+ *
6
+ * Pure + immutable: returns a NEW object (shallow clone) with the two arrays
7
+ * replaced by filtered copies. Settings lacking these fields pass through
8
+ * unchanged. A plugin name that is a prefix of another (`mimi-video` vs
9
+ * `mimi-video-pro`) only matches its own exact name / `name:` prefix.
10
+ */
11
+ import { writeFileSync, mkdirSync, existsSync, readFileSync, renameSync } from "node:fs";
12
+ import { join, dirname } from "node:path";
13
+ import { homedir } from "node:os";
14
+ /**
15
+ * Resolve the user's home dir, preferring `process.env.HOME` so HOME-isolated
16
+ * tests (and runtime overrides) take effect — same contract as
17
+ * SettingsManager.userHome and installer/paths.ts, without dragging in the
18
+ * settings module's heavier transitive deps.
19
+ */
20
+ function userHome() {
21
+ return process.env.HOME ?? homedir();
22
+ }
23
+ export function pruneDisabledEntriesForPlugin(settings, pluginName) {
24
+ const skillPrefix = `${pluginName}:`;
25
+ const out = { ...settings };
26
+ if (Array.isArray(settings.disabledSkills)) {
27
+ out.disabledSkills = settings.disabledSkills.filter((s) => !s.startsWith(skillPrefix));
28
+ }
29
+ if (Array.isArray(settings.disabledPlugins)) {
30
+ out.disabledPlugins = settings.disabledPlugins.filter((p) => p !== pluginName);
31
+ }
32
+ return out;
33
+ }
34
+ /**
35
+ * Read the user settings.json (under {@link userHome}, so HOME-isolated tests
36
+ * are honored), prune the plugin's orphaned disable entries, and write back —
37
+ * only when something actually changed. Never throws: a missing/corrupt file or
38
+ * any IO error is swallowed (uninstall already succeeded; settings cleanup is
39
+ * best-effort). Atomic write via .tmp + rename mirrors SettingsManager.
40
+ */
41
+ export function pruneDisabledSettingsForPlugin(pluginName) {
42
+ try {
43
+ const path = join(userHome(), ".code-shell", "settings.json");
44
+ if (!existsSync(path))
45
+ return;
46
+ let current;
47
+ try {
48
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
49
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
50
+ return;
51
+ current = parsed;
52
+ }
53
+ catch {
54
+ return; // corrupt file — leave it alone
55
+ }
56
+ const pruned = pruneDisabledEntriesForPlugin(current, pluginName);
57
+ const changed = JSON.stringify(pruned.disabledSkills) !== JSON.stringify(current.disabledSkills) ||
58
+ JSON.stringify(pruned.disabledPlugins) !== JSON.stringify(current.disabledPlugins);
59
+ if (!changed)
60
+ return;
61
+ mkdirSync(dirname(path), { recursive: true });
62
+ const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
63
+ // mode 0o600 — settings.json can hold plaintext API keys, so the pruned
64
+ // rewrite must stay owner-only (matching settings/manager.ts), not fall back
65
+ // to the umask default 0o644 (world-readable). The tmp IS the final file
66
+ // after renameSync, so the mode must be set at create time, not via chmod.
67
+ writeFileSync(tmp, JSON.stringify(pruned, null, 2), { encoding: "utf-8", mode: 0o600 });
68
+ renameSync(tmp, path);
69
+ }
70
+ catch {
71
+ // best-effort cleanup — never fail the uninstall over settings IO
72
+ }
73
+ }
@@ -30,18 +30,21 @@ export declare const CSMeta: z.ZodObject<{
30
30
  version: z.ZodOptional<z.ZodString>;
31
31
  source: z.ZodString;
32
32
  installedAt: z.ZodString;
33
+ commit: z.ZodOptional<z.ZodString>;
33
34
  }, "strip", z.ZodTypeAny, {
34
35
  source: string;
35
36
  name: string;
36
- format: "codex" | "cc";
37
+ format: "cc" | "codex";
37
38
  installedAt: string;
38
39
  version?: string | undefined;
40
+ commit?: string | undefined;
39
41
  }, {
40
42
  source: string;
41
43
  name: string;
42
- format: "codex" | "cc";
44
+ format: "cc" | "codex";
43
45
  installedAt: string;
44
46
  version?: string | undefined;
47
+ commit?: string | undefined;
45
48
  }>;
46
49
  export type CSMeta = z.infer<typeof CSMeta>;
47
50
  export declare class PluginInstallError extends Error {
@@ -18,6 +18,7 @@ export const CSMeta = z.object({
18
18
  version: z.string().optional(),
19
19
  source: z.string(),
20
20
  installedAt: z.string(),
21
+ commit: z.string().optional(), // remote (git) source: the HEAD SHA we installed
21
22
  });
22
23
  export class PluginInstallError extends Error {
23
24
  constructor(message) {
@@ -2,6 +2,7 @@ import { existsSync, rmSync } from "node:fs";
2
2
  import { pluginInstallDir, assertSafePluginName } from "./paths.js";
3
3
  import { removeInstallEntries, pluginInstallKey } from "../installedPlugins.js";
4
4
  import { PluginInstallError } from "./types.js";
5
+ import { pruneDisabledSettingsForPlugin } from "./pruneDisabled.js";
5
6
  /** Remove a locally-installed plugin dir + its installed_plugins.json entry. */
6
7
  export function uninstallPluginByName(name) {
7
8
  assertSafePluginName(name);
@@ -11,4 +12,7 @@ export function uninstallPluginByName(name) {
11
12
  }
12
13
  rmSync(dir, { recursive: true, force: true });
13
14
  removeInstallEntries(pluginInstallKey(name, "local"));
15
+ // Clear orphaned disable flags (disabledSkills "name:skill" /
16
+ // disabledPlugins "name") so a removed plugin leaves no dangling entries.
17
+ pruneDisabledSettingsForPlugin(name);
14
18
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Reject path traversal ("zip slip"): a malicious archive can carry entries
3
+ * like `../../etc/foo` that escape the extraction root. We normalize and
4
+ * require the resolved path to stay within `destDir`.
5
+ */
6
+ export declare function safeJoin(destDir: string, entryName: string): string;
7
+ /**
8
+ * Extract a .zip into destDir (must already exist). Streams each entry to disk
9
+ * so a large archive doesn't buffer in memory. Directory entries (trailing
10
+ * "/") just create the dir; symlinks and other special entries are skipped —
11
+ * we only materialize regular files and directories, which is all a plugin
12
+ * needs and keeps the surface safe.
13
+ */
14
+ export declare function extractZip(zipPath: string, destDir: string): Promise<void>;
@@ -0,0 +1,82 @@
1
+ import { createRequire } from "node:module";
2
+ import { createWriteStream } from "node:fs";
3
+ import { mkdir } from "node:fs/promises";
4
+ import { dirname, join, normalize, sep } from "node:path";
5
+ import { pipeline } from "node:stream/promises";
6
+ import { PluginInstallError } from "./types.js";
7
+ let _yauzl;
8
+ function yauzl() {
9
+ if (!_yauzl) {
10
+ _yauzl = createRequire(import.meta.url ?? __filename)("yauzl");
11
+ }
12
+ return _yauzl;
13
+ }
14
+ /**
15
+ * Reject path traversal ("zip slip"): a malicious archive can carry entries
16
+ * like `../../etc/foo` that escape the extraction root. We normalize and
17
+ * require the resolved path to stay within `destDir`.
18
+ */
19
+ export function safeJoin(destDir, entryName) {
20
+ // Zip entries always use forward slashes; normalize to the host separator.
21
+ const target = normalize(join(destDir, entryName));
22
+ const root = normalize(destDir.endsWith(sep) ? destDir : destDir + sep);
23
+ if (target !== normalize(destDir) && !target.startsWith(root)) {
24
+ throw new PluginInstallError(`refusing zip entry that escapes target: ${entryName}`);
25
+ }
26
+ return target;
27
+ }
28
+ /**
29
+ * Extract a .zip into destDir (must already exist). Streams each entry to disk
30
+ * so a large archive doesn't buffer in memory. Directory entries (trailing
31
+ * "/") just create the dir; symlinks and other special entries are skipped —
32
+ * we only materialize regular files and directories, which is all a plugin
33
+ * needs and keeps the surface safe.
34
+ */
35
+ export async function extractZip(zipPath, destDir) {
36
+ const zip = await openZip(zipPath);
37
+ try {
38
+ await drainEntries(zip, destDir);
39
+ }
40
+ finally {
41
+ zip.close();
42
+ }
43
+ }
44
+ function openZip(zipPath) {
45
+ return new Promise((resolve, reject) => {
46
+ yauzl().open(zipPath, { lazyEntries: true }, (err, z) => {
47
+ if (err || !z) {
48
+ reject(new PluginInstallError(`cannot open zip: ${err?.message ?? "unknown"}`));
49
+ return;
50
+ }
51
+ resolve(z);
52
+ });
53
+ });
54
+ }
55
+ function drainEntries(zip, destDir) {
56
+ return new Promise((resolve, reject) => {
57
+ zip.on("error", (e) => reject(e));
58
+ zip.on("end", () => resolve());
59
+ zip.on("entry", (entry) => {
60
+ const onErr = (e) => reject(e);
61
+ // Directory entry — yauzl signals these with a trailing slash.
62
+ if (/\/$/.test(entry.fileName)) {
63
+ mkdir(safeJoin(destDir, entry.fileName), { recursive: true })
64
+ .then(() => zip.readEntry())
65
+ .catch(onErr);
66
+ return;
67
+ }
68
+ const outPath = safeJoin(destDir, entry.fileName);
69
+ zip.openReadStream(entry, (err, stream) => {
70
+ if (err || !stream) {
71
+ onErr(new PluginInstallError(`cannot read zip entry ${entry.fileName}: ${err?.message ?? "unknown"}`));
72
+ return;
73
+ }
74
+ mkdir(dirname(outPath), { recursive: true })
75
+ .then(() => pipeline(stream, createWriteStream(outPath)))
76
+ .then(() => zip.readEntry())
77
+ .catch(onErr);
78
+ });
79
+ });
80
+ zip.readEntry();
81
+ });
82
+ }
@@ -2,6 +2,20 @@ export interface UpdateResult {
2
2
  updated: boolean;
3
3
  reason: string;
4
4
  }
5
+ /**
6
+ * Reinstall a plugin atomically: a failed update leaves the OLD plugin intact.
7
+ *
8
+ * The installers (installPluginFromPath / installPluginFromSource) build into a
9
+ * temp dir then rename into pluginInstallDir(name), and THROW if the final dir
10
+ * already exists. So we must free the final dir *before* installing — we do that
11
+ * by renaming the live dir to a sibling backup. On success we drop the backup;
12
+ * on failure we delete any partial install and rename the backup back.
13
+ *
14
+ * Async fs only — this runs in the Electron main process, so we never block the
15
+ * event loop with sync fs. process.pid (not Date.now(), unavailable here) makes
16
+ * the backup name unique.
17
+ */
18
+ export declare function reinstallAtomic(name: string, source: string, doInstall: () => Promise<unknown>): Promise<void>;
5
19
  /**
6
20
  * Reinstall a plugin from its recorded source.
7
21
  *
@@ -1,12 +1,62 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
+ import { rename, rm } from "node:fs/promises";
2
3
  import { join } from "node:path";
3
4
  import { detectPluginFormat } from "./detectFormat.js";
4
5
  import { pluginInstallDir } from "./paths.js";
5
- import { uninstallPluginByName } from "./uninstall.js";
6
6
  import { installPluginFromPath } from "./install.js";
7
7
  import { installPluginFromSource } from "./installFromSource.js";
8
8
  import { parseSource } from "./parseSource.js";
9
+ import { pluginInstallKey, readInstalledPlugins, removeInstallEntries, writeInstalledPlugins } from "../installedPlugins.js";
9
10
  import { CodexPluginManifest, CSMeta, PluginInstallError } from "./types.js";
11
+ /**
12
+ * Reinstall a plugin atomically: a failed update leaves the OLD plugin intact.
13
+ *
14
+ * The installers (installPluginFromPath / installPluginFromSource) build into a
15
+ * temp dir then rename into pluginInstallDir(name), and THROW if the final dir
16
+ * already exists. So we must free the final dir *before* installing — we do that
17
+ * by renaming the live dir to a sibling backup. On success we drop the backup;
18
+ * on failure we delete any partial install and rename the backup back.
19
+ *
20
+ * Async fs only — this runs in the Electron main process, so we never block the
21
+ * event loop with sync fs. process.pid (not Date.now(), unavailable here) makes
22
+ * the backup name unique.
23
+ */
24
+ export async function reinstallAtomic(name, source, doInstall) {
25
+ const dir = pluginInstallDir(name);
26
+ const backup = `${dir}.bak-${process.pid}`;
27
+ // Capture the existing install_plugins.json entries so a failed reinstall can
28
+ // restore them (uninstall-then-install used to drop them on failure; here the
29
+ // old plugin survives, so its registration must survive too).
30
+ const key = pluginInstallKey(name, "local");
31
+ const savedEntries = readInstalledPlugins().plugins[key];
32
+ await rm(backup, { recursive: true, force: true });
33
+ await rename(dir, backup);
34
+ // The installer re-adds the entry on success; remove the stale one first so a
35
+ // successful reinstall doesn't accumulate a duplicate.
36
+ removeInstallEntries(key);
37
+ try {
38
+ await doInstall();
39
+ }
40
+ catch (err) {
41
+ // Roll back: drop any partial install, restore the old dir + its entries.
42
+ await rm(dir, { recursive: true, force: true });
43
+ await rename(backup, dir);
44
+ if (savedEntries) {
45
+ // Restore in a single read-modify-write rather than one append per entry,
46
+ // which both narrows the race window against any concurrent writer and
47
+ // avoids re-appending onto whatever partial state doInstall() may have
48
+ // left for this key.
49
+ const data = readInstalledPlugins();
50
+ data.plugins[key] = savedEntries;
51
+ writeInstalledPlugins(data);
52
+ }
53
+ throw new PluginInstallError(`update failed; the old version of '${name}' was kept: ` +
54
+ `${err instanceof Error ? err.message : String(err)}. Source: ${source}.`);
55
+ }
56
+ // Success — drop the backup (best-effort; a leftover backup is harmless but
57
+ // we clean it up so it never shows up in the plugins root).
58
+ await rm(backup, { recursive: true, force: true });
59
+ }
10
60
  /**
11
61
  * Reinstall a plugin from its recorded source.
12
62
  *
@@ -25,18 +75,7 @@ export async function updatePluginByName(name, installedAt, force) {
25
75
  const parsed = parseSource(meta.source);
26
76
  if (parsed.kind === "remote") {
27
77
  // No local version to diff against — always re-clone and reinstall.
28
- // uninstall-then-install isn't atomic: if the reinstall fails (clone
29
- // error, bad plugin) the old copy is already gone. We can't roll back the
30
- // removal, but we surface a clear error instead of leaving silent
31
- // inconsistency.
32
- uninstallPluginByName(name);
33
- try {
34
- await installPluginFromSource(parsed, name, installedAt);
35
- }
36
- catch (err) {
37
- throw new PluginInstallError(`update failed and '${name}' was removed during reinstall: ` +
38
- `${err instanceof Error ? err.message : String(err)}. Reinstall it from ${meta.source}.`);
39
- }
78
+ await reinstallAtomic(name, meta.source, () => installPluginFromSource(parsed, name, installedAt));
40
79
  return { updated: true, reason: "reinstalled from git source" };
41
80
  }
42
81
  if (!existsSync(meta.source)) {
@@ -53,13 +92,6 @@ export async function updatePluginByName(name, installedAt, force) {
53
92
  return { updated: false, reason: "CC plugin needs --force to reinstall" };
54
93
  }
55
94
  }
56
- uninstallPluginByName(name);
57
- try {
58
- await installPluginFromPath(meta.source, name, installedAt);
59
- }
60
- catch (err) {
61
- throw new PluginInstallError(`update failed and '${name}' was removed during reinstall: ` +
62
- `${err instanceof Error ? err.message : String(err)}. Reinstall it from ${meta.source}.`);
63
- }
95
+ await reinstallAtomic(name, meta.source, () => installPluginFromPath(meta.source, name, installedAt));
64
96
  return { updated: true, reason: "reinstalled" };
65
97
  }
@@ -42,7 +42,21 @@
42
42
  * Errors are swallowed per-plugin: a malformed hooks.json from one plugin
43
43
  * must not block other plugins from loading.
44
44
  */
45
+ import type { HookEventName } from "../hooks/events.js";
45
46
  import type { HookRegistry } from "../hooks/registry.js";
47
+ /**
48
+ * Stable identity for ONE plugin-provided hook, used as the record key in
49
+ * `capabilityOverrides.pluginHooks` (per-hook project off-switch). Derived
50
+ * from content (`plugin:RawEvent:command`) rather than scan order so it
51
+ * survives plugin reinstalls and hooks.json reordering. Two entries that
52
+ * differ only by matcher collide — acceptable: toggling one toggles both,
53
+ * and identical commands on the same event are in practice the same hook.
54
+ */
55
+ export declare function pluginHookKey(hook: {
56
+ plugin: string;
57
+ rawEvent: string;
58
+ command: string;
59
+ }): string;
46
60
  /**
47
61
  * Walk installed plugins and register their command hooks. Safe to call
48
62
  * multiple times — the caller is responsible for not double-registering
@@ -55,5 +69,37 @@ import type { HookRegistry } from "../hooks/registry.js";
55
69
  * also suppresses its SessionStart injection — not just its Skill-tool
56
70
  * entries. Without this, `disabledPlugins` only filtered the skill list and
57
71
  * the plugin's hooks still fired regardless.
72
+ *
73
+ * `disabledPluginHooks` is the fine-grained per-hook switch — a list of
74
+ * {@link pluginHookKey} keys (folded from the project's
75
+ * `capabilityOverrides.pluginHooks` by readDisabledLists). A matching hook
76
+ * is skipped while the rest of the plugin keeps working.
77
+ */
78
+ export declare function loadPluginHooks(registry: HookRegistry, disabledPlugins?: string[], disabledPluginHooks?: string[]): void;
79
+ /** A single plugin-provided hook, surfaced read-only to the settings UI. */
80
+ export interface PluginHookEntry {
81
+ /** Source plugin name (bare, no @marketplace). */
82
+ plugin: string;
83
+ /** Mapped codeshell event name. */
84
+ event: HookEventName;
85
+ /** Original CC event name as written in hooks.json (for display). */
86
+ rawEvent: string;
87
+ /** The shell command the hook runs. */
88
+ command: string;
89
+ /** Optional matcher (regex) from the hooks.json group. */
90
+ matcher?: string;
91
+ /** Whether the owning plugin is currently disabled (its hooks don't fire). */
92
+ disabled: boolean;
93
+ /** Stable per-hook identity ({@link pluginHookKey}) — the record key the
94
+ * UI writes to `capabilityOverrides.pluginHooks` to toggle just this hook. */
95
+ key: string;
96
+ }
97
+ /**
98
+ * Read-only counterpart to {@link loadPluginHooks}: scan installed plugins and
99
+ * RETURN their command hooks (instead of registering them), so the settings
100
+ * "钩子" page can show plugin-provided hooks alongside the user's hand-written
101
+ * ones, labelled by owner plugin. `disabledPlugins` doesn't filter the list —
102
+ * disabled plugins are still listed but flagged `disabled:true` (so the UI can
103
+ * show "由 xxx 插件提供(已禁用)"); pass it so that flag is accurate.
58
104
  */
59
- export declare function loadPluginHooks(registry: HookRegistry, disabledPlugins?: string[]): void;
105
+ export declare function listPluginHooks(disabledPlugins?: string[]): PluginHookEntry[];
@@ -109,6 +109,17 @@ function pluginNameFromKey(key) {
109
109
  const at = key.lastIndexOf("@");
110
110
  return at > 0 ? key.slice(0, at) : key;
111
111
  }
112
+ /**
113
+ * Stable identity for ONE plugin-provided hook, used as the record key in
114
+ * `capabilityOverrides.pluginHooks` (per-hook project off-switch). Derived
115
+ * from content (`plugin:RawEvent:command`) rather than scan order so it
116
+ * survives plugin reinstalls and hooks.json reordering. Two entries that
117
+ * differ only by matcher collide — acceptable: toggling one toggles both,
118
+ * and identical commands on the same event are in practice the same hook.
119
+ */
120
+ export function pluginHookKey(hook) {
121
+ return `${hook.plugin}:${hook.rawEvent}:${hook.command}`;
122
+ }
112
123
  /**
113
124
  * Walk installed plugins and register their command hooks. Safe to call
114
125
  * multiple times — the caller is responsible for not double-registering
@@ -121,10 +132,16 @@ function pluginNameFromKey(key) {
121
132
  * also suppresses its SessionStart injection — not just its Skill-tool
122
133
  * entries. Without this, `disabledPlugins` only filtered the skill list and
123
134
  * the plugin's hooks still fired regardless.
135
+ *
136
+ * `disabledPluginHooks` is the fine-grained per-hook switch — a list of
137
+ * {@link pluginHookKey} keys (folded from the project's
138
+ * `capabilityOverrides.pluginHooks` by readDisabledLists). A matching hook
139
+ * is skipped while the rest of the plugin keeps working.
124
140
  */
125
- export function loadPluginHooks(registry, disabledPlugins = []) {
141
+ export function loadPluginHooks(registry, disabledPlugins = [], disabledPluginHooks = []) {
126
142
  const data = readInstalledPlugins();
127
143
  const disabledSet = new Set(disabledPlugins);
144
+ const disabledHookSet = new Set(disabledPluginHooks);
128
145
  for (const [key, entries] of Object.entries(data.plugins)) {
129
146
  if (disabledSet.has(pluginNameFromKey(key)))
130
147
  continue;
@@ -150,6 +167,13 @@ export function loadPluginHooks(registry, disabledPlugins = []) {
150
167
  if (cmd.type !== "command" || typeof cmd.command !== "string") {
151
168
  continue;
152
169
  }
170
+ if (disabledHookSet.has(pluginHookKey({
171
+ plugin: pluginNameFromKey(key),
172
+ rawEvent: eventNameRaw,
173
+ command: cmd.command,
174
+ }))) {
175
+ continue;
176
+ }
153
177
  const commandLine = cmd.command;
154
178
  const timeoutMs = cmd.timeout_ms;
155
179
  const matcher = group.matcher;
@@ -170,3 +194,51 @@ export function loadPluginHooks(registry, disabledPlugins = []) {
170
194
  }
171
195
  }
172
196
  }
197
+ /**
198
+ * Read-only counterpart to {@link loadPluginHooks}: scan installed plugins and
199
+ * RETURN their command hooks (instead of registering them), so the settings
200
+ * "钩子" page can show plugin-provided hooks alongside the user's hand-written
201
+ * ones, labelled by owner plugin. `disabledPlugins` doesn't filter the list —
202
+ * disabled plugins are still listed but flagged `disabled:true` (so the UI can
203
+ * show "由 xxx 插件提供(已禁用)"); pass it so that flag is accurate.
204
+ */
205
+ export function listPluginHooks(disabledPlugins = []) {
206
+ const data = readInstalledPlugins();
207
+ const disabledSet = new Set(disabledPlugins);
208
+ const out = [];
209
+ for (const [key, entries] of Object.entries(data.plugins)) {
210
+ const plugin = pluginNameFromKey(key);
211
+ const disabled = disabledSet.has(plugin);
212
+ for (const entry of entries) {
213
+ const installPath = entry.installPath;
214
+ if (!installPath || !existsSync(installPath))
215
+ continue;
216
+ const raw = readHooksJson(installPath);
217
+ if (!raw?.hooks)
218
+ continue;
219
+ for (const [eventNameRaw, groups] of Object.entries(raw.hooks)) {
220
+ const mapped = EVENT_NAME_MAP[eventNameRaw];
221
+ if (mapped === null || mapped === undefined)
222
+ continue;
223
+ if (!Array.isArray(groups))
224
+ continue;
225
+ for (const group of groups) {
226
+ for (const cmd of group.hooks ?? []) {
227
+ if (cmd.type !== "command" || typeof cmd.command !== "string")
228
+ continue;
229
+ out.push({
230
+ plugin,
231
+ event: mapped,
232
+ rawEvent: eventNameRaw,
233
+ command: cmd.command,
234
+ matcher: group.matcher,
235
+ disabled,
236
+ key: pluginHookKey({ plugin, rawEvent: eventNameRaw, command: cmd.command }),
237
+ });
238
+ }
239
+ }
240
+ }
241
+ }
242
+ }
243
+ return out;
244
+ }
@@ -29,6 +29,13 @@ export type AddMarketplaceResult = {
29
29
  * an entry into known_marketplaces.json.
30
30
  */
31
31
  export declare function addMarketplace(name: string, source: MarketplaceSource): Promise<AddMarketplaceResult>;
32
+ /**
33
+ * Refreshes a known marketplace: re-pulls its source so the cached
34
+ * marketplace.json reflects upstream (new plugins, version bumps). Reuses
35
+ * addMarketplace's same-source path (git fetch + reset). Returns an error if
36
+ * the marketplace isn't known.
37
+ */
38
+ export declare function refreshMarketplace(name: string): Promise<AddMarketplaceResult>;
32
39
  /**
33
40
  * Removes a marketplace: deletes the cached clone and the manifest entry.
34
41
  * Returns whether anything was actually removed.
@@ -11,6 +11,7 @@ import { homedir } from "node:os";
11
11
  import { gitClone, gitFetchAndReset, githubRepoToCloneUrl } from "./gitOps.js";
12
12
  import { readKnownMarketplaces, upsertKnownMarketplace, removeKnownMarketplace, } from "./knownMarketplaces.js";
13
13
  import { validateMarketplace } from "./schemas.js";
14
+ import { assertSafePluginName } from "./installer/paths.js";
14
15
  function userHome() {
15
16
  return process.env.HOME ?? homedir();
16
17
  }
@@ -18,6 +19,11 @@ export function marketplacesRoot() {
18
19
  return join(userHome(), ".code-shell", "plugins", "marketplaces");
19
20
  }
20
21
  export function marketplaceDir(name) {
22
+ // Single choke point for add/remove/refresh (all derive `dir` from here and
23
+ // then gitClone / rmSync it). Validate `name` as one safe path segment so a
24
+ // `..` / separator / NUL can't escape marketplacesRoot. Normal derived names
25
+ // (lowercase repo tails) are unaffected; reuses the plugin segment guard.
26
+ assertSafePluginName(name);
21
27
  return join(marketplacesRoot(), name);
22
28
  }
23
29
  const CC_MANIFEST_REL = [".claude-plugin", "marketplace.json"];
@@ -118,6 +124,20 @@ export async function addMarketplace(name, source) {
118
124
  upsertKnownMarketplace(name, entry);
119
125
  return { ok: true, name, marketplace: v.value, replaced: Boolean(existing) };
120
126
  }
127
+ /**
128
+ * Refreshes a known marketplace: re-pulls its source so the cached
129
+ * marketplace.json reflects upstream (new plugins, version bumps). Reuses
130
+ * addMarketplace's same-source path (git fetch + reset). Returns an error if
131
+ * the marketplace isn't known.
132
+ */
133
+ export async function refreshMarketplace(name) {
134
+ const known = readKnownMarketplaces();
135
+ const entry = known[name];
136
+ if (!entry) {
137
+ return { ok: false, error: `Unknown marketplace: ${name}` };
138
+ }
139
+ return addMarketplace(name, entry.source);
140
+ }
121
141
  /**
122
142
  * Removes a marketplace: deletes the cached clone and the manifest entry.
123
143
  * Returns whether anything was actually removed.