@piewf/cli 5.17.0 → 5.17.2
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 +1 -1
- package/dist/src/cli.js +2 -2
- package/dist/src/doctor-cleanup.js +6 -7
- package/dist/src/doctor.js +51 -24
- package/dist/src/session-inspector.js +2 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ Roles come from the bundled starter roles, `~/.pi/agent/pi-extensible-workflows/
|
|
|
26
26
|
## piewf
|
|
27
27
|
|
|
28
28
|
```sh
|
|
29
|
-
piewf doctor [--role <role>] [--prompt <text>] [--json]
|
|
29
|
+
piewf doctor [role|role-file] [--role <role>] [--prompt <text>] [--json]
|
|
30
30
|
piewf doctor cleanup [--older-than-days <days>] [--yes]
|
|
31
31
|
piewf inspect [session-id] [--json|--summary] [--failed]
|
|
32
32
|
piewf transcript <session-file>
|
package/dist/src/cli.js
CHANGED
|
@@ -803,7 +803,7 @@ export async function runCli(args, options = {}, write = (text) => { process.std
|
|
|
803
803
|
const stderr = options.stderr ?? ((text) => { process.stderr.write(text); });
|
|
804
804
|
if (args[0] === "doctor" && args[1] !== "cleanup") {
|
|
805
805
|
if (args.slice(1).some((arg) => arg === "--help" || arg === "-h")) {
|
|
806
|
-
write("Usage: piewf doctor [role] [--role <role>] [--prompt <text>] [--json]\n");
|
|
806
|
+
write("Usage: piewf doctor [role|role-file] [--role <role>] [--prompt <text>] [--json]\n");
|
|
807
807
|
return 0;
|
|
808
808
|
}
|
|
809
809
|
try {
|
|
@@ -902,7 +902,7 @@ export async function runCli(args, options = {}, write = (text) => { process.std
|
|
|
902
902
|
return 1;
|
|
903
903
|
}
|
|
904
904
|
}
|
|
905
|
-
write("Usage: piewf doctor [role] [--role <role>] [--prompt <text>] [--json] | inspect [session-id] [--json|--summary] [--failed] | transcript <session-file> | share <run-id> | bundle <workflow-name> [--name <command>] [--output <path>] [--force] | run <workflow-name> [workflow arguments] | run --script <path> [--name <workflow-name>] [--input <json>] | export <workflow-name> [--name <command>] [--output <path>] [--force] [--bundle]\n");
|
|
905
|
+
write("Usage: piewf doctor [role|role-file] [--role <role>] [--prompt <text>] [--json] | inspect [session-id] [--json|--summary] [--failed] | transcript <session-file> | share <run-id> | bundle <workflow-name> [--name <command>] [--output <path>] [--force] | run <workflow-name> [workflow arguments] | run --script <path> [--name <workflow-name>] [--input <json>] | export <workflow-name> [--name <command>] [--output <path>] [--force] [--bundle]\n");
|
|
906
906
|
return 1;
|
|
907
907
|
}
|
|
908
908
|
if (process.argv[1] && sameFilesystemPath(fileURLToPath(import.meta.url), process.argv[1])) {
|
|
@@ -3,7 +3,7 @@ import { lstat, readdir, readFile, stat } from "node:fs/promises";
|
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join, resolve } from "node:path";
|
|
5
5
|
import { AGENT_STATES as CORE_AGENT_STATES, BUDGET_DIMENSIONS as CORE_BUDGET_DIMENSIONS, BUDGET_EVENT_TYPES as CORE_BUDGET_EVENT_TYPES, HARD_TERMINAL_RUN_STATES, RUN_STATES, errorText, isNodeError, isThinkingLevel, jsonValue, object, validateBudget, validateModelAliases, validateSchema } from "pi-extensible-workflows";
|
|
6
|
-
import { acquireSessionLease, hasLiveSessionLease, projectSessionsDirectory, RunStore } from "pi-extensible-workflows/persistence";
|
|
6
|
+
import { acquireSessionLease, hasLiveSessionLease, json, projectSessionsDirectory, RunStore } from "pi-extensible-workflows/persistence";
|
|
7
7
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
8
8
|
const REQUIRED_RUN_FILES = ["workflow.js", "state.json", "snapshot.json", "journal.json", "ownership.json", "worktrees.json", "borrowed-worktrees.json", "system-prompts.json"];
|
|
9
9
|
const OPTIONAL_RUN_FILES = new Set(["result.json", "summary.json"]);
|
|
@@ -22,7 +22,6 @@ function positiveDays(value) { if (!Number.isSafeInteger(value) || value < 1 ||
|
|
|
22
22
|
throw new Error("older-than-days must be a positive integer"); return value; }
|
|
23
23
|
function runItem(entry, action, reason) { return { sessionId: entry.sessionId, runId: entry.runId, action, state: entry.run.state, stateMtimeMs: entry.stateMtimeMs, path: entry.store.directory, ...(reason ? { reason } : {}) }; }
|
|
24
24
|
function sameNames(left, right) { return left.length === right.length && left.every((value, index) => value === right[index]); }
|
|
25
|
-
async function jsonFile(path) { return JSON.parse(await readFile(path, "utf8")); }
|
|
26
25
|
async function requiredFile(path) { const info = await lstat(path); if (!info.isFile())
|
|
27
26
|
throw new Error(`Required artifact is not a regular file: ${path}`); }
|
|
28
27
|
function stringList(value, label, nonEmpty = false) { if (!isList(value) || value.some((item) => typeof item !== "string" || (nonEmpty && !item)))
|
|
@@ -271,7 +270,7 @@ function validateJournal(value) { if (!object(value) || !object(value.completed)
|
|
|
271
270
|
validateBudget(decision.proposed);
|
|
272
271
|
} }
|
|
273
272
|
async function validateSystemPrompts(store) {
|
|
274
|
-
const value = await
|
|
273
|
+
const value = await json(store.systemPromptPath());
|
|
275
274
|
if (object(value) && value.version === 2) {
|
|
276
275
|
await store.systemPrompts();
|
|
277
276
|
return;
|
|
@@ -312,14 +311,14 @@ async function validateRunArtifacts(store, workflowScript, state) {
|
|
|
312
311
|
if (await readFile(join(store.directory, "workflow.js"), "utf8") !== workflowScript)
|
|
313
312
|
throw new Error("Persisted workflow source does not match its launch snapshot");
|
|
314
313
|
await validateSystemPrompts(store);
|
|
315
|
-
const result = await
|
|
314
|
+
const result = await json(join(store.directory, "result.json")).catch((error) => { if (isNodeError(error, "ENOENT"))
|
|
316
315
|
return undefined; throw error; });
|
|
317
316
|
if (result === undefined && state === "completed")
|
|
318
317
|
throw new Error("Completed run result is missing");
|
|
319
318
|
if (result !== undefined && !jsonValue(result))
|
|
320
319
|
throw new Error("Persisted workflow result is invalid");
|
|
321
|
-
validateJournal(await
|
|
322
|
-
const rawOwnership = await
|
|
320
|
+
validateJournal(await json(join(store.directory, "journal.json")));
|
|
321
|
+
const rawOwnership = await json(join(store.directory, "ownership.json"));
|
|
323
322
|
if (!isList(rawOwnership))
|
|
324
323
|
throw new Error("Persisted ownership records are invalid");
|
|
325
324
|
const ownershipIds = new Set();
|
|
@@ -362,7 +361,7 @@ async function scanSession(cwd, sessionId, home, expectedLease) {
|
|
|
362
361
|
const ownerPath = join(runsPath, "owner.json");
|
|
363
362
|
let liveLease = false;
|
|
364
363
|
if (expectedLease && expectedLease.path === ownerPath) {
|
|
365
|
-
const owned = await
|
|
364
|
+
const owned = await json(ownerPath);
|
|
366
365
|
if (!object(owned) || owned.token !== expectedLease.token)
|
|
367
366
|
throw new Error("Session ownership lease changed before deletion");
|
|
368
367
|
}
|
package/dist/src/doctor.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
-
import { basename, dirname, extname, join } from "node:path";
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
3
3
|
import { InMemoryCredentialStore, InMemoryModelsStore } from "@earendil-works/pi-ai";
|
|
4
4
|
import { ModelRuntime, createAgentSessionFromServices, createAgentSessionServices, getAgentDir, hasTrustRequiringProjectResources, SessionManager, SettingsManager, } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { DEFAULT_SETTINGS, canonicalPath, createLocalPiSession, errorText, isNodeError, isObject, loadSettings, prepareAgentSetupForInspection, resolveAgentResourcePolicy, resolveWorkflowSettings, resolveModelReference, resourcePatternHasMagic, parseThinking, registeredWorkflowFunctions, registeredWorkflowRoleDirectoryRegistrations, workflowProjectSettingsPath, workflowSettingsPath, } from "pi-extensible-workflows";
|
|
@@ -207,10 +207,9 @@ function validateModel(value, known, available, source, diagnostics, aliases, dy
|
|
|
207
207
|
diagnostics.push(diagnostic("error", "MODEL_INVALID", message, source, message.includes("thinking") ? THINKING_HINT : "Use provider/model:thinking."));
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
|
-
function
|
|
211
|
-
let definition;
|
|
210
|
+
function parseRole(path, diagnostics, source) {
|
|
212
211
|
try {
|
|
213
|
-
|
|
212
|
+
return parseRoleMarkdown(readFileSync(path, "utf8"), true, path);
|
|
214
213
|
}
|
|
215
214
|
catch (error) {
|
|
216
215
|
if (usesLegacyRoleSelectors(path)) {
|
|
@@ -221,6 +220,8 @@ function inspectRole(path, activeTools, knownModels, availableModels, diagnostic
|
|
|
221
220
|
diagnostics.push(diagnostic("error", "ROLE_FRONTMATTER", source ? `${roleProvenance(source)} contains invalid role at "${path}": ${message}` : message, path, "Fix the role YAML frontmatter."));
|
|
222
221
|
return undefined;
|
|
223
222
|
}
|
|
223
|
+
}
|
|
224
|
+
function inspectRoleUsage(path, definition, activeTools, knownModels, availableModels, diagnostics, aliases, dynamicAliases, settingsPath) {
|
|
224
225
|
const toolSelectorDiagnostic = positiveOnlyToolSelectorDiagnostic(path, definition.tools);
|
|
225
226
|
if (toolSelectorDiagnostic)
|
|
226
227
|
diagnostics.push(toolSelectorDiagnostic);
|
|
@@ -236,8 +237,13 @@ function inspectRole(path, activeTools, knownModels, availableModels, diagnostic
|
|
|
236
237
|
for (const selector of definition.tools ?? []) {
|
|
237
238
|
const tool = selector.startsWith("!") ? selector.slice(1) : selector;
|
|
238
239
|
if (!selector.startsWith("!") && !resourcePatternHasMagic(selector) && !activeTools.has(tool))
|
|
239
|
-
diagnostics.push(diagnostic("
|
|
240
|
+
diagnostics.push(diagnostic("warning", "ROLE_TOOL_INACTIVE", `Tool is not in Pi's headless active tool list: ${tool}`, path, "Doctor cannot see tools that extensions add when a session starts; otherwise use a tool listed under Pi active tools or enable its Pi extension."));
|
|
240
241
|
}
|
|
242
|
+
}
|
|
243
|
+
function inspectRole(path, activeTools, knownModels, availableModels, diagnostics, aliases, dynamicAliases, settingsPath, source) {
|
|
244
|
+
const definition = parseRole(path, diagnostics, source);
|
|
245
|
+
if (definition)
|
|
246
|
+
inspectRoleUsage(path, definition, activeTools, knownModels, availableModels, diagnostics, aliases, dynamicAliases, settingsPath);
|
|
241
247
|
return definition;
|
|
242
248
|
}
|
|
243
249
|
function matchResourcePolicy(policy, pi) {
|
|
@@ -435,15 +441,13 @@ export async function doctor(options = {}) {
|
|
|
435
441
|
const starterPath = starterOverrides.get(file.path);
|
|
436
442
|
const overriddenBy = starterOverriddenBy.get(file.path);
|
|
437
443
|
roles.push({ name: file.name, path: file.path, scope: "extension", active: overriddenBy === undefined, extension: file.extension, ...(starterPath ? { overrides: starterPath } : {}), ...(overriddenBy ? { overriddenBy } : {}) });
|
|
438
|
-
const definition =
|
|
439
|
-
if (definition)
|
|
440
|
-
validateDoctorExtensionSettings(registry, mergeWorkflowExtensionSettings(settings.extensionSettings, definition.extensionSettings), "role", cwd, pi.trust.trusted, file.path, diagnostics, file.name);
|
|
444
|
+
const definition = parseRole(file.path, diagnostics, { directory: file.directory, extension: file.extension });
|
|
441
445
|
if (duplicateExtensionNames.has(file.name))
|
|
442
446
|
continue;
|
|
443
447
|
if (extensionPaths.get(file.name) !== file.path)
|
|
444
448
|
continue;
|
|
445
449
|
if (definition)
|
|
446
|
-
definitions.set(file.name, definition);
|
|
450
|
+
definitions.set(file.name, { path: file.path, definition });
|
|
447
451
|
}
|
|
448
452
|
const globalPaths = new Map();
|
|
449
453
|
const globalRoleDirs = workflowRoleDirectories(agentDir);
|
|
@@ -459,11 +463,9 @@ export async function doctor(options = {}) {
|
|
|
459
463
|
extension.overriddenBy = path;
|
|
460
464
|
}
|
|
461
465
|
}
|
|
462
|
-
const definition =
|
|
463
|
-
if (definition)
|
|
464
|
-
validateDoctorExtensionSettings(registry, mergeWorkflowExtensionSettings(settings.extensionSettings, definition.extensionSettings), "role", cwd, pi.trust.trusted, path, diagnostics, name);
|
|
466
|
+
const definition = parseRole(path, diagnostics);
|
|
465
467
|
if (definition)
|
|
466
|
-
definitions.set(name, definition);
|
|
468
|
+
definitions.set(name, { path, definition });
|
|
467
469
|
else
|
|
468
470
|
definitions.delete(name);
|
|
469
471
|
}
|
|
@@ -490,24 +492,49 @@ export async function doctor(options = {}) {
|
|
|
490
492
|
extension.overriddenBy = path;
|
|
491
493
|
}
|
|
492
494
|
}
|
|
493
|
-
const definition =
|
|
495
|
+
const definition = parseRole(path, diagnostics);
|
|
494
496
|
if (definition)
|
|
495
|
-
|
|
496
|
-
if (definition)
|
|
497
|
-
definitions.set(name, definition);
|
|
497
|
+
definitions.set(name, { path, definition });
|
|
498
498
|
else
|
|
499
499
|
definitions.delete(name);
|
|
500
500
|
}
|
|
501
|
+
for (const [name, { path, definition }] of definitions) {
|
|
502
|
+
inspectRoleUsage(path, definition, activeTools, knownModels, availableModels, diagnostics, aliases, dynamicAliases, settingsPath);
|
|
503
|
+
validateDoctorExtensionSettings(registry, mergeWorkflowExtensionSettings(settings.extensionSettings, definition.extensionSettings), "role", cwd, pi.trust.trusted, path, diagnostics, name);
|
|
504
|
+
}
|
|
501
505
|
const rolePaths = new Set(roles.map(({ path }) => path));
|
|
502
506
|
if (diagnostics.some(({ code, source }) => source !== undefined && rolePaths.has(source) && (code === "ROLE_FRONTMATTER" || code === "AGENT_RESOURCE_SELECTOR_MIGRATION")))
|
|
503
507
|
diagnostics.push(diagnostic("error", "ROLE_LOAD_BLOCKED", "Workflow role loading is blocked because the runtime rejects the complete role set when any active role file is invalid.", undefined, "Fix the reported role file before launching workflows."));
|
|
504
508
|
let roleInspection;
|
|
505
509
|
if (options.role !== undefined) {
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
if (
|
|
509
|
-
|
|
510
|
+
let target;
|
|
511
|
+
//NOTE: installed role names drop ".md", so a target ending in ".md" is a role file path.
|
|
512
|
+
if (options.role.endsWith(".md")) {
|
|
513
|
+
const path = resolve(cwd, options.role);
|
|
514
|
+
if (!existsSync(path) || !statSync(path).isFile())
|
|
515
|
+
diagnostics.push(diagnostic("error", "ROLE_FILE_NOT_FOUND", `Role file not found: ${path}`, path));
|
|
516
|
+
else {
|
|
517
|
+
const name = basename(path, ".md");
|
|
518
|
+
const found = [];
|
|
519
|
+
const definition = inspectRole(path, activeTools, knownModels, availableModels, found, aliases, dynamicAliases, settingsPath);
|
|
520
|
+
if (definition)
|
|
521
|
+
validateDoctorExtensionSettings(registry, mergeWorkflowExtensionSettings(settings.extensionSettings, definition.extensionSettings), "role", cwd, pi.trust.trusted, path, found, name);
|
|
522
|
+
// Discovery already reported an installed file; add only what is new.
|
|
523
|
+
const known = new Set(diagnostics.map((item) => JSON.stringify(item)));
|
|
524
|
+
diagnostics.push(...found.filter((item) => !known.has(JSON.stringify(item))));
|
|
525
|
+
if (definition)
|
|
526
|
+
target = { name, path, definition };
|
|
527
|
+
}
|
|
528
|
+
}
|
|
510
529
|
else {
|
|
530
|
+
const effective = definitions.get(options.role);
|
|
531
|
+
if (!effective)
|
|
532
|
+
diagnostics.push(diagnostic("error", "ROLE_NOT_FOUND", `Active role not found: ${options.role}`, options.role));
|
|
533
|
+
else
|
|
534
|
+
target = { name: options.role, ...effective };
|
|
535
|
+
}
|
|
536
|
+
if (target) {
|
|
537
|
+
const { name, path, definition } = target;
|
|
511
538
|
const rootReference = pi.model ? `${pi.model.provider}/${pi.model.model}` : pi.availableModels[0] ?? pi.knownModels[0];
|
|
512
539
|
if (!rootReference)
|
|
513
540
|
diagnostics.push(diagnostic("error", "ROLE_INSPECTION_MODEL", "Cannot inspect a role because Pi has no registered model"));
|
|
@@ -525,12 +552,12 @@ export async function doctor(options = {}) {
|
|
|
525
552
|
const dynamic = await registry.resolveModelAliases({ cwd, projectTrusted: pi.trust.trusted, rootModel, knownModels, availableModels, signal: new AbortController().signal });
|
|
526
553
|
roleAliases = { ...aliases, ...dynamic };
|
|
527
554
|
}
|
|
528
|
-
roleInspection = await inspectRoleSession(cwd, agentDir,
|
|
555
|
+
roleInspection = await inspectRoleSession(cwd, agentDir, name, definition, path, resourcePolicy, rootModel, [...activeTools], roleAliases, knownModels, availableModels, settingsPath, settings.extensionSettings, options.prompt ?? "", registry.agentSetupHooks(), diagnostics);
|
|
529
556
|
if (roleInspection)
|
|
530
557
|
diagnostics.push(...roleInspection.setup.diagnostics);
|
|
531
558
|
}
|
|
532
559
|
catch (error) {
|
|
533
|
-
diagnostics.push(diagnostic("error", "ROLE_INSPECTION_MODEL", errorText(error),
|
|
560
|
+
diagnostics.push(diagnostic("error", "ROLE_INSPECTION_MODEL", errorText(error), path));
|
|
534
561
|
}
|
|
535
562
|
}
|
|
536
563
|
}
|
|
@@ -113,7 +113,8 @@ function transcript(manager) {
|
|
|
113
113
|
}
|
|
114
114
|
function readTranscript(path) {
|
|
115
115
|
try {
|
|
116
|
-
|
|
116
|
+
const info = existsSync(path) ? statSync(path) : undefined;
|
|
117
|
+
if (!info?.isFile() || info.size === 0)
|
|
117
118
|
return undefined;
|
|
118
119
|
const manager = SessionManager.open(path);
|
|
119
120
|
if (!manager.getHeader())
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@piewf/cli",
|
|
3
|
-
"version": "5.17.
|
|
3
|
+
"version": "5.17.2",
|
|
4
4
|
"description": "CLI for pi-extensible-workflows",
|
|
5
5
|
"homepage": "https://vekexasia.github.io/pi-extensible-workflows/",
|
|
6
6
|
"repository": {
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@earendil-works/pi-coding-agent": "0.85.0",
|
|
46
46
|
"@earendil-works/pi-server": "0.85.0",
|
|
47
47
|
"@earendil-works/pi-tui": "0.85.0",
|
|
48
|
-
"pi-extensible-workflows": "5.17.
|
|
48
|
+
"pi-extensible-workflows": "5.17.2",
|
|
49
49
|
"typebox": "1.3.7"
|
|
50
50
|
},
|
|
51
51
|
"publishConfig": {
|