one-code-extension 0.1.0
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 +32 -0
- package/README.md +202 -0
- package/agents/explore.md +24 -0
- package/agents/general-purpose.md +21 -0
- package/agents/plan.md +22 -0
- package/docs/decisions/auto-mode.md +526 -0
- package/docs/decisions/branding.md +127 -0
- package/docs/decisions/code-review-remediation.md +106 -0
- package/docs/decisions/compaction.md +140 -0
- package/docs/decisions/distribution.md +133 -0
- package/docs/decisions/hooks.md +64 -0
- package/docs/decisions/lsp.md +58 -0
- package/docs/decisions/mcp.md +104 -0
- package/docs/decisions/memory-state.md +170 -0
- package/docs/decisions/model-policy.md +126 -0
- package/docs/decisions/modes.md +178 -0
- package/docs/decisions/skills-plugins.md +56 -0
- package/docs/decisions/subagents-workflows.md +225 -0
- package/docs/decisions/system-prompt.md +98 -0
- package/docs/decisions/tools.md +407 -0
- package/docs/decisions/tui.md +202 -0
- package/docs/decisions.md +148 -0
- package/extensions/ask-user/index.ts +124 -0
- package/extensions/ask-user/questions.ts +60 -0
- package/extensions/auto-mode/actions.ts +78 -0
- package/extensions/auto-mode/classifier.ts +388 -0
- package/extensions/auto-mode/config.ts +237 -0
- package/extensions/auto-mode/decision-log.ts +60 -0
- package/extensions/auto-mode/defaults.ts +65 -0
- package/extensions/auto-mode/instructions.ts +74 -0
- package/extensions/auto-mode/model-picker.ts +225 -0
- package/extensions/auto-mode/model-select.ts +178 -0
- package/extensions/auto-mode/paths.ts +109 -0
- package/extensions/auto-mode/pause.ts +96 -0
- package/extensions/auto-mode/prompt.ts +307 -0
- package/extensions/auto-mode/rules.ts +96 -0
- package/extensions/auto-mode/safety-floor.ts +100 -0
- package/extensions/auto-mode/sensitive.ts +137 -0
- package/extensions/auto-mode/shell-analysis.ts +702 -0
- package/extensions/background/index.ts +405 -0
- package/extensions/background/registry.ts +81 -0
- package/extensions/background/wakeup.ts +41 -0
- package/extensions/bash/background.ts +136 -0
- package/extensions/bash/index.ts +149 -0
- package/extensions/branding/assistant-marker.ts +53 -0
- package/extensions/branding/index.ts +366 -0
- package/extensions/branding/prompt-editor.ts +42 -0
- package/extensions/branding/prompt-marker.ts +29 -0
- package/extensions/branding/startup.ts +152 -0
- package/extensions/claude-compat/index.ts +50 -0
- package/extensions/claude-context/index.ts +79 -0
- package/extensions/compaction/index.ts +231 -0
- package/extensions/compaction/prompt.ts +191 -0
- package/extensions/context-management/index.ts +147 -0
- package/extensions/effort/index.ts +216 -0
- package/extensions/effort/slider.ts +253 -0
- package/extensions/exit/index.ts +28 -0
- package/extensions/file-tracker/index.ts +103 -0
- package/extensions/file-tracker/tracker.ts +145 -0
- package/extensions/hooks/debug.ts +53 -0
- package/extensions/hooks/executor.ts +132 -0
- package/extensions/hooks/index.ts +310 -0
- package/extensions/hooks/matcher.ts +72 -0
- package/extensions/hooks/plugin-hooks.ts +79 -0
- package/extensions/hooks/protocol.ts +151 -0
- package/extensions/hooks/settings.ts +163 -0
- package/extensions/hooks/trust.ts +151 -0
- package/extensions/lib/branch-restore.ts +35 -0
- package/extensions/lib/claude-context.ts +148 -0
- package/extensions/lib/deferred.ts +119 -0
- package/extensions/lib/git.ts +14 -0
- package/extensions/lib/memory.ts +219 -0
- package/extensions/lib/model-policy.ts +436 -0
- package/extensions/lib/model-tier.ts +99 -0
- package/extensions/lib/notifications.ts +18 -0
- package/extensions/lib/paths.ts +26 -0
- package/extensions/lib/per-cwd.ts +21 -0
- package/extensions/lib/persisted-output.ts +70 -0
- package/extensions/lib/pi-version.ts +54 -0
- package/extensions/lib/plugins.ts +227 -0
- package/extensions/lib/reminders.ts +212 -0
- package/extensions/lib/scratchpad.ts +68 -0
- package/extensions/lib/tui-render.ts +522 -0
- package/extensions/lsp/client.ts +278 -0
- package/extensions/lsp/format.ts +46 -0
- package/extensions/lsp/index.ts +193 -0
- package/extensions/lsp/protocol.ts +64 -0
- package/extensions/lsp/servers.ts +79 -0
- package/extensions/mcp/client.ts +205 -0
- package/extensions/mcp/config.ts +199 -0
- package/extensions/mcp/index.ts +373 -0
- package/extensions/mcp/schema.ts +161 -0
- package/extensions/memory/index.ts +90 -0
- package/extensions/notebook/index.ts +77 -0
- package/extensions/notebook/notebook.ts +108 -0
- package/extensions/permissions/index.ts +876 -0
- package/extensions/permissions/matcher.ts +455 -0
- package/extensions/permissions/modes.ts +107 -0
- package/extensions/permissions/protected-paths.ts +125 -0
- package/extensions/permissions/settings.ts +98 -0
- package/extensions/plan-mode/index.ts +209 -0
- package/extensions/plan-mode/reminder.ts +33 -0
- package/extensions/plan-mode/slug.ts +28 -0
- package/extensions/plan-mode/viewer.ts +124 -0
- package/extensions/plugins/index.ts +115 -0
- package/extensions/plugins/template.ts +42 -0
- package/extensions/search-tools/index.ts +0 -0
- package/extensions/search-tools/tools.ts +24 -0
- package/extensions/skill/index.ts +170 -0
- package/extensions/subagents/agents.ts +126 -0
- package/extensions/subagents/child.ts +414 -0
- package/extensions/subagents/default-model.ts +122 -0
- package/extensions/subagents/index.ts +1119 -0
- package/extensions/subagents/model-select.ts +346 -0
- package/extensions/subagents/rpc-turns.ts +123 -0
- package/extensions/subagents/runs.ts +93 -0
- package/extensions/subagents/usage.ts +53 -0
- package/extensions/subagents/worktree.ts +69 -0
- package/extensions/system-prompt/environment.ts +35 -0
- package/extensions/system-prompt/index.ts +54 -0
- package/extensions/system-prompt/template.ts +103 -0
- package/extensions/system-prompt/tiers/common.ts +63 -0
- package/extensions/system-prompt/tiers/frontier.ts +24 -0
- package/extensions/system-prompt/tiers/low.ts +71 -0
- package/extensions/system-prompt/tiers/mid.ts +56 -0
- package/extensions/system-reminder/index.ts +37 -0
- package/extensions/tasks/index.ts +140 -0
- package/extensions/tasks/store.ts +174 -0
- package/extensions/todo/index.ts +133 -0
- package/extensions/tool-search/index.ts +168 -0
- package/extensions/tool-style/index.ts +84 -0
- package/extensions/web/index.ts +43 -0
- package/extensions/web-fetch/extract.ts +139 -0
- package/extensions/web-fetch/index.ts +291 -0
- package/extensions/web-fetch/summarize.ts +78 -0
- package/extensions/workflow/agent-session.ts +368 -0
- package/extensions/workflow/globals.ts +251 -0
- package/extensions/workflow/index.ts +364 -0
- package/extensions/workflow/journal.ts +75 -0
- package/extensions/workflow/permission-gate.ts +71 -0
- package/extensions/workflow/run-manager.ts +306 -0
- package/extensions/workflow/saved-workflows.ts +45 -0
- package/extensions/workflow/script-source.ts +201 -0
- package/extensions/workflow/types.ts +99 -0
- package/extensions/workflow/vm-runtime.ts +31 -0
- package/extensions/workflow/widget.ts +56 -0
- package/extensions/worktree/index.ts +266 -0
- package/extensions/worktree/rewrite.ts +59 -0
- package/package.json +101 -0
- package/themes/one-code-light.json +76 -0
- package/themes/one-code.json +76 -0
- package/types/app.d.ts +5 -0
- package/types/community-packages.d.ts +12 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Memory & session state
|
|
2
|
+
|
|
3
|
+
Part of [Decisions](../decisions.md).
|
|
4
|
+
|
|
5
|
+
## Memory: own file-based implementation
|
|
6
|
+
|
|
7
|
+
The original plan scoped memory out as "Anthropic-hosted", but the memory
|
|
8
|
+
system Claude Code actually runs on is machine-local plain files the model
|
|
9
|
+
manages itself with ordinary file tools: one fact per markdown file under
|
|
10
|
+
`~/.claude/projects/<slug>/memory/`, a `MEMORY.md` index, a `# Memory` system
|
|
11
|
+
prompt section describing the format, and the index injected into the first
|
|
12
|
+
turn. All of that is local, so we implement it (`extensions/lib/memory.ts` +
|
|
13
|
+
`extensions/memory/`) rather than depend on `pi-memory`, whose model differs
|
|
14
|
+
(its own tools and storage format) and would not read or write Claude Code's
|
|
15
|
+
existing memory directories.
|
|
16
|
+
|
|
17
|
+
Fidelity details confirmed against the official memory doc
|
|
18
|
+
(<https://code.claude.com/docs/en/memory>), `payload.json`, and a live Claude
|
|
19
|
+
Code context:
|
|
20
|
+
|
|
21
|
+
- **Slug is keyed by git root, not cwd** — all worktrees and subdirectories of
|
|
22
|
+
a repo share one memory directory; outside a repo the cwd is used. We resolve
|
|
23
|
+
with `findGitRoot` (now in `lib/git.ts`).
|
|
24
|
+
- **Only the first 200 lines or 25KB of MEMORY.md load**; `truncateIndex`
|
|
25
|
+
mirrors that. Topic files are never startup-loaded — the model reads them on
|
|
26
|
+
demand.
|
|
27
|
+
- **Placement**: Claude Code nests the index inside the first-turn claudeMd
|
|
28
|
+
`<system-reminder>` ("Contents of … (user's auto-memory, persists across
|
|
29
|
+
conversations):"). We reuse that exact framing but emit a standalone
|
|
30
|
+
reminder, because our CLAUDE.md equivalent travels through pi's
|
|
31
|
+
`contextFiles` into the system prompt instead of a first-turn reminder.
|
|
32
|
+
- **Write-time frontmatter stamping** (found by diffing a real session
|
|
33
|
+
transcript's Write inputs against the files on disk): Claude Code enriches a
|
|
34
|
+
memory file's `metadata:` at write time with `node_type: memory`,
|
|
35
|
+
`originSessionId`, and a `modified` ISO timestamp — and never adds
|
|
36
|
+
frontmatter to a file that has none, which keeps MEMORY.md unstamped. We
|
|
37
|
+
stamp by mutating the `write` tool's input in `tool_call`, *before*
|
|
38
|
+
execution, so the file lands stamped and file-tracker observes the same
|
|
39
|
+
bytes that hit disk (stamping after its `tool_result` observation would make
|
|
40
|
+
every memory write look externally modified).
|
|
41
|
+
- **Write-time index limit guard**: after a write/edit of MEMORY.md, Claude
|
|
42
|
+
Code measures the loadable content against the 200-line/25KB limits —
|
|
43
|
+
near-limit it reminds the model to shorten the index; over-limit the write
|
|
44
|
+
succeeds but the result is an error telling the model to rewrite. Replicated
|
|
45
|
+
via the reminder queue and pi's rewritable `tool_result`.
|
|
46
|
+
|
|
47
|
+
Not replicated: relevance-based mid-session recall of individual memory files.
|
|
48
|
+
Its selection mechanism is undocumented client internals (the frontmatter
|
|
49
|
+
`description` is "used to decide relevance during recall", and the UI shows
|
|
50
|
+
"Recalled N memories"), and no recalled-memory block has been
|
|
51
|
+
observed in practice — including in a long live session working on this very
|
|
52
|
+
project with a relevant memory on file. There is nothing observable to copy;
|
|
53
|
+
the injected index is the entry point and the model follows links from there.
|
|
54
|
+
|
|
55
|
+
Split follows the house pattern: pure helpers (slug, paths, truncation,
|
|
56
|
+
prompt/reminder text) in `lib/memory.ts`; the `memory` extension does mkdir +
|
|
57
|
+
first-turn reminder over the queue; `system-prompt` re-derives the directory
|
|
58
|
+
for its section (no shared module state — jiti). Using Claude Code's exact
|
|
59
|
+
slug and layout means memories written by real Claude Code sessions are picked
|
|
60
|
+
up unchanged, and vice versa.
|
|
61
|
+
|
|
62
|
+
Verified live via tmux + `debug-capture.ts` (the sandboxed shell can't run pi
|
|
63
|
+
directly): the `# Memory` section lands just before `# Environment`, the
|
|
64
|
+
directory is created with the expected slug on session start (from a
|
|
65
|
+
subdirectory of a git repo it resolves to the repo root), and a planted
|
|
66
|
+
`MEMORY.md` arrives as a `<system-reminder>` on the first user message.
|
|
67
|
+
Stamping was verified end-to-end with a real model run through the permission
|
|
68
|
+
gate (allow rules, no bypass flag): the model wrote plain frontmatter and the
|
|
69
|
+
file landed with `node_type`/`originSessionId`/`modified` in Claude Code's
|
|
70
|
+
field order, while MEMORY.md stayed unstamped.
|
|
71
|
+
|
|
72
|
+
## The memory dir is harness-designated working space, like the plan file
|
|
73
|
+
|
|
74
|
+
Auto mode blocked the auto-memory feature it ships next to: the system prompt
|
|
75
|
+
instructs the model to write memories into `~/.claude/projects/<slug>/memory`,
|
|
76
|
+
which is both outside the working directory and under the protected `.claude`
|
|
77
|
+
dir, so every memory write went to the classifier and its out-of-project rule
|
|
78
|
+
(correctly, per its own text) blocked it. The harness was flagging its own
|
|
79
|
+
instruction.
|
|
80
|
+
|
|
81
|
+
The fix follows the plan-file precedent exactly: `decide()` takes a
|
|
82
|
+
`memoryDirPath` and deterministically allows *writing-tool* calls that land
|
|
83
|
+
inside it, with cause `memory-dir`. Scope notes, since this punches through
|
|
84
|
+
the protected-path check:
|
|
85
|
+
|
|
86
|
+
- Deny rules and explicit ask rules still win — an ask rule is the user's
|
|
87
|
+
stated intent to be prompted, and the check sits after both.
|
|
88
|
+
- Only the exact per-project dir clears. Another project's memory dir, and
|
|
89
|
+
everything else under `.claude`, still hits the protected-path check.
|
|
90
|
+
- The check judges `resolvedSubject` when available — where the write actually
|
|
91
|
+
*lands* — so a symlink planted inside the memory dir cannot convert the
|
|
92
|
+
allow into a write elsewhere; `resolve()` normalises `..` traversals for the
|
|
93
|
+
same reason. Bash writes into the dir are not cleared, only the write tools.
|
|
94
|
+
- The permissions extension re-derives the dir (`memoryDir` + `findGitRoot`)
|
|
95
|
+
rather than sharing state with the memory extension, per the jiti
|
|
96
|
+
module-isolation rule. The workflow permission gate does the same, since
|
|
97
|
+
memory writes inside `agent()` calls previously died on "needs interactive
|
|
98
|
+
approval".
|
|
99
|
+
- Plan mode still denies memory writes (unchanged); the auto-mode safety floor
|
|
100
|
+
is unaffected — it deliberately targets exact settings files and already
|
|
101
|
+
documents that `~/.claude` holds memory the agent writes routinely.
|
|
102
|
+
- Trap, caught live: `resolveForContainment` returns a *case-folded* path, so
|
|
103
|
+
any prefix/equality check against it must case-fold both sides (as
|
|
104
|
+
`isProtectedPath` does). The first cut compared case-sensitively, matched
|
|
105
|
+
nothing on macOS, and every memory write silently fell through to the
|
|
106
|
+
protected-path check — visible only because the live run was checked before
|
|
107
|
+
committing.
|
|
108
|
+
|
|
109
|
+
## Session scratchpad, Claude Code-style
|
|
110
|
+
|
|
111
|
+
Claude Code gives every session a scratchpad
|
|
112
|
+
(`<tmp>/claude-<uid>/<project-slug>/<session-id>/scratchpad`) via a system
|
|
113
|
+
prompt section and lets it be used "generally without permission prompts";
|
|
114
|
+
One Code had nothing, so temp files landed in `/tmp` or the project — and in
|
|
115
|
+
auto mode, `/tmp` writes are exactly the out-of-project pattern the classifier
|
|
116
|
+
flags. Same failure family as the memory-dir block, fixed with the same
|
|
117
|
+
mechanism:
|
|
118
|
+
|
|
119
|
+
- `lib/scratchpad.ts` derives the path (pure core + `sessionScratchpadDir`
|
|
120
|
+
re-derived by every consumer, per the jiti module-isolation rule) and
|
|
121
|
+
carries the prompt section verbatim from `payload.json`. `/tmp` is resolved
|
|
122
|
+
through its symlink up front so the prompt, the permission comparison, and
|
|
123
|
+
the case-folded resolved subject all name one real location.
|
|
124
|
+
- The system-prompt extension mkdirs it at `session_start` and renders the
|
|
125
|
+
section. The path embeds the session id, so it deliberately lives *outside*
|
|
126
|
+
the (cwd, model)-cached environment block — constant within a session, which
|
|
127
|
+
is all provider prompt caching needs. An unwritable tmp drops the section
|
|
128
|
+
rather than promising a directory writes will error on.
|
|
129
|
+
- `decide()` grew a `scratchpadDirPath` beside `memoryDirPath` — one shared
|
|
130
|
+
`isInsideDir` check (resolved subject, case-folded, traversal-safe), deny
|
|
131
|
+
and ask rules still winning, other sessions' scratchpads and bare `/tmp`
|
|
132
|
+
not cleared. The workflow gate derives the *child's* scratchpad lazily from
|
|
133
|
+
its first tool call, since the child session id does not exist when the
|
|
134
|
+
gate is constructed.
|
|
135
|
+
- The write tools get the deterministic allow; bash into the scratchpad still
|
|
136
|
+
goes to the classifier, so the outside-cwd default rule now names the
|
|
137
|
+
harness's designated directories (scratchpad, auto-memory) as clearing it.
|
|
138
|
+
|
|
139
|
+
Verified live without `--dangerously-skip-permissions`: a plain `-p` session
|
|
140
|
+
was asked for "a temporary file in the right place per your instructions",
|
|
141
|
+
wrote `notes.txt` into its own session scratchpad path, and the file was on
|
|
142
|
+
disk — no prompt, correct slug and session id. Typecheck and 711 unit tests
|
|
143
|
+
pass.
|
|
144
|
+
|
|
145
|
+
## Own state, borrowed config: `.claude` is read-only, One Code writes to `~/.one-code`
|
|
146
|
+
|
|
147
|
+
One Code sat in a three-way namespace muddle: pi owns `~/.pi` (sessions,
|
|
148
|
+
models.json — harness plumbing, invisible, and untouchable without forking),
|
|
149
|
+
`.claude` is Claude Code's directory, and One Code had started *generating*
|
|
150
|
+
state into the latter (plan files at first; memory was already there at
|
|
151
|
+
`~/.claude/projects/<slug>/memory`). Reading `.claude` deeply is the product —
|
|
152
|
+
"your Claude Code setup works on any model" is the pitch, so settings, skills,
|
|
153
|
+
commands, agents, plugins, and CLAUDE.md stay read-from-`.claude` forever. But
|
|
154
|
+
*writing* into a namespace another product owns invites collisions: Claude
|
|
155
|
+
Code also writes under `~/.claude/projects/<slug>/` and evolves that layout
|
|
156
|
+
without notice, and a user auditing "what did Claude Code do" finds artifacts
|
|
157
|
+
it didn't make.
|
|
158
|
+
|
|
159
|
+
The policy, in one line: **`.claude` is a read-only compat surface; everything
|
|
160
|
+
One Code generates goes to `~/.one-code`.** `extensions/lib/paths.ts` centralises
|
|
161
|
+
both roots — `claudeConfigDir()` (honouring `CLAUDE_CONFIG_DIR`, which Claude
|
|
162
|
+
Code itself supports and One Code previously ignored) and `oneCodeStateDir()`
|
|
163
|
+
(honouring `ONE_CODE_STATE_DIR`). Plan files moved to `~/.one-code/plans`
|
|
164
|
+
immediately, while the feature was a day old. `.one-code` joined the protected
|
|
165
|
+
dirs (the gate's own namespace must be as guarded in its new home as in the
|
|
166
|
+
old), with `.one-code/plans` excepted as working space. Deliberately *not* done:
|
|
167
|
+
a One Code-native settings schema (no demand yet — compat reads suffice), and
|
|
168
|
+
the memory-dir migration, which has a real tradeoff (sharing memory with
|
|
169
|
+
Claude Code on the same repo is arguably a feature) and waits on its own
|
|
170
|
+
decision.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Model policy
|
|
2
|
+
|
|
3
|
+
Part of [Decisions](../decisions.md).
|
|
4
|
+
|
|
5
|
+
## Shared role profiles: smaller classifiers and delegated workers by default
|
|
6
|
+
|
|
7
|
+
A real OpenAI Codex session exposed two different defaults with the same costly
|
|
8
|
+
result: selecting `gpt-5.6-sol` made the banner read `classifier 5.6-sol
|
|
9
|
+
(planned)` and `subagents 5.6-sol (session)`. The classifier's provider table
|
|
10
|
+
predated Luna and 5.4 Mini, so its deliberate "session before anything chosen
|
|
11
|
+
only by price" fallback did exactly what it should. Subagents had a separate,
|
|
12
|
+
intentional session-model default. Workflow `agent()` had a third path: it
|
|
13
|
+
always received `ctx.model`, so it did not actually honour `/subagent` despite
|
|
14
|
+
the command and reminder saying it did.
|
|
15
|
+
|
|
16
|
+
The fix is one pure policy layer (`extensions/lib/model-policy.ts`) shared by
|
|
17
|
+
both selectors, but **not one shared capability rule**:
|
|
18
|
+
|
|
19
|
+
- classifier profiles contain only reviewed small-but-capable families; price
|
|
20
|
+
alone can never promote a model into the security boundary;
|
|
21
|
+
- subagent profiles prefer economical coding/reasoning models, then may use a
|
|
22
|
+
cheaper price-ranked model when the provider policy says containment is
|
|
23
|
+
knowable;
|
|
24
|
+
- explicit classifier, per-call, agent-frontmatter, and `/subagent` choices
|
|
25
|
+
retain their old precedence and may cross providers because naming one is
|
|
26
|
+
choosing it; `inherit` explicitly suppresses automatic selection;
|
|
27
|
+
- both roles finish at the session model rather than breaking the feature.
|
|
28
|
+
|
|
29
|
+
Automatic subagent selection is a *cost optimisation*, and review tightened it
|
|
30
|
+
to demand cost evidence: it engages only when both the session model and the
|
|
31
|
+
candidate carry real prices and the candidate is strictly cheaper. Without that
|
|
32
|
+
rule an unpriced catalog let the profile's order silently *upgrade* a haiku
|
|
33
|
+
session to sonnet-class. The price-ranked fallback (for when the profile has
|
|
34
|
+
gone stale) additionally requires one of the known small-model name words and
|
|
35
|
+
ranks those tiers ahead of raw price — otherwise the absolute cheapest
|
|
36
|
+
contained model became the default coding worker, which on a mainstream catalog
|
|
37
|
+
is nano/lite-tier or something wholly unknown. And once any *explicit* choice
|
|
38
|
+
(agent frontmatter or configured default) fails to resolve, automatic selection
|
|
39
|
+
stays out of it: substituting a cheaper vetted model for a model somebody named
|
|
40
|
+
is a model nobody described, so the remaining chain and then the session model
|
|
41
|
+
serve.
|
|
42
|
+
|
|
43
|
+
The profile inventory was checked on 2026-08-06 against pi 0.84.0's generated
|
|
44
|
+
catalog and official model pages: [Anthropic](https://platform.claude.com/docs/en/about-claude/models/overview),
|
|
45
|
+
[OpenAI](https://developers.openai.com/api/docs/models),
|
|
46
|
+
[Google](https://ai.google.dev/gemini-api/docs/models),
|
|
47
|
+
[xAI](https://docs.x.ai/developers/models),
|
|
48
|
+
[Mistral](https://docs.mistral.ai/getting-started/models/models_overview/),
|
|
49
|
+
[DeepSeek](https://api-docs.deepseek.com/quick_start/pricing),
|
|
50
|
+
[Z.AI](https://docs.z.ai/), [Qwen Coding Plan](https://help.aliyun.com/en/model-studio/coding-plan),
|
|
51
|
+
[Kimi](https://platform.kimi.ai/docs/models),
|
|
52
|
+
[MiniMax](https://platform.minimax.io/docs/guides/models-intro),
|
|
53
|
+
[Xiaomi](https://mimo.mi.com/docs/quick-start/summary/model), and
|
|
54
|
+
[Ant Ling](https://developer.ant-ling.com/en/docs/models/ling/). Hosted-profile
|
|
55
|
+
IDs came from the official catalogs for [NVIDIA NIM](https://build.nvidia.com/models),
|
|
56
|
+
[Groq](https://console.groq.com/docs/models),
|
|
57
|
+
[Cerebras](https://inference-docs.cerebras.ai/models/overview),
|
|
58
|
+
[Fireworks](https://fireworks.ai/models),
|
|
59
|
+
[Together](https://docs.together.ai/docs/serverless/models),
|
|
60
|
+
[Baseten](https://www.baseten.co/products/model-apis/), and
|
|
61
|
+
[Cloudflare Workers AI](https://developers.cloudflare.com/workers-ai/models/).
|
|
62
|
+
The lists are intentionally short prefixes applied to the *live authenticated
|
|
63
|
+
catalog*, not copied catalogs; unavailable entries simply fall through.
|
|
64
|
+
|
|
65
|
+
All 39 built-in pi language-model provider IDs have an explicit policy. Direct
|
|
66
|
+
vendors contain to the provider. Stable hosted inference services such as Groq
|
|
67
|
+
and NVIDIA contain to that host even when IDs carry publisher-like prefixes.
|
|
68
|
+
Gateways require more care:
|
|
69
|
+
|
|
70
|
+
- OpenRouter's `creator/model` prefix identifies a model namespace, **not the
|
|
71
|
+
serving inference provider**; automatic switching stays in that namespace and
|
|
72
|
+
reuses its direct-family profile, but One Code cannot claim processor
|
|
73
|
+
containment because pi exposes none of OpenRouter's provider-routing options
|
|
74
|
+
([official routing semantics](https://openrouter.ai/docs/guides/routing/provider-selection)).
|
|
75
|
+
- Vercel likewise separates creator IDs from serving providers; profile reuse
|
|
76
|
+
stays in the creator namespace ([provider options](https://vercel.com/docs/ai-gateway/models-and-providers/provider-options)).
|
|
77
|
+
- Cloudflare's generated pi catalog strips some upstream prefixes, so route
|
|
78
|
+
identity comes from API/base transport rather than slash parsing
|
|
79
|
+
([provider integrations](https://developers.cloudflare.com/ai-gateway/providers/)).
|
|
80
|
+
- Bedrock containment includes geography plus family: an automatic choice may
|
|
81
|
+
not move from `us.` to `eu.` or `global.`. Opaque application-profile ARNs
|
|
82
|
+
stay on the session model ([model IDs](https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html)).
|
|
83
|
+
- Hugging Face, Radius, OpenCode Zen, OpenCode Go, unknown aliases, and custom
|
|
84
|
+
providers whose route cannot be established are session-only automatically.
|
|
85
|
+
Hugging Face repository IDs in particular do not name the serving inference
|
|
86
|
+
provider unless a provider suffix is pinned
|
|
87
|
+
([routing docs](https://huggingface.co/docs/inference-providers/en/index)).
|
|
88
|
+
|
|
89
|
+
With no explicit default, an OpenAI Codex Sol session now plans both its
|
|
90
|
+
classifier and delegated workers on Luna. The banner labels the latter `(auto)`
|
|
91
|
+
rather than implying it came from settings. `workflow/index.ts` passes the same
|
|
92
|
+
user/managed default into `AgentRunner`, and workflow calls no longer bypass
|
|
93
|
+
the automatic resolver when `model` is omitted.
|
|
94
|
+
|
|
95
|
+
Verified against fresh pi processes: the TUI banner showed `model 5.6-sol ·
|
|
96
|
+
subagents 5.6-luna (auto)`; an omitted-model foreground subagent recorded
|
|
97
|
+
`openai-codex/gpt-5.6-luna`; and an omitted-model synchronous workflow completed
|
|
98
|
+
on the same low-cost path. Luna's classifier was exercised in both directions
|
|
99
|
+
without `--dangerously-skip-permissions`: an exact user-named `/tmp` write was
|
|
100
|
+
allowed by verified intent, while a delegated outside-project backup path was
|
|
101
|
+
blocked under S5. Typecheck and all 682 unit tests passed.
|
|
102
|
+
|
|
103
|
+
## Upward cost pressure: an informational warning and a per-call gate
|
|
104
|
+
|
|
105
|
+
Automatic selection only ever moves cost *down*, but two paths still let a
|
|
106
|
+
subagent run on something pricier than the session model: a configured default
|
|
107
|
+
(deliberate cheap-driver/strong-worker setups exist) and the per-call `model`
|
|
108
|
+
field (chosen by the main model itself). Those get different treatment because
|
|
109
|
+
their authors differ:
|
|
110
|
+
|
|
111
|
+
- **Configured default pricier than the session model** → one informational
|
|
112
|
+
line in the every-turn reminder, with both prices. It suggests a cheaper
|
|
113
|
+
listed model for routine tasks but does not tell the model to override the
|
|
114
|
+
user's knob — the user set it, and second-guessing a user setting from the
|
|
115
|
+
reminder would invert authority.
|
|
116
|
+
- **Per-call `model` pricier than the session model** → the subagent tool
|
|
117
|
+
errors with both prices and the menu, and workflow `agent()` throws, unless
|
|
118
|
+
the call sets `allow_expensive: true` (`allowExpensive` in agent() opts). The
|
|
119
|
+
schema tells the model to set it only when the user explicitly asked for that
|
|
120
|
+
model. Only `source: "call"` resolutions are gated: `subagentModel`, the env
|
|
121
|
+
var, agent-file frontmatter, and `inherit` are user-installed choices, and
|
|
122
|
+
the codebase rule is that naming a model is choosing it.
|
|
123
|
+
|
|
124
|
+
The gate opens when either price is unknown — a cost gate that fails closed on
|
|
125
|
+
an unpriced catalog blocks the feature outright, the same failure shape as
|
|
126
|
+
gating a tool other tools sit behind.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Modes & keybindings
|
|
2
|
+
|
|
3
|
+
Part of [Decisions](../decisions.md).
|
|
4
|
+
|
|
5
|
+
## `/effort`, and ultracode as a standing mode
|
|
6
|
+
|
|
7
|
+
Claude Code exposes reasoning effort as a Faster→Smarter slider whose last stop,
|
|
8
|
+
past `max` and behind a divider, is `ultracode` — subtitled "xhigh + workflows".
|
|
9
|
+
The divider is the whole point: every other stop buys more thinking, while that
|
|
10
|
+
one also changes *how the model works*, arming multi-agent orchestration on every
|
|
11
|
+
turn instead of the single turn the keyword arms. One Code now has the same
|
|
12
|
+
command, and the same two-tier opt-in: the keyword in a message for one turn
|
|
13
|
+
(`extensions/workflow`), `/effort ultracode` for a standing mode that persists
|
|
14
|
+
until switched off.
|
|
15
|
+
|
|
16
|
+
The mode is implemented the way the permissions extension implements plan mode —
|
|
17
|
+
an `every-turn` reminder on `one-code:system-reminder` under a key, withdrawn with
|
|
18
|
+
`{remove: true}` when leaving — plus `ctx.ui.setStatus` for a `✦ ultracode`
|
|
19
|
+
footer indicator. Its standing text is deliberately stronger than the keyword's
|
|
20
|
+
one-turn nudge: orchestrate substantive work *by default* and verify
|
|
21
|
+
adversarially, with trivial and conversational turns carved out so the mode does
|
|
22
|
+
not spawn a fleet to rename a variable.
|
|
23
|
+
|
|
24
|
+
The slider itself is a hand-rolled `Component` passed to `ctx.ui.custom()`,
|
|
25
|
+
following branding's precedent of implementing pi-tui's interface inline. That is
|
|
26
|
+
not stylistic: `@earendil-works/pi-tui` is a regular dependency of
|
|
27
|
+
pi-coding-agent rather than a peer, so it is unhoisted and unresolvable from this
|
|
28
|
+
package — pi's own thinking picker is a vertical `SelectList`, which we cannot
|
|
29
|
+
import and which is the wrong shape anyway. The cost is decoding keys from raw
|
|
30
|
+
bytes (`handleInput` hands over terminal data, not key names) and owning the
|
|
31
|
+
layout, so the marker row, labels, and track are laid out against measured
|
|
32
|
+
column positions and collapse to a single line rather than wrapping when the pane
|
|
33
|
+
is too narrow.
|
|
34
|
+
|
|
35
|
+
One live finding shaped the design. `setThinkingLevel` **clamps to model
|
|
36
|
+
capability silently** — no throw, no warning, and `thinking_level_select` fires
|
|
37
|
+
after the fact without a veto — so asking for xhigh on haiku-4-5 quietly yields
|
|
38
|
+
`high`. Rather than report a level the model never accepted, the command sets,
|
|
39
|
+
reads back, and says so: "Effort: ultracode — high reasoning, workflows armed
|
|
40
|
+
every turn (this model caps reasoning at high)". That also fixed a subtler bug:
|
|
41
|
+
the shift+tab hygiene (drop the mode when the user cycles thinking away, so the
|
|
42
|
+
footer cannot lie) originally compared against xhigh, which on a clamping model
|
|
43
|
+
would have made ultracode cancel itself the moment anything re-announced the
|
|
44
|
+
clamped level. It compares against the level the mode actually settled on.
|
|
45
|
+
|
|
46
|
+
Verified: 19 unit tests over the pure slider (choice/level mapping, both arrow
|
|
47
|
+
encodings, clamped movement, marker alignment under the selected label, the
|
|
48
|
+
ultracode subtitle appearing only when selected, narrow-pane degradation) and a
|
|
49
|
+
live tmux TUI run — the slider opened preselected at the current level, arrow
|
|
50
|
+
keys moved the marker, `ultracode` revealed its subtitle, Enter applied it with
|
|
51
|
+
the clamp reported honestly, `✦ ultracode` appeared in the footer, and a
|
|
52
|
+
`CC_E2E_LOG` capture confirmed the standing reminder in **both** subsequent
|
|
53
|
+
turns' payloads rather than just the first.
|
|
54
|
+
|
|
55
|
+
## Aligning `/effort` with shift+tab
|
|
56
|
+
|
|
57
|
+
The first cut left two dials disagreeing: shift+tab cycled pi's seven thinking
|
|
58
|
+
levels and called it "thinking", while `/effort` showed Claude Code's five plus
|
|
59
|
+
ultracode and called it "effort". Same underlying number, two names, two sets.
|
|
60
|
+
|
|
61
|
+
Taking over the key is not available: `app.thinking.cycle` is on pi's
|
|
62
|
+
`RESERVED_KEYBINDINGS_FOR_EXTENSION_CONFLICTS` list, so `registerShortcut` on
|
|
63
|
+
shift+tab is skipped with a warning rather than honoured, and no extension API
|
|
64
|
+
unbinds or retargets a reserved key (only the user's own keybindings config can).
|
|
65
|
+
Disabling it was therefore off the table too, which settles the direction:
|
|
66
|
+
conform to the key rather than compete with it. The slider shows the full `off`→
|
|
67
|
+
`max` ladder plus `ultracode` past the divider, but **enables only the stops the
|
|
68
|
+
active model can reach**: pi filters shift+tab's own cycle through
|
|
69
|
+
`getSupportedThinkingLevels(model)` (a per-model set derived from the model's
|
|
70
|
+
`thinkingLevelMap`, see findings §3), so a fixed seven-stop ladder disagreed with
|
|
71
|
+
shift+tab on every model that doesn't support all of them. Unsupported stops stay
|
|
72
|
+
on the track dimmed and are skipped by navigation, with a `dimmed = unsupported by
|
|
73
|
+
<model>` note; everything we write calls the dial "effort" (banner hint included;
|
|
74
|
+
pi's own footer still says what it says), and the footer names shift+tab as the
|
|
75
|
+
shortcut for the plain stops, so the two read as one control with two entry points
|
|
76
|
+
rather than two competing settings.
|
|
77
|
+
|
|
78
|
+
Building the slider from the fixed ladder rather than the model's set was a live
|
|
79
|
+
bug: on `deepseek-v4-flash` (supported levels `{high, max}`) picking `low` made
|
|
80
|
+
pi's `clampThinkingLevel` snap *upward* to `high`, which the old code mislabelled
|
|
81
|
+
"caps reasoning at high". A downward clamp (the model genuinely can't reach the
|
|
82
|
+
requested level) is now the only thing flagged — "tops out at" — and the typed
|
|
83
|
+
path (`/effort low`) refuses an unreachable level, naming what the model does
|
|
84
|
+
support, instead of silently rerouting it. Verified live against
|
|
85
|
+
`deepseek-v4-flash`.
|
|
86
|
+
|
|
87
|
+
Widening the track from six stops to eight is what exposed a latent bug: the
|
|
88
|
+
layout only checked whether the *track* fit the pane, so the longer hint line
|
|
89
|
+
overflowed at 80 columns. Width is now measured on the unstyled text of every row
|
|
90
|
+
(escape codes would inflate the count), and anything that does not fit collapses
|
|
91
|
+
to a clipped one-line stop list — verified by a test that walks every stop at
|
|
92
|
+
widths from 200 down to 10.
|
|
93
|
+
|
|
94
|
+
## Permission-mode cycling on ctrl+q, not shift+tab, ctrl+m, or alt+m
|
|
95
|
+
|
|
96
|
+
Claude Code cycles permission modes with shift+tab; typing
|
|
97
|
+
`/permission-mode acceptEdits` for the same switch was friction worth removing.
|
|
98
|
+
The obvious key is taken: pi reserves shift+tab for `app.thinking.cycle` (see
|
|
99
|
+
"Aligning `/effort` with shift+tab" above), and freeing it would mean writing
|
|
100
|
+
`app.thinking.cycle: []` into the user's `~/.pi/agent/keybindings.json` — a
|
|
101
|
+
package silently editing user config to defeat a deliberate reservation. That
|
|
102
|
+
was prototyped and rejected in favour of keeping pi's default: shift+tab stays
|
|
103
|
+
the effort dial.
|
|
104
|
+
|
|
105
|
+
ctrl+m was considered next and is impossible at the protocol level: ctrl+m *is*
|
|
106
|
+
carriage return (0x0D) in terminal encoding, so outside kitty-protocol
|
|
107
|
+
terminals `matchesKey("\r", "ctrl+m")` is true and every Enter press would
|
|
108
|
+
cycle the mode. pi-tui's `rawCtrlChar` confirms there is no special-casing.
|
|
109
|
+
alt+m — Claude Code's own documented fallback binding, used on Windows when VT
|
|
110
|
+
input is unavailable — shipped briefly and was rejected for macOS ergonomics:
|
|
111
|
+
option+m types "µ" unless the terminal is configured to send option as Meta,
|
|
112
|
+
which is off by default everywhere and not a setting to ask users for.
|
|
113
|
+
|
|
114
|
+
That leaves the ctrl+letters, and pi plus the terminal claim nearly all of
|
|
115
|
+
them: a/b/e/f/k/u/w/y (editor), c/d/z (clear/exit/suspend), g/l/n/o/p/t/v/x
|
|
116
|
+
(pi app keys), r/s (session picker), and h/i/j/m are Backspace/Tab/LF/CR at
|
|
117
|
+
the byte level. **ctrl+q** is the one left standing — historically XON flow
|
|
118
|
+
control, but pi's raw-mode TUI disables IXON so it arrives everywhere,
|
|
119
|
+
macOS included, with no configuration.
|
|
120
|
+
|
|
121
|
+
The modes themselves follow Claude Code v2.1 (verified against
|
|
122
|
+
code.claude.com/docs/en/permission-modes.md): `default` is displayed as
|
|
123
|
+
"manual" and `manual` is accepted as an alias everywhere a mode is named; the
|
|
124
|
+
cycle is manual → acceptEdits → plan, with bypassPermissions joining only when
|
|
125
|
+
the session started with it; `dontAsk` (deny instead of prompting; never in
|
|
126
|
+
the cycle) is accepted via flag/settings; footer badges use Claude Code's
|
|
127
|
+
exact strings (`⏸ manual mode on`, `⏵⏵ accept edits on`, …) via
|
|
128
|
+
`ctx.ui.setStatus`. Claude Code's `auto` mode is deliberately not implemented:
|
|
129
|
+
it is gated on a server-side approval classifier we have no equivalent of, and
|
|
130
|
+
Claude Code itself drops it from the cycle when unavailable, so its absence is
|
|
131
|
+
faithful rather than a gap. `/permission-mode` is gone; mode-change reminders
|
|
132
|
+
are keyed so cycling through several modes announces only the one settled on.
|
|
133
|
+
Verified in a live tmux TUI: badge at startup, three-stop cycle, bypass
|
|
134
|
+
joining under `--dangerously-skip-permissions`, and `/permission` completing
|
|
135
|
+
to only `/permissions`.
|
|
136
|
+
|
|
137
|
+
## Plan mode is file-based, like current Claude Code
|
|
138
|
+
|
|
139
|
+
Claude Code moved plan mode from "pass the plan as an ExitPlanMode parameter"
|
|
140
|
+
to a **plan file**: entering plan mode allocates `~/.one-code/plans/<slug>.md`,
|
|
141
|
+
a per-turn system message names it as the one writable path and prescribes an
|
|
142
|
+
explore→design→review→write→approve workflow, and ExitPlanMode takes no
|
|
143
|
+
parameters — it reads the file. Observed live in Claude Code and mirrored here: the plan
|
|
144
|
+
survives compaction, the user can open/edit it, and long plans stop bloating
|
|
145
|
+
tool calls and `ui.select` titles.
|
|
146
|
+
|
|
147
|
+
**Who owns what.** Mode stays with `permissions`; the file belongs to
|
|
148
|
+
`plan-mode`. Allocation happens in `before_agent_start` — the one hook that
|
|
149
|
+
covers all three entry points (tool, ctrl+q, `defaultMode: "plan"`) and runs
|
|
150
|
+
after every extension's `session_start`, so restoring the branch's previous
|
|
151
|
+
path (a `plan-mode-file` custom entry, read via `getBranch()` like tasks/todo)
|
|
152
|
+
can never race a fresh allocation. The path crosses to the matcher over a new
|
|
153
|
+
`one-code:plan-file-path` channel, mirroring `one-code:set-permission-mode` in the
|
|
154
|
+
other direction; `plan-mode` learns the mode from the existing
|
|
155
|
+
`one-code:permission-status` channel rather than a shared module (jiti).
|
|
156
|
+
|
|
157
|
+
**The reminder key moved.** `setMode` no longer emits plan mode's reminder;
|
|
158
|
+
`plan-mode` re-emits under the same `"permission-mode"` key every turn, with
|
|
159
|
+
`existsSync` flipping "create it at …" to "continue building …" the turn after
|
|
160
|
+
the file appears. Every-turn re-emits replace by key, so nothing accumulates.
|
|
161
|
+
|
|
162
|
+
**The carve-out allows outright.** In plan mode a write whose subject (or
|
|
163
|
+
symlink-resolved subject) is the plan file returns `allow`, and everything
|
|
164
|
+
else keeps denying. Outside plan mode the file has no special status, matching
|
|
165
|
+
Claude Code. Deny rules still beat the carve-out. (`.one-code` itself is a
|
|
166
|
+
protected dir like `.claude`; `.one-code/plans` is excepted as working space —
|
|
167
|
+
plan documents are rendered to the user, never executed.)
|
|
168
|
+
|
|
169
|
+
**Slug fidelity note.** Claude Code's real slugs start with the user's opening
|
|
170
|
+
words plus two random words (`we-are-going-to-async-turtle.md`); One Code uses
|
|
171
|
+
three random words. Deriving from the prompt was skipped — the slug is
|
|
172
|
+
allocated before any user text is guaranteed to exist (ctrl+q, defaultMode).
|
|
173
|
+
|
|
174
|
+
Verified live: an RPC run (no `--dangerously-skip-permissions`) showed a
|
|
175
|
+
blocked ordinary write and an unprompted plan-file write in plan mode; a tmux
|
|
176
|
+
TUI run exercised the scrollable approval viewer (scroll, choice switch,
|
|
177
|
+
reject→stay-planning, Enter→approve→manual mode) and ctrl+q entry reusing the
|
|
178
|
+
session's existing file.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Skills & plugins
|
|
2
|
+
|
|
3
|
+
Part of [Decisions](../decisions.md).
|
|
4
|
+
|
|
5
|
+
## Skills and plugins
|
|
6
|
+
|
|
7
|
+
**The `skill` tool.** pi's own skill mechanism lists skills in the system prompt
|
|
8
|
+
and expects the model to `read` the SKILL.md path. Claude Code instead exposes a
|
|
9
|
+
`Skill` tool that returns the instructions as a tool result, which is what makes
|
|
10
|
+
`/skill-name` invocation work. `extensions/skill/` adds that: it indexes what pi
|
|
11
|
+
already discovered (via `before_agent_start`'s `systemPromptOptions.skills`,
|
|
12
|
+
rather than rediscovering) plus plugin skills, and returns the body with the
|
|
13
|
+
skill's own path so relative `references/` and `scripts/` remain readable.
|
|
14
|
+
|
|
15
|
+
**Plugins.** `extensions/plugins/` reads `~/.claude/plugins/installed_plugins.json`
|
|
16
|
+
and wires each plugin's resources in, namespaced the way Claude Code namespaces
|
|
17
|
+
them so two plugins can both ship a `commit`:
|
|
18
|
+
|
|
19
|
+
| Plugin directory | Becomes |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `agents/*.md` | subagents, as `<plugin>:<agent>` |
|
|
22
|
+
| `skills/<name>/SKILL.md` | skills, as `<plugin>:<skill>` |
|
|
23
|
+
| `commands/*.md` | slash commands, as `/<plugin>:<command>` |
|
|
24
|
+
| `.mcp.json` | MCP servers |
|
|
25
|
+
|
|
26
|
+
Command templates get full Claude Code expansion: `$ARGUMENTS`, `$@`, positional
|
|
27
|
+
`$1`, and **`` !`command` `` substitution**, which is what makes plugin commands
|
|
28
|
+
like `commit` work — the template gathers `git status` and `git diff` before the
|
|
29
|
+
model sees the prompt. The expanded text is delivered as a user turn.
|
|
30
|
+
|
|
31
|
+
Verified against a real installation (7 plugins): 7 namespaced agents, 3 skills
|
|
32
|
+
listed and invocable, 7 commands registered, 2 MCP configs picked up, and
|
|
33
|
+
`/commit-commands:commit` expanded with live git output embedded.
|
|
34
|
+
|
|
35
|
+
Three findings from this work, each of which had been silently wrong:
|
|
36
|
+
|
|
37
|
+
1. **Module state is not shared between extension files.** Under jiti each
|
|
38
|
+
extension gets its own module instance, so a shared registry singleton written
|
|
39
|
+
by one extension reads as *empty* in another — no error, just nothing. The
|
|
40
|
+
deferred-tool registry only worked because its data crossed via the event bus.
|
|
41
|
+
Plugin discovery is therefore a function each consumer calls for itself
|
|
42
|
+
(`discoverPlugins`, memoised per module).
|
|
43
|
+
2. **Real frontmatter is not always valid YAML.** pi's parser threw
|
|
44
|
+
"Nested mappings are not allowed in compact mappings" on a genuine plugin
|
|
45
|
+
agent whose unquoted `description:` contained `: `, and our `catch` dropped the
|
|
46
|
+
whole definition — the file lost this way was, aptly,
|
|
47
|
+
`silent-failure-hunter.md`. `parseAgentFile` now falls back to line-wise
|
|
48
|
+
extraction. Also: `model: inherit` means "use the session model", not a model
|
|
49
|
+
id.
|
|
50
|
+
3. **Never gate a loader behind a permission prompt.** The `skill` tool was
|
|
51
|
+
blocked by our own permission system in non-interactive runs, and the same
|
|
52
|
+
applied to `tool_search` — which would have made every deferred tool
|
|
53
|
+
unreachable by default. Both are now auto-allowed along with `lsp_diagnostics`
|
|
54
|
+
and `list_mcp_resources`; network egress and MCP calls still ask. This class of
|
|
55
|
+
bug was invisible because earlier end-to-end tests all ran with
|
|
56
|
+
`--dangerously-skip-permissions`.
|