pi-extensible-workflows 0.3.2 → 1.0.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/README.md +9 -0
- package/dist/src/agent-execution.d.ts +81 -8
- package/dist/src/agent-execution.js +465 -75
- package/dist/src/ambient-workflow-evals.js +14 -33
- package/dist/src/doctor.d.ts +4 -1
- package/dist/src/doctor.js +57 -14
- package/dist/src/index.d.ts +222 -24
- package/dist/src/index.js +1548 -410
- package/dist/src/persistence.d.ts +28 -1
- package/dist/src/persistence.js +126 -9
- package/dist/src/session-inspector.d.ts +13 -1
- package/dist/src/session-inspector.js +22 -4
- package/dist/src/workflow-evals.d.ts +1 -0
- package/dist/src/workflow-evals.js +23 -25
- package/package.json +5 -2
- package/skills/pi-extensible-workflows/SKILL.md +14 -6
- package/src/agent-execution.ts +391 -86
- package/src/ambient-workflow-evals.ts +18 -28
- package/src/doctor.ts +60 -16
- package/src/index.ts +1332 -395
- package/src/persistence.ts +98 -10
- package/src/session-inspector.ts +25 -7
- package/src/workflow-evals.ts +13 -16
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
|
-
import { existsSync, mkdirSync, mkdtempSync,
|
|
4
|
+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
|
|
5
5
|
import { homedir, tmpdir } from "node:os";
|
|
6
6
|
import { join, relative } from "node:path";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import { CAPTURE_IDENTITY } from "./eval-capture-extension.js";
|
|
9
|
-
import {
|
|
9
|
+
import { isObject } from "./index.js";
|
|
10
|
+
import { extractCapturedWorkflows, extractParentOracleFile, findSessionFile } from "./workflow-evals.js";
|
|
10
11
|
export const AMBIENT_OPT_IN = "PI_WORKFLOW_EVAL_AMBIENT";
|
|
11
12
|
export const AMBIENT_CAPTURE_NOTE = "Ambient Tier D uses the explicit capture extension. Pi 0.80.6 orders CLI extensions before discovered extensions and the first tool registration wins, so ambient tools and skills remain available while workflow execution stays capture-only.";
|
|
12
13
|
export const AMBIENT_INVOCATION_MODE = "ambient-capture-only";
|
|
@@ -234,34 +235,12 @@ function emptyAccounting() {
|
|
|
234
235
|
function ambientAgentDir(environment) {
|
|
235
236
|
return environment.PI_CODING_AGENT_DIR ?? process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
|
|
236
237
|
}
|
|
237
|
-
function isObject(value) { return typeof value === "object" && value !== null && !Array.isArray(value); }
|
|
238
|
-
function findSessionFile(directory, sessionId) {
|
|
239
|
-
if (!existsSync(directory))
|
|
240
|
-
return undefined;
|
|
241
|
-
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
242
|
-
const path = join(directory, entry.name);
|
|
243
|
-
if (entry.isDirectory()) {
|
|
244
|
-
const found = findSessionFile(path, sessionId);
|
|
245
|
-
if (found)
|
|
246
|
-
return found;
|
|
247
|
-
}
|
|
248
|
-
else if (entry.name.endsWith(".jsonl")) {
|
|
249
|
-
try {
|
|
250
|
-
const header = JSON.parse(readFileSync(path, "utf8").split("\n")[0] ?? "{}");
|
|
251
|
-
if (isObject(header) && header.id === sessionId)
|
|
252
|
-
return path;
|
|
253
|
-
}
|
|
254
|
-
catch { /* Ignore incomplete sessions. */ }
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return undefined;
|
|
258
|
-
}
|
|
259
238
|
function emptyResult(candidate, repository, worktree, environment, error) {
|
|
260
239
|
const accounting = emptyAccounting();
|
|
261
240
|
return {
|
|
262
241
|
id: candidate.id, status: "failed", workflows: [], accounting, accountingTrustworthy: false, diagnostics: [], errors: [error],
|
|
263
242
|
manifest: {
|
|
264
|
-
invocationMode: AMBIENT_INVOCATION_MODE, fixtureRoot: repository.fixtureRoot, worktreePath: worktree.path, ambientAgentDir: ambientAgentDir(environment), fixtureFileList: worktree.fixtureFiles, gitStatusBefore: worktree.gitStatusBefore,
|
|
243
|
+
invocationMode: AMBIENT_INVOCATION_MODE, fixtureRoot: repository.fixtureRoot, worktreePath: worktree.path, ambientAgentDir: ambientAgentDir(environment), fixtureFileList: worktree.fixtureFiles, gitStatusBefore: worktree.gitStatusBefore, parentToolSequence: [], skillReads: [], workflowCalls: [], workflowCallCount: 0, tokenCost: accounting,
|
|
265
244
|
cleanup: { processExited: false, processGroupTerminated: false, worktreeRemoved: false, fixtureRepoRemoved: false, tempRootRemoved: false, captureIdentityVerified: false, realWorkflowAgentsLaunched: 0 },
|
|
266
245
|
},
|
|
267
246
|
};
|
|
@@ -271,12 +250,13 @@ async function runAmbientCase(repository, candidate, artifactsDir, environment,
|
|
|
271
250
|
const sessionId = randomUUID();
|
|
272
251
|
const sessionDir = join(repository.root, "sessions", candidate.id);
|
|
273
252
|
let result;
|
|
274
|
-
let
|
|
253
|
+
let failure;
|
|
254
|
+
let gitStatusAfter;
|
|
275
255
|
try {
|
|
276
256
|
const pi = await runAmbientPiProcess({ worktree: worktree.path, sessionDir, sessionId, prompt: candidate.prompt, provider, model, ...(thinking ? { thinking } : {}), ...(piCommand ? { piCommand } : {}), timeoutMs: candidate.timeoutMs, maxCost: candidate.maxCost, environment });
|
|
277
257
|
const sessionFile = findSessionFile(sessionDir, sessionId);
|
|
278
258
|
if (!sessionFile)
|
|
279
|
-
|
|
259
|
+
failure = "Ambient parent session was not written.";
|
|
280
260
|
else {
|
|
281
261
|
const oracle = extractParentOracleFile(sessionFile);
|
|
282
262
|
const workflows = extractCapturedWorkflows(oracle);
|
|
@@ -286,14 +266,14 @@ async function runAmbientCase(repository, candidate, artifactsDir, environment,
|
|
|
286
266
|
result = {
|
|
287
267
|
id: candidate.id, status, workflows, accounting: oracle.usage, accountingTrustworthy: !pi.timedOut && !pi.budgetExceeded && pi.exitCode === 0, diagnostics: [pi.stderr].filter(Boolean), errors,
|
|
288
268
|
manifest: {
|
|
289
|
-
invocationMode: AMBIENT_INVOCATION_MODE, fixtureRoot: repository.fixtureRoot, worktreePath: worktree.path, ambientAgentDir: ambientAgentDir(environment), fixtureFileList: worktree.fixtureFiles, gitStatusBefore: worktree.gitStatusBefore,
|
|
269
|
+
invocationMode: AMBIENT_INVOCATION_MODE, fixtureRoot: repository.fixtureRoot, worktreePath: worktree.path, ambientAgentDir: ambientAgentDir(environment), fixtureFileList: worktree.fixtureFiles, gitStatusBefore: worktree.gitStatusBefore, parentToolSequence: oracle.parentToolSequence, skillReads: oracle.skillReads, workflowCalls: workflows, workflowCallCount: oracle.workflowCallCount, tokenCost: oracle.usage,
|
|
290
270
|
cleanup: { processExited: pi.exitCode !== null, processGroupTerminated: pi.processGroupTerminated, worktreeRemoved: false, fixtureRepoRemoved: false, tempRootRemoved: false, captureIdentityVerified, realWorkflowAgentsLaunched: 0 },
|
|
291
271
|
},
|
|
292
272
|
};
|
|
293
273
|
}
|
|
294
274
|
}
|
|
295
275
|
catch (error) {
|
|
296
|
-
|
|
276
|
+
failure = error instanceof Error ? error.message : String(error);
|
|
297
277
|
}
|
|
298
278
|
finally {
|
|
299
279
|
try {
|
|
@@ -304,12 +284,13 @@ async function runAmbientCase(repository, candidate, artifactsDir, environment,
|
|
|
304
284
|
}
|
|
305
285
|
const worktreeRemoved = removeAmbientCaseWorktree(repository, worktree);
|
|
306
286
|
if (!result)
|
|
307
|
-
result = emptyResult(candidate, repository, worktree, environment, "Ambient case produced no result.");
|
|
287
|
+
result = emptyResult(candidate, repository, worktree, environment, failure ?? "Ambient case produced no result.");
|
|
308
288
|
const cleanup = { ...result.manifest.cleanup, worktreeRemoved };
|
|
309
|
-
result = { ...result, manifest: { ...result.manifest,
|
|
310
|
-
writeFileSync(join(artifactsDir, `${candidate.id}.json`), `${JSON.stringify(result, null, 2)}\n`, { mode: 0o600 });
|
|
289
|
+
result = { ...result, manifest: { ...result.manifest, cleanup } };
|
|
311
290
|
}
|
|
312
|
-
|
|
291
|
+
const finalized = { ...result, manifest: { ...result.manifest, gitStatusAfter } };
|
|
292
|
+
writeFileSync(join(artifactsDir, `${candidate.id}.json`), `${JSON.stringify(finalized, null, 2)}\n`, { mode: 0o600 });
|
|
293
|
+
return finalized;
|
|
313
294
|
}
|
|
314
295
|
export function assertAmbientOptIn(environment = process.env) {
|
|
315
296
|
if (environment[AMBIENT_OPT_IN] !== "1")
|
package/dist/src/doctor.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type WorkflowScriptDefinition, type WorkflowSettings } from "./index.js";
|
|
1
|
+
import { type AgentResourcePolicy, type WorkflowScriptDefinition, type WorkflowSettings } from "./index.js";
|
|
2
2
|
export type DoctorSeverity = "error" | "warning";
|
|
3
3
|
export interface DoctorDiagnostic {
|
|
4
4
|
severity: DoctorSeverity;
|
|
@@ -34,6 +34,8 @@ export interface DoctorPiState {
|
|
|
34
34
|
path?: string;
|
|
35
35
|
message: string;
|
|
36
36
|
}[];
|
|
37
|
+
extensions?: readonly string[];
|
|
38
|
+
skills?: readonly string[];
|
|
37
39
|
workflows: Readonly<Record<string, WorkflowScriptDefinition>>;
|
|
38
40
|
}
|
|
39
41
|
export interface DoctorReport {
|
|
@@ -45,6 +47,7 @@ export interface DoctorReport {
|
|
|
45
47
|
activeTools: readonly string[];
|
|
46
48
|
roles: readonly DoctorRole[];
|
|
47
49
|
workflows: readonly DoctorWorkflow[];
|
|
50
|
+
resourcePolicy: AgentResourcePolicy;
|
|
48
51
|
diagnostics: readonly DoctorDiagnostic[];
|
|
49
52
|
}
|
|
50
53
|
export interface DoctorOptions {
|
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,
|
|
5
|
+
import { DEFAULT_SETTINGS, loadSettings, resolveAgentResourcePolicy, resolveModelReference, parseRoleMarkdown, preflight, registeredWorkflowDefinitions, workflowRoleDirectories, workflowProjectSettingsPath, workflowSettingsPath, WorkflowError, } from "./index.js";
|
|
6
6
|
const THINKING_HINT = "Use off, minimal, low, medium, high, xhigh, or max.";
|
|
7
7
|
function canonical(path) {
|
|
8
8
|
const absolute = resolve(path);
|
|
@@ -65,7 +65,7 @@ async function discoverPi(cwd, agentDir) {
|
|
|
65
65
|
agentDir,
|
|
66
66
|
settingsManager,
|
|
67
67
|
modelRuntime,
|
|
68
|
-
resourceLoaderOptions: {
|
|
68
|
+
resourceLoaderOptions: { noPromptTemplates: true, noThemes: true, noContextFiles: true },
|
|
69
69
|
resourceLoaderReloadOptions: { resolveProjectTrust: async () => trusted },
|
|
70
70
|
});
|
|
71
71
|
const allModels = services.modelRuntime.getModels();
|
|
@@ -76,11 +76,14 @@ async function discoverPi(cwd, agentDir) {
|
|
|
76
76
|
const { session } = await createAgentSessionFromServices({ services, sessionManager: SessionManager.inMemory(), model });
|
|
77
77
|
const activeTools = session.agent.state.tools.map(({ name }) => name).filter((name) => name !== "workflow" && name !== "workflow_respond" && name !== "workflow_catalog");
|
|
78
78
|
const extensions = services.resourceLoader.getExtensions();
|
|
79
|
+
const skills = services.resourceLoader.getSkills().skills;
|
|
79
80
|
return {
|
|
80
81
|
trust: { required, trusted, source },
|
|
81
82
|
activeTools,
|
|
82
83
|
knownModels: allModels.map(({ provider, id }) => `${provider}/${id}`),
|
|
83
84
|
availableModels: availableModels.map(({ provider, id }) => `${provider}/${id}`),
|
|
85
|
+
extensions: extensions.extensions.map(({ resolvedPath }) => resolvedPath),
|
|
86
|
+
skills: skills.map(({ name }) => name),
|
|
84
87
|
extensionErrors: [
|
|
85
88
|
...extensions.errors.map(({ path, error }) => ({ path, message: error })),
|
|
86
89
|
...services.diagnostics.filter(({ type }) => type === "error").map(({ message }) => ({ message })),
|
|
@@ -112,9 +115,13 @@ function roleFilesFrom(dirs) {
|
|
|
112
115
|
function diagnostic(severity, code, message, source, hint) {
|
|
113
116
|
return { severity, code, message, ...(source ? { source } : {}), ...(hint ? { hint } : {}) };
|
|
114
117
|
}
|
|
115
|
-
function
|
|
118
|
+
function emptyResourcePolicy(globalSettingsPath, cwd, projectTrusted) {
|
|
119
|
+
const empty = { skills: [], extensions: [] };
|
|
120
|
+
return { globalSettingsPath, projectSettingsPath: workflowProjectSettingsPath(cwd), projectTrusted, global: empty, project: empty, effective: empty, unmatchedSkills: [], unmatchedExtensions: [] };
|
|
121
|
+
}
|
|
122
|
+
function validateModel(value, known, available, source, diagnostics, aliases, settingsPath) {
|
|
116
123
|
try {
|
|
117
|
-
const parsed =
|
|
124
|
+
const parsed = resolveModelReference(value, aliases, known, settingsPath);
|
|
118
125
|
const name = `${parsed.provider}/${parsed.model}`;
|
|
119
126
|
if (!known.has(name) || !available.has(name))
|
|
120
127
|
diagnostics.push(diagnostic("warning", "MODEL_UNAVAILABLE", `Model is valid-shaped but unavailable: ${name}`, source));
|
|
@@ -123,10 +130,10 @@ function validateModel(value, known, available, source, diagnostics) {
|
|
|
123
130
|
diagnostics.push(diagnostic("error", "MODEL_INVALID", error.message, source, error.message.includes("thinking") ? THINKING_HINT : "Use provider/model or provider/model:thinking."));
|
|
124
131
|
}
|
|
125
132
|
}
|
|
126
|
-
function inspectRole(path, activeTools, knownModels, availableModels, diagnostics) {
|
|
133
|
+
function inspectRole(path, activeTools, knownModels, availableModels, diagnostics, aliases, settingsPath) {
|
|
127
134
|
let definition;
|
|
128
135
|
try {
|
|
129
|
-
definition = parseRoleMarkdown(readFileSync(path, "utf8"), true);
|
|
136
|
+
definition = parseRoleMarkdown(readFileSync(path, "utf8"), true, path);
|
|
130
137
|
}
|
|
131
138
|
catch (error) {
|
|
132
139
|
diagnostics.push(diagnostic("error", "ROLE_FRONTMATTER", error.message, path, "Fix the role YAML frontmatter."));
|
|
@@ -140,14 +147,19 @@ function inspectRole(path, activeTools, knownModels, availableModels, diagnostic
|
|
|
140
147
|
if (/{{\s*[^{}]+\s*}}/.test(body))
|
|
141
148
|
diagnostics.push(diagnostic("warning", "ROLE_PLACEHOLDER", "Role body contains an unsupported placeholder-looking token", path));
|
|
142
149
|
if (definition.model)
|
|
143
|
-
validateModel(definition.model, knownModels, availableModels, path, diagnostics);
|
|
150
|
+
validateModel(definition.model, knownModels, availableModels, path, diagnostics, aliases, settingsPath);
|
|
144
151
|
for (const tool of definition.tools ?? [])
|
|
145
152
|
if (!activeTools.has(tool))
|
|
146
153
|
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."));
|
|
147
154
|
return definition;
|
|
148
155
|
}
|
|
149
|
-
|
|
150
|
-
|
|
156
|
+
function matchResourcePolicy(policy, pi) {
|
|
157
|
+
const extensions = new Set((pi.extensions ?? []).map(canonical));
|
|
158
|
+
const skills = new Set(pi.skills ?? []);
|
|
159
|
+
return { ...policy, unmatchedSkills: policy.effective.skills.filter((name) => !skills.has(name)), unmatchedExtensions: policy.effective.extensions.filter((path) => !extensions.has(canonical(path))) };
|
|
160
|
+
}
|
|
161
|
+
function resourcePolicySource(policy, kind, selector) {
|
|
162
|
+
return policy.project[kind].includes(selector) && !policy.global[kind].includes(selector) ? policy.projectSettingsPath : policy.globalSettingsPath;
|
|
151
163
|
}
|
|
152
164
|
export async function doctor(options = {}) {
|
|
153
165
|
const cwd = canonical(options.cwd ?? process.cwd());
|
|
@@ -175,9 +187,24 @@ export async function doctor(options = {}) {
|
|
|
175
187
|
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."));
|
|
176
188
|
for (const error of pi.extensionErrors)
|
|
177
189
|
diagnostics.push(diagnostic("error", "EXTENSION_LOAD", error.message, error.path, "Fix or disable the failing Pi extension."));
|
|
190
|
+
let resourcePolicy;
|
|
191
|
+
try {
|
|
192
|
+
resourcePolicy = matchResourcePolicy(resolveAgentResourcePolicy(cwd, pi.trust.trusted, settingsPath), pi);
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
196
|
+
if (!diagnostics.some(({ code, source }) => code === "SETTINGS_INVALID" && source === settingsPath))
|
|
197
|
+
diagnostics.push(diagnostic("error", "SETTINGS_INVALID", message, message.includes(".json") ? message.match(/(?:settings: )?([^ )]+\.json)/)?.[1] : undefined, "Fix or remove the invalid workflow settings file."));
|
|
198
|
+
resourcePolicy = emptyResourcePolicy(settingsPath, cwd, pi.trust.trusted);
|
|
199
|
+
}
|
|
200
|
+
for (const skill of resourcePolicy.unmatchedSkills)
|
|
201
|
+
diagnostics.push(diagnostic("warning", "AGENT_RESOURCE_UNMATCHED", `Disabled skill selector currently matches no discovered skill: ${skill}`, `${resourcePolicySource(resourcePolicy, "skills", skill)}.disabledAgentResources.skills`));
|
|
202
|
+
for (const extension of resourcePolicy.unmatchedExtensions)
|
|
203
|
+
diagnostics.push(diagnostic("warning", "AGENT_RESOURCE_UNMATCHED", `Disabled extension selector currently matches no discovered extension source: ${extension}`, `${resourcePolicySource(resourcePolicy, "extensions", extension)}.disabledAgentResources.extensions`));
|
|
178
204
|
const activeTools = new Set(pi.activeTools);
|
|
179
205
|
const knownModels = new Set(pi.knownModels);
|
|
180
206
|
const availableModels = new Set(pi.availableModels);
|
|
207
|
+
const aliases = settings.modelAliases ?? {};
|
|
181
208
|
const roles = [];
|
|
182
209
|
const definitions = new Map();
|
|
183
210
|
const globalPaths = new Map();
|
|
@@ -186,7 +213,7 @@ export async function doctor(options = {}) {
|
|
|
186
213
|
const name = basename(path, ".md");
|
|
187
214
|
roles.push({ name, path, scope: "global", active: true });
|
|
188
215
|
globalPaths.set(name, path);
|
|
189
|
-
const definition = inspectRole(path, activeTools, knownModels, availableModels, diagnostics);
|
|
216
|
+
const definition = inspectRole(path, activeTools, knownModels, availableModels, diagnostics, aliases, settingsPath);
|
|
190
217
|
if (definition)
|
|
191
218
|
definitions.set(name, definition);
|
|
192
219
|
}
|
|
@@ -204,7 +231,7 @@ export async function doctor(options = {}) {
|
|
|
204
231
|
global.overriddenBy = path;
|
|
205
232
|
}
|
|
206
233
|
}
|
|
207
|
-
const definition = inspectRole(path, activeTools, knownModels, availableModels, diagnostics);
|
|
234
|
+
const definition = inspectRole(path, activeTools, knownModels, availableModels, diagnostics, aliases, settingsPath);
|
|
208
235
|
if (definition)
|
|
209
236
|
definitions.set(name, definition);
|
|
210
237
|
else
|
|
@@ -215,9 +242,13 @@ export async function doctor(options = {}) {
|
|
|
215
242
|
let valid = true;
|
|
216
243
|
try {
|
|
217
244
|
const checked = preflight(workflow.script, {
|
|
218
|
-
models: new
|
|
245
|
+
models: new Set(pi.knownModels),
|
|
219
246
|
tools: activeTools,
|
|
220
247
|
agentTypes: new Set(definitions.keys()),
|
|
248
|
+
modelAliases: aliases,
|
|
249
|
+
knownModels,
|
|
250
|
+
settingsPath,
|
|
251
|
+
skipModelAvailability: true,
|
|
221
252
|
}, [], { name, description: workflow.description });
|
|
222
253
|
for (const model of checked.referenced.models)
|
|
223
254
|
if (!knownModels.has(model) || !availableModels.has(model))
|
|
@@ -233,7 +264,7 @@ export async function doctor(options = {}) {
|
|
|
233
264
|
const severityOrder = { error: 0, warning: 1 };
|
|
234
265
|
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));
|
|
235
266
|
roles.sort((left, right) => left.name.localeCompare(right.name) || left.scope.localeCompare(right.scope));
|
|
236
|
-
return { cwd, agentDir, settingsPath, settings, trust: pi.trust, activeTools: [...activeTools].sort(), roles, workflows, diagnostics };
|
|
267
|
+
return { cwd, agentDir, settingsPath, settings, trust: pi.trust, activeTools: [...activeTools].sort(), roles, workflows, resourcePolicy, diagnostics };
|
|
237
268
|
}
|
|
238
269
|
function count(report, severity) { return report.diagnostics.filter((item) => item.severity === severity).length; }
|
|
239
270
|
export function doctorExitCode(report) { return count(report, "error") > 0 ? 1 : 0; }
|
|
@@ -245,11 +276,23 @@ export function formatDoctorReport(report) {
|
|
|
245
276
|
`- CWD: \`${report.cwd}\``,
|
|
246
277
|
`- Agent dir: \`${report.agentDir}\``,
|
|
247
278
|
`- Workflow settings: \`${report.settingsPath}\``,
|
|
248
|
-
`- Limits: concurrency=${String(report.settings.concurrency)}
|
|
279
|
+
`- Limits: concurrency=${String(report.settings.concurrency)}`,
|
|
249
280
|
"",
|
|
250
281
|
"## Trust/resources",
|
|
251
282
|
`- [${report.trust.trusted ? "ok" : "warning"}] ${report.trust.source}`,
|
|
252
283
|
"",
|
|
284
|
+
"## Agent resource exclusions",
|
|
285
|
+
`- Global settings: \`${report.resourcePolicy.globalSettingsPath}\``,
|
|
286
|
+
`- Global skills: ${report.resourcePolicy.global.skills.join(", ") || "(none)"}`,
|
|
287
|
+
`- Global extensions: ${report.resourcePolicy.global.extensions.join(", ") || "(none)"}`,
|
|
288
|
+
`- Project settings: \`${report.resourcePolicy.projectSettingsPath}\` (${report.resourcePolicy.projectTrusted ? "trusted" : "ignored: project untrusted"})`,
|
|
289
|
+
`- Project skills: ${report.resourcePolicy.project.skills.join(", ") || "(none)"}`,
|
|
290
|
+
`- Project extensions: ${report.resourcePolicy.project.extensions.join(", ") || "(none)"}`,
|
|
291
|
+
`- Effective skills: ${report.resourcePolicy.effective.skills.join(", ") || "(none)"}`,
|
|
292
|
+
`- Effective extensions: ${report.resourcePolicy.effective.extensions.join(", ") || "(none)"}`,
|
|
293
|
+
`- Unmatched skills: ${report.resourcePolicy.unmatchedSkills.join(", ") || "(none)"}`,
|
|
294
|
+
`- Unmatched extensions: ${report.resourcePolicy.unmatchedExtensions.join(", ") || "(none)"}`,
|
|
295
|
+
"",
|
|
253
296
|
"## Active tools",
|
|
254
297
|
...(report.activeTools.length ? report.activeTools.map((tool) => `- \`${tool}\``) : ["- None resolved"]),
|
|
255
298
|
"",
|