@oh-my-pi/pi-coding-agent 13.7.6 → 13.9.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/package.json +7 -7
  3. package/scripts/generate-docs-index.ts +3 -3
  4. package/src/capability/context-file.ts +6 -3
  5. package/src/capability/fs.ts +18 -0
  6. package/src/capability/index.ts +3 -2
  7. package/src/capability/rule.ts +0 -4
  8. package/src/capability/types.ts +2 -0
  9. package/src/cli/agents-cli.ts +1 -1
  10. package/src/cli/args.ts +7 -12
  11. package/src/commands/launch.ts +3 -2
  12. package/src/config/model-resolver.ts +118 -33
  13. package/src/config/settings-schema.ts +14 -2
  14. package/src/config/settings.ts +1 -17
  15. package/src/discovery/agents-md.ts +3 -4
  16. package/src/discovery/agents.ts +104 -84
  17. package/src/discovery/builtin.ts +28 -15
  18. package/src/discovery/claude.ts +27 -9
  19. package/src/discovery/helpers.ts +10 -17
  20. package/src/extensibility/extensions/loader.ts +1 -2
  21. package/src/extensibility/extensions/types.ts +2 -1
  22. package/src/extensibility/skills.ts +2 -2
  23. package/src/internal-urls/docs-index.generated.ts +1 -1
  24. package/src/main.ts +21 -10
  25. package/src/modes/components/agent-dashboard.ts +12 -13
  26. package/src/modes/components/model-selector.ts +157 -59
  27. package/src/modes/components/read-tool-group.ts +36 -2
  28. package/src/modes/components/settings-defs.ts +11 -8
  29. package/src/modes/components/settings-selector.ts +1 -1
  30. package/src/modes/components/thinking-selector.ts +3 -15
  31. package/src/modes/controllers/selector-controller.ts +6 -4
  32. package/src/modes/rpc/rpc-client.ts +2 -2
  33. package/src/modes/rpc/rpc-types.ts +2 -2
  34. package/src/modes/theme/theme.ts +2 -1
  35. package/src/patch/hashline.ts +113 -0
  36. package/src/patch/index.ts +27 -18
  37. package/src/prompts/tools/hashline.md +9 -10
  38. package/src/prompts/tools/read.md +2 -2
  39. package/src/sdk.ts +21 -25
  40. package/src/session/agent-session.ts +54 -59
  41. package/src/task/executor.ts +10 -8
  42. package/src/task/types.ts +1 -2
  43. package/src/tools/fetch.ts +152 -4
  44. package/src/tools/read.ts +88 -264
  45. package/src/utils/frontmatter.ts +25 -4
  46. package/src/web/scrapers/choosealicense.ts +1 -1
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * Agents (standard) Provider
3
3
  *
4
- * Loads user-level skills, rules, prompts, commands, context files, and system prompts from ~/.agent/.
4
+ * Loads skills, rules, prompts, commands, context files, and system prompts
5
+ * from .agent/ and .agents/ directories at both user (~/) and project levels.
6
+ * Project-level discovery walks up from cwd to repoRoot.
5
7
  */
6
8
  import * as path from "node:path";
7
9
  import { registerProvider } from "../capability";
@@ -13,76 +15,97 @@ import { type Skill, skillCapability } from "../capability/skill";
13
15
  import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
14
16
  import { type SystemPrompt, systemPromptCapability } from "../capability/system-prompt";
15
17
  import type { LoadContext, LoadResult } from "../capability/types";
16
- import { buildRuleFromMarkdown, createSourceMeta, loadFilesFromDir, scanSkillsFromDir } from "./helpers";
18
+ import {
19
+ buildRuleFromMarkdown,
20
+ calculateDepth,
21
+ createSourceMeta,
22
+ loadFilesFromDir,
23
+ scanSkillsFromDir,
24
+ } from "./helpers";
17
25
 
18
26
  const PROVIDER_ID = "agents";
19
27
  const DISPLAY_NAME = "Agents (standard)";
20
28
  const PRIORITY = 70;
21
- const USER_AGENT_DIR_CANDIDATES = [".agent", ".agents"] as const;
29
+ const AGENT_DIR_CANDIDATES = [".agent", ".agents"] as const;
22
30
 
