@oh-my-pi/pi-coding-agent 2.3.1337 → 3.1.1337

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 (117) hide show
  1. package/CHANGELOG.md +72 -34
  2. package/README.md +100 -100
  3. package/docs/compaction.md +8 -8
  4. package/docs/config-usage.md +113 -0
  5. package/docs/custom-tools.md +8 -8
  6. package/docs/extension-loading.md +58 -58
  7. package/docs/hooks.md +11 -11
  8. package/docs/rpc.md +4 -4
  9. package/docs/sdk.md +14 -14
  10. package/docs/session-tree-plan.md +1 -1
  11. package/docs/session.md +2 -2
  12. package/docs/skills.md +16 -16
  13. package/docs/theme.md +9 -9
  14. package/docs/tui.md +1 -1
  15. package/examples/README.md +1 -1
  16. package/examples/custom-tools/README.md +4 -4
  17. package/examples/custom-tools/subagent/README.md +13 -13
  18. package/examples/custom-tools/subagent/agents.ts +2 -2
  19. package/examples/custom-tools/subagent/index.ts +5 -5
  20. package/examples/hooks/README.md +3 -3
  21. package/examples/hooks/auto-commit-on-exit.ts +1 -1
  22. package/examples/hooks/custom-compaction.ts +1 -1
  23. package/examples/sdk/01-minimal.ts +1 -1
  24. package/examples/sdk/04-skills.ts +1 -1
  25. package/examples/sdk/05-tools.ts +1 -1
  26. package/examples/sdk/08-slash-commands.ts +1 -1
  27. package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
  28. package/examples/sdk/README.md +2 -2
  29. package/package.json +13 -11
  30. package/src/capability/context-file.ts +40 -0
  31. package/src/capability/extension.ts +48 -0
  32. package/src/capability/hook.ts +40 -0
  33. package/src/capability/index.ts +616 -0
  34. package/src/capability/instruction.ts +37 -0
  35. package/src/capability/mcp.ts +52 -0
  36. package/src/capability/prompt.ts +35 -0
  37. package/src/capability/rule.ts +52 -0
  38. package/src/capability/settings.ts +35 -0
  39. package/src/capability/skill.ts +49 -0
  40. package/src/capability/slash-command.ts +40 -0
  41. package/src/capability/system-prompt.ts +35 -0
  42. package/src/capability/tool.ts +38 -0
  43. package/src/capability/types.ts +166 -0
  44. package/src/cli/args.ts +2 -2
  45. package/src/cli/plugin-cli.ts +24 -19
  46. package/src/cli/update-cli.ts +10 -10
  47. package/src/config.ts +290 -6
  48. package/src/core/auth-storage.ts +32 -9
  49. package/src/core/bash-executor.ts +1 -1
  50. package/src/core/custom-commands/loader.ts +44 -50
  51. package/src/core/custom-tools/index.ts +1 -0
  52. package/src/core/custom-tools/loader.ts +67 -69
  53. package/src/core/custom-tools/types.ts +10 -1
  54. package/src/core/hooks/loader.ts +13 -42
  55. package/src/core/index.ts +0 -1
  56. package/src/core/logger.ts +7 -7
  57. package/src/core/mcp/client.ts +1 -1
  58. package/src/core/mcp/config.ts +94 -146
  59. package/src/core/mcp/index.ts +0 -4
  60. package/src/core/mcp/loader.ts +26 -22
  61. package/src/core/mcp/manager.ts +18 -23
  62. package/src/core/mcp/tool-bridge.ts +9 -1
  63. package/src/core/mcp/types.ts +2 -0
  64. package/src/core/model-registry.ts +25 -8
  65. package/src/core/plugins/installer.ts +1 -1
  66. package/src/core/plugins/loader.ts +17 -11
  67. package/src/core/plugins/manager.ts +2 -2
  68. package/src/core/plugins/paths.ts +12 -7
  69. package/src/core/plugins/types.ts +3 -3
  70. package/src/core/sdk.ts +48 -27
  71. package/src/core/session-manager.ts +4 -4
  72. package/src/core/settings-manager.ts +45 -21
  73. package/src/core/skills.ts +208 -293
  74. package/src/core/slash-commands.ts +34 -165
  75. package/src/core/system-prompt.ts +58 -65
  76. package/src/core/timings.ts +2 -2
  77. package/src/core/tools/lsp/config.ts +38 -17
  78. package/src/core/tools/task/agents.ts +21 -0
  79. package/src/core/tools/task/artifacts.ts +1 -1
  80. package/src/core/tools/task/bundled-agents/reviewer.md +2 -1
  81. package/src/core/tools/task/bundled-agents/task.md +1 -0
  82. package/src/core/tools/task/commands.ts +30 -107
  83. package/src/core/tools/task/discovery.ts +75 -66
  84. package/src/core/tools/task/executor.ts +25 -10
  85. package/src/core/tools/task/index.ts +35 -10
  86. package/src/core/tools/task/model-resolver.ts +27 -25
  87. package/src/core/tools/task/types.ts +6 -2
  88. package/src/core/tools/web-fetch.ts +3 -3
  89. package/src/core/tools/web-search/auth.ts +40 -34
  90. package/src/core/tools/web-search/index.ts +1 -1
  91. package/src/core/tools/web-search/providers/anthropic.ts +1 -1
  92. package/src/discovery/agents-md.ts +75 -0
  93. package/src/discovery/builtin.ts +646 -0
  94. package/src/discovery/claude.ts +623 -0
  95. package/src/discovery/cline.ts +102 -0
  96. package/src/discovery/codex.ts +571 -0
  97. package/src/discovery/cursor.ts +264 -0
  98. package/src/discovery/gemini.ts +368 -0
  99. package/src/discovery/github.ts +120 -0
  100. package/src/discovery/helpers.test.ts +127 -0
  101. package/src/discovery/helpers.ts +249 -0
  102. package/src/discovery/index.ts +84 -0
  103. package/src/discovery/mcp-json.ts +127 -0
  104. package/src/discovery/vscode.ts +99 -0
  105. package/src/discovery/windsurf.ts +216 -0
  106. package/src/main.ts +14 -13
  107. package/src/migrations.ts +24 -3
  108. package/src/modes/interactive/components/hook-editor.ts +1 -1
  109. package/src/modes/interactive/components/plugin-settings.ts +1 -1
  110. package/src/modes/interactive/components/settings-defs.ts +38 -2
  111. package/src/modes/interactive/components/settings-selector.ts +1 -0
  112. package/src/modes/interactive/components/welcome.ts +2 -2
  113. package/src/modes/interactive/interactive-mode.ts +233 -16
  114. package/src/modes/interactive/theme/theme-schema.json +1 -1
  115. package/src/utils/clipboard.ts +1 -1
  116. package/src/utils/shell-snapshot.ts +2 -2
  117. package/src/utils/shell.ts +7 -7
