pi-extensible-workflows 0.3.0 → 1.0.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/README.md +12 -3
- package/dist/src/agent-execution.d.ts +81 -8
- package/dist/src/agent-execution.js +421 -71
- package/dist/src/doctor.d.ts +4 -1
- package/dist/src/doctor.js +56 -11
- package/dist/src/index.d.ts +217 -22
- package/dist/src/index.js +1416 -337
- package/dist/src/persistence.d.ts +26 -1
- package/dist/src/persistence.js +100 -6
- package/dist/src/session-inspector.d.ts +13 -1
- package/dist/src/session-inspector.js +22 -4
- package/dist/src/workflow-evals.js +1 -1
- package/package.json +1 -1
- package/skills/pi-extensible-workflows/SKILL.md +9 -5
- package/src/agent-execution.ts +350 -83
- package/src/doctor.ts +59 -12
- package/src/index.ts +1231 -360
- package/src/persistence.ts +76 -7
- package/src/session-inspector.ts +25 -7
- package/src/workflow-evals.ts +1 -1
package/src/doctor.ts
CHANGED
|
@@ -14,12 +14,17 @@ import {
|
|
|
14
14
|
DEFAULT_SETTINGS,
|
|
15
15
|
loadSettings,
|
|
16
16
|
parseModelReference,
|
|
17
|
+
resolveAgentResourcePolicy,
|
|
18
|
+
resolveModelReference,
|
|
17
19
|
parseRoleMarkdown,
|
|
18
20
|
preflight,
|
|
19
21
|
registeredWorkflowDefinitions,
|
|
20
22
|
workflowRoleDirectories,
|
|
23
|
+
workflowProjectSettingsPath,
|
|
21
24
|
workflowSettingsPath,
|
|
22
25
|
WorkflowError,
|
|
26
|
+
type AgentResourceExclusions,
|
|
27
|
+
type AgentResourcePolicy,
|
|
23
28
|
type WorkflowScriptDefinition,
|
|
24
29
|
type WorkflowSettings,
|
|
25
30
|
} from "./index.js";
|
|
@@ -36,6 +41,8 @@ export interface DoctorPiState {
|
|
|
36
41
|
knownModels: readonly string[];
|
|
37
42
|
availableModels: readonly string[];
|
|
38
43
|
extensionErrors: readonly { path?: string; message: string }[];
|
|
44
|
+
extensions?: readonly string[];
|
|
45
|
+
skills?: readonly string[];
|
|
39
46
|
workflows: Readonly<Record<string, WorkflowScriptDefinition>>;
|
|
40
47
|
}
|
|
41
48
|
export interface DoctorReport {
|
|
@@ -47,6 +54,7 @@ export interface DoctorReport {
|
|
|
47
54
|
activeTools: readonly string[];
|
|
48
55
|
roles: readonly DoctorRole[];
|
|
49
56
|
workflows: readonly DoctorWorkflow[];
|
|
57
|
+
resourcePolicy: AgentResourcePolicy;
|
|
50
58
|
diagnostics: readonly DoctorDiagnostic[];
|
|
51
59
|
}
|
|
52
60
|
export interface DoctorOptions {
|
|
@@ -107,7 +115,7 @@ async function discoverPi(cwd: string, agentDir: string): Promise<DoctorPiState>
|
|
|
107
115
|
agentDir,
|
|
108
116
|
settingsManager,
|
|
109
117
|
modelRuntime,
|
|
110
|
-
resourceLoaderOptions: {
|
|
118
|
+
resourceLoaderOptions: { noPromptTemplates: true, noThemes: true, noContextFiles: true },
|
|
111
119
|
resourceLoaderReloadOptions: { resolveProjectTrust: async () => trusted },
|
|
112
120
|
});
|
|
113
121
|
const allModels = services.modelRuntime.getModels();
|
|
@@ -117,11 +125,14 @@ async function discoverPi(cwd: string, agentDir: string): Promise<DoctorPiState>
|
|
|
117
125
|
const { session } = await createAgentSessionFromServices({ services, sessionManager: SessionManager.inMemory(), model });
|
|
118
126
|
const activeTools = session.agent.state.tools.map(({ name }) => name).filter((name) => name !== "workflow" && name !== "workflow_respond" && name !== "workflow_catalog");
|
|
119
127
|
const extensions = services.resourceLoader.getExtensions();
|
|
128
|
+
const skills = services.resourceLoader.getSkills().skills;
|
|
120
129
|
return {
|
|
121
130
|
trust: { required, trusted, source },
|
|
122
131
|
activeTools,
|
|
123
132
|
knownModels: allModels.map(({ provider, id }) => `${provider}/${id}`),
|
|
124
133
|
availableModels: availableModels.map(({ provider, id }) => `${provider}/${id}`),
|
|
134
|
+
extensions: extensions.extensions.map(({ resolvedPath }) => resolvedPath),
|
|
135
|
+
skills: skills.map(({ name }) => name),
|
|
125
136
|
extensionErrors: [
|
|
126
137
|
...extensions.errors.map(({ path, error }) => ({ path, message: error })),
|
|
127
138
|
...services.diagnostics.filter(({ type }) => type === "error").map(({ message }) => ({ message })),
|
|
@@ -147,10 +158,13 @@ function roleFilesFrom(dirs: readonly string[]): string[] {
|
|
|
147
158
|
function diagnostic(severity: DoctorSeverity, code: string, message: string, source?: string, hint?: string): DoctorDiagnostic {
|
|
148
159
|
return { severity, code, message, ...(source ? { source } : {}), ...(hint ? { hint } : {}) };
|
|
149
160
|
}
|
|
150
|
-
|
|
151
|
-
|
|
161
|
+
function emptyResourcePolicy(globalSettingsPath: string, cwd: string, projectTrusted: boolean): AgentResourcePolicy {
|
|
162
|
+
const empty: AgentResourceExclusions = { skills: [], extensions: [] };
|
|
163
|
+
return { globalSettingsPath, projectSettingsPath: workflowProjectSettingsPath(cwd), projectTrusted, global: empty, project: empty, effective: empty, unmatchedSkills: [], unmatchedExtensions: [] };
|
|
164
|
+
}
|
|
165
|
+
function validateModel(value: string, known: ReadonlySet<string>, available: ReadonlySet<string>, source: string, diagnostics: DoctorDiagnostic[], aliases: Readonly<Record<string, string>>, settingsPath: string): void {
|
|
152
166
|
try {
|
|
153
|
-
const parsed =
|
|
167
|
+
const parsed = resolveModelReference(value, aliases, known, settingsPath);
|
|
154
168
|
const name = `${parsed.provider}/${parsed.model}`;
|
|
155
169
|
if (!known.has(name) || !available.has(name)) diagnostics.push(diagnostic("warning", "MODEL_UNAVAILABLE", `Model is valid-shaped but unavailable: ${name}`, source));
|
|
156
170
|
} catch (error) {
|
|
@@ -158,9 +172,9 @@ function validateModel(value: string, known: ReadonlySet<string>, available: Rea
|
|
|
158
172
|
}
|
|
159
173
|
}
|
|
160
174
|
|
|
161
|
-
function inspectRole(path: string, activeTools: ReadonlySet<string>, knownModels: ReadonlySet<string>, availableModels: ReadonlySet<string>, diagnostics: DoctorDiagnostic[]): AgentDefinition | undefined {
|
|
175
|
+
function inspectRole(path: string, activeTools: ReadonlySet<string>, knownModels: ReadonlySet<string>, availableModels: ReadonlySet<string>, diagnostics: DoctorDiagnostic[], aliases: Readonly<Record<string, string>>, settingsPath: string): AgentDefinition | undefined {
|
|
162
176
|
let definition: AgentDefinition;
|
|
163
|
-
try { definition = parseRoleMarkdown(readFileSync(path, "utf8"), true); }
|
|
177
|
+
try { definition = parseRoleMarkdown(readFileSync(path, "utf8"), true, path); }
|
|
164
178
|
catch (error) {
|
|
165
179
|
diagnostics.push(diagnostic("error", "ROLE_FRONTMATTER", (error as Error).message, path, "Fix the role YAML frontmatter."));
|
|
166
180
|
return undefined;
|
|
@@ -169,7 +183,7 @@ function inspectRole(path: string, activeTools: ReadonlySet<string>, knownModels
|
|
|
169
183
|
if (body.trim() === "") diagnostics.push(diagnostic("warning", "ROLE_BODY_EMPTY", "Role body is empty", path));
|
|
170
184
|
if (Buffer.byteLength(body) > 50 * 1024) diagnostics.push(diagnostic("warning", "ROLE_BODY_LARGE", "Role body exceeds 50KB", path));
|
|
171
185
|
if (/{{\s*[^{}]+\s*}}/.test(body)) diagnostics.push(diagnostic("warning", "ROLE_PLACEHOLDER", "Role body contains an unsupported placeholder-looking token", path));
|
|
172
|
-
if (definition.model) validateModel(definition.model, knownModels, availableModels, path, diagnostics);
|
|
186
|
+
if (definition.model) validateModel(definition.model, knownModels, availableModels, path, diagnostics, aliases, settingsPath);
|
|
173
187
|
for (const tool of definition.tools ?? []) if (!activeTools.has(tool)) diagnostics.push(diagnostic("error", "ROLE_TOOL_INACTIVE", `Tool is unknown or inactive: ${tool}`, path, "Use a tool listed under Active tools or enable its Pi extension."));
|
|
174
188
|
return definition;
|
|
175
189
|
}
|
|
@@ -177,7 +191,14 @@ function inspectRole(path: string, activeTools: ReadonlySet<string>, knownModels
|
|
|
177
191
|
class DoctorModelSet extends Set<string> {
|
|
178
192
|
override has(value: string): boolean { parseModelReference(value); return true; }
|
|
179
193
|
}
|
|
180
|
-
|
|
194
|
+
function matchResourcePolicy(policy: AgentResourcePolicy, pi: DoctorPiState): AgentResourcePolicy {
|
|
195
|
+
const extensions = new Set((pi.extensions ?? []).map(canonical));
|
|
196
|
+
const skills = new Set(pi.skills ?? []);
|
|
197
|
+
return { ...policy, unmatchedSkills: policy.effective.skills.filter((name) => !skills.has(name)), unmatchedExtensions: policy.effective.extensions.filter((path) => !extensions.has(canonical(path))) };
|
|
198
|
+
}
|
|
199
|
+
function resourcePolicySource(policy: AgentResourcePolicy, kind: keyof AgentResourceExclusions, selector: string): string {
|
|
200
|
+
return policy.project[kind].includes(selector) && !policy.global[kind].includes(selector) ? policy.projectSettingsPath : policy.globalSettingsPath;
|
|
201
|
+
}
|
|
181
202
|
export async function doctor(options: DoctorOptions = {}): Promise<DoctorReport> {
|
|
182
203
|
const cwd = canonical(options.cwd ?? process.cwd());
|
|
183
204
|
const agentDir = canonical(options.agentDir ?? getAgentDir());
|
|
@@ -196,10 +217,21 @@ export async function doctor(options: DoctorOptions = {}): Promise<DoctorReport>
|
|
|
196
217
|
if (options.activeTools) pi = { ...pi, activeTools: options.activeTools.filter((tool) => tool !== "workflow" && tool !== "workflow_respond" && tool !== "workflow_catalog") };
|
|
197
218
|
if (pi.trust.required && !pi.trust.trusted) diagnostics.push(diagnostic("warning", "PROJECT_UNTRUSTED", "Pi project resources are inactive because the project is not trusted", cwd, "Open this project in Pi, choose Trust, then rerun doctor."));
|
|
198
219
|
for (const error of pi.extensionErrors) diagnostics.push(diagnostic("error", "EXTENSION_LOAD", error.message, error.path, "Fix or disable the failing Pi extension."));
|
|
220
|
+
let resourcePolicy: AgentResourcePolicy;
|
|
221
|
+
try {
|
|
222
|
+
resourcePolicy = matchResourcePolicy(resolveAgentResourcePolicy(cwd, pi.trust.trusted, settingsPath), pi);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
225
|
+
if (!diagnostics.some(({ code, source }) => code === "SETTINGS_INVALID" && source === settingsPath)) diagnostics.push(diagnostic("error", "SETTINGS_INVALID", message, message.includes(".json") ? message.match(/(?:settings: )?([^ )]+\.json)/)?.[1] : undefined, "Fix or remove the invalid workflow settings file."));
|
|
226
|
+
resourcePolicy = emptyResourcePolicy(settingsPath, cwd, pi.trust.trusted);
|
|
227
|
+
}
|
|
228
|
+
for (const skill of resourcePolicy.unmatchedSkills) diagnostics.push(diagnostic("warning", "AGENT_RESOURCE_UNMATCHED", `Disabled skill selector currently matches no discovered skill: ${skill}`, `${resourcePolicySource(resourcePolicy, "skills", skill)}.disabledAgentResources.skills`));
|
|
229
|
+
for (const extension of resourcePolicy.unmatchedExtensions) diagnostics.push(diagnostic("warning", "AGENT_RESOURCE_UNMATCHED", `Disabled extension selector currently matches no discovered extension source: ${extension}`, `${resourcePolicySource(resourcePolicy, "extensions", extension)}.disabledAgentResources.extensions`));
|
|
199
230
|
|
|
200
231
|
const activeTools = new Set(pi.activeTools);
|
|
201
232
|
const knownModels = new Set(pi.knownModels);
|
|
202
233
|
const availableModels = new Set(pi.availableModels);
|
|
234
|
+
const aliases = settings.modelAliases ?? {};
|
|
203
235
|
const roles: DoctorRole[] = [];
|
|
204
236
|
const definitions = new Map<string, AgentDefinition>();
|
|
205
237
|
const globalPaths = new Map<string, string>();
|
|
@@ -208,7 +240,7 @@ export async function doctor(options: DoctorOptions = {}): Promise<DoctorReport>
|
|
|
208
240
|
const name = basename(path, ".md");
|
|
209
241
|
roles.push({ name, path, scope: "global", active: true });
|
|
210
242
|
globalPaths.set(name, path);
|
|
211
|
-
const definition = inspectRole(path, activeTools, knownModels, availableModels, diagnostics);
|
|
243
|
+
const definition = inspectRole(path, activeTools, knownModels, availableModels, diagnostics, aliases, settingsPath);
|
|
212
244
|
if (definition) definitions.set(name, definition);
|
|
213
245
|
}
|
|
214
246
|
for (const path of roleFilesFrom([join(cwd, ".pi", "pi-extensible-workflows", "roles")])) {
|
|
@@ -221,7 +253,7 @@ export async function doctor(options: DoctorOptions = {}): Promise<DoctorReport>
|
|
|
221
253
|
const global = roles.find((role) => role.path === globalPath);
|
|
222
254
|
if (global) { global.active = false; global.overriddenBy = path; }
|
|
223
255
|
}
|
|
224
|
-
const definition = inspectRole(path, activeTools, knownModels, availableModels, diagnostics);
|
|
256
|
+
const definition = inspectRole(path, activeTools, knownModels, availableModels, diagnostics, aliases, settingsPath);
|
|
225
257
|
if (definition) definitions.set(name, definition); else definitions.delete(name);
|
|
226
258
|
}
|
|
227
259
|
|
|
@@ -233,6 +265,9 @@ export async function doctor(options: DoctorOptions = {}): Promise<DoctorReport>
|
|
|
233
265
|
models: new DoctorModelSet(pi.knownModels),
|
|
234
266
|
tools: activeTools,
|
|
235
267
|
agentTypes: new Set(definitions.keys()),
|
|
268
|
+
modelAliases: aliases,
|
|
269
|
+
knownModels,
|
|
270
|
+
settingsPath,
|
|
236
271
|
}, [], { name, description: workflow.description });
|
|
237
272
|
for (const model of checked.referenced.models) if (!knownModels.has(model) || !availableModels.has(model)) diagnostics.push(diagnostic("warning", "MODEL_UNAVAILABLE", `Model is valid-shaped but unavailable: ${model}`, name));
|
|
238
273
|
} catch (error) {
|
|
@@ -246,7 +281,7 @@ export async function doctor(options: DoctorOptions = {}): Promise<DoctorReport>
|
|
|
246
281
|
const severityOrder: Record<DoctorSeverity, number> = { error: 0, warning: 1 };
|
|
247
282
|
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));
|
|
248
283
|
roles.sort((left, right) => left.name.localeCompare(right.name) || left.scope.localeCompare(right.scope));
|
|
249
|
-
return { cwd, agentDir, settingsPath, settings, trust: pi.trust, activeTools: [...activeTools].sort(), roles, workflows, diagnostics };
|
|
284
|
+
return { cwd, agentDir, settingsPath, settings, trust: pi.trust, activeTools: [...activeTools].sort(), roles, workflows, resourcePolicy, diagnostics };
|
|
250
285
|
}
|
|
251
286
|
|
|
252
287
|
function count(report: DoctorReport, severity: DoctorSeverity): number { return report.diagnostics.filter((item) => item.severity === severity).length; }
|
|
@@ -261,11 +296,23 @@ export function formatDoctorReport(report: DoctorReport): string {
|
|
|
261
296
|
`- CWD: \`${report.cwd}\``,
|
|
262
297
|
`- Agent dir: \`${report.agentDir}\``,
|
|
263
298
|
`- Workflow settings: \`${report.settingsPath}\``,
|
|
264
|
-
`- Limits: concurrency=${String(report.settings.concurrency)}
|
|
299
|
+
`- Limits: concurrency=${String(report.settings.concurrency)}`,
|
|
265
300
|
"",
|
|
266
301
|
"## Trust/resources",
|
|
267
302
|
`- [${report.trust.trusted ? "ok" : "warning"}] ${report.trust.source}`,
|
|
268
303
|
"",
|
|
304
|
+
"## Agent resource exclusions",
|
|
305
|
+
`- Global settings: \`${report.resourcePolicy.globalSettingsPath}\``,
|
|
306
|
+
`- Global skills: ${report.resourcePolicy.global.skills.join(", ") || "(none)"}`,
|
|
307
|
+
`- Global extensions: ${report.resourcePolicy.global.extensions.join(", ") || "(none)"}`,
|
|
308
|
+
`- Project settings: \`${report.resourcePolicy.projectSettingsPath}\` (${report.resourcePolicy.projectTrusted ? "trusted" : "ignored: project untrusted"})`,
|
|
309
|
+
`- Project skills: ${report.resourcePolicy.project.skills.join(", ") || "(none)"}`,
|
|
310
|
+
`- Project extensions: ${report.resourcePolicy.project.extensions.join(", ") || "(none)"}`,
|
|
311
|
+
`- Effective skills: ${report.resourcePolicy.effective.skills.join(", ") || "(none)"}`,
|
|
312
|
+
`- Effective extensions: ${report.resourcePolicy.effective.extensions.join(", ") || "(none)"}`,
|
|
313
|
+
`- Unmatched skills: ${report.resourcePolicy.unmatchedSkills.join(", ") || "(none)"}`,
|
|
314
|
+
`- Unmatched extensions: ${report.resourcePolicy.unmatchedExtensions.join(", ") || "(none)"}`,
|
|
315
|
+
"",
|
|
269
316
|
"## Active tools",
|
|
270
317
|
...(report.activeTools.length ? report.activeTools.map((tool) => `- \`${tool}\``) : ["- None resolved"]),
|
|
271
318
|
"",
|