23
- function getUserAgentPathCandidates(ctx: LoadContext, ...segments: string[]): string[] {
24
- return USER_AGENT_DIR_CANDIDATES.map(baseDir => path.join(ctx.home, baseDir, ...segments));
31
+ /** User-level paths: ~/.agent/<segments> and ~/.agents/<segments>. */
32
+ function getUserPathCandidates(ctx: LoadContext, ...segments: string[]): string[] {
33
+ return AGENT_DIR_CANDIDATES.map(baseDir => path.join(ctx.home, baseDir, ...segments));
25
34
  }
26
35
 
27
- async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
28
- const items: Skill[] = [];
29
- const warnings: string[] = [];
30
- for (const userSkillsDir of getUserAgentPathCandidates(ctx, "skills")) {
31
- const result = await scanSkillsFromDir(ctx, {
32
- dir: userSkillsDir,
33
- providerId: PROVIDER_ID,
34
- level: "user",
35
- });
36
- items.push(...result.items);
37
- warnings.push(...(result.warnings ?? []));
36
+ /** Project-level paths: walk up from cwd to repoRoot, returning .agent/<segments> and .agents/<segments> at each level. */
37
+ function getProjectPathCandidates(ctx: LoadContext, ...segments: string[]): string[] {
38
+ const paths: string[] = [];
39
+ let current = ctx.cwd;
40
+ while (true) {
41
+ for (const baseDir of AGENT_DIR_CANDIDATES) {
42
+ paths.push(path.join(current, baseDir, ...segments));
43
+ }
44
+ if (current === (ctx.repoRoot ?? ctx.home)) break;
45
+ const parent = path.dirname(current);
46
+ if (parent === current) break;
47
+ current = parent;
38
48
  }
49
+ return paths;
50
+ }
51
+
52
+ // Skills
53
+ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
54
+ const projectScans = getProjectPathCandidates(ctx, "skills").map(dir =>
55
+ scanSkillsFromDir(ctx, { dir, providerId: PROVIDER_ID, level: "project" }),
56
+ );
57
+ const userScans = getUserPathCandidates(ctx, "skills").map(dir =>
58
+ scanSkillsFromDir(ctx, { dir, providerId: PROVIDER_ID, level: "user" }),
59
+ );
60
+
61
+ const results = await Promise.all([...projectScans, ...userScans]);
62
+
39
63
  return {
40
- items,
41
- warnings,
64
+ items: results.flatMap(r => r.items),
65
+ warnings: results.flatMap(r => r.warnings ?? []),
42
66
  };
43
67
  }
44
68
 
45
69
  registerProvider<Skill>(skillCapability.id, {
46
70
  id: PROVIDER_ID,
47
71
  displayName: DISPLAY_NAME,
48
- description: "Load skills from ~/.agent/skills (fallback ~/.agents/skills)",
72
+ description: "Load skills from .agent/skills and .agents/skills (project walk-up + user home)",
49
73
  priority: PRIORITY,
50
74
  load: loadSkills,
51
75
  });
52
76
 
53
77
  // Rules
54
78
  async function loadRules(ctx: LoadContext): Promise<LoadResult<Rule>> {
55
- const items: Rule[] = [];
56
- const warnings: string[] = [];
57
- for (const userRulesDir of getUserAgentPathCandidates(ctx, "rules")) {
58
- const result = await loadFilesFromDir<Rule>(ctx, userRulesDir, PROVIDER_ID, "user", {
79
+ const load = (dir: string, level: "user" | "project") =>
80
+ loadFilesFromDir<Rule>(ctx, dir, PROVIDER_ID, level, {
59
81
  extensions: ["md", "mdc"],
60
82
  transform: (name, content, filePath, source) =>
61
83
  buildRuleFromMarkdown(name, content, filePath, source, { stripNamePattern: /\.(md|mdc)$/ }),
62
84
  });
63
- items.push(...result.items);
64
- warnings.push(...(result.warnings ?? []));
65
- }
85
+
86
+ const results = await Promise.all([
87
+ ...getProjectPathCandidates(ctx, "rules").map(dir => load(dir, "project")),
88
+ ...getUserPathCandidates(ctx, "rules").map(dir => load(dir, "user")),
89
+ ]);
90
+
66
91
  return {
67
- items,
68
- warnings,
92
+ items: results.flatMap(r => r.items),
93
+ warnings: results.flatMap(r => r.warnings ?? []),
69
94
  };
70
95
  }
71
96
 
72
97
  registerProvider<Rule>(ruleCapability.id, {
73
98
  id: PROVIDER_ID,
74
99
  displayName: DISPLAY_NAME,
75
- description: "Load rules from ~/.agent/rules (fallback ~/.agents/rules)",
100
+ description: "Load rules from .agent/rules and .agents/rules (project walk-up + user home)",
76
101
  priority: PRIORITY,
77
102
  load: loadRules,
78
103
  });
79
104
 
80
105
  // Prompts
81
106
  async function loadPrompts(ctx: LoadContext): Promise<LoadResult<Prompt>> {
82
- const items: Prompt[] = [];
83
- const warnings: string[] = [];
84
- for (const userPromptsDir of getUserAgentPathCandidates(ctx, "prompts")) {
85
- const result = await loadFilesFromDir<Prompt>(ctx, userPromptsDir, PROVIDER_ID, "user", {
107
+ const load = (dir: string, level: "user" | "project") =>
108
+ loadFilesFromDir<Prompt>(ctx, dir, PROVIDER_ID, level, {
86
109
  extensions: ["md"],
87
110
  transform: (name, content, filePath, source) => ({
88
111
  name: name.replace(/\.md$/, ""),
@@ -91,109 +114,106 @@ async function loadPrompts(ctx: LoadContext): Promise<LoadResult<Prompt>> {
91
114
  _source: source,
92
115
  }),
93
116
  });
94
- items.push(...result.items);
95
- warnings.push(...(result.warnings ?? []));
96
- }
117
+
118
+ const results = await Promise.all([
119
+ ...getProjectPathCandidates(ctx, "prompts").map(dir => load(dir, "project")),
120
+ ...getUserPathCandidates(ctx, "prompts").map(dir => load(dir, "user")),
121
+ ]);
122
+
97
123
  return {
98
- items,
99
- warnings,
124
+ items: results.flatMap(r => r.items),
125
+ warnings: results.flatMap(r => r.warnings ?? []),
100
126
  };
101
127
  }
102
128
 
103
129
  registerProvider<Prompt>(promptCapability.id, {
104
130
  id: PROVIDER_ID,
105
131
  displayName: DISPLAY_NAME,
106
- description: "Load prompts from ~/.agent/prompts (fallback ~/.agents/prompts)",
132
+ description: "Load prompts from .agent/prompts and .agents/prompts (project walk-up + user home)",
107
133
  priority: PRIORITY,
108
134
  load: loadPrompts,
109
135
  });
110
136
 
111
137
  // Slash Commands
112
138
  async function loadSlashCommands(ctx: LoadContext): Promise<LoadResult<SlashCommand>> {
113
- const items: SlashCommand[] = [];
114
- const warnings: string[] = [];
115
- for (const userCommandsDir of getUserAgentPathCandidates(ctx, "commands")) {
116
- const result = await loadFilesFromDir<SlashCommand>(ctx, userCommandsDir, PROVIDER_ID, "user", {
139
+ const load = (dir: string, level: "user" | "project") =>
140
+ loadFilesFromDir<SlashCommand>(ctx, dir, PROVIDER_ID, level, {
117
141
  extensions: ["md"],
118
142
  transform: (name, content, filePath, source) => ({
119
143
  name: name.replace(/\.md$/, ""),
120
144
  path: filePath,
121
145
  content,
122
- level: "user",
146
+ level,
123
147
  _source: source,
124
148
  }),
125
149
  });
126
- items.push(...result.items);
127
- warnings.push(...(result.warnings ?? []));
128
- }
150
+
151
+ const results = await Promise.all([
152
+ ...getProjectPathCandidates(ctx, "commands").map(dir => load(dir, "project")),
153
+ ...getUserPathCandidates(ctx, "commands").map(dir => load(dir, "user")),
154
+ ]);
155
+
129
156
  return {
130
- items,
131
- warnings,
157
+ items: results.flatMap(r => r.items),
158
+ warnings: results.flatMap(r => r.warnings ?? []),
132
159
  };
133
160
  }
134
161
 
135
162
  registerProvider<SlashCommand>(slashCommandCapability.id, {
136
163
  id: PROVIDER_ID,
137
164
  displayName: DISPLAY_NAME,
138
- description: "Load commands from ~/.agent/commands (fallback ~/.agents/commands)",
165
+ description: "Load commands from .agent/commands and .agents/commands (project walk-up + user home)",
139
166
  priority: PRIORITY,
140
167
  load: loadSlashCommands,
141
168
  });
142
169
 
143
170
  // Context Files (AGENTS.md)
144
171
  async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFile>> {
145
- const items: ContextFile[] = [];
146
- for (const agentsPath of getUserAgentPathCandidates(ctx, "AGENTS.md")) {
147
- const content = await readFile(agentsPath);
148
- if (!content) {
149
- continue;
150
- }
151
- items.push({
152
- path: agentsPath,
153
- content,
154
- level: "user",
155
- _source: createSourceMeta(PROVIDER_ID, agentsPath, "user"),
156
- });
157
- }
158
- return {
159
- items,
160
- warnings: [],
172
+ const load = async (filePath: string, level: "user" | "project"): Promise<ContextFile | null> => {
173
+ const content = await readFile(filePath);
174
+ if (!content) return null;
175
+ // filePath is <ancestor>/.agent(s)/AGENTS.md — go up past the config dir to the ancestor
176
+ const ancestorDir = path.dirname(path.dirname(filePath));
177
+ const depth = level === "project" ? calculateDepth(ctx.cwd, ancestorDir, path.sep) : undefined;
178
+ return { path: filePath, content, level, depth, _source: createSourceMeta(PROVIDER_ID, filePath, level) };
161
179
  };
180
+
181
+ const results = await Promise.all([
182
+ ...getProjectPathCandidates(ctx, "AGENTS.md").map(p => load(p, "project")),
183
+ ...getUserPathCandidates(ctx, "AGENTS.md").map(p => load(p, "user")),
184
+ ]);
185
+
186
+ return { items: results.filter((r): r is ContextFile => r !== null), warnings: [] };
162
187
  }
163
188
 
164
189
  registerProvider<ContextFile>(contextFileCapability.id, {
165
190
  id: PROVIDER_ID,
166
191
  displayName: DISPLAY_NAME,
167
- description: "Load AGENTS.md from ~/.agent (fallback ~/.agents)",
192
+ description: "Load AGENTS.md from .agent and .agents (project walk-up + user home)",
168
193
  priority: PRIORITY,
169
194
  load: loadContextFiles,
170
195
  });
171
196
 
172
197
  // System Prompt (SYSTEM.md)
173
198
  async function loadSystemPrompt(ctx: LoadContext): Promise<LoadResult<SystemPrompt>> {
174
- const items: SystemPrompt[] = [];
175
- for (const systemPath of getUserAgentPathCandidates(ctx, "SYSTEM.md")) {
176
- const content = await readFile(systemPath);
177
- if (!content) {
178
- continue;
179
- }
180
- items.push({
181
- path: systemPath,
182
- content,
183
- level: "user",
184
- _source: createSourceMeta(PROVIDER_ID, systemPath, "user"),
185
- });
186
- }
187
- return {
188
- items,
189
- warnings: [],
199
+ const load = async (filePath: string, level: "user" | "project"): Promise<SystemPrompt | null> => {
200
+ const content = await readFile(filePath);
201
+ if (!content) return null;
202
+ return { path: filePath, content, level, _source: createSourceMeta(PROVIDER_ID, filePath, level) };
190
203
  };
204
+
205
+ const results = await Promise.all([
206
+ ...getProjectPathCandidates(ctx, "SYSTEM.md").map(p => load(p, "project")),
207
+ ...getUserPathCandidates(ctx, "SYSTEM.md").map(p => load(p, "user")),
208
+ ]);
209
+
210
+ return { items: results.filter((r): r is SystemPrompt => r !== null), warnings: [] };
191
211
  }
192
212
 
193
213
  registerProvider<SystemPrompt>(systemPromptCapability.id, {
194
214
  id: PROVIDER_ID,
195
215
  displayName: DISPLAY_NAME,
196
- description: "Load SYSTEM.md from ~/.agent (fallback ~/.agents)",
216
+ description: "Load SYSTEM.md from .agent and .agents (project walk-up + user home)",
197
217
  priority: PRIORITY,
198
218
  load: loadSystemPrompt,
199
219
  });
@@ -68,12 +68,13 @@ async function getConfigDirs(ctx: LoadContext): Promise<Array<{ dir: string; lev
68
68
  return result;
69
69
  }
70
70
 
71
- function getAncestorDirs(cwd: string): Array<{ dir: string; depth: number }> {
71
+ function getAncestorDirs(cwd: string, stopAt?: string | null): Array<{ dir: string; depth: number }> {
72
72
  const ancestors: Array<{ dir: string; depth: number }> = [];
73
73
  let current = cwd;
74
74
  let depth = 0;
75
75
  while (true) {
76
76
  ancestors.push({ dir: current, depth });
77
+ if (stopAt && current === stopAt) break;
77
78
  const parent = path.dirname(current);
78
79
  if (parent === current) break;
79
80
  current = parent;
@@ -82,8 +83,11 @@ function getAncestorDirs(cwd: string): Array<{ dir: string; depth: number }> {
82
83
  return ancestors;
83
84
  }
84
85
 
85
- async function findNearestProjectConfigDir(cwd: string): Promise<{ dir: string; depth: number } | null> {
86
- for (const ancestor of getAncestorDirs(cwd)) {
86
+ async function findNearestProjectConfigDir(
87
+ cwd: string,
88
+ repoRoot?: string | null,
89
+ ): Promise<{ dir: string; depth: number } | null> {
90
+ for (const ancestor of getAncestorDirs(cwd, repoRoot)) {
87
91
  const configDir = await ifNonEmptyDir(ancestor.dir, PATHS.projectDir);
88
92
  if (configDir) return { dir: configDir, depth: ancestor.depth };
89
93
  }
@@ -215,7 +219,7 @@ async function loadSystemPrompt(ctx: LoadContext): Promise<LoadResult<SystemProm
215
219
  });
216
220
  }
217
221
 
218
- const nearestProjectConfigDir = await findNearestProjectConfigDir(ctx.cwd);
222
+ const nearestProjectConfigDir = await findNearestProjectConfigDir(ctx.cwd, ctx.repoRoot);
219
223
  if (nearestProjectConfigDir) {
220
224
  const projectPath = path.join(nearestProjectConfigDir.dir, "SYSTEM.md");
221
225
  const projectContent = await readFile(projectPath);
@@ -242,18 +246,27 @@ registerProvider<SystemPrompt>(systemPromptCapability.id, {
242
246
 
243
247
  // Skills
244
248
  async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
245
- const configDirs = await getConfigDirs(ctx);
246
- const results = await Promise.all(
247
- configDirs.map(({ dir, level }) =>
248
- scanSkillsFromDir(ctx, {
249
- dir: path.join(dir, "skills"),
250
- providerId: PROVIDER_ID,
251
- level,
252
- requireDescription: true,
253
- }),
254
- ),
249
+ // Walk up from cwd finding .omp/skills/ in ancestors (closest first)
250
+ const ancestors = getAncestorDirs(ctx.cwd, ctx.repoRoot ?? ctx.home);
251
+ const projectScans = ancestors.map(({ dir }) =>
252
+ scanSkillsFromDir(ctx, {
253
+ dir: path.join(dir, PATHS.projectDir, "skills"),
254
+ providerId: PROVIDER_ID,
255
+ level: "project",
256
+ requireDescription: true,
257
+ }),
255
258
  );
256
259
 
260
+ // User-level scan from ~/.omp/agent/skills/
261
+ const userScan = scanSkillsFromDir(ctx, {
262
+ dir: path.join(ctx.home, PATHS.userAgent, "skills"),
263
+ providerId: PROVIDER_ID,
264
+ level: "user",
265
+ requireDescription: true,
266
+ });
267
+
268
+ const results = await Promise.all([...projectScans, userScan]);
269
+
257
270
  return {
258
271
  items: results.flatMap(r => r.items),
259
272
  warnings: results.flatMap(r => r.warnings ?? []),
@@ -795,7 +808,7 @@ async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFil
795
808
  });
796
809
  }
797
810
 
798
- const nearestProjectConfigDir = await findNearestProjectConfigDir(ctx.cwd);
811
+ const nearestProjectConfigDir = await findNearestProjectConfigDir(ctx.cwd, ctx.repoRoot);
799
812
  if (nearestProjectConfigDir) {
800
813
  const projectPath = path.join(nearestProjectConfigDir.dir, "AGENTS.md");
801
814
  const projectContent = await readFile(projectPath);
@@ -145,7 +145,7 @@ async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFil
145
145
  const projectClaudeMd = path.join(projectBase, "CLAUDE.md");
146
146
  const projectContent = await readFile(projectClaudeMd);
147
147
  if (projectContent !== null) {
148
- const depth = calculateDepth(ctx.cwd, projectBase, path.sep);
148
+ const depth = calculateDepth(ctx.cwd, path.dirname(projectBase), path.sep);
149
149
  items.push({
150
150
  path: projectClaudeMd,
151
151
  content: projectContent,
@@ -164,11 +164,27 @@ async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFil
164
164
 
165
165
  async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
166
166
  const userSkillsDir = path.join(getUserClaude(ctx), "skills");
167
- const projectSkillsDir = path.join(getProjectClaude(ctx), "skills");
168
167
 
169
- const [userResult, projectResult] = await Promise.allSettled([
168
+ // Walk up from cwd finding .claude/skills/ in ancestors
169
+ const projectScans: Promise<LoadResult<Skill>>[] = [];
170
+ let current = ctx.cwd;
171
+ while (true) {
172
+ projectScans.push(
173
+ scanSkillsFromDir(ctx, {
174
+ dir: path.join(current, CONFIG_DIR, "skills"),
175
+ providerId: PROVIDER_ID,
176
+ level: "project",
177
+ }),
178
+ );
179
+ if (current === (ctx.repoRoot ?? ctx.home)) break;
180
+ const parent = path.dirname(current);
181
+ if (parent === current) break; // filesystem root
182
+ current = parent;
183
+ }
184
+
185
+ const [userResult, ...projectResults] = await Promise.allSettled([
170
186
  scanSkillsFromDir(ctx, { dir: userSkillsDir, providerId: PROVIDER_ID, level: "user" }),
171
- scanSkillsFromDir(ctx, { dir: projectSkillsDir, providerId: PROVIDER_ID, level: "project" }),
187
+ ...projectScans,
172
188
  ]);
173
189
 
174
190
  const items: Skill[] = [];
@@ -181,11 +197,13 @@ async function loadSkills(ctx: LoadContext): Promise<LoadResult<Skill>> {
181
197
  warnings.push(`Failed to scan Claude user skills in ${userSkillsDir}: ${String(userResult.reason)}`);
182
198
  }
183
199
 
184
- if (projectResult.status === "fulfilled") {
185
- items.push(...projectResult.value.items);
186
- warnings.push(...(projectResult.value.warnings ?? []));
187
- } else if (!isMissingDirectoryError(projectResult.reason)) {
188
- warnings.push(`Failed to scan Claude project skills in ${projectSkillsDir}: ${String(projectResult.reason)}`);
200
+ for (const projectResult of projectResults) {
201
+ if (projectResult.status === "fulfilled") {
202
+ items.push(...projectResult.value.items);
203
+ warnings.push(...(projectResult.value.warnings ?? []));
204
+ } else if (!isMissingDirectoryError(projectResult.reason)) {
205
+ warnings.push(`Failed to scan Claude project skills: ${String(projectResult.reason)}`);
206
+ }
189
207
  }
190
208
 
191
209
  return { items, warnings };
@@ -1,6 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
- import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
3
+ import type { ThinkingLevel } from "@oh-my-pi/pi-ai";
4
+ import { parseThinkingLevel } from "@oh-my-pi/pi-ai";
4
5
  import { FileType, glob } from "@oh-my-pi/pi-natives";
5
6
  import { CONFIG_DIR_NAME, tryParseJson } from "@oh-my-pi/pi-utils";
6
7
  import { readFile } from "../capability/fs";
@@ -9,8 +10,6 @@ import type { Skill, SkillFrontmatter } from "../capability/skill";
9
10
  import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
10
11
  import { parseFrontmatter } from "../utils/frontmatter";
11
12
 
12
- const VALID_THINKING_LEVELS: readonly string[] = ["off", "minimal", "low", "medium", "high", "xhigh"];
13
-
14
13
  /**
15
14
  * Standard paths for each config source.
16
15
  */
@@ -100,18 +99,6 @@ export function createSourceMeta(provider: string, filePath: string, level: "use
100
99
  };
101
100
  }
102
101
 
103
- /**
104
- * Parse thinking level from frontmatter.
105
- * Supports keys: thinkingLevel, thinking-level, thinking
106
- */
107
- export function parseThinkingLevel(frontmatter: Record<string, unknown>): ThinkingLevel | undefined {
108
- const raw = frontmatter.thinkingLevel ?? frontmatter["thinking-level"] ?? frontmatter.thinking;
109
- if (typeof raw === "string" && VALID_THINKING_LEVELS.includes(raw)) {
110
- return raw as ThinkingLevel;
111
- }
112
- return undefined;
113
- }
114
-
115
102
  export function parseBoolean(value: unknown): boolean | undefined {
116
103
  if (typeof value === "boolean") return value;
117
104
  if (typeof value === "string") {
@@ -247,10 +234,16 @@ export function parseAgentFields(frontmatter: Record<string, unknown>): ParsedAg
247
234
  }
248
235
 
249
236
  const output = frontmatter.output !== undefined ? frontmatter.output : undefined;
237
+ const rawThinkingLevel =
238
+ typeof frontmatter.thinkingLevel === "string"
239
+ ? frontmatter.thinkingLevel
240
+ : typeof frontmatter.thinking === "string"
241
+ ? frontmatter.thinking
242
+ : undefined;
243
+
244
+ const thinkingLevel = parseThinkingLevel(rawThinkingLevel);
250
245
  const model = parseModelList(frontmatter.model);
251
- const thinkingLevel = parseThinkingLevel(frontmatter);
252
246
  const blocking = parseBoolean(frontmatter.blocking);
253
-
254
247
  return { name, description, tools, spawns, model, output, thinkingLevel, blocking };
255
248
  }
256
249
 
@@ -4,8 +4,7 @@
4
4
  import type * as fs1 from "node:fs";
5
5
  import * as fs from "node:fs/promises";
6
6
  import * as path from "node:path";
7
- import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
8
- import type { ImageContent, Model, TextContent } from "@oh-my-pi/pi-ai";
7
+ import type { ImageContent, Model, TextContent, ThinkingLevel } from "@oh-my-pi/pi-ai";
9
8
  import * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
10
9
  import type { KeyId } from "@oh-my-pi/pi-tui";
11
10
  import { hasFsCode, isEacces, isEnoent, logger } from "@oh-my-pi/pi-utils";
@@ -7,7 +7,7 @@
7
7
  * - Register commands, keyboard shortcuts, and CLI flags
8
8
  * - Interact with the user via UI primitives
9
9
  */
10
- import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
10
+ import type { AgentMessage, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
11
11
  import type {
12
12
  Api,
13
13
  AssistantMessageEvent,
@@ -19,6 +19,7 @@ import type {
19
19
  OAuthLoginCallbacks,
20
20
  SimpleStreamOptions,
21
21
  TextContent,
22
+ ThinkingLevel,
22
23
  ToolResultMessage,
23
24
  } from "@oh-my-pi/pi-ai";
24
25
  import type * as piCodingAgent from "@oh-my-pi/pi-coding-agent";
@@ -40,7 +40,7 @@ export async function loadSkillsFromDir(options: LoadSkillsFromDirOptions): Prom
40
40
  const providerId = rawProviderId || "custom";
41
41
  const level: "user" | "project" = rawLevel === "project" ? "project" : "user";
42
42
  const result = await scanSkillsFromDir(
43
- { cwd: getProjectDir(), home: os.homedir() },
43
+ { cwd: getProjectDir(), home: os.homedir(), repoRoot: null },
44
44
  {
45
45
  dir: options.dir,
46
46
  providerId,
@@ -175,7 +175,7 @@ export async function loadSkills(options: LoadSkillsOptions = {}): Promise<LoadS
175
175
  customDirectories.map(async dir => {
176
176
  const expandedDir = expandTilde(dir);
177
177
  const scanResult = await scanSkillsFromDir(
178
- { cwd, home: os.homedir() },
178
+ { cwd, home: os.homedir(), repoRoot: null },
179
179
  {
180
180
  dir: expandedDir,
181
181
  providerId: "custom",