@@ -1,32 +1,37 @@
1
1
  import { homedir } from "node:os";
2
2
  import { join } from "node:path";
3
- import { CONFIG_DIR_NAME } from "../../config";
3
+ import { CONFIG_DIR_NAME, getConfigDirPaths } from "../../config";
4
4
 
5
5
  // =============================================================================
6
6
  // Plugin Directory Paths
7
7
  // =============================================================================
8
8
 
9
- /** Root plugin directory: ~/.pi/plugins (not under agent/) */
9
+ /** Root plugin directory: ~/.omp/plugins (not under agent/) */
10
10
  export function getPluginsDir(): string {
11
11
  return join(homedir(), CONFIG_DIR_NAME, "plugins");
12
12
  }
13
13
 
14
- /** Where npm installs packages: ~/.pi/plugins/node_modules */
14
+ /** Where npm installs packages: ~/.omp/plugins/node_modules */
15
15
  export function getPluginsNodeModules(): string {
16
16
  return join(getPluginsDir(), "node_modules");
17
17
  }
18
18
 
19
- /** Plugin manifest: ~/.pi/plugins/package.json */
19
+ /** Plugin manifest: ~/.omp/plugins/package.json */
20
20
  export function getPluginsPackageJson(): string {
21
21
  return join(getPluginsDir(), "package.json");
22
22
  }
23
23
 
