@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,8 +1,7 @@
1
- import { existsSync, readdirSync, readFileSync } from "node:fs";
2
- import { homedir } from "node:os";
3
- import { join, resolve } from "node:path";
4
- import { CONFIG_DIR_NAME, getCommandsDir } from "../config";
5
- import { logger } from "./logger";
1
+ import { slashCommandCapability } from "../capability/slash-command";
2
+ import type { SlashCommand } from "../discovery";
3
+ import { loadSync } from "../discovery";
4
+ import { parseFrontmatter } from "../discovery/helpers";
6
5
 
7
6
  /**
8
7
  * Represents a custom slash command loaded from a file
@@ -11,37 +10,9 @@ export interface FileSlashCommand {
11
10
  name: string;
12
11
  description: string;
13
12
  content: string;
14
- source: string; // e.g., "(user)", "(project)", "(project:frontend)"
15
- }
16
-
17
- /**
18
- * Parse YAML frontmatter from markdown content
19
- * Returns { frontmatter, content } where content has frontmatter stripped
20
- */
21
- function parseFrontmatter(content: string): { frontmatter: Record<string, string>; content: string } {
22
- const frontmatter: Record<string, string> = {};
23
-
24
- if (!content.startsWith("---")) {
25
- return { frontmatter, content };
26
- }
27
-
28
- const endIndex = content.indexOf("\n---", 3);
29
- if (endIndex === -1) {
30
- return { frontmatter, content };
31
- }
32
-
33
- const frontmatterBlock = content.slice(4, endIndex);
34
- const remainingContent = content.slice(endIndex + 4).trim();
35
-
36
- // Simple YAML parsing - just key: value pairs
37
- for (const line of frontmatterBlock.split("\n")) {
38
- const match = line.match(/^(\w+):\s*(.*)$/);
39
- if (match) {
40
- frontmatter[match[1]] = match[2].trim();
41
- }
42
- }
43
-
44
- return { frontmatter, content: remainingContent };
13
+ source: string; // e.g., "via Claude Code (User)"
14
+ /** Source metadata for display */
15
+ _source?: { providerName: string; level: "user" | "project" | "native" };
45
16
  }
46
17
 
47
18
  /**
@@ -100,146 +71,44 @@ export function substituteArgs(content: string, args: string[]): string {
100
71
  return result;
101
72
  }
102
73
 
103
- type CommandSource = "builtin" | "claude-user" | "claude-project" | "user" | "project";
104
-
105
- /**
106
- * Recursively scan a directory for .md files (and symlinks to .md files) and load them as slash commands
107
- */
108
- function loadCommandsFromDir(dir: string, source: CommandSource, subdir: string = ""): FileSlashCommand[] {
109
- const commands: FileSlashCommand[] = [];
110
-
111
- if (!existsSync(dir)) {
112
- return commands;
113
- }
114
-
115
- try {
116
- const entries = readdirSync(dir, { withFileTypes: true });
117
-
118
- for (const entry of entries) {
119
- const fullPath = join(dir, entry.name);
120
-
121
- if (entry.isDirectory()) {
122
- // Recurse into subdirectory
123
- const newSubdir = subdir ? `${subdir}:${entry.name}` : entry.name;
124
- commands.push(...loadCommandsFromDir(fullPath, source, newSubdir));
125
- } else if ((entry.isFile() || entry.isSymbolicLink()) && entry.name.endsWith(".md")) {
126
- try {
127
- const rawContent = readFileSync(fullPath, "utf-8");
128
- const { frontmatter, content } = parseFrontmatter(rawContent);
129
-
130
- const name = entry.name.slice(0, -3); // Remove .md extension
131
-
132
- // Build source string based on source type
133
- const sourceLabel =
134
- source === "builtin"
135
- ? "builtin"
136
- : source === "claude-user"
137
- ? "claude-user"
138
- : source === "claude-project"
139
- ? "claude-project"
140
- : source === "user"
141
- ? "user"
142
- : "project";
143
- const sourceStr = subdir ? `(${sourceLabel}:${subdir})` : `(${sourceLabel})`;
144
-
145
- // Get description from frontmatter or first non-empty line
146
- let description = frontmatter.description || "";
147
- if (!description) {
148
- const firstLine = content.split("\n").find((line) => line.trim());
149
- if (firstLine) {
150
- // Truncate if too long
151
- description = firstLine.slice(0, 60);
152
- if (firstLine.length > 60) description += "...";
153
- }
154
- }
155
-
156
- // Append source to description
157
- description = description ? `${description} ${sourceStr}` : sourceStr;
158
-
159
- commands.push({
160
- name,
161
- description,
162
- content,
163
- source: sourceStr,
164
- });
165
- } catch (err) {
166
- logger.debug("Failed to read slash command file", { error: String(err) });
167
- }
168
- }
169
- }
170
- } catch (err) {
171
- logger.debug("Failed to read slash command directory", { error: String(err) });
172
- }
173
-
174
- return commands;
175
- }
176
-
177
74
  export interface LoadSlashCommandsOptions {
178
75
  /** Working directory for project-local commands. Default: process.cwd() */
179
76
  cwd?: string;
180
- /** Agent config directory for global commands. Default: from getCommandsDir() */
181
- agentDir?: string;
182
- /** Enable loading from ~/.claude/commands/. Default: true */
183
- enableClaudeUser?: boolean;
184
- /** Enable loading from .claude/commands/. Default: true */
185
- enableClaudeProject?: boolean;
186
77
  }
