@oh-my-pi/pi-coding-agent 4.1.0 → 4.2.1
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 +66 -0
- package/README.md +2 -1
- package/docs/sdk.md +0 -3
- package/package.json +6 -5
- package/src/config.ts +9 -0
- package/src/core/agent-session.ts +3 -3
- package/src/core/agent-storage.ts +450 -0
- package/src/core/auth-storage.ts +102 -183
- package/src/core/compaction/branch-summarization.ts +5 -4
- package/src/core/compaction/compaction.ts +7 -6
- package/src/core/compaction/utils.ts +6 -11
- package/src/core/custom-commands/bundled/review/index.ts +22 -94
- package/src/core/custom-share.ts +66 -0
- package/src/core/export-html/index.ts +1 -33
- package/src/core/history-storage.ts +15 -7
- package/src/core/prompt-templates.ts +271 -1
- package/src/core/sdk.ts +14 -3
- package/src/core/settings-manager.ts +100 -34
- package/src/core/slash-commands.ts +4 -1
- package/src/core/storage-migration.ts +215 -0
- package/src/core/system-prompt.ts +130 -290
- package/src/core/title-generator.ts +3 -2
- package/src/core/tools/ask.ts +2 -2
- package/src/core/tools/bash.ts +2 -1
- package/src/core/tools/calculator.ts +2 -1
- package/src/core/tools/complete.ts +5 -2
- package/src/core/tools/edit.ts +2 -1
- package/src/core/tools/find.ts +2 -1
- package/src/core/tools/gemini-image.ts +2 -1
- package/src/core/tools/git.ts +2 -2
- package/src/core/tools/grep.ts +2 -1
- package/src/core/tools/index.test.ts +0 -28
- package/src/core/tools/index.ts +0 -6
- package/src/core/tools/lsp/index.ts +2 -1
- package/src/core/tools/output.ts +2 -1
- package/src/core/tools/read.ts +4 -1
- package/src/core/tools/ssh.ts +4 -2
- package/src/core/tools/task/agents.ts +56 -30
- package/src/core/tools/task/commands.ts +5 -8
- package/src/core/tools/task/index.ts +7 -15
- package/src/core/tools/web-fetch.ts +2 -1
- package/src/core/tools/web-search/auth.ts +106 -16
- package/src/core/tools/web-search/index.ts +3 -2
- package/src/core/tools/web-search/providers/anthropic.ts +44 -6
- package/src/core/tools/write.ts +2 -1
- package/src/core/voice.ts +3 -1
- package/src/discovery/builtin.ts +9 -54
- package/src/discovery/claude.ts +16 -69
- package/src/discovery/codex.ts +11 -36
- package/src/discovery/helpers.ts +52 -1
- package/src/main.ts +1 -1
- package/src/migrations.ts +20 -20
- package/src/modes/interactive/controllers/command-controller.ts +527 -0
- package/src/modes/interactive/controllers/event-controller.ts +340 -0
- package/src/modes/interactive/controllers/extension-ui-controller.ts +600 -0
- package/src/modes/interactive/controllers/input-controller.ts +585 -0
- package/src/modes/interactive/controllers/selector-controller.ts +585 -0
- package/src/modes/interactive/interactive-mode.ts +363 -3139
- package/src/modes/interactive/theme/theme.ts +5 -5
- package/src/modes/interactive/types.ts +189 -0
- package/src/modes/interactive/utils/ui-helpers.ts +449 -0
- package/src/modes/interactive/utils/voice-manager.ts +96 -0
- package/src/prompts/{explore.md → agents/explore.md} +7 -5
- package/src/prompts/agents/frontmatter.md +7 -0
- package/src/prompts/{plan.md → agents/plan.md} +3 -3
- package/src/prompts/agents/planner.md +112 -0
- package/src/prompts/agents/task.md +15 -0
- package/src/prompts/review-request.md +44 -8
- package/src/prompts/system/custom-system-prompt.md +80 -0
- package/src/prompts/system/file-operations.md +12 -0
- package/src/prompts/system/system-prompt.md +237 -0
- package/src/prompts/system/title-system.md +2 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/task.md +34 -22
- package/src/core/tools/rulebook.ts +0 -132
- package/src/prompts/architect-plan.md +0 -10
- package/src/prompts/implement-with-critic.md +0 -11
- package/src/prompts/implement.md +0 -11
- package/src/prompts/system-prompt.md +0 -43
- package/src/prompts/task.md +0 -14
- package/src/prompts/title-system.md +0 -8
- /package/src/prompts/{init.md → agents/init.md} +0 -0
- /package/src/prompts/{reviewer.md → agents/reviewer.md} +0 -0
- /package/src/prompts/{branch-summary-preamble.md → compaction/branch-summary-preamble.md} +0 -0
- /package/src/prompts/{branch-summary.md → compaction/branch-summary.md} +0 -0
- /package/src/prompts/{compaction-summary.md → compaction/compaction-summary.md} +0 -0
- /package/src/prompts/{compaction-turn-prefix.md → compaction/compaction-turn-prefix.md} +0 -0
- /package/src/prompts/{compaction-update-summary.md → compaction/compaction-update-summary.md} +0 -0
- /package/src/prompts/{summarization-system.md → system/summarization-system.md} +0 -0
|
@@ -22,6 +22,12 @@ const DEFAULT_MAX_TOKENS = 4096;
|
|
|
22
22
|
const WEB_SEARCH_TOOL_NAME = "web_search";
|
|
23
23
|
const WEB_SEARCH_TOOL_TYPE = "web_search_20250305";
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Applies OAuth-specific tool prefix to search tool name.
|
|
27
|
+
* @param name - The base tool name
|
|
28
|
+
* @param isOAuth - Whether OAuth authentication is being used
|
|
29
|
+
* @returns Tool name with prefix if OAuth, otherwise unchanged
|
|
30
|
+
*/
|
|
25
31
|
const applySearchToolPrefix = (name: string, isOAuth: boolean): string => {
|
|
26
32
|
return isOAuth ? applyClaudeToolPrefix(name) : name;
|
|
27
33
|
};
|
|
@@ -33,11 +39,21 @@ export interface AnthropicSearchParams {
|
|
|
33
39
|
num_results?: number;
|
|
34
40
|
}
|
|
35
41
|
|
|
36
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Gets the model to use for web search from environment or default.
|
|
44
|
+
* @returns Model identifier string
|
|
45
|
+
*/
|
|
37
46
|
async function getModel(): Promise<string> {
|
|
38
47
|
return (await getEnv("ANTHROPIC_SEARCH_MODEL")) ?? DEFAULT_MODEL;
|
|
39
48
|
}
|
|
40
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Builds system instruction blocks for the Anthropic API request.
|
|
52
|
+
* @param auth - Authentication configuration
|
|
53
|
+
* @param model - Model identifier (affects whether Claude Code instruction is included)
|
|
54
|
+
* @param systemPrompt - Optional custom system prompt
|
|
55
|
+
* @returns Array of system blocks for the API request
|
|
56
|
+
*/
|
|
41
57
|
function buildSystemBlocks(
|
|
42
58
|
auth: AnthropicAuthConfig,
|
|
43
59
|
model: string,
|
|
@@ -53,7 +69,16 @@ function buildSystemBlocks(
|
|
|
53
69
|
});
|
|
54
70
|
}
|
|
55
71
|
|
|
56
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Calls the Anthropic API with web search tool enabled.
|
|
74
|
+
* @param auth - Authentication configuration (API key or OAuth)
|
|
75
|
+
* @param model - Model identifier to use
|
|
76
|
+
* @param query - Search query from the user
|
|
77
|
+
* @param systemPrompt - Optional custom system prompt
|
|
78
|
+
* @param maxTokens - Maximum tokens for the response
|
|
79
|
+
* @returns Raw API response from Anthropic
|
|
80
|
+
* @throws {WebSearchProviderError} If the API request fails
|
|
81
|
+
*/
|
|
57
82
|
async function callWebSearch(
|
|
58
83
|
auth: AnthropicAuthConfig,
|
|
59
84
|
model: string,
|
|
@@ -100,7 +125,11 @@ async function callWebSearch(
|
|
|
100
125
|
return response.json() as Promise<AnthropicApiResponse>;
|
|
101
126
|
}
|
|
102
127
|
|
|
103
|
-
/**
|
|
128
|
+
/**
|
|
129
|
+
* Parses a human-readable page age string into seconds.
|
|
130
|
+
* @param pageAge - Age string like "2 days ago", "3h ago", "1 week ago"
|
|
131
|
+
* @returns Age in seconds, or undefined if parsing fails
|
|
132
|
+
*/
|
|
104
133
|
function parsePageAge(pageAge: string | null | undefined): number | undefined {
|
|
105
134
|
if (!pageAge) return undefined;
|
|
106
135
|
|
|
@@ -132,7 +161,11 @@ function parsePageAge(pageAge: string | null | undefined): number | undefined {
|
|
|
132
161
|
return value * (multipliers[unit] ?? 86400);
|
|
133
162
|
}
|
|
134
163
|
|
|
135
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* Parses the Anthropic API response into a unified WebSearchResponse.
|
|
166
|
+
* @param response - Raw API response containing content blocks
|
|
167
|
+
* @returns Normalized response with answer, sources, citations, and usage
|
|
168
|
+
*/
|
|
136
169
|
function parseResponse(response: AnthropicApiResponse): WebSearchResponse {
|
|
137
170
|
const answerParts: string[] = [];
|
|
138
171
|
const searchQueries: string[] = [];
|
|
@@ -193,12 +226,17 @@ function parseResponse(response: AnthropicApiResponse): WebSearchResponse {
|
|
|
193
226
|
};
|
|
194
227
|
}
|
|
195
228
|
|
|
196
|
-
/**
|
|
229
|
+
/**
|
|
230
|
+
* Executes a web search using Anthropic's Claude with built-in web search tool.
|
|
231
|
+
* @param params - Search parameters including query and optional settings
|
|
232
|
+
* @returns Search response with synthesized answer, sources, and citations
|
|
233
|
+
* @throws {Error} If no Anthropic credentials are configured
|
|
234
|
+
*/
|
|
197
235
|
export async function searchAnthropic(params: AnthropicSearchParams): Promise<WebSearchResponse> {
|
|
198
236
|
const auth = await findAnthropicAuth();
|
|
199
237
|
if (!auth) {
|
|
200
238
|
throw new Error(
|
|
201
|
-
"No Anthropic credentials found. Set ANTHROPIC_API_KEY or configure OAuth in ~/.omp/agent/
|
|
239
|
+
"No Anthropic credentials found. Set ANTHROPIC_API_KEY or configure OAuth in ~/.omp/agent/agent.db",
|
|
202
240
|
);
|
|
203
241
|
}
|
|
204
242
|
|
package/src/core/tools/write.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { Type } from "@sinclair/typebox";
|
|
|
5
5
|
import { getLanguageFromPath, highlightCode, type Theme } from "../../modes/interactive/theme/theme";
|
|
6
6
|
import writeDescription from "../../prompts/tools/write.md" with { type: "text" };
|
|
7
7
|
import type { RenderResultOptions } from "../custom-tools/types";
|
|
8
|
+
import { renderPromptTemplate } from "../prompt-templates";
|
|
8
9
|
import type { ToolSession } from "../sdk";
|
|
9
10
|
import { untilAborted } from "../utils";
|
|
10
11
|
import { createLspWritethrough, type FileDiagnosticsResult } from "./lsp/index";
|
|
@@ -28,7 +29,7 @@ export function createWriteTool(session: ToolSession): AgentTool<typeof writeSch
|
|
|
28
29
|
return {
|
|
29
30
|
name: "write",
|
|
30
31
|
label: "Write",
|
|
31
|
-
description: writeDescription,
|
|
32
|
+
description: renderPromptTemplate(writeDescription),
|
|
32
33
|
parameters: writeSchema,
|
|
33
34
|
execute: async (
|
|
34
35
|
_toolCallId: string,
|
package/src/core/voice.ts
CHANGED
|
@@ -7,12 +7,14 @@ import voiceSummaryPrompt from "../prompts/voice-summary.md" with { type: "text"
|
|
|
7
7
|
import { logger } from "./logger";
|
|
8
8
|
import type { ModelRegistry } from "./model-registry";
|
|
9
9
|
import { findSmolModel } from "./model-resolver";
|
|
10
|
+
import { renderPromptTemplate } from "./prompt-templates";
|
|
10
11
|
import type { VoiceSettings } from "./settings-manager";
|
|
11
12
|
|
|
12
13
|
const DEFAULT_SAMPLE_RATE = 16000;
|
|
13
14
|
const DEFAULT_CHANNELS = 1;
|
|
14
15
|
const DEFAULT_BITS = 16;
|
|
15
16
|
const SUMMARY_MAX_CHARS = 6000;
|
|
17
|
+
const VOICE_SUMMARY_PROMPT = renderPromptTemplate(voiceSummaryPrompt);
|
|
16
18
|
|
|
17
19
|
export interface VoiceRecordingHandle {
|
|
18
20
|
filePath: string;
|
|
@@ -286,7 +288,7 @@ export async function summarizeForVoice(
|
|
|
286
288
|
const truncated = text.length > SUMMARY_MAX_CHARS ? `${text.slice(0, SUMMARY_MAX_CHARS)}...` : text;
|
|
287
289
|
const request = {
|
|
288
290
|
model: `${model.provider}/${model.id}`,
|
|
289
|
-
systemPrompt:
|
|
291
|
+
systemPrompt: VOICE_SUMMARY_PROMPT,
|
|
290
292
|
userMessage: `<assistant_response>\n${truncated}\n</assistant_response>`,
|
|
291
293
|
};
|
|
292
294
|
logger.debug("voice: summary request", request);
|
package/src/discovery/builtin.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* .pi is an alias for backwards compatibility.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { dirname, isAbsolute, join, resolve } from "path";
|
|
9
9
|
import { type ContextFile, contextFileCapability } from "../capability/context-file";
|
|
10
10
|
import { type Extension, type ExtensionManifest, extensionCapability } from "../capability/extension";
|
|
11
11
|
import { type ExtensionModule, extensionModuleCapability } from "../capability/extension-module";
|
|
@@ -16,7 +16,7 @@ import { type MCPServer, mcpCapability } from "../capability/mcp";
|
|
|
16
16
|
import { type Prompt, promptCapability } from "../capability/prompt";
|
|
17
17
|
import { type Rule, ruleCapability } from "../capability/rule";
|
|
18
18
|
import { type Settings, settingsCapability } from "../capability/settings";
|
|
19
|
-
import { type Skill,
|
|
19
|
+
import { type Skill, skillCapability } from "../capability/skill";
|
|
20
20
|
import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
|
|
21
21
|
import { type SystemPrompt, systemPromptCapability } from "../capability/system-prompt";
|
|
22
22
|
import { type CustomTool, toolCapability } from "../capability/tool";
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
expandEnvVarsDeep,
|
|
28
28
|
getExtensionNameFromPath,
|
|
29
29
|
loadFilesFromDir,
|
|
30
|
+
loadSkillsFromDir,
|
|
30
31
|
parseFrontmatter,
|
|
31
32
|
parseJSON,
|
|
32
33
|
SOURCE_PATHS,
|
|
@@ -190,64 +191,18 @@ registerProvider<SystemPrompt>(systemPromptCapability.id, {
|
|
|
190
191
|
});
|
|
191
192
|
|
|
192
193
|
// Skills
|
|
193
|
-
function loadSkillFromFile(ctx: LoadContext, path: string, level: "user" | "project"): Skill | null {
|
|
194
|
-
const content = ctx.fs.readFile(path);
|
|
195
|
-
if (!content) return null;
|
|
196
|
-
|
|
197
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
198
|
-
const skillDir = dirname(path);
|
|
199
|
-
const parentDirName = basename(skillDir);
|
|
200
|
-
const name = (frontmatter.name as string) || parentDirName;
|
|
201
|
-
|
|
202
|
-
if (!frontmatter.description) return null;
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
name,
|
|
206
|
-
path,
|
|
207
|
-
content: body,
|
|
208
|
-
frontmatter: frontmatter as SkillFrontmatter,
|
|
209
|
-
level,
|
|
210
|
-
_source: createSourceMeta(PROVIDER_ID, path, level),
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function loadSkillsRecursive(ctx: LoadContext, dir: string, level: "user" | "project"): LoadResult<Skill> {
|
|
215
|
-
const items: Skill[] = [];
|
|
216
|
-
const warnings: string[] = [];
|
|
217
|
-
|
|
218
|
-
if (!ctx.fs.isDir(dir)) return { items, warnings };
|
|
219
|
-
|
|
220
|
-
for (const name of ctx.fs.readDir(dir)) {
|
|
221
|
-
if (name.startsWith(".") || name === "node_modules") continue;
|
|
222
|
-
|
|
223
|
-
const path = join(dir, name);
|
|
224
|
-
|
|
225
|
-
if (ctx.fs.isDir(path)) {
|
|
226
|
-
const skillFile = join(path, "SKILL.md");
|
|
227
|
-
if (ctx.fs.isFile(skillFile)) {
|
|
228
|
-
const skill = loadSkillFromFile(ctx, skillFile, level);
|
|
229
|
-
if (skill) items.push(skill);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const sub = loadSkillsRecursive(ctx, path, level);
|
|
233
|
-
items.push(...sub.items);
|
|
234
|
-
if (sub.warnings) warnings.push(...sub.warnings);
|
|
235
|
-
} else if (name === "SKILL.md") {
|
|
236
|
-
const skill = loadSkillFromFile(ctx, path, level);
|
|
237
|
-
if (skill) items.push(skill);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return { items, warnings };
|
|
242
|
-
}
|
|
243
|
-
|
|
244
194
|
function loadSkills(ctx: LoadContext): LoadResult<Skill> {
|
|
245
195
|
const items: Skill[] = [];
|
|
246
196
|
const warnings: string[] = [];
|
|
247
197
|
|
|
248
198
|
for (const { dir, level } of getConfigDirs(ctx)) {
|
|
249
199
|
const skillsDir = join(dir, "skills");
|
|
250
|
-
const result =
|
|
200
|
+
const result = loadSkillsFromDir(ctx, {
|
|
201
|
+
dir: skillsDir,
|
|
202
|
+
providerId: PROVIDER_ID,
|
|
203
|
+
level,
|
|
204
|
+
requireDescription: true,
|
|
205
|
+
});
|
|
251
206
|
items.push(...result.items);
|
|
252
207
|
if (result.warnings) warnings.push(...result.warnings);
|
|
253
208
|
}
|
package/src/discovery/claude.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
expandEnvVarsDeep,
|
|
25
25
|
getExtensionNameFromPath,
|
|
26
26
|
loadFilesFromDir,
|
|
27
|
-
|
|
27
|
+
loadSkillsFromDir,
|
|
28
28
|
parseJSON,
|
|
29
29
|
} from "./helpers";
|
|
30
30
|
|
|
@@ -218,78 +218,25 @@ function loadSkills(ctx: LoadContext): LoadResult<Skill> {
|
|
|
218
218
|
const items: Skill[] = [];
|
|
219
219
|
const warnings: string[] = [];
|
|
220
220
|
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (dirName.startsWith(".")) continue;
|
|
230
|
-
|
|
231
|
-
const skillDir = join(userSkillsDir, dirName);
|
|
232
|
-
if (!ctx.fs.isDir(skillDir)) continue;
|
|
233
|
-
|
|
234
|
-
const skillFile = join(skillDir, "SKILL.md");
|
|
235
|
-
if (!ctx.fs.isFile(skillFile)) continue;
|
|
236
|
-
|
|
237
|
-
const content = ctx.fs.readFile(skillFile);
|
|
238
|
-
if (!content) {
|
|
239
|
-
warnings.push(`Failed to read ${skillFile}`);
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
244
|
-
const name = (frontmatter.name as string) || dirName;
|
|
245
|
-
|
|
246
|
-
items.push({
|
|
247
|
-
name,
|
|
248
|
-
path: skillFile,
|
|
249
|
-
content: body,
|
|
250
|
-
frontmatter,
|
|
251
|
-
level: "user",
|
|
252
|
-
_source: createSourceMeta(PROVIDER_ID, skillFile, "user"),
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
}
|
|
221
|
+
const userSkillsDir = join(getUserClaude(ctx), "skills");
|
|
222
|
+
const userResult = loadSkillsFromDir(ctx, {
|
|
223
|
+
dir: userSkillsDir,
|
|
224
|
+
providerId: PROVIDER_ID,
|
|
225
|
+
level: "user",
|
|
226
|
+
});
|
|
227
|
+
items.push(...userResult.items);
|
|
228
|
+
if (userResult.warnings) warnings.push(...userResult.warnings);
|
|
256
229
|
|
|
257
|
-
// Project-level: <project>/.claude/skills/*/SKILL.md
|
|
258
230
|
const projectBase = getProjectClaude(ctx);
|
|
259
231
|
if (projectBase) {
|
|
260
232
|
const projectSkillsDir = join(projectBase, "skills");
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const skillDir = join(projectSkillsDir, dirName);
|
|
269
|
-
if (!ctx.fs.isDir(skillDir)) continue;
|
|
270
|
-
|
|
271
|
-
const skillFile = join(skillDir, "SKILL.md");
|
|
272
|
-
if (!ctx.fs.isFile(skillFile)) continue;
|
|
273
|
-
|
|
274
|
-
const content = ctx.fs.readFile(skillFile);
|
|
275
|
-
if (!content) {
|
|
276
|
-
warnings.push(`Failed to read ${skillFile}`);
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
281
|
-
const name = (frontmatter.name as string) || dirName;
|
|
282
|
-
|
|
283
|
-
items.push({
|
|
284
|
-
name,
|
|
285
|
-
path: skillFile,
|
|
286
|
-
content: body,
|
|
287
|
-
frontmatter,
|
|
288
|
-
level: "project",
|
|
289
|
-
_source: createSourceMeta(PROVIDER_ID, skillFile, "project"),
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
}
|
|
233
|
+
const projectResult = loadSkillsFromDir(ctx, {
|
|
234
|
+
dir: projectSkillsDir,
|
|
235
|
+
providerId: PROVIDER_ID,
|
|
236
|
+
level: "project",
|
|
237
|
+
});
|
|
238
|
+
items.push(...projectResult.items);
|
|
239
|
+
if (projectResult.warnings) warnings.push(...projectResult.warnings);
|
|
293
240
|
}
|
|
294
241
|
|
|
295
242
|
return { items, warnings };
|
package/src/discovery/codex.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
discoverExtensionModulePaths,
|
|
34
34
|
getExtensionNameFromPath,
|
|
35
35
|
loadFilesFromDir,
|
|
36
|
+
loadSkillsFromDir,
|
|
36
37
|
parseFrontmatter,
|
|
37
38
|
SOURCE_PATHS,
|
|
38
39
|
} from "./helpers";
|
|
@@ -209,51 +210,25 @@ function loadSkills(ctx: LoadContext): LoadResult<Skill> {
|
|
|
209
210
|
const items: Skill[] = [];
|
|
210
211
|
const warnings: string[] = [];
|
|
211
212
|
|
|
212
|
-
// User level: ~/.codex/skills/
|
|
213
213
|
const userSkillsDir = join(ctx.home, SOURCE_PATHS.codex.userBase, "skills");
|
|
214
|
-
const userResult =
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
219
|
-
const skillName = frontmatter.name || name.replace(/\.md$/, "");
|
|
220
|
-
|
|
221
|
-
return {
|
|
222
|
-
name: String(skillName),
|
|
223
|
-
path,
|
|
224
|
-
content: body,
|
|
225
|
-
frontmatter,
|
|
226
|
-
level: "user" as const,
|
|
227
|
-
_source: source,
|
|
228
|
-
};
|
|
229
|
-
},
|
|
214
|
+
const userResult = loadSkillsFromDir(ctx, {
|
|
215
|
+
dir: userSkillsDir,
|
|
216
|
+
providerId: PROVIDER_ID,
|
|
217
|
+
level: "user",
|
|
230
218
|
});
|
|
231
219
|
items.push(...userResult.items);
|
|
232
|
-
warnings.push(...
|
|
220
|
+
if (userResult.warnings) warnings.push(...userResult.warnings);
|
|
233
221
|
|
|
234
|
-
// Project level: .codex/skills/
|
|
235
222
|
const codexDir = ctx.fs.walkUp(".codex", { dir: true });
|
|
236
223
|
if (codexDir) {
|
|
237
224
|
const projectSkillsDir = join(codexDir, "skills");
|
|
238
|
-
const projectResult =
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const { frontmatter, body } = parseFrontmatter(content);
|
|
243
|
-
const skillName = frontmatter.name || name.replace(/\.md$/, "");
|
|
244
|
-
|
|
245
|
-
return {
|
|
246
|
-
name: String(skillName),
|
|
247
|
-
path,
|
|
248
|
-
content: body,
|
|
249
|
-
frontmatter,
|
|
250
|
-
level: "project" as const,
|
|
251
|
-
_source: source,
|
|
252
|
-
};
|
|
253
|
-
},
|
|
225
|
+
const projectResult = loadSkillsFromDir(ctx, {
|
|
226
|
+
dir: projectSkillsDir,
|
|
227
|
+
providerId: PROVIDER_ID,
|
|
228
|
+
level: "project",
|
|
254
229
|
});
|
|
255
230
|
items.push(...projectResult.items);
|
|
256
|
-
warnings.push(...
|
|
231
|
+
if (projectResult.warnings) warnings.push(...projectResult.warnings);
|
|
257
232
|
}
|
|
258
233
|
|
|
259
234
|
return { items, warnings };
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { join, resolve } from "path";
|
|
6
6
|
import { parse as parseYAML } from "yaml";
|
|
7
|
+
import type { Skill, SkillFrontmatter } from "../capability/skill";
|
|
7
8
|
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -126,6 +127,56 @@ export function parseFrontmatter(content: string): {
|
|
|
126
127
|
}
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
export function loadSkillsFromDir(
|
|
131
|
+
ctx: LoadContext,
|
|
132
|
+
options: {
|
|
133
|
+
dir: string;
|
|
134
|
+
providerId: string;
|
|
135
|
+
level: "user" | "project";
|
|
136
|
+
requireDescription?: boolean;
|
|
137
|
+
},
|
|
138
|
+
): LoadResult<Skill> {
|
|
139
|
+
const items: Skill[] = [];
|
|
140
|
+
const warnings: string[] = [];
|
|
141
|
+
const { dir, level, providerId, requireDescription = false } = options;
|
|
142
|
+
|
|
143
|
+
if (!ctx.fs.isDir(dir)) {
|
|
144
|
+
return { items, warnings };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const name of ctx.fs.readDir(dir)) {
|
|
148
|
+
if (name.startsWith(".") || name === "node_modules") continue;
|
|
149
|
+
|
|
150
|
+
const skillDir = join(dir, name);
|
|
151
|
+
if (!ctx.fs.isDir(skillDir)) continue;
|
|
152
|
+
|
|
153
|
+
const skillFile = join(skillDir, "SKILL.md");
|
|
154
|
+
if (!ctx.fs.isFile(skillFile)) continue;
|
|
155
|
+
|
|
156
|
+
const content = ctx.fs.readFile(skillFile);
|
|
157
|
+
if (!content) {
|
|
158
|
+
warnings.push(`Failed to read ${skillFile}`);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const { frontmatter, body } = parseFrontmatter(content);
|
|
163
|
+
if (requireDescription && !frontmatter.description) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
items.push({
|
|
168
|
+
name: (frontmatter.name as string) || name,
|
|
169
|
+
path: skillFile,
|
|
170
|
+
content: body,
|
|
171
|
+
frontmatter: frontmatter as SkillFrontmatter,
|
|
172
|
+
level,
|
|
173
|
+
_source: createSourceMeta(providerId, skillFile, level),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return { items, warnings };
|
|
178
|
+
}
|
|
179
|
+
|
|
129
180
|
/**
|
|
130
181
|
* Expand environment variables in a string.
|
|
131
182
|
* Supports ${VAR} and ${VAR:-default} syntax.
|
|
@@ -286,7 +337,7 @@ export function discoverExtensionModulePaths(ctx: LoadContext, dir: string): str
|
|
|
286
337
|
const discovered: string[] = [];
|
|
287
338
|
|
|
288
339
|
for (const name of ctx.fs.readDir(dir)) {
|
|
289
|
-
if (name.startsWith(".")) continue;
|
|
340
|
+
if (name.startsWith(".") || name === "node_modules") continue;
|
|
290
341
|
|
|
291
342
|
const entryPath = join(dir, name);
|
|
292
343
|
|
package/src/main.ts
CHANGED
|
@@ -76,7 +76,7 @@ async function runInteractiveMode(
|
|
|
76
76
|
mode.renderInitialMessages();
|
|
77
77
|
|
|
78
78
|
if (migratedProviders.length > 0) {
|
|
79
|
-
mode.showWarning(`Migrated credentials to
|
|
79
|
+
mode.showWarning(`Migrated credentials to agent.db: ${migratedProviders.join(", ")}`);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
if (modelsJsonError) {
|
package/src/migrations.ts
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
-
import {
|
|
6
|
+
import { join } from "node:path";
|
|
7
7
|
import chalk from "chalk";
|
|
8
|
-
import { getAgentDir, getBinDir } from "./config";
|
|
8
|
+
import { getAgentDbPath, getAgentDir, getBinDir } from "./config";
|
|
9
|
+
import { AgentStorage } from "./core/agent-storage";
|
|
10
|
+
import type { AuthCredential } from "./core/auth-storage";
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* Migrate PI_* environment variables to OMP_* equivalents.
|
|
@@ -29,28 +31,27 @@ export function migrateEnvVars(): string[] {
|
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
|
-
* Migrate legacy oauth.json and settings.json apiKeys to
|
|
34
|
+
* Migrate legacy oauth.json and settings.json apiKeys to agent.db.
|
|
33
35
|
*
|
|
34
36
|
* @returns Array of provider names that were migrated
|
|
35
37
|
*/
|
|
36
|
-
export function
|
|
38
|
+
export function migrateAuthToAgentDb(): string[] {
|
|
37
39
|
const agentDir = getAgentDir();
|
|
38
|
-
const authPath = join(agentDir, "auth.json");
|
|
39
40
|
const oauthPath = join(agentDir, "oauth.json");
|
|
40
41
|
const settingsPath = join(agentDir, "settings.json");
|
|
42
|
+
const storage = AgentStorage.open(getAgentDbPath(agentDir));
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
if (existsSync(authPath)) return [];
|
|
44
|
-
|
|
45
|
-
const migrated: Record<string, unknown> = {};
|
|
44
|
+
const migrated: Record<string, AuthCredential[]> = {};
|
|
46
45
|
const providers: string[] = [];
|
|
47
46
|
|
|
48
|
-
// Migrate oauth.json
|
|
49
47
|
if (existsSync(oauthPath)) {
|
|
50
48
|
try {
|
|
51
49
|
const oauth = JSON.parse(readFileSync(oauthPath, "utf-8"));
|
|
52
50
|
for (const [provider, cred] of Object.entries(oauth)) {
|
|
53
|
-
|
|
51
|
+
if (storage.listAuthCredentials(provider).length > 0) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
migrated[provider] = [{ type: "oauth", ...(cred as object) } as AuthCredential];
|
|
54
55
|
providers.push(provider);
|
|
55
56
|
}
|
|
56
57
|
renameSync(oauthPath, `${oauthPath}.migrated`);
|
|
@@ -59,17 +60,17 @@ export function migrateAuthToAuthJson(): string[] {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
// Migrate settings.json apiKeys
|
|
63
63
|
if (existsSync(settingsPath)) {
|
|
64
64
|
try {
|
|
65
65
|
const content = readFileSync(settingsPath, "utf-8");
|
|
66
66
|
const settings = JSON.parse(content);
|
|
67
67
|
if (settings.apiKeys && typeof settings.apiKeys === "object") {
|
|
68
68
|
for (const [provider, key] of Object.entries(settings.apiKeys)) {
|
|
69
|
-
if (
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
69
|
+
if (typeof key !== "string") continue;
|
|
70
|
+
if (migrated[provider]) continue;
|
|
71
|
+
if (storage.listAuthCredentials(provider).length > 0) continue;
|
|
72
|
+
migrated[provider] = [{ type: "api_key", key }];
|
|
73
|
+
providers.push(provider);
|
|
73
74
|
}
|
|
74
75
|
delete settings.apiKeys;
|
|
75
76
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
@@ -79,9 +80,8 @@ export function migrateAuthToAuthJson(): string[] {
|
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
writeFileSync(authPath, JSON.stringify(migrated, null, 2), { mode: 0o600 });
|
|
83
|
+
for (const [provider, credentials] of Object.entries(migrated)) {
|
|
84
|
+
storage.replaceAuthCredentialsForProvider(provider, credentials);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
return providers;
|
|
@@ -201,7 +201,7 @@ export async function runMigrations(_cwd: string): Promise<{
|
|
|
201
201
|
const migratedEnvVars = migrateEnvVars();
|
|
202
202
|
|
|
203
203
|
// Then: run data migrations
|
|
204
|
-
const migratedAuthProviders =
|
|
204
|
+
const migratedAuthProviders = migrateAuthToAgentDb();
|
|
205
205
|
migrateSessionsFromAgentRoot();
|
|
206
206
|
migrateToolsToBin();
|
|
207
207
|
|