@oh-my-pi/pi-coding-agent 9.3.1 → 9.6.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.
Files changed (88) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/examples/hooks/snake.ts +5 -5
  3. package/package.json +9 -8
  4. package/src/capability/index.ts +7 -9
  5. package/src/cli/config-cli.ts +86 -73
  6. package/src/cli/update-cli.ts +45 -3
  7. package/src/commit/agentic/agent.ts +4 -4
  8. package/src/commit/agentic/index.ts +6 -5
  9. package/src/commit/agentic/tools/analyze-file.ts +5 -7
  10. package/src/commit/agentic/tools/index.ts +3 -3
  11. package/src/commit/model-selection.ts +13 -17
  12. package/src/commit/pipeline.ts +5 -5
  13. package/src/config/model-registry.ts +7 -0
  14. package/src/config/settings-schema.ts +836 -0
  15. package/src/config/settings.ts +702 -0
  16. package/src/discovery/helpers.ts +55 -11
  17. package/src/exa/index.ts +1 -1
  18. package/src/exec/bash-executor.ts +13 -13
  19. package/src/exec/shell-session.ts +15 -3
  20. package/src/export/ttsr.ts +1 -1
  21. package/src/extensibility/skills.ts +40 -9
  22. package/src/index.ts +2 -10
  23. package/src/ipy/gateway-coordinator.ts +5 -159
  24. package/src/ipy/kernel.ts +6 -171
  25. package/src/ipy/runtime.ts +198 -0
  26. package/src/lsp/client.ts +14 -1
  27. package/src/lsp/defaults.json +0 -6
  28. package/src/lsp/index.ts +1 -1
  29. package/src/lsp/types.ts +2 -0
  30. package/src/main.ts +26 -48
  31. package/src/modes/components/armin.ts +7 -7
  32. package/src/modes/components/extensions/extension-dashboard.ts +33 -13
  33. package/src/modes/components/extensions/extension-list.ts +2 -2
  34. package/src/modes/components/footer.ts +5 -5
  35. package/src/modes/components/history-search.ts +2 -1
  36. package/src/modes/components/hook-selector.ts +2 -2
  37. package/src/modes/components/index.ts +1 -1
  38. package/src/modes/components/model-selector.ts +7 -7
  39. package/src/modes/components/session-selector.ts +2 -1
  40. package/src/modes/components/settings-defs.ts +210 -915
  41. package/src/modes/components/settings-selector.ts +80 -106
  42. package/src/modes/components/status-line/types.ts +2 -8
  43. package/src/modes/components/status-line-segment-editor.ts +4 -4
  44. package/src/modes/components/status-line.ts +28 -5
  45. package/src/modes/components/welcome.ts +3 -3
  46. package/src/modes/controllers/command-controller.ts +2 -2
  47. package/src/modes/controllers/event-controller.ts +9 -8
  48. package/src/modes/controllers/input-controller.ts +19 -15
  49. package/src/modes/controllers/selector-controller.ts +30 -14
  50. package/src/modes/interactive-mode.ts +10 -10
  51. package/src/modes/rpc/rpc-mode.ts +10 -0
  52. package/src/modes/rpc/rpc-types.ts +3 -0
  53. package/src/modes/types.ts +2 -2
  54. package/src/modes/utils/ui-helpers.ts +4 -3
  55. package/src/patch/index.ts +7 -7
  56. package/src/patch/normalize.ts +3 -1
  57. package/src/prompts/system/plan-mode-active.md +5 -4
  58. package/src/prompts/system/system-prompt.md +0 -1
  59. package/src/prompts/tools/bash.md +12 -2
  60. package/src/prompts/tools/task.md +180 -73
  61. package/src/sdk.ts +38 -61
  62. package/src/session/agent-session.ts +66 -55
  63. package/src/session/agent-storage.ts +1 -1
  64. package/src/session/session-manager.ts +10 -10
  65. package/src/system-prompt.ts +2 -2
  66. package/src/task/executor.ts +9 -9
  67. package/src/task/index.ts +2 -2
  68. package/src/tools/ask.ts +5 -6
  69. package/src/tools/bash-interceptor.ts +39 -1
  70. package/src/tools/bash-normalize.ts +126 -0
  71. package/src/tools/bash.ts +31 -5
  72. package/src/tools/find.ts +51 -33
  73. package/src/tools/gemini-image.ts +7 -8
  74. package/src/tools/index.ts +5 -23
  75. package/src/tools/plan-mode-guard.ts +1 -6
  76. package/src/tools/python.ts +29 -4
  77. package/src/tools/read.ts +2 -2
  78. package/src/tools/write.ts +2 -2
  79. package/src/tui/output-block.ts +2 -2
  80. package/src/tui/utils.ts +2 -2
  81. package/src/utils/ignore-files.ts +119 -0
  82. package/src/web/search/auth.ts +6 -58
  83. package/src/web/search/index.ts +2 -6
  84. package/src/web/search/providers/anthropic.ts +6 -6
  85. package/src/web/search/providers/exa.ts +2 -62
  86. package/src/web/search/providers/perplexity.ts +7 -53
  87. package/examples/sdk/10-settings.ts +0 -37
  88. package/src/config/settings-manager.ts +0 -2015