187
78
 
188
79
  /**
189
- * Load all custom slash commands from:
190
- * 1. Builtin: package commands/
191
- * 2. Claude user: ~/.claude/commands/
192
- * 3. Claude project: .claude/commands/
193
- * 4. Pi user: agentDir/commands/
194
- * 5. Pi project: cwd/{CONFIG_DIR_NAME}/commands/
195
- *
196
- * First occurrence wins (earlier sources have priority).
80
+ * Load all custom slash commands using the capability API.
81
+ * Loads from all registered providers (builtin, user, project).
197
82
  */
198
83
  export function loadSlashCommands(options: LoadSlashCommandsOptions = {}): FileSlashCommand[] {
199
- const resolvedCwd = options.cwd ?? process.cwd();
200
- const resolvedAgentDir = options.agentDir ?? getCommandsDir();
201
- const enableClaudeUser = options.enableClaudeUser ?? true;
202
- const enableClaudeProject = options.enableClaudeProject ?? true;
203
-
204
- const commands: FileSlashCommand[] = [];
205
- const seenNames = new Set<string>();
206
-
207
- const addCommands = (newCommands: FileSlashCommand[]) => {
208
- for (const cmd of newCommands) {
209
- if (!seenNames.has(cmd.name)) {
210
- commands.push(cmd);
211
- seenNames.add(cmd.name);
84
+ const result = loadSync<SlashCommand>(slashCommandCapability.id, { cwd: options.cwd });
85
+
86
+ return result.items.map((cmd) => {
87
+ const { frontmatter, body } = parseFrontmatter(cmd.content);
88
+ const frontmatterDesc = typeof frontmatter.description === "string" ? frontmatter.description.trim() : "";
89
+
90
+ // Get description from frontmatter or first non-empty line
91
+ let description = frontmatterDesc;
92
+ if (!description) {
93
+ const firstLine = body.split("\n").find((line) => line.trim());
94
+ if (firstLine) {
95
+ description = firstLine.slice(0, 60);
96
+ if (firstLine.length > 60) description += "...";
212
97
  }
213
98
  }
214
- };
215
-
216
- // 1. Builtin commands (from package)
217
- const builtinDir = join(import.meta.dir, "../commands");
218
- if (existsSync(builtinDir)) {
219
- addCommands(loadCommandsFromDir(builtinDir, "builtin"));
220
- }
221
-
222
- // 2. Claude user commands (~/.claude/commands/)
223
- if (enableClaudeUser) {
224
- const claudeUserDir = join(homedir(), ".claude", "commands");
225
- addCommands(loadCommandsFromDir(claudeUserDir, "claude-user"));
226
- }
227
99
 
228
- // 3. Claude project commands (.claude/commands/)
229
- if (enableClaudeProject) {
230
- const claudeProjectDir = resolve(resolvedCwd, ".claude", "commands");
231
- addCommands(loadCommandsFromDir(claudeProjectDir, "claude-project"));
232
- }
233
-
234
- // 4. Pi user commands (agentDir/commands/)
235
- const globalCommandsDir = options.agentDir ? join(options.agentDir, "commands") : resolvedAgentDir;
236
- addCommands(loadCommandsFromDir(globalCommandsDir, "user"));
237
-
238
- // 5. Pi project commands (cwd/{CONFIG_DIR_NAME}/commands/)
239
- const projectCommandsDir = resolve(resolvedCwd, CONFIG_DIR_NAME, "commands");
240
- addCommands(loadCommandsFromDir(projectCommandsDir, "project"));
241
-
242
- return commands;
100
+ // Format source label: "via ProviderName Level"
101
+ const capitalizedLevel = cmd.level.charAt(0).toUpperCase() + cmd.level.slice(1);
102
+ const sourceStr = `via ${cmd._source.providerName} ${capitalizedLevel}`;
103
+
104
+ return {
105
+ name: cmd.name,
106
+ description,
107
+ content: body,
108
+ source: sourceStr,
109
+ _source: { providerName: cmd._source.providerName, level: cmd.level },
110
+ };
111
+ });
243
112
  }
244
113
 
245
114
  /**
@@ -3,9 +3,11 @@
3
3
  */
4
4
 
5
5
  import { existsSync, readFileSync } from "node:fs";
6
- import { join, resolve } from "node:path";
7
6
  import chalk from "chalk";
8
- import { getAgentDir, getDocsPath, getExamplesPath, getReadmePath } from "../config";
7
+ import { contextFileCapability } from "../capability/context-file";
8
+ import { systemPromptCapability } from "../capability/system-prompt";
9
+ import { getDocsPath, getExamplesPath, getReadmePath } from "../config";
10
+ import { type ContextFile, loadSync, type SystemPrompt as SystemPromptFile } from "../discovery/index";
9
11
  import type { SkillsSettings } from "./settings-manager";
10
12
  import { formatSkillsForPrompt, loadSkills, type Skill } from "./skills";
11
13
  import type { ToolName } from "./tools/index";
@@ -160,81 +162,65 @@ export function resolvePromptInput(input: string | undefined, description: strin
160
162
  return input;
161
163
  }
162
164
 
163
- /** Look for AGENTS.md or CLAUDE.md in a directory (prefers AGENTS.md) */
164
- function loadContextFileFromDir(dir: string): { path: string; content: string } | null {
165
- const candidates = ["AGENTS.md", "CLAUDE.md"];
166
- for (const filename of candidates) {
167
- const filePath = join(dir, filename);
168
- if (existsSync(filePath)) {
169
- try {
170
- return {
171
- path: filePath,
172
- content: readFileSync(filePath, "utf-8"),
173
- };
174
- } catch (error) {
175
- console.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));
176
- }
177
- }
178
- }
179
- return null;
180
- }
181
-
182
165
  export interface LoadContextFilesOptions {
183
166
  /** Working directory to start walking up from. Default: process.cwd() */
184
167
  cwd?: string;
185
- /** Agent config directory for global context. Default: from getAgentDir() */
186
- agentDir?: string;
187
168
  }
