@piewf/cli 4.0.3 → 5.1.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/dist/src/bundles.js +2 -0
- package/dist/src/cli.d.ts +4 -0
- package/dist/src/cli.js +54 -8
- package/dist/src/doctor-cleanup.js +25 -6
- package/dist/src/doctor.d.ts +40 -0
- package/dist/src/doctor.js +150 -9
- package/dist/src/session-inspector.js +3 -1
- package/package.json +4 -4
package/dist/src/bundles.js
CHANGED
|
@@ -328,6 +328,8 @@ function roleMarkdown(role) {
|
|
|
328
328
|
metadata.push(`tools: ${JSON.stringify(role.tools)}`);
|
|
329
329
|
if (role.overrideSystemPrompt !== undefined)
|
|
330
330
|
metadata.push(`overrideSystemPrompt: ${String(role.overrideSystemPrompt)}`);
|
|
331
|
+
if (role.contextFiles !== undefined)
|
|
332
|
+
metadata.push(`contextFiles: ${JSON.stringify(role.contextFiles)}`);
|
|
331
333
|
if (role.disabledAgentResources !== undefined)
|
|
332
334
|
metadata.push(`disabledAgentResources: ${JSON.stringify(role.disabledAgentResources)}`);
|
|
333
335
|
metadata.push("---");
|
package/dist/src/cli.d.ts
CHANGED
|
@@ -15,5 +15,9 @@ export interface CliOptions extends DoctorOptions {
|
|
|
15
15
|
}
|
|
16
16
|
export declare function formatWorkflowCliHelp(fn: WorkflowCatalogFunction, command?: string): string;
|
|
17
17
|
export declare function parseWorkflowCliArgs(schema: JsonSchema, rawArgs: readonly string[]): Record<string, JsonValue>;
|
|
18
|
+
export declare function parseDoctorArgs(rawArgs: readonly string[]): {
|
|
19
|
+
role?: string;
|
|
20
|
+
prompt?: string;
|
|
21
|
+
};
|
|
18
22
|
export declare function parseDoctorCleanupArgs(rawArgs: readonly string[]): Required<Pick<DoctorCleanupOptions, "olderThanDays" | "yes">>;
|
|
19
23
|
export declare function runCli(args: readonly string[], options?: CliOptions, write?: (text: string) => void): Promise<number>;
|
package/dist/src/cli.js
CHANGED
|
@@ -218,6 +218,41 @@ function parseInspectArgs(rawArgs) {
|
|
|
218
218
|
}
|
|
219
219
|
return { ...(sessionId ? { sessionId } : {}), mode: failedOnly && mode === "tui" ? "summary" : mode, failedOnly };
|
|
220
220
|
}
|
|
221
|
+
export function parseDoctorArgs(rawArgs) {
|
|
222
|
+
let role;
|
|
223
|
+
let prompt;
|
|
224
|
+
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
225
|
+
const token = rawArgs[index];
|
|
226
|
+
const equals = token.indexOf("=");
|
|
227
|
+
const option = equals >= 0 ? token.slice(0, equals) : token;
|
|
228
|
+
if (option === "--role" || option === "--prompt") {
|
|
229
|
+
const value = equals >= 0 ? token.slice(equals + 1) : rawArgs[++index];
|
|
230
|
+
if (!value)
|
|
231
|
+
throw new Error(`Missing value for ${option}`);
|
|
232
|
+
if (option === "--role") {
|
|
233
|
+
if (role !== undefined)
|
|
234
|
+
throw new Error("--role may only be provided once");
|
|
235
|
+
role = value;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
if (prompt !== undefined)
|
|
239
|
+
throw new Error("--prompt may only be provided once");
|
|
240
|
+
prompt = value;
|
|
241
|
+
}
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (token === "--help" || token === "-h")
|
|
245
|
+
throw new Error("help");
|
|
246
|
+
if (token.startsWith("--"))
|
|
247
|
+
throw new Error(`Unknown doctor option: ${token}`);
|
|
248
|
+
if (role !== undefined)
|
|
249
|
+
throw new Error(`Unexpected argument: ${token}`);
|
|
250
|
+
role = token;
|
|
251
|
+
}
|
|
252
|
+
if (prompt !== undefined && role === undefined)
|
|
253
|
+
throw new Error("--prompt requires --role");
|
|
254
|
+
return { ...(role === undefined ? {} : { role }), ...(prompt === undefined ? {} : { prompt }) };
|
|
255
|
+
}
|
|
221
256
|
export function parseDoctorCleanupArgs(rawArgs) {
|
|
222
257
|
let olderThanDays = 90;
|
|
223
258
|
let yes = false;
|
|
@@ -360,8 +395,8 @@ import { homedir } from "node:os";
|
|
|
360
395
|
import { join } from "node:path";
|
|
361
396
|
import { pathToFileURL } from "node:url";
|
|
362
397
|
let cli;
|
|
363
|
-
try { cli = await import(
|
|
364
|
-
if (!cli) try { cli = await import(
|
|
398
|
+
try { cli = await import(pathToFileURL(join(process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent"), "npm", "node_modules", "@piewf/cli", "dist", "src", "cli.js")).href); } catch {}
|
|
399
|
+
if (!cli) try { cli = await import(import.meta.resolve("@piewf/cli")); } catch {}
|
|
365
400
|
if (cli) process.exitCode = await cli.runCli(["run", ${JSON.stringify(workflowName)}, ...process.argv.slice(2)]);
|
|
366
401
|
else { const result = spawnSync("piewf", ["run", ${JSON.stringify(workflowName)}, ...process.argv.slice(2)], { stdio: "inherit" }); if (result.error) { console.error("Could not resolve @piewf/cli; install it or put piewf on PATH."); process.exitCode = 1; } else process.exitCode = result.status ?? 1; }
|
|
367
402
|
`;
|
|
@@ -467,7 +502,7 @@ async function invokeWorkflow(fn, args, runtime, options, context) {
|
|
|
467
502
|
return; announcedRunId = runId; options.stderr(`Run ID: ${runId}\n`); };
|
|
468
503
|
const progress = new CliProgress(options.stderr, options.isTTY ?? process.stderr.isTTY, announceRunId);
|
|
469
504
|
try {
|
|
470
|
-
const result = await runtime.workflowTool.execute(randomUUID(), {
|
|
505
|
+
const result = await runtime.workflowTool.execute(randomUUID(), { name: fn.name, script: `return await ${fn.name}(args);`, args, foreground: true }, options.signal, (update) => { if (object(update) && object(update.details) && object(update.details.run))
|
|
471
506
|
progress.update(update.details.run); }, context);
|
|
472
507
|
const details = object(result.details) ? result.details : {};
|
|
473
508
|
const runId = typeof details.runId === "string" ? details.runId : undefined;
|
|
@@ -674,10 +709,21 @@ async function bundleWorkflowCli(rawArgs, options) {
|
|
|
674
709
|
}
|
|
675
710
|
export async function runCli(args, options = {}, write = (text) => { process.stdout.write(text); }) {
|
|
676
711
|
const stderr = options.stderr ?? ((text) => { process.stderr.write(text); });
|
|
677
|
-
if (args[0] === "doctor" && args
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
712
|
+
if (args[0] === "doctor" && args[1] !== "cleanup") {
|
|
713
|
+
if (args.slice(1).some((arg) => arg === "--help" || arg === "-h")) {
|
|
714
|
+
write("Usage: piewf doctor [role] [--role <role>] [--prompt <text>]\n");
|
|
715
|
+
return 0;
|
|
716
|
+
}
|
|
717
|
+
try {
|
|
718
|
+
const parsed = parseDoctorArgs(args.slice(1));
|
|
719
|
+
const report = await doctor({ ...options, ...parsed });
|
|
720
|
+
write(formatDoctorReport(report));
|
|
721
|
+
return doctorExitCode(report);
|
|
722
|
+
}
|
|
723
|
+
catch (error) {
|
|
724
|
+
stderr(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
725
|
+
return 1;
|
|
726
|
+
}
|
|
681
727
|
}
|
|
682
728
|
if (args[0] === "doctor" && args[1] === "cleanup") {
|
|
683
729
|
if (args.slice(2).some((arg) => arg === "--help" || arg === "-h")) {
|
|
@@ -735,7 +781,7 @@ export async function runCli(args, options = {}, write = (text) => { process.std
|
|
|
735
781
|
return 1;
|
|
736
782
|
}
|
|
737
783
|
}
|
|
738
|
-
write("Usage: piewf doctor | inspect [session-id] [--json|--summary] [--failed] | transcript <session-file> | bundle <workflow-name> [--name <command>] [--output <path>] [--force] | run <workflow-name> [workflow arguments] | export <workflow-name> [--name <command>] [--output <path>] [--force] [--bundle]\n");
|
|
784
|
+
write("Usage: piewf doctor [role] [--role <role>] [--prompt <text>] | inspect [session-id] [--json|--summary] [--failed] | transcript <session-file> | bundle <workflow-name> [--name <command>] [--output <path>] [--force] | run <workflow-name> [workflow arguments] | export <workflow-name> [--name <command>] [--output <path>] [--force] [--bundle]\n");
|
|
739
785
|
return 1;
|
|
740
786
|
}
|
|
741
787
|
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
@@ -50,9 +50,33 @@ function agentDefinition(value, label) { if (!object(value))
|
|
|
50
50
|
throw new Error(`${label} is invalid`); optionalString(value.prompt, `${label}.prompt`); optionalString(value.description, `${label}.description`); optionalString(value.model, `${label}.model`); if (value.thinking !== undefined && !["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(value.thinking))
|
|
51
51
|
throw new Error(`${label}.thinking is invalid`); if (value.tools !== undefined)
|
|
52
52
|
stringList(value.tools, `${label}.tools`); resourceExclusions(value.disabledAgentResources, `${label}.disabledAgentResources`); }
|
|
53
|
+
function optionalRole(value, label) {
|
|
54
|
+
if (value === undefined)
|
|
55
|
+
return;
|
|
56
|
+
if (typeof value === "string") {
|
|
57
|
+
if (!value.trim())
|
|
58
|
+
throw new Error(`${label} is invalid`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (!object(value) || typeof value.name !== "string" || !value.name.trim())
|
|
62
|
+
throw new Error(`${label} is invalid`);
|
|
63
|
+
for (const key of ["model", "description"])
|
|
64
|
+
if (value[key] !== undefined && value[key] !== null && typeof value[key] !== "string")
|
|
65
|
+
throw new Error(`${label}.${key} is invalid`);
|
|
66
|
+
if (value.thinking !== undefined && value.thinking !== null && !["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(value.thinking))
|
|
67
|
+
throw new Error(`${label}.thinking is invalid`);
|
|
68
|
+
if (value.tools !== undefined && value.tools !== null)
|
|
69
|
+
stringList(value.tools, `${label}.tools`);
|
|
70
|
+
if (value.overrideSystemPrompt !== undefined && value.overrideSystemPrompt !== null && typeof value.overrideSystemPrompt !== "boolean")
|
|
71
|
+
throw new Error(`${label}.overrideSystemPrompt is invalid`);
|
|
72
|
+
if (value.contextFiles !== undefined && value.contextFiles !== null)
|
|
73
|
+
stringList(value.contextFiles, `${label}.contextFiles`);
|
|
74
|
+
if (value.disabledAgentResources !== undefined && value.disabledAgentResources !== null)
|
|
75
|
+
resourceExclusions(value.disabledAgentResources, `${label}.disabledAgentResources`);
|
|
76
|
+
}
|
|
53
77
|
function validateScheduledOptions(value, label) { if (!object(value) || typeof value.label !== "string" || !value.label || typeof value.cwd !== "string" || !value.cwd)
|
|
54
78
|
throw new Error(`${label} is invalid`); optionalString(value.requestedLabel, `${label}.requestedLabel`); optionalString(value.parentBreadcrumb, `${label}.parentBreadcrumb`); stringList(value.tools, `${label}.tools`); optionalString(value.worktreeOwner, `${label}.worktreeOwner`); optionalString(value.model, `${label}.model`); if (value.thinking !== undefined && !["off", "minimal", "low", "medium", "high", "xhigh", "max"].includes(value.thinking))
|
|
55
|
-
throw new Error(`${label}.thinking is invalid`);
|
|
79
|
+
throw new Error(`${label}.thinking is invalid`); optionalRole(value.role, `${label}.role`); if (value.schema !== undefined)
|
|
56
80
|
validateSchema(value.schema, `${label}.schema`); if (value.retries !== undefined)
|
|
57
81
|
nonNegativeInteger(value.retries, `${label}.retries`); if (value.timeoutMs !== undefined && value.timeoutMs !== null)
|
|
58
82
|
positiveInteger(value.timeoutMs, `${label}.timeoutMs`); if (value.agentOptions !== undefined && (!object(value.agentOptions) || !jsonValue(value.agentOptions)))
|
|
@@ -212,11 +236,6 @@ function validateSnapshot(snapshot) {
|
|
|
212
236
|
throw new Error("Persisted launch snapshot is invalid");
|
|
213
237
|
if (snapshot.identityVersion !== undefined)
|
|
214
238
|
positiveInteger(snapshot.identityVersion, "Persisted snapshot identity version");
|
|
215
|
-
if (snapshot.launchKind !== undefined && !["inline", "function"].includes(snapshot.launchKind))
|
|
216
|
-
throw new Error("Persisted snapshot launch kind is invalid");
|
|
217
|
-
if (snapshot.launchKind === "function" && (typeof snapshot.functionName !== "string" || !snapshot.functionName))
|
|
218
|
-
throw new Error("Persisted snapshot function name is invalid");
|
|
219
|
-
optionalString(snapshot.functionName, "Persisted snapshot function name");
|
|
220
239
|
optionalString(snapshot.settingsPath, "Persisted snapshot settings path");
|
|
221
240
|
if (snapshot.settingsSources !== undefined) {
|
|
222
241
|
if (!object(snapshot.settingsSources) || typeof snapshot.settingsSources.concurrency !== "string" || typeof snapshot.settingsSources.modelAliases !== "string" || typeof snapshot.settingsSources.disabledAgentResources !== "string")
|
package/dist/src/doctor.d.ts
CHANGED
|
@@ -27,8 +27,42 @@ export interface DoctorTrust {
|
|
|
27
27
|
trusted: boolean;
|
|
28
28
|
source: string;
|
|
29
29
|
}
|
|
30
|
+
export interface DoctorRoleInspection {
|
|
31
|
+
role: string;
|
|
32
|
+
path: string;
|
|
33
|
+
model: {
|
|
34
|
+
provider: string;
|
|
35
|
+
model: string;
|
|
36
|
+
thinking?: string;
|
|
37
|
+
inherited?: boolean;
|
|
38
|
+
};
|
|
39
|
+
tools: readonly string[];
|
|
40
|
+
resources: {
|
|
41
|
+
skills: readonly string[];
|
|
42
|
+
extensions: readonly string[];
|
|
43
|
+
excludedSkills: readonly string[];
|
|
44
|
+
excludedExtensions: readonly string[];
|
|
45
|
+
unmatchedSkills: readonly string[];
|
|
46
|
+
unmatchedExtensions: readonly string[];
|
|
47
|
+
};
|
|
48
|
+
systemPrompt: {
|
|
49
|
+
probe: string;
|
|
50
|
+
expandedProbe: string;
|
|
51
|
+
text: string;
|
|
52
|
+
source?: string;
|
|
53
|
+
};
|
|
54
|
+
setup: {
|
|
55
|
+
hooks: readonly string[];
|
|
56
|
+
diagnostics: readonly DoctorDiagnostic[];
|
|
57
|
+
};
|
|
58
|
+
}
|
|
30
59
|
export interface DoctorPiState {
|
|
31
60
|
trust: DoctorTrust;
|
|
61
|
+
model?: {
|
|
62
|
+
provider: string;
|
|
63
|
+
model: string;
|
|
64
|
+
thinking?: string;
|
|
65
|
+
};
|
|
32
66
|
activeTools: readonly string[];
|
|
33
67
|
knownModels: readonly string[];
|
|
34
68
|
availableModels: readonly string[];
|
|
@@ -48,16 +82,22 @@ export interface DoctorReport {
|
|
|
48
82
|
settingsSources: WorkflowSettingsSources;
|
|
49
83
|
trust: DoctorTrust;
|
|
50
84
|
activeTools: readonly string[];
|
|
85
|
+
piExtensions: readonly string[];
|
|
86
|
+
piSkills: readonly string[];
|
|
51
87
|
roles: readonly DoctorRole[];
|
|
52
88
|
functions: readonly DoctorFunction[];
|
|
53
89
|
resourcePolicy: AgentResourcePolicy;
|
|
54
90
|
modelAliases: readonly WorkflowCatalogModelAlias[];
|
|
91
|
+
roleTarget?: string;
|
|
92
|
+
roleInspection?: DoctorRoleInspection;
|
|
55
93
|
diagnostics: readonly DoctorDiagnostic[];
|
|
56
94
|
}
|
|
57
95
|
export interface DoctorOptions {
|
|
58
96
|
cwd?: string;
|
|
59
97
|
agentDir?: string;
|
|
60
98
|
settingsPath?: string;
|
|
99
|
+
role?: string;
|
|
100
|
+
prompt?: string;
|
|
61
101
|
discoverPi?: (cwd: string, agentDir: string) => Promise<DoctorPiState>;
|
|
62
102
|
activeTools?: readonly string[];
|
|
63
103
|
registry?: WorkflowRegistryApi;
|
package/dist/src/doctor.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFileSync, readdirSync, realpathSync } from "node:fs";
|
|
|
2
2
|
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
3
3
|
import { InMemoryCredentialStore } from "@earendil-works/pi-ai";
|
|
4
4
|
import { ModelRuntime, createAgentSessionFromServices, createAgentSessionServices, getAgentDir, hasTrustRequiringProjectResources, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import { DEFAULT_SETTINGS, loadSettings, resolveAgentResourcePolicy, resolveWorkflowSettings, resolveModelReference, parseThinking, parseRoleMarkdown, registeredWorkflowFunctions, registeredWorkflowRoleDirectoryRegistrations, workflowRoleDirectories, workflowProjectSettingsPath, workflowSettingsPath, } from "pi-extensible-workflows";
|
|
5
|
+
import { DEFAULT_SETTINGS, createLocalPiSession, loadSettings, prepareAgentSetupForInspection, resolveAgentResourcePolicy, resolveWorkflowSettings, resolveModelReference, parseThinking, parseRoleMarkdown, registeredWorkflowFunctions, registeredWorkflowRoleDirectoryRegistrations, workflowRoleDirectories, workflowProjectSettingsPath, workflowSettingsPath, } from "pi-extensible-workflows";
|
|
6
6
|
import { loadingRegistry } from "pi-extensible-workflows";
|
|
7
7
|
import { disabledResources, unmatchedResourcePatterns } from "pi-extensible-workflows";
|
|
8
8
|
const THINKING_HINT = "Use off, minimal, low, medium, high, xhigh, or max.";
|
|
@@ -86,6 +86,7 @@ async function discoverPi(cwd, agentDir) {
|
|
|
86
86
|
const skills = services.resourceLoader.getSkills().skills;
|
|
87
87
|
return {
|
|
88
88
|
trust: { required, trusted, source },
|
|
89
|
+
model: { provider: model.provider, model: model.id, thinking: session.thinkingLevel },
|
|
89
90
|
activeTools,
|
|
90
91
|
knownModels: allModels.map(({ provider, id }) => `${provider}/${id}`),
|
|
91
92
|
availableModels: availableModels.map(({ provider, id }) => `${provider}/${id}`),
|
|
@@ -184,7 +185,7 @@ function inspectRole(path, activeTools, knownModels, availableModels, diagnostic
|
|
|
184
185
|
validateModel(definition.model, knownModels, availableModels, path, diagnostics, aliases, dynamicAliases, settingsPath);
|
|
185
186
|
for (const tool of definition.tools ?? [])
|
|
186
187
|
if (!activeTools.has(tool))
|
|
187
|
-
diagnostics.push(diagnostic("error", "ROLE_TOOL_INACTIVE", `Tool is unknown or inactive: ${tool}`, path, "Use a tool listed under
|
|
188
|
+
diagnostics.push(diagnostic("error", "ROLE_TOOL_INACTIVE", `Tool is unknown or inactive: ${tool}`, path, "Use a tool listed under Pi active tools or enable its Pi extension."));
|
|
188
189
|
return definition;
|
|
189
190
|
}
|
|
190
191
|
function matchResourcePolicy(policy, pi) {
|
|
@@ -192,6 +193,60 @@ function matchResourcePolicy(policy, pi) {
|
|
|
192
193
|
const skills = [...new Set(pi.skills ?? [])];
|
|
193
194
|
return { ...policy, excludedSkills: disabledResources(policy.effective.skills, skills), excludedExtensions: disabledResources(policy.effective.extensions, extensions), unmatchedSkills: unmatchedResourcePatterns(policy.effective.skills, skills), unmatchedExtensions: unmatchedResourcePatterns(policy.effective.extensions, extensions) };
|
|
194
195
|
}
|
|
196
|
+
async function inspectRoleSession(cwd, agentDir, roleName, definition, rolePath, basePolicy, rootModel, activeTools, aliases, knownModels, availableModels, settingsPath, prompt, hooks, diagnostics) {
|
|
197
|
+
const setupDiagnostics = [];
|
|
198
|
+
const signal = new AbortController().signal;
|
|
199
|
+
const transport = { id: "doctor-local", createSession: async () => { throw new Error("Doctor inspection does not create transport sessions"); } };
|
|
200
|
+
const run = { cwd, sessionId: "doctor", runId: "doctor", workflow: { name: "doctor" }, args: null, signal };
|
|
201
|
+
const root = { cwd, model: { ...rootModel }, tools: new Set(activeTools), agentDefinitions: { [roleName]: definition }, agentDir, modelAliases: aliases, knownModels, availableModels, settingsPath, agentSetupHooks: hooks, agentResourcePolicy: () => structuredClone(basePolicy), runContext: run };
|
|
202
|
+
const options = { label: roleName, workflowName: "doctor", role: roleName };
|
|
203
|
+
let prepared;
|
|
204
|
+
try {
|
|
205
|
+
prepared = await prepareAgentSetupForInspection(root, prompt, options, transport);
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
setupDiagnostics.push(diagnostic("error", "ROLE_INSPECTION", error instanceof Error ? error.message : String(error), rolePath));
|
|
209
|
+
diagnostics.push(...setupDiagnostics);
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
if (prepared.failure) {
|
|
213
|
+
const error = prepared.failure.error;
|
|
214
|
+
const code = prepared.failure.hook ? "ROLE_SETUP_HOOK" : "ROLE_INSPECTION";
|
|
215
|
+
setupDiagnostics.push(diagnostic("error", code, `${prepared.failure.hook ? `Role setup hook ${prepared.failure.hook} failed: ` : ""}${error instanceof Error ? error.message : String(error)}`, prepared.failure.hook ?? rolePath));
|
|
216
|
+
diagnostics.push(...setupDiagnostics);
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
const session = await (async () => { try {
|
|
220
|
+
return await createLocalPiSession({ ...prepared.setup.sessionInput, sessionManager: SessionManager.inMemory() });
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
setupDiagnostics.push(diagnostic("error", "ROLE_INSPECTION", error instanceof Error ? error.message : String(error), rolePath));
|
|
224
|
+
return undefined;
|
|
225
|
+
} })();
|
|
226
|
+
if (!session) {
|
|
227
|
+
diagnostics.push(...setupDiagnostics);
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
const promptResult = await session.preparePrompt(prompt);
|
|
232
|
+
const resources = session.getResourceInspection();
|
|
233
|
+
const state = session.agent?.state;
|
|
234
|
+
const inherited = prepared.setup.sessionInput.model.provider === rootModel.provider && prepared.setup.sessionInput.model.model === rootModel.model && prepared.setup.sessionInput.model.thinking === rootModel.thinking;
|
|
235
|
+
const actualModel = session.model?.provider && (session.model.model ?? session.model.id) ? { provider: session.model.provider, model: session.model.model ?? session.model.id ?? prepared.setup.sessionInput.model.model, ...(session.thinkingLevel ? { thinking: session.thinkingLevel } : {}), ...(inherited ? { inherited: true } : {}) } : { ...prepared.setup.sessionInput.model, ...(inherited ? { inherited: true } : {}) };
|
|
236
|
+
const policy = prepared.setup.sessionInput.resourcePolicy ?? basePolicy;
|
|
237
|
+
for (const item of [...resources.diagnostics, ...promptResult.diagnostics])
|
|
238
|
+
setupDiagnostics.push(diagnostic(item.type === "error" ? "error" : "warning", "ROLE_INSPECTION", item.message, item.source));
|
|
239
|
+
return { role: roleName, path: rolePath, model: actualModel, tools: state?.tools.map(({ name }) => name) ?? [...prepared.setup.sessionInput.tools], resources: { skills: resources.skills, extensions: resources.extensions, excludedSkills: policy.excludedSkills ?? [], excludedExtensions: policy.excludedExtensions ?? [], unmatchedSkills: policy.unmatchedSkills, unmatchedExtensions: policy.unmatchedExtensions }, systemPrompt: { probe: prompt, expandedProbe: promptResult.expandedPrompt, text: promptResult.systemPrompt, ...(resources.systemPromptSource ? { source: resources.systemPromptSource } : {}) }, setup: { hooks: prepared.summary.hookNames, diagnostics: setupDiagnostics } };
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
setupDiagnostics.push(diagnostic("error", "ROLE_INSPECTION", error instanceof Error ? error.message : String(error), rolePath));
|
|
243
|
+
diagnostics.push(...setupDiagnostics);
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
finally {
|
|
247
|
+
session.dispose();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
195
250
|
function resourcePolicySource(settingsSource) { return settingsSource; }
|
|
196
251
|
export async function doctor(options = {}) {
|
|
197
252
|
const cwd = canonical(options.cwd ?? process.cwd());
|
|
@@ -335,6 +390,40 @@ export async function doctor(options = {}) {
|
|
|
335
390
|
else
|
|
336
391
|
definitions.delete(name);
|
|
337
392
|
}
|
|
393
|
+
let roleInspection;
|
|
394
|
+
if (options.role !== undefined) {
|
|
395
|
+
const activeRole = roles.find(({ name, active }) => name === options.role && active);
|
|
396
|
+
const definition = activeRole ? definitions.get(options.role) : undefined;
|
|
397
|
+
if (!activeRole || !definition)
|
|
398
|
+
diagnostics.push(diagnostic("error", "ROLE_NOT_FOUND", `Active role not found: ${options.role}`, options.role));
|
|
399
|
+
else {
|
|
400
|
+
const rootReference = pi.model ? `${pi.model.provider}/${pi.model.model}` : pi.availableModels[0] ?? pi.knownModels[0];
|
|
401
|
+
if (!rootReference)
|
|
402
|
+
diagnostics.push(diagnostic("error", "ROLE_INSPECTION_MODEL", "Cannot inspect a role because Pi has no registered model"));
|
|
403
|
+
else {
|
|
404
|
+
let rootModel;
|
|
405
|
+
try {
|
|
406
|
+
if (pi.model) {
|
|
407
|
+
const thinking = parseThinking(pi.model.thinking);
|
|
408
|
+
rootModel = { provider: pi.model.provider, model: pi.model.model, ...(thinking ? { thinking } : {}) };
|
|
409
|
+
}
|
|
410
|
+
else
|
|
411
|
+
rootModel = resolveModelReference(rootReference, aliases, knownModels, settingsPath);
|
|
412
|
+
let roleAliases = aliases;
|
|
413
|
+
if (definition.model && isDynamicModelAlias(definition.model, dynamicAliases)) {
|
|
414
|
+
const dynamic = await registry.resolveModelAliases({ cwd, projectTrusted: pi.trust.trusted, rootModel, knownModels, availableModels, signal: new AbortController().signal });
|
|
415
|
+
roleAliases = { ...aliases, ...dynamic };
|
|
416
|
+
}
|
|
417
|
+
roleInspection = await inspectRoleSession(cwd, agentDir, options.role, definition, activeRole.path, resourcePolicy, rootModel, [...activeTools], roleAliases, knownModels, availableModels, settingsPath, options.prompt ?? "", registry.agentSetupHooks(), diagnostics);
|
|
418
|
+
if (roleInspection)
|
|
419
|
+
diagnostics.push(...roleInspection.setup.diagnostics);
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
diagnostics.push(diagnostic("error", "ROLE_INSPECTION_MODEL", error instanceof Error ? error.message : String(error), activeRole.path));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
338
427
|
const functions = [];
|
|
339
428
|
for (const [name, fn] of Object.entries(pi.functions).sort(([left], [right]) => left.localeCompare(right))) {
|
|
340
429
|
functions.push({ name, description: fn.description, valid: true });
|
|
@@ -342,11 +431,55 @@ export async function doctor(options = {}) {
|
|
|
342
431
|
const severityOrder = { error: 0, warning: 1 };
|
|
343
432
|
diagnostics.sort((left, right) => severityOrder[left.severity] - severityOrder[right.severity] || (left.source ?? "").localeCompare(right.source ?? "") || left.code.localeCompare(right.code) || left.message.localeCompare(right.message));
|
|
344
433
|
roles.sort((left, right) => left.name.localeCompare(right.name) || left.scope.localeCompare(right.scope));
|
|
345
|
-
return { cwd, agentDir, settingsPath, settings, settingsSources, trust: pi.trust, activeTools: [...activeTools].sort(), roles, functions, modelAliases, resourcePolicy, diagnostics };
|
|
434
|
+
return { cwd, agentDir, settingsPath, settings, settingsSources, trust: pi.trust, activeTools: [...activeTools].sort(), piExtensions: [...new Set((pi.extensions ?? []).map(canonical))].sort(), piSkills: [...new Set(pi.skills ?? [])].sort(), roles, functions, modelAliases, resourcePolicy, ...(options.role !== undefined ? { roleTarget: options.role } : {}), ...(roleInspection ? { roleInspection } : {}), diagnostics };
|
|
346
435
|
}
|
|
347
436
|
function count(report, severity) { return report.diagnostics.filter((item) => item.severity === severity).length; }
|
|
348
437
|
export function doctorExitCode(report) { return count(report, "error") > 0 ? 1 : 0; }
|
|
438
|
+
function exposedValues(values, excluded) {
|
|
439
|
+
const disabled = new Set(excluded);
|
|
440
|
+
return values.filter((value) => !disabled.has(value));
|
|
441
|
+
}
|
|
442
|
+
function nestedValues(label, values) {
|
|
443
|
+
return [`- ${label}:`, ...(values.length ? values.map((value) => ` - \`${value}\``) : [" - (none)"])];
|
|
444
|
+
}
|
|
445
|
+
function roleInspectionLines(inspection) {
|
|
446
|
+
return [
|
|
447
|
+
`- Role: \`${inspection.role}\` - \`${inspection.path}\``,
|
|
448
|
+
`- Model: \`${inspection.model.provider}/${inspection.model.model}\` (${inspection.model.inherited ? "inherited, " : ""}${inspection.model.thinking ?? "off"})`,
|
|
449
|
+
...nestedValues("Tools", inspection.tools),
|
|
450
|
+
...nestedValues("Effective skills", inspection.resources.skills),
|
|
451
|
+
...nestedValues("Effective extensions", inspection.resources.extensions),
|
|
452
|
+
...nestedValues("Excluded skills", inspection.resources.excludedSkills),
|
|
453
|
+
...nestedValues("Excluded extensions", inspection.resources.excludedExtensions),
|
|
454
|
+
...nestedValues("Unmatched skills", inspection.resources.unmatchedSkills),
|
|
455
|
+
...nestedValues("Unmatched extensions", inspection.resources.unmatchedExtensions),
|
|
456
|
+
`- Prompt probe: ${inspection.systemPrompt.probe ? JSON.stringify(inspection.systemPrompt.probe) : "empty"}`,
|
|
457
|
+
`- Expanded probe: ${JSON.stringify(inspection.systemPrompt.expandedProbe)}`,
|
|
458
|
+
`- System prompt source: ${inspection.systemPrompt.source ?? "(none)"}`,
|
|
459
|
+
"### Final system prompt",
|
|
460
|
+
"```",
|
|
461
|
+
inspection.systemPrompt.text,
|
|
462
|
+
"```",
|
|
463
|
+
...nestedValues("Applied setup hooks", inspection.setup.hooks),
|
|
464
|
+
`- Setup diagnostics: ${String(inspection.setup.diagnostics.length)}`,
|
|
465
|
+
];
|
|
466
|
+
}
|
|
349
467
|
export function formatDoctorReport(report) {
|
|
468
|
+
if (report.roleInspection || report.roleTarget !== undefined) {
|
|
469
|
+
const lines = [
|
|
470
|
+
"# pi-extensible-workflows doctor",
|
|
471
|
+
"",
|
|
472
|
+
"## Role inspection",
|
|
473
|
+
...(report.roleInspection ? roleInspectionLines(report.roleInspection) : [`- Role: \`${report.roleTarget ?? "(unknown)"}\``, "- Inspection unavailable"]),
|
|
474
|
+
"",
|
|
475
|
+
"## Diagnostics",
|
|
476
|
+
...(report.diagnostics.length ? report.diagnostics.map((item) => `- [${item.severity}] ${item.code}${item.source ? ` \`${item.source}\`` : ""}: ${item.message}${item.hint ? ` Fix: ${item.hint}` : ""}`) : ["- [ok] No diagnostics"]),
|
|
477
|
+
"",
|
|
478
|
+
"## Summary",
|
|
479
|
+
`- ${String(count(report, "error"))} error(s), ${String(count(report, "warning"))} warning(s)`,
|
|
480
|
+
];
|
|
481
|
+
return `${lines.join("\n")}\n`;
|
|
482
|
+
}
|
|
350
483
|
const lines = [
|
|
351
484
|
"# pi-extensible-workflows doctor",
|
|
352
485
|
"",
|
|
@@ -361,7 +494,16 @@ export function formatDoctorReport(report) {
|
|
|
361
494
|
"## Trust/resources",
|
|
362
495
|
`- [${report.trust.trusted ? "ok" : "warning"}] ${report.trust.source}`,
|
|
363
496
|
"",
|
|
364
|
-
"##
|
|
497
|
+
"## Pi active tools",
|
|
498
|
+
...(report.activeTools.length ? report.activeTools.map((tool) => `- \`${tool}\``) : ["- None resolved"]),
|
|
499
|
+
"",
|
|
500
|
+
"## Pi active extensions",
|
|
501
|
+
...(report.piExtensions.length ? report.piExtensions.map((extension) => `- \`${extension}\``) : ["- None resolved"]),
|
|
502
|
+
"",
|
|
503
|
+
"## Pi active skills",
|
|
504
|
+
...(report.piSkills.length ? report.piSkills.map((skill) => `- \`${skill}\``) : ["- None resolved"]),
|
|
505
|
+
"",
|
|
506
|
+
"## Workflow agent resources (after disabledAgentResources)",
|
|
365
507
|
`- Global settings: \`${report.resourcePolicy.globalSettingsPath}\``,
|
|
366
508
|
`- Global skills: ${report.resourcePolicy.global.skills.join(", ") || "(none)"}`,
|
|
367
509
|
`- Global extensions: ${report.resourcePolicy.global.extensions.join(", ") || "(none)"}`,
|
|
@@ -370,14 +512,13 @@ export function formatDoctorReport(report) {
|
|
|
370
512
|
`- Project extensions: ${report.resourcePolicy.project.extensions.join(", ") || "(none)"}`,
|
|
371
513
|
`- Effective skills: ${report.resourcePolicy.effective.skills.join(", ") || "(none)"}`,
|
|
372
514
|
`- Effective extensions: ${report.resourcePolicy.effective.extensions.join(", ") || "(none)"}`,
|
|
373
|
-
|
|
374
|
-
|
|
515
|
+
...nestedValues("Exposed skills", exposedValues(report.piSkills, report.resourcePolicy.excludedSkills ?? [])),
|
|
516
|
+
...nestedValues("Exposed extensions", exposedValues(report.piExtensions, report.resourcePolicy.excludedExtensions ?? [])),
|
|
517
|
+
...nestedValues("Disabled skills", report.resourcePolicy.excludedSkills ?? []),
|
|
518
|
+
...nestedValues("Disabled extensions", report.resourcePolicy.excludedExtensions ?? []),
|
|
375
519
|
`- Unmatched skills: ${report.resourcePolicy.unmatchedSkills.join(", ") || "(none)"}`,
|
|
376
520
|
`- Unmatched extensions: ${report.resourcePolicy.unmatchedExtensions.join(", ") || "(none)"}`,
|
|
377
521
|
"",
|
|
378
|
-
"## Active tools",
|
|
379
|
-
...(report.activeTools.length ? report.activeTools.map((tool) => `- \`${tool}\``) : ["- None resolved"]),
|
|
380
|
-
"",
|
|
381
522
|
"## Roles",
|
|
382
523
|
...(report.roles.length ? report.roles.map((role) => `- \`${role.name}\` (${role.scope}, ${role.active ? "active" : role.overriddenBy ? `overridden by ${role.overriddenBy}` : "inactive: project untrusted"}) - \`${role.path}\`${role.extension ? `; ${extensionLabel(role.extension)} role directory "${dirname(role.path)}"` : ""}${role.overrides ? `; overrides \`${role.overrides}\`` : ""}`) : ["- None found"]),
|
|
383
524
|
"",
|
|
@@ -46,6 +46,8 @@ export function transcriptLines(entries) {
|
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
export function transcriptFileLines(path) {
|
|
49
|
+
if (!existsSync(path))
|
|
50
|
+
throw new Error(`Transcript file not found: ${path}`);
|
|
49
51
|
return transcriptLines(SessionManager.open(path).buildContextEntries());
|
|
50
52
|
}
|
|
51
53
|
function mergedModels(groups) {
|
|
@@ -215,7 +217,7 @@ export async function loadSessionReport(path, home = homedir()) {
|
|
|
215
217
|
const args = call.arguments;
|
|
216
218
|
const agents = loaded ? await Promise.all(loaded.run.agents.map(agentReport)) : [];
|
|
217
219
|
const models = mergedModels(agents.flatMap(({ attempts }) => attempts.map(({ models: attemptModels }) => attemptModels)));
|
|
218
|
-
const name = loaded?.run.workflowName ?? (typeof args.
|
|
220
|
+
const name = loaded?.run.workflowName ?? (typeof args.name === "string" ? args.name : "workflow");
|
|
219
221
|
const description = typeof args.description === "string" ? args.description : loaded?.snapshot.metadata.description;
|
|
220
222
|
const script = typeof args.script === "string" && args.script.trim() ? args.script : loaded?.snapshot.script;
|
|
221
223
|
let staticCalls = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@piewf/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.1",
|
|
4
4
|
"description": "CLI for pi-extensible-workflows",
|
|
5
5
|
"homepage": "https://vekexasia.github.io/pi-extensible-workflows/",
|
|
6
6
|
"repository": {
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"typebox": "*"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@earendil-works/pi-ai": "0.
|
|
36
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
35
|
+
"@earendil-works/pi-ai": "0.82.1",
|
|
36
|
+
"@earendil-works/pi-coding-agent": "0.82.1",
|
|
37
37
|
"@eslint/js": "10.0.1",
|
|
38
38
|
"@types/node": "24.12.4",
|
|
39
39
|
"eslint": "10.0.3",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
},
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"pi-extensible-workflows": "
|
|
48
|
+
"pi-extensible-workflows": "5.1.1"
|
|
49
49
|
},
|
|
50
50
|
"publishConfig": {
|
|
51
51
|
"access": "public"
|