@@ -1,22 +1,183 @@
1
1
  # Task
2
2
 
3
- Launch a new agent to handle complex, multi-step tasks autonomously. Each agent type has specific capabilities and tools available to it.
3
+ Launch agents to handle complex, multi-step tasks autonomously.
4
4
 
5
5
  <critical>
6
- This matters. Get it right.
6
+ ## Context is everything
7
7
 
8
- Subagents have NO access to conversation history. They only see:
9
- 1. Their agent-specific system prompt
10
- 2. The `context` string you provide
11
- 3. The `task` string you provide
8
+ Subagents fail when context is vague. They cannot read your mind or infer project conventions. Every task needs:
9
+ 1. **Goal** - What this accomplishes (one sentence)
10
+ 2. **Constraints** - Hard requirements, banned approaches, naming conventions
11
+ 3. **Existing Code** - File paths and function signatures to use as patterns
12
+ 4. **API Contract** - If the task produces or consumes an interface, spell it out
12
13
 
13
- Use a single Task call with multiple `tasks` entries when parallelizing. Multiple concurrent Task calls bypass coordination.
14
+ Subagents CAN grep the parent conversation file for supplementary details. They CANNOT grep for:
15
+ - Decisions you made but didn't write down
16
+ - Conventions that exist only in your head
17
+ - Which of 50 possible approaches you want
18
+ **Rule of thumb:** If you'd need to answer a clarifying question for a junior dev to do this task, that information belongs in context.
19
+ </critical>
14
20
 
15
- For code changes, have subagents write files directly with Edit/Write. Do not ask them to return patches for you to apply.
21
+ <context-structure>
22
+ ## Required context structure
16
23
 
