pi-subagents 0.13.4 → 0.14.0
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 +11 -0
- package/README.md +32 -11
- package/agent-management.ts +15 -6
- package/agent-manager-detail.ts +12 -2
- package/agent-manager-edit.ts +75 -23
- package/agent-manager-list.ts +9 -2
- package/agent-manager.ts +199 -11
- package/agents.ts +315 -20
- package/artifacts.ts +11 -5
- package/async-execution.ts +92 -73
- package/chain-clarify.ts +45 -156
- package/chain-execution.ts +23 -63
- package/execution.ts +53 -48
- package/index.ts +1 -1
- package/model-fallback.ts +8 -2
- package/package.json +1 -1
- package/schemas.ts +1 -1
- package/settings.ts +6 -4
- package/skills.ts +165 -75
- package/subagent-executor.ts +43 -9
- package/subagent-runner.ts +167 -50
- package/types.ts +64 -13
- package/utils.ts +5 -10
package/skills.ts
CHANGED
|
@@ -38,6 +38,11 @@ interface CachedSkillEntry {
|
|
|
38
38
|
order: number;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
interface SkillSearchPath {
|
|
42
|
+
path: string;
|
|
43
|
+
source: SkillSource;
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
const skillCache = new Map<string, SkillCacheEntry>();
|
|
42
47
|
const MAX_CACHE_SIZE = 50;
|
|
43
48
|
|
|
@@ -74,22 +79,45 @@ function isWithinPath(filePath: string, dir: string): boolean {
|
|
|
74
79
|
return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
|
|
75
80
|
}
|
|
76
81
|
|
|
77
|
-
function
|
|
78
|
-
|
|
82
|
+
function readOptionalJsonFile(filePath: string, label: string): unknown {
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
85
|
+
} catch (error) {
|
|
86
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
87
|
+
? (error as { code?: unknown }).code
|
|
88
|
+
: undefined;
|
|
89
|
+
if (code === "ENOENT") return null;
|
|
90
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
91
|
+
throw new Error(`Failed to read ${label} '${filePath}': ${message}`, {
|
|
92
|
+
cause: error instanceof Error ? error : undefined,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readJsonFileBestEffort(filePath: string): unknown {
|
|
79
98
|
try {
|
|
80
|
-
|
|
81
|
-
const pkg = JSON.parse(content);
|
|
82
|
-
const piSkills = pkg?.pi?.skills;
|
|
83
|
-
if (!Array.isArray(piSkills)) return [];
|
|
84
|
-
return piSkills
|
|
85
|
-
.filter((s: unknown) => typeof s === "string")
|
|
86
|
-
.map((s: string) => path.resolve(packageRoot, s));
|
|
99
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
87
100
|
} catch {
|
|
88
|
-
// Package
|
|
89
|
-
return
|
|
101
|
+
// Package scans over installed dependencies are opportunistic.
|
|
102
|
+
return null;
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
105
|
|
|
106
|
+
function extractSkillPathsFromPackageRoot(packageRoot: string, source: SkillSource, bestEffort = false): SkillSearchPath[] {
|
|
107
|
+
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
108
|
+
const pkg = bestEffort
|
|
109
|
+
? readJsonFileBestEffort(packageJsonPath)
|
|
110
|
+
: readOptionalJsonFile(packageJsonPath, "package manifest");
|
|
111
|
+
if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) return [];
|
|
112
|
+
const pi = (pkg as { pi?: unknown }).pi;
|
|
113
|
+
if (!pi || typeof pi !== "object" || Array.isArray(pi)) return [];
|
|
114
|
+
const skills = (pi as { skills?: unknown }).skills;
|
|
115
|
+
if (!Array.isArray(skills)) return [];
|
|
116
|
+
return skills
|
|
117
|
+
.filter((entry): entry is string => typeof entry === "string")
|
|
118
|
+
.map((entry) => ({ path: path.resolve(packageRoot, entry), source }));
|
|
119
|
+
}
|
|
120
|
+
|
|
93
121
|
let cachedGlobalNpmRoot: string | null = null;
|
|
94
122
|
|
|
95
123
|
function getGlobalNpmRoot(): string | null {
|
|
@@ -104,27 +132,25 @@ function getGlobalNpmRoot(): string | null {
|
|
|
104
132
|
}
|
|
105
133
|
}
|
|
106
134
|
|
|
107
|
-
function
|
|
108
|
-
const dirs = [
|
|
109
|
-
path.join(cwd, CONFIG_DIR, "npm", "node_modules"),
|
|
110
|
-
path.join(AGENT_DIR, "npm", "node_modules"),
|
|
135
|
+
function collectInstalledPackageSkillPaths(cwd: string): SkillSearchPath[] {
|
|
136
|
+
const dirs: SkillSearchPath[] = [
|
|
137
|
+
{ path: path.join(cwd, CONFIG_DIR, "npm", "node_modules"), source: "project-package" },
|
|
138
|
+
{ path: path.join(AGENT_DIR, "npm", "node_modules"), source: "user-package" },
|
|
111
139
|
];
|
|
112
|
-
|
|
113
|
-
// Add global npm root if available (where pi installs global packages)
|
|
140
|
+
|
|
114
141
|
const globalRoot = getGlobalNpmRoot();
|
|
115
142
|
if (globalRoot) {
|
|
116
|
-
dirs.push(globalRoot);
|
|
143
|
+
dirs.push({ path: globalRoot, source: "user-package" });
|
|
117
144
|
}
|
|
118
|
-
|
|
119
|
-
const results:
|
|
145
|
+
|
|
146
|
+
const results: SkillSearchPath[] = [];
|
|
120
147
|
|
|
121
148
|
for (const dir of dirs) {
|
|
122
|
-
if (!fs.existsSync(dir)) continue;
|
|
149
|
+
if (!fs.existsSync(dir.path)) continue;
|
|
123
150
|
let entries: fs.Dirent[];
|
|
124
151
|
try {
|
|
125
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
152
|
+
entries = fs.readdirSync(dir.path, { withFileTypes: true });
|
|
126
153
|
} catch {
|
|
127
|
-
// Ignore unreadable package roots and continue scanning other roots.
|
|
128
154
|
continue;
|
|
129
155
|
}
|
|
130
156
|
|
|
@@ -133,75 +159,125 @@ function collectPackageSkillPaths(cwd: string): string[] {
|
|
|
133
159
|
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
134
160
|
|
|
135
161
|
if (entry.name.startsWith("@")) {
|
|
136
|
-
const scopeDir = path.join(dir, entry.name);
|
|
162
|
+
const scopeDir = path.join(dir.path, entry.name);
|
|
137
163
|
let scopeEntries: fs.Dirent[];
|
|
138
164
|
try {
|
|
139
165
|
scopeEntries = fs.readdirSync(scopeDir, { withFileTypes: true });
|
|
140
166
|
} catch {
|
|
141
|
-
// Ignore unreadable scoped package directories and continue.
|
|
142
167
|
continue;
|
|
143
168
|
}
|
|
144
169
|
for (const scopeEntry of scopeEntries) {
|
|
145
170
|
if (scopeEntry.name.startsWith(".")) continue;
|
|
146
171
|
if (!scopeEntry.isDirectory() && !scopeEntry.isSymbolicLink()) continue;
|
|
147
172
|
const pkgRoot = path.join(scopeDir, scopeEntry.name);
|
|
148
|
-
results.push(...
|
|
173
|
+
results.push(...extractSkillPathsFromPackageRoot(pkgRoot, dir.source, true));
|
|
149
174
|
}
|
|
150
175
|
continue;
|
|
151
176
|
}
|
|
152
177
|
|
|
153
|
-
const pkgRoot = path.join(dir, entry.name);
|
|
154
|
-
results.push(...
|
|
178
|
+
const pkgRoot = path.join(dir.path, entry.name);
|
|
179
|
+
results.push(...extractSkillPathsFromPackageRoot(pkgRoot, dir.source, true));
|
|
155
180
|
}
|
|
156
181
|
}
|
|
157
182
|
|
|
158
183
|
return results;
|
|
159
184
|
}
|
|
160
185
|
|
|
161
|
-
function collectSettingsSkillPaths(cwd: string):
|
|
162
|
-
const results:
|
|
186
|
+
function collectSettingsSkillPaths(cwd: string): SkillSearchPath[] {
|
|
187
|
+
const results: SkillSearchPath[] = [];
|
|
163
188
|
const settingsFiles = [
|
|
164
|
-
{ file: path.join(cwd, CONFIG_DIR, "settings.json"), base: path.join(cwd, CONFIG_DIR) },
|
|
165
|
-
{ file: path.join(AGENT_DIR, "settings.json"), base: AGENT_DIR },
|
|
189
|
+
{ file: path.join(cwd, CONFIG_DIR, "settings.json"), base: path.join(cwd, CONFIG_DIR), source: "project-settings" as const },
|
|
190
|
+
{ file: path.join(AGENT_DIR, "settings.json"), base: AGENT_DIR, source: "user-settings" as const },
|
|
166
191
|
];
|
|
167
192
|
|
|
168
|
-
for (const { file, base } of settingsFiles) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
resolved = path.resolve(base, resolved);
|
|
181
|
-
}
|
|
182
|
-
results.push(resolved);
|
|
193
|
+
for (const { file, base, source } of settingsFiles) {
|
|
194
|
+
const settings = readOptionalJsonFile(file, "skills settings file");
|
|
195
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) continue;
|
|
196
|
+
const skills = (settings as { skills?: unknown }).skills;
|
|
197
|
+
if (!Array.isArray(skills)) continue;
|
|
198
|
+
for (const entry of skills) {
|
|
199
|
+
if (typeof entry !== "string") continue;
|
|
200
|
+
let resolved = entry;
|
|
201
|
+
if (resolved.startsWith("~/")) {
|
|
202
|
+
resolved = path.join(os.homedir(), resolved.slice(2));
|
|
203
|
+
} else if (!path.isAbsolute(resolved)) {
|
|
204
|
+
resolved = path.resolve(base, resolved);
|
|
183
205
|
}
|
|
184
|
-
|
|
185
|
-
|
|
206
|
+
results.push({ path: resolved, source });
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return results;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function resolveSettingsPackageRoot(source: string, baseDir: string): string | undefined {
|
|
214
|
+
const trimmed = source.trim();
|
|
215
|
+
if (!trimmed) return undefined;
|
|
216
|
+
const normalized = trimmed.startsWith("file:") ? trimmed.slice(5) : trimmed;
|
|
217
|
+
if (normalized === "~") return os.homedir();
|
|
218
|
+
if (normalized.startsWith("~/")) return path.join(os.homedir(), normalized.slice(2));
|
|
219
|
+
if (path.isAbsolute(normalized)) return normalized;
|
|
220
|
+
if (normalized === "." || normalized === ".." || normalized.startsWith("./") || normalized.startsWith("../")) {
|
|
221
|
+
return path.resolve(baseDir, normalized);
|
|
222
|
+
}
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function collectSettingsPackageSkillPaths(cwd: string): SkillSearchPath[] {
|
|
227
|
+
const settingsFiles = [
|
|
228
|
+
{ file: path.join(cwd, CONFIG_DIR, "settings.json"), base: path.join(cwd, CONFIG_DIR), source: "project-package" as const },
|
|
229
|
+
{ file: path.join(AGENT_DIR, "settings.json"), base: AGENT_DIR, source: "user-package" as const },
|
|
230
|
+
];
|
|
231
|
+
const results: SkillSearchPath[] = [];
|
|
232
|
+
|
|
233
|
+
for (const { file, base, source } of settingsFiles) {
|
|
234
|
+
const settings = readOptionalJsonFile(file, "skills settings file");
|
|
235
|
+
if (!settings || typeof settings !== "object" || Array.isArray(settings)) continue;
|
|
236
|
+
const packages = (settings as { packages?: unknown }).packages;
|
|
237
|
+
if (!Array.isArray(packages)) continue;
|
|
238
|
+
|
|
239
|
+
for (const entry of packages) {
|
|
240
|
+
const packageSource = typeof entry === "string"
|
|
241
|
+
? entry
|
|
242
|
+
: typeof entry === "object" && entry !== null && typeof (entry as { source?: unknown }).source === "string"
|
|
243
|
+
? (entry as { source: string }).source
|
|
244
|
+
: undefined;
|
|
245
|
+
if (!packageSource) continue;
|
|
246
|
+
|
|
247
|
+
const packageRoot = resolveSettingsPackageRoot(packageSource, base);
|
|
248
|
+
if (!packageRoot) continue;
|
|
249
|
+
results.push(...extractSkillPathsFromPackageRoot(packageRoot, source));
|
|
186
250
|
}
|
|
187
251
|
}
|
|
188
252
|
|
|
189
253
|
return results;
|
|
190
254
|
}
|
|
191
255
|
|
|
192
|
-
function buildSkillPaths(cwd: string):
|
|
193
|
-
const
|
|
194
|
-
path.join(cwd, CONFIG_DIR, "skills"),
|
|
195
|
-
path.join(cwd, ".agents", "skills"),
|
|
196
|
-
path.join(AGENT_DIR, "skills"),
|
|
197
|
-
path.join(os.homedir(), ".agents", "skills"),
|
|
256
|
+
function buildSkillPaths(cwd: string): SkillSearchPath[] {
|
|
257
|
+
const skillPaths: SkillSearchPath[] = [
|
|
258
|
+
{ path: path.join(cwd, CONFIG_DIR, "skills"), source: "project" },
|
|
259
|
+
{ path: path.join(cwd, ".agents", "skills"), source: "project" },
|
|
260
|
+
{ path: path.join(AGENT_DIR, "skills"), source: "user" },
|
|
261
|
+
{ path: path.join(os.homedir(), ".agents", "skills"), source: "user" },
|
|
262
|
+
...collectInstalledPackageSkillPaths(cwd),
|
|
263
|
+
...collectSettingsPackageSkillPaths(cwd),
|
|
264
|
+
...extractSkillPathsFromPackageRoot(cwd, "project-package"),
|
|
265
|
+
...collectSettingsSkillPaths(cwd),
|
|
198
266
|
];
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
267
|
+
|
|
268
|
+
const deduped = new Map<string, SkillSearchPath>();
|
|
269
|
+
for (const entry of skillPaths) {
|
|
270
|
+
const resolvedPath = path.resolve(entry.path);
|
|
271
|
+
if (!deduped.has(resolvedPath)) {
|
|
272
|
+
deduped.set(resolvedPath, { path: resolvedPath, source: entry.source });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return [...deduped.values()];
|
|
202
276
|
}
|
|
203
277
|
|
|
204
|
-
function inferSkillSource(filePath: string, cwd: string): SkillSource {
|
|
278
|
+
function inferSkillSource(filePath: string, cwd: string, sourceHint?: SkillSource): SkillSource {
|
|
279
|
+
if (sourceHint) return sourceHint;
|
|
280
|
+
|
|
205
281
|
const projectConfigRoot = path.resolve(cwd, CONFIG_DIR);
|
|
206
282
|
const projectSkillsRoot = path.resolve(cwd, CONFIG_DIR, "skills");
|
|
207
283
|
const projectPackagesRoot = path.resolve(cwd, CONFIG_DIR, "npm", "node_modules");
|
|
@@ -252,12 +328,12 @@ function maybeReadSkillDescription(filePath: string): string | undefined {
|
|
|
252
328
|
}
|
|
253
329
|
}
|
|
254
330
|
|
|
255
|
-
function collectFilesystemSkills(cwd: string, skillPaths:
|
|
331
|
+
function collectFilesystemSkills(cwd: string, skillPaths: SkillSearchPath[]): CachedSkillEntry[] {
|
|
256
332
|
const entries: CachedSkillEntry[] = [];
|
|
257
333
|
const seen = new Set<string>();
|
|
258
334
|
let order = 0;
|
|
259
335
|
|
|
260
|
-
const pushEntry = (name: string, filePath: string) => {
|
|
336
|
+
const pushEntry = (name: string, filePath: string, sourceHint?: SkillSource) => {
|
|
261
337
|
const resolvedFile = path.resolve(filePath);
|
|
262
338
|
if (seen.has(resolvedFile)) return;
|
|
263
339
|
if (!fs.existsSync(resolvedFile)) return;
|
|
@@ -265,60 +341,58 @@ function collectFilesystemSkills(cwd: string, skillPaths: string[]): CachedSkill
|
|
|
265
341
|
entries.push({
|
|
266
342
|
name,
|
|
267
343
|
filePath: resolvedFile,
|
|
268
|
-
source: inferSkillSource(resolvedFile, cwd),
|
|
344
|
+
source: inferSkillSource(resolvedFile, cwd, sourceHint),
|
|
269
345
|
description: maybeReadSkillDescription(resolvedFile),
|
|
270
346
|
order: order++,
|
|
271
347
|
});
|
|
272
348
|
};
|
|
273
349
|
|
|
274
350
|
for (const skillPath of skillPaths) {
|
|
275
|
-
if (!fs.existsSync(skillPath)) continue;
|
|
351
|
+
if (!fs.existsSync(skillPath.path)) continue;
|
|
276
352
|
|
|
277
353
|
let stat: fs.Stats;
|
|
278
354
|
try {
|
|
279
|
-
stat = fs.statSync(skillPath);
|
|
355
|
+
stat = fs.statSync(skillPath.path);
|
|
280
356
|
} catch {
|
|
281
|
-
// Ignore paths that disappear or become unreadable during discovery.
|
|
282
357
|
continue;
|
|
283
358
|
}
|
|
284
359
|
|
|
285
360
|
if (stat.isFile()) {
|
|
286
|
-
const fileName = path.basename(skillPath);
|
|
361
|
+
const fileName = path.basename(skillPath.path);
|
|
287
362
|
if (!fileName.toLowerCase().endsWith(".md")) continue;
|
|
288
363
|
const skillName = fileName.toLowerCase() === "skill.md"
|
|
289
|
-
? path.basename(path.dirname(skillPath))
|
|
364
|
+
? path.basename(path.dirname(skillPath.path))
|
|
290
365
|
: path.basename(fileName, path.extname(fileName));
|
|
291
|
-
pushEntry(skillName, skillPath);
|
|
366
|
+
pushEntry(skillName, skillPath.path, skillPath.source);
|
|
292
367
|
continue;
|
|
293
368
|
}
|
|
294
369
|
|
|
295
370
|
if (!stat.isDirectory()) continue;
|
|
296
371
|
|
|
297
|
-
const rootSkillFile = path.join(skillPath, "SKILL.md");
|
|
372
|
+
const rootSkillFile = path.join(skillPath.path, "SKILL.md");
|
|
298
373
|
if (fs.existsSync(rootSkillFile)) {
|
|
299
|
-
pushEntry(path.basename(skillPath), rootSkillFile);
|
|
374
|
+
pushEntry(path.basename(skillPath.path), rootSkillFile, skillPath.source);
|
|
300
375
|
}
|
|
301
376
|
|
|
302
377
|
let childEntries: fs.Dirent[];
|
|
303
378
|
try {
|
|
304
|
-
childEntries = fs.readdirSync(skillPath, { withFileTypes: true });
|
|
379
|
+
childEntries = fs.readdirSync(skillPath.path, { withFileTypes: true });
|
|
305
380
|
} catch {
|
|
306
|
-
// Ignore unreadable skill directories and continue scanning.
|
|
307
381
|
continue;
|
|
308
382
|
}
|
|
309
383
|
|
|
310
384
|
for (const child of childEntries) {
|
|
311
385
|
if (child.name.startsWith(".")) continue;
|
|
312
|
-
const childPath = path.join(skillPath, child.name);
|
|
386
|
+
const childPath = path.join(skillPath.path, child.name);
|
|
313
387
|
if (child.isDirectory() || child.isSymbolicLink()) {
|
|
314
388
|
const nestedSkillPath = path.join(childPath, "SKILL.md");
|
|
315
389
|
if (fs.existsSync(nestedSkillPath)) {
|
|
316
|
-
pushEntry(child.name, nestedSkillPath);
|
|
390
|
+
pushEntry(child.name, nestedSkillPath, skillPath.source);
|
|
317
391
|
}
|
|
318
392
|
continue;
|
|
319
393
|
}
|
|
320
394
|
if (child.isFile() && child.name.toLowerCase().endsWith(".md")) {
|
|
321
|
-
pushEntry(path.basename(child.name, path.extname(child.name)), childPath);
|
|
395
|
+
pushEntry(path.basename(child.name, path.extname(child.name)), childPath, skillPath.source);
|
|
322
396
|
}
|
|
323
397
|
}
|
|
324
398
|
}
|
|
@@ -418,6 +492,22 @@ export function resolveSkills(
|
|
|
418
492
|
return { resolved, missing };
|
|
419
493
|
}
|
|
420
494
|
|
|
495
|
+
export function resolveSkillsWithFallback(
|
|
496
|
+
skillNames: string[],
|
|
497
|
+
primaryCwd: string,
|
|
498
|
+
fallbackCwd?: string,
|
|
499
|
+
): { resolved: ResolvedSkill[]; missing: string[] } {
|
|
500
|
+
const primary = resolveSkills(skillNames, primaryCwd);
|
|
501
|
+
if (!fallbackCwd || primary.missing.length === 0) return primary;
|
|
502
|
+
if (path.resolve(primaryCwd) === path.resolve(fallbackCwd)) return primary;
|
|
503
|
+
|
|
504
|
+
const fallback = resolveSkills(primary.missing, fallbackCwd);
|
|
505
|
+
return {
|
|
506
|
+
resolved: [...primary.resolved, ...fallback.resolved],
|
|
507
|
+
missing: fallback.missing,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
421
511
|
export function buildSkillInjection(skills: ResolvedSkill[]): string {
|
|
422
512
|
if (skills.length === 0) return "";
|
|
423
513
|
|
package/subagent-executor.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { executeChain } from "./chain-execution.ts";
|
|
|
10
10
|
import { resolveExecutionAgentScope } from "./agent-scope.ts";
|
|
11
11
|
import { handleManagementAction } from "./agent-management.ts";
|
|
12
12
|
import { runSync } from "./execution.ts";
|
|
13
|
+
import { resolveModelCandidate } from "./model-fallback.ts";
|
|
13
14
|
import { aggregateParallelOutputs } from "./parallel-utils.ts";
|
|
14
15
|
import { recordRun } from "./run-history.ts";
|
|
15
16
|
import {
|
|
@@ -364,7 +365,12 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
364
365
|
};
|
|
365
366
|
}
|
|
366
367
|
const id = randomUUID();
|
|
367
|
-
const asyncCtx = {
|
|
368
|
+
const asyncCtx = {
|
|
369
|
+
pi: deps.pi,
|
|
370
|
+
cwd: ctx.cwd,
|
|
371
|
+
currentSessionId: deps.state.currentSessionId!,
|
|
372
|
+
currentModelProvider: ctx.model?.provider,
|
|
373
|
+
};
|
|
368
374
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
369
375
|
provider: m.provider,
|
|
370
376
|
id: m.id,
|
|
@@ -409,6 +415,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
409
415
|
const normalizedSkills = normalizeSkillInput(params.skill);
|
|
410
416
|
const skills = normalizedSkills === false ? [] : normalizedSkills;
|
|
411
417
|
const maxSubagentDepth = resolveChildMaxSubagentDepth(currentMaxSubagentDepth, a.maxSubagentDepth);
|
|
418
|
+
const modelOverride = resolveModelCandidate((params.model as string | undefined) ?? a.model, availableModels, ctx.model?.provider);
|
|
412
419
|
return executeAsyncSingle(id, {
|
|
413
420
|
agent: params.agent!,
|
|
414
421
|
task: params.context === "fork" ? wrapForkTask(params.task!) : params.task!,
|
|
@@ -424,7 +431,7 @@ function runAsyncPath(data: ExecutionContextData, deps: ExecutorDeps): AgentTool
|
|
|
424
431
|
sessionFile: sessionFileForIndex(0),
|
|
425
432
|
skills,
|
|
426
433
|
output: effectiveOutput,
|
|
427
|
-
modelOverride
|
|
434
|
+
modelOverride,
|
|
428
435
|
maxSubagentDepth,
|
|
429
436
|
worktreeSetupHook: deps.config.worktreeSetupHook,
|
|
430
437
|
worktreeSetupHookTimeoutMs: deps.config.worktreeSetupHookTimeoutMs,
|
|
@@ -485,7 +492,12 @@ async function runChainPath(data: ExecutionContextData, deps: ExecutorDeps): Pro
|
|
|
485
492
|
};
|
|
486
493
|
}
|
|
487
494
|
const id = randomUUID();
|
|
488
|
-
const asyncCtx = {
|
|
495
|
+
const asyncCtx = {
|
|
496
|
+
pi: deps.pi,
|
|
497
|
+
cwd: ctx.cwd,
|
|
498
|
+
currentSessionId: deps.state.currentSessionId!,
|
|
499
|
+
currentModelProvider: ctx.model?.provider,
|
|
500
|
+
};
|
|
489
501
|
const asyncChain = wrapChainTasksForFork(chainResult.requestedAsync.chain, params.context);
|
|
490
502
|
return executeAsyncChain(id, {
|
|
491
503
|
chain: asyncChain,
|
|
@@ -632,6 +644,7 @@ async function runForegroundParallelTasks(input: ForegroundParallelRunInput): Pr
|
|
|
632
644
|
maxSubagentDepth: input.maxSubagentDepths[index],
|
|
633
645
|
modelOverride: input.modelOverrides[index],
|
|
634
646
|
availableModels: input.availableModels,
|
|
647
|
+
preferredModelProvider: input.ctx.model?.provider,
|
|
635
648
|
skills: effectiveSkills === false ? [] : effectiveSkills,
|
|
636
649
|
onUpdate: input.onUpdate
|
|
637
650
|
? (progressUpdate) => {
|
|
@@ -707,13 +720,16 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
707
720
|
if (worktreeTaskCwdError) return buildParallelModeError(worktreeTaskCwdError);
|
|
708
721
|
}
|
|
709
722
|
|
|
723
|
+
const currentProvider = ctx.model?.provider;
|
|
710
724
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
711
725
|
provider: m.provider,
|
|
712
726
|
id: m.id,
|
|
713
727
|
fullId: `${m.provider}/${m.id}`,
|
|
714
728
|
}));
|
|
715
729
|
let taskTexts = tasks.map((t) => t.task);
|
|
716
|
-
const modelOverrides: (string | undefined)[] = tasks.map((t) =>
|
|
730
|
+
const modelOverrides: (string | undefined)[] = tasks.map((t, i) =>
|
|
731
|
+
resolveModelCandidate(t.model ?? agentConfigs[i]?.model, availableModels, currentProvider),
|
|
732
|
+
);
|
|
717
733
|
const skillOverrides: (string[] | false | undefined)[] = tasks.map((t) =>
|
|
718
734
|
normalizeSkillInput(t.skill),
|
|
719
735
|
);
|
|
@@ -722,7 +738,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
722
738
|
const behaviors = agentConfigs.map((c, i) =>
|
|
723
739
|
resolveStepBehavior(c, { skills: skillOverrides[i] }),
|
|
724
740
|
);
|
|
725
|
-
const availableSkills = discoverAvailableSkills(ctx.cwd);
|
|
741
|
+
const availableSkills = discoverAvailableSkills(params.cwd ?? ctx.cwd);
|
|
726
742
|
|
|
727
743
|
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
728
744
|
(tui, theme, _kb, done) =>
|
|
@@ -734,6 +750,7 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
734
750
|
undefined,
|
|
735
751
|
behaviors,
|
|
736
752
|
availableModels,
|
|
753
|
+
currentProvider,
|
|
737
754
|
availableSkills,
|
|
738
755
|
done,
|
|
739
756
|
"parallel",
|
|
@@ -761,7 +778,12 @@ async function runParallelPath(data: ExecutionContextData, deps: ExecutorDeps):
|
|
|
761
778
|
};
|
|
762
779
|
}
|
|
763
780
|
const id = randomUUID();
|
|
764
|
-
const asyncCtx = {
|
|
781
|
+
const asyncCtx = {
|
|
782
|
+
pi: deps.pi,
|
|
783
|
+
cwd: ctx.cwd,
|
|
784
|
+
currentSessionId: deps.state.currentSessionId!,
|
|
785
|
+
currentModelProvider: ctx.model?.provider,
|
|
786
|
+
};
|
|
765
787
|
const parallelTasks = tasks.map((t, i) => ({
|
|
766
788
|
agent: t.agent,
|
|
767
789
|
task: params.context === "fork" ? wrapForkTask(taskTexts[i]!) : taskTexts[i]!,
|
|
@@ -901,13 +923,18 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
901
923
|
};
|
|
902
924
|
}
|
|
903
925
|
|
|
926
|
+
const currentProvider = ctx.model?.provider;
|
|
904
927
|
const availableModels: ModelInfo[] = ctx.modelRegistry.getAvailable().map((m) => ({
|
|
905
928
|
provider: m.provider,
|
|
906
929
|
id: m.id,
|
|
907
930
|
fullId: `${m.provider}/${m.id}`,
|
|
908
931
|
}));
|
|
909
932
|
let task = params.task!;
|
|
910
|
-
let modelOverride: string | undefined =
|
|
933
|
+
let modelOverride: string | undefined = resolveModelCandidate(
|
|
934
|
+
(params.model as string | undefined) ?? agentConfig.model,
|
|
935
|
+
availableModels,
|
|
936
|
+
currentProvider,
|
|
937
|
+
);
|
|
911
938
|
let skillOverride: string[] | false | undefined = normalizeSkillInput(params.skill);
|
|
912
939
|
const rawOutput = params.output !== undefined ? params.output : agentConfig.output;
|
|
913
940
|
let effectiveOutput: string | false | undefined = rawOutput === true ? agentConfig.output : (rawOutput as string | false | undefined);
|
|
@@ -916,7 +943,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
916
943
|
|
|
917
944
|
if (params.clarify === true && ctx.hasUI) {
|
|
918
945
|
const behavior = resolveStepBehavior(agentConfig, { output: effectiveOutput, skills: skillOverride });
|
|
919
|
-
const availableSkills = discoverAvailableSkills(ctx.cwd);
|
|
946
|
+
const availableSkills = discoverAvailableSkills(params.cwd ?? ctx.cwd);
|
|
920
947
|
|
|
921
948
|
const result = await ctx.ui.custom<ChainClarifyResult>(
|
|
922
949
|
(tui, theme, _kb, done) =>
|
|
@@ -928,6 +955,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
928
955
|
undefined,
|
|
929
956
|
[behavior],
|
|
930
957
|
availableModels,
|
|
958
|
+
currentProvider,
|
|
931
959
|
availableSkills,
|
|
932
960
|
done,
|
|
933
961
|
"single",
|
|
@@ -954,7 +982,12 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
954
982
|
};
|
|
955
983
|
}
|
|
956
984
|
const id = randomUUID();
|
|
957
|
-
const asyncCtx = {
|
|
985
|
+
const asyncCtx = {
|
|
986
|
+
pi: deps.pi,
|
|
987
|
+
cwd: ctx.cwd,
|
|
988
|
+
currentSessionId: deps.state.currentSessionId!,
|
|
989
|
+
currentModelProvider: ctx.model?.provider,
|
|
990
|
+
};
|
|
958
991
|
return executeAsyncSingle(id, {
|
|
959
992
|
agent: params.agent!,
|
|
960
993
|
task: params.context === "fork" ? wrapForkTask(task) : task,
|
|
@@ -1009,6 +1042,7 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
|
|
|
1009
1042
|
onUpdate,
|
|
1010
1043
|
modelOverride,
|
|
1011
1044
|
availableModels,
|
|
1045
|
+
preferredModelProvider: currentProvider,
|
|
1012
1046
|
skills: effectiveSkills,
|
|
1013
1047
|
});
|
|
1014
1048
|
recordRun(params.agent!, cleanTask, r.exitCode, r.progressSummary?.durationMs ?? 0);
|