188
169
 
189
170
  /**
190
- * Load all project context files in order:
191
- * 1. Global: agentDir/AGENTS.md or CLAUDE.md
192
- * 2. Parent directories (top-most first) down to cwd
193
- * Each returns {path, content} for separate messages
171
+ * Load all project context files using the capability API.
172
+ * Returns {path, content, depth} entries for all discovered context files.
173
+ * Files are sorted by depth (descending) so files closer to cwd appear last/more prominent.
194
174
  */
195
175
  export function loadProjectContextFiles(
196
176
  options: LoadContextFilesOptions = {},
197
- ): Array<{ path: string; content: string }> {
177
+ ): Array<{ path: string; content: string; depth?: number }> {
198
178
  const resolvedCwd = options.cwd ?? process.cwd();
199
- const resolvedAgentDir = options.agentDir ?? getAgentDir();
200
179
 
201
- const contextFiles: Array<{ path: string; content: string }> = [];
202
- const seenPaths = new Set<string>();
180
+ const result = loadSync(contextFileCapability.id, { cwd: resolvedCwd });
203
181
 
204
- // 1. Load global context from agentDir
205
- const globalContext = loadContextFileFromDir(resolvedAgentDir);
206
- if (globalContext) {
207
- contextFiles.push(globalContext);
208
- seenPaths.add(globalContext.path);
209
- }
182
+ // Convert ContextFile items and preserve depth info
183
+ const files = result.items.map((item) => {
184
+ const contextFile = item as ContextFile;
185
+ return {
186
+ path: contextFile.path,
187
+ content: contextFile.content,
188
+ depth: contextFile.depth,
189
+ };
190
+ });
210
191
 
211
- // 2. Walk up from cwd to root, collecting all context files
212
- const ancestorContextFiles: Array<{ path: string; content: string }> = [];
192
+ // Sort by depth (descending): higher depth (farther from cwd) comes first,
193
+ // so files closer to cwd appear later and are more prominent
194
+ files.sort((a, b) => {
195
+ const depthA = a.depth ?? -1;
196
+ const depthB = b.depth ?? -1;
197
+ return depthB - depthA;
198
+ });
213
199
 
214
- let currentDir = resolvedCwd;
215
- const root = resolve("/");
200
+ return files;
201
+ }
216
202
 