17
- Agents with `output="structured"` enforce their own schema; the `schema` parameter is ignored for those agents.
18
- **Never describe expected output in `context` or task descriptions.** All response format requirements go in the `schema` parameter. Use structured schemas with typed properties—not `{ "type": "string" }`. Prose like "respond as a bullet list" is prohibited.
19
- </critical>
24
+ Use this template. Sections can be omitted only if truly N/A.
25
+
26
+ ````
27
+ ## Goal
28
+ [One sentence: what this task accomplishes]
29
+
30
+ ## Constraints
31
+ - [Hard requirements - MUST/MUST NOT style]
32
+ - [API conventions, naming patterns, error handling]
33
+ - [What already exists vs what to create]
34
+
35
+ ## Existing Code
36
+ Reference files the agent MUST read or use as patterns:
37
+ - `path/to/file.ts` - [what pattern it demonstrates]
38
+ - `path/to/other.rs` - [what to reuse from it]
39
+
40
+ ## API Contract (if applicable)
41
+ ```language
42
+ // Exact signatures the agent must implement or consume
43
+ fn example(input: Type) -> Result<Output>
44
+ ```
45
+
46
+ ## Task
47
+ {{description}}
48
+
49
+ ## Files
50
+ {{files}}
51
+ ````
52
+
53
+ ### Bad context (agent will fail or guess wrong)
54
+
55
+ ```
56
+ N-API migration. Keep highlight sync. Use JsString. No WASM.
57
+ Task: {{description}} Files: {{files}}
58
+ ```
59
+
60
+ Why it fails:
61
+ - No existing code to reference - agent doesn't know your patterns
62
+ - No API contract - agent will invent signatures that don't match consumers
63
+ - No goal - agent doesn't know what success looks like
64
+ - "Keep highlight sync" is meaningless without knowing what highlight is or where it lives
65
+
66
+ ### Good context (agent can act confidently)
67
+
68
+ ````
69
+ ## Goal
70
+ Port grep module from WASM to N-API, matching existing text module patterns.
71
+
72
+ ## Constraints
73
+ - Use `#[napi]` attribute macro on all exports (not `#[napi(js_name = "...")]`)
74
+ - Return `napi::Result<T>` for fallible ops, never panic
75
+ - Use `spawn_blocking` for any operation that touches filesystem or runs >1ms
76
+ - Accept `JsString` for string params (NOT JsStringUtf8 - it has lifetime issues)
77
+ - Keep all existing function names - TS bindings depend on them
78
+ - No new crate dependencies
79
+
80
+ ## Existing Code
81
+ - `crates/pi-natives/src/text.rs` - reference N-API pattern: see how `visible_width` uses JsString
82
+ - `crates/pi-natives/src/lib.rs` - module registration pattern
83
+ - `crates/pi-natives/Cargo.toml` - available dependencies (ignore, regex already present)
84
+
85
+ ## API Contract
86
+ Current sync API to convert to async:
87
+ ```rust
88
+ // BEFORE (sync, blocks event loop)
89
+ #[napi]
90
+ pub fn search(pattern: String, path: String) -> Vec<Match>
91
+
92
+ // AFTER (async, uses spawn_blocking)
93
+ #[napi]
94
+ pub async fn search(pattern: JsString, path: JsString, env: Env) -> napi::Result<Vec<Match>>
95
+ ```
96
+
97
+ ## Task
98
+ {{description}}
99
+
100
+ ## Files
101
+ {{files}}
102
+ ````
103
+ </context-structure>
104
+
105
+ <parallelization>
106
+ ## When to parallelize vs sequence
107
+ **The test:** Can agent B write correct code without seeing agent A's output?
108
+ - If YES → parallelize
109
+ - If NO → sequence (A completes, then B runs with A's output in context)
110
+
111
+ ### Dependency patterns that MUST be sequential
112
+
113
+ |First|Then|Why|
114
+ |---|---|---|
115
+ |Create Rust API|Update TS bindings|Bindings need to know export names and signatures|
116
+ |Define interface/types|Implement consumers|Consumers need the contract|
117
+ |Scaffold with signatures|Implement bodies|Implementations need the shape|
118
+ |Core module|Dependent modules|Dependents import from core|
119
+
120
+ ### Safe to parallelize
121
+ - Independent modules that don't import each other
122
+ - Tests for already-implemented code
123
+ - Documentation for stable APIs
124
+ - Refactors in isolated file scopes
125
+
126
+ ### Phased execution pattern
127
+
128
+ For migrations/refactors with layers:
129
+ **Phase 1 - Foundation (do yourself or single task):**
130
+ Create the scaffold, define interfaces, establish API shape. Never fan out until the contract is known.
131
+ **Phase 2 - Parallel implementation:**
132
+ Fan out to independent tasks that all consume the same known interface. Include the API contract from Phase 1 in every task's context.
133
+ **Phase 3 - Integration (do yourself):**
134
+ Wire things together, update build/CI, fix any mismatches.
135
+ **Phase 4 - Dependent layer:**
136
+ Fan out again for work that consumes Phase 2 outputs.
137
+
138
+ ### Example: WASM to N-API migration
139
+ **WRONG** (launched together, will fail):
140
+
141
+ ```
142
+ tasks: [
143
+ { id: "RustApi", description: "Implement N-API exports" },
144
+ { id: "TsBindings", description: "Update TS to use N-API" }, // ← needs RustApi output!
145
+ ]
146
+ ```
147
+ **RIGHT** (phased):
148
+
149
+ ```
150
+ // Phase 1: You create scaffold with signatures in lib.rs
151
+
152
+ // Phase 2: Fan out Rust implementation
153
+ tasks: [
154
+ { id: "Grep", description: "Implement grep module", args: { files: "src/grep.rs" } },
155
+ { id: "Text", description: "Implement text module", args: { files: "src/text.rs" } },
156
+ // Each task gets the API contract you defined in Phase 1
157
+ ]
158
+
159
+ // Phase 3: You verify Rust compiles, exports are correct
160
+
161
+ // Phase 4: Fan out TS bindings (now they know what Rust exports)
162
+ tasks: [
163
+ { id: "GrepBindings", description: "Update grep TS", args: { files: "src/grep/index.ts" } },
164
+ // Context includes actual export names from Phase 2
165
+ ]
166
+ ```
167
+ </parallelization>
168
+
169
+ <parameters>
170
+ - `agent`: Agent type for all tasks
171
+ - `context`: Template with `{{placeholders}}`. **Must follow the structure above.** Include Goal, Constraints, Existing Code references. Subagents can search parent context for background, but core requirements must be explicit here.
172
+ - `isolated`: (optional) Run in git worktree, return patches
173
+ - `tasks`: Array of `{id, description, args}`
174
+ - `id`: CamelCase identifier (max 32 chars)
175
+ - `description`: What the task does (for logging)
176
+ - `args`: Object with keys matching `{{placeholders}}` in context
177
+ - `skills`: (optional) Skill names to preload
178
+ - `schema`: JTD schema for response structure. **Required.** Use typed properties, not `{ "type": "string" }`.
179
+ **Schema goes in `schema` parameter. Never describe output format in `context`.**
180
+ </parameters>
20
181
 
21
182
  <agents>
22
183
  {{#list agents join="\n"}}
@@ -27,66 +188,12 @@ Agents with `output="structured"` enforce their own schema; the `schema` paramet
27
188
  {{/list}}
28
189
  </agents>
29
190
 
30
- <instruction>
31
- This matters. Be thorough.
32
- 1. Plan before acting. Define the goal, acceptance criteria, and scope per task.
33
- 2. Put shared constraints and decisions in `context`; keep each task request short and unambiguous. **Do not describe response format here.**
34
- 3. State whether each task is research-only or should modify files.
35
- 4. **Always provide a `schema`** with typed properties. Avoid `{ "type": "string" }`—if data has any structure (list, fields, categories), model it. Plain text is almost never the right choice.
36
- 5. Assign distinct file scopes per task to avoid conflicts.
37
- 6. Trust the returned data, then verify with tools when correctness matters.
38
- 7. The `context` must be self-contained. Paste relevant file contents, quote user requirements verbatim, include data from prior tool results. "The output user showed" means nothing to a subagent.
39
- </instruction>
40
-
41
- <parameters>
42
- - `agent`: Agent type to use for all tasks
43
- - `context`: Template with `\{{placeholders}}` for multi-task. Must be self-contained—include all information the subagent needs. Subagents cannot see conversation history, images, or prior tool results. Reproduce relevant content directly: paste file snippets, quote user requirements, embed data. Each placeholder is filled from task args. `\{{id}}` and `\{{description}}` are always available.
44
- - `isolated`: (optional) Run each task in its own git worktree and return patches; patches are applied only if all apply cleanly.
45
- - `tasks`: Array of `{id, description, args}` - tasks to run in parallel
46
- - `id`: Short CamelCase identifier (max 32 chars, e.g., "SessionStore", "LspRefactor")
47
- - `description`: Short human-readable description of what the task does
48
- - `args`: Object with keys matching `\{{placeholders}}` in context (always include this, even if empty)
49
- - `skills`: (optional) Array of skill names to preload into this task's system prompt. When set, the skills index section is omitted and the full SKILL.md contents are embedded.
50
- - `schema`: JTD schema defining expected response structure. **Required.** Use objects with typed properties—e.g., `{ "properties": { "items": { "elements": { "type": "string" } } } }` for lists.
51
- </parameters>
52
-
53
- <output>
54
- Returns task results for each spawned agent:
55
- - Truncated preview of agent output (use `read agent://<id>` for full content if truncated)
56
- - Summary with line/character counts
57
- - For agents with `schema`: structured JSON accessible via `agent://<id>?q=<query>` or `agent://<id>/<path>`
58
-
59
- Results are keyed by task `id` (e.g., "AuthProvider", "AuthApi").
60
- </output>
61
-
62
- <example>
63
- user: "Looks good, execute the plan"
64
- assistant: I'll execute the refactoring plan.
65
- assistant: Uses the Task tool:
66
- {
67
- "agent": "task",
68
- "context": "Refactoring the auth module into separate concerns.\n\nPlan:\n1. AuthProvider - Extract React context and provider from src/auth/index.tsx\n2. AuthApi - Extract API calls to src/auth/api.ts, use existing fetchJson helper\n3. AuthTypes - Move types to types.ts, re-export from index\n\nConstraints:\n- Preserve all existing exports from src/auth/index.tsx\n- Use project's fetchJson (src/utils/http.ts), don't use raw fetch\n- No new dependencies\n\nTask: \{{step}}\n\nFiles: \{{files}}",
69
- "schema": {
70
- "properties": {
71
- "summary": { "type": "string" },
72
- "decisions": { "elements": { "type": "string" } },
73
- "concerns": { "elements": { "type": "string" } }
74
- }
75
- },
76
- "tasks": [
77
- { "id": "AuthProvider", "description": "Extract React context", "args": { "step": "Execute step 1: Extract AuthProvider and AuthContext", "files": "src/auth/index.tsx" } },
78
- { "id": "AuthApi", "description": "Extract API layer", "args": { "step": "Execute step 2: Extract API calls to api.ts", "files": "src/auth/api.ts" } },
79
- { "id": "AuthTypes", "description": "Extract types", "args": { "step": "Execute step 3: Move types to types.ts", "files": "src/auth/types.ts" } }
80
- ]
81
- }
82
- </example>
83
-
84
191
  <avoid>
85
- - Describing response format in `context` (e.g., "respond as JSON", "return a bullet list")—use `schema` parameter instead
86
- - Confirmation bias: ask for factual discovery instead of yes/no exploration prompts
87
- - Reading a specific file path Use Read tool instead
88
- - Finding files by pattern/name Use Find tool instead
89
- - Searching for a specific class/function definition Use Grep tool instead
90
- - Searching code within 2-3 specific files Use Read tool instead
91
- - Tasks unrelated to the agent descriptions above
92
- </avoid>
192
+ - Terse context that requires agents to guess conventions
193
+ - Launching dependent tasks in parallel (bindings + API, consumer + producer)
194
+ - Missing "Existing Code" references - agents need patterns to follow
195
+ - Assuming agents know your codebase - they start fresh each time
196
+ - Describing output format in context instead of schema
197
+ - Single tasks doing too much - prefer focused, file-scoped tasks
198
+ </avoid>
199
+ ````
package/src/sdk.ts CHANGED
@@ -40,7 +40,7 @@ import { getAgentDir, getConfigDirPaths } from "./config";
40
40
  import { ModelRegistry } from "./config/model-registry";
41
41
  import { formatModelString, parseModelString } from "./config/model-resolver";
42
42
  import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "./config/prompt-templates";
43
- import { type Settings, SettingsManager, type SkillsSettings } from "./config/settings-manager";
43
+ import { Settings, type SkillsSettings } from "./config/settings";
44
44
  import { CursorExecHandlers } from "./cursor";
45
45
  import "./discovery";
46
46
  import { initializeWithSettings } from "./discovery";
@@ -184,8 +184,8 @@ export interface CreateAgentSessionOptions {
184
184
  /** Session manager. Default: SessionManager.create(cwd) */
185
185
  sessionManager?: SessionManager;
186
186
 
187
- /** Settings manager. Default: SettingsManager.create(cwd, agentDir) */
188
- settingsManager?: SettingsManager;
187
+ /** Settings instance. Default: Settings.init({ cwd, agentDir }) */
188
+ settingsInstance?: Settings;
189
189
 
190
190
  /** Whether UI is available (enables interactive tools like ask). Default: false */
191
191
  hasUI?: boolean;
@@ -210,7 +210,7 @@ export interface CreateAgentSessionResult {
210
210
  // Re-exports
211
211
 
212
212
  export type { PromptTemplate } from "./config/prompt-templates";
213
- export type { Settings, SkillsSettings } from "./config/settings-manager";
213
+ export { Settings, type SkillsSettings } from "./config/settings";
214
214
  export type { CustomCommand, CustomCommandFactory } from "./extensibility/custom-commands/types";
215
215
  export type { CustomTool, CustomToolFactory } from "./extensibility/custom-tools/types";
216
216
  export type {
@@ -430,33 +430,6 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
430
430
  });
431
431
  }
432
432
 
433
- // Settings
434
-
435
- /**
436
- * Load settings from agentDir/settings.json merged with cwd/.omp/settings.json.
437
- */
438
- export async function loadSettings(cwd?: string, agentDir?: string): Promise<Settings> {
439
- const manager = await SettingsManager.create(cwd ?? process.cwd(), agentDir ?? getDefaultAgentDir());
440
- return {
441
- modelRoles: manager.getModelRoles(),
442
- defaultThinkingLevel: manager.getDefaultThinkingLevel(),
443
- steeringMode: manager.getSteeringMode(),
444
- followUpMode: manager.getFollowUpMode(),
445
- interruptMode: manager.getInterruptMode(),
446
- theme: manager.getTheme(),
447
- compaction: manager.getCompactionSettings(),
448
- retry: manager.getRetrySettings(),
449
- hideThinkingBlock: manager.getHideThinkingBlock(),
450
- shellPath: manager.getShellPath(),
451
- shellForceBasic: manager.getShellForceBasic(),
452
- collapseChangelog: manager.getCollapseChangelog(),
453
- extensions: manager.getExtensionPaths(),
454
- skills: manager.getSkillsSettings(),
455
- terminal: { showImages: manager.getShowImages() },
456
- images: { autoResize: manager.getImageAutoResize(), blockImages: manager.getBlockImages() },
457
- };
458
- }
459
-
460
433
  // Internal Helpers
461
434
 
462
435
  function createCustomToolContext(ctx: ExtensionContext): CustomToolContext {
@@ -610,14 +583,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
610
583
  const modelRegistry = options.modelRegistry ?? discoverModels(authStorage, agentDir);
611
584
  time("discoverModels");
612
585
 
613
- const settingsManager = options.settingsManager ?? (await SettingsManager.create(cwd, agentDir));
614
- time("settingsManager");
615
- initializeWithSettings(settingsManager);
586
+ const settingsInstance = options.settingsInstance ?? (await Settings.init({ cwd, agentDir }));
587
+ time("settings");
588
+ initializeWithSettings(settingsInstance);
616
589
  time("initializeWithSettings");
617
590
 
618
591
  // Initialize provider preferences from settings
619
- setPreferredWebSearchProvider(settingsManager.getWebSearchProvider());
620
- setPreferredImageProvider(settingsManager.getImageProvider());
592
+ setPreferredWebSearchProvider(settingsInstance.get("providers.webSearch") ?? "auto");
593
+ setPreferredImageProvider(settingsInstance.get("providers.image") ?? "auto");
621
594
 
622
595
  const sessionManager = options.sessionManager ?? SessionManager.create(cwd);
623
596
  time("sessionManager");
@@ -649,7 +622,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
649
622
 
650
623
  // If still no model, try settings default
651
624
  if (!model) {
652
- const settingsDefaultModel = settingsManager.getModelRole("default");
625
+ const settingsDefaultModel = settingsInstance.getModelRole("default");
653
626
  if (settingsDefaultModel) {
654
627
  const parsedModel = parseModelString(settingsDefaultModel);
655
628
  if (parsedModel) {
@@ -689,7 +662,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
689
662
 
690
663
  // Fall back to settings default
691
664
  if (thinkingLevel === undefined) {
692
- thinkingLevel = settingsManager.getDefaultThinkingLevel() ?? "off";
665
+ thinkingLevel = settingsInstance.get("defaultThinkingLevel") ?? "off";
693
666
  }
694
667
 
695
668
  // Clamp to model capabilities
@@ -705,14 +678,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
705
678
  skills = options.skills;
706
679
  skillWarnings = [];
707
680
  } else {
708
- const discovered = await discoverSkills(cwd, agentDir, settingsManager.getSkillsSettings());
681
+ const skillsSettings = settingsInstance.getGroup("skills") as SkillsSettings;
682
+ const discovered = await discoverSkills(cwd, agentDir, skillsSettings);
709
683
  skills = discovered.skills;
710
684
  skillWarnings = discovered.warnings;
711
685
  }
712
686
  time("discoverSkills");
713
687
 
714
688
  // Discover rules
715
- const ttsrManager = new TtsrManager(settingsManager.getTtsrSettings());
689
+ const ttsrSettings = settingsInstance.getGroup("ttsr");
690
+ const ttsrManager = new TtsrManager(ttsrSettings);
716
691
  const rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });
717
692
  for (const rule of rulesResult.items) {
718
693
  if (rule.ttsrTrigger) {
@@ -761,8 +736,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
761
736
  },
762
737
  getPlanModeState: () => session.getPlanModeState(),
763
738
  getCompactContext: () => session.formatCompactContext(),
764
- settings: settingsManager,
765
- settingsManager,
739
+ settings: settingsInstance,
766
740
  authStorage,
767
741
  modelRegistry,
768
742
  };
@@ -777,7 +751,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
777
751
  internalRouter.register(new ArtifactProtocolHandler({ getArtifactsDir }));
778
752
  internalRouter.register(
779
753
  new PlanProtocolHandler({
780
- getPlansDirectory: settingsManager.getPlansDirectory.bind(settingsManager),
754
+ getPlansDirectory: () => settingsInstance.getPlansDirectory(),
781
755
  cwd,
782
756
  }),
783
757
  );
@@ -814,10 +788,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
814
788
  );
815
789
  }
816
790
  },
817
- enableProjectConfig: settingsManager.getMCPProjectConfigEnabled(),
791
+ enableProjectConfig: settingsInstance.get("mcp.enableProjectConfig") ?? true,
818
792
  // Always filter Exa - we have native integration
819
793
  filterExa: true,
820
- cacheStorage: settingsManager.getStorage(),
794
+ cacheStorage: settingsInstance.getStorage(),
821
795
  });
