@cjhyy/code-shell-core 0.5.0-rc.0 → 0.5.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 (283) hide show
  1. package/dist/agent/agent-definition-registry.d.ts +29 -0
  2. package/dist/agent/agent-definition-registry.js +74 -0
  3. package/dist/agent/agent-definition.d.ts +37 -0
  4. package/dist/agent/agent-definition.js +59 -0
  5. package/dist/arena/arena.js +11 -12
  6. package/dist/arena/context/context-tools.js +2 -4
  7. package/dist/arena/context/within-root.d.ts +7 -0
  8. package/dist/arena/context/within-root.js +15 -0
  9. package/dist/arena/iterate/phases/argue.js +8 -2
  10. package/dist/arena/iterate/phases/revise.js +1 -1
  11. package/dist/arena/iterate/phases/tournament.js +3 -3
  12. package/dist/arena/phases/adjudication.js +1 -4
  13. package/dist/arena/phases/build-consensus.js +1 -4
  14. package/dist/arena/phases/cross-review.js +2 -8
  15. package/dist/arena/phases/debate-rounds.js +1 -4
  16. package/dist/arena/phases/participant-research.js +1 -4
  17. package/dist/arena/phases/planning-detail-expansion.js +8 -10
  18. package/dist/arena/planner.js +0 -1
  19. package/dist/arena/providers/docs.d.ts +1 -0
  20. package/dist/arena/providers/docs.js +5 -2
  21. package/dist/arena/providers/repo.d.ts +1 -0
  22. package/dist/arena/providers/repo.js +5 -2
  23. package/dist/arena/strategies/utils.js +36 -3
  24. package/dist/arena/transitions.d.ts +7 -2
  25. package/dist/arena/transitions.js +9 -4
  26. package/dist/arena/types.d.ts +8 -2
  27. package/dist/automation/cron-expr.d.ts +31 -0
  28. package/dist/automation/cron-expr.js +151 -0
  29. package/dist/automation/index.d.ts +41 -0
  30. package/dist/automation/index.js +39 -0
  31. package/dist/automation/runner.d.ts +67 -0
  32. package/dist/automation/runner.js +56 -0
  33. package/dist/automation/scheduler.d.ts +156 -0
  34. package/dist/automation/scheduler.js +546 -0
  35. package/dist/automation/store.d.ts +39 -0
  36. package/dist/automation/store.js +119 -0
  37. package/dist/automation/write-policy.d.ts +34 -0
  38. package/dist/automation/write-policy.js +113 -0
  39. package/dist/automation/write-run.d.ts +51 -0
  40. package/dist/automation/write-run.js +38 -0
  41. package/dist/capability-control/index.d.ts +5 -0
  42. package/dist/capability-control/index.js +3 -0
  43. package/dist/capability-control/overlay.d.ts +49 -0
  44. package/dist/capability-control/overlay.js +89 -0
  45. package/dist/capability-control/project.d.ts +65 -0
  46. package/dist/capability-control/project.js +147 -0
  47. package/dist/capability-control/service.d.ts +70 -0
  48. package/dist/capability-control/service.js +147 -0
  49. package/dist/capability-control/types.d.ts +62 -0
  50. package/dist/capability-control/types.js +17 -0
  51. package/dist/cli/agent-server-stdio.d.ts +12 -1
  52. package/dist/cli/agent-server-stdio.js +118 -18
  53. package/dist/cli/agent-server-tcp.d.ts +20 -0
  54. package/dist/cli/agent-server-tcp.js +120 -0
  55. package/dist/cli/graceful-shutdown.d.ts +27 -0
  56. package/dist/cli/graceful-shutdown.js +30 -0
  57. package/dist/context/compaction.js +23 -15
  58. package/dist/context/manager.d.ts +0 -20
  59. package/dist/context/manager.js +0 -37
  60. package/dist/cron/cron-runtime.d.ts +2 -0
  61. package/dist/cron/cron-runtime.js +2 -0
  62. package/dist/cron/cron-store.d.ts +2 -0
  63. package/dist/cron/cron-store.js +2 -0
  64. package/dist/cron/scheduler.d.ts +5 -32
  65. package/dist/cron/scheduler.js +5 -113
  66. package/dist/data/openrouter-sync.js +1 -1
  67. package/dist/engine/engine.d.ts +256 -16
  68. package/dist/engine/engine.js +856 -200
  69. package/dist/engine/goal.d.ts +42 -0
  70. package/dist/engine/goal.js +52 -0
  71. package/dist/engine/image-compression.d.ts +81 -0
  72. package/dist/engine/image-compression.js +186 -0
  73. package/dist/engine/image-policy.d.ts +152 -0
  74. package/dist/engine/image-policy.js +165 -0
  75. package/dist/engine/reactive-threshold.d.ts +13 -0
  76. package/dist/engine/reactive-threshold.js +17 -0
  77. package/dist/engine/runtime.js +7 -0
  78. package/dist/engine/session-title.d.ts +13 -0
  79. package/dist/engine/session-title.js +34 -0
  80. package/dist/engine/turn-loop.d.ts +29 -8
  81. package/dist/engine/turn-loop.js +498 -316
  82. package/dist/git/parse-log.d.ts +13 -0
  83. package/dist/git/parse-log.js +21 -0
  84. package/dist/git/utils.d.ts +8 -6
  85. package/dist/git/utils.js +57 -26
  86. package/dist/git/worktree.js +33 -18
  87. package/dist/hooks/events.d.ts +23 -1
  88. package/dist/hooks/goal-stop-hook.d.ts +30 -0
  89. package/dist/hooks/goal-stop-hook.js +102 -0
  90. package/dist/hooks/hook-output.d.ts +33 -0
  91. package/dist/hooks/hook-output.js +79 -0
  92. package/dist/hooks/registry.d.ts +7 -0
  93. package/dist/hooks/registry.js +38 -1
  94. package/dist/hooks/shell-runner.d.ts +2 -0
  95. package/dist/hooks/shell-runner.js +43 -2
  96. package/dist/index.d.ts +32 -7
  97. package/dist/index.js +30 -3
  98. package/dist/llm/capabilities/reasoning-control.d.ts +24 -0
  99. package/dist/llm/capabilities/reasoning-control.js +38 -0
  100. package/dist/llm/capabilities/rules.js +33 -3
  101. package/dist/llm/capabilities/types.d.ts +22 -2
  102. package/dist/llm/clamp-max-tokens.d.ts +11 -0
  103. package/dist/llm/clamp-max-tokens.js +17 -0
  104. package/dist/llm/client-base.d.ts +41 -4
  105. package/dist/llm/client-base.js +67 -13
  106. package/dist/llm/client-factory.d.ts +3 -3
  107. package/dist/llm/client-factory.js +2 -2
  108. package/dist/llm/model-pool.d.ts +15 -11
  109. package/dist/llm/model-pool.js +23 -20
  110. package/dist/llm/provider-catalog.d.ts +2 -2
  111. package/dist/llm/providers/anthropic.d.ts +32 -2
  112. package/dist/llm/providers/anthropic.js +147 -8
  113. package/dist/llm/providers/openai.d.ts +11 -2
  114. package/dist/llm/providers/openai.js +253 -52
  115. package/dist/llm/reasoning-setting.d.ts +51 -0
  116. package/dist/llm/reasoning-setting.js +32 -0
  117. package/dist/llm/stop-reason.d.ts +13 -0
  118. package/dist/llm/stop-reason.js +21 -0
  119. package/dist/llm/strip-vision.d.ts +32 -0
  120. package/dist/llm/strip-vision.js +94 -0
  121. package/dist/llm/types.d.ts +5 -6
  122. package/dist/logging/logger.js +18 -4
  123. package/dist/logging/sanitize-messages.d.ts +10 -0
  124. package/dist/logging/sanitize-messages.js +90 -0
  125. package/dist/lsp/client.js +28 -8
  126. package/dist/lsp/manager.js +2 -1
  127. package/dist/lsp/root-path.d.ts +9 -0
  128. package/dist/lsp/root-path.js +12 -0
  129. package/dist/onboarding.js +18 -11
  130. package/dist/plugins/gitOps.d.ts +34 -0
  131. package/dist/plugins/gitOps.js +59 -3
  132. package/dist/plugins/installer/codex/convertAgents.d.ts +7 -0
  133. package/dist/plugins/installer/codex/convertAgents.js +55 -0
  134. package/dist/plugins/installer/codex/convertMcp.d.ts +8 -0
  135. package/dist/plugins/installer/codex/convertMcp.js +45 -0
  136. package/dist/plugins/installer/codex/convertSkills.d.ts +12 -0
  137. package/dist/plugins/installer/codex/convertSkills.js +33 -0
  138. package/dist/plugins/installer/detectFormat.d.ts +2 -0
  139. package/dist/plugins/installer/detectFormat.js +6 -0
  140. package/dist/plugins/installer/install.d.ts +7 -0
  141. package/dist/plugins/installer/install.js +99 -0
  142. package/dist/plugins/installer/installFromSource.d.ts +13 -0
  143. package/dist/plugins/installer/installFromSource.js +45 -0
  144. package/dist/plugins/installer/list.d.ts +9 -0
  145. package/dist/plugins/installer/list.js +30 -0
  146. package/dist/plugins/installer/loadPluginAgents.d.ts +3 -0
  147. package/dist/plugins/installer/loadPluginAgents.js +23 -0
  148. package/dist/plugins/installer/loadPluginMcp.d.ts +7 -0
  149. package/dist/plugins/installer/loadPluginMcp.js +74 -0
  150. package/dist/plugins/installer/parseSource.d.ts +20 -0
  151. package/dist/plugins/installer/parseSource.js +74 -0
  152. package/dist/plugins/installer/paths.d.ts +5 -0
  153. package/dist/plugins/installer/paths.js +25 -0
  154. package/dist/plugins/installer/types.d.ts +49 -0
  155. package/dist/plugins/installer/types.js +27 -0
  156. package/dist/plugins/installer/uninstall.d.ts +2 -0
  157. package/dist/plugins/installer/uninstall.js +14 -0
  158. package/dist/plugins/installer/update.d.ts +14 -0
  159. package/dist/plugins/installer/update.js +65 -0
  160. package/dist/plugins/loadPluginHooks.d.ts +8 -1
  161. package/dist/plugins/loadPluginHooks.js +11 -1
  162. package/dist/plugins/marketplaceManager.d.ts +8 -1
  163. package/dist/plugins/marketplaceManager.js +38 -4
  164. package/dist/plugins/pluginCommandHook.js +34 -0
  165. package/dist/plugins/pluginInstaller.d.ts +21 -0
  166. package/dist/plugins/pluginInstaller.js +128 -29
  167. package/dist/plugins/schemas.js +40 -6
  168. package/dist/plugins/types.d.ts +4 -0
  169. package/dist/preset/index.js +16 -0
  170. package/dist/prompt/composer.d.ts +10 -0
  171. package/dist/prompt/composer.js +29 -4
  172. package/dist/prompt/sections/orchestration.md +8 -0
  173. package/dist/protocol/chat-session-manager.d.ts +8 -1
  174. package/dist/protocol/chat-session-manager.js +10 -0
  175. package/dist/protocol/chat-session.d.ts +17 -0
  176. package/dist/protocol/chat-session.js +28 -0
  177. package/dist/protocol/redact.d.ts +50 -0
  178. package/dist/protocol/redact.js +71 -0
  179. package/dist/protocol/server.d.ts +41 -2
  180. package/dist/protocol/server.js +231 -33
  181. package/dist/protocol/tcp-transport.d.ts +45 -0
  182. package/dist/protocol/tcp-transport.js +74 -0
  183. package/dist/protocol/types.d.ts +23 -0
  184. package/dist/protocol/types.js +6 -0
  185. package/dist/remote/bridge.d.ts +11 -2
  186. package/dist/remote/bridge.js +61 -33
  187. package/dist/run/ArtifactTracker.js +5 -4
  188. package/dist/run/EngineRunner.d.ts +27 -1
  189. package/dist/run/EngineRunner.js +46 -8
  190. package/dist/run/FileRunStore.js +32 -8
  191. package/dist/run/RunLock.d.ts +28 -2
  192. package/dist/run/RunLock.js +49 -7
  193. package/dist/run/RunManager.js +51 -3
  194. package/dist/run/RunQueue.d.ts +1 -0
  195. package/dist/run/RunQueue.js +8 -2
  196. package/dist/run/factory.d.ts +7 -0
  197. package/dist/run/factory.js +1 -0
  198. package/dist/run/index.d.ts +2 -2
  199. package/dist/run/index.js +1 -1
  200. package/dist/run/redirect-target.d.ts +7 -0
  201. package/dist/run/redirect-target.js +13 -0
  202. package/dist/run/types.js +1 -1
  203. package/dist/services/browser-open.d.ts +13 -0
  204. package/dist/services/browser-open.js +16 -0
  205. package/dist/services/dream-consolidation.d.ts +57 -0
  206. package/dist/services/dream-consolidation.js +151 -0
  207. package/dist/services/memory-orchestrator.js +18 -0
  208. package/dist/services/notifier.d.ts +8 -0
  209. package/dist/services/notifier.js +39 -25
  210. package/dist/services/oauth.js +5 -7
  211. package/dist/services/session-memory-sort.d.ts +8 -0
  212. package/dist/services/session-memory-sort.js +9 -0
  213. package/dist/services/session-memory.js +8 -9
  214. package/dist/session/file-history.js +10 -3
  215. package/dist/session/session-manager.d.ts +23 -1
  216. package/dist/session/session-manager.js +73 -2
  217. package/dist/settings/disk-defaults.d.ts +35 -0
  218. package/dist/settings/disk-defaults.js +24 -0
  219. package/dist/settings/manager.d.ts +45 -1
  220. package/dist/settings/manager.js +121 -17
  221. package/dist/settings/manager.test.d.ts +1 -0
  222. package/dist/settings/manager.test.js +73 -0
  223. package/dist/settings/personalization.d.ts +19 -0
  224. package/dist/settings/personalization.js +7 -0
  225. package/dist/settings/schema.d.ts +1163 -126
  226. package/dist/settings/schema.js +126 -10
  227. package/dist/skills/scanner.d.ts +13 -6
  228. package/dist/skills/scanner.js +23 -4
  229. package/dist/tool-system/builtin/add-marketplace.d.ts +12 -0
  230. package/dist/tool-system/builtin/add-marketplace.js +76 -0
  231. package/dist/tool-system/builtin/agent-registry.d.ts +15 -0
  232. package/dist/tool-system/builtin/agent-registry.js +14 -4
  233. package/dist/tool-system/builtin/agent.d.ts +55 -0
  234. package/dist/tool-system/builtin/agent.js +165 -17
  235. package/dist/tool-system/builtin/apply-patch/index.js +22 -3
  236. package/dist/tool-system/builtin/arena.js +6 -7
  237. package/dist/tool-system/builtin/complete-goal.d.ts +25 -0
  238. package/dist/tool-system/builtin/complete-goal.js +45 -0
  239. package/dist/tool-system/builtin/cron.js +53 -9
  240. package/dist/tool-system/builtin/edit.d.ts +2 -1
  241. package/dist/tool-system/builtin/edit.js +6 -1
  242. package/dist/tool-system/builtin/generate-image.d.ts +25 -0
  243. package/dist/tool-system/builtin/generate-image.js +146 -0
  244. package/dist/tool-system/builtin/glob.js +7 -0
  245. package/dist/tool-system/builtin/grep.js +6 -0
  246. package/dist/tool-system/builtin/index.d.ts +18 -1
  247. package/dist/tool-system/builtin/index.js +62 -1
  248. package/dist/tool-system/builtin/notebook-edit.d.ts +2 -1
  249. package/dist/tool-system/builtin/notebook-edit.js +8 -1
  250. package/dist/tool-system/builtin/read.d.ts +2 -1
  251. package/dist/tool-system/builtin/read.js +9 -1
  252. package/dist/tool-system/builtin/skill.js +23 -5
  253. package/dist/tool-system/builtin/sleep.js +9 -3
  254. package/dist/tool-system/builtin/task.js +4 -1
  255. package/dist/tool-system/builtin/update-automation-memory.d.ts +24 -0
  256. package/dist/tool-system/builtin/update-automation-memory.js +60 -0
  257. package/dist/tool-system/builtin/view-image.d.ts +18 -0
  258. package/dist/tool-system/builtin/view-image.js +96 -0
  259. package/dist/tool-system/builtin/web-search.d.ts +4 -1
  260. package/dist/tool-system/builtin/web-search.js +36 -3
  261. package/dist/tool-system/builtin/write.d.ts +2 -1
  262. package/dist/tool-system/builtin/write.js +7 -1
  263. package/dist/tool-system/context.d.ts +43 -0
  264. package/dist/tool-system/executor.d.ts +0 -1
  265. package/dist/tool-system/executor.js +34 -40
  266. package/dist/tool-system/investigation-guard.d.ts +4 -1
  267. package/dist/tool-system/investigation-guard.js +19 -3
  268. package/dist/tool-system/mcp-manager.d.ts +36 -12
  269. package/dist/tool-system/mcp-manager.js +173 -10
  270. package/dist/tool-system/path-policy.d.ts +94 -0
  271. package/dist/tool-system/path-policy.js +279 -0
  272. package/dist/tool-system/permission.js +43 -50
  273. package/dist/tool-system/plan-mode-allowlist.d.ts +21 -0
  274. package/dist/tool-system/plan-mode-allowlist.js +43 -0
  275. package/dist/tool-system/registry.js +12 -1
  276. package/dist/tool-system/validation.d.ts +10 -0
  277. package/dist/tool-system/validation.js +16 -3
  278. package/dist/types.d.ts +90 -15
  279. package/dist/utils/format.js +4 -2
  280. package/dist/utils/lockfile.js +9 -2
  281. package/dist/utils/theme.d.ts +6 -0
  282. package/dist/utils/theme.js +4 -2
  283. package/package.json +2 -1