217
- while (true) {
218
- const contextFile = loadContextFileFromDir(currentDir);
219
- if (contextFile && !seenPaths.has(contextFile.path)) {
220
- // Add to beginning so we get top-most parent first
221
- ancestorContextFiles.unshift(contextFile);
222
- seenPaths.add(contextFile.path);
223
- }
203
+ /**
204
+ * Load system prompt customization files (SYSTEM.md).
205
+ * Returns combined content from all discovered SYSTEM.md files.
206
+ */
207
+ export function loadSystemPromptFiles(options: LoadContextFilesOptions = {}): string | null {
208
+ const resolvedCwd = options.cwd ?? process.cwd();
224
209
 
225
- // Stop if we've reached root
226
- if (currentDir === root) break;
210
+ const result = loadSync<SystemPromptFile>(systemPromptCapability.id, { cwd: resolvedCwd });
227
211
 
228
- // Move up one directory
229
- const parentDir = resolve(currentDir, "..");
230
- if (parentDir === currentDir) break; // Safety check
231
- currentDir = parentDir;
232
- }
212
+ if (result.items.length === 0) return null;
233
213
 
234
- // Add ancestor files in order (top-most cwd)
235
- contextFiles.push(...ancestorContextFiles);
214
+ // Combine all SYSTEM.md contents (user-level first, then project-level)
215
+ const userLevel = result.items.filter((item) => item.level === "user");
216
+ const projectLevel = result.items.filter((item) => item.level === "project");
236
217
 
237
- return contextFiles;
218
+ const parts: string[] = [];
219
+ for (const item of [...userLevel, ...projectLevel]) {
220
+ parts.push(item.content);
221
+ }
222
+
223
+ return parts.join("\n\n");
238
224
  }
239
225
 
240
226
  export interface BuildSystemPromptOptions {
@@ -248,10 +234,8 @@ export interface BuildSystemPromptOptions {
248
234
  skillsSettings?: SkillsSettings;
249
235
  /** Working directory. Default: process.cwd() */
250
236
  cwd?: string;
251
- /** Agent config directory. Default: from getAgentDir() */
252
- agentDir?: string;
253
237
  /** Pre-loaded context files (skips discovery if provided). */
254
- contextFiles?: Array<{ path: string; content: string }>;
238
+ contextFiles?: Array<{ path: string; content: string; depth?: number }>;
255
239
  /** Pre-loaded skills (skips discovery if provided). */
256
240
  skills?: Skill[];
257
241
  }
@@ -264,7 +248,6 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
264
248
  appendSystemPrompt,
265
249
  skillsSettings,
266
250
  cwd,
267
- agentDir,
268
251
  contextFiles: providedContextFiles,
269
252
  skills: providedSkills,
270
253
  } = options;
@@ -272,6 +255,9 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
272
255
  const resolvedCustomPrompt = resolvePromptInput(customPrompt, "system prompt");
273
256
  const resolvedAppendPrompt = resolvePromptInput(appendSystemPrompt, "append system prompt");
274
257
 
258
+ // Load SYSTEM.md customization (prepended to prompt)
259
+ const systemPromptCustomization = loadSystemPromptFiles({ cwd: resolvedCwd });
260
+
275
261
  const now = new Date();
