pi-workflow-engine 1.3.0 → 1.4.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/.pi/extensions/pi-workflow-engine/index.ts +1 -1
- package/.pi/extensions/pi-workflow-engine/src/background-workflows.ts +2 -7
- package/.pi/extensions/pi-workflow-engine/src/workflow-result-context.ts +41 -5
- package/.pi/extensions/pi-workflow-engine/src/workflow-settings.ts +3 -3
- package/USAGE.md +3 -3
- package/package.json +1 -1
|
@@ -616,7 +616,7 @@ function registerWorkflowTool(
|
|
|
616
616
|
parallelSubmissionLimit: Type.Optional(Type.Number({ description: "Optional limit for eagerly submitted parallel thunks" })),
|
|
617
617
|
maxAgents: Type.Optional(
|
|
618
618
|
Type.Integer({
|
|
619
|
-
description: `Maximum live model calls started across this run; replay hits are excluded; clamped to ${WORKFLOW_MAX_AGENTS_MIN}-${WORKFLOW_MAX_AGENTS_MAX}`,
|
|
619
|
+
description: `Maximum live model calls started across this run; replay hits are excluded; clamped to ${WORKFLOW_MAX_AGENTS_MIN}-${WORKFLOW_MAX_AGENTS_MAX}. Leave unset for a registered workflow unless the user asks for a cap: it sizes its own stages`,
|
|
620
620
|
}),
|
|
621
621
|
),
|
|
622
622
|
agentTimeoutMs: Type.Optional(
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
1
|
import { SessionManager, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
2
|
import { WorkflowAbortError, WorkflowPauseError } from "./cancellation.ts";
|
|
4
3
|
import { isRecord } from "./guards.ts";
|
|
5
|
-
import { WORKFLOW_RUNS_DIR } from "./journal.ts";
|
|
6
4
|
import type { WorkflowBackgroundOrigin } from "./types.ts";
|
|
7
5
|
import {
|
|
8
6
|
stopWorkflowRunRecord,
|
|
@@ -11,7 +9,7 @@ import {
|
|
|
11
9
|
type WorkflowRunState,
|
|
12
10
|
} from "./workflow-run-record.ts";
|
|
13
11
|
import { updateWorkflowRunDelivery, type WorkflowRunDelivery } from "./workflow-run-background.ts";
|
|
14
|
-
import { ProjectWorkflowRunStore,
|
|
12
|
+
import { ProjectWorkflowRunStore, type WorkflowRunStore } from "./workflow-run-store.ts";
|
|
15
13
|
import { truncateText } from "./text.ts";
|
|
16
14
|
import { formatWorkflowUsageLine } from "./usage.ts";
|
|
17
15
|
import { workflowResultSummary } from "./workflow-execution.ts";
|
|
@@ -375,10 +373,7 @@ function wokenDeliveryBody(record: WorkflowRunRecord, summary: string): string {
|
|
|
375
373
|
|
|
376
374
|
/** The retained record, script and journal, relative to the workflow cwd like the synchronous result's lines. */
|
|
377
375
|
function backgroundRunFileLines(record: WorkflowRunRecord): string[] {
|
|
378
|
-
return
|
|
379
|
-
`Record: ${join(WORKFLOW_RUNS_DIR, `${record.runId}${WORKFLOW_RUN_RECORD_SUFFIX}`)}`,
|
|
380
|
-
...formatWorkflowRunFileLines(record.runId, record.scriptFile !== undefined),
|
|
381
|
-
];
|
|
376
|
+
return formatWorkflowRunFileLines(record.runId, record.scriptFile !== undefined);
|
|
382
377
|
}
|
|
383
378
|
|
|
384
379
|
function sessionHasBackgroundDelivery(ctx: ExtensionContext, runId: string): boolean {
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import {
|
|
2
|
+
import { Type, type Static } from "typebox";
|
|
3
|
+
import { Value } from "typebox/value";
|
|
4
|
+
import {
|
|
5
|
+
AdvisoryVerifierVerdictSchema,
|
|
6
|
+
formatAdvisoryLocation,
|
|
7
|
+
isAdvisoryReport,
|
|
8
|
+
type AdvisoryFinding,
|
|
9
|
+
type AdvisoryReport,
|
|
10
|
+
} from "./advisory-schema.ts";
|
|
3
11
|
import { isRecord } from "./guards.ts";
|
|
4
12
|
import { WORKFLOW_RUNS_DIR } from "./journal.ts";
|
|
5
13
|
import { workflowRunScriptFile } from "./workflow-run-record.ts";
|
|
14
|
+
import { WORKFLOW_RUN_RECORD_SUFFIX } from "./workflow-run-store.ts";
|
|
6
15
|
import { toReviewIssues } from "./review/review-issues.ts";
|
|
7
16
|
import { formatPerfSummary } from "./perf.ts";
|
|
8
17
|
import { prettyJson } from "./text.ts";
|
|
@@ -35,21 +44,31 @@ export function formatWorkflowRunLine(run: { readonly runId: string; readonly re
|
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
/**
|
|
38
|
-
* Where the host model can read what the run did, relative to the workflow cwd: the
|
|
39
|
-
* can edit and rerun with `scriptPath`, and the replay journal of completed
|
|
47
|
+
* Where the host model can read what the run did, relative to the workflow cwd: the record holding the full
|
|
48
|
+
* result, the persisted script it can edit and rerun with `scriptPath`, and the replay journal of completed
|
|
49
|
+
* agent results.
|
|
40
50
|
*/
|
|
41
51
|
export function formatWorkflowRunFileLines(runId: string, scriptPersisted: boolean): string[] {
|
|
42
52
|
return [
|
|
53
|
+
`Record: ${join(WORKFLOW_RUNS_DIR, `${runId}${WORKFLOW_RUN_RECORD_SUFFIX}`)}`,
|
|
43
54
|
...(scriptPersisted ? [`Script: ${join(WORKFLOW_RUNS_DIR, workflowRunScriptFile(runId))}`] : []),
|
|
44
55
|
`Journal: ${join(WORKFLOW_RUNS_DIR, `${runId}.jsonl`)}`,
|
|
45
56
|
"If the result is empty or unexpected, read the journal before diagnosing.",
|
|
46
57
|
];
|
|
47
58
|
}
|
|
48
59
|
|
|
60
|
+
/** A verified candidate as lens reviews retain it beside their report, reduced to the fields the host listing reads. */
|
|
61
|
+
const VerifiedCandidateSchema = Type.Object({
|
|
62
|
+
sourceCandidateIds: Type.Array(Type.String()),
|
|
63
|
+
summary: Type.String(),
|
|
64
|
+
verdict: AdvisoryVerifierVerdictSchema,
|
|
65
|
+
});
|
|
66
|
+
type VerifiedCandidate = Static<typeof VerifiedCandidateSchema>;
|
|
67
|
+
|
|
49
68
|
/** A workflow result as the host model reads it: advisory reports as findings, other values as bounded JSON. */
|
|
50
69
|
export function formatResultForContext(result: unknown): string {
|
|
51
70
|
if (typeof result === "string") return result;
|
|
52
|
-
if (isAdvisoryReport(result)) return formatAdvisoryReportForContext(result);
|
|
71
|
+
if (isAdvisoryReport(result)) return formatAdvisoryReportForContext(result, unreportedCandidates(result));
|
|
53
72
|
const summary = workflowResultSummary(result);
|
|
54
73
|
if (summary !== undefined && isRecord(result) && Object.keys(result).length === 1) return summary;
|
|
55
74
|
const json = formatResultJson(result);
|
|
@@ -57,7 +76,20 @@ export function formatResultForContext(result: unknown): string {
|
|
|
57
76
|
return summary === undefined ? json : `${summary}\n\n${json}`;
|
|
58
77
|
}
|
|
59
78
|
|
|
60
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Verified candidates that no finding draws on: ones synthesis left out under the reporting bar, and refuted
|
|
81
|
+
* ones. Listing them lets the host resolve the candidate IDs a synthesis summary cites when it explains an
|
|
82
|
+
* exclusion, without reading the run record.
|
|
83
|
+
*/
|
|
84
|
+
function unreportedCandidates(report: AdvisoryReport & { readonly verification?: unknown }): VerifiedCandidate[] {
|
|
85
|
+
const { verification } = report;
|
|
86
|
+
if (!Array.isArray(verification)) return [];
|
|
87
|
+
const reported = new Set(report.findings.flatMap((finding) => finding.sourceCandidateIds ?? []));
|
|
88
|
+
return verification.filter((entry): entry is VerifiedCandidate =>
|
|
89
|
+
Value.Check(VerifiedCandidateSchema, entry) && !entry.sourceCandidateIds.some((id) => reported.has(id)));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function formatAdvisoryReportForContext(report: AdvisoryReport, unreported: readonly VerifiedCandidate[]): string {
|
|
61
93
|
const lines = [report.summary];
|
|
62
94
|
const issues = toReviewIssues(report);
|
|
63
95
|
if (issues.length > 0) {
|
|
@@ -75,6 +107,10 @@ function formatAdvisoryReportForContext(report: AdvisoryReport): string {
|
|
|
75
107
|
);
|
|
76
108
|
}
|
|
77
109
|
}
|
|
110
|
+
if (unreported.length > 0) {
|
|
111
|
+
lines.push("", "Verified but not reported (verdict, candidate IDs):",
|
|
112
|
+
...unreported.map((candidate) => `- ${candidate.verdict} ${candidate.sourceCandidateIds.join(", ")}: ${candidate.summary}`));
|
|
113
|
+
}
|
|
78
114
|
if (report.gaps && report.gaps.length > 0) {
|
|
79
115
|
lines.push("", "Coverage gaps:", ...report.gaps.map((gap) => `- ${gap}`));
|
|
80
116
|
}
|
|
@@ -65,7 +65,7 @@ export function setWorkflowSize(configPath: string, size: WorkflowSizeGuideline)
|
|
|
65
65
|
/** The catalog line for a size, or undefined when the guideline is off. */
|
|
66
66
|
export function workflowSizeGuidelineLine(size: WorkflowSizeGuideline): string | undefined {
|
|
67
67
|
if (size === "off") return undefined;
|
|
68
|
-
return `Workflow size guideline: ${size} — keep each workflow under ${SIZE_AGENT_LIMITS[size]} agents unless the user asks for more; the user changes this with /workflow:size.`;
|
|
68
|
+
return `Workflow size guideline: ${size} — keep each workflow script you author under ${SIZE_AGENT_LIMITS[size]} agents unless the user asks for more; registered workflows size their own stages, so run them without maxAgents unless the user sets a cap. The user changes this with /workflow:size.`;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
type WorkflowSizeCommand =
|
|
@@ -76,7 +76,7 @@ type WorkflowSizeCommand =
|
|
|
76
76
|
const USAGE = "Usage: /workflow:size [small|medium|large|off] [--user|--project]";
|
|
77
77
|
const SIZE_COMPLETIONS = WORKFLOW_SIZE_GUIDELINES.map((value) => ({
|
|
78
78
|
value,
|
|
79
|
-
description: value === "off" ? "No size guideline in the prompt" : `Keep each workflow under ${SIZE_AGENT_LIMITS[value]} agents`,
|
|
79
|
+
description: value === "off" ? "No size guideline in the prompt" : `Keep each authored workflow under ${SIZE_AGENT_LIMITS[value]} agents`,
|
|
80
80
|
}));
|
|
81
81
|
const SCOPE_COMPLETIONS = [
|
|
82
82
|
{ value: "--user", description: "Write the user-level workflow settings" },
|
|
@@ -110,7 +110,7 @@ export function formatWorkflowSize(resolved: ResolvedWorkflowSize): string {
|
|
|
110
110
|
const source = resolved.source === "default" ? "default" : `${resolved.source} config ${resolved.configPath}`;
|
|
111
111
|
const rule = resolved.size === "off"
|
|
112
112
|
? "no size guideline is added to the prompt"
|
|
113
|
-
: `keep each workflow under ${SIZE_AGENT_LIMITS[resolved.size]} agents unless the user asks for more`;
|
|
113
|
+
: `keep each authored workflow script under ${SIZE_AGENT_LIMITS[resolved.size]} agents unless the user asks for more; registered workflows are not capped by it`;
|
|
114
114
|
return [
|
|
115
115
|
`Workflow size guideline: ${resolved.size} (${source}) — ${rule}.`,
|
|
116
116
|
`The maxAgents run limit (default ${DEFAULT_WORKFLOW_MAX_AGENTS}) stays the hard cap on live agents per run.`,
|
package/USAGE.md
CHANGED
|
@@ -207,13 +207,13 @@ The catalog ends with one line telling the host agent how large to make each wor
|
|
|
207
207
|
|
|
208
208
|
```text
|
|
209
209
|
/workflow:size # show the current guideline and where it comes from
|
|
210
|
-
/workflow:size small # keep each workflow under 5 agents
|
|
210
|
+
/workflow:size small # keep each authored workflow under 5 agents
|
|
211
211
|
/workflow:size medium # under 10 agents (the default)
|
|
212
212
|
/workflow:size large --project # under 25 agents, for this project only
|
|
213
213
|
/workflow:size off # leave the line out
|
|
214
214
|
```
|
|
215
215
|
|
|
216
|
-
The line reads, for example, `Workflow size guideline: medium — keep each workflow under 10 agents unless the user asks for more; the user changes this with /workflow:size.` The setting is stored like the model profiles: `~/.pi/agent/workflow-settings.json` for the user, overridden by `<project>/.pi/workflow-settings.json`; setting a size writes the user file unless `--project` is given. A minimal file is `{ "size": "small" }`. Changing it rebuilds the current session's catalog once.
|
|
216
|
+
The line reads, for example, `Workflow size guideline: medium — keep each workflow script you author under 10 agents unless the user asks for more; registered workflows size their own stages, so run them without maxAgents unless the user sets a cap. The user changes this with /workflow:size.` The setting is stored like the model profiles: `~/.pi/agent/workflow-settings.json` for the user, overridden by `<project>/.pi/workflow-settings.json`; setting a size writes the user file unless `--project` is given. A minimal file is `{ "size": "small" }`. Changing it rebuilds the current session's catalog once.
|
|
217
217
|
|
|
218
218
|
The guideline only shapes what the host agent writes. The `maxAgents` run limit (default `64`, see [Runtime knobs](#runtime-knobs)) stays the separate hard cap: once a run has started that many live agents, further `agent()` calls fail with `WorkflowAgentLimitError`, whatever the guideline says.
|
|
219
219
|
|
|
@@ -287,7 +287,7 @@ Persistence is intentionally bounded and private by default. Raw workflow args,
|
|
|
287
287
|
|
|
288
288
|
An inline run (a tool `script` or `scriptPath`) also saves its script to `.pi/.workflow-runs/<run-id>.script.js`, a separate file the record only references, because the record never holds prompts. The script holds nothing new: pi's session transcript already contains it as the tool call's arguments. A pruned record takes its script with it. Whenever the engine creates `.pi/.workflow-runs/`, it adds a `.gitignore` containing `*` so run files stay out of the project's git history; an existing `.gitignore` there is never replaced.
|
|
289
289
|
|
|
290
|
-
The result handed to the host agent names the run's files after the run ID: `Script:` for an inline run, and `Journal:` with a reminder to read the journal before diagnosing an empty or unexpected result. A failed
|
|
290
|
+
The result handed to the host agent names the run's files after the run ID: `Record:` (which holds the full result when the text is truncated), `Script:` for an inline run, and `Journal:` with a reminder to read the journal before diagnosing an empty or unexpected result. A failed run lists the same lines after its error, a background start names the script too, and a background delivery names the same files (see [Background workflows](#background-workflows)). A review result also lists the verified candidates that no reported finding draws on, with their verdicts and candidate IDs, so the host can resolve the IDs a synthesis summary cites when it explains what it left out. To iterate, edit the script and rerun it with `scriptPath` (see [Author an inline workflow](#author-an-inline-workflow)).
|
|
291
291
|
|
|
292
292
|
### Background workflows
|
|
293
293
|
|