@@ -21,5 +21,8 @@ export interface ResolvedSearchConfig {
21
21
  * the user where the value came from when debugging.
22
22
  */
23
23
  export declare function resolveSearchConfig(cwd?: string): ResolvedSearchConfig;
24
+ export declare function isWebSearchAvailable(cwd?: string, nowMs?: number): boolean;
24
25
  export declare const webSearchToolDef: ToolDefinition;
25
- export declare function webSearchTool(args: Record<string, unknown>): Promise<string>;
26
+ export declare function webSearchTool(args: Record<string, unknown>, ctx?: {
27
+ cwd?: string;
28
+ }): Promise<string>;
@@ -18,7 +18,11 @@ export function resolveSearchConfig(cwd = process.cwd()) {
18
18
  let settingsKey;
19
19
  let settingsBaseUrl;
20
20
  try {
21
- const merged = new SettingsManager(cwd).get();
21
+ // scope "full" so the USER-level ~/.code-shell/settings.json is read, not
22
+ // just ${cwd}/.code-shell — the search key is typically a personal/global
23
+ // credential. Default "project" scope would miss it (the "No search provider
24
+ // configured" bug, esp. for automation runs rooted at the app dir).
25
+ const merged = new SettingsManager(cwd, "full").get();
22
26
  const search = merged.search ?? {};
23
27
  const p = search.provider;
24
28
  if (p === "serper" || p === "tavily" || p === "searxng")
@@ -68,6 +72,33 @@ export function resolveSearchConfig(cwd = process.cwd()) {
68
72
  return { provider: "searxng", baseUrl: envSearxng, source: "env" };
69
73
  return { provider: "serper", source: "none" };
70
74
  }
75
+ /**
76
+ * Tool-visibility guard: WebSearch is only useful when a search provider is
77
+ * configured. Mirrors the runtime check in the tool itself (source === "none"
78
+ * means no provider).
79
+ *
80
+ * Cached per-cwd with a short TTL: this runs on EVERY message's toolDefs
81
+ * assembly (engine.ts), and resolveSearchConfig reads + parses settings from
82
+ * disk each call. The 1s TTL collapses back-to-back reads while still picking
83
+ * up a newly-configured provider before the user's next message. The execution
84
+ * path (resolveSearchConfig) is intentionally NOT cached — only this boolean.
85
+ */
86
+ const availCache = new Map();
87
+ const AVAIL_TTL_MS = 1000;
88
+ export function isWebSearchAvailable(cwd = process.cwd(), nowMs = Date.now()) {
89
+ const hit = availCache.get(cwd);
90
+ if (hit && nowMs - hit.at < AVAIL_TTL_MS)
91
+ return hit.value;
92
+ let value = false;
93
+ try {
94
+ value = resolveSearchConfig(cwd).source !== "none";
95
+ }
96
+ catch {
97
+ value = false; // unresolved config → treat as unavailable
98
+ }
99
+ availCache.set(cwd, { at: nowMs, value });
100
+ return value;
101
+ }
71
102
  export const webSearchToolDef = {
72
103
  name: "WebSearch",
73
104
  description: "Search the web for information. Returns a list of search results with titles, URLs, and snippets. " +
@@ -84,12 +115,14 @@ export const webSearchToolDef = {
84
115
  required: ["query"],
85
116
  },
86
117
  };
87
- export async function webSearchTool(args) {
118
+ export async function webSearchTool(args, ctx) {
88
119
  const query = args.query;
89
120
  if (!query)
90
121
  return "Error: query is required";
91
122
  const numResults = Math.min(args.num_results || 10, 20);
92
- const config = resolveSearchConfig();
123
+ // Use the run's cwd (e.g. an automation job's project dir) so per-project
124
+ // settings resolve correctly; falls back to process.cwd() when absent.
125
+ const config = resolveSearchConfig(ctx?.cwd);
93
126
  if (config.source === "none") {
94
127
  return "Error: No search provider configured. Open desktop 设置 → 连接 to add credentials, or set SERPER_API_KEY / TAVILY_API_KEY / SEARXNG_URL.";
95
128
  }
@@ -2,5 +2,6 @@
2
2
  * Built-in Write file tool.
3
3
  */
4
4
  import type { ToolDefinition } from "../../types.js";
5
+ import type { ToolContext } from "../context.js";
5
6
  export declare const writeToolDef: ToolDefinition;
6
- export declare function writeTool(args: Record<string, unknown>): Promise<string>;
7
+ export declare function writeTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;
@@ -4,6 +4,7 @@
4
4
  import { writeFile, mkdir } from "node:fs/promises";
5
5
  import { dirname } from "node:path";
6
6
  import { fileCache } from "./file-cache.js";
7
+ import { enforcePathPolicy } from "../path-policy.js";
7
8
  export const writeToolDef = {
8
9
  name: "Write",
9
10
  description: "Write content to a file. Creates the file if it doesn't exist, " +
@@ -17,13 +18,18 @@ export const writeToolDef = {
17
18
  required: ["file_path", "content"],
18
19
  },
19
20
  };
20
- export async function writeTool(args) {
21
+ export async function writeTool(args, ctx) {
21
22
  const filePath = args.file_path;
22
23
  const content = args.content;
23
24
  if (!filePath)
24
25
  return "Error: file_path is required";
25
26
  if (content === undefined)
26
27
  return "Error: content is required";
28
+ // Path policy gate: deny writes to sensitive paths, refuse writes outside
29
+ // workspace until approved. See tool-system/path-policy.ts.
30
+ const blocked = enforcePathPolicy(filePath, "write", ctx?.cwd);
31
+ if (blocked)
32
+ return blocked;
27
33
  try {
28
34
  await mkdir(dirname(filePath), { recursive: true });
29
35
  await writeFile(filePath, content, "utf-8");
@@ -65,6 +65,25 @@ export interface SubAgentSpawnRequest {
65
65
  * gets `agent_start` / `agent_end` markers via `spawner.parentStream`.
66
66
  */
67
67
  streamOverride?: StreamCallback;
68
+ /**
69
+ * Optional ModelPool key for the child Engine's LLM (e.g. "flash").
70
+ * Undefined → child inherits the parent's model (current behavior).
71
+ */
72
+ model?: string;
73
+ /**
74
+ * Optional tool-name allowlist for the child. When set, the child's tool
75
+ * pool is restricted to these names (still minus the nested-agent tools).
76
+ * Undefined → child inherits the parent's full tool set (current behavior).
77
+ */
78
+ toolAllowlist?: string[];
79
+ /**
80
+ * Optional per-call system prompt appended to the child Engine's prompt
81
+ * (the role definition's Markdown body). Undefined → child inherits only
82
+ * the parent's appendSystemPrompt (current behavior).
83
+ */
84
+ appendSystemPrompt?: string;
85
+ /** True for read-only reviewer/researcher children; tunes investigation guard reminders. */
86
+ readOnlySession?: boolean;
68
87
  }
69
88
  export interface SubAgentSpawner {
70
89
  /** Run a sub-agent synchronously and return its text output. */
@@ -97,6 +116,12 @@ export interface ToolContext {
97
116
  askUser?: AskUserFn;
98
117
  /** Sub-agent spawner (Agent tool). Undefined → Agent tool unavailable. */
99
118
  subAgentSpawner?: SubAgentSpawner;
119
+ /**
120
+ * Reusable sub-agent role definitions (loaded from .code-shell/agents/*.md).
121
+ * The Agent tool reads this to resolve `agent_type`. Undefined → only the
122
+ * ephemeral (inline prompt) mode is available.
123
+ */
124
+ agentDefinitions?: import("../agent/agent-definition-registry.js").AgentDefinitionRegistry;
100
125
  /**
101
126
  * True when this Engine is itself a sub-agent. Set from EngineConfig.
102
127
  * The Agent tool refuses to spawn when this is true — runtime check
@@ -150,6 +175,24 @@ export interface ToolContext {
150
175
  * Populated from `settings.disabledSkills` by Engine.run().
151
176
  */
152
177
  disabledSkills?: string[];
178
+ /**
179
+ * Plugin names the user has totally disabled. Every skill whose
180
+ * namespaced name starts with `${pluginName}:` is hidden from the
181
+ * LLM and rejected at dispatch by the skill builtin tool with a
182
+ * distinct "disabled plugin" message. Populated from
183
+ * `settings.disabledPlugins` by Engine.run().
184
+ */
185
+ disabledPlugins?: string[];
186
+ /**
187
+ * Builtin tool names the project's capabilityOverrides marked `off` for
188
+ * this cwd. Engine.run() already HIDES these from the LLM-visible tool
189
+ * list, but the model can still NAME a hidden builtin (hallucinated, or
190
+ * remembered from an earlier turn when it was visible). The executor reads
191
+ * this set and rejects such a call — turning `off` into a real execution
192
+ * gate, not just a prompt-visibility filter. Empty/undefined for
193
+ * sub-agents and no-cwd contexts (same as readBuiltinOverride).
194
+ */
195
+ disabledBuiltins?: Set<string>;
153
196
  }
154
197
  /**
155
198
  * Per-Engine container that produces a fresh ToolContext on demand.
@@ -40,7 +40,6 @@ export declare class ToolExecutor {
40
40
  isConcurrencySafe(toolName: string): boolean;
41
41
  executeSingle(callIn: ToolCall): Promise<ToolResult>;
42
42
  private isReadOnlyBashCommand;
43
- executeAll(calls: ToolCall[]): Promise<ToolResult[]>;
44
43
  /**
45
44
  * Convert tool results into Message entries for the transcript.
46
45
  */
@@ -5,6 +5,7 @@ import { setMaxListeners } from "node:events";
5
5
  import { logger as rootLogger, getCurrentSid } from "../logging/logger.js";
6
6
  import { recordToolCall, recordToolResult } from "../logging/session-recorder.js";
7
7
  import { validateToolArgs } from "./validation.js";
8
+ import { PLAN_MODE_ALLOWED_TOOLS } from "./plan-mode-allowlist.js";
8
9
  // A1 hardening: hooks must never promote a non-`allow` classifier
9
10
  // decision to `allow`. They may otherwise adjust the decision freely
10
11
  // (e.g. tighten `allow` to `deny`/`ask`, or relax `deny` to `ask` to
@@ -83,22 +84,41 @@ export class ToolExecutor {
83
84
  // Local `call` so we can rewrite args via pre_tool_use updatedInput
84
85
  // without mutating the caller's object.
85
86
  let call = callIn;
87
+ // 0. Abort fast-path: if the run signal is already aborted, return a
88
+ // synthetic error result WITHOUT running pre_tool_use hooks, the
89
+ // permission classifier, or the handler. registry.executeTool also checks
90
+ // abort, but only after this method has paid for all the per-tool
91
+ // hook/permission round-trips. For an aborted sub-agent whose turn queued
92
+ // a batch of tools (e.g. 10 Reads), this collapses the whole batch
93
+ // instantly instead of round-tripping each one — part of the sub-agent
94
+ // leak fix that keeps an aborted child from burning work post-abort.
95
+ if (this.signal?.aborted) {
96
+ return {
97
+ id: call.id,
98
+ toolName: call.toolName,
99
+ error: `Tool aborted before execution: ${call.toolName}`,
100
+ isError: true,
101
+ };
102
+ }
103
+ // 0. Capability override: a builtin the project marked `off` is HIDDEN from
104
+ // the model's tool list (engine.ts applyBuiltinOverrideVisibility), but the
105
+ // model can still NAME it (hallucination, or a remembered earlier turn). The
106
+ // registry still holds the tool, so without a gate here it would execute.
107
+ // Reject it the same way plan mode rejects a disallowed tool — return an
108
+ // error result, never run the handler.
109
+ if (this.toolCtx?.disabledBuiltins?.has(call.toolName)) {
110
+ return {
111
+ id: call.id,
112
+ toolName: call.toolName,
113
+ error: `Tool ${call.toolName} is disabled by this project's capability override and cannot be used. Do NOT retry this tool call.`,
114
+ isError: true,
115
+ };
116
+ }
86
117
  // 0. Plan mode: only allow read-only tools — no file writes at all
87
118
  if (this.toolCtx?.planMode) {
88
- const allowedInPlan = new Set([
89
- "EnterPlanMode",
90
- "ExitPlanMode",
91
- "Read",
92
- "Glob",
93
- "Grep",
94
- "WebSearch",
95
- "WebFetch",
96
- "AskUserQuestion",
97
- "Agent",
98
- "ToolSearch",
99
- "TodoWrite",
100
- ]);
101
- if (!allowedInPlan.has(call.toolName)) {
119
+ // Shared with engine.ts's tool-visibility filter so the set the model
120
+ // SEES and the set the executor RUNS can't drift (they had).
121
+ if (!PLAN_MODE_ALLOWED_TOOLS.has(call.toolName)) {
102
122
  if (call.toolName === "Bash" && this.isReadOnlyBashCommand(call.args)) {
103
123
  // Read-only bash is fine
104
124
  }
@@ -447,32 +467,6 @@ export class ToolExecutor {
447
467
  }
448
468
  return false;
449
469
  }
450
- async executeAll(calls) {
451
- const results = [];
452
- // Separate safe (concurrent) and unsafe (sequential) tools
453
- const safe = [];
454
- const unsafe = [];
455
- for (const call of calls) {
456
- const tool = this.registry.getTool(call.toolName);
457
- if (tool?.isConcurrencySafe) {
458
- safe.push(call);
459
- }
460
- else {
461
- unsafe.push(call);
462
- }
463
- }
464
- // Execute safe tools concurrently
465
- if (safe.length > 0) {
466
- const safeResults = await Promise.all(safe.map((c) => this.executeSingle(c)));
467
- results.push(...safeResults);
468
- }
469
- // Execute unsafe tools sequentially
470
- for (const call of unsafe) {
471
- const result = await this.executeSingle(call);
472
- results.push(result);
473
- }
474
- return results;
475
- }
476
470
  /**
477
471
  * Convert tool results into Message entries for the transcript.
478
472
  */
@@ -21,6 +21,7 @@ export interface GuardDecision {
21
21
  block?: string;
22
22
  prepend?: string;
23
23
  }
24
+ export type InvestigationGuardPolicy = "normal" | "soft" | "read-only-review";
24
25
  export declare class InvestigationGuard {
25
26
  private readHistory;
26
27
  private consecutiveReads;
@@ -35,8 +36,10 @@ export declare class InvestigationGuard {
35
36
  * be killed by the guard — it should still get the signal to change
36
37
  * strategy, but the tool call goes through.
37
38
  */
38
- private softMode;
39
+ private policy;
40
+ private readOnlyReviewBudgetReminderShown;
39
41
  setSoftMode(soft: boolean): void;
42
+ setPolicy(policy: InvestigationGuardPolicy): void;
40
43
  preToolCheck(call: ToolCall): GuardDecision | undefined;
41
44
  noteText(text: string | undefined): void;
42
45
  noteToolResult(_call: ToolCall, _result: ToolResult): void;
@@ -38,9 +38,13 @@ export class InvestigationGuard {
38
38
  * be killed by the guard — it should still get the signal to change
39
39
  * strategy, but the tool call goes through.
40
40
  */
41
- softMode = false;
41
+ policy = "normal";
42
+ readOnlyReviewBudgetReminderShown = false;
42
43
  setSoftMode(soft) {
43
- this.softMode = soft;
44
+ this.policy = soft ? "soft" : "normal";
45
+ }
46
+ setPolicy(policy) {
47
+ this.policy = policy;
44
48
  }
45
49
  preToolCheck(call) {
46
50
  const tool = call.toolName;
@@ -68,7 +72,7 @@ export class InvestigationGuard {
68
72
  `Re-reading the same content will not produce new information. ` +
69
73
  `Switch strategy: run a command with side effects (Bash, debug log, repro), make a code change, or ask the user a specific question. ` +
70
74
  `If you genuinely need this content, summarize what you already know about it from prior reads instead of re-fetching.`;
71
- if (this.softMode) {
75
+ if (this.policy !== "normal") {
72
76
  // Soft mode (headless): deliver the same signal as a prepend so the
73
77
  // model still sees it, but allow the tool call to proceed. Hard-
74
78
  // blocking an unattended `code-shell run …` would dead-end the task
@@ -84,6 +88,14 @@ export class InvestigationGuard {
84
88
  `Re-reading rarely yields new information. After this result, change strategy (Bash side-effect, edit, or ask user) rather than reading further.</system-reminder>`);
85
89
  }
86
90
  if (this.consecutiveReads > READ_BUDGET) {
91
+ if (this.policy === "read-only-review") {
92
+ if (!this.readOnlyReviewBudgetReminderShown) {
93
+ this.readOnlyReviewBudgetReminderShown = true;
94
+ prependParts.push(`<system-reminder>Investigation guard: ${this.consecutiveReads} consecutive read-only calls during an explicit read-only review. ` +
95
+ `Continue only if each read is adding new evidence, and surface a concise status update when useful.</system-reminder>`);
96
+ }
97
+ return prependParts.length ? { prepend: prependParts.join("\n") } : undefined;
98
+ }
87
99
  prependParts.push(`<system-reminder>Investigation guard: ${this.consecutiveReads} consecutive read-only calls with no action taken. ` +
88
100
  `Per coding.md, change strategy now — make a code change, run a command with side effects, or ask the user.</system-reminder>`);
89
101
  }
@@ -106,6 +118,10 @@ export class InvestigationGuard {
106
118
  this.turnHasSideEffect = false;
107
119
  if (wasSilent >= SILENT_TURN_BUDGET && turnNumber !== this.lastReminderSilent) {
108
120
  this.lastReminderSilent = turnNumber;
121
+ if (this.policy === "read-only-review") {
122
+ return (`<system-reminder>Investigation guard: you have taken ${wasSilent} consecutive turns of read-only investigation ` +
123
+ `without any text update to the user. Surface a brief status update with what you have confirmed, ruled out, and will inspect next.</system-reminder>`);
124
+ }
109
125
  return (`<system-reminder>Investigation guard: you have taken ${wasSilent} consecutive turns of read-only investigation ` +
110
126
  `without any text update to the user or side-effecting action. ` +
111
127
  `Either surface a status update (what have you ruled out, what's your current hypothesis?), ` +
@@ -7,25 +7,38 @@ import type { Tool as McpTool } from "@modelcontextprotocol/sdk/types.js";
7
7
  import type { MCPServerConfig, RegisteredTool } from "../types.js";
8
8
  import { ToolRegistry } from "./registry.js";
9
9
  /**
10
- * Build the static metadata for a discovered MCP tool.
10
+ * Persist an MCP-returned image to disk and return the textual
11
+ * reference the LLM should see. Images larger than
12
+ * MAX_MCP_IMAGE_BYTES are dropped with a placeholder so a misbehaving
13
+ * MCP server (e.g. a screenshot agent in a loop) can't blow up disk
14
+ * or the context.
11
15
  *
12
- * Policy (Gate 2 / Standard §S3):
13
- * - Default: isConcurrencySafe=false, isReadOnly=false conservative
14
- * safe-by-default for unknown servers that may hold mutable state.
15
- * - Opt-in: when the MCP server explicitly declares
16
- * `annotations.readOnlyHint === true` per the MCP spec, we honour
17
- * that hint and set BOTH isConcurrencySafe and isReadOnly to true,
18
- * enabling parallel execution for provably read-only tools.
19
- * - Anything other than the boolean literal `true`
20
- * (undefined, null, false, "true", missing annotations) stays false.
21
- *
22
- * @internal exported for unit testing without spinning up a real transport.
16
+ * Returns a one-line note like:
17
+ * [mcp-image] server=playwright tool=screenshot saved=/abs/path.png (123 KB)
23
18
  */
19
+ export declare function spillMcpImage(serverName: string, toolName: string, base64: string, mimeType: string, opts?: {
20
+ baseDir?: string;
21
+ now?: () => number;
22
+ }): Promise<string>;
23
+ export declare function wrapMcpOutput(serverName: string, toolName: string, body: string): string;
24
+ export declare function stripInternalToolArgs(args: Record<string, unknown>): Record<string, unknown>;
24
25
  export declare function buildRegisteredTool(serverName: string, tool: McpTool): RegisteredTool;
25
26
  export declare class MCPManager {
26
27
  private readonly toolRegistry;
27
28
  private static instance;
28
29
  private connections;
30
+ /**
31
+ * In-flight connect()s keyed by server name. When the broadcast config
32
+ * reload (server.ts forEachSession → every session's refreshRuntimeConfig)
33
+ * calls connectAll for the SAME `added` server on this ONE shared pool, K
34
+ * concurrent connect(name) calls would each start a fresh handshake because
35
+ * `connections.has(name)` only becomes true AFTER the handshake completes —
36
+ * a thundering herd of duplicate connections racing to set(). Coalescing by
37
+ * name here collapses them to a SINGLE underlying connection; the late
38
+ * callers await the same promise and return. Cleared in finally so a failed
39
+ * connect can be retried later.
40
+ */
41
+ private connecting;
29
42
  constructor(toolRegistry: ToolRegistry);
30
43
  static getInstance(): MCPManager;
31
44
  /**
@@ -34,8 +47,19 @@ export declare class MCPManager {
34
47
  connectAll(servers: Record<string, MCPServerConfig>): Promise<void>;
35
48
  /**
36
49
  * Connect to a single MCP server.
50
+ *
51
+ * Coalesces concurrent calls for the same `name`: an already-connected server
52
+ * returns immediately, and a connect already in flight for this name is
53
+ * awaited rather than restarted (#5 — thundering-herd guard on the shared
54
+ * pool). The actual handshake lives in `performConnect`.
37
55
  */
38
56
  connect(name: string, config: MCPServerConfig): Promise<void>;
57
+ /**
58
+ * Perform the actual handshake + tool discovery for one server. Separated
59
+ * from `connect` so the coalescing/dedup logic stays in one place. Override
60
+ * `connect` (not this) in test doubles that want to count handshakes.
61
+ */
62
+ protected performConnect(name: string, config: MCPServerConfig): Promise<void>;
39
63
  /**
40
64
  * Discover tools from an MCP server and register them.
41
65
  */