@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.
- package/CHANGELOG.md +72 -34
- package/README.md +100 -100
- package/docs/compaction.md +8 -8
- package/docs/config-usage.md +113 -0
- package/docs/custom-tools.md +8 -8
- package/docs/extension-loading.md +58 -58
- package/docs/hooks.md +11 -11
- package/docs/rpc.md +4 -4
- package/docs/sdk.md +14 -14
- package/docs/session-tree-plan.md +1 -1
- package/docs/session.md +2 -2
- package/docs/skills.md +16 -16
- package/docs/theme.md +9 -9
- package/docs/tui.md +1 -1
- package/examples/README.md +1 -1
- package/examples/custom-tools/README.md +4 -4
- package/examples/custom-tools/subagent/README.md +13 -13
- package/examples/custom-tools/subagent/agents.ts +2 -2
- package/examples/custom-tools/subagent/index.ts +5 -5
- package/examples/hooks/README.md +3 -3
- package/examples/hooks/auto-commit-on-exit.ts +1 -1
- package/examples/hooks/custom-compaction.ts +1 -1
- package/examples/sdk/01-minimal.ts +1 -1
- package/examples/sdk/04-skills.ts +1 -1
- package/examples/sdk/05-tools.ts +1 -1
- package/examples/sdk/08-slash-commands.ts +1 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/README.md +2 -2
- package/package.json +13 -11
- package/src/capability/context-file.ts +40 -0
- package/src/capability/extension.ts +48 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +616 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +52 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +52 -0
- package/src/capability/settings.ts +35 -0
- package/src/capability/skill.ts +49 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/system-prompt.ts +35 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +166 -0
- package/src/cli/args.ts +2 -2
- package/src/cli/plugin-cli.ts +24 -19
- package/src/cli/update-cli.ts +10 -10
- package/src/config.ts +290 -6
- package/src/core/auth-storage.ts +32 -9
- package/src/core/bash-executor.ts +1 -1
- package/src/core/custom-commands/loader.ts +44 -50
- package/src/core/custom-tools/index.ts +1 -0
- package/src/core/custom-tools/loader.ts +67 -69
- package/src/core/custom-tools/types.ts +10 -1
- package/src/core/hooks/loader.ts +13 -42
- package/src/core/index.ts +0 -1
- package/src/core/logger.ts +7 -7
- package/src/core/mcp/client.ts +1 -1
- package/src/core/mcp/config.ts +94 -146
- package/src/core/mcp/index.ts +0 -4
- package/src/core/mcp/loader.ts +26 -22
- package/src/core/mcp/manager.ts +18 -23
- package/src/core/mcp/tool-bridge.ts +9 -1
- package/src/core/mcp/types.ts +2 -0
- package/src/core/model-registry.ts +25 -8
- package/src/core/plugins/installer.ts +1 -1
- package/src/core/plugins/loader.ts +17 -11
- package/src/core/plugins/manager.ts +2 -2
- package/src/core/plugins/paths.ts +12 -7
- package/src/core/plugins/types.ts +3 -3
- package/src/core/sdk.ts +48 -27
- package/src/core/session-manager.ts +4 -4
- package/src/core/settings-manager.ts +45 -21
- package/src/core/skills.ts +208 -293
- package/src/core/slash-commands.ts +34 -165
- package/src/core/system-prompt.ts +58 -65
- package/src/core/timings.ts +2 -2
- package/src/core/tools/lsp/config.ts +38 -17
- package/src/core/tools/task/agents.ts +21 -0
- package/src/core/tools/task/artifacts.ts +1 -1
- package/src/core/tools/task/bundled-agents/reviewer.md +2 -1
- package/src/core/tools/task/bundled-agents/task.md +1 -0
- package/src/core/tools/task/commands.ts +30 -107
- package/src/core/tools/task/discovery.ts +75 -66
- package/src/core/tools/task/executor.ts +25 -10
- package/src/core/tools/task/index.ts +35 -10
- package/src/core/tools/task/model-resolver.ts +27 -25
- package/src/core/tools/task/types.ts +6 -2
- package/src/core/tools/web-fetch.ts +3 -3
- package/src/core/tools/web-search/auth.ts +40 -34
- package/src/core/tools/web-search/index.ts +1 -1
- package/src/core/tools/web-search/providers/anthropic.ts +1 -1
- package/src/discovery/agents-md.ts +75 -0
- package/src/discovery/builtin.ts +646 -0
- package/src/discovery/claude.ts +623 -0
- package/src/discovery/cline.ts +102 -0
- package/src/discovery/codex.ts +571 -0
- package/src/discovery/cursor.ts +264 -0
- package/src/discovery/gemini.ts +368 -0
- package/src/discovery/github.ts +120 -0
- package/src/discovery/helpers.test.ts +127 -0
- package/src/discovery/helpers.ts +249 -0
- package/src/discovery/index.ts +84 -0
- package/src/discovery/mcp-json.ts +127 -0
- package/src/discovery/vscode.ts +99 -0
- package/src/discovery/windsurf.ts +216 -0
- package/src/main.ts +14 -13
- package/src/migrations.ts +24 -3
- package/src/modes/interactive/components/hook-editor.ts +1 -1
- package/src/modes/interactive/components/plugin-settings.ts +1 -1
- package/src/modes/interactive/components/settings-defs.ts +38 -2
- package/src/modes/interactive/components/settings-selector.ts +1 -0
- package/src/modes/interactive/components/welcome.ts +2 -2
- package/src/modes/interactive/interactive-mode.ts +233 -16
- package/src/modes/interactive/theme/theme-schema.json +1 -1
- package/src/utils/clipboard.ts +1 -1
- package/src/utils/shell-snapshot.ts +2 -2
- package/src/utils/shell.ts +7 -7
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
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., "
|
|
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
|
|
190
|
-
*
|
|
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
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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 {
|
|
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
|
|
191
|
-
*
|
|
192
|
-
*
|
|
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
|
|
202
|
-
const seenPaths = new Set<string>();
|
|
180
|
+
const result = loadSync(contextFileCapability.id, { cwd: resolvedCwd });
|
|
203
181
|
|
|
204
|
-
//
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
//
|
|
212
|
-
|
|
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
|
-
|
|
215
|
-
|
|
200
|
+
return files;
|
|
201
|
+
}
|
|
216
202
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
-
|
|
226
|
-
if (currentDir === root) break;
|
|
210
|
+
const result = loadSync<SystemPromptFile>(systemPromptCapability.id, { cwd: resolvedCwd });
|
|
227
211
|
|
|
228
|
-
|
|
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
|
-
//
|
|
235
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
281
|
+
(skillsSettings?.enabled !== false ? loadSkills({ ...skillsSettings, cwd: resolvedCwd }).skills : []);
|
|
296
282
|
|
|
297
283
|
if (resolvedCustomPrompt) {
|
|
298
|
-
let prompt =
|
|
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
|
}
|
package/src/core/timings.ts
CHANGED
|
@@ -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: "
|
|
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
|
|
677
|
+
* Supports both visible and hidden variants at each config location.
|
|
677
678
|
*/
|
|
678
679
|
function getConfigPaths(cwd: string): string[] {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
join(cwd,
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
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
|
|
698
|
-
* 2.
|
|
699
|
-
* 3.
|
|
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(), `
|
|
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,
|
|
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
|
|