@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
@@ -6,7 +6,7 @@
6
6
  * Supports AbortSignal for cascading cancellation.
7
7
  * Supports onStream for real-time output passthrough.
8
8
  */
9
- import { asyncAgentRegistry } from "./agent-registry.js";
9
+ import { asyncAgentRegistry, MAX_BACKGROUND_AGENTS } from "./agent-registry.js";
10
10
  import { createTranscriptTranslator } from "./agent-transcript-translator.js";
11
11
  import { notificationQueue } from "./agent-notifications.js";
12
12
  import { nanoid } from "nanoid";
@@ -32,18 +32,107 @@ function safeEmit(sink, event) {
32
32
  });
33
33
  }
34
34
  }
35
+ /** Preferred default role when the caller omits agent_type but roles exist. */
36
+ const DEFAULT_AGENT_TYPE = "general-purpose";
37
+ /**
38
+ * Resolve an `agent_type` against the role registry into spawn overrides.
39
+ *
40
+ * - Omitted type + non-empty registry → fall back to a configured role
41
+ * ("general-purpose" if present, else the first available) instead of
42
+ * running a nameless ephemeral agent. Relaxed from the earlier "throw when
43
+ * agent_type omitted": the model habitually omits it, and a hard error just
44
+ * turned every spawn into a failure. Falling back to a real role keeps the
45
+ * tool-allowlist / system-prompt benefits without breaking the call.
46
+ * - Omitted type + empty registry → empty overrides (true ephemeral; nothing
47
+ * to fall back to).
48
+ * - Unknown explicit type → throw, so the LLM gets a clear correction rather
49
+ * than silently running a generic agent.
50
+ */
51
+ export function resolveAgentTypeOverrides(agentType, registry) {
52
+ const available = registry?.list().map((d) => d.name) ?? [];
53
+ let resolvedType = agentType;
54
+ if (!resolvedType) {
55
+ if (available.length === 0)
56
+ return {};
57
+ resolvedType = available.includes(DEFAULT_AGENT_TYPE) ? DEFAULT_AGENT_TYPE : available[0];
58
+ }
59
+ const def = registry?.get(resolvedType);
60
+ if (!def) {
61
+ const list = available.join(", ") || "(none defined)";
62
+ throw new Error(`unknown agent_type '${resolvedType}'. Available: ${list}`);
63
+ }
64
+ return {
65
+ resolvedType,
66
+ model: def.model,
67
+ maxTurns: def.maxTurns,
68
+ toolAllowlist: def.tools,
69
+ appendSystemPrompt: def.systemPrompt,
70
+ };
71
+ }
72
+ /**
73
+ * Render the "Available agent types" block injected into the Agent tool's
74
+ * description, listing the roles defined in .code-shell/agents/*.md so the
75
+ * model knows it can pass `agent_type` instead of hand-rolling an ad-hoc
76
+ * agent. Without this the model never sees the registry (it lives per-engine,
77
+ * not in the static tool def) and falls back to nameless ephemeral agents —
78
+ * see the Core A/B/C incident. Returns "" when no roles are defined, so the
79
+ * base description is left untouched.
80
+ */
81
+ export function buildAgentTypesBlock(registry) {
82
+ const defs = registry?.list() ?? [];
83
+ if (defs.length === 0)
84
+ return "";
85
+ const lines = defs.map((d) => {
86
+ const tools = d.tools && d.tools.length > 0 ? d.tools.join(", ") : "all parent tools";
87
+ return `- ${d.name}: ${d.description} (tools: ${tools})`;
88
+ });
89
+ return [
90
+ "",
91
+ "Available agent types (pass one as `agent_type` to reuse its role, tool allowlist, and turn cap):",
92
+ ...lines,
93
+ "Pass the closest matching agent_type (e.g. read-only investigation → researcher/explorer, planning → planner, full multi-step work → general-purpose). If you omit agent_type it defaults to general-purpose (or the first available role) — passing an explicit one is preferred.",
94
+ ].join("\n");
95
+ }
96
+ /**
97
+ * Produce an Agent tool definition whose description ends with the live
98
+ * available-agent-types listing. Pure: takes the registry, returns a new def
99
+ * (the base `agentToolDef` const is never mutated). When no roles exist the
100
+ * base def is returned unchanged.
101
+ */
102
+ export function agentToolDefWithTypes(registry) {
103
+ const block = buildAgentTypesBlock(registry);
104
+ if (!block)
105
+ return agentToolDef;
106
+ return { ...agentToolDef, description: agentToolDef.description + "\n" + block };
107
+ }
108
+ /**
109
+ * Emit a sub-agent lifecycle event via the existing `notification` hook,
110
+ * tagged with a `kind`. No-op when hooks are absent. Fire-and-forget: emit is
111
+ * async, we deliberately `void` it so bookkeeping never blocks on a handler
112
+ * (mirrors the background-completion notification below).
113
+ */
114
+ export function emitSubAgentHook(hooks, kind, payload) {
115
+ void hooks?.emit("notification", { kind, ...payload });
116
+ }
35
117
  export const agentToolDef = {
36
118
  name: "Agent",
37
- description: "Launch a sub-agent to handle a complex task autonomously. " +
38
- "The sub-agent has access to the same tools and runs independently. " +
39
- "Use this for tasks that can be delegated, parallelized, or require deep exploration. " +
40
- "Provide a clear, complete description of what the agent should do.\n\n" +
41
- "When you launch multiple agents for independent work, send them in a single " +
42
- "message with multiple tool uses so they run concurrently.\n\n" +
43
- "You can optionally run agents in the background using the run_in_background " +
44
- "parameter. When an agent runs in the background, you will be automatically " +
45
- "notified when it completes do NOT sleep, poll, or proactively check on its " +
46
- "progress. Continue with other work or respond to the user instead.",
119
+ description: "Launch a sub-agent to handle a task in its own clean, isolated context. " +
120
+ "The sub-agent runs independently with access to the same tools, and its final " +
121
+ "report is the ONLY thing returned to you — it is stateless, so you cannot send " +
122
+ "follow-up messages. Write a complete, self-contained task description.\n\n" +
123
+ "PRIMARY USE context isolation: when a task needs to read many files or run a " +
124
+ "broad investigation but you only need the conclusion, delegate it. The sub-agent " +
125
+ "absorbs the noisy intermediate output in its own context; you keep the answer, not " +
126
+ "the file dumps. This protects your main context from being flooded.\n\n" +
127
+ "DON'T use this for a quick lookup where you know the file/symbol and expect a few " +
128
+ "matches use Read/Grep/Glob directly instead; spawning an agent wastes a turn.\n\n" +
129
+ "Parallel fan-out is the EXCEPTION: only launch several agents in one message when " +
130
+ "the work truly splits into independent pieces with no shared state. Prefer one " +
131
+ "well-scoped delegation over a swarm.\n\n" +
132
+ "You can optionally run an agent in the background using the run_in_background " +
133
+ "parameter. When it runs in the background, you will be automatically notified when " +
134
+ "it completes — do NOT sleep, poll, or proactively check on its progress. Continue " +
135
+ "with other work or respond to the user instead.",
47
136
  inputSchema: {
48
137
  type: "object",
49
138
  properties: {
@@ -53,6 +142,15 @@ export const agentToolDef = {
53
142
  "Shown in the agent dock to identify what kind of work this sub-agent is doing. " +
54
143
  "Keep it 1-2 words. Defaults to 'Agent' if omitted.",
55
144
  },
145
+ agent_type: {
146
+ type: "string",
147
+ description: "Optional reusable role defined in .code-shell/agents/*.md (e.g. 'researcher'). " +
148
+ "Loads that role's model, tool allowlist, turn cap, and system prompt. " +
149
+ "Disabled roles are not available. If you pass an unknown role you'll get " +
150
+ "an error listing the currently available roles. " +
151
+ "If omitted, defaults to a configured role (general-purpose or the first available); " +
152
+ "with no roles configured it runs an ephemeral agent. Passing an explicit role is preferred.",
153
+ },
56
154
  description: {
57
155
  type: "string",
58
156
  description: "A short (3-5 word) description of the task",
@@ -97,16 +195,18 @@ uiStream,
97
195
  * sub-agents.
98
196
  */
99
197
  streamOverride) {
100
- const { agentId, name, description } = opts;
198
+ const { agentId, name, description, agentType } = opts;
101
199
  const startEndSink = uiStream ?? spawner.parentStream;
102
- safeEmit(startEndSink, { type: "agent_start", agentId, name, description });
200
+ safeEmit(startEndSink, { type: "agent_start", agentId, name, description, agentType });
201
+ emitSubAgentHook(opts.hooks, "subagent_start", { agentId, description });
103
202
  // `resetPlanMode` / `restorePlanMode` operated on a module-level singleton
104
203
  // that no longer exists. The child Engine is a fresh instance; plan-mode
105
204
  // isolation between parent and child is enforced via separate Engine
106
205
  // instances (finalized in T6).
107
206
  const text = await spawner.spawn({ ...opts, streamOverride });
108
207
  const finalText = text || `Agent completed but produced no text output.`;
109
- safeEmit(startEndSink, { type: "agent_end", agentId, name, description, text: finalText });
208
+ safeEmit(startEndSink, { type: "agent_end", agentId, name, description, text: finalText, agentType });
209
+ emitSubAgentHook(opts.hooks, "subagent_finish", { agentId, description, text: finalText });
110
210
  return finalText;
111
211
  }
112
212
  export async function agentTool(args, ctx) {
@@ -132,7 +232,15 @@ export async function agentTool(args, ctx) {
132
232
  if (parentSignal?.aborted) {
133
233
  return "Agent aborted before starting.";
134
234
  }
135
- const maxTurns = args.max_turns || 15;
235
+ const agentType = args.agent_type?.trim() || undefined;
236
+ let overrides;
237
+ try {
238
+ overrides = resolveAgentTypeOverrides(agentType, ctx?.agentDefinitions);
239
+ }
240
+ catch (err) {
241
+ return `Error: ${err.message}`;
242
+ }
243
+ const maxTurns = args.max_turns || overrides.maxTurns || 15;
136
244
  const runInBackground = args.run_in_background === true;
137
245
  const agentId = nanoid(8);
138
246
  const parentStream = spawner.parentStream;
@@ -142,11 +250,19 @@ export async function agentTool(args, ctx) {
142
250
  // (background agents survive the spawning turn). Cancellation goes
143
251
  // through AgentCancel(agent_id).
144
252
  if (runInBackground) {
253
+ if (asyncAgentRegistry.runningCount() >= MAX_BACKGROUND_AGENTS) {
254
+ return `Error: too many background agents running (limit ${MAX_BACKGROUND_AGENTS}). ` +
255
+ `Wait for some to finish or cancel one with AgentCancel(agent_id) before launching more.`;
256
+ }
145
257
  const controller = new AbortController();
146
258
  asyncAgentRegistry.register({
147
259
  agentId,
148
260
  name,
261
+ agentType: overrides.resolvedType,
149
262
  description,
263
+ // Tag with the spawning session so the parent Engine.run waits only on
264
+ // its own background agents (hasRunningForSession).
265
+ sessionId: ctx?.sessionId,
150
266
  status: "running",
151
267
  startedAt: Date.now(),
152
268
  abort: () => controller.abort(),
@@ -169,8 +285,14 @@ export async function agentTool(args, ctx) {
169
285
  agentId,
170
286
  name,
171
287
  description,
288
+ agentType: overrides.resolvedType,
172
289
  prompt,
173
290
  maxTurns,
291
+ model: overrides.model,
292
+ toolAllowlist: overrides.toolAllowlist,
293
+ appendSystemPrompt: overrides.appendSystemPrompt,
294
+ readOnlySession: overrides.resolvedType === "researcher" || overrides.resolvedType === "explorer",
295
+ hooks: ctx?.hooks,
174
296
  signal: controller.signal,
175
297
  }, parentStream, // uiStream: agent_start/end → main feed
176
298
  transcriptSink)
@@ -262,22 +384,48 @@ export async function agentTool(args, ctx) {
262
384
  ].join("\n");
263
385
  }
264
386
  // ─── Synchronous path ──────────────────────────────────────────
387
+ // No wall-clock timeout on the sub-agent lifecycle — that matches Claude
388
+ // Code / Codex, where a sub-agent (Task) is bounded by maxTurns + per-tool
389
+ // timeouts (Bash etc. carry their own) + parent/user abort, NOT by a global
390
+ // countdown that kills legitimate heavy work mid-task. The old
391
+ // runWithTimeout(5min) both (a) murdered a "read 40 files" agent at 5:00 and
392
+ // (b) raced with the normal completion path: the timeout aborted the child,
393
+ // the catch emitted agent_end{error}, AND the aborted child still let
394
+ // runSubAgent reach its line-280 agent_end{text} — two agent_end events for
395
+ // one agent, the later text one overwriting the error so a timed-out agent
396
+ // rendered as "done". Dropping the timeout removes the race entirely: a
397
+ // genuine spawn error throws BEFORE the success emit, so only the catch
398
+ // fires; a clean run emits agent_end{text} exactly once. (fix:
399
+ // subagent-timeout-double-agent-end)
400
+ const syncController = new AbortController();
401
+ const onParentAbort = () => syncController.abort();
402
+ parentSignal?.addEventListener("abort", onParentAbort, { once: true });
265
403
  try {
266
404
  return await runSubAgent(spawner, {
267
405
  agentId,
268
406
  name,
269
407
  description,
408
+ agentType: overrides.resolvedType,
270
409
  prompt,
271
410
  maxTurns,
272
- signal: parentSignal ?? new AbortController().signal,
411
+ model: overrides.model,
412
+ toolAllowlist: overrides.toolAllowlist,
413
+ appendSystemPrompt: overrides.appendSystemPrompt,
414
+ readOnlySession: overrides.resolvedType === "researcher" || overrides.resolvedType === "explorer",
415
+ hooks: ctx?.hooks,
416
+ signal: syncController.signal,
273
417
  });
274
418
  }
275
419
  catch (err) {
276
- safeEmit(parentStream, { type: "agent_end", agentId, name, description, error: err.message });
420
+ emitSubAgentHook(ctx?.hooks, "subagent_error", { agentId, description, error: err.message });
421
+ safeEmit(parentStream, { type: "agent_end", agentId, name, description, error: err.message, agentType: overrides.resolvedType });
277
422
  if (parentSignal?.aborted)
278
423
  return "Agent was aborted.";
279
424
  return `Agent error: ${err.message}`;
280
425
  }
426
+ finally {
427
+ parentSignal?.removeEventListener("abort", onParentAbort);
428
+ }
281
429
  }
282
430
  // ─── AgentStatus / AgentCancel — companions to run_in_background ─
283
431
  export const agentStatusToolDef = {
@@ -17,6 +17,8 @@
17
17
  import { fileCache } from "../file-cache.js";
18
18
  import { applyPatch } from "./applier.js";
19
19
  import { parsePatch } from "./parser.js";
20
+ import { enforcePathPolicy } from "../../path-policy.js";
21
+ import { resolve as resolvePath } from "node:path";
20
22
  export const applyPatchToolDef = {
21
23
  name: "ApplyPatch",
22
24
  description: "Apply a V4A-format patch atomically across one or more files. " +
@@ -68,6 +70,20 @@ export async function applyPatchTool(args, ctx) {
68
70
  // A4: resolve relative patch paths against the Engine's cwd, not the
69
71
  // host process cwd. See standard §S5.
70
72
  const cwd = ctx?.cwd ?? process.cwd();
73
+ // Path-policy gate every target before any IO. A multi-file patch is
74
+ // atomic — if *any* target is blocked the whole patch must reject so we
75
+ // don't end up writing some files and refusing others.
76
+ for (const hunk of parsed.hunks) {
77
+ const targets = [resolvePath(cwd, hunk.path)];
78
+ if (hunk.kind === "update" && hunk.movePath) {
79
+ targets.push(resolvePath(cwd, hunk.movePath));
80
+ }
81
+ for (const t of targets) {
82
+ const blocked = enforcePathPolicy(t, "write", cwd);
83
+ if (blocked)
84
+ return blocked;
85
+ }
86
+ }
71
87
  let result;
72
88
  try {
73
89
  result = await applyPatch(parsed.hunks, { cwd });
@@ -75,11 +91,14 @@ export async function applyPatchTool(args, ctx) {
75
91
  catch (err) {
76
92
  return `Error applying patch: ${err.message}`;
77
93
  }
78
- // Invalidate file cache for every touched path.
94
+ // Invalidate file cache for every touched path. The cache is keyed by
95
+ // ABSOLUTE path (see file-cache.ts), and patch paths are relative to the
96
+ // Engine cwd — resolve them the same way the path-policy gate did above, or
97
+ // the stale entry survives and a later Read returns pre-patch content.
79
98
  for (const hunk of parsed.hunks) {
80
- fileCache.invalidate(hunk.path);
99
+ fileCache.invalidate(resolvePath(cwd, hunk.path));
81
100
  if (hunk.kind === "update" && hunk.movePath) {
82
- fileCache.invalidate(hunk.movePath);
101
+ fileCache.invalidate(resolvePath(cwd, hunk.movePath));
83
102
  }
84
103
  }
85
104
  const parts = [];
@@ -123,9 +123,8 @@ function resolveParticipant(nameOrPath, llmConfig, pool) {
123
123
  maxTokens: preset.maxOutputTokens,
124
124
  baseUrl: fallbackBaseUrl,
125
125
  apiKey: fallbackApiKey,
126
- temperature: ARENA_TEMPERATURE,
127
- enableStreaming: false,
128
126
  },
127
+ clientDefaults: { temperature: ARENA_TEMPERATURE },
129
128
  };
130
129
  }
131
130
  // (3) Raw model path
@@ -138,9 +137,8 @@ function resolveParticipant(nameOrPath, llmConfig, pool) {
138
137
  maxTokens: getMaxOutputTokens(nameOrPath),
139
138
  baseUrl: fallbackBaseUrl,
140
139
  apiKey: fallbackApiKey,
141
- temperature: ARENA_TEMPERATURE,
142
- enableStreaming: false,
143
140
  },
141
+ clientDefaults: { temperature: ARENA_TEMPERATURE },
144
142
  };
145
143
  }
146
144
  function participantFromPool(entry, llmConfig) {
@@ -156,9 +154,8 @@ function participantFromPool(entry, llmConfig) {
156
154
  maxTokens: entry.maxOutputTokens ?? getMaxOutputTokens(entry.model),
157
155
  baseUrl: entry.baseUrl ?? llmConfig.baseUrl,
158
156
  apiKey: entry.apiKey ?? llmConfig.apiKey,
159
- temperature: ARENA_TEMPERATURE,
160
- enableStreaming: false,
161
157
  },
158
+ clientDefaults: { temperature: ARENA_TEMPERATURE },
162
159
  };
163
160
  }
164
161
  /**
@@ -411,6 +408,8 @@ export async function arenaTool(args, ctx) {
411
408
  catch (err) {
412
409
  if (signal?.aborted)
413
410
  return "Arena aborted.";
414
- return `Arena error: ${err.message}`;
411
+ // err may be a non-Error (string/null); `(err as Error).message` would be
412
+ // undefined → "Arena error: undefined", masking the real cause.
413
+ return `Arena error: ${err instanceof Error ? err.message : String(err)}`;
415
414
  }
416
415
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * complete_goal — Goal mode P0.
3
+ *
4
+ * The model calls this to explicitly DECLARE the current goal complete
5
+ * (mirrors Codex's update_goal(status=Complete)). When the turn loop sees
6
+ * this tool call it short-circuits to a "completed" stop WITHOUT running the
7
+ * judge LLM (wired in Task 4). This tool itself just records an
8
+ * acknowledgement string into the transcript; the loop-level short-circuit
9
+ * lives in turn-loop.ts.
10
+ *
11
+ * Shape note: builtin tools in this codebase are a `ToolDefinition`
12
+ * (name/description/inputSchema only — see types.ts) plus a separate executor
13
+ * `(args: Record<string, unknown>) => Promise<string>`. The runtime metadata
14
+ * (source/permissionDefault/isReadOnly/...) is attached in builtin/index.ts at
15
+ * registration time, NOT on the def. Registration is Task 6.
16
+ */
17
+ import type { ToolDefinition } from "../../types.js";
18
+ /**
19
+ * The tool name, shared with turn-loop.ts's short-circuit check so a rename
20
+ * can't silently break completion detection (the name lives in exactly one
21
+ * place).
22
+ */
23
+ export declare const COMPLETE_GOAL_TOOL_NAME = "complete_goal";
24
+ export declare const completeGoalToolDef: ToolDefinition;
25
+ export declare function completeGoalTool(args: Record<string, unknown>): Promise<string>;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * complete_goal — Goal mode P0.
3
+ *
4
+ * The model calls this to explicitly DECLARE the current goal complete
5
+ * (mirrors Codex's update_goal(status=Complete)). When the turn loop sees
6
+ * this tool call it short-circuits to a "completed" stop WITHOUT running the
7
+ * judge LLM (wired in Task 4). This tool itself just records an
8
+ * acknowledgement string into the transcript; the loop-level short-circuit
9
+ * lives in turn-loop.ts.
10
+ *
11
+ * Shape note: builtin tools in this codebase are a `ToolDefinition`
12
+ * (name/description/inputSchema only — see types.ts) plus a separate executor
13
+ * `(args: Record<string, unknown>) => Promise<string>`. The runtime metadata
14
+ * (source/permissionDefault/isReadOnly/...) is attached in builtin/index.ts at
15
+ * registration time, NOT on the def. Registration is Task 6.
16
+ */
17
+ /**
18
+ * The tool name, shared with turn-loop.ts's short-circuit check so a rename
19
+ * can't silently break completion detection (the name lives in exactly one
20
+ * place).
21
+ */
22
+ export const COMPLETE_GOAL_TOOL_NAME = "complete_goal";
23
+ export const completeGoalToolDef = {
24
+ name: COMPLETE_GOAL_TOOL_NAME,
25
+ description: "声明当前目标已完全达成。仅当目标确实完整完成时才调用本工具(模型在目标完全达成时显式调用以声明完成)。" +
26
+ "调用后应当停止。可选传入 summary 作为一句话完成总结。\n" +
27
+ "Declare the current goal complete. Call this ONLY when the goal is fully achieved. " +
28
+ "After calling, you should stop. Optionally pass a one-line summary of what was accomplished.",
29
+ inputSchema: {
30
+ type: "object",
31
+ properties: {
32
+ summary: {
33
+ type: "string",
34
+ description: "可选:一句话总结所完成的工作。Optional one-line summary of what was accomplished.",
35
+ },
36
+ },
37
+ required: [],
38
+ },
39
+ };
40
+ export async function completeGoalTool(args) {
41
+ const summary = typeof args.summary === "string" ? args.summary.trim() : "";
42
+ return summary
43
+ ? `目标已完成 (goal complete). Summary: ${summary}`
44
+ : "目标已完成 (goal complete).";
45
+ }
@@ -1,17 +1,41 @@
1
1
  /**
2
2
  * Cron tools — CronCreate, CronDelete, CronList.
3
3
  */
4
- import { cronScheduler } from "../../cron/scheduler.js";
4
+ import { cronScheduler } from "../../automation/scheduler.js";
5
5
  export const cronCreateToolDef = {
6
6
  name: "CronCreate",
7
- description: "Create a scheduled recurring task. The task will run automatically at the specified interval. " +
8
- "Schedule format: '30s', '5m', '1h', '1d'.",
7
+ description: "Create a scheduled automation job that runs a prompt on a recurring schedule. " +
8
+ "Use this when the user asks to set up a recurring/automated/scheduled task " +
9
+ "(monitoring, daily reports, periodic checks, etc.).\n\n" +
10
+ "Translate the user's natural-language timing into the `schedule` field. Two forms:\n" +
11
+ " • Interval: '30s', '5m', '1h', '1d' (runs every N from creation).\n" +
12
+ " • Cron expression (5 fields: minute hour day-of-month month day-of-week) for " +
13
+ "calendar times. Examples: '0 9 * * 1-5' = 9am every weekday; '0 */6 * * *' = every 6 hours; " +
14
+ "'30 8 * * 1' = 8:30am every Monday. Day-of-week: 0=Sunday..6=Saturday.\n\n" +
15
+ "For calendar schedules, set `timezone` to the user's IANA zone (e.g. 'Asia/Shanghai', " +
16
+ "'America/New_York'); ask the user if unknown. Set `cwd` to the project the job operates on. " +
17
+ "Leave `permissionLevel` as 'read-only' unless the user explicitly wants the job to modify code.",
9
18
  inputSchema: {
10
19
  type: "object",
11
20
  properties: {
12
- name: { type: "string", description: "Name for the cron job" },
13
- schedule: { type: "string", description: "Interval (e.g. '5m', '1h', '30s')" },
14
- prompt: { type: "string", description: "The task prompt to run on each execution" },
21
+ name: { type: "string", description: "Short human-readable name for the job (e.g. '工作日晨间简报')" },
22
+ schedule: {
23
+ type: "string",
24
+ description: "Interval ('5m','1h','1d') or a 5-field cron expression ('0 9 * * 1-5'). " +
25
+ "Derive this from the user's described timing.",
26
+ },
27
+ prompt: { type: "string", description: "The task prompt the agent runs on each execution" },
28
+ timezone: {
29
+ type: "string",
30
+ description: "IANA timezone for cron-expression schedules (e.g. 'Asia/Shanghai'). Optional; defaults to UTC.",
31
+ },
32
+ cwd: { type: "string", description: "Working directory / project the job runs in. Optional." },
33
+ permissionLevel: {
34
+ type: "string",
35
+ enum: ["read-only", "workspace-write", "full"],
36
+ description: "What the job may do: 'read-only' (monitoring; default), 'workspace-write' (edit files), " +
37
+ "'full' (edit + run git/gh to open PRs). Use the least privilege the task needs.",
38
+ },
15
39
  },
16
40
  required: ["name", "schedule", "prompt"],
17
41
  },
@@ -22,8 +46,27 @@ export async function cronCreateTool(args) {
22
46
  const prompt = args.prompt;
23
47
  if (!name || !schedule || !prompt)
24
48
  return "Error: name, schedule, and prompt are required";
25
- const job = cronScheduler.create(name, schedule, prompt);
26
- return `Cron job #${job.id} "${job.name}" created. Schedule: every ${job.schedule}.`;
49
+ const timezone = typeof args.timezone === "string" ? args.timezone : undefined;
50
+ const cwd = typeof args.cwd === "string" ? args.cwd : undefined;
51
+ const permissionLevel = args.permissionLevel === "read-only" ||
52
+ args.permissionLevel === "workspace-write" ||
53
+ args.permissionLevel === "full"
54
+ ? args.permissionLevel
55
+ : undefined;
56
+ let job;
57
+ try {
58
+ job = cronScheduler.create(name, schedule, prompt, {
59
+ ...(timezone !== undefined ? { timezone } : {}),
60
+ ...(cwd !== undefined ? { cwd } : {}),
61
+ ...(permissionLevel !== undefined ? { permissionLevel } : {}),
62
+ });
63
+ }
64
+ catch (err) {
65
+ return `Error: ${err instanceof Error ? err.message : String(err)}`;
66
+ }
67
+ const tz = job.timezone ? ` (${job.timezone})` : "";
68
+ const next = job.nextRun ? new Date(job.nextRun).toLocaleString() : "n/a";
69
+ return `Cron job #${job.id} "${job.name}" created. Schedule: ${job.schedule}${tz}. Next run: ${next}.`;
27
70
  }
28
71
  export const cronDeleteToolDef = {
29
72
  name: "CronDelete",
@@ -55,7 +98,8 @@ export async function cronListTool(_args) {
55
98
  const lines = jobs.map((j) => {
56
99
  const status = j.enabled ? "active" : "paused";
57
100
  const lastRun = j.lastRun ? new Date(j.lastRun).toLocaleString() : "never";
58
- return ` #${j.id} "${j.name}" [${status}] every ${j.schedule} | runs: ${j.runCount} | last: ${lastRun}`;
101
+ const tz = j.timezone ? ` ${j.timezone}` : "";
102
+ return ` #${j.id} "${j.name}" [${status}] ${j.schedule}${tz} | runs: ${j.runCount} | last: ${lastRun}`;
59
103
  });
60
104
  return `Cron Jobs (${jobs.length}):\n${lines.join("\n")}`;
61
105
  }
@@ -2,5 +2,6 @@
2
2
  * Built-in Edit file tool — exact string replacement.
3
3
  */
4
4
  import type { ToolDefinition } from "../../types.js";
5
+ import type { ToolContext } from "../context.js";
5
6
  export declare const editToolDef: ToolDefinition;
6
- export declare function editTool(args: Record<string, unknown>): Promise<string>;
7
+ export declare function editTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;
@@ -4,6 +4,7 @@
4
4
  import { readFile, writeFile } 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
8
  export const editToolDef = {
8
9
  name: "Edit",
9
10
  description: "Perform exact string replacements in a file. " +
@@ -30,7 +31,7 @@ export const editToolDef = {
30
31
  required: ["file_path", "old_string", "new_string"],
31
32
  },
32
33
  };
33
- export async function editTool(args) {
34
+ export async function editTool(args, ctx) {
34
35
  const filePath = args.file_path;
35
36
  const oldString = args.old_string;
36
37
  const newString = args.new_string;
@@ -45,6 +46,10 @@ export async function editTool(args) {
45
46
  return "Error: old_string and new_string must be different";
46
47
  if (!existsSync(filePath))
47
48
  return `Error: File not found: ${filePath}`;
49
+ // Path policy gate before any IO.
50
+ const blocked = enforcePathPolicy(filePath, "write", ctx?.cwd);
51
+ if (blocked)
52
+ return blocked;
48
53
  try {
49
54
  const content = await readFile(filePath, "utf-8");
50
55
  if (!content.includes(oldString)) {
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Built-in GenerateImage tool — text-to-image via the OpenAI Images API
3
+ * (`gpt-image-2`).
4
+ *
5
+ * Credentials come from config, not env: the tool finds the `kind: "openai"`
6
+ * entry in `settings.providers[]` and uses its `apiKey` + `baseUrl`. No
7
+ * `OPENAI_API_KEY` required, no new config — an existing OpenAI provider is
8
+ * reused as-is.
9
+ *
10
+ * The generated PNG is decoded from the API's base64 payload and written to
11
+ * `<cwd>/.code-shell/generated_images/<timestamp>.png`; the tool returns the
12
+ * absolute path so the model can Read or reference it on later turns. This
13
+ * mirrors codex's imagegen CLI fallback (`scripts/image_gen.py`): call API →
14
+ * take b64_json → decode → write → report path.
15
+ *
16
+ * Scope is deliberately narrow (text-to-image only). Edit/mask/transparent/
17
+ * batch are out of scope — see the design doc.
18
+ *
19
+ * Uses the native `fetch` (codeshell does not depend on the `openai` SDK).
20
+ */
21
+ import type { ToolDefinition } from "../../types.js";
22
+ import type { ToolContext } from "../context.js";
23
+ export declare const generateImageToolDef: ToolDefinition;
24
+ export declare function isGenerateImageAvailable(cwd?: string, nowMs?: number): boolean;
25
+ export declare function generateImageTool(args: Record<string, unknown>, ctx?: ToolContext): Promise<string>;