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.
- package/LICENSE +20 -0
- package/README.md +222 -177
- package/agents/explore.md +9 -6
- package/agents/general-purpose.md +1 -1
- package/agents/plan.md +6 -4
- package/docs/guide/README.md +52 -0
- package/docs/guide/bring-your-claude-code-setup.md +128 -0
- package/docs/guide/configuration.md +173 -0
- package/docs/guide/differences-from-claude-code.md +152 -0
- package/docs/guide/doctor.md +88 -0
- package/docs/guide/hooks.md +111 -0
- package/docs/guide/installation.md +126 -0
- package/docs/guide/permissions-modes-and-auto-mode.md +452 -0
- package/docs/guide/providers-and-models.md +196 -0
- package/docs/guide/reference.md +226 -0
- package/docs/guide/sessions-and-context.md +192 -0
- package/docs/guide/skills-plugins-and-mcp.md +178 -0
- package/docs/guide/subagents-and-workflows.md +195 -0
- package/docs/guide/tasks-and-background-work.md +113 -0
- package/docs/guide/terminal-interface.md +157 -0
- package/docs/guide/tools.md +177 -0
- package/docs/guide/troubleshooting.md +177 -0
- package/extensions/ask-user/index.ts +65 -71
- package/extensions/ask-user/questions.ts +15 -28
- package/extensions/ask-user/widget.ts +533 -0
- package/extensions/auto-mode/actions.ts +9 -26
- package/extensions/auto-mode/classifier-prompt.ts +127 -0
- package/extensions/auto-mode/classifier.ts +305 -227
- package/extensions/auto-mode/config.ts +279 -77
- package/extensions/auto-mode/defaults.ts +17 -60
- package/extensions/auto-mode/instructions.ts +2 -1
- package/extensions/auto-mode/model-picker.ts +53 -31
- package/extensions/auto-mode/model-select.ts +153 -82
- package/extensions/auto-mode/paths.ts +13 -1
- package/extensions/auto-mode/pause.ts +8 -0
- package/extensions/auto-mode/prompt.ts +205 -238
- package/extensions/auto-mode/recoverability.ts +229 -0
- package/extensions/auto-mode/rules.ts +92 -75
- package/extensions/auto-mode/safety-floor.ts +43 -12
- package/extensions/auto-mode/sensitive.ts +11 -0
- package/extensions/auto-mode/setup-run.ts +245 -0
- package/extensions/auto-mode/setup.ts +235 -0
- package/extensions/auto-mode/shell-analysis.ts +332 -26
- package/extensions/auto-mode/transcript.ts +102 -0
- package/extensions/background/index.ts +256 -43
- package/extensions/background/monitor-batch.ts +58 -0
- package/extensions/background/registry.ts +9 -0
- package/extensions/background/wakeup.ts +45 -0
- package/extensions/bash/background.ts +47 -22
- package/extensions/bash/guards.ts +229 -0
- package/extensions/bash/index.ts +81 -19
- package/extensions/branding/index.ts +44 -18
- package/extensions/branding/startup.ts +23 -8
- package/extensions/claude-compat/index.ts +17 -2
- package/extensions/claude-context/index.ts +41 -12
- package/extensions/clear/index.ts +27 -0
- package/extensions/compaction/fit.ts +121 -0
- package/extensions/compaction/index.ts +176 -71
- package/extensions/compaction/prompt.ts +21 -1
- package/extensions/context-budget/budget.ts +61 -0
- package/extensions/context-budget/index.ts +66 -0
- package/extensions/context-management/index.ts +40 -16
- package/extensions/doctor/build.ts +226 -0
- package/extensions/doctor/cli.ts +99 -0
- package/extensions/doctor/compat.ts +532 -0
- package/extensions/doctor/dependencies.ts +186 -0
- package/extensions/doctor/fix-prompt.ts +125 -0
- package/extensions/doctor/index.ts +308 -0
- package/extensions/doctor/models.ts +231 -0
- package/extensions/doctor/presets.ts +208 -0
- package/extensions/doctor/report.ts +189 -0
- package/extensions/doctor/update-lookup.ts +46 -0
- package/extensions/doctor/viewer.ts +124 -0
- package/extensions/effort/index.ts +41 -4
- package/extensions/effort/slider.ts +33 -2
- package/extensions/file-tracker/index.ts +143 -20
- package/extensions/file-tracker/replay.ts +46 -0
- package/extensions/file-tracker/tracker.ts +51 -4
- package/extensions/footer/footer-line.ts +207 -0
- package/extensions/footer/index.ts +152 -0
- package/extensions/footer/pr.ts +53 -0
- package/extensions/hooks/executor.ts +57 -20
- package/extensions/hooks/formatter-notice.ts +76 -0
- package/extensions/hooks/index.ts +314 -73
- package/extensions/hooks/matcher.ts +35 -1
- package/extensions/hooks/plugin-hooks.ts +3 -3
- package/extensions/hooks/protocol.ts +58 -0
- package/extensions/hooks/subagent-bridge.ts +77 -0
- package/extensions/hooks/trust.ts +15 -4
- package/extensions/init/index.ts +26 -0
- package/extensions/init/prompt.ts +40 -0
- package/extensions/interrupted/index.ts +35 -0
- package/extensions/interrupted/line.ts +9 -0
- package/extensions/lib/abort.ts +15 -0
- package/extensions/lib/agent-loader.ts +183 -0
- package/extensions/lib/anthropic-payload.ts +15 -0
- package/extensions/lib/atomic-write.ts +56 -0
- package/extensions/lib/bridge-watch.ts +20 -0
- package/extensions/lib/cache-retention.ts +60 -0
- package/extensions/lib/capability-index.ts +466 -0
- package/extensions/lib/claude-context.ts +318 -27
- package/extensions/lib/claude-settings.ts +64 -0
- package/extensions/lib/consent-preview.ts +30 -0
- package/extensions/lib/deferred.ts +144 -13
- package/extensions/lib/favorites.ts +43 -0
- package/extensions/lib/fetch-timeout.ts +15 -0
- package/extensions/lib/frontmatter.ts +31 -0
- package/extensions/lib/git.ts +44 -2
- package/extensions/lib/hook-gate.ts +90 -0
- package/extensions/lib/interrupt.ts +94 -0
- package/extensions/lib/mcp-overrides.ts +61 -0
- package/extensions/lib/mcp-share.ts +25 -0
- package/extensions/lib/mcp-status.ts +34 -0
- package/extensions/lib/memory.ts +41 -6
- package/extensions/lib/model-facts.generated.json +2480 -0
- package/extensions/lib/model-facts.ts +162 -0
- package/extensions/lib/model-policy.ts +145 -189
- package/extensions/lib/model-tier.ts +480 -64
- package/extensions/lib/model-unusable.ts +37 -0
- package/extensions/lib/notifications.ts +320 -1
- package/extensions/lib/one-code-settings.ts +63 -0
- package/extensions/lib/original-command.ts +71 -0
- package/extensions/lib/package-version.ts +31 -0
- package/extensions/lib/paths.ts +39 -4
- package/extensions/lib/permission-gate.ts +192 -0
- package/extensions/lib/persisted-output.ts +18 -0
- package/extensions/lib/pi-ai-estimate.ts +160 -0
- package/extensions/lib/pi-version.ts +1 -1
- package/extensions/lib/plan-mode-channels.ts +11 -0
- package/extensions/lib/plugin-overrides.ts +24 -0
- package/extensions/lib/plugin-root.ts +44 -0
- package/extensions/lib/plugins.ts +233 -54
- package/extensions/lib/prefix-warm-gate.ts +157 -0
- package/extensions/lib/process-tree.ts +56 -0
- package/extensions/lib/reminders.ts +321 -68
- package/extensions/lib/scratchpad.ts +11 -8
- package/extensions/lib/session-lifecycle.ts +28 -0
- package/extensions/lib/settings-channels.ts +14 -0
- package/extensions/lib/shell-tasks.ts +54 -0
- package/extensions/lib/skill-overrides.ts +86 -0
- package/extensions/lib/skill-scan.ts +110 -0
- package/extensions/lib/text-width.ts +267 -0
- package/extensions/lib/tui-render.ts +281 -54
- package/extensions/lib/usage-bus.ts +75 -0
- package/extensions/lib/usage-tracker.ts +67 -0
- package/extensions/lib/worktree-isolation.ts +170 -0
- package/extensions/lsp/client.ts +69 -12
- package/extensions/lsp/format.ts +12 -2
- package/extensions/lsp/index.ts +273 -84
- package/extensions/lsp/install-hints.ts +43 -0
- package/extensions/lsp/keep-alive.ts +19 -0
- package/extensions/lsp/plugin-servers.ts +331 -0
- package/extensions/lsp/watcher.ts +157 -0
- package/extensions/mcp/announce.ts +99 -0
- package/extensions/mcp/client.ts +181 -22
- package/extensions/mcp/config.ts +55 -24
- package/extensions/mcp/index.ts +586 -86
- package/extensions/mcp/oauth/browser.ts +26 -0
- package/extensions/mcp/oauth/callback.ts +104 -0
- package/extensions/mcp/oauth/flow.ts +68 -0
- package/extensions/mcp/oauth/provider.ts +147 -0
- package/extensions/mcp/oauth/store.ts +86 -0
- package/extensions/mcp/panel/keys.ts +34 -0
- package/extensions/mcp/panel/model.ts +62 -0
- package/extensions/mcp/panel/render.ts +141 -0
- package/extensions/mcp/panel/state.ts +108 -0
- package/extensions/mcp/schema.ts +83 -6
- package/extensions/mcp/trust.ts +340 -0
- package/extensions/memory/entries.ts +163 -0
- package/extensions/memory/index.ts +119 -2
- package/extensions/memory/open-external.ts +97 -0
- package/extensions/memory/panel.ts +124 -0
- package/extensions/model-default/index.ts +74 -0
- package/extensions/notebook/index.ts +36 -18
- package/extensions/notebook/notebook.ts +110 -17
- package/extensions/permissions/index.ts +1012 -247
- package/extensions/permissions/matcher.ts +488 -57
- package/extensions/permissions/modes.ts +55 -7
- package/extensions/permissions/project-trust.ts +81 -0
- package/extensions/permissions/protected-paths.ts +16 -3
- package/extensions/permissions/rule-prose.ts +79 -0
- package/extensions/permissions/session-grant.ts +91 -0
- package/extensions/permissions/settings.ts +112 -33
- package/extensions/permissions/subagent-gate.ts +57 -0
- package/extensions/plan-mode/index.ts +143 -33
- package/extensions/plan-mode/reminder.ts +16 -19
- package/extensions/plan-mode/viewer.ts +40 -31
- package/extensions/plugins/counts.ts +68 -0
- package/extensions/plugins/index.ts +496 -32
- package/extensions/plugins/install/install.ts +167 -0
- package/extensions/plugins/install/paths.ts +23 -0
- package/extensions/plugins/install/registry.ts +72 -0
- package/extensions/plugins/marketplace/git.ts +103 -0
- package/extensions/plugins/marketplace/official.ts +36 -0
- package/extensions/plugins/marketplace/parse.ts +94 -0
- package/extensions/plugins/marketplace/registry.ts +60 -0
- package/extensions/plugins/marketplace/sync.ts +146 -0
- package/extensions/plugins/marketplace/types.ts +128 -0
- package/extensions/plugins/panel/keys.ts +52 -0
- package/extensions/plugins/panel/render.ts +315 -0
- package/extensions/plugins/panel/rows.ts +230 -0
- package/extensions/plugins/panel/state.ts +333 -0
- package/extensions/recap/index.ts +215 -0
- package/extensions/recap/prompt.ts +44 -0
- package/extensions/recap/scheduler.ts +87 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/search-tools/tools.ts +7 -5
- package/extensions/skill/index.ts +360 -34
- package/extensions/skill/invoke.ts +185 -0
- package/extensions/skill/listing.ts +21 -0
- package/extensions/skill/panel/keys.ts +41 -0
- package/extensions/skill/panel/render.ts +91 -0
- package/extensions/skill/panel/state.ts +115 -0
- package/extensions/spinner/index.ts +110 -0
- package/extensions/spinner/line.ts +51 -0
- package/extensions/spinner/verbs.ts +201 -0
- package/extensions/subagents/agents.ts +88 -44
- package/extensions/subagents/default-model.ts +62 -32
- package/extensions/subagents/delegation-steer.ts +23 -0
- package/extensions/subagents/hand-back-review.ts +67 -0
- package/extensions/subagents/index.ts +1545 -346
- package/extensions/subagents/live-runs.ts +293 -0
- package/extensions/subagents/model-select.ts +252 -82
- package/extensions/subagents/outcome.ts +90 -0
- package/extensions/subagents/panel-keys.ts +41 -0
- package/extensions/subagents/panel-render.ts +357 -0
- package/extensions/subagents/panel-widget.ts +330 -0
- package/extensions/subagents/pending-claim.ts +45 -0
- package/extensions/subagents/prose.ts +105 -0
- package/extensions/subagents/runner.ts +674 -0
- package/extensions/subagents/runs.ts +68 -1
- package/extensions/subagents/send-to-main-tool.ts +55 -0
- package/extensions/subagents/session-turns.ts +138 -0
- package/extensions/subagents/shell-panel.ts +198 -0
- package/extensions/subagents/usage.ts +16 -4
- package/extensions/subagents/worktree.ts +13 -0
- package/extensions/system-prompt/environment.ts +5 -3
- package/extensions/system-prompt/git-status.ts +93 -0
- package/extensions/system-prompt/index.ts +23 -1
- package/extensions/system-prompt/template.ts +34 -4
- package/extensions/system-prompt/tiers/common.ts +31 -6
- package/extensions/system-prompt/tiers/low.ts +13 -3
- package/extensions/system-prompt/tiers/mid.ts +21 -8
- package/extensions/system-reminder/index.ts +53 -9
- package/extensions/tasks/index.ts +65 -16
- package/extensions/tasks/store.ts +76 -3
- package/extensions/tool-search/announce.ts +42 -0
- package/extensions/tool-search/index.ts +136 -13
- package/extensions/turn-duration/index.ts +60 -0
- package/extensions/turn-duration/line.ts +22 -0
- package/extensions/turn-duration/span.ts +41 -0
- package/extensions/turn-duration/verbs.ts +24 -0
- package/extensions/web/backends.ts +421 -0
- package/extensions/web/index.ts +178 -21
- package/extensions/web-fetch/index.ts +74 -18
- package/extensions/web-fetch/summarize.ts +1 -1
- package/extensions/workflow/agent-session.ts +182 -112
- package/extensions/workflow/globals.ts +129 -106
- package/extensions/workflow/index.ts +343 -28
- package/extensions/workflow/records.ts +118 -0
- package/extensions/workflow/run-manager.ts +80 -31
- package/extensions/workflow/saved-workflows.ts +2 -1
- package/extensions/workflow/script-worker.mjs +110 -0
- package/extensions/workflow/types.ts +74 -8
- package/extensions/workflow/viewer.ts +562 -0
- package/extensions/workflow/vm-runtime.ts +118 -22
- package/extensions/workflow/widget.ts +126 -18
- package/extensions/worktree/guards.ts +240 -0
- package/extensions/worktree/index.ts +30 -5
- package/extensions/worktree/rewrite.ts +32 -16
- package/package.json +20 -10
- package/skills/code-review/SKILL.md +165 -0
- package/skills/fewer-permission-prompts/SKILL.md +61 -0
- package/skills/security-review/SKILL.md +178 -0
- package/skills/simplify/SKILL.md +70 -0
- package/themes/{one-code-light.json → onecode-light.json} +6 -1
- package/themes/{one-code.json → onecode.json} +6 -1
- package/types/app.d.ts +8 -1
- package/types/community-packages.d.ts +45 -2
- package/docs/decisions/auto-mode.md +0 -526
- package/docs/decisions/branding.md +0 -127
- package/docs/decisions/code-review-remediation.md +0 -106
- package/docs/decisions/compaction.md +0 -140
- package/docs/decisions/distribution.md +0 -168
- package/docs/decisions/hooks.md +0 -64
- package/docs/decisions/lsp.md +0 -58
- package/docs/decisions/mcp.md +0 -104
- package/docs/decisions/memory-state.md +0 -170
- package/docs/decisions/model-policy.md +0 -126
- package/docs/decisions/modes.md +0 -178
- package/docs/decisions/skills-plugins.md +0 -56
- package/docs/decisions/subagents-workflows.md +0 -225
- package/docs/decisions/system-prompt.md +0 -98
- package/docs/decisions/tools.md +0 -407
- package/docs/decisions/tui.md +0 -217
- package/docs/decisions.md +0 -148
- package/extensions/subagents/child.ts +0 -414
- package/extensions/subagents/rpc-turns.ts +0 -123
- package/extensions/todo/index.ts +0 -133
- 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)
|
|
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 {
|
|
17
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
11
18
|
import { basename, join } from "node:path";
|
|
12
|
-
import {
|
|
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
|
-
|
|
27
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
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
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
73
|
-
const
|
|
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:
|
|
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, "
|
|
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 {
|
|
28
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
95
|
-
return claudeFamily ? configured : undefined;
|
|
121
|
+
return isClaudeFamilyModel(sessionModel) ? configured : undefined;
|
|
96
122
|
}
|
|
97
123
|
|
|
98
124
|
/**
|
|
99
|
-
* Persist `subagentModel` in
|
|
100
|
-
* key. `undefined` removes
|
|
101
|
-
* the
|
|
102
|
-
*
|
|
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
|
|
106
|
-
*
|
|
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 =
|
|
110
|
-
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
+
}
|