@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
@@ -0,0 +1,29 @@
1
+ import { type AgentDefinition } from "./agent-definition.js";
2
+ export interface AgentSourceDir {
3
+ dir: string;
4
+ source: "project" | "user" | "plugin";
5
+ }
6
+ /**
7
+ * Loads reusable sub-agent role definitions from one or more directories
8
+ * of `*.md` files. Non-recursive. Malformed files are skipped with a
9
+ * warning rather than failing the whole load — one bad role file must
10
+ * not break the agent system.
11
+ *
12
+ * When multiple dirs define the same role name, the LAST dir wins. This is
13
+ * pure mechanism — the project>user POLICY lives in the caller's dir ORDER
14
+ * (loadAgentDefinitionsForCwd passes [user, ...plugins, project] so a repo's
15
+ * in-tree agent wins; see spec §7.2). The shadowing def is marked
16
+ * `override: true` and records the sources it shadowed in `shadowedSources`.
17
+ * Names in `disabled` are filtered out entirely after the merge, so the LLM
18
+ * never sees them.
19
+ */
20
+ export declare class AgentDefinitionRegistry {
21
+ private defs;
22
+ readonly warnings: string[];
23
+ /** Back-compat single-dir loader (project source, no disabled filter). */
24
+ static loadFromDir(dir: string): AgentDefinitionRegistry;
25
+ static loadFromDirs(dirs: AgentSourceDir[], disabled: string[]): AgentDefinitionRegistry;
26
+ has(name: string): boolean;
27
+ get(name: string): AgentDefinition | undefined;
28
+ list(): AgentDefinition[];
29
+ }
@@ -0,0 +1,74 @@
1
+ import { readdirSync, readFileSync, existsSync, statSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { parseAgentDefinition } from "./agent-definition.js";
4
+ /**
5
+ * Loads reusable sub-agent role definitions from one or more directories
6
+ * of `*.md` files. Non-recursive. Malformed files are skipped with a
7
+ * warning rather than failing the whole load — one bad role file must
8
+ * not break the agent system.
9
+ *
10
+ * When multiple dirs define the same role name, the LAST dir wins. This is
11
+ * pure mechanism — the project>user POLICY lives in the caller's dir ORDER
12
+ * (loadAgentDefinitionsForCwd passes [user, ...plugins, project] so a repo's
13
+ * in-tree agent wins; see spec §7.2). The shadowing def is marked
14
+ * `override: true` and records the sources it shadowed in `shadowedSources`.
15
+ * Names in `disabled` are filtered out entirely after the merge, so the LLM
16
+ * never sees them.
17
+ */
18
+ export class AgentDefinitionRegistry {
19
+ defs = new Map();
20
+ warnings = [];
21
+ /** Back-compat single-dir loader (project source, no disabled filter). */
22
+ static loadFromDir(dir) {
23
+ return AgentDefinitionRegistry.loadFromDirs([{ dir, source: "project" }], []);
24
+ }
25
+ static loadFromDirs(dirs, disabled) {
26
+ const reg = new AgentDefinitionRegistry();
27
+ for (const { dir, source } of dirs) {
28
+ if (!existsSync(dir) || !statSync(dir).isDirectory())
29
+ continue;
30
+ for (const entry of readdirSync(dir).sort()) {
31
+ if (!entry.endsWith(".md"))
32
+ continue;
33
+ const full = join(dir, entry);
34
+ try {
35
+ if (!statSync(full).isFile())
36
+ continue;
37
+ const def = parseAgentDefinition(readFileSync(full, "utf8"), entry);
38
+ def.source = source;
39
+ def.filePath = full;
40
+ // A later dir overriding an earlier one. Carry forward the shadowed
41
+ // sources (the prev def's own source + anything it already shadowed)
42
+ // so a project def that shadows a plugin def that shadowed a user
43
+ // def reports both.
44
+ const prev = reg.defs.get(def.name);
45
+ if (prev) {
46
+ def.override = true;
47
+ const shadowed = new Set(prev.shadowedSources ?? []);
48
+ if (prev.source)
49
+ shadowed.add(prev.source);
50
+ def.shadowedSources = [...shadowed];
51
+ }
52
+ reg.defs.set(def.name, def);
53
+ }
54
+ catch (err) {
55
+ reg.warnings.push(`${entry}: ${err.message}`);
56
+ }
57
+ }
58
+ }
59
+ // Filter disabled roles after the merge so a user override of a
60
+ // disabled name is still removed.
61
+ for (const name of new Set(disabled))
62
+ reg.defs.delete(name);
63
+ return reg;
64
+ }
65
+ has(name) {
66
+ return this.defs.has(name);
67
+ }
68
+ get(name) {
69
+ return this.defs.get(name);
70
+ }
71
+ list() {
72
+ return [...this.defs.values()];
73
+ }
74
+ }
@@ -0,0 +1,37 @@
1
+ /** A reusable sub-agent role, loaded from a Markdown file. */
2
+ export interface AgentDefinition {
3
+ /** Unique role key, e.g. "researcher". Matched against Agent({ agent_type }). */
4
+ name: string;
5
+ /** Human-facing summary of when to use this role. */
6
+ description: string;
7
+ /** Optional ModelPool key (e.g. "flash"). Undefined → inherit parent model. */
8
+ model?: string;
9
+ /** Optional turn cap for this role. Undefined → caller/default decides. */
10
+ maxTurns?: number;
11
+ /** Optional tool allowlist. Undefined → inherit parent's full tool set. */
12
+ tools?: string[];
13
+ /** Markdown body — becomes the child Engine's appendSystemPrompt. */
14
+ systemPrompt: string;
15
+ /** Where this def was loaded from. Runtime-only; never serialized. */
16
+ source?: "project" | "user" | "plugin";
17
+ /** Absolute path of the file it came from. Runtime-only. */
18
+ filePath?: string;
19
+ /** True when this def shadows a same-named def from a lower-priority dir. */
20
+ override?: boolean;
21
+ /** Sources whose same-named def this one shadows (runtime-only). Drives the
22
+ * UI "this project overrides your user version" warning (spec §7.2). */
23
+ shadowedSources?: Array<"project" | "user" | "plugin">;
24
+ }
25
+ /**
26
+ * Parse a Markdown agent-definition file (YAML frontmatter + body).
27
+ * Pure: no filesystem access. `sourceName` is only used in error messages.
28
+ */
29
+ export declare function parseAgentDefinition(raw: string, sourceName: string): AgentDefinition;
30
+ /**
31
+ * Serialize an AgentDefinition back to a Markdown file body (YAML
32
+ * frontmatter + system prompt). Inverse of parseAgentDefinition.
33
+ * Optional fields that are unset are omitted entirely so an inheriting
34
+ * role (e.g. model undefined → inherit parent) stays clean on disk.
35
+ * Runtime-only metadata (source/filePath/override) is never written.
36
+ */
37
+ export declare function serializeAgentDefinition(def: AgentDefinition): string;
@@ -0,0 +1,59 @@
1
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
2
+ /**
3
+ * Parse a Markdown agent-definition file (YAML frontmatter + body).
4
+ * Pure: no filesystem access. `sourceName` is only used in error messages.
5
+ */
6
+ export function parseAgentDefinition(raw, sourceName) {
7
+ const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(raw.trim());
8
+ if (!match) {
9
+ throw new Error(`${sourceName}: missing YAML frontmatter (expected leading '---' block)`);
10
+ }
11
+ const [, yamlSrc, body] = match;
12
+ let fm;
13
+ try {
14
+ fm = (parseYaml(yamlSrc) ?? {});
15
+ }
16
+ catch (err) {
17
+ throw new Error(`${sourceName}: invalid YAML frontmatter — ${err.message}`, { cause: err });
18
+ }
19
+ if (typeof fm.name !== "string" || fm.name.trim().length === 0) {
20
+ throw new Error(`${sourceName}: frontmatter must include a non-empty 'name'`);
21
+ }
22
+ if (typeof fm.description !== "string" || fm.description.trim().length === 0) {
23
+ throw new Error(`${sourceName}: frontmatter must include a non-empty 'description'`);
24
+ }
25
+ const def = {
26
+ name: fm.name.trim(),
27
+ description: fm.description.trim(),
28
+ systemPrompt: body.trim(),
29
+ };
30
+ if (typeof fm.model === "string" && fm.model.trim())
31
+ def.model = fm.model.trim();
32
+ if (typeof fm.maxTurns === "number")
33
+ def.maxTurns = fm.maxTurns;
34
+ if (Array.isArray(fm.tools)) {
35
+ def.tools = fm.tools.filter((t) => typeof t === "string");
36
+ }
37
+ return def;
38
+ }
39
+ /**
40
+ * Serialize an AgentDefinition back to a Markdown file body (YAML
41
+ * frontmatter + system prompt). Inverse of parseAgentDefinition.
42
+ * Optional fields that are unset are omitted entirely so an inheriting
43
+ * role (e.g. model undefined → inherit parent) stays clean on disk.
44
+ * Runtime-only metadata (source/filePath/override) is never written.
45
+ */
46
+ export function serializeAgentDefinition(def) {
47
+ const fm = {
48
+ name: def.name,
49
+ description: def.description,
50
+ };
51
+ if (def.model !== undefined)
52
+ fm.model = def.model;
53
+ if (def.maxTurns !== undefined)
54
+ fm.maxTurns = def.maxTurns;
55
+ if (def.tools !== undefined)
56
+ fm.tools = def.tools;
57
+ const yaml = stringifyYaml(fm).trimEnd();
58
+ return `---\n${yaml}\n---\n${def.systemPrompt}\n`;
59
+ }
@@ -28,6 +28,15 @@ import { runDetailExpansion } from "./phases/planning-detail-expansion.js";
28
28
  import { withLanguage } from "./strategies/language-wrapper.js";
29
29
  import { ArenaLedger } from "./ledger.js";
30
30
  import { registerClaims } from "./phases/claim-registry.js";
31
+ /** Bucket claims by status. Shared by the two consensus phases. */
32
+ function summarizeClaims(all) {
33
+ return {
34
+ verified: all.filter((c) => c.status === "verified"),
35
+ contested: all.filter((c) => c.status === "contested"),
36
+ unresolved: all.filter((c) => c.status === "unresolved"),
37
+ rejected: all.filter((c) => c.status === "rejected"),
38
+ };
39
+ }
31
40
  export class Arena {
32
41
  config;
33
42
  strategy;
@@ -192,12 +201,7 @@ export class Arena {
192
201
  signal?.throwIfAborted();
193
202
  // ─── Roadmap Consensus ───────────────────────────────────
194
203
  const allClaims = ledger.getAllClaims();
195
- const claimSummary = {
196
- verified: allClaims.filter((c) => c.status === "verified"),
197
- contested: allClaims.filter((c) => c.status === "contested"),
198
- unresolved: allClaims.filter((c) => c.status === "unresolved"),
199
- rejected: allClaims.filter((c) => c.status === "rejected"),
200
- };
204
+ const claimSummary = summarizeClaims(allClaims);
201
205
  logger.info("arena.planning_consensus_phase", { concluder: concluder.name });
202
206
  const consensus = await buildConsensus({
203
207
  concluder,
@@ -295,12 +299,7 @@ export class Arena {
295
299
  signal?.throwIfAborted();
296
300
  // ─── Consensus (claim-aware) ─────────────────────────────
297
301
  const allClaims = ledger.getAllClaims();
298
- const claimSummary = {
299
- verified: allClaims.filter((c) => c.status === "verified"),
300
- contested: allClaims.filter((c) => c.status === "contested"),
301
- unresolved: allClaims.filter((c) => c.status === "unresolved"),
302
- rejected: allClaims.filter((c) => c.status === "rejected"),
303
- };
302
+ const claimSummary = summarizeClaims(allClaims);
304
303
  logger.info("arena.consensus_phase", {
305
304
  concluder: concluder.name,
306
305
  verified: claimSummary.verified.length,
@@ -9,6 +9,7 @@
9
9
  import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
10
10
  import { execFileSync } from "node:child_process";
11
11
  import { resolve, join } from "node:path";
12
+ import { isWithinRoot } from "./within-root.js";
12
13
  export const MAX_TOOL_RESULT = 15_000; // chars per tool call
13
14
  // 5 was too generous for thinking-mode models — they'd spend the
14
15
  // entire budget on read_file with textLen=0, then need an
@@ -112,10 +113,7 @@ export function executeContextTool(tc) {
112
113
  */
113
114
  function validatePath(filePath) {
114
115
  const resolved = resolve(filePath);
115
- if (resolved === REPO_ROOT || resolved.startsWith(REPO_ROOT + "/")) {
116
- return resolved;
117
- }
118
- return null;
116
+ return isWithinRoot(REPO_ROOT, resolved) ? resolved : null;
119
117
  }
120
118
  function executeReadFile(args) {
121
119
  const filePath = args.path;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * True when `target` is the root itself or a descendant of it. Uses the
3
+ * platform separator (the old check hardcoded "/", which broke on Windows)
4
+ * and avoids the sibling-prefix trap (/repo vs /repo-evil). Both inputs are
5
+ * assumed already resolved to absolute paths.
6
+ */
7
+ export declare function isWithinRoot(root: string, target: string): boolean;
@@ -0,0 +1,15 @@
1
+ import * as path from "node:path";
2
+ /**
3
+ * True when `target` is the root itself or a descendant of it. Uses the
4
+ * platform separator (the old check hardcoded "/", which broke on Windows)
5
+ * and avoids the sibling-prefix trap (/repo vs /repo-evil). Both inputs are
6
+ * assumed already resolved to absolute paths.
7
+ */
8
+ export function isWithinRoot(root, target) {
9
+ if (target === root)
10
+ return true;
11
+ const rel = path.relative(root, target);
12
+ // Outside the root iff the relative path climbs out ("..") or is absolute
13
+ // (different drive on Windows).
14
+ return rel !== "" && !rel.startsWith("..") && !path.isAbsolute(rel);
15
+ }
@@ -61,7 +61,7 @@ export async function runArgueRound(args) {
61
61
  // ─── Single-shot argue (no tools) ─────────────────────────────────────
62
62
  async function argueSingleShot(args) {
63
63
  const { critic, subject, format, draft, idPrefix, signal } = args;
64
- const client = await createLLMClient({ ...critic.llm, enableStreaming: false });
64
+ const client = await createLLMClient(critic.llm, critic.clientDefaults);
65
65
  const resp = await client.createMessage({
66
66
  systemPrompt: format.argueSystem(format.format),
67
67
  messages: [{ role: "user", content: format.argueUser(subject, draft) }],
@@ -78,7 +78,7 @@ async function argueSingleShot(args) {
78
78
  // ─── Tool-use argue (with web search) ─────────────────────────────────
79
79
  async function argueWithToolLoop(args) {
80
80
  const { critic, subject, format, draft, idPrefix, maxToolRounds, signal } = args;
81
- const client = await createLLMClient({ ...critic.llm, enableStreaming: false });
81
+ const client = await createLLMClient(critic.llm, critic.clientDefaults);
82
82
  const messages = [
83
83
  { role: "user", content: format.argueUser(subject, draft) },
84
84
  ];
@@ -149,6 +149,12 @@ async function argueWithToolLoop(args) {
149
149
  });
150
150
  finalText = resp.text;
151
151
  }
152
+ if (!finalText) {
153
+ // Both the tool loop and the force-conclude fallback produced no text
154
+ // (e.g. repeated max-token truncation). Log it — parseCritiquesResponse
155
+ // will just yield no critiques rather than silently parsing "".
156
+ logger.warn("arena.iterate.argue_empty_response", { critic: critic.name, toolRounds });
157
+ }
152
158
  const critiques = parseCritiquesResponse(finalText, critic.name, idPrefix);
153
159
  logger.info("arena.iterate.argue_toolloop", {
154
160
  critic: critic.name,
@@ -7,7 +7,7 @@ import { parseReviseResponse } from "../parse.js";
7
7
  export async function runRevise(args) {
8
8
  const { subject, format, previous, critiques, author, minDraftLength, signal, onProgress } = args;
9
9
  onProgress?.({ type: "revise_start", round: previous.version + 1, participant: author.name });
10
- const client = await createLLMClient({ ...author.llm, enableStreaming: false });
10
+ const client = await createLLMClient(author.llm, author.clientDefaults);
11
11
  const prompt = format.revisePrompt(subject, previous, critiques, minDraftLength);
12
12
  const resp = await client.createMessage({
13
13
  systemPrompt: prompt,
@@ -11,7 +11,7 @@ export async function runTournamentCandidates(args) {
11
11
  onProgress?.({ type: "v1_tournament_start", data: { count: participants.length } });
12
12
  const tasks = participants.map(async (p, idx) => {
13
13
  signal?.throwIfAborted();
14
- const client = await createLLMClient({ ...p.llm, enableStreaming: false });
14
+ const client = await createLLMClient(p.llm, p.clientDefaults);
15
15
  const prompt = format.draftPrompt(subject, minDraftLength);
16
16
  // Long-form generation needs a high output ceiling; the default 8k from
17
17
  // LLMClientBase is too low for a substantive PRD or design doc and
@@ -66,7 +66,7 @@ export async function runTournamentCandidates(args) {
66
66
  export async function mergeCandidatesToV1(args) {
67
67
  const { subject, format, author, candidates, minDraftLength, signal, onProgress } = args;
68
68
  onProgress?.({ type: "v1_merge_start", participant: author.name });
69
- const client = await createLLMClient({ ...author.llm, enableStreaming: false });
69
+ const client = await createLLMClient(author.llm, author.clientDefaults);
70
70
  const prompt = format.mergePrompt(subject, candidates, minDraftLength);
71
71
  const resp = await client.createMessage({
72
72
  systemPrompt: prompt,
@@ -95,7 +95,7 @@ export async function mergeCandidatesToV1(args) {
95
95
  /** Single-author v1 (tournament disabled) — author writes alone. */
96
96
  export async function singleAuthorV1(args) {
97
97
  const { subject, format, author, minDraftLength, signal } = args;
98
- const client = await createLLMClient({ ...author.llm, enableStreaming: false });
98
+ const client = await createLLMClient(author.llm, author.clientDefaults);
99
99
  const prompt = format.draftPrompt(subject, minDraftLength);
100
100
  const resp = await client.createMessage({
101
101
  systemPrompt: prompt,
@@ -33,10 +33,7 @@ export async function runAdjudication(options) {
33
33
  verified: verifiedClaims.length,
34
34
  });
35
35
  // Adjudicate contested claims via LLM
36
- const client = await createLLMClient({
37
- ...concluder.llm,
38
- enableStreaming: false,
39
- });
36
+ const client = await createLLMClient(concluder.llm, concluder.clientDefaults);
40
37
  for (const claim of contestedClaims) {
41
38
  signal?.throwIfAborted();
42
39
  const digest = buildDigest(ledger, {
@@ -18,10 +18,7 @@ import { isStrategyV2 } from "../types.js";
18
18
  export async function buildConsensus(options) {
19
19
  const { concluder, strategy, topic, reports, reviews, claimSummary, signal, onProgress } = options;
20
20
  onProgress?.({ type: "consensus_start" });
21
- const client = await createLLMClient({
22
- ...concluder.llm,
23
- enableStreaming: false,
24
- });
21
+ const client = await createLLMClient(concluder.llm, concluder.clientDefaults);
25
22
  const systemPrompt = strategy.consensusSystemPrompt();
26
23
  // Choose user prompt: claim-aware (V2) or standard (V1)
27
24
  let userContent;
@@ -28,10 +28,7 @@ export async function runCrossReview(options) {
28
28
  return [];
29
29
  if (!myReport)
30
30
  return [];
31
- const client = await createLLMClient({
32
- ...p.llm,
33
- enableStreaming: false,
34
- });
31
+ const client = await createLLMClient(p.llm, p.clientDefaults);
35
32
  const systemPrompt = strategy.crossReviewSystemPrompt(p.name);
36
33
  const userContent = strategy.crossReviewUserPrompt(topic, myReport, otherReports);
37
34
  let response = await client.createMessage({
@@ -110,10 +107,7 @@ export async function runVerificationReview(options) {
110
107
  const myClaims = claimsToReview.filter((c) => c.owner !== p.name);
111
108
  if (myClaims.length === 0)
112
109
  return;
113
- const client = await createLLMClient({
114
- ...p.llm,
115
- enableStreaming: false,
116
- });
110
+ const client = await createLLMClient(p.llm, p.clientDefaults);
117
111
  const systemPrompt = strategy.crossReviewSystemPrompt(p.name);
118
112
  if (v2) {
119
113
  // V2 path: claim-aware verification review (or merge review for planning)
@@ -75,10 +75,7 @@ async function debateClaim(options) {
75
75
  });
76
76
  // Each debater produces a turn
77
77
  const turnPromises = debaters.map(async (p) => {
78
- const client = await createLLMClient({
79
- ...p.llm,
80
- enableStreaming: false,
81
- });
78
+ const client = await createLLMClient(p.llm, p.clientDefaults);
82
79
  const systemPrompt = strategy.crossReviewSystemPrompt(p.name);
83
80
  let userContent;
84
81
  if (v2) {
@@ -27,10 +27,7 @@ export async function runParticipantResearchWithDossiers(options) {
27
27
  const tools = enableContextTools ? (contextTools ?? CONTEXT_TOOLS) : undefined;
28
28
  const tasks = participants.map(async (p) => {
29
29
  onProgress?.({ type: "research_start", participant: p.name });
30
- const client = await createLLMClient({
31
- ...p.llm,
32
- enableStreaming: false,
33
- });
30
+ const client = await createLLMClient(p.llm, p.clientDefaults);
34
31
  const messages = [
35
32
  {
36
33
  role: "user",
@@ -31,10 +31,7 @@ export async function runDetailExpansion(options) {
31
31
  onProgress?.({ type: "roadmap_expansion_start", phaseCount: toExpand.length });
32
32
  logger.info("arena.detail_expansion_start", { phaseCount: toExpand.length });
33
33
  const details = [];
34
- const client = await createLLMClient({
35
- ...concluder.llm,
36
- enableStreaming: false,
37
- });
34
+ const client = await createLLMClient(concluder.llm, concluder.clientDefaults);
38
35
  const systemPrompt = strategy.detailExpansionSystemPrompt();
39
36
  // Expand each phase sequentially
40
37
  for (const phase of toExpand) {
@@ -45,13 +42,18 @@ export async function runDetailExpansion(options) {
45
42
  const userContent = strategy.detailExpansionUserPrompt(topic, phase, digest);
46
43
  const messages = [{ role: "user", content: userContent }];
47
44
  let finalText = "";
48
- // Tool-use loop: allow LLM to verify file paths and interfaces
45
+ // Tool-use loop: allow LLM to verify file paths and interfaces.
46
+ // `maxRounds` bounds the number of tool rounds; after the last one we make
47
+ // one final call (without tools) so the model produces text FROM the tool
48
+ // results rather than the empty text it emitted alongside the tool calls.
49
49
  const maxRounds = tools ? Math.min(MAX_EXPANSION_TOOL_ROUNDS, MAX_TOOL_ROUNDS) : 0;
50
50
  for (let round = 0; round <= maxRounds; round++) {
51
+ // On the final round, stop offering tools so the model must answer.
52
+ const offerTools = round < maxRounds ? tools : undefined;
51
53
  const response = await client.createMessage({
52
54
  systemPrompt,
53
55
  messages,
54
- tools,
56
+ tools: offerTools,
55
57
  signal,
56
58
  });
57
59
  const hasTools = response.toolCalls && response.toolCalls.length > 0;
@@ -90,10 +92,6 @@ export async function runDetailExpansion(options) {
90
92
  });
91
93
  }
92
94
  messages.push({ role: "user", content: toolResultBlocks });
93
- // Last round: force a text response
94
- if (round === maxRounds) {
95
- finalText = response.text;
96
- }
97
95
  }
98
96
  // Retry if truncated
99
97
  if (!finalText) {
@@ -67,7 +67,6 @@ export async function planArena(topic, llmConfig, flags, signal) {
67
67
  signal?.throwIfAborted();
68
68
  const client = await createLLMClient({
69
69
  ...llmConfig,
70
- enableStreaming: false,
71
70
  maxTokens: 1024,
72
71
  });
73
72
  const response = await client.createMessage({
@@ -4,3 +4,4 @@
4
4
  */
5
5
  import type { ArenaContextProvider } from "../types.js";
6
6
  export declare const docsProvider: ArenaContextProvider;
7
+ export declare function truncate(content: string): string;
@@ -99,10 +99,13 @@ function safeReadDoc(filePath) {
99
99
  return null;
100
100
  }
101
101
  }
102
- function truncate(content) {
102
+ export function truncate(content) {
103
103
  if (content.length <= MAX_DOC_CHARS)
104
104
  return content;
105
105
  const t = content.slice(0, MAX_DOC_CHARS);
106
106
  const lastNl = t.lastIndexOf("\n");
107
- return t.slice(0, lastNl) + `\n... (truncated, ${content.length} chars total)`;
107
+ // No newline in the window → lastNl is -1 and slice(0,-1) would drop just
108
+ // the last char; cut at MAX_DOC_CHARS instead.
109
+ const body = lastNl > 0 ? t.slice(0, lastNl) : t;
110
+ return body + `\n... (truncated, ${content.length} chars total)`;
108
111
  }
@@ -4,3 +4,4 @@
4
4
  */
5
5
  import type { ArenaContextProvider } from "../types.js";
6
6
  export declare const repoProvider: ArenaContextProvider;
7
+ export declare function truncate(content: string): string;
@@ -192,12 +192,15 @@ function safeReadFile(filePath) {
192
192
  return null;
193
193
  }
194
194
  }
195
- function truncate(content) {
195
+ export function truncate(content) {
196
196
  if (content.length <= MAX_FILE_CHARS)
197
197
  return content;
198
198
  const t = content.slice(0, MAX_FILE_CHARS);
199
199
  const lastNl = t.lastIndexOf("\n");
200
- return t.slice(0, lastNl) + `\n... (truncated, ${content.length} chars total)`;
200
+ // No newline in the window (e.g. a minified file) → lastNl is -1 and
201
+ // slice(0,-1) would drop just the last char; cut at MAX_FILE_CHARS instead.
202
+ const body = lastNl > 0 ? t.slice(0, lastNl) : t;
203
+ return body + `\n... (truncated, ${content.length} chars total)`;
201
204
  }
202
205
  async function safeGrep(pattern) {
203
206
  try {
@@ -68,11 +68,44 @@ export function extractJSONArray(text) {
68
68
  const fenced = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
69
69
  if (fenced)
70
70
  return fenced[1].trim();
71
- const arrayMatch = text.match(/\[[\s\S]*\]/);
72
- if (arrayMatch)
73
- return arrayMatch[0];
71
+ // Return the FIRST balanced top-level array. A greedy /\[[\s\S]*\]/ spanned
72
+ // to the last ']', merging two arrays (or trailing prose) into one invalid
73
+ // blob. Scan for bracket balance, ignoring brackets inside strings.
74
+ const balanced = firstBalancedArray(text);
75
+ if (balanced)
76
+ return balanced;
74
77
  return text;
75
78
  }
79
+ function firstBalancedArray(text) {
80
+ const start = text.indexOf("[");
81
+ if (start === -1)
82
+ return undefined;
83
+ let depth = 0;
84
+ let inString = false;
85
+ let escaped = false;
86
+ for (let i = start; i < text.length; i++) {
87
+ const ch = text[i];
88
+ if (inString) {
89
+ if (escaped)
90
+ escaped = false;
91
+ else if (ch === "\\")
92
+ escaped = true;
93
+ else if (ch === '"')
94
+ inString = false;
95
+ continue;
96
+ }
97
+ if (ch === '"')
98
+ inString = true;
99
+ else if (ch === "[")
100
+ depth++;
101
+ else if (ch === "]") {
102
+ depth--;
103
+ if (depth === 0)
104
+ return text.slice(start, i + 1);
105
+ }
106
+ }
107
+ return undefined; // unbalanced
108
+ }
76
109
  /** Format base context into a readable text block for LLM prompts */
77
110
  export function formatBaseContext(ctx) {
78
111
  const sections = [];
@@ -5,9 +5,14 @@
5
5
  * consistent state transitions across phases.
6
6
  *
7
7
  * State machine:
8
- * proposed → under_review
9
- * under_review → verified | contested | rejected
8
+ * proposed → under_review | unresolved
9
+ * under_review → verified | contested | rejected | unresolved
10
10
  * contested → under_review | unresolved | verified | rejected
11
+ *
12
+ * `unresolved` is reachable from proposed/under_review (not just contested):
13
+ * adjudication sweeps any claims left in those states when the round/budget
14
+ * is exhausted (adjudication.ts) — without these edges markUnresolved() would
15
+ * silently no-op and the claims would be dropped from the consensus summary.
11
16
  */
12
17
  import type { ClaimRecord, ClaimStatus, ClaimChallenge } from "./types.js";
13
18
  /**
@@ -5,14 +5,19 @@
5
5
  * consistent state transitions across phases.
6
6
  *
7
7
  * State machine:
8
- * proposed → under_review
9
- * under_review → verified | contested | rejected
8
+ * proposed → under_review | unresolved
9
+ * under_review → verified | contested | rejected | unresolved
10
10
  * contested → under_review | unresolved | verified | rejected
11
+ *
12
+ * `unresolved` is reachable from proposed/under_review (not just contested):
13
+ * adjudication sweeps any claims left in those states when the round/budget
14
+ * is exhausted (adjudication.ts) — without these edges markUnresolved() would
15
+ * silently no-op and the claims would be dropped from the consensus summary.
11
16
  */
12
17
  /** Valid transitions from each status */
13
18
  const VALID_TRANSITIONS = {
14
- proposed: ["under_review"],
15
- under_review: ["verified", "contested", "rejected"],
19
+ proposed: ["under_review", "unresolved"],
20
+ under_review: ["verified", "contested", "rejected", "unresolved"],
16
21
  contested: ["under_review", "unresolved", "verified", "rejected"],
17
22
  verified: [],
18
23
  rejected: [],