822
796
  time("discoverAndLoadMCPTools");
823
797
  mcpManager = mcpResult.manager;
@@ -847,11 +821,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
847
821
  time("getGeminiImageTools");
848
822
 
849
823
  // Add specialized Exa web search tools if EXA_API_KEY is available
850
- const exaSettings = settingsManager.getExaSettings();
824
+ const exaSettings = settingsInstance.getGroup("exa");
851
825
  if (exaSettings.enabled && exaSettings.enableSearch) {
852
826
  const exaWebSearchTools = await getWebSearchTools({
853
- enableLinkedin: exaSettings.enableLinkedin,
854
- enableCompany: exaSettings.enableCompany,
827
+ enableLinkedin: exaSettings.enableLinkedin as boolean,
828
+ enableCompany: exaSettings.enableCompany as boolean,
855
829
  });
856
830
  // Filter out the base web_search (already in built-in tools), add specialized Exa tools
857
831
  const specializedTools = exaWebSearchTools.filter(t => t.name !== "web_search");
@@ -879,12 +853,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
879
853
  extensionsResult = options.preloadedExtensions;
880
854
  } else {
881
855
  // Merge CLI extension paths with settings extension paths
882
- const configuredPaths = [...(options.additionalExtensionPaths ?? []), ...settingsManager.getExtensionPaths()];
856
+ const configuredPaths = [
857
+ ...(options.additionalExtensionPaths ?? []),
858
+ ...((settingsInstance.get("extensions") as string[]) ?? []),
859
+ ];
883
860
  extensionsResult = await discoverAndLoadExtensions(
884
861
  configuredPaths,
885
862
  cwd,
886
863
  eventBus,
887
- settingsManager.getDisabledExtensions(),
864
+ (settingsInstance.get("disabledExtensions") as string[]) ?? [],
888
865
  );
889
866
  time("discoverAndLoadExtensions");
890
867
  for (const { path, error } of extensionsResult.errors) {
@@ -1006,7 +983,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1006
983
  tools,
1007
984
  toolNames,
1008
985
  rules: rulebookRules,
1009
- skillsSettings: settingsManager.getSkillsSettings(),
986
+ skillsSettings: settingsInstance.getGroup("skills") as SkillsSettings,
1010
987
  isCoordinator: options.hasUI,
1011
988
  });
