one-code-extension 0.1.4 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +222 -177
  3. package/agents/explore.md +9 -6
  4. package/agents/general-purpose.md +1 -1
  5. package/agents/plan.md +6 -4
  6. package/docs/guide/README.md +52 -0
  7. package/docs/guide/bring-your-claude-code-setup.md +128 -0
  8. package/docs/guide/configuration.md +173 -0
  9. package/docs/guide/differences-from-claude-code.md +152 -0
  10. package/docs/guide/doctor.md +88 -0
  11. package/docs/guide/hooks.md +111 -0
  12. package/docs/guide/installation.md +126 -0
  13. package/docs/guide/permissions-modes-and-auto-mode.md +452 -0
  14. package/docs/guide/providers-and-models.md +196 -0
  15. package/docs/guide/reference.md +226 -0
  16. package/docs/guide/sessions-and-context.md +192 -0
  17. package/docs/guide/skills-plugins-and-mcp.md +178 -0
  18. package/docs/guide/subagents-and-workflows.md +195 -0
  19. package/docs/guide/tasks-and-background-work.md +113 -0
  20. package/docs/guide/terminal-interface.md +157 -0
  21. package/docs/guide/tools.md +177 -0
  22. package/docs/guide/troubleshooting.md +177 -0
  23. package/extensions/ask-user/index.ts +65 -71
  24. package/extensions/ask-user/questions.ts +15 -28
  25. package/extensions/ask-user/widget.ts +533 -0
  26. package/extensions/auto-mode/actions.ts +9 -26
  27. package/extensions/auto-mode/classifier-prompt.ts +127 -0
  28. package/extensions/auto-mode/classifier.ts +305 -227
  29. package/extensions/auto-mode/config.ts +279 -77
  30. package/extensions/auto-mode/defaults.ts +17 -60
  31. package/extensions/auto-mode/instructions.ts +2 -1
  32. package/extensions/auto-mode/model-picker.ts +53 -31
  33. package/extensions/auto-mode/model-select.ts +153 -82
  34. package/extensions/auto-mode/paths.ts +13 -1
  35. package/extensions/auto-mode/pause.ts +8 -0
  36. package/extensions/auto-mode/prompt.ts +205 -238
  37. package/extensions/auto-mode/recoverability.ts +229 -0
  38. package/extensions/auto-mode/rules.ts +92 -75
  39. package/extensions/auto-mode/safety-floor.ts +43 -12
  40. package/extensions/auto-mode/sensitive.ts +11 -0
  41. package/extensions/auto-mode/setup-run.ts +245 -0
  42. package/extensions/auto-mode/setup.ts +235 -0
  43. package/extensions/auto-mode/shell-analysis.ts +332 -26
  44. package/extensions/auto-mode/transcript.ts +102 -0
  45. package/extensions/background/index.ts +256 -43
  46. package/extensions/background/monitor-batch.ts +58 -0
  47. package/extensions/background/registry.ts +9 -0
  48. package/extensions/background/wakeup.ts +45 -0
  49. package/extensions/bash/background.ts +47 -22
  50. package/extensions/bash/guards.ts +229 -0
  51. package/extensions/bash/index.ts +81 -19
  52. package/extensions/branding/index.ts +44 -18
  53. package/extensions/branding/startup.ts +23 -8
  54. package/extensions/claude-compat/index.ts +17 -2
  55. package/extensions/claude-context/index.ts +41 -12
  56. package/extensions/clear/index.ts +27 -0
  57. package/extensions/compaction/fit.ts +121 -0
  58. package/extensions/compaction/index.ts +176 -71
  59. package/extensions/compaction/prompt.ts +21 -1
  60. package/extensions/context-budget/budget.ts +61 -0
  61. package/extensions/context-budget/index.ts +66 -0
  62. package/extensions/context-management/index.ts +40 -16
  63. package/extensions/doctor/build.ts +226 -0
  64. package/extensions/doctor/cli.ts +99 -0
  65. package/extensions/doctor/compat.ts +532 -0
  66. package/extensions/doctor/dependencies.ts +186 -0
  67. package/extensions/doctor/fix-prompt.ts +125 -0
  68. package/extensions/doctor/index.ts +308 -0
  69. package/extensions/doctor/models.ts +231 -0
  70. package/extensions/doctor/presets.ts +208 -0
  71. package/extensions/doctor/report.ts +189 -0
  72. package/extensions/doctor/update-lookup.ts +46 -0
  73. package/extensions/doctor/viewer.ts +124 -0
  74. package/extensions/effort/index.ts +41 -4
  75. package/extensions/effort/slider.ts +33 -2
  76. package/extensions/file-tracker/index.ts +143 -20
  77. package/extensions/file-tracker/replay.ts +46 -0
  78. package/extensions/file-tracker/tracker.ts +51 -4
  79. package/extensions/footer/footer-line.ts +207 -0
  80. package/extensions/footer/index.ts +152 -0
  81. package/extensions/footer/pr.ts +53 -0
  82. package/extensions/hooks/executor.ts +57 -20
  83. package/extensions/hooks/formatter-notice.ts +76 -0
  84. package/extensions/hooks/index.ts +314 -73
  85. package/extensions/hooks/matcher.ts +35 -1
  86. package/extensions/hooks/plugin-hooks.ts +3 -3
  87. package/extensions/hooks/protocol.ts +58 -0
  88. package/extensions/hooks/subagent-bridge.ts +77 -0
  89. package/extensions/hooks/trust.ts +15 -4
  90. package/extensions/init/index.ts +26 -0
  91. package/extensions/init/prompt.ts +40 -0
  92. package/extensions/interrupted/index.ts +35 -0
  93. package/extensions/interrupted/line.ts +9 -0
  94. package/extensions/lib/abort.ts +15 -0
  95. package/extensions/lib/agent-loader.ts +183 -0
  96. package/extensions/lib/anthropic-payload.ts +15 -0
  97. package/extensions/lib/atomic-write.ts +56 -0
  98. package/extensions/lib/bridge-watch.ts +20 -0
  99. package/extensions/lib/cache-retention.ts +60 -0
  100. package/extensions/lib/capability-index.ts +466 -0
  101. package/extensions/lib/claude-context.ts +318 -27
  102. package/extensions/lib/claude-settings.ts +64 -0
  103. package/extensions/lib/consent-preview.ts +30 -0
  104. package/extensions/lib/deferred.ts +144 -13
  105. package/extensions/lib/favorites.ts +43 -0
  106. package/extensions/lib/fetch-timeout.ts +15 -0
  107. package/extensions/lib/frontmatter.ts +31 -0
  108. package/extensions/lib/git.ts +44 -2
  109. package/extensions/lib/hook-gate.ts +90 -0
  110. package/extensions/lib/interrupt.ts +94 -0
  111. package/extensions/lib/mcp-overrides.ts +61 -0
  112. package/extensions/lib/mcp-share.ts +25 -0
  113. package/extensions/lib/mcp-status.ts +34 -0
  114. package/extensions/lib/memory.ts +41 -6
  115. package/extensions/lib/model-facts.generated.json +2480 -0
  116. package/extensions/lib/model-facts.ts +162 -0
  117. package/extensions/lib/model-policy.ts +145 -189
  118. package/extensions/lib/model-tier.ts +480 -64
  119. package/extensions/lib/model-unusable.ts +37 -0
  120. package/extensions/lib/notifications.ts +320 -1
  121. package/extensions/lib/one-code-settings.ts +63 -0
  122. package/extensions/lib/original-command.ts +71 -0
  123. package/extensions/lib/package-version.ts +31 -0
  124. package/extensions/lib/paths.ts +39 -4
  125. package/extensions/lib/permission-gate.ts +192 -0
  126. package/extensions/lib/persisted-output.ts +18 -0
  127. package/extensions/lib/pi-ai-estimate.ts +160 -0
  128. package/extensions/lib/pi-version.ts +1 -1
  129. package/extensions/lib/plan-mode-channels.ts +11 -0
  130. package/extensions/lib/plugin-overrides.ts +24 -0
  131. package/extensions/lib/plugin-root.ts +44 -0
  132. package/extensions/lib/plugins.ts +233 -54
  133. package/extensions/lib/prefix-warm-gate.ts +157 -0
  134. package/extensions/lib/process-tree.ts +56 -0
  135. package/extensions/lib/reminders.ts +321 -68
  136. package/extensions/lib/scratchpad.ts +11 -8
  137. package/extensions/lib/session-lifecycle.ts +28 -0
  138. package/extensions/lib/settings-channels.ts +14 -0
  139. package/extensions/lib/shell-tasks.ts +54 -0
  140. package/extensions/lib/skill-overrides.ts +86 -0
  141. package/extensions/lib/skill-scan.ts +110 -0
  142. package/extensions/lib/text-width.ts +267 -0
  143. package/extensions/lib/tui-render.ts +281 -54
  144. package/extensions/lib/usage-bus.ts +75 -0
  145. package/extensions/lib/usage-tracker.ts +67 -0
  146. package/extensions/lib/worktree-isolation.ts +170 -0
  147. package/extensions/lsp/client.ts +69 -12
  148. package/extensions/lsp/format.ts +12 -2
  149. package/extensions/lsp/index.ts +273 -84
  150. package/extensions/lsp/install-hints.ts +43 -0
  151. package/extensions/lsp/keep-alive.ts +19 -0
  152. package/extensions/lsp/plugin-servers.ts +331 -0
  153. package/extensions/lsp/watcher.ts +157 -0
  154. package/extensions/mcp/announce.ts +99 -0
  155. package/extensions/mcp/client.ts +181 -22
  156. package/extensions/mcp/config.ts +55 -24
  157. package/extensions/mcp/index.ts +586 -86
  158. package/extensions/mcp/oauth/browser.ts +26 -0
  159. package/extensions/mcp/oauth/callback.ts +104 -0
  160. package/extensions/mcp/oauth/flow.ts +68 -0
  161. package/extensions/mcp/oauth/provider.ts +147 -0
  162. package/extensions/mcp/oauth/store.ts +86 -0
  163. package/extensions/mcp/panel/keys.ts +34 -0
  164. package/extensions/mcp/panel/model.ts +62 -0
  165. package/extensions/mcp/panel/render.ts +141 -0
  166. package/extensions/mcp/panel/state.ts +108 -0
  167. package/extensions/mcp/schema.ts +83 -6
  168. package/extensions/mcp/trust.ts +340 -0
  169. package/extensions/memory/entries.ts +163 -0
  170. package/extensions/memory/index.ts +119 -2
  171. package/extensions/memory/open-external.ts +97 -0
  172. package/extensions/memory/panel.ts +124 -0
  173. package/extensions/model-default/index.ts +74 -0
  174. package/extensions/notebook/index.ts +36 -18
  175. package/extensions/notebook/notebook.ts +110 -17
  176. package/extensions/permissions/index.ts +1012 -247
  177. package/extensions/permissions/matcher.ts +488 -57
  178. package/extensions/permissions/modes.ts +55 -7
  179. package/extensions/permissions/project-trust.ts +81 -0
  180. package/extensions/permissions/protected-paths.ts +16 -3
  181. package/extensions/permissions/rule-prose.ts +79 -0
  182. package/extensions/permissions/session-grant.ts +91 -0
  183. package/extensions/permissions/settings.ts +112 -33
  184. package/extensions/permissions/subagent-gate.ts +57 -0
  185. package/extensions/plan-mode/index.ts +143 -33
  186. package/extensions/plan-mode/reminder.ts +16 -19
  187. package/extensions/plan-mode/viewer.ts +40 -31
  188. package/extensions/plugins/counts.ts +68 -0
  189. package/extensions/plugins/index.ts +496 -32
  190. package/extensions/plugins/install/install.ts +167 -0
  191. package/extensions/plugins/install/paths.ts +23 -0
  192. package/extensions/plugins/install/registry.ts +72 -0
  193. package/extensions/plugins/marketplace/git.ts +103 -0
  194. package/extensions/plugins/marketplace/official.ts +36 -0
  195. package/extensions/plugins/marketplace/parse.ts +94 -0
  196. package/extensions/plugins/marketplace/registry.ts +60 -0
  197. package/extensions/plugins/marketplace/sync.ts +146 -0
  198. package/extensions/plugins/marketplace/types.ts +128 -0
  199. package/extensions/plugins/panel/keys.ts +52 -0
  200. package/extensions/plugins/panel/render.ts +315 -0
  201. package/extensions/plugins/panel/rows.ts +230 -0
  202. package/extensions/plugins/panel/state.ts +333 -0
  203. package/extensions/recap/index.ts +215 -0
  204. package/extensions/recap/prompt.ts +44 -0
  205. package/extensions/recap/scheduler.ts +87 -0
  206. package/extensions/search-tools/index.ts +0 -0
  207. package/extensions/search-tools/tools.ts +7 -5
  208. package/extensions/skill/index.ts +360 -34
  209. package/extensions/skill/invoke.ts +185 -0
  210. package/extensions/skill/listing.ts +21 -0
  211. package/extensions/skill/panel/keys.ts +41 -0
  212. package/extensions/skill/panel/render.ts +91 -0
  213. package/extensions/skill/panel/state.ts +115 -0
  214. package/extensions/spinner/index.ts +110 -0
  215. package/extensions/spinner/line.ts +51 -0
  216. package/extensions/spinner/verbs.ts +201 -0
  217. package/extensions/subagents/agents.ts +88 -44
  218. package/extensions/subagents/default-model.ts +62 -32
  219. package/extensions/subagents/delegation-steer.ts +23 -0
  220. package/extensions/subagents/hand-back-review.ts +67 -0
  221. package/extensions/subagents/index.ts +1545 -346
  222. package/extensions/subagents/live-runs.ts +293 -0
  223. package/extensions/subagents/model-select.ts +252 -82
  224. package/extensions/subagents/outcome.ts +90 -0
  225. package/extensions/subagents/panel-keys.ts +41 -0
  226. package/extensions/subagents/panel-render.ts +357 -0
  227. package/extensions/subagents/panel-widget.ts +330 -0
  228. package/extensions/subagents/pending-claim.ts +45 -0
  229. package/extensions/subagents/prose.ts +105 -0
  230. package/extensions/subagents/runner.ts +674 -0
  231. package/extensions/subagents/runs.ts +68 -1
  232. package/extensions/subagents/send-to-main-tool.ts +55 -0
  233. package/extensions/subagents/session-turns.ts +138 -0
  234. package/extensions/subagents/shell-panel.ts +198 -0
  235. package/extensions/subagents/usage.ts +16 -4
  236. package/extensions/subagents/worktree.ts +13 -0
  237. package/extensions/system-prompt/environment.ts +5 -3
  238. package/extensions/system-prompt/git-status.ts +93 -0
  239. package/extensions/system-prompt/index.ts +23 -1
  240. package/extensions/system-prompt/template.ts +34 -4
  241. package/extensions/system-prompt/tiers/common.ts +31 -6
  242. package/extensions/system-prompt/tiers/low.ts +13 -3
  243. package/extensions/system-prompt/tiers/mid.ts +21 -8
  244. package/extensions/system-reminder/index.ts +53 -9
  245. package/extensions/tasks/index.ts +65 -16
  246. package/extensions/tasks/store.ts +76 -3
  247. package/extensions/tool-search/announce.ts +42 -0
  248. package/extensions/tool-search/index.ts +136 -13
  249. package/extensions/turn-duration/index.ts +60 -0
  250. package/extensions/turn-duration/line.ts +22 -0
  251. package/extensions/turn-duration/span.ts +41 -0
  252. package/extensions/turn-duration/verbs.ts +24 -0
  253. package/extensions/web/backends.ts +421 -0
  254. package/extensions/web/index.ts +178 -21
  255. package/extensions/web-fetch/index.ts +74 -18
  256. package/extensions/web-fetch/summarize.ts +1 -1
  257. package/extensions/workflow/agent-session.ts +182 -112
  258. package/extensions/workflow/globals.ts +129 -106
  259. package/extensions/workflow/index.ts +343 -28
  260. package/extensions/workflow/records.ts +118 -0
  261. package/extensions/workflow/run-manager.ts +80 -31
  262. package/extensions/workflow/saved-workflows.ts +2 -1
  263. package/extensions/workflow/script-worker.mjs +110 -0
  264. package/extensions/workflow/types.ts +74 -8
  265. package/extensions/workflow/viewer.ts +562 -0
  266. package/extensions/workflow/vm-runtime.ts +118 -22
  267. package/extensions/workflow/widget.ts +126 -18
  268. package/extensions/worktree/guards.ts +240 -0
  269. package/extensions/worktree/index.ts +30 -5
  270. package/extensions/worktree/rewrite.ts +32 -16
  271. package/package.json +20 -10
  272. package/skills/code-review/SKILL.md +165 -0
  273. package/skills/fewer-permission-prompts/SKILL.md +61 -0
  274. package/skills/security-review/SKILL.md +178 -0
  275. package/skills/simplify/SKILL.md +70 -0
  276. package/themes/{one-code-light.json → onecode-light.json} +6 -1
  277. package/themes/{one-code.json → onecode.json} +6 -1
  278. package/types/app.d.ts +8 -1
  279. package/types/community-packages.d.ts +45 -2
  280. package/docs/decisions/auto-mode.md +0 -526
  281. package/docs/decisions/branding.md +0 -127
  282. package/docs/decisions/code-review-remediation.md +0 -106
  283. package/docs/decisions/compaction.md +0 -140
  284. package/docs/decisions/distribution.md +0 -168
  285. package/docs/decisions/hooks.md +0 -64
  286. package/docs/decisions/lsp.md +0 -58
  287. package/docs/decisions/mcp.md +0 -104
  288. package/docs/decisions/memory-state.md +0 -170
  289. package/docs/decisions/model-policy.md +0 -126
  290. package/docs/decisions/modes.md +0 -178
  291. package/docs/decisions/skills-plugins.md +0 -56
  292. package/docs/decisions/subagents-workflows.md +0 -225
  293. package/docs/decisions/system-prompt.md +0 -98
  294. package/docs/decisions/tools.md +0 -407
  295. package/docs/decisions/tui.md +0 -217
  296. package/docs/decisions.md +0 -148
  297. package/extensions/subagents/child.ts +0 -414
  298. package/extensions/subagents/rpc-turns.ts +0 -123
  299. package/extensions/todo/index.ts +0 -133
  300. package/extensions/workflow/permission-gate.ts +0 -71