24
- /** Plugin lock file: ~/.pi/plugins/pi-plugins.lock.json */
24
+ /** Plugin lock file: ~/.omp/plugins/omp-plugins.lock.json */
25
25
  export function getPluginsLockfile(): string {
26
- return join(getPluginsDir(), "pi-plugins.lock.json");
26
+ return join(getPluginsDir(), "omp-plugins.lock.json");
27
27
  }
28
28
 
29
- /** Project-local plugin overrides: .pi/plugin-overrides.json */
29
+ /** Project-local plugin overrides: .omp/plugin-overrides.json (primary) */
30
30
  export function getProjectPluginOverrides(cwd: string): string {
31
31
  return join(cwd, CONFIG_DIR_NAME, "plugin-overrides.json");
32
32
  }
33
+
34
+ /** All possible project plugin override paths (primary + legacy) */
35
+ export function getAllProjectPluginOverridePaths(cwd: string): string[] {
36
+ return getConfigDirPaths("plugin-overrides.json", { user: false, cwd });
37
+ }
@@ -115,7 +115,7 @@ export interface InstalledPlugin {
115
115
  }
116
116
 
117
117
  // =============================================================================
118
- // Runtime Config Types (stored in pi-plugins.lock.json)
118
+ // Runtime Config Types (stored in omp-plugins.lock.json)
119
119
  // =============================================================================
120
120
 
121
121
  /**
@@ -131,7 +131,7 @@ export interface PluginRuntimeState {
131
131
  }
132
132
 
133
133
  /**
134
- * Runtime configuration persisted to pi-plugins.lock.json.
134
+ * Runtime configuration persisted to omp-plugins.lock.json.
135
135
  * Tracks plugin states and settings across sessions.
136
136
  */
