@oh-my-pi/pi-coding-agent 2.2.1337 → 3.0.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 (116) hide show
  1. package/CHANGELOG.md +64 -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 +16 -12
  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/export-html/index.ts +9 -9
  55. package/src/core/export-html/template.generated.ts +2 -0
  56. package/src/core/hooks/loader.ts +13 -42
  57. package/src/core/index.ts +0 -1
  58. package/src/core/logger.ts +7 -7
  59. package/src/core/mcp/client.ts +1 -1
  60. package/src/core/mcp/config.ts +94 -146
  61. package/src/core/mcp/index.ts +0 -4
  62. package/src/core/mcp/loader.ts +26 -22
  63. package/src/core/mcp/manager.ts +18 -23
  64. package/src/core/mcp/tool-bridge.ts +9 -1
  65. package/src/core/mcp/types.ts +2 -0
  66. package/src/core/model-registry.ts +25 -8
  67. package/src/core/plugins/installer.ts +1 -1
  68. package/src/core/plugins/loader.ts +17 -11
  69. package/src/core/plugins/manager.ts +2 -2
  70. package/src/core/plugins/paths.ts +12 -7
  71. package/src/core/plugins/types.ts +3 -3
  72. package/src/core/sdk.ts +48 -27
  73. package/src/core/session-manager.ts +4 -4
  74. package/src/core/settings-manager.ts +45 -21
  75. package/src/core/skills.ts +222 -293
  76. package/src/core/slash-commands.ts +34 -165
  77. package/src/core/system-prompt.ts +58 -65
  78. package/src/core/timings.ts +2 -2
  79. package/src/core/tools/lsp/config.ts +38 -17
  80. package/src/core/tools/task/artifacts.ts +1 -1
  81. package/src/core/tools/task/commands.ts +30 -107
  82. package/src/core/tools/task/discovery.ts +54 -66
  83. package/src/core/tools/task/executor.ts +9 -9
  84. package/src/core/tools/task/index.ts +10 -10
  85. package/src/core/tools/task/model-resolver.ts +27 -25
  86. package/src/core/tools/task/types.ts +2 -2
  87. package/src/core/tools/web-fetch.ts +3 -3
  88. package/src/core/tools/web-search/auth.ts +40 -34
  89. package/src/core/tools/web-search/index.ts +1 -1
  90. package/src/core/tools/web-search/providers/anthropic.ts +1 -1
  91. package/src/discovery/agents-md.ts +75 -0
  92. package/src/discovery/builtin.ts +646 -0
  93. package/src/discovery/claude.ts +623 -0
  94. package/src/discovery/cline.ts +102 -0
  95. package/src/discovery/codex.ts +571 -0
  96. package/src/discovery/cursor.ts +264 -0
  97. package/src/discovery/gemini.ts +368 -0
  98. package/src/discovery/github.ts +120 -0
  99. package/src/discovery/helpers.test.ts +127 -0
  100. package/src/discovery/helpers.ts +249 -0
  101. package/src/discovery/index.ts +84 -0
  102. package/src/discovery/mcp-json.ts +127 -0
  103. package/src/discovery/vscode.ts +99 -0
  104. package/src/discovery/windsurf.ts +216 -0
  105. package/src/main.ts +14 -13
  106. package/src/migrations.ts +24 -3
  107. package/src/modes/interactive/components/hook-editor.ts +1 -1
  108. package/src/modes/interactive/components/plugin-settings.ts +1 -1
  109. package/src/modes/interactive/components/settings-defs.ts +38 -2
  110. package/src/modes/interactive/components/settings-selector.ts +1 -0
  111. package/src/modes/interactive/components/welcome.ts +2 -2
  112. package/src/modes/interactive/interactive-mode.ts +211 -16
  113. package/src/modes/interactive/theme/theme-schema.json +1 -1
  114. package/src/utils/clipboard.ts +1 -1
  115. package/src/utils/shell-snapshot.ts +2 -2
  116. package/src/utils/shell.ts +7 -7