@@ -1,21 +1,39 @@
1
1
  /**
2
2
  * Agent definition discovery (pure) — Claude Code's `.claude/agents/*.md`
3
- * layout: YAML frontmatter (name, description, tools, model) plus a system
4
- * prompt body.
3
+ * layout: YAML frontmatter (name, description, tools, disallowedTools, model)
4
+ * plus a system prompt body.
5
+ *
6
+ * Tool names in `tools`/`disallowedTools` are Claude Code's (`Read`, `Edit`,
7
+ * `Bash`, `Grep`, `Glob`, `WebFetch`, …) in every real agent file, and pi's
8
+ * allowlist is an exact-name set over pi's snake_case names — passing CC names
9
+ * through verbatim gave the child NO tools at all (SUBAGENT-REVIEW H2). Both
10
+ * lists are therefore normalized through the shared CC↔pi alias table at parse
11
+ * time; pi names and `mcp__*` names pass through unchanged.
5
12
  *
6
13
  * Precedence: user (~/.claude/agents) < project (<cwd>/.claude/agents).
7
14
  * Subdirectories are searched recursively.
8
15
  */
9
16
 
10
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
17
+ import { readdirSync, readFileSync, statSync } from "node:fs";
11
18
  import { basename, join } from "node:path";
12
- import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
19
+ import { parseFrontmatterLoosely } from "../lib/frontmatter.ts";
20
+ import { claudeUserDir } from "../lib/paths.ts";
21
+ import { normalizeToolName } from "../permissions/matcher.ts";
13
22
 