137
137
  export interface PluginRuntimeConfig {
@@ -146,7 +146,7 @@ export interface PluginRuntimeConfig {
146
146
  // =============================================================================
147
147
 
148
148
  /**
149
- * Project-local plugin overrides (stored in .pi/plugin-overrides.json).
149
+ * Project-local plugin overrides (stored in .omp/plugin-overrides.json).
150
150
  * Allows per-project plugin configuration without modifying global state.
151
151
  */
152
152
  export interface ProjectPluginOverrides {
package/src/core/sdk.ts CHANGED
@@ -32,7 +32,9 @@
32
32
  import { join } from "node:path";
33
33
  import { Agent, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
34
34
  import type { Model } from "@oh-my-pi/pi-ai";
35
- import { getAgentDir } from "../config";
35
+ // Import discovery to register all providers on startup
36
+ import "../discovery";
37
+ import { getAgentDir, getConfigDirPaths } from "../config";
36
38
  import { AgentSession } from "./agent-session";
37
39
  import { AuthStorage } from "./auth-storage";
38
40
  import {
@@ -48,6 +50,7 @@ import {
48
50
  import type { CustomTool } from "./custom-tools/types";
49
51
  import { discoverAndLoadHooks, HookRunner, type LoadedHook, wrapToolsWithHooks } from "./hooks/index";
50
52
  import type { HookFactory } from "./hooks/types";
53
+ import { logger } from "./logger";
51
54
  import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp/index";
52
55
  import { convertToLlm } from "./messages";
53
56
  import { ModelRegistry } from "./model-registry";
@@ -91,7 +94,7 @@ import {
91
94
  export interface CreateAgentSessionOptions {
92
95
  /** Working directory for project-local discovery. Default: process.cwd() */
93
96
  cwd?: string;
94
- /** Global config directory. Default: ~/.pi/agent */
97
+ /** Global config directory. Default: ~/.omp/agent */
95
98
  agentDir?: string;
96
99
 
97
100
  /** Auth storage for credentials. Default: discoverAuthStorage(agentDir) */
@@ -125,7 +128,7 @@ export interface CreateAgentSessionOptions {
125
128
  skills?: Skill[];
126
129
  /** Context files (AGENTS.md content). Default: discovered walking up from cwd */
127
130
  contextFiles?: Array<{ path: string; content: string }>;
128
- /** Slash commands. Default: discovered from cwd/.pi/commands/ + agentDir/commands/ */
131
+ /** Slash commands. Default: discovered from cwd/.omp/commands/ + agentDir/commands/ */
129
132
  slashCommands?: FileSlashCommand[];
130
133
 
131
134
  /** Enable MCP server discovery from .mcp.json files. Default: true */
@@ -202,17 +205,33 @@ function getDefaultAgentDir(): string {
202
205
  // Discovery Functions
203
206
 
204
207
  /**
205
- * Create an AuthStorage instance for the given agent directory.
208
+ * Create an AuthStorage instance with fallback support.
209
+ * Reads from primary path first, then falls back to legacy paths (.pi, .claude).
206
210
  */
207
211
  export function discoverAuthStorage(agentDir: string = getDefaultAgentDir()): AuthStorage {
208
- return new AuthStorage(join(agentDir, "auth.json"));
212
+ const primaryPath = join(agentDir, "auth.json");
213
+ // Get all auth.json paths (user-level only), excluding the primary
214
+ const allPaths = getConfigDirPaths("auth.json", { project: false });
215
+ const fallbackPaths = allPaths.filter((p) => p !== primaryPath);
216
+
217
+ logger.debug("discoverAuthStorage", { agentDir, primaryPath, allPaths, fallbackPaths });
218
+
219
+ return new AuthStorage(primaryPath, fallbackPaths);
209
220
  }
210
221
 
211
222
  /**
212
- * Create a ModelRegistry for the given agent directory.
223
+ * Create a ModelRegistry with fallback support.
224
+ * Reads from primary path first, then falls back to legacy paths (.pi, .claude).
213
225
  */
214
226
  export function discoverModels(authStorage: AuthStorage, agentDir: string = getDefaultAgentDir()): ModelRegistry {
215
- return new ModelRegistry(authStorage, join(agentDir, "models.json"));
227
+ const primaryPath = join(agentDir, "models.json");
228
+ // Get all models.json paths (user-level only), excluding the primary
229
+ const allPaths = getConfigDirPaths("models.json", { project: false });
230
+ const fallbackPaths = allPaths.filter((p) => p !== primaryPath);
231
+
232
+ logger.debug("discoverModels", { primaryPath, fallbackPaths });
233
+
234
+ return new ModelRegistry(authStorage, primaryPath, fallbackPaths);
216
235
  }
217
236
 
218
237
  /**
@@ -220,12 +239,11 @@ export function discoverModels(authStorage: AuthStorage, agentDir: string = getD
220
239
  */
221
240
  export async function discoverHooks(
222
241
  cwd?: string,
223
- agentDir?: string,
242
+ _agentDir?: string,
224
243
  ): Promise<Array<{ path: string; factory: HookFactory }>> {
225
244
  const resolvedCwd = cwd ?? process.cwd();
226
- const resolvedAgentDir = agentDir ?? getDefaultAgentDir();
227
245
 
228
- const { hooks, errors } = await discoverAndLoadHooks([], resolvedCwd, resolvedAgentDir);
246
+ const { hooks, errors } = await discoverAndLoadHooks([], resolvedCwd);
229
247
 
230
248
  // Log errors but don't fail
231
249
  for (const { path, error } of errors) {
@@ -243,12 +261,11 @@ export async function discoverHooks(
243
261
  */
244
262
  export async function discoverCustomTools(
245
263
  cwd?: string,
246
- agentDir?: string,
264
+ _agentDir?: string,
247
265
  ): Promise<Array<{ path: string; tool: CustomTool }>> {
248
266
  const resolvedCwd = cwd ?? process.cwd();
249
- const resolvedAgentDir = agentDir ?? getDefaultAgentDir();
250
267
 
251
- const { tools, errors } = await discoverAndLoadCustomTools([], resolvedCwd, Object.keys(allTools), resolvedAgentDir);
268
+ const { tools, errors } = await discoverAndLoadCustomTools([], resolvedCwd, Object.keys(allTools));
252
269
 
253
270
  // Log errors but don't fail
254
271
  for (const { path, error } of errors) {
@@ -264,22 +281,24 @@ export async function discoverCustomTools(
264
281
  /**
265
282
  * Discover skills from cwd and agentDir.
266
283
  */
267
- export function discoverSkills(cwd?: string, agentDir?: string, settings?: SkillsSettings): Skill[] {
284
+ export function discoverSkills(cwd?: string, _agentDir?: string, settings?: SkillsSettings): Skill[] {
268
285
  const { skills } = loadSkillsInternal({
269
286
  ...settings,
270
287
  cwd: cwd ?? process.cwd(),
271
- agentDir: agentDir ?? getDefaultAgentDir(),
272
288
  });
273
289
  return skills;
274
290
  }
275
291
 
276
292
  /**
277
293
  * Discover context files (AGENTS.md) walking up from cwd.
294
+ * Returns files sorted by depth (farther from cwd first, so closer files appear last/more prominent).
278
295
  */
279
- export function discoverContextFiles(cwd?: string, agentDir?: string): Array<{ path: string; content: string }> {
296
+ export function discoverContextFiles(
297
+ cwd?: string,
298
+ _agentDir?: string,
299
+ ): Array<{ path: string; content: string; depth?: number }> {
280
300
  return loadContextFilesInternal({
281
301
  cwd: cwd ?? process.cwd(),
282
- agentDir: agentDir ?? getDefaultAgentDir(),
283
302
  });
284
303
  }
285
304
 
@@ -288,14 +307,11 @@ export function discoverContextFiles(cwd?: string, agentDir?: string): Array<{ p
288
307
  */
289
308
  export function discoverSlashCommands(
290
309
  cwd?: string,
291
- agentDir?: string,
292
- settings?: CommandsSettings,
310
+ _agentDir?: string,
311
+ _settings?: CommandsSettings,
293
312
  ): FileSlashCommand[] {
294
313
  return loadSlashCommandsInternal({
295
314
  cwd: cwd ?? process.cwd(),
296
- agentDir: agentDir ?? getDefaultAgentDir(),
297
- enableClaudeUser: settings?.enableClaudeUser,
298
- enableClaudeProject: settings?.enableClaudeProject,
299
315
  });
300
316
  }
301
317
 
@@ -348,7 +364,7 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
348
364
  // Settings
349
365
 
350
366
  /**
351
- * Load settings from agentDir/settings.json merged with cwd/.pi/settings.json.
367
+ * Load settings from agentDir/settings.json merged with cwd/.omp/settings.json.
352
368
  */
353
369
  export function loadSettings(cwd?: string, agentDir?: string): Settings {
354
370
  const manager = SettingsManager.create(cwd ?? process.cwd(), agentDir ?? getDefaultAgentDir());
@@ -496,6 +512,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
496
512
 
497
513
  const settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);
498
514
  time("settingsManager");
515
+
516
+ // Initialize discovery system with settings for provider persistence
517
+ const { initializeWithSettings } = await import("../discovery");
518
+ initializeWithSettings(settingsManager);
519
+ time("initializeWithSettings");
520
+
499
521
  const sessionManager = options.sessionManager ?? SessionManager.create(cwd);
500
522
  time("sessionManager");
501
523
 
@@ -591,7 +613,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
591
613
  } else {
592
614
  // Discover hooks, merging with additional paths
593
615
  const configuredPaths = [...settingsManager.getHookPaths(), ...(options.additionalHookPaths ?? [])];
594
- const { hooks, errors } = await discoverAndLoadHooks(configuredPaths, cwd, agentDir);
616
+ const { hooks, errors } = await discoverAndLoadHooks(configuredPaths, cwd);
595
617
  time("discoverAndLoadHooks");
596
618
  for (const { path, error } of errors) {
597
619
  console.error(`Failed to load hook "${path}": ${error}`);
@@ -629,7 +651,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
629
651
  } else {
630
652
  // Discover custom tools, merging with additional paths
631
653
  const configuredPaths = [...settingsManager.getCustomToolPaths(), ...(options.additionalCustomToolPaths ?? [])];
632
- customToolsResult = await discoverAndLoadCustomTools(configuredPaths, cwd, Object.keys(allTools), agentDir);
654
+ customToolsResult = await discoverAndLoadCustomTools(configuredPaths, cwd, Object.keys(allTools));
633
655
  time("discoverAndLoadCustomTools");
634
656
  for (const { path, error } of customToolsResult.errors) {
635
657
  console.error(`Failed to load custom tool "${path}": ${error}`);
@@ -687,6 +709,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
687
709
  path: "<exa>",
688
710
  resolvedPath: "<exa>",
689
711
  tool,
712
+ source: { provider: "builtin", providerName: "builtin", level: "user" },
690
713
  }));
691
714
  customToolsResult = {
692
715
  ...customToolsResult,
@@ -743,7 +766,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
743
766
  let systemPrompt: string;
744
767
  const defaultPrompt = buildSystemPromptInternal({
745
768
  cwd,
746
- agentDir,
747
769
  skills,
748
770
  contextFiles,
749
771
  });
@@ -754,7 +776,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
754
776
  } else if (typeof options.systemPrompt === "string") {
755
777
  systemPrompt = buildSystemPromptInternal({
756
778
  cwd,
757
- agentDir,
758
779
  skills,
759
780
  contextFiles,
760
781
  customPrompt: options.systemPrompt,
@@ -394,7 +394,7 @@ export function buildSessionContext(
394
394
 
395
395
  /**
396
396
  * Compute the default session directory for a cwd.
397
- * Encodes cwd into a safe directory name under ~/.pi/agent/sessions/.
397
+ * Encodes cwd into a safe directory name under ~/.omp/agent/sessions/.
398
398
  */
399
399
  function getDefaultSessionDir(cwd: string): string {
400
400
  const safePath = `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
@@ -1101,7 +1101,7 @@ export class SessionManager {
1101
1101
  /**
1102
1102
  * Create a new session.
1103
1103
  * @param cwd Working directory (stored in session header)
1104
- * @param sessionDir Optional session directory. If omitted, uses default (~/.pi/agent/sessions/<encoded-cwd>/).
1104
+ * @param sessionDir Optional session directory. If omitted, uses default (~/.omp/agent/sessions/<encoded-cwd>/).
1105
1105
  */
1106
1106
  static create(cwd: string, sessionDir?: string): SessionManager {
1107
1107
  const dir = sessionDir ?? getDefaultSessionDir(cwd);
@@ -1126,7 +1126,7 @@ export class SessionManager {
1126
1126
  /**
1127
1127
  * Continue the most recent session, or create new if none.
1128
1128
  * @param cwd Working directory
1129
- * @param sessionDir Optional session directory. If omitted, uses default (~/.pi/agent/sessions/<encoded-cwd>/).
1129
+ * @param sessionDir Optional session directory. If omitted, uses default (~/.omp/agent/sessions/<encoded-cwd>/).
1130
1130
  */
1131
1131
  static continueRecent(cwd: string, sessionDir?: string): SessionManager {
1132
1132
  const dir = sessionDir ?? getDefaultSessionDir(cwd);
@@ -1145,7 +1145,7 @@ export class SessionManager {
1145
1145
  /**
1146
1146
  * List all sessions.
1147
1147
  * @param cwd Working directory (used to compute default session directory)
1148
- * @param sessionDir Optional session directory. If omitted, uses default (~/.pi/agent/sessions/<encoded-cwd>/).
1148
+ * @param sessionDir Optional session directory. If omitted, uses default (~/.omp/agent/sessions/<encoded-cwd>/).
1149
1149
  */
1150
1150
  static list(cwd: string, sessionDir?: string): SessionInfo[] {
1151
1151
  const dir = sessionDir ?? getDefaultSessionDir(cwd);
@@ -1,6 +1,8 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
- import { CONFIG_DIR_NAME, getAgentDir } from "../config";
3
+ import { type Settings as SettingsItem, settingsCapability } from "../capability/settings";
4
+ import { getAgentDir } from "../config";
5
+ import { loadSync } from "../discovery";
4
6
 
5
7
  export interface CompactionSettings {
6
8
  enabled?: boolean; // default: true
@@ -91,6 +93,7 @@ export interface Settings {
91
93
  mcp?: MCPSettings;
92
94
  lsp?: LspSettings;
93
95
  edit?: EditSettings;
96
+ disabledProviders?: string[]; // Discovery provider IDs that are disabled
94
97
  }
95
98
 
96
99
  /** Deep merge settings: project/overrides take precedence, nested objects merge recursively */
@@ -126,19 +129,14 @@ function deepMergeSettings(base: Settings, overrides: Settings): Settings {
126
129
 
127
130
  export class SettingsManager {
128
131
  private settingsPath: string | null;
129
- private projectSettingsPath: string | null;
132
+ private cwd: string | null;
130
133
  private globalSettings: Settings;
131
134
  private settings: Settings;
132
135
  private persist: boolean;
133
136
 
134
- private constructor(
135
- settingsPath: string | null,
136
- projectSettingsPath: string | null,
137
- initialSettings: Settings,
138
- persist: boolean,
139
- ) {
137
+ private constructor(settingsPath: string | null, cwd: string | null, initialSettings: Settings, persist: boolean) {
140
138
  this.settingsPath = settingsPath;
141
- this.projectSettingsPath = projectSettingsPath;
139
+ this.cwd = cwd;
142
140
  this.persist = persist;
143
141
  this.globalSettings = initialSettings;
144
142
  const projectSettings = this.loadProjectSettings();
@@ -148,9 +146,23 @@ export class SettingsManager {
148
146
  /** Create a SettingsManager that loads from files */
149
147
  static create(cwd: string = process.cwd(), agentDir: string = getAgentDir()): SettingsManager {
150
148
  const settingsPath = join(agentDir, "settings.json");
151
- const projectSettingsPath = join(cwd, CONFIG_DIR_NAME, "settings.json");
152
- const globalSettings = SettingsManager.loadFromFile(settingsPath);
153
- return new SettingsManager(settingsPath, projectSettingsPath, globalSettings, true);
149
+
150
+ // Use capability API to load user-level settings from all providers
151
+ const result = loadSync(settingsCapability.id, { cwd });
152
+
153
+ // Merge all user-level settings
154
+ let globalSettings: Settings = {};
155
+ for (const item of result.items as SettingsItem[]) {
156
+ if (item.level === "user") {
157
+ globalSettings = deepMergeSettings(globalSettings, item.data as Settings);
158
+ }
159
+ }
160
+
161
+ // Also load from agentDir for backward compatibility (if not covered by providers)
162
+ const legacySettings = SettingsManager.loadFromFile(settingsPath);
163
+ globalSettings = deepMergeSettings(globalSettings, legacySettings);
164
+
165
+ return new SettingsManager(settingsPath, cwd, globalSettings, true);
154
166
  }
155
167
 
156
168
  /** Create an in-memory SettingsManager (no file I/O) */
@@ -172,17 +184,20 @@ export class SettingsManager {
172
184
  }
173
185
 
174
186
  private loadProjectSettings(): Settings {
175
- if (!this.projectSettingsPath || !existsSync(this.projectSettingsPath)) {
176
- return {};
177
- }
187
+ if (!this.cwd) return {};
178
188
 
179
- try {
180
- const content = readFileSync(this.projectSettingsPath, "utf-8");
181
- return JSON.parse(content);
182
- } catch (error) {
183
- console.error(`Warning: Could not read project settings file: ${error}`);
184
- return {};
189
+ // Use capability API to discover settings from all providers
190
+ const result = loadSync(settingsCapability.id, { cwd: this.cwd });
191
+
192
+ // Merge only project-level settings (user-level settings are handled separately via globalSettings)
193
+ let merged: Settings = {};
194
+ for (const item of result.items as SettingsItem[]) {
195
+ if (item.level === "project") {
196
+ merged = deepMergeSettings(merged, item.data as Settings);
197
+ }
185
198
  }
199
+
200
+ return merged;
186
201
  }
187
202
 
188
203
  /** Apply additional overrides on top of current settings */
@@ -558,4 +573,13 @@ export class SettingsManager {
558
573
  this.globalSettings.edit.fuzzyMatch = enabled;
559
574
  this.save();
560
575
  }
576
+
577
+ getDisabledProviders(): string[] {
578
+ return [...(this.settings.disabledProviders ?? [])];
579
+ }
580
+
581
+ setDisabledProviders(providerIds: string[]): void {
582
+ this.globalSettings.disabledProviders = providerIds;
583
+ this.save();
584
+ }
561
585
  }