276
262
  const dateTime = now.toLocaleString("en-US", {
277
263
  weekday: "long",
@@ -287,15 +273,17 @@ export function buildSystemPrompt(options: BuildSystemPromptOptions = {}): strin
287
273
  const appendSection = resolvedAppendPrompt ? `\n\n${resolvedAppendPrompt}` : "";
288
274
 
289
275
  // Resolve context files: use provided or discover
290
- const contextFiles = providedContextFiles ?? loadProjectContextFiles({ cwd: resolvedCwd, agentDir });
276
+ const contextFiles = providedContextFiles ?? loadProjectContextFiles({ cwd: resolvedCwd });
291
277
 
292
278
  // Resolve skills: use provided or discover
293
279
  const skills =
294
280
  providedSkills ??
295
- (skillsSettings?.enabled !== false ? loadSkills({ ...skillsSettings, cwd: resolvedCwd, agentDir }).skills : []);
281
+ (skillsSettings?.enabled !== false ? loadSkills({ ...skillsSettings, cwd: resolvedCwd }).skills : []);
296
282
 
297
283
  if (resolvedCustomPrompt) {
298
- let prompt = resolvedCustomPrompt;
284
+ let prompt = systemPromptCustomization
285
+ ? `${systemPromptCustomization}\n\n${resolvedCustomPrompt}`
286
+ : resolvedCustomPrompt;
299
287
 
300
288
  if (appendSection) {
301
289
  prompt += appendSection;
@@ -435,5 +423,10 @@ Documentation:
435
423
  prompt += `\nCurrent date and time: ${dateTime}`;
436
424
  prompt += `\nCurrent working directory: ${resolvedCwd}`;
437
425
 
426
+ // Prepend SYSTEM.md customization if present
427
+ if (systemPromptCustomization) {
428
+ prompt = `${systemPromptCustomization}\n\n${prompt}`;
429
+ }
430
+
438
431
  return prompt;
439
432
  }
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Central timing instrumentation for startup profiling.
3
- * Enable with PI_TIMING=1 environment variable.
3
+ * Enable with OMP_TIMING=1 or PI_TIMING=1 environment variable.
4
4
  */
5
5
 
6
- const ENABLED = process.env.PI_TIMING === "1";
6
+ const ENABLED = process.env.OMP_TIMING === "1" || process.env.PI_TIMING === "1";
7
7
  const timings: Array<{ label: string; ms: number }> = [];
8
8
  let lastTime = Date.now();
9
9
 
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { extname, join } from "node:path";
4
+ import { getConfigDirPaths } from "../../../config.js";
4
5
  import type { ServerConfig } from "./types";
5
6
 
6
7
  export interface LspConfig {
@@ -94,7 +95,7 @@ export const SERVERS: Record<string, ServerConfig> = {
94
95
  fileTypes: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
95
96
  rootMarkers: ["package.json", "tsconfig.json", "jsconfig.json"],
96
97
  initOptions: {
97
- hostInfo: "pi-coding-agent",
98
+ hostInfo: "omp-coding-agent",
98
99
  preferences: {
99
100
  includeInlayParameterNameHints: "all",
100
101
  includeInlayVariableTypeHints: true,
@@ -673,30 +674,50 @@ export function resolveCommand(command: string, cwd: string): string | null {
673
674
 
674
675
  /**
675
676
  * Configuration file search paths (in priority order).
676
- * Supports both visible and hidden variants, and both .pi subdirectory and root.
677
+ * Supports both visible and hidden variants at each config location.
677
678
  */
678
679
  function getConfigPaths(cwd: string): string[] {
679
- return [
680
- // Project-level configs (highest priority)
681
- join(cwd, "lsp.json"),
682
- join(cwd, ".lsp.json"),
683
- join(cwd, ".pi", "lsp.json"),
684
- join(cwd, ".pi", ".lsp.json"),
685
- // User-level configs (fallback)
686
- join(homedir(), ".pi", "lsp.json"),
687
- join(homedir(), ".pi", ".lsp.json"),
688
- join(homedir(), "lsp.json"),
689
- join(homedir(), ".lsp.json"),
690
- ];
680
+ const filenames = ["lsp.json", ".lsp.json"];
681
+ const paths: string[] = [];
682
+
683
+ // Project root files (highest priority)
684
+ for (const filename of filenames) {
685
+ paths.push(join(cwd, filename));
686
+ }
687
+
688
+ // Project config directories (.omp/, .pi/, .claude/)
689
+ const projectDirs = getConfigDirPaths("", { user: false, project: true, cwd });
690
+ for (const dir of projectDirs) {
691
+ for (const filename of filenames) {
692
+ paths.push(join(dir, filename));
693
+ }
694
+ }
695
+
696
+ // User config directories (~/.omp/agent/, ~/.pi/agent/, ~/.claude/)
697
+ const userDirs = getConfigDirPaths("", { user: true, project: false });
698
+ for (const dir of userDirs) {
699
+ for (const filename of filenames) {
700
+ paths.push(join(dir, filename));
701
+ }
702
+ }
703
+
704
+ // User home root files (lowest priority fallback)
705
+ for (const filename of filenames) {
706
+ paths.push(join(homedir(), filename));
707
+ }
708
+
709
+ return paths;
691
710
  }
692
711
 
693
712
  /**
694
713
  * Load LSP configuration.
695
714
  *
696
715
  * Priority:
697
- * 1. Project-level config: lsp.json, .lsp.json, .pi/lsp.json, .pi/.lsp.json
698
- * 2. User-level config: ~/.pi/lsp.json, ~/.pi/.lsp.json, ~/lsp.json, ~/.lsp.json
699
- * 3. Auto-detect from project markers + available binaries
716
+ * 1. Project root: lsp.json, .lsp.json
717
+ * 2. Project config dirs: .omp/lsp.json, .pi/lsp.json, .claude/lsp.json (+ hidden variants)
718
+ * 3. User config dirs: ~/.omp/agent/lsp.json, ~/.pi/agent/lsp.json, ~/.claude/lsp.json (+ hidden variants)
719
+ * 4. User home root: ~/lsp.json, ~/.lsp.json
720
+ * 5. Auto-detect from project markers + available binaries
700
721
  *
701
722
  * Config file format:
702
723
  * ```json
@@ -68,6 +68,26 @@ function parseAgent(fileName: string, content: string, source: AgentSource): Age
68
68
  .map((t) => t.trim())
69
69
  .filter(Boolean);
70
70
 
71
+ // Parse spawns field
72
+ let spawns: string[] | "*" | undefined;
73
+ if (frontmatter.spawns !== undefined) {
74
+ const spawnsRaw = frontmatter.spawns.trim();
75
+ if (spawnsRaw === "*") {
76
+ spawns = "*";
77
+ } else if (spawnsRaw) {
78
+ spawns = spawnsRaw
79
+ .split(",")
80
+ .map((s) => s.trim())
81
+ .filter(Boolean);
82
+ if (spawns.length === 0) spawns = undefined;
83
+ }
84
+ }
85
+
86
+ // Backward compat: infer spawns: "*" when tools includes "task"
87
+ if (spawns === undefined && tools?.includes("task")) {
88
+ spawns = "*";
89
+ }
90
+
71
91
  const recursive =
72
92
  frontmatter.recursive === undefined ? false : frontmatter.recursive === "true" || frontmatter.recursive === "1";
73
93
 
@@ -75,6 +95,7 @@ function parseAgent(fileName: string, content: string, source: AgentSource): Age
75
95
  name: frontmatter.name,
76
96
  description: frontmatter.description,
77
97
  tools: tools && tools.length > 0 ? tools : undefined,
98
+ spawns,
78
99
  model: frontmatter.model,
79
100
  recursive,
80
101
  systemPrompt: body,
@@ -84,7 +84,7 @@ export async function writeArtifacts(
84
84
  */
85
85
  export function createTempArtifactsDir(runId?: string): string {
86
86
  const id = runId || `${Date.now()}-${Math.random().toString(36).slice(2)}`;
87
- const dir = path.join(os.tmpdir(), `pi-task-${id}`);
87
+ const dir = path.join(os.tmpdir(), `omp-task-${id}`);
88
88
  ensureArtifactsDir(dir);
89
89
  return dir;
90
90
  }
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: reviewer
3
3
  description: Code review specialist for quality and security analysis
4
- tools: read, grep, find, ls, bash, task, report_finding, submit_review
4
+ tools: read, grep, find, ls, bash, report_finding, submit_review
5
+ spawns: explore
5
6
  model: pi/slow, gpt-5.2-codex, gpt-5.2, codex, gpt
6
7
  ---
7
8
 
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  name: task
3
3
  description: General-purpose subagent with full capabilities for delegated multi-step tasks
4
+ spawns: explore
4
5
  model: default
5
6
  ---
6
7