14
23
  export interface AgentDefinition {
15
24
  name: string;
16
25
  description: string;
17
- /** Tool allowlist for the child; undefined means the child's defaults. */
26
+ /** Tool allowlist for the child (pi names); undefined means the child's defaults. */
18
27
  tools?: string[];
28
+ /**
29
+ * Tool denylist (pi names): the child gets its default toolset MINUS these —
30
+ * Claude Code's "All tools except …" grant shape (its Explore/Plan agents),
31
+ * spelled `disallowedTools` in CC frontmatter; `excludeTools` is accepted as
32
+ * a synonym for definitions written against earlier One Code builds. Filters
33
+ * built-ins, extension tools, and injected custom tools alike (pi's
34
+ * excludeTools). Combinable with `tools`, though agents use one or the other.
35
+ */
36
+ excludeTools?: string[];
19
37
  /** Model override (pi model id, e.g. "anthropic/claude-sonnet-5"). */
20
38
  model?: string;
21
39
  systemPrompt: string;
@@ -23,10 +41,23 @@ export interface AgentDefinition {
23
41
  }
24
42
 
25
43
  function collectMarkdownFiles(dir: string, out: string[] = []): string[] {
26
- if (!existsSync(dir)) return out;
27
- for (const entry of readdirSync(dir)) {
44
+ let entries: string[];
45
+ try {
46
+ entries = readdirSync(dir);
47
+ } catch {
48
+ return out; // missing or unreadable directory: nothing to discover
49
+ }
50
+ for (const entry of entries) {
28
51
  const full = join(dir, entry);
29
- if (statSync(full).isDirectory()) {
52
+ // A dangling symlink or an unreadable entry must not take the whole
53
+ // catalog (and every Agent call) down with it — skip it (review L1).
54
+ let isDirectory: boolean;
55
+ try {
56
+ isDirectory = statSync(full).isDirectory();
57
+ } catch {
58
+ continue;
59
+ }
60
+ if (isDirectory) {
30
61
  collectMarkdownFiles(full, out);
31
62
  } else if (entry.endsWith(".md")) {
32
63
  out.push(full);
@@ -36,31 +67,23 @@ function collectMarkdownFiles(dir: string, out: string[] = []): string[] {
36
67
  }
37
68
 
38
69
  /**
39
- * Frontmatter in the wild is not always valid YAML: real Claude Code plugin
40
- * agents contain unquoted descriptions with `: ` in them, which pi's parser
41
- * rejects ("Nested mappings are not allowed in compact mappings"). Falling back
42
- * to line-wise extraction keeps those definitions usable instead of dropping
43
- * them silently.
70
+ * A frontmatter tool list (comma string or YAML list) as pi tool names: Claude
71
+ * Code spellings map through the alias table (`Glob` → `find`, `WebFetch` →
72
+ * `web_fetch`, `Task` → `Agent`, …), pi names and `mcp__*` pass through,
73
+ * duplicates collapse. Exported for tests.
44
74
  */
45
- function parseFrontmatterLoosely(content: string): { frontmatter: Record<string, unknown>; body: string } {
46
- try {
47
- const parsed = parseFrontmatter(content) as { frontmatter?: Record<string, unknown>; body: string };
48
- return { frontmatter: parsed.frontmatter ?? {}, body: parsed.body };
49
- } catch {
50
- // Fall through to the lenient path.
51
- }
52
-
53
- const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
54
- if (!match) return { frontmatter: {}, body: content };
55
-
56
- const frontmatter: Record<string, unknown> = {};
57
- for (const line of match[1].split(/\r?\n/)) {
58
- const keyValue = line.match(/^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/);
59
- if (!keyValue) continue;
60
- const value = keyValue[2].trim().replace(/^["']|["']$/g, "");
61
- if (value) frontmatter[keyValue[1]] = value;
62
- }
63
- return { frontmatter, body: match[2] };
75
+ export function parseToolList(raw: unknown): string[] | undefined {
76
+ const list =
77
+ typeof raw === "string"
78
+ ? raw
79
+ .split(",")
80
+ .map((t) => t.trim())
81
+ .filter(Boolean)
82
+ : Array.isArray(raw)
83
+ ? raw.filter((t): t is string => typeof t === "string").map((t) => t.trim()).filter(Boolean)
84
+ : undefined;
85
+ if (!list || list.length === 0) return undefined;
86
+ return [...new Set(list.map(normalizeToolName))];
64
87
  }
65
88
 
66
89
  export function parseAgentFile(path: string, content: string): AgentDefinition | undefined {
@@ -69,21 +92,14 @@ export function parseAgentFile(path: string, content: string): AgentDefinition |
69
92
  const name = typeof fm.name === "string" && fm.name.trim() ? fm.name.trim() : basename(path, ".md");
70
93
  if (!body.trim()) return undefined;
71
94
 
72
- const rawTools = fm.tools;
73
- const tools =
74
- typeof rawTools === "string"
75
- ? rawTools
76
- .split(",")
77
- .map((t) => t.trim())
78
- .filter(Boolean)
79
- : Array.isArray(rawTools)
80
- ? rawTools.filter((t): t is string => typeof t === "string")
81
- : undefined;
95
+ // CC's key first; the One Code synonym merges in so neither spelling is lost.
96
+ const disallowed = [...(parseToolList(fm.disallowedTools) ?? []), ...(parseToolList(fm.excludeTools) ?? [])];
82
97
 
83
98
  return {
84
99
  name,
85
100
  description: typeof fm.description === "string" ? fm.description : "",
86
- tools: tools && tools.length > 0 ? tools : undefined,
101
+ tools: parseToolList(fm.tools),
102
+ excludeTools: disallowed.length > 0 ? [...new Set(disallowed)] : undefined,
87
103
  // "inherit" is Claude Code's way of saying "use the session model".
88
104
  model: typeof fm.model === "string" && fm.model !== "inherit" ? fm.model : undefined,
89
105
  systemPrompt: body.trim(),
@@ -97,7 +113,7 @@ export function parseAgentFile(path: string, content: string): AgentDefinition |
97
113
  * project definition with the same name replaces a bundled one.
98
114
  */
99
115
  export function agentDirs(cwd: string, home: string, bundled?: string): string[] {
100
- return [...(bundled ? [bundled] : []), join(home, ".claude", "agents"), join(cwd, ".claude", "agents")];
116
+ return [...(bundled ? [bundled] : []), join(claudeUserDir(home), "agents"), join(cwd, ".claude", "agents")];
101
117
  }
102
118
 
103
119
  /** A directory whose agents are exposed as `<namespace>:<name>` (plugins). */
@@ -124,3 +140,31 @@ export function discoverAgents(sources: Array<string | AgentSource>): AgentDefin
124
140
  }
125
141
  return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
126
142
  }
143
+
144
+ /**
145
+ * Tools a child cannot function without, re-added when an agent file's `tools`
146
+ * allowlist omits them: pi's allowlist filters built-ins, extension tools AND
147
+ * injected custom tools by exact name, so a CC-style `tools: Read, Grep` would
148
+ * otherwise also strip the child's report channel (`SendMessage` → main), its
149
+ * deferred-tool loader and the runtime's `structured_output`. Claude Code's
150
+ * allowlist never strips its own plumbing either.
151
+ */
152
+ const ALLOWLIST_ESSENTIALS = ["SendMessage", "tool_search", "structured_output"];
153
+
154
+ /** Apply an agent's allowlist to pi's `tools` option, keeping the essentials. */
155
+ export function childToolAllowlist(tools: string[] | undefined): string[] | undefined {
156
+ if (!tools) return undefined;
157
+ return [...new Set([...tools, ...ALLOWLIST_ESSENTIALS])];
158
+ }
159
+
160
+ /**
161
+ * The names of an agent's `tools` allowlist that pi actually registered. Empty
162
+ * means the list named no real tool (CC spellings that missed the alias table,
163
+ * typos), which must fail loud rather than run a tool-less agent that
164
+ * "completes" with an excuse. Judged against the agent's OWN list — an agent
165
+ * that deliberately lists only `SendMessage` is narrow, not broken.
166
+ */
167
+ export function usableAllowlistedTools(registered: string[], allowlist: string[]): string[] {
168
+ const available = new Set(registered);
169
+ return allowlist.filter((name) => available.has(name));
170
+ }
@@ -12,7 +12,13 @@
12
12
  * same containment rule as `autoMode` config.
13
13
  * - `subagentModel` — One Code's own top-level setting, same shape as
14
14
  * `autoMode.classifierModel`. Wins over the env var when both are set,
15
- * because it is the more specific statement of intent.
15
+ * because it is the more specific statement of intent. It is One Code's key,
16
+ * not Claude Code's: it is written to and read from `~/.onecode/settings.json`
17
+ * (+ managed settings), never from `~/.claude`, where an old One Code build may
18
+ * have left a stale value the current session cannot reach. Claude Code's own
19
+ * `CLAUDE_CODE_SUBAGENT_MODEL` env block is still honoured from `~/.claude`
20
+ * (that key *is* Claude Code's). See "Own state, borrowed config" in
21
+ * docs/decisions/memory-state.md.
16
22
  *
17
23
  * The env var additionally applies only to Claude-family sessions (see
18
24
  * `applicableSubagentDefault`): it is Claude Code's knob, and its typical
@@ -24,40 +30,61 @@
24
30
  * pass through to resolveSubagentModel, which owns the semantics.
25
31
  */
26
32
 
27
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
28
- import { dirname, join } from "node:path";
33
+ import { claudeUserSettingsPath, readSettingsFile } from "../lib/claude-settings.ts";
34
+ import { isClaudeFamilyModel } from "../lib/model-policy.ts";
35
+ import { oneCodeSettingsPath, readSettingsForWrite, writeSettings } from "../lib/one-code-settings.ts";
29
36
  import { autoModeSettingsPaths } from "../auto-mode/config.ts";
30
37
 
31
38
  export interface SubagentDefault {
32
39
  spec: string;
33
40
  /** Which knob set it, for notices. */
34
41
  source: "subagentModel setting" | "CLAUDE_CODE_SUBAGENT_MODEL";
42
+ /**
43
+ * For the `subagentModel` setting only: the *containment identity* of the
44
+ * session it was stamped for when set via `/subagent` — `modelIdentity().containment`,
45
+ * i.e. the plain provider for direct vendors but `provider:route:vendor` on a
46
+ * gateway (openrouter etc.), matching the granularity `crossesProvider` uses.
47
+ * When it differs from the current session's containment, a cross-provider
48
+ * setting is treated as stale (set for a session since left) and a
49
+ * same-provider model runs instead — see `resolveSubagentModel`. Undefined for
50
+ * a hand-edited setting (no stamp) and for the env var.
51
+ */
52
+ setForContainment?: string;
35
53
  }
36
54
 
37
55
  interface SettingsFile {
38
56
  subagentModel?: unknown;
57
+ subagentModelSetFor?: unknown;
39
58
  env?: Record<string, unknown>;
40
59
  }
41
60
 
61
+ /** The shared lenient reader, narrowed to the subagent-relevant shape. */
42
62
  function readSettings(path: string): SettingsFile | undefined {
43
- if (!existsSync(path)) return undefined;
44
- try {
45
- return JSON.parse(readFileSync(path, "utf-8")) as SettingsFile;
46
- } catch {
47
- return undefined;
48
- }
63
+ return readSettingsFile(path) as SettingsFile | undefined;
49
64
  }
50
65
 
51
66
  export function loadSubagentDefault(home: string, env: NodeJS.ProcessEnv = process.env): SubagentDefault | undefined {
52
67
  let setting: string | undefined;
68
+ let settingSetFor: string | undefined;
53
69
  let settingsEnv: string | undefined;
54
70
 
71
+ // `subagentModel` is One Code's own key — read from `~/.onecode` and managed
72
+ // settings, never from `~/.claude`, where an old build may have left a stale
73
+ // value. Claude Code's `CLAUDE_CODE_SUBAGENT_MODEL` env block IS Claude Code's,
74
+ // so it is still read from every file.
75
+ const claudeUser = claudeUserSettingsPath(home);
55
76
  // Lowest precedence first, so managed settings override user settings.
56
77
  for (const path of autoModeSettingsPaths(home)) {
57
78
  const file = readSettings(path);
58
79
  if (!file) continue;
59
- if (typeof file.subagentModel === "string" && file.subagentModel.trim()) {
80
+ if (path !== claudeUser && typeof file.subagentModel === "string" && file.subagentModel.trim()) {
60
81
  setting = file.subagentModel.trim();
82
+ // The stamp travels with the model from the same file (a lower-precedence
83
+ // stamp must not attach to a higher-precedence model).
84
+ settingSetFor =
85
+ typeof file.subagentModelSetFor === "string" && file.subagentModelSetFor.trim()
86
+ ? file.subagentModelSetFor.trim()
87
+ : undefined;
61
88
  }
62
89
  const fromEnvBlock = file.env?.CLAUDE_CODE_SUBAGENT_MODEL;
63
90
  if (typeof fromEnvBlock === "string" && fromEnvBlock.trim()) {
@@ -65,7 +92,7 @@ export function loadSubagentDefault(home: string, env: NodeJS.ProcessEnv = proce
65
92
  }
66
93
  }
67
94
 
68
- if (setting) return { spec: setting, source: "subagentModel setting" };
95
+ if (setting) return { spec: setting, source: "subagentModel setting", setForContainment: settingSetFor };
69
96
 
70
97
  const fromEnv = env.CLAUDE_CODE_SUBAGENT_MODEL?.trim();
71
98
  if (fromEnv) return { spec: fromEnv, source: "CLAUDE_CODE_SUBAGENT_MODEL" };
@@ -91,32 +118,35 @@ export function applicableSubagentDefault(
91
118
  ): SubagentDefault | undefined {
92
119
  if (!configured || configured.source === "subagentModel setting") return configured;
93
120
  if (!sessionModel) return configured;
94
- const claudeFamily = sessionModel.provider === "anthropic" || /claude/i.test(sessionModel.id);
95
- return claudeFamily ? configured : undefined;
121
+ return isClaudeFamilyModel(sessionModel) ? configured : undefined;
96
122
  }
97
123
 
98
124
  /**
99
- * Persist `subagentModel` in the *user* settings file, preserving every other
100
- * key. `undefined` removes the setting. Always user scope, like the loader:
101
- * the default may move subagent work to another provider, so it lives where
102
- * only the user writes — never in a project file (see the module comment).
125
+ * Persist `subagentModel` in One Code's own settings file
126
+ * (`~/.onecode/settings.json`), preserving every other key. `undefined` removes
127
+ * the setting. Never touches Claude Code's files: this is One Code's key, and the
128
+ * default may move subagent work to another provider, so it lives in One Code's
129
+ * own state (see the module comment).
103
130
  *
104
131
  * Unlike loading, this throws on a malformed file: a lenient read merely
105
- * skips a setting, but a lenient write would replace the user's whole
106
- * settings file with only ours.
132
+ * skips a setting, but a lenient write would replace the whole settings file
133
+ * with only ours.
134
+ *
135
+ * `setForContainment` stamps the containment identity of the session the model
136
+ * was chosen on (modelIdentity().containment), so a later session on a different
137
+ * provider/vendor can treat the setting as stale. Pass it for a concrete model;
138
+ * it is cleared for `inherit` (never cross-provider) and on removal.
107
139
  */
108
- export function persistSubagentModel(spec: string | undefined, home: string): void {
109
- const path = join(home, ".claude", "settings.json");
110
- let file: Record<string, unknown> = {};
111
- if (existsSync(path)) {
112
- const parsed = JSON.parse(readFileSync(path, "utf-8")) as unknown;
113
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
114
- throw new Error(`${path}: root must be a JSON object`);
115
- }
116
- file = parsed as Record<string, unknown>;
140
+ export function persistSubagentModel(spec: string | undefined, home: string, setForContainment?: string): void {
141
+ const path = oneCodeSettingsPath(home);
142
+ const file = readSettingsForWrite(path);
143
+ if (spec === undefined) {
144
+ delete file.subagentModel;
145
+ delete file.subagentModelSetFor;
146
+ } else {
147
+ file.subagentModel = spec;
148
+ if (setForContainment && spec !== "inherit") file.subagentModelSetFor = setForContainment;
149
+ else delete file.subagentModelSetFor;
117
150
  }
118
- if (spec === undefined) delete file.subagentModel;
119
- else file.subagentModel = spec;
120
- mkdirSync(dirname(path), { recursive: true });
121
- writeFileSync(path, `${JSON.stringify(file, null, 2)}\n`);
151
+ writeSettings(path, file);
122
152
  }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Tiny-tier-only strict delegation directive (pure prompt text), riding the
3
+ * context stack right after the agent catalog. Static prompt sections
4
+ * measurably move capable models to delegate broad sweeps (workhorse/cheap
5
+ * went 2/6 → 6/6 in the 2026-08-31 probe battery) but a sub-Haiku model
6
+ * ignored the same section (qwen3.6-27b: 0/6 with it verified on the wire); a
7
+ * <system-reminder> sits closer to the user text, where weak models actually
8
+ * attend. Keyed and byte-stable, so it is cache-neutral; removed when a model
9
+ * switch leaves the tiny tier (see index.ts emitDelegationSteer).
10
+ *
11
+ * Sibling texts (same delegation policy, separately tuned registers):
12
+ * DELEGATE_STRICT in system-prompt/tiers/low.ts (tiny prompt) and
13
+ * DELEGATING_WORK in tiers/mid.ts (workhorse/cheap prompt). The shared policy
14
+ * invariants are pinned by test/unit/delegation-prose.test.ts — when editing
15
+ * one register, keep the siblings aligned or that test will say so.
16
+ */
17
+
18
+ export const DELEGATION_STEER = [
19
+ 'Delegation policy: when a request requires reading or searching MANY files (a codebase overview, "find every place where…", a consistency audit, exploring unfamiliar code), do NOT sweep the files yourself.',
20
+ 'Make ONE Agent tool call with subagent_type: "explore" and the complete question as the task. The agent searches in its own separate context and returns just the answer; reading file after file yourself fills your context and degrades your answer.',
21
+ "Search directly only for a single targeted lookup (one known file or symbol).",
22
+ "When the agent has answered, report its answer and move on — do not re-read the files it already covered.",
23
+ ].join("\n");
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The hand-back review of a resident agent's turn, awaited so the verdict
3
+ * travels WITH the completion report instead of trailing it.
4
+ *
5
+ * A resident's spawning call has already returned when its turn ends, so there
6
+ * is no tool_result for the auto-mode gate to attach its holistic review to.
7
+ * Emitting the actions and sending the report immediately (the previous
8
+ * shape) let the "flagged" notice land one or more tool boundaries after the
9
+ * report — the model could act on an agent's output before hearing that auto
10
+ * mode had concerns about how it was produced. Claude Code gates the hand-back
11
+ * (findings §17.2). This module holds the wait: the payload carries an
12
+ * `onReview` callback the gate answers exactly once (a rendered flag, or
13
+ * undefined for clean / not in auto mode), and a timeout bounds the wait so a
14
+ * slow or absent reviewer degrades to "report plus a note", never to a report
15
+ * that waits forever.
16
+ */
17
+ import { SUBAGENT_ACTIONS_CHANNEL, type ChildAction, type SubagentActionsPayload } from "../auto-mode/actions.ts";
18
+
19
+ /** How long a completion report waits for the gate's verdict before going out with a note instead. */
20
+ export const HAND_BACK_REVIEW_TIMEOUT_MS = 45_000;
21
+
22
+ /** The note that replaces a verdict that did not arrive in time. */
23
+ export const HAND_BACK_REVIEW_TIMEOUT_NOTE =
24
+ "<system-reminder>\nAuto mode's review of this agent's actions did not finish before the report was delivered. Treat the output as unverified until you have checked it.\n</system-reminder>";
25
+
26
+ /** The events slice this module needs. */
27
+ export interface ActionsEmitter {
28
+ emit(channel: string, data: unknown): void;
29
+ }
30
+
31
+ /**
32
+ * Emit a resident turn's actions for review and resolve with the gate's
33
+ * rendered flag (undefined when clean, no actions, or the gate is not in auto
34
+ * mode). Never rejects. The first answer wins; late or repeated answers are
35
+ * ignored.
36
+ */
37
+ export function awaitHandBackReview(
38
+ events: ActionsEmitter,
39
+ run: { taskId: string; name: string },
40
+ actions: ChildAction[] | undefined,
41
+ timeoutMs = HAND_BACK_REVIEW_TIMEOUT_MS,
42
+ ): Promise<string | undefined> {
43
+ if (!actions?.length) return Promise.resolve(undefined);
44
+ return new Promise((resolve) => {
45
+ let settled = false;
46
+ const finish = (flag: string | undefined) => {
47
+ if (settled) return;
48
+ settled = true;
49
+ clearTimeout(timer);
50
+ resolve(flag);
51
+ };
52
+ const timer = setTimeout(() => finish(HAND_BACK_REVIEW_TIMEOUT_NOTE), timeoutMs);
53
+ timer.unref?.();
54
+ events.emit(SUBAGENT_ACTIONS_CHANNEL, {
55
+ toolCallId: run.taskId,
56
+ actions,
57
+ background: true,
58
+ agentName: run.name,
59
+ onReview: finish,
60
+ } satisfies SubagentActionsPayload);
61
+ });
62
+ }
63
+
64
+ /** Prepend a review flag to a notification body, when there is one. */
65
+ export function withReview(body: string, flag: string | undefined): string {
66
+ return flag ? `${flag}\n\n${body}` : body;
67
+ }