pi-extensible-workflows 3.0.0 → 3.2.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 +15 -17
- package/dist/src/agent-execution.d.ts +4 -80
- package/dist/src/agent-execution.js +20 -10
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +48 -2
- package/dist/src/doctor-cleanup.d.ts +41 -0
- package/dist/src/doctor-cleanup.js +597 -0
- package/dist/src/doctor.d.ts +7 -2
- package/dist/src/doctor.js +127 -22
- package/dist/src/execution.js +13 -4
- package/dist/src/host.d.ts +83 -4
- package/dist/src/host.js +1246 -410
- package/dist/src/index.d.ts +5 -2
- package/dist/src/index.js +3 -1
- package/dist/src/persistence.d.ts +3 -0
- package/dist/src/persistence.js +49 -1
- package/dist/src/registry.d.ts +21 -9
- package/dist/src/registry.js +131 -21
- package/dist/src/session-inspector.js +4 -2
- package/dist/src/types.d.ts +135 -7
- package/dist/src/utils.d.ts +6 -0
- package/dist/src/utils.js +45 -5
- package/dist/src/validation.d.ts +6 -2
- package/dist/src/validation.js +157 -31
- package/dist/src/workflow-artifacts.d.ts +13 -0
- package/dist/src/workflow-artifacts.js +39 -0
- package/examples/workflow-extension-template/README.md +37 -0
- package/examples/workflow-extension-template/extension.test.mjs +59 -0
- package/examples/workflow-extension-template/index.js +51 -0
- package/examples/workflow-extension-template/roles/reviewer.md +4 -0
- package/package.json +5 -3
- package/skills/pi-extensible-workflows/SKILL.md +45 -18
- package/src/agent-execution.ts +23 -37
- package/src/cli.ts +33 -2
- package/src/doctor-cleanup.ts +337 -0
- package/src/doctor.ts +117 -24
- package/src/execution.ts +13 -4
- package/src/host.ts +1039 -366
- package/src/index.ts +5 -2
- package/src/persistence.ts +35 -1
- package/src/registry.ts +108 -25
- package/src/session-inspector.ts +4 -2
- package/src/types.ts +53 -8
- package/src/utils.ts +39 -5
- package/src/validation.ts +130 -31
- package/src/workflow-artifacts.ts +34 -0
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
Turn multi-agent tasks into deterministic jobs that fan out in parallel, pause for approval, and resume without rerunning completed work.
|
|
8
8
|
|
|
9
|
-
[Documentation](https://vekexasia.github.io/pi-extensible-workflows/) | [Developer guide](https://vekexasia.github.io/pi-extensible-workflows/developers.html) | [Agent guide](https://vekexasia.github.io/pi-extensible-workflows/agents.html)
|
|
9
|
+
[Documentation](https://vekexasia.github.io/pi-extensible-workflows/) | [Developer guide](https://vekexasia.github.io/pi-extensible-workflows/developers.html) | [Agent guide](https://vekexasia.github.io/pi-extensible-workflows/agents.html) | [Extension authoring](https://vekexasia.github.io/pi-extensible-workflows/extensions.html) | [Video overview](https://youtu.be/qAiivspEHmU)
|
|
10
10
|
|
|
11
11
|
Requires Node.js 22.19 or newer. This is a trusted Pi extension with the same filesystem and process access as Pi.
|
|
12
12
|
|
|
@@ -20,35 +20,32 @@ For source installs and local development, see the [installation guide](https://
|
|
|
20
20
|
|
|
21
21
|
## Capabilities
|
|
22
22
|
|
|
23
|
-
The
|
|
24
|
-
|
|
25
|
-
Inline workflow launches require a non-empty `name`; registered function launches reject `name` and use their registered function name as the run name. Workflow worktree scopes always use the explicit `withWorktree(name, callback)` form.
|
|
26
|
-
|
|
27
|
-
A workflow can fan out across specialized agents, combine their results, and resume without rerunning completed work.
|
|
23
|
+
The default path is a named inline workflow: write a `script` that fans out independent work with `parallel(...)`, awaits the keyed results, passes them into one summarizing `agent(...)`, and returns. Inline launches require a non-empty `name`; registered function launches reject `name` and use their registered function name as the run name. Runs are backgrounded by default; set `foreground: true` when the final value must be returned in the same tool call.
|
|
28
24
|
|
|
29
25
|
```js
|
|
30
26
|
const reviews = await parallel("review", {
|
|
31
27
|
correctness: () => agent("Review the current changes for correctness issues."),
|
|
32
|
-
security: () => agent("Review the current changes for security risks.",
|
|
33
|
-
role: "security-specialist",
|
|
34
|
-
}),
|
|
28
|
+
security: () => agent("Review the current changes for security risks."),
|
|
35
29
|
tests: () => agent("Review the current changes for missing test coverage."),
|
|
36
30
|
});
|
|
37
31
|
|
|
38
|
-
|
|
39
|
-
prompt("
|
|
32
|
+
return await agent(
|
|
33
|
+
prompt("Summarize and prioritize these findings:\n\n{reviews}", { reviews }),
|
|
40
34
|
);
|
|
41
|
-
|
|
42
|
-
return summary;
|
|
43
35
|
```
|
|
44
36
|
|
|
37
|
+
**Advanced capabilities:** Use registered functions, `outputSchema`, budgets, checkpoints, worktrees, retry/resume, CLI export, and `pipeline(...)` when the task requires them. They remain available without complicating the basic inline path. Workflow worktree scopes always use the explicit `withWorktree(name, callback)` form.
|
|
38
|
+
|
|
39
|
+
The main Pi agent writes these scripts on the fly for each task; extensions can add reusable functions and variables, and completed workflows can resume without rerunning completed work.
|
|
40
|
+
|
|
45
41
|
Learn more about roles, workflow contracts, and extension APIs in the documentation:
|
|
46
42
|
|
|
47
43
|
- [Workflow tool and invocation API](https://vekexasia.github.io/pi-extensible-workflows/developers.html#tool-api)
|
|
48
44
|
- [Global and project settings](https://vekexasia.github.io/pi-extensible-workflows/developers.html#settings)
|
|
49
45
|
- [Aggregate run budgets](https://vekexasia.github.io/pi-extensible-workflows/developers.html#budgets)
|
|
50
46
|
- [Workflow DSL and worktrees](https://vekexasia.github.io/pi-extensible-workflows/developers.html#dsl)
|
|
51
|
-
- [
|
|
47
|
+
- [Extension authoring guide](https://vekexasia.github.io/pi-extensible-workflows/extensions.html)
|
|
48
|
+
- [Copy-paste extension template](https://github.com/vekexasia/pi-extensible-workflows/tree/main/examples/workflow-extension-template)
|
|
52
49
|
- [Run artifacts and lifecycle events](https://vekexasia.github.io/pi-extensible-workflows/developers.html#lifecycle)
|
|
53
50
|
- [Run inspection and recovery](https://vekexasia.github.io/pi-extensible-workflows/developers.html#operations)
|
|
54
51
|
- [Agent patterns and model selection](https://vekexasia.github.io/pi-extensible-workflows/agents.html#patterns)
|
|
@@ -56,20 +53,21 @@ Learn more about roles, workflow contracts, and extension APIs in the documentat
|
|
|
56
53
|
|
|
57
54
|
## Configuration
|
|
58
55
|
|
|
59
|
-
Global workflow settings live at `~/.pi/agent/pi-extensible-workflows/settings.json` by default
|
|
56
|
+
Global workflow settings live at `~/.pi/agent/pi-extensible-workflows/settings.json` by default. Trusted projects can add partial overrides at `<project>/.pi/pi-extensible-workflows/settings.json`; precedence is defaults < global < trusted project < per-run options. Project collections replace global collections, so `{}` and empty arrays clear inherited aliases or resource exclusions. Untrusted project settings are ignored. See [global and project settings](https://vekexasia.github.io/pi-extensible-workflows/developers.html#settings) for the schema, trust gate, source reporting, and merge rules.
|
|
60
57
|
|
|
61
58
|
## CLI
|
|
62
59
|
|
|
63
60
|
```sh
|
|
64
61
|
npx pi-extensible-workflows doctor
|
|
62
|
+
npx pi-extensible-workflows doctor cleanup [--older-than-days <days>] [--yes]
|
|
65
63
|
npx pi-extensible-workflows inspect [session-id]
|
|
66
64
|
npx pi-extensible-workflows transcript <session-file>
|
|
67
65
|
npx pi-extensible-workflows run <workflow-name> [workflow arguments]
|
|
68
66
|
npx pi-extensible-workflows export <workflow-name> [--name <command>] [--output <path>] [--force]
|
|
69
67
|
```
|
|
70
68
|
|
|
71
|
-
`doctor` validates the installation and active Pi resources. `inspect` opens a read-only terminal view of persisted workflow runs. `transcript` renders a session transcript to stdout. `run` derives flat CLI arguments and help from a registered function's input schema. Use `--input '<json>'` for nested or otherwise complex inputs. It executes in the current working directory, writes the final JSON result to stdout, and writes progress and errors to stderr. `export` creates an executable POSIX launcher in `~/.local/bin` by default.
|
|
72
|
-
`run` and `export` accept the trust overrides `--approve` and `--no-approve`; the generated launcher forwards its arguments to `run`. `--` ends launcher option parsing, and later tokens are passed to workflow input instead of being interpreted as launcher options.
|
|
69
|
+
`doctor` validates the installation and active Pi resources and remains read-only. `doctor cleanup` is an explicit, dry-run-first maintenance command: it previews terminal workflow runs older than 90 days across the current project's stored sessions and deletes them only with `--yes`; use `--older-than-days` to change the positive age threshold. It protects active, corrupt, leased, and dependency-linked runs. `inspect` opens a read-only terminal view of persisted workflow runs. `transcript` renders a session transcript to stdout. `run` derives flat CLI arguments and help from a registered function's input schema. Use `--input '<json>'` for nested or otherwise complex inputs. It executes in the current working directory, writes the final JSON result to stdout, and writes progress and errors to stderr. `export` creates an executable POSIX launcher in `~/.local/bin` by default.
|
|
70
|
+
`run` and `export` accept the trust overrides `--approve` and `--no-approve`; the generated launcher forwards its arguments to `run`. `--` ends launcher option parsing, and later tokens are passed to workflow input instead of being interpreted as launcher options. Headless `run` and generated `export` launchers cannot execute workflows containing checkpoints; use the Pi workflow tool/UI path for checkpointed workflows.
|
|
73
71
|
Launch snapshots use identity version 5. Cold resume rejects older snapshots, including v4 snapshots created with the previous worktree or registered-function naming contracts, with `RESUME_INCOMPATIBLE`; relaunch the workflow instead.
|
|
74
72
|
|
|
75
73
|
## Development
|
|
@@ -1,22 +1,8 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
3
|
-
type
|
|
4
|
-
role: string;
|
|
5
|
-
content?: unknown;
|
|
6
|
-
stopReason?: string;
|
|
7
|
-
errorMessage?: string;
|
|
8
|
-
usage?: {
|
|
9
|
-
input: number;
|
|
10
|
-
output: number;
|
|
11
|
-
cacheRead: number;
|
|
12
|
-
cacheWrite: number;
|
|
13
|
-
cost: {
|
|
14
|
-
total: number;
|
|
15
|
-
};
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
import type { AgentIdentity, AgentResourceExclusions, AgentResourcePolicy, AgentSetupSummary, JsonSchema, JsonValue, ModelSpec, WorkflowRunContext } from "./types.js";
|
|
3
|
+
import type { AgentIdentity, AgentResourceExclusions, AgentResourcePolicy, AgentSetupSummary, JsonSchema, JsonValue, ModelSpec, NativeSession, RegisteredAgentSetupHook, SessionFactory, SessionInput, WorkflowRunContext } from "./types.js";
|
|
19
4
|
import type { RunStore } from "./persistence.js";
|
|
5
|
+
export type { AgentSetup, AgentSetupContext, AgentSetupHook, NativeSession, RegisteredAgentSetupHook, SessionFactory, SessionInput } from "./types.js";
|
|
20
6
|
export interface AgentBudgetHooks {
|
|
21
7
|
beforeAttempt(): void;
|
|
22
8
|
beforeTurn(): void;
|
|
@@ -121,68 +107,6 @@ export interface AgentExecutionResult {
|
|
|
121
107
|
attempts: readonly AgentAttempt[];
|
|
122
108
|
cwd: string;
|
|
123
109
|
}
|
|
124
|
-
export interface AgentSetup {
|
|
125
|
-
prompt: string;
|
|
126
|
-
options: Record<string, JsonValue>;
|
|
127
|
-
sessionInput: SessionInput;
|
|
128
|
-
createSession: SessionFactory;
|
|
129
|
-
}
|
|
130
|
-
export interface AgentSetupContext {
|
|
131
|
-
readonly run: Readonly<WorkflowRunContext>;
|
|
132
|
-
readonly identity: Readonly<AgentIdentity>;
|
|
133
|
-
readonly attempt: number;
|
|
134
|
-
readonly signal: AbortSignal;
|
|
135
|
-
}
|
|
136
|
-
export interface AgentSetupHook {
|
|
137
|
-
priority?: number;
|
|
138
|
-
setup: (agent: AgentSetup, context: Readonly<AgentSetupContext>) => void | Promise<void>;
|
|
139
|
-
}
|
|
140
|
-
export interface RegisteredAgentSetupHook {
|
|
141
|
-
name: string;
|
|
142
|
-
priority: number;
|
|
143
|
-
setup: AgentSetupHook["setup"];
|
|
144
|
-
}
|
|
145
|
-
type NativeSessionStats = Pick<SessionStats, "tokens" | "cost">;
|
|
146
|
-
export interface NativeSession {
|
|
147
|
-
readonly sessionId: string;
|
|
148
|
-
readonly sessionFile: string | undefined;
|
|
149
|
-
readonly messages: readonly AgentMessage[];
|
|
150
|
-
getSessionStats(): NativeSessionStats;
|
|
151
|
-
readonly systemPrompt?: string;
|
|
152
|
-
readonly model?: {
|
|
153
|
-
provider: string;
|
|
154
|
-
model?: string;
|
|
155
|
-
id?: string;
|
|
156
|
-
};
|
|
157
|
-
readonly agent?: {
|
|
158
|
-
state: {
|
|
159
|
-
tools: readonly {
|
|
160
|
-
name: string;
|
|
161
|
-
}[];
|
|
162
|
-
};
|
|
163
|
-
};
|
|
164
|
-
getLeafId?: () => string | null;
|
|
165
|
-
getToolDefinitions?: () => unknown;
|
|
166
|
-
subscribe?(listener: (event: AgentSessionEvent) => void): () => void;
|
|
167
|
-
prompt(text: string): Promise<void>;
|
|
168
|
-
steer?(text: string): Promise<void>;
|
|
169
|
-
abort?(): Promise<void>;
|
|
170
|
-
dispose(): void;
|
|
171
|
-
}
|
|
172
|
-
export interface SessionInput {
|
|
173
|
-
cwd: string;
|
|
174
|
-
model: ModelSpec;
|
|
175
|
-
tools: string[];
|
|
176
|
-
sessionLabel: string;
|
|
177
|
-
agentDir?: string;
|
|
178
|
-
customTools?: ToolDefinition[];
|
|
179
|
-
resultTool?: ToolDefinition;
|
|
180
|
-
systemPromptAppend?: string;
|
|
181
|
-
extensionFactories?: InlineExtension[];
|
|
182
|
-
resourcePolicy?: AgentResourcePolicy;
|
|
183
|
-
options?: Record<string, JsonValue>;
|
|
184
|
-
}
|
|
185
|
-
export type SessionFactory = (input: SessionInput) => Promise<NativeSession>;
|
|
186
110
|
export declare function createNativeAgentSession(input: SessionInput): Promise<NativeSession>;
|
|
187
111
|
export declare class WorkflowAgentExecutor {
|
|
188
112
|
private readonly root;
|
|
@@ -265,6 +189,7 @@ export declare class FairAgentScheduler {
|
|
|
265
189
|
private readonly writeOwnership?;
|
|
266
190
|
constructor(runner: ScheduledAgentRunner, sessionLimit?: number, writeOwnership?: OwnershipWriter | undefined);
|
|
267
191
|
addRun(runId: string, limit?: number, beforeLaunch?: () => void): void;
|
|
192
|
+
updateRunLimit(runId: string, limit: number): void;
|
|
268
193
|
spawn(runId: string, prompt: string, options: ScheduledAgentOptions, parentId?: string): {
|
|
269
194
|
id: string;
|
|
270
195
|
result: Promise<ScheduledAgentResult>;
|
|
@@ -282,4 +207,3 @@ export declare class FairAgentScheduler {
|
|
|
282
207
|
restoreRun(runId: string, limit: number, ownership: readonly OwnershipRecord[], beforeLaunch?: () => void): void;
|
|
283
208
|
flush(): Promise<void>;
|
|
284
209
|
}
|
|
285
|
-
export {};
|
|
@@ -3,7 +3,7 @@ import { join, resolve } from "node:path";
|
|
|
3
3
|
import { Type } from "@earendil-works/pi-ai";
|
|
4
4
|
import { Value } from "typebox/value";
|
|
5
5
|
import { createAgentSession, DefaultPackageManager, DefaultResourceLoader, getAgentDir, ModelRuntime, SessionManager, SettingsManager } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { jsonObject, mergeAgentResourceExclusions, modelAliasName, modelCapability, resolveModelReference } from "./utils.js";
|
|
6
|
+
import { jsonObject, disabledResources, mergeAgentResourceExclusions, modelAliasName, modelCapability, resolveModelReference, unmatchedResourcePatterns } from "./utils.js";
|
|
7
7
|
import { WorkflowError } from "./types.js";
|
|
8
8
|
function parseModel(value, fallback, thinking, aliases = {}, knownModels, settingsPath) {
|
|
9
9
|
if (!value)
|
|
@@ -69,14 +69,17 @@ export async function createNativeAgentSession(input) {
|
|
|
69
69
|
settingsManager.setProjectTrusted(policy.projectTrusted);
|
|
70
70
|
const packageManager = new DefaultPackageManager({ cwd: input.cwd, agentDir, settingsManager });
|
|
71
71
|
const resolved = await packageManager.resolve();
|
|
72
|
-
const
|
|
73
|
-
const
|
|
72
|
+
const discoveredExtensions = [...new Set(resolved.extensions.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => canonicalSourcePath(path)))];
|
|
73
|
+
const excludedExtensions = new Set(disabledResources(policy.effective.extensions, discoveredExtensions));
|
|
74
|
+
const extensionPaths = discoveredExtensions.filter((path) => !excludedExtensions.has(path));
|
|
75
|
+
Object.assign(policy, { excludedExtensions: [...excludedExtensions], unmatchedExtensions: unmatchedResourcePatterns(policy.effective.extensions, discoveredExtensions) });
|
|
74
76
|
const skillPaths = [...new Set(resolved.skills.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => path))];
|
|
75
77
|
const updateSkillMatches = (skills) => {
|
|
76
|
-
const names = new Set(skills.map(({ name }) => name));
|
|
77
|
-
|
|
78
|
+
const names = [...new Set(skills.map(({ name }) => name))];
|
|
79
|
+
const excludedSkills = disabledResources(policy.effective.skills, names);
|
|
80
|
+
Object.assign(policy, { excludedSkills, unmatchedSkills: unmatchedResourcePatterns(policy.effective.skills, names) });
|
|
81
|
+
return new Set(excludedSkills);
|
|
78
82
|
};
|
|
79
|
-
const disabledSkills = new Set(policy.effective.skills);
|
|
80
83
|
resourceLoader = new DefaultResourceLoader({
|
|
81
84
|
cwd: input.cwd,
|
|
82
85
|
agentDir,
|
|
@@ -87,14 +90,12 @@ export async function createNativeAgentSession(input) {
|
|
|
87
90
|
additionalSkillPaths: skillPaths,
|
|
88
91
|
...(input.extensionFactories?.length ? { extensionFactories: input.extensionFactories } : {}),
|
|
89
92
|
skillsOverride: (base) => {
|
|
90
|
-
updateSkillMatches(base.skills);
|
|
93
|
+
const disabledSkills = updateSkillMatches(base.skills);
|
|
91
94
|
return { ...base, skills: base.skills.filter(({ name }) => !disabledSkills.has(name)) };
|
|
92
95
|
},
|
|
93
96
|
...(input.systemPromptAppend ? { appendSystemPromptOverride: (base) => [...base, input.systemPromptAppend ?? ""] } : {}),
|
|
94
97
|
});
|
|
95
98
|
await resourceLoader.reload();
|
|
96
|
-
const discoveredExtensions = new Set(resolved.extensions.filter(({ enabled, metadata }) => enabled && (policy.projectTrusted || metadata.scope !== "project")).map(({ path }) => canonicalSourcePath(path)));
|
|
97
|
-
Object.assign(policy, { unmatchedExtensions: policy.effective.extensions.filter((path) => !discoveredExtensions.has(canonicalSourcePath(path))) });
|
|
98
99
|
}
|
|
99
100
|
else if (input.systemPromptAppend || input.extensionFactories?.length) {
|
|
100
101
|
resourceLoader = new DefaultResourceLoader({ cwd: input.cwd, agentDir, ...(input.extensionFactories?.length ? { extensionFactories: input.extensionFactories } : {}), ...(input.systemPromptAppend ? { appendSystemPromptOverride: (base) => [...base, input.systemPromptAppend ?? ""] } : {}) });
|
|
@@ -133,7 +134,7 @@ function fallbackSetupContext(root, options, signal) {
|
|
|
133
134
|
return { run, identity: Object.freeze({ ...identity, structuralPath: Object.freeze([...identity.structuralPath]) }) };
|
|
134
135
|
}
|
|
135
136
|
function resourcePolicySummary(policy) {
|
|
136
|
-
return { skills: [...policy.effective.skills], extensions: [...policy.effective.extensions], unmatchedSkills: [...policy.unmatchedSkills], unmatchedExtensions: [...policy.unmatchedExtensions] };
|
|
137
|
+
return { skills: [...policy.effective.skills], extensions: [...policy.effective.extensions], excludedSkills: [...(policy.excludedSkills ?? [])], excludedExtensions: [...(policy.excludedExtensions ?? [])], unmatchedSkills: [...policy.unmatchedSkills], unmatchedExtensions: [...policy.unmatchedExtensions] };
|
|
137
138
|
}
|
|
138
139
|
async function prepareAgentSetup(root, createSession, task, options, resolved, cwd, attempt, signal, customTools, resultTool) {
|
|
139
140
|
const setupSignal = signal ?? root.runContext?.signal ?? new AbortController().signal;
|
|
@@ -520,6 +521,15 @@ export class FairAgentScheduler {
|
|
|
520
521
|
this.#runs.set(runId, { limit, ...(beforeLaunch ? { beforeLaunch } : {}), logical: 0, active: 0, queue: [] });
|
|
521
522
|
this.#runOrder.push(runId);
|
|
522
523
|
}
|
|
524
|
+
updateRunLimit(runId, limit) {
|
|
525
|
+
const run = this.#runs.get(runId);
|
|
526
|
+
if (!run)
|
|
527
|
+
throw new WorkflowError("INTERNAL_ERROR", `Unknown scheduler run: ${runId}`);
|
|
528
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > this.sessionLimit)
|
|
529
|
+
throw new WorkflowError("INVALID_SETTINGS", "Invalid run concurrency");
|
|
530
|
+
run.limit = limit;
|
|
531
|
+
this.#dispatch();
|
|
532
|
+
}
|
|
523
533
|
spawn(runId, prompt, options, parentId) {
|
|
524
534
|
const run = this.#runs.get(runId);
|
|
525
535
|
if (!run)
|
package/dist/src/cli.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { type DoctorOptions } from "./doctor.js";
|
|
3
|
+
import { type DoctorCleanupOptions } from "./doctor-cleanup.js";
|
|
3
4
|
import { type JsonSchema, type JsonValue } from "./index.js";
|
|
4
5
|
import type { WorkflowCatalogFunction } from "./index.js";
|
|
5
6
|
export interface CliOptions extends DoctorOptions {
|
|
@@ -12,4 +13,5 @@ export interface CliOptions extends DoctorOptions {
|
|
|
12
13
|
}
|
|
13
14
|
export declare function formatWorkflowCliHelp(fn: WorkflowCatalogFunction, command?: string): string;
|
|
14
15
|
export declare function parseWorkflowCliArgs(schema: JsonSchema, rawArgs: readonly string[]): Record<string, JsonValue>;
|
|
16
|
+
export declare function parseDoctorCleanupArgs(rawArgs: readonly string[]): Required<Pick<DoctorCleanupOptions, "olderThanDays" | "yes">>;
|
|
15
17
|
export declare function runCli(args: readonly string[], options?: CliOptions, write?: (text: string) => void): Promise<number>;
|
package/dist/src/cli.js
CHANGED
|
@@ -7,7 +7,8 @@ import { fileURLToPath, pathToFileURL } from "node:url";
|
|
|
7
7
|
import { ProjectTrustStore, SessionManager, SettingsManager, createAgentSessionFromServices, createAgentSessionServices, getAgentDir, hasTrustRequiringProjectResources } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { Value } from "typebox/value";
|
|
9
9
|
import { doctor, doctorExitCode, formatDoctorReport } from "./doctor.js";
|
|
10
|
-
import
|
|
10
|
+
import { doctorCleanup, doctorCleanupExitCode, formatDoctorCleanupReport } from "./doctor-cleanup.js";
|
|
11
|
+
import workflowExtension, { formatWorkflowProgress, truncateWorkflowProgress, workflowCatalog, workflowSettingsPath } from "./index.js";
|
|
11
12
|
import { runSessionInspector, transcriptFileLines } from "./session-inspector.js";
|
|
12
13
|
function object(value) { return typeof value === "object" && value !== null && !Array.isArray(value); }
|
|
13
14
|
function has(value, key) { return Object.prototype.hasOwnProperty.call(value, key); }
|
|
@@ -193,6 +194,34 @@ function launcherHelpLines() {
|
|
|
193
194
|
}
|
|
194
195
|
function workflowUsage() { return [`Usage: pi-extensible-workflows run <workflow-name> [workflow arguments] | export <workflow-name> [--name <command>] [--output <path>] [--force]`, "", "Launcher options:", ...launcherHelpLines()].join("\n") + "\n"; }
|
|
195
196
|
function exportUsage() { return [`Usage: pi-extensible-workflows export <workflow-name> [--name <command>] [--output <path>] [--force]`, "", "Launcher options:", ...launcherHelpLines()].join("\n") + "\n"; }
|
|
197
|
+
export function parseDoctorCleanupArgs(rawArgs) {
|
|
198
|
+
let olderThanDays = 90;
|
|
199
|
+
let yes = false;
|
|
200
|
+
let seenDays = false;
|
|
201
|
+
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
202
|
+
const token = rawArgs[index];
|
|
203
|
+
if (token === "--yes") {
|
|
204
|
+
yes = true;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
const inline = token.startsWith("--older-than-days=") ? token.slice("--older-than-days=".length) : undefined;
|
|
208
|
+
if (token === "--older-than-days" || inline !== undefined) {
|
|
209
|
+
if (seenDays)
|
|
210
|
+
throw new Error("--older-than-days may only be provided once");
|
|
211
|
+
const raw = inline ?? rawArgs[++index];
|
|
212
|
+
if (raw === undefined || !/^[1-9]\d*$/.test(raw))
|
|
213
|
+
throw new Error("older-than-days must be a positive integer");
|
|
214
|
+
const parsed = Number(raw);
|
|
215
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1)
|
|
216
|
+
throw new Error("older-than-days must be a positive integer");
|
|
217
|
+
olderThanDays = parsed;
|
|
218
|
+
seenDays = true;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
throw new Error(`Unknown cleanup option: ${token}`);
|
|
222
|
+
}
|
|
223
|
+
return { olderThanDays, yes };
|
|
224
|
+
}
|
|
196
225
|
function stripTrustOptions(rawArgs) {
|
|
197
226
|
const args = [];
|
|
198
227
|
let trustOverride;
|
|
@@ -278,7 +307,7 @@ async function createWorkflowRuntime(options, shutdownHandlers = []) {
|
|
|
278
307
|
const workflowTool = tools.find((tool) => object(tool) && tool.name === "workflow");
|
|
279
308
|
if (!workflowTool)
|
|
280
309
|
throw new Error("The workflow runtime could not be initialized");
|
|
281
|
-
return { catalog: workflowCatalog(), services, workflowTool, shutdownHandlers };
|
|
310
|
+
return { catalog: workflowCatalog({ cwd, projectTrusted: settingsManager.isProjectTrusted(), globalSettingsPath: workflowSettingsPath(agentDir) }), services, workflowTool, shutdownHandlers };
|
|
282
311
|
}
|
|
283
312
|
function availableModelInfo(services, available = false) {
|
|
284
313
|
const models = available ? services.modelRuntime.getAvailableSnapshot() : services.modelRuntime.getModels();
|
|
@@ -512,6 +541,23 @@ export async function runCli(args, options = {}, write = (text) => { process.std
|
|
|
512
541
|
write(formatDoctorReport(report));
|
|
513
542
|
return doctorExitCode(report);
|
|
514
543
|
}
|
|
544
|
+
if (args[0] === "doctor" && args[1] === "cleanup") {
|
|
545
|
+
if (args.slice(2).some((arg) => arg === "--help" || arg === "-h")) {
|
|
546
|
+
write("Usage: pi-extensible-workflows doctor cleanup [--older-than-days <days>] [--yes]\n");
|
|
547
|
+
return 0;
|
|
548
|
+
}
|
|
549
|
+
try {
|
|
550
|
+
const parsed = parseDoctorCleanupArgs(args.slice(2));
|
|
551
|
+
const cleanupOptions = { ...parsed, ...(options.cwd !== undefined ? { cwd: options.cwd } : {}) };
|
|
552
|
+
const report = await doctorCleanup(cleanupOptions);
|
|
553
|
+
write(formatDoctorCleanupReport(report));
|
|
554
|
+
return doctorCleanupExitCode(report);
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
stderr(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
558
|
+
return 1;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
515
561
|
if (args[0] === "inspect" && args.length <= 2) {
|
|
516
562
|
try {
|
|
517
563
|
await (options.inspect ?? runSessionInspector)(args[1]);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface DoctorCleanupOptions {
|
|
2
|
+
cwd?: string;
|
|
3
|
+
home?: string;
|
|
4
|
+
olderThanDays?: number;
|
|
5
|
+
yes?: boolean;
|
|
6
|
+
now?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface CleanupRunResult {
|
|
9
|
+
sessionId: string;
|
|
10
|
+
runId: string;
|
|
11
|
+
action: "candidate" | "skipped" | "deleted" | "failed";
|
|
12
|
+
state: string;
|
|
13
|
+
stateMtimeMs: number;
|
|
14
|
+
path: string;
|
|
15
|
+
reason?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface CleanupFailure {
|
|
18
|
+
sessionId: string;
|
|
19
|
+
runId?: string;
|
|
20
|
+
message: string;
|
|
21
|
+
}
|
|
22
|
+
export interface CleanupSessionReport {
|
|
23
|
+
sessionId: string;
|
|
24
|
+
path: string;
|
|
25
|
+
status: "preview" | "cleaned" | "skipped" | "failed";
|
|
26
|
+
reason?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface DoctorCleanupReport {
|
|
29
|
+
cwd: string;
|
|
30
|
+
cutoffMs: number;
|
|
31
|
+
olderThanDays: number;
|
|
32
|
+
yes: boolean;
|
|
33
|
+
sessions: readonly CleanupSessionReport[];
|
|
34
|
+
candidates: readonly CleanupRunResult[];
|
|
35
|
+
skipped: readonly CleanupRunResult[];
|
|
36
|
+
deleted: readonly CleanupRunResult[];
|
|
37
|
+
failures: readonly CleanupFailure[];
|
|
38
|
+
}
|
|
39
|
+
export declare function doctorCleanup(options?: DoctorCleanupOptions): Promise<DoctorCleanupReport>;
|
|
40
|
+
export declare function doctorCleanupExitCode(report: DoctorCleanupReport): 0 | 1;
|
|
41
|
+
export declare function formatDoctorCleanupReport(report: DoctorCleanupReport): string;
|