1012
989
 
@@ -1022,7 +999,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1022
999
  tools,
1023
1000
  toolNames,
1024
1001
  rules: rulebookRules,
1025
- skillsSettings: settingsManager.getSkillsSettings(),
1002
+ skillsSettings: settingsInstance.getGroup("skills") as SkillsSettings,
1026
1003
  customPrompt: options.systemPrompt,
1027
1004
  isCoordinator: options.hasUI,
1028
1005
  });
@@ -1062,7 +1039,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1062
1039
  const convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {
1063
1040
  const converted = convertToLlm(messages);
1064
1041
  // Check setting dynamically so mid-session changes take effect
1065
- if (!settingsManager.getBlockImages()) {
1042
+ if (!settingsInstance.get("images.blockImages")) {
1066
1043
  return converted;
1067
1044
  }
1068
1045
  // Filter out ImageContent from all messages, replacing with text placeholder
@@ -1115,11 +1092,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1115
1092
  return extensionRunner.emitContext(messages);
1116
1093
  }
1117
1094
  : undefined,
1118
- steeringMode: settingsManager.getSteeringMode(),
1119
- followUpMode: settingsManager.getFollowUpMode(),
1120
- interruptMode: settingsManager.getInterruptMode(),
1121
- thinkingBudgets: settingsManager.getThinkingBudgets(),
1122
- kimiApiFormat: settingsManager.getKimiApiFormat(),
1095
+ steeringMode: settingsInstance.get("steeringMode") ?? "one-at-a-time",
1096
+ followUpMode: settingsInstance.get("followUpMode") ?? "one-at-a-time",
1097
+ interruptMode: settingsInstance.get("interruptMode") ?? "immediate",
1098
+ thinkingBudgets: settingsInstance.getGroup("thinkingBudgets"),
1099
+ kimiApiFormat: settingsInstance.get("providers.kimiApiFormat") ?? "anthropic",
1123
1100
  getToolContext: tc => toolContextStore.getContext(tc),
1124
1101
  getApiKey: async () => {
1125
1102
  const currentModel = agent.state.model;
@@ -1151,7 +1128,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1151
1128
  session = new AgentSession({
1152
1129
  agent,
1153
1130
  sessionManager,
1154
- settingsManager,
1131
+ settings: settingsInstance,
1155
1132
  scopedModels: options.scopedModels,
1156
1133
  promptTemplates,
1157
1134
  slashCommands,
@@ -1159,7 +1136,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1159
1136
  customCommands: customCommandsResult.commands,
1160
1137
  skills,
1161
1138
  skillWarnings,
1162
- skillsSettings: settingsManager.getSkillsSettings(),
1139
+ skillsSettings: settingsInstance.getGroup("skills") as Required<SkillsSettings>,
1163
1140
  modelRegistry,
1164
1141
  toolRegistry,
1165
1142
  rebuildSystemPrompt,
@@ -1169,7 +1146,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1169
1146
 
1170
1147
  // Warm up LSP servers (connects to detected servers)
1171
1148
  let lspServers: CreateAgentSessionResult["lspServers"];
1172
- if (enableLsp && settingsManager.getLspDiagnosticsOnWrite()) {
1149
+ if (enableLsp && settingsInstance.get("lsp.diagnosticsOnWrite")) {
1173
1150
  try {
1174
1151
  const result = await warmupLspServers(cwd, {
1175
1152
  onConnecting: serverNames => {