@@ -4,9 +4,9 @@
4
4
  * Commands are embedded at build time via Bun's import with { type: "text" }.
5
5
  */
6
6
 
7
- import * as fs from "node:fs";
8
- import * as os from "node:os";
9
7
  import * as path from "node:path";
8
+ import { type SlashCommand, slashCommandCapability } from "../../../capability/slash-command";
9
+ import { loadSync } from "../../../discovery";
10
10
 
11
11
  // Embed command markdown files at build time
12
12
  import architectPlanMd from "./bundled-commands/architect-plan.md" with { type: "text" };
@@ -61,84 +61,6 @@ function parseFrontmatter(content: string): { frontmatter: Record<string, string
61
61
  return { frontmatter, body };
62
62
  }
63
63
 
64
- /**
65
- * Load commands from a directory (for user/project commands).
66
- */
67
- function loadCommandsFromDir(dir: string, source: "user" | "project"): WorkflowCommand[] {
68
- const commands: WorkflowCommand[] = [];
69
-
70
- if (!fs.existsSync(dir)) {
71
- return commands;
72
- }
73
-
74
- let entries: fs.Dirent[];
75
- try {
76
- entries = fs.readdirSync(dir, { withFileTypes: true });
77
- } catch {
78
- return commands;
79
- }
80
-
81
- for (const entry of entries) {
82
- if (!entry.name.endsWith(".md")) continue;
83
-
84
- const filePath = path.join(dir, entry.name);
85
-
86
- try {
87
- if (!fs.statSync(filePath).isFile()) continue;
88
- } catch {
89
- continue;
90
- }
91
-
92
- let content: string;
93
- try {
94
- content = fs.readFileSync(filePath, "utf-8");
95
- } catch {
96
- continue;
97
- }
98
-
99
- const { frontmatter, body } = parseFrontmatter(content);
100
-
101
- // Name is filename without extension
102
- const name = entry.name.replace(/\.md$/, "");
103
-
104
- commands.push({
105
- name,
106
- description: frontmatter.description || "",
107
- instructions: body,
108
- source,
109
- filePath,
110
- });
111
- }
112
-
113
- return commands;
114
- }
115
-
116
- /**
117
- * Check if path is a directory.
118
- */
119
- function isDirectory(p: string): boolean {
120
- try {
121
- return fs.statSync(p).isDirectory();
122
- } catch {
123
- return false;
124
- }
125
- }
126
-
127
- /**
128
- * Find nearest directory by walking up from cwd.
129
- */
130
- function findNearestDir(cwd: string, relPath: string): string | null {
131
- let currentDir = cwd;
132
- while (true) {
133
- const candidate = path.join(currentDir, relPath);
134
- if (isDirectory(candidate)) return candidate;
135
-
136
- const parentDir = path.dirname(currentDir);
137
- if (parentDir === currentDir) return null;
138
- currentDir = parentDir;
139
- }
140
- }
141
-
142
64
  /** Cache for bundled commands */
143
65
  let bundledCommandsCache: WorkflowCommand[] | null = null;
144
66
 
@@ -172,43 +94,44 @@ export function loadBundledCommands(): WorkflowCommand[] {
172
94
  /**
173
95
  * Discover all available commands.
174
96
  *
175
- * Precedence: project > user > bundled
97
+ * Precedence (highest wins): .omp > .pi > .claude (project before user), then bundled
176
98
  */
177
99
  export function discoverCommands(cwd: string): WorkflowCommand[] {
178
- const commandMap = new Map<string, WorkflowCommand>();
100
+ const resolvedCwd = path.resolve(cwd);
179
101
 
180
- // Bundled commands (lowest priority)
181
- for (const cmd of loadBundledCommands()) {
182
- commandMap.set(cmd.name, cmd);
183
- }
102
+ // Load slash commands from capability API
103
+ const result = loadSync<SlashCommand>(slashCommandCapability.id, { cwd: resolvedCwd });
104
+
105
+ const commands: WorkflowCommand[] = [];
106
+ const seen = new Set<string>();
184
107
 
185
- // User commands
186
- const userPiDir = path.join(os.homedir(), ".pi", "agent", "commands");
187
- const userClaudeDir = path.join(os.homedir(), ".claude", "commands");
108
+ // Convert SlashCommand to WorkflowCommand format
109
+ for (const cmd of result.items) {
110
+ if (seen.has(cmd.name)) continue;
188
111
 
189
- for (const cmd of loadCommandsFromDir(userClaudeDir, "user")) {
190
- commandMap.set(cmd.name, cmd);
191
- }
192
- for (const cmd of loadCommandsFromDir(userPiDir, "user")) {
193
- commandMap.set(cmd.name, cmd);
194
- }
112
+ const { frontmatter, body } = parseFrontmatter(cmd.content);
195
113
 
196
- // Project commands (highest priority)
197
- const projectPiDir = findNearestDir(cwd, ".pi/commands");
198
- const projectClaudeDir = findNearestDir(cwd, ".claude/commands");
114
+ // Map capability levels to WorkflowCommand source
115
+ const source: "bundled" | "user" | "project" = cmd.level === "native" ? "bundled" : cmd.level;
199
116
 
200
- if (projectClaudeDir) {
201
- for (const cmd of loadCommandsFromDir(projectClaudeDir, "project")) {
202
- commandMap.set(cmd.name, cmd);
203
- }
117
+ commands.push({
118
+ name: cmd.name,
119
+ description: frontmatter.description || "",
120
+ instructions: body,
121
+ source,
122
+ filePath: cmd.path,
123
+ });
124
+ seen.add(cmd.name);
204
125
  }
205
- if (projectPiDir) {
206
- for (const cmd of loadCommandsFromDir(projectPiDir, "project")) {
207
- commandMap.set(cmd.name, cmd);
208
- }
126
+
127
+ // Add bundled commands if not already present
128
+ for (const cmd of loadBundledCommands()) {
129
+ if (seen.has(cmd.name)) continue;
130
+ commands.push(cmd);
131
+ seen.add(cmd.name);
209
132
  }
210
133
 
211
- return Array.from(commandMap.values());
134
+ return commands;
212
135
  }
213
136
 
214
137
  /**
@@ -2,17 +2,19 @@
2
2
  * Agent discovery from filesystem.
3
3
  *
4
4
  * Discovers agent definitions from:
5
- * - ~/.pi/agent/agents/*.md (user-level, primary)
6
- * - ~/.claude/agents/*.md (user-level, fallback)
7
- * - .pi/agents/*.md (project-level, primary)
8
- * - .claude/agents/*.md (project-level, fallback)
5
+ * - ~/.omp/agent/agents/*.md (user-level, primary)
6
+ * - ~/.pi/agent/agents/*.md (user-level, legacy)
7
+ * - ~/.claude/agents/*.md (user-level, legacy)
8
+ * - .omp/agents/*.md (project-level, primary)
9
+ * - .pi/agents/*.md (project-level, legacy)
10
+ * - .claude/agents/*.md (project-level, legacy)
9
11
  *
10
12
  * Agent files use markdown with YAML frontmatter.
11
13
  */
12
14
 
13
15
  import * as fs from "node:fs";
14
- import * as os from "node:os";
15
16
  import * as path from "node:path";
17
+ import { findAllNearestProjectConfigDirs, getConfigDirs } from "../../../config";
16
18
  import { loadBundledAgents } from "./agents";
17
19
  import type { AgentDefinition, AgentSource } from "./types";
18
20
 
@@ -76,7 +78,7 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentDefinition[]
76
78
  for (const entry of entries) {
77
79
  if (!entry.name.endsWith(".md")) continue;
78
80
 
79
- const filePath = path.join(dir, entry.name);
81
+ const filePath = path.resolve(dir, entry.name);
80
82
 
81
83
  // Handle both regular files and symlinks
82
84
  try {
@@ -124,80 +126,66 @@ function loadAgentsFromDir(dir: string, source: AgentSource): AgentDefinition[]
124
126
  return agents;
125
127
  }
126
128
 
127
- /**
128
- * Check if path is a directory.
129
- */
130
- function isDirectory(p: string): boolean {
131
- try {
132
- return fs.statSync(p).isDirectory();
133
- } catch {
134
- return false;
135
- }
136
- }
137
-
138
- /**
139
- * Find nearest directory by walking up from cwd.
140
- */
141
- function findNearestDir(cwd: string, relPath: string): string | null {
142
- let currentDir = cwd;
143
- while (true) {
144
- const candidate = path.join(currentDir, relPath);
145
- if (isDirectory(candidate)) return candidate;
146
-
147
- const parentDir = path.dirname(currentDir);
148
- if (parentDir === currentDir) return null;
149
- currentDir = parentDir;
150
- }
151
- }
152
-
153
129
  /**
154
130
  * Discover agents from filesystem and merge with bundled agents.
155
131
  *
156
- * Precedence (highest wins): project > user > bundled
157
- * Within each level: .pi > .claude
132
+ * Precedence (highest wins): .omp > .pi > .claude (project before user), then bundled
158
133
  *
159
134
  * @param cwd - Current working directory for project agent discovery
160
135
  */
161
136
  export function discoverAgents(cwd: string): DiscoveryResult {
162
- // Primary directories (.pi)
163
- const userPiDir = path.join(os.homedir(), ".pi", "agent", "agents");
164
- const projectPiDir = findNearestDir(cwd, ".pi/agents");
165
-
166
- // Fallback directories (.claude)
167
- const userClaudeDir = path.join(os.homedir(), ".claude", "agents");
168
- const projectClaudeDir = findNearestDir(cwd, ".claude/agents");
169
-
170
- const agentMap = new Map<string, AgentDefinition>();
171
-
172
- // 1. Bundled agents (lowest priority)
173
- for (const agent of loadBundledAgents()) {
174
- agentMap.set(agent.name, agent);
137
+ const resolvedCwd = path.resolve(cwd);
138
+ const agentSources = Array.from(new Set(getConfigDirs("", { project: false }).map((entry) => entry.source)));
139
+
140
+ // Get user directories (priority order: .omp, .pi, .claude, ...)
141
+ const userDirs = getConfigDirs("agents", { project: false })
142
+ .filter((entry) => agentSources.includes(entry.source))
143
+ .map((entry) => ({
144
+ ...entry,
145
+ path: path.resolve(entry.path),
146
+ }));
147
+
148
+ // Get project directories by walking up from cwd (priority order)
149
+ const projectDirs = findAllNearestProjectConfigDirs("agents", resolvedCwd)
150
+ .filter((entry) => agentSources.includes(entry.source))
151
+ .map((entry) => ({
152
+ ...entry,
153
+ path: path.resolve(entry.path),
154
+ }));
155
+
156
+ const orderedSources = agentSources.filter(
157
+ (source) =>
158
+ userDirs.some((entry) => entry.source === source) || projectDirs.some((entry) => entry.source === source),
159
+ );
160
+
161
+ const orderedDirs: Array<{ dir: string; source: AgentSource }> = [];
162
+ for (const source of orderedSources) {
163
+ const project = projectDirs.find((entry) => entry.source === source);
164
+ if (project) orderedDirs.push({ dir: project.path, source: "project" });
165
+ const user = userDirs.find((entry) => entry.source === source);
166
+ if (user) orderedDirs.push({ dir: user.path, source: "user" });
175
167
  }
176
168
 
177
- // 2. User agents (.claude then .pi - .pi overrides .claude)
178
- for (const agent of loadAgentsFromDir(userClaudeDir, "user")) {
179
- agentMap.set(agent.name, agent);
180
- }
181
- for (const agent of loadAgentsFromDir(userPiDir, "user")) {
182
- agentMap.set(agent.name, agent);
183
- }
169
+ const agents: AgentDefinition[] = [];
170
+ const seen = new Set<string>();
184
171
 
185
- // 3. Project agents (highest priority - .claude then .pi)
186
- if (projectClaudeDir) {
187
- for (const agent of loadAgentsFromDir(projectClaudeDir, "project")) {
188
- agentMap.set(agent.name, agent);
172
+ for (const { dir, source } of orderedDirs) {
173
+ for (const agent of loadAgentsFromDir(dir, source)) {
174
+ if (seen.has(agent.name)) continue;
175
+ agents.push(agent);
176
+ seen.add(agent.name);
189
177
  }
190
178
  }
191
- if (projectPiDir) {
192
- for (const agent of loadAgentsFromDir(projectPiDir, "project")) {
193
- agentMap.set(agent.name, agent);
194
- }
179
+
180
+ for (const agent of loadBundledAgents()) {
181
+ if (seen.has(agent.name)) continue;
182
+ agents.push(agent);
183
+ seen.add(agent.name);
195
184
  }
196
185
 
197
- return {
198
- agents: Array.from(agentMap.values()),
199
- projectAgentsDir: projectPiDir,
200
- };
186
+ const projectAgentsDir = projectDirs.length > 0 ? projectDirs[0].path : null;
187
+
188
+ return { agents, projectAgentsDir };
201
189
  }
202
190
 
203
191
  /**
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Subprocess execution for subagents.
3
3
  *
4
- * Spawns `pi` in JSON mode to execute tasks with isolated context.
4
+ * Spawns `omp` in JSON mode to execute tasks with isolated context.
5
5
  * Parses JSON events for progress tracking.
6
6
  */
7
7
 
@@ -18,15 +18,15 @@ import {
18
18
  type AgentProgress,
19
19
  MAX_OUTPUT_BYTES,
20
20
  MAX_OUTPUT_LINES,
21
- PI_BLOCKED_AGENT_ENV,
21
+ OMP_BLOCKED_AGENT_ENV,
22
22
  type SingleResult,
23
23
  } from "./types";
24
24
 
25
- /** pi command: 'pi.cmd' on Windows, 'pi' elsewhere */
26
- const PI_CMD = process.platform === "win32" ? "pi.cmd" : "pi";
25
+ /** omp command: 'omp.cmd' on Windows, 'omp' elsewhere */
26
+ const OMP_CMD = process.platform === "win32" ? "omp.cmd" : "omp";
27
27
 
28
28
  /** Windows shell option for spawn */
29
- const PI_SHELL_OPT = process.platform === "win32";
29
+ const OMP_SHELL_OPT = process.platform === "win32";
30
30
 
31
31
  /** Options for subprocess execution */
32
32
  export interface ExecutorOptions {
@@ -143,7 +143,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
143
143
  const tempDir = os.tmpdir();
144
144
  const promptFile = path.join(
145
145
  tempDir,
146
- `pi-agent-${agent.name}-${Date.now()}-${Math.random().toString(36).slice(2)}.md`,
146
+ `omp-agent-${agent.name}-${Date.now()}-${Math.random().toString(36).slice(2)}.md`,
147
147
  );
148
148
 
149
149
  try {
@@ -217,14 +217,14 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
217
217
  // Set up environment - block same-agent recursion unless explicitly recursive
218
218
  const env = { ...process.env };
219
219
  if (!agent.recursive) {
220
- env[PI_BLOCKED_AGENT_ENV] = agent.name;
220
+ env[OMP_BLOCKED_AGENT_ENV] = agent.name;
221
221
  }
222
222
 
223
223
  // Spawn subprocess
224
- const proc = spawn(PI_CMD, args, {
224
+ const proc = spawn(OMP_CMD, args, {
225
225
  cwd,
226
226
  stdio: ["ignore", "pipe", "pipe"],
227
- shell: PI_SHELL_OPT,
227
+ shell: OMP_SHELL_OPT,
228
228
  env,
229
229
  });
230
230
 
@@ -2,9 +2,9 @@
2
2
  * Task tool - Delegate tasks to specialized agents.
3
3
  *
4
4
  * Discovers agent definitions from:
5
- * - Bundled agents (shipped with pi-coding-agent)
6
- * - ~/.pi/agent/agents/*.md (user-level)
7
- * - .pi/agents/*.md (project-level)
5
+ * - Bundled agents (shipped with omp-coding-agent)
6
+ * - ~/.omp/agent/agents/*.md (user-level)
7
+ * - .omp/agents/*.md (project-level)
8
8
  *
9
9
  * Supports:
10
10
  * - Single agent execution
@@ -25,8 +25,8 @@ import {
25
25
  MAX_AGENTS_IN_DESCRIPTION,
26
26
  MAX_CONCURRENCY,
27
27
  MAX_PARALLEL_TASKS,
28
- PI_BLOCKED_AGENT_ENV,
29
- PI_NO_SUBAGENTS_ENV,
28
+ OMP_BLOCKED_AGENT_ENV,
29
+ OMP_NO_SUBAGENTS_ENV,
30
30
  type TaskToolDetails,
31
31
  taskSchema,
32
32
  } from "./types";
@@ -122,13 +122,13 @@ function buildDescription(cwd: string): string {
122
122
  `- tasks: Array of {agent, task, model?} - tasks to run in parallel (max ${MAX_PARALLEL_TASKS}, ${MAX_CONCURRENCY} concurrent)`,
123
123
  );
124
124
  lines.push(
125
- ' - model: (optional) Override the agent\'s default model with fuzzy matching (e.g., "sonnet", "codex", "5.2"). Supports comma-separated fallbacks: "gpt, opus" tries gpt first, then opus. Use "default" for pi\'s default model',
125
+ ' - model: (optional) Override the agent\'s default model with fuzzy matching (e.g., "sonnet", "codex", "5.2"). Supports comma-separated fallbacks: "gpt, opus" tries gpt first, then opus. Use "default" for omp\'s default model',
126
126
  );
127
127
  lines.push(
128
128
  "- context: (optional) Shared context string prepended to all task prompts - use this to avoid repeating instructions",
129
129
  );
130
130
  lines.push("");
131
- lines.push("Results are always written to {tempdir}/pi-task-{runId}/task_{agent}_{index}.md");
131
+ lines.push("Results are always written to {tempdir}/omp-task-{runId}/task_{agent}_{index}.md");
132
132
  lines.push("");
133
133
  lines.push("Example usage:");
134
134
  lines.push("");
@@ -173,7 +173,7 @@ function buildDescription(cwd: string): string {
173
173
  lines.push(' { "agent": "explore", "task": "Search in tests/" }');
174
174
  lines.push(" ]");
175
175
  lines.push("}");
176
- lines.push("Results → {tempdir}/pi-task-{runId}/task_explore_*.md");
176
+ lines.push("Results → {tempdir}/omp-task-{runId}/task_explore_*.md");
177
177
  lines.push("</example>");
178
178
 
179
179
  return lines.join("\n");
@@ -189,7 +189,7 @@ export function createTaskTool(
189
189
  ): AgentTool<typeof taskSchema, TaskToolDetails, Theme> {
190
190
  const hasOutputTool = options?.availableTools?.has("output") ?? false;
191
191
  // Check if subagents are completely inhibited (legacy recursion prevention)
192
- if (process.env[PI_NO_SUBAGENTS_ENV]) {
192
+ if (process.env[OMP_NO_SUBAGENTS_ENV]) {
193
193
  return {
194
194
  name: "task",
195
195
  label: "Task",
@@ -207,7 +207,7 @@ export function createTaskTool(
207
207
  }
208
208
 
209
209
  // Check for same-agent blocking (allows other agent types)
210
- const blockedAgent = process.env[PI_BLOCKED_AGENT_ENV];
210
+ const blockedAgent = process.env[OMP_BLOCKED_AGENT_ENV];
211
211
 
212
212
  return {
213
213
  name: "task",
@@ -8,19 +8,18 @@
8
8
  * - Fuzzy match: "opus" → "p-anthropic/claude-opus-4-5"
9
9
  * - Comma fallback: "gpt, opus" → tries gpt first, then opus
10
10
  * - "default" → undefined (use system default)
11
- * - "pi/slow" → configured slow model from settings
11
+ * - "omp/slow" → configured slow model from settings
12
12
  */
13
13
 
14
14
  import { spawnSync } from "node:child_process";
15
- import { existsSync, readFileSync } from "node:fs";
16
- import { homedir } from "node:os";
17
- import { join } from "node:path";
15
+ import { type Settings, settingsCapability } from "../../../capability/settings";
16
+ import { loadSync } from "../../../discovery";
18
17
 
19
- /** pi command: 'pi.cmd' on Windows, 'pi' elsewhere */
20
- const PI_CMD = process.platform === "win32" ? "pi.cmd" : "pi";
18
+ /** omp command: 'omp.cmd' on Windows, 'omp' elsewhere */
19
+ const OMP_CMD = process.platform === "win32" ? "omp.cmd" : "omp";
21
20
 
22
21
  /** Windows shell option for spawn/spawnSync */
23
- const PI_SHELL_OPT = process.platform === "win32";
22
+ const OMP_SHELL_OPT = process.platform === "win32";
24
23
 
25
24
  /** Cache for available models (provider/modelId format) */
26
25
  let cachedModels: string[] | null = null;
@@ -31,7 +30,7 @@ let cacheExpiry = 0;
31
30
  const CACHE_TTL_MS = 5 * 60 * 1000;
32
31
 
33
32
  /**
34
- * Get available models from `pi --list-models`.
33
+ * Get available models from `omp --list-models`.
35
34
  * Returns models in "provider/modelId" format.
36
35
  * Caches the result for performance.
37
36
  */
@@ -42,10 +41,10 @@ export function getAvailableModels(): string[] {
42
41
  }
43
42
 
44
43
  try {
45
- const result = spawnSync(PI_CMD, ["--list-models"], {
44
+ const result = spawnSync(OMP_CMD, ["--list-models"], {
46
45
  encoding: "utf-8",
47
46
  timeout: 5000,
48
- shell: PI_SHELL_OPT,
47
+ shell: OMP_SHELL_OPT,
49
48
  });
50
49
 
51
50
  if (result.status !== 0 || !result.stdout) {
@@ -83,26 +82,29 @@ export function clearModelCache(): void {
83
82
  }
84
83
 
85
84
  /**
86
- * Load model roles from settings file.
85
+ * Load model roles from settings files using capability API.
87
86
  */
88
87
  function loadModelRoles(): Record<string, string> {
89
- try {
90
- const settingsPath = join(homedir(), ".pi", "agent", "settings.json");
91
- if (!existsSync(settingsPath)) return {};
92
- const content = readFileSync(settingsPath, "utf-8");
93
- const settings = JSON.parse(content);
94
- return settings.modelRoles ?? {};
95
- } catch {
96
- return {};
88
+ const result = loadSync<Settings>(settingsCapability.id, { cwd: process.cwd() });
89
+
90
+ // Merge all settings, prioritizing first (highest priority)
91
+ let modelRoles: Record<string, string> = {};
92
+ for (const settings of result.items.reverse()) {
93
+ const roles = settings.data.modelRoles as Record<string, string> | undefined;
94
+ if (roles) {
95
+ modelRoles = { ...modelRoles, ...roles };
96
+ }
97
97
  }
98
+
99
+ return modelRoles;
98
100
  }
99
101
 
100
102
  /**
101
- * Resolve a pi/<role> alias to a model string.
103
+ * Resolve an omp/<role> alias to a model string.
102
104
  * Looks up the role in settings.modelRoles and returns the configured model.
103
105
  * Returns undefined if the role isn't configured.
104
106
  */
105
- function resolvePiAlias(role: string, availableModels: string[]): string | undefined {
107
+ function resolveOmpAlias(role: string, availableModels: string[]): string | undefined {
106
108
  const roles = loadModelRoles();
107
109
 
108
110
  // Look up role in settings (case-insensitive)
@@ -148,10 +150,10 @@ export function resolveModelPattern(pattern: string | undefined, availableModels
148
150
  .filter(Boolean);
149
151
 
150
152
  for (const p of patterns) {
151
- // Handle pi/<role> aliases - looks up role in settings.modelRoles
152
- if (p.toLowerCase().startsWith("pi/")) {
153
- const role = p.slice(3); // Remove "pi/" prefix
154
- const resolved = resolvePiAlias(role, models);
153
+ // Handle omp/<role> aliases - looks up role in settings.modelRoles
154
+ if (p.toLowerCase().startsWith("omp/")) {
155
+ const role = p.slice(4); // Remove "omp/" prefix
156
+ const resolved = resolveOmpAlias(role, models);
155
157
  if (resolved) return resolved;
156
158
  continue; // Role not configured, try next pattern
157
159
  }
@@ -28,10 +28,10 @@ export const MAX_OUTPUT_LINES = 5000;
28
28
  export const MAX_AGENTS_IN_DESCRIPTION = 10;
29
29
 
30
30
  /** Environment variable to inhibit subagent spawning (legacy, still checked for backwards compat) */
31
- export const PI_NO_SUBAGENTS_ENV = "PI_NO_SUBAGENTS";
31
+ export const OMP_NO_SUBAGENTS_ENV = "OMP_NO_SUBAGENTS";
32
32
 
33
33
  /** Environment variable containing blocked agent name (self-recursion prevention) */
34
- export const PI_BLOCKED_AGENT_ENV = "PI_BLOCKED_AGENT";
34
+ export const OMP_BLOCKED_AGENT_ENV = "OMP_BLOCKED_AGENT";
35
35
 
36
36
  /** Task tool parameters */
37
37
  export const taskSchema = Type.Object({
@@ -309,7 +309,7 @@ function convertWithMarkitdown(
309
309
 
310
310
  // Write to temp file with extension hint
311
311
  const ext = extensionHint || ".bin";
312
- const tmpFile = path.join(os.tmpdir(), `pi-convert-${Date.now()}${ext}`);
312
+ const tmpFile = path.join(os.tmpdir(), `omp-convert-${Date.now()}${ext}`);
313
313
 
314
314
  try {
315
315
  fs.writeFileSync(tmpFile, content);
@@ -531,7 +531,7 @@ function parseFeedToMarkdown(content: string, maxItems = 10): string {
531
531
  * Render HTML to text using lynx
532
532
  */
533
533
  function renderWithLynx(html: string, timeout: number): { content: string; ok: boolean } {
534
- const tmpFile = path.join(os.tmpdir(), `pi-render-${Date.now()}.html`);
534
+ const tmpFile = path.join(os.tmpdir(), `omp-render-${Date.now()}.html`);
535
535
  try {
536
536
  fs.writeFileSync(tmpFile, html);
537
537
  // Convert path to file URL (handles Windows paths correctly)
@@ -712,7 +712,7 @@ async function fetchGitHubApi(endpoint: string, timeout: number): Promise<{ data
712
712
 
713
713
  const headers: Record<string, string> = {
714
714
  Accept: "application/vnd.github.v3+json",
715
- "User-Agent": "pi-web-fetch/1.0",
715
+ "User-Agent": "omp-web-fetch/1.0",
716
716
  };
717
717
 
718
718
  // Use GITHUB_TOKEN if available