pi-workflow-engine 1.1.0 → 1.3.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 +199 -85
- package/.pi/extensions/pi-workflow-engine/src/advisory-challenge.ts +5 -2
- package/.pi/extensions/pi-workflow-engine/src/advisory-schema.ts +8 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-limits.ts +4 -0
- package/.pi/extensions/pi-workflow-engine/src/agent-runner-types.ts +3 -1
- package/.pi/extensions/pi-workflow-engine/src/agent-runner.ts +18 -4
- package/.pi/extensions/pi-workflow-engine/src/agent-session.ts +34 -7
- package/.pi/extensions/pi-workflow-engine/src/background-workflow-tool.ts +9 -3
- package/.pi/extensions/pi-workflow-engine/src/background-workflows.ts +51 -8
- package/.pi/extensions/pi-workflow-engine/src/cancellation.ts +14 -0
- package/.pi/extensions/pi-workflow-engine/src/codemode-slots.ts +73 -0
- package/.pi/extensions/pi-workflow-engine/src/config-file.ts +52 -0
- package/.pi/extensions/pi-workflow-engine/src/discovery.ts +127 -58
- package/.pi/extensions/pi-workflow-engine/src/dynamax.ts +112 -31
- package/.pi/extensions/pi-workflow-engine/src/engine.ts +75 -18
- package/.pi/extensions/pi-workflow-engine/src/filesystem-error.ts +5 -0
- package/.pi/extensions/pi-workflow-engine/src/inline-workflow.ts +7 -1
- package/.pi/extensions/pi-workflow-engine/src/journal.ts +22 -4
- package/.pi/extensions/pi-workflow-engine/src/model-profiles.ts +9 -47
- package/.pi/extensions/pi-workflow-engine/src/progress-types.ts +1 -1
- package/.pi/extensions/pi-workflow-engine/src/progress.ts +32 -4
- package/.pi/extensions/pi-workflow-engine/src/prompt-sections.ts +15 -0
- package/.pi/extensions/pi-workflow-engine/src/research-evidence.ts +4 -4
- package/.pi/extensions/pi-workflow-engine/src/resume-context.ts +1 -1
- package/.pi/extensions/pi-workflow-engine/src/types.ts +54 -9
- package/.pi/extensions/pi-workflow-engine/src/ui/kit.ts +1 -0
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-format.ts +9 -3
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-inspector.ts +69 -18
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-runs-browser.ts +17 -6
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-tool-renderer.ts +8 -3
- package/.pi/extensions/pi-workflow-engine/src/ui/workflow-widget.ts +16 -8
- package/.pi/extensions/pi-workflow-engine/src/workflow-advisory-utils.ts +82 -23
- package/.pi/extensions/pi-workflow-engine/src/workflow-catalog.ts +79 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-execution.ts +6 -2
- package/.pi/extensions/pi-workflow-engine/src/workflow-input.ts +13 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-module.ts +21 -4
- package/.pi/extensions/pi-workflow-engine/src/workflow-result-context.ts +26 -6
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-background.ts +2 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-controller.ts +69 -13
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-history.ts +23 -4
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-record.ts +27 -1
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-seal.ts +83 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-run-store.ts +30 -4
- package/.pi/extensions/pi-workflow-engine/src/workflow-script-file.ts +50 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-settings.ts +147 -0
- package/.pi/extensions/pi-workflow-engine/src/workflow-tool-request.ts +61 -0
- package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +59 -22
- package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +7 -4
- package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +7 -4
- package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +7 -4
- package/.pi/extensions/pi-workflow-engine/workflows/research.ts +12 -4
- package/README.md +11 -3
- package/USAGE.md +124 -23
- package/package.json +13 -4
- package/skills/workflow-authoring/SKILL.md +162 -0
|
@@ -4,11 +4,19 @@ import { getSelectListTheme, keyText, VERSION, type ExtensionAPI, type Extension
|
|
|
4
4
|
import type { AutocompleteItem, SelectItem } from "@earendil-works/pi-tui";
|
|
5
5
|
import type { WorkflowProgressSnapshot } from "./src/progress-types.ts";
|
|
6
6
|
import type { WorkflowRunState } from "./src/workflow-run-record.ts";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
WORKFLOW_TOOL_NAME,
|
|
9
|
+
type LoadedWorkflow,
|
|
10
|
+
type WorkflowModule,
|
|
11
|
+
type WorkflowProgressSource,
|
|
12
|
+
type WorkflowRef,
|
|
13
|
+
type WorkflowRunMetadata,
|
|
14
|
+
} from "./src/types.ts";
|
|
8
15
|
import { showWorkflowInspector } from "./src/ui/workflow-inspector.ts";
|
|
9
16
|
import { WorkflowPicker } from "./src/ui/workflow-picker.ts";
|
|
10
17
|
import type { PerfSink } from "./src/perf.ts";
|
|
11
|
-
import {
|
|
18
|
+
import type { DiscoverWorkflowsOptions } from "./src/discovery.ts";
|
|
19
|
+
import { registerDynamax, resolveTurnBudget, type DynamaxHandle } from "./src/dynamax.ts";
|
|
12
20
|
import { sessionKey } from "./src/session-identity.ts";
|
|
13
21
|
import { resolveDynamaxShortcuts, type DynamaxShortcuts } from "./src/dynamax-shortcuts.ts";
|
|
14
22
|
import { ReviewSessionCoordinator } from "./src/review/review-session-coordinator.ts";
|
|
@@ -32,7 +40,7 @@ import {
|
|
|
32
40
|
WORKFLOW_USAGE_LIMIT_DELAY_MIN_MS,
|
|
33
41
|
} from "./src/options.ts";
|
|
34
42
|
import { executeWorkflowInvocation, type WorkflowResultEnvelope } from "./src/workflow-execution.ts";
|
|
35
|
-
import { formatWorkflowResultForContext } from "./src/workflow-result-context.ts";
|
|
43
|
+
import { formatWorkflowResultForContext, formatWorkflowRunFileLines, formatWorkflowRunLine } from "./src/workflow-result-context.ts";
|
|
36
44
|
import { registerWorkflowModelProfileCommand } from "./src/model-profile-command.ts";
|
|
37
45
|
import { BackgroundWorkflowCoordinator } from "./src/background-workflows.ts";
|
|
38
46
|
import { backgroundUnavailableResult, startBackgroundWorkflowTool } from "./src/background-workflow-tool.ts";
|
|
@@ -43,13 +51,22 @@ import { formatWorkflowInspection, workflowInspectionSnapshot } from "./src/ui/w
|
|
|
43
51
|
import { workflowUsageForPi, type WorkflowUsageSnapshot } from "./src/usage.ts";
|
|
44
52
|
import { unknownErrorMessage } from "./src/unknown-error.ts";
|
|
45
53
|
import { createHostToolBridge } from "./src/host-tools.ts";
|
|
54
|
+
import { appendSectionToForcedPrompt } from "./src/prompt-sections.ts";
|
|
55
|
+
import { WORKFLOWS_SECTION, WorkflowCatalogPrompt } from "./src/workflow-catalog.ts";
|
|
56
|
+
import { registerWorkflowSizeCommand, resolveWorkflowSize, workflowSettingsPaths } from "./src/workflow-settings.ts";
|
|
57
|
+
import { isRecord } from "./src/guards.ts";
|
|
58
|
+
import { INVALID_WORKFLOW_INVOCATION_MESSAGE, normalizeWorkflowToolRequest, workflowToolReach } from "./src/workflow-tool-request.ts";
|
|
59
|
+
import { workflowArgsText } from "./src/workflow-input.ts";
|
|
46
60
|
|
|
47
61
|
/** Extension root (this file lives in <repo>/.pi/extensions/pi-workflow-engine/index.ts). */
|
|
48
62
|
const EXTENSION_DIR = fileURLToPath(new URL(".", import.meta.url));
|
|
63
|
+
/** The package's authoring skill, named by path so the guideline still resolves when skills are filtered out. */
|
|
64
|
+
const WORKFLOW_AUTHORING_SKILL_PATH = fileURLToPath(new URL("../../../skills/workflow-authoring/SKILL.md", import.meta.url));
|
|
49
65
|
|
|
50
66
|
type DiscoveryModule = typeof import("./src/discovery.ts");
|
|
51
67
|
type EngineModule = typeof import("./src/engine.ts");
|
|
52
68
|
type InlineWorkflowModule = typeof import("./src/inline-workflow.ts");
|
|
69
|
+
type WorkflowScriptFileModule = typeof import("./src/workflow-script-file.ts");
|
|
53
70
|
|
|
54
71
|
async function loadDiscovery(): Promise<DiscoveryModule> {
|
|
55
72
|
return await import("./src/discovery.ts");
|
|
@@ -63,18 +80,47 @@ async function loadInlineWorkflow(): Promise<InlineWorkflowModule> {
|
|
|
63
80
|
return await import("./src/inline-workflow.ts");
|
|
64
81
|
}
|
|
65
82
|
|
|
83
|
+
async function loadWorkflowScriptFile(): Promise<WorkflowScriptFileModule> {
|
|
84
|
+
return await import("./src/workflow-script-file.ts");
|
|
85
|
+
}
|
|
86
|
+
|
|
66
87
|
async function createInvocationPerf(options: ResolvedWorkflowRunOptions): Promise<PerfSink | undefined> {
|
|
67
88
|
if (!options.perf) return undefined;
|
|
68
89
|
const { createPerfRecorder } = await import("./src/perf.ts");
|
|
69
90
|
return createPerfRecorder(true);
|
|
70
91
|
}
|
|
71
92
|
|
|
93
|
+
/** What workflow discovery reads from a session: its cwd, and whether its project's saved scripts may load. */
|
|
94
|
+
export type WorkflowDiscoveryContext = Pick<ExtensionContext, "cwd" | "isProjectTrusted">;
|
|
95
|
+
|
|
96
|
+
/** Project `.pi/workflows` scripts load only in a project pi trusts, like pi's own project resources. */
|
|
97
|
+
function projectWorkflowDir(ctx: WorkflowDiscoveryContext): string | undefined {
|
|
98
|
+
return ctx.isProjectTrusted() ? ctx.cwd : undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Every workflow a session can run, with the project-trust gate applied in this one place. */
|
|
102
|
+
async function discoverForSession(
|
|
103
|
+
ctx: WorkflowDiscoveryContext,
|
|
104
|
+
options: Omit<DiscoverWorkflowsOptions, "projectDir"> = {},
|
|
105
|
+
): Promise<Map<string, LoadedWorkflow>> {
|
|
106
|
+
const { discoverWorkflows } = await loadDiscovery();
|
|
107
|
+
return await discoverWorkflows(EXTENSION_DIR, { ...options, projectDir: projectWorkflowDir(ctx) });
|
|
108
|
+
}
|
|
109
|
+
|
|
72
110
|
/**
|
|
73
|
-
* Resolve an `api.workflow()` reference
|
|
111
|
+
* Resolve an `api.workflow()` reference: a registered workflow by name, or an inline-format script file
|
|
112
|
+
* via `{ scriptPath }` relative to the cwd. Throws on an unknown name, an unreadable scriptPath, or a
|
|
113
|
+
* compile error.
|
|
74
114
|
*/
|
|
75
|
-
export async function resolveWorkflowRef(ref: WorkflowRef, perf?: PerfSink): Promise<LoadedWorkflow> {
|
|
76
|
-
|
|
77
|
-
|
|
115
|
+
export async function resolveWorkflowRef(ctx: WorkflowDiscoveryContext, ref: WorkflowRef, perf?: PerfSink): Promise<LoadedWorkflow> {
|
|
116
|
+
if (typeof ref !== "string") {
|
|
117
|
+
// Inline scripts are untyped, so check the shape the type promises.
|
|
118
|
+
const scriptPath: unknown = isRecord(ref) ? ref.scriptPath : undefined;
|
|
119
|
+
if (typeof scriptPath !== "string") throw new Error("workflow() expects a workflow name or { scriptPath }");
|
|
120
|
+
const { compileWorkflowScriptFile } = await loadWorkflowScriptFile();
|
|
121
|
+
return await compileWorkflowScriptFile(ctx.cwd, scriptPath);
|
|
122
|
+
}
|
|
123
|
+
const workflows = await discoverForSession(ctx, { perf });
|
|
78
124
|
const mod = workflows.get(ref);
|
|
79
125
|
if (!mod) {
|
|
80
126
|
const available = [...workflows.keys()].join(", ") || "(none)";
|
|
@@ -89,7 +135,7 @@ const AUTHOR_TEMP_WORKFLOW_DESCRIPTION = "Ask the host agent to author and run a
|
|
|
89
135
|
|
|
90
136
|
const WORKFLOW_OPTION_COMPLETIONS = [
|
|
91
137
|
{ value: "--inspect", description: "Open the live workflow inspector" },
|
|
92
|
-
{ value: "--refresh", description: "
|
|
138
|
+
{ value: "--refresh", description: "Reload .ts drop-in workflows" },
|
|
93
139
|
{ value: "--perf", description: "Collect workflow performance metrics" },
|
|
94
140
|
{ value: "--result-viewer", description: "Open supported result viewers" },
|
|
95
141
|
{ value: "--no-result-viewer", description: "Skip supported result viewers" },
|
|
@@ -103,11 +149,12 @@ const WORKFLOW_OPTION_COMPLETIONS = [
|
|
|
103
149
|
{ value: "--resume=", description: "Resume from a retained workflow run ID" },
|
|
104
150
|
] as const;
|
|
105
151
|
|
|
106
|
-
|
|
152
|
+
/** Completions get no context, so the project's scripts come from the active session's tracked dir. */
|
|
153
|
+
async function workflowArgumentCompletions(argumentPrefix: string, projectDir: string | undefined): Promise<AutocompleteItem[] | null> {
|
|
107
154
|
const parts = splitArgumentPrefix(argumentPrefix);
|
|
108
155
|
if (parts.completed.length === 0) {
|
|
109
156
|
const { discoverWorkflows } = await loadDiscovery();
|
|
110
|
-
const workflows = await discoverWorkflows(EXTENSION_DIR);
|
|
157
|
+
const workflows = await discoverWorkflows(EXTENSION_DIR, { projectDir });
|
|
111
158
|
return completeCurrentArgument(
|
|
112
159
|
argumentPrefix,
|
|
113
160
|
[...workflows.values()].map((workflow) => ({
|
|
@@ -119,6 +166,21 @@ async function workflowArgumentCompletions(argumentPrefix: string): Promise<Auto
|
|
|
119
166
|
return completeCurrentArgument(argumentPrefix, WORKFLOW_OPTION_COMPLETIONS);
|
|
120
167
|
}
|
|
121
168
|
|
|
169
|
+
/**
|
|
170
|
+
* The active session's trusted project dir, kept for surfaces pi calls without a context. Session
|
|
171
|
+
* replacement invalidates a context, so only this value is kept.
|
|
172
|
+
*/
|
|
173
|
+
function trackProjectWorkflowDir(pi: ExtensionAPI): () => string | undefined {
|
|
174
|
+
let projectDir: string | undefined;
|
|
175
|
+
pi.on("session_start", (_event, ctx) => {
|
|
176
|
+
projectDir = projectWorkflowDir(ctx);
|
|
177
|
+
});
|
|
178
|
+
pi.on("session_shutdown", () => {
|
|
179
|
+
projectDir = undefined;
|
|
180
|
+
});
|
|
181
|
+
return () => projectDir;
|
|
182
|
+
}
|
|
183
|
+
|
|
122
184
|
/** Registered workflows in discovery order (built-ins first), then the author option. */
|
|
123
185
|
async function selectWorkflowValue(workflows: ReadonlyMap<string, WorkflowModule>, ctx: ExtensionCommandContext): Promise<string | undefined> {
|
|
124
186
|
const workflowItems: SelectItem[] = [...workflows.values()].map((workflow) => ({
|
|
@@ -152,6 +214,7 @@ export interface ActiveWorkflowInspection {
|
|
|
152
214
|
readonly startedAt: number;
|
|
153
215
|
readonly snapshot: () => WorkflowProgressSnapshot;
|
|
154
216
|
readonly state?: () => WorkflowRunState;
|
|
217
|
+
readonly skipAgent?: (agentId: number) => boolean;
|
|
155
218
|
}
|
|
156
219
|
|
|
157
220
|
interface SessionWorkflowInspections {
|
|
@@ -166,7 +229,9 @@ export async function openWorkflowInspector(ctx: ExtensionContext, inspection: L
|
|
|
166
229
|
ctx.ui.notify(formatWorkflowInspection(inspection), "info");
|
|
167
230
|
return;
|
|
168
231
|
}
|
|
169
|
-
|
|
232
|
+
// Only a live inspection can skip agents; a finished one is a snapshot.
|
|
233
|
+
const controls = "skipAgent" in inspection ? { skipAgent: inspection.skipAgent } : undefined;
|
|
234
|
+
await showWorkflowInspector(ctx.ui, () => workflowInspectionSnapshot(inspection), () => ({ state: inspection.state?.() }), controls);
|
|
170
235
|
}
|
|
171
236
|
|
|
172
237
|
function workflowInspectionState(pi: ExtensionAPI, ctx: ExtensionContext): SessionWorkflowInspections {
|
|
@@ -189,7 +254,7 @@ async function openAvailableWorkflowInspector(pi: ExtensionAPI, ctx: ExtensionCo
|
|
|
189
254
|
}
|
|
190
255
|
|
|
191
256
|
function bindActiveWorkflowInspection(name: string, args: string, source: WorkflowProgressSource): ActiveWorkflowInspection {
|
|
192
|
-
return { name, args, startedAt: Date.now(), snapshot: () => source.snapshot(), state: source.state };
|
|
257
|
+
return { name, args, startedAt: Date.now(), snapshot: () => source.snapshot(), state: source.state, skipAgent: source.skipAgent };
|
|
193
258
|
}
|
|
194
259
|
|
|
195
260
|
export function buildTemporaryWorkflowAuthorPrompt(brief: string): string {
|
|
@@ -198,22 +263,10 @@ export function buildTemporaryWorkflowAuthorPrompt(brief: string): string {
|
|
|
198
263
|
User brief:
|
|
199
264
|
${brief.trim()}
|
|
200
265
|
|
|
201
|
-
Use the workflow tool with a script argument, not a saved workflow name, and
|
|
266
|
+
Use the workflow tool with a script argument, not a saved workflow name, and read the workflow-authoring skill before writing the script.
|
|
202
267
|
Do not edit files unless the user explicitly requested edits.`;
|
|
203
268
|
}
|
|
204
269
|
|
|
205
|
-
export interface WorkflowToolRequestParams {
|
|
206
|
-
readonly name?: string;
|
|
207
|
-
readonly script?: string;
|
|
208
|
-
readonly resumeFromRunId?: string;
|
|
209
|
-
readonly background?: boolean;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export type WorkflowToolRequest =
|
|
213
|
-
| { readonly kind: "named"; readonly name: string }
|
|
214
|
-
| { readonly kind: "inline"; readonly script: string }
|
|
215
|
-
| { readonly kind: "error"; readonly error: "invalid_workflow_invocation"; readonly message: string };
|
|
216
|
-
|
|
217
270
|
export interface WorkflowToolErrorResult<TDetails = WorkflowToolErrorDetails> {
|
|
218
271
|
readonly content: Array<{ readonly type: "text"; readonly text: string }>;
|
|
219
272
|
readonly details: TDetails;
|
|
@@ -221,23 +274,15 @@ export interface WorkflowToolErrorResult<TDetails = WorkflowToolErrorDetails> {
|
|
|
221
274
|
readonly isError: true;
|
|
222
275
|
}
|
|
223
276
|
|
|
224
|
-
type WorkflowToolErrorDetails =
|
|
277
|
+
type WorkflowToolErrorDetails =
|
|
278
|
+
| { readonly error: "invalid_workflow_invocation" }
|
|
279
|
+
| { readonly error: "inline_compile_error"; readonly message: string }
|
|
280
|
+
| { readonly error: "invalid_script_path"; readonly message: string };
|
|
225
281
|
|
|
226
282
|
function workflowToolError<TDetails>(text: string, details: TDetails): WorkflowToolErrorResult<TDetails> {
|
|
227
283
|
return { content: [{ type: "text", text }], details, isError: true };
|
|
228
284
|
}
|
|
229
285
|
|
|
230
|
-
const INVALID_WORKFLOW_INVOCATION_MESSAGE = "Provide exactly one workflow name or inline workflow script.";
|
|
231
|
-
|
|
232
|
-
export function normalizeWorkflowToolRequest(params: WorkflowToolRequestParams): WorkflowToolRequest {
|
|
233
|
-
const name = params.name?.trim() ?? "";
|
|
234
|
-
const script = params.script?.trim() ?? "";
|
|
235
|
-
const hasName = name.length > 0;
|
|
236
|
-
const hasScript = script.length > 0;
|
|
237
|
-
if (hasName === hasScript) return { kind: "error", error: "invalid_workflow_invocation", message: INVALID_WORKFLOW_INVOCATION_MESSAGE };
|
|
238
|
-
return hasName ? { kind: "named", name } : { kind: "inline", script };
|
|
239
|
-
}
|
|
240
|
-
|
|
241
286
|
export function invalidWorkflowInvocationResult(): WorkflowToolErrorResult {
|
|
242
287
|
return workflowToolError(INVALID_WORKFLOW_INVOCATION_MESSAGE, { error: "invalid_workflow_invocation" });
|
|
243
288
|
}
|
|
@@ -246,6 +291,27 @@ export function inlineCompileErrorResult(message: string): WorkflowToolErrorResu
|
|
|
246
291
|
return workflowToolError(`Inline workflow did not compile: ${message}`, { error: "inline_compile_error", message });
|
|
247
292
|
}
|
|
248
293
|
|
|
294
|
+
function invalidScriptPathResult(message: string): WorkflowToolErrorResult {
|
|
295
|
+
return workflowToolError(message, { error: "invalid_script_path", message });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Compile the tool's inline `script` or `scriptPath` file, shaping contract failures as tool errors. */
|
|
299
|
+
async function compileInlineToolWorkflow(
|
|
300
|
+
cwd: string,
|
|
301
|
+
request: { readonly kind: "inline"; readonly script: string } | { readonly kind: "script-path"; readonly scriptPath: string },
|
|
302
|
+
): Promise<LoadedWorkflow | WorkflowToolErrorResult> {
|
|
303
|
+
const [inline, scriptFile] = await Promise.all([loadInlineWorkflow(), loadWorkflowScriptFile()]);
|
|
304
|
+
try {
|
|
305
|
+
return request.kind === "inline"
|
|
306
|
+
? inline.compileInlineWorkflow(request.script)
|
|
307
|
+
: await scriptFile.compileWorkflowScriptFile(cwd, request.scriptPath);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
if (error instanceof scriptFile.WorkflowScriptPathError) return invalidScriptPathResult(error.message);
|
|
310
|
+
if (error instanceof inline.InlineWorkflowCompileError) return inlineCompileErrorResult(error.message);
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
249
315
|
export type WorkflowPickerSelection =
|
|
250
316
|
| { readonly kind: "run"; readonly name: string; readonly args: string }
|
|
251
317
|
| { readonly kind: "author"; readonly brief: string };
|
|
@@ -288,27 +354,29 @@ export async function sendWorkflowResult(
|
|
|
288
354
|
await reviewSessions.present(ctx, retained, options);
|
|
289
355
|
}
|
|
290
356
|
|
|
357
|
+
/** `input` is a slash command's argument string, or a tool call's string or JSON value. */
|
|
291
358
|
async function executeResolvedWorkflow(
|
|
292
359
|
pi: ExtensionAPI,
|
|
293
360
|
ctx: ExtensionContext,
|
|
294
361
|
name: string,
|
|
295
362
|
mod: LoadedWorkflow,
|
|
296
|
-
|
|
363
|
+
input: unknown,
|
|
297
364
|
options: ResolvedWorkflowRunOptions,
|
|
298
365
|
perfRecorder?: PerfSink,
|
|
299
366
|
): Promise<WorkflowResultEnvelope> {
|
|
300
367
|
const { runResolvedWorkflow } = await loadEngine();
|
|
301
368
|
let liveInspection: ActiveWorkflowInspection | undefined;
|
|
302
369
|
const inspections = workflowInspectionState(pi, ctx);
|
|
370
|
+
const args = workflowArgsText(input);
|
|
303
371
|
return await executeWorkflowInvocation({
|
|
304
372
|
ctx,
|
|
305
373
|
name,
|
|
306
374
|
mod,
|
|
307
|
-
args,
|
|
375
|
+
args: input,
|
|
308
376
|
options,
|
|
309
377
|
perfRecorder,
|
|
310
378
|
runResolvedWorkflow,
|
|
311
|
-
resolveWorkflow: (ref) => resolveWorkflowRef(ref, perfRecorder),
|
|
379
|
+
resolveWorkflow: (ref) => resolveWorkflowRef(ctx, ref, perfRecorder),
|
|
312
380
|
onProgressSource(source) {
|
|
313
381
|
if (source) {
|
|
314
382
|
liveInspection = bindActiveWorkflowInspection(name, args, source);
|
|
@@ -351,10 +419,10 @@ export default function workflowEngine(pi: ExtensionAPI, shortcuts: DynamaxShort
|
|
|
351
419
|
assertSupportedPiVersion(VERSION);
|
|
352
420
|
const reviewSessions = createReviewSessionCoordinator(pi);
|
|
353
421
|
const backgroundWorkflows = new BackgroundWorkflowCoordinator(pi);
|
|
422
|
+
const sessionProjectDir = trackProjectWorkflowDir(pi);
|
|
354
423
|
const workflowRuns = new WorkflowRunController(backgroundWorkflows, {
|
|
355
|
-
async resolveWorkflow(name) {
|
|
356
|
-
|
|
357
|
-
return (await discoverWorkflows(EXTENSION_DIR)).get(name);
|
|
424
|
+
async resolveWorkflow(name, ctx) {
|
|
425
|
+
return (await discoverForSession(ctx)).get(name);
|
|
358
426
|
},
|
|
359
427
|
async execute(ctx, name, workflow, options) {
|
|
360
428
|
const perfRecorder = await createInvocationPerf(options);
|
|
@@ -363,6 +431,15 @@ export default function workflowEngine(pi: ExtensionAPI, shortcuts: DynamaxShort
|
|
|
363
431
|
},
|
|
364
432
|
});
|
|
365
433
|
backgroundWorkflows.onRunSettled((ctx, runId) => workflowRuns.runSettled(ctx, runId));
|
|
434
|
+
const workflowCatalog = new WorkflowCatalogPrompt({
|
|
435
|
+
async discover(ctx) {
|
|
436
|
+
return [...(await discoverForSession(ctx)).values()].map((workflow) => workflow.meta);
|
|
437
|
+
},
|
|
438
|
+
size: (ctx) => resolveWorkflowSize(workflowSettingsPaths(ctx.cwd)).size,
|
|
439
|
+
});
|
|
440
|
+
// Before Dynamax, so the stable catalog section precedes the per-turn opt-in section.
|
|
441
|
+
registerWorkflowCatalog(pi, workflowCatalog);
|
|
442
|
+
registerWorkflowSizeCommand(pi, (ctx) => workflowCatalog.forget(ctx));
|
|
366
443
|
const dynamax = registerDynamax(pi, shortcuts, { openInspector: (ctx) => openAvailableWorkflowInspector(pi, ctx) });
|
|
367
444
|
registerWorkflowModelProfileCommand(pi);
|
|
368
445
|
registerWorkflowRunCommand(pi, workflowRuns);
|
|
@@ -423,7 +500,7 @@ export default function workflowEngine(pi: ExtensionAPI, shortcuts: DynamaxShort
|
|
|
423
500
|
// /workflow <name> [args] — user-invoked.
|
|
424
501
|
pi.registerCommand("workflow", {
|
|
425
502
|
description: "Run a multi-agent workflow: /workflow <name> [args]",
|
|
426
|
-
getArgumentCompletions: workflowArgumentCompletions,
|
|
503
|
+
getArgumentCompletions: (argumentPrefix) => workflowArgumentCompletions(argumentPrefix, sessionProjectDir()),
|
|
427
504
|
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
428
505
|
const invocation = parseWorkflowInvocation(args);
|
|
429
506
|
if (invocation.optionErrors.length > 0) {
|
|
@@ -432,8 +509,7 @@ export default function workflowEngine(pi: ExtensionAPI, shortcuts: DynamaxShort
|
|
|
432
509
|
}
|
|
433
510
|
const options = resolveWorkflowRunOptions(invocation.options);
|
|
434
511
|
const perfRecorder = await createInvocationPerf(options);
|
|
435
|
-
const {
|
|
436
|
-
const workflows = await discoverWorkflows(EXTENSION_DIR, { refresh: invocation.refreshDiscovery, perf: perfRecorder });
|
|
512
|
+
const workflows = await discoverForSession(ctx, { refresh: invocation.refreshDiscovery, perf: perfRecorder });
|
|
437
513
|
const available = [...workflows.keys()].join(", ") || "(none)";
|
|
438
514
|
if (!invocation.name && !ctx.hasUI) {
|
|
439
515
|
ctx.ui.notify(`Usage: /workflow <name> [args]. Available: ${available}`, "warning");
|
|
@@ -463,7 +539,22 @@ export default function workflowEngine(pi: ExtensionAPI, shortcuts: DynamaxShort
|
|
|
463
539
|
},
|
|
464
540
|
});
|
|
465
541
|
|
|
466
|
-
registerWorkflowTool(pi, reviewSessions, backgroundWorkflows, shortcuts);
|
|
542
|
+
registerWorkflowTool(pi, reviewSessions, backgroundWorkflows, shortcuts, dynamax);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** Add the `<workflows>` catalog to every run that can call the workflow tool. */
|
|
546
|
+
function registerWorkflowCatalog(pi: ExtensionAPI, catalog: WorkflowCatalogPrompt): void {
|
|
547
|
+
pi.on("session_start", (_event, ctx) => catalog.forget(ctx));
|
|
548
|
+
pi.on("session_shutdown", (_event, ctx) => catalog.forget(ctx));
|
|
549
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
550
|
+
const options = event.systemPromptOptions;
|
|
551
|
+
// Subagent sessions withhold the workflow tool, so they skip the catalog as well.
|
|
552
|
+
if (!options.selectedTools.includes(WORKFLOW_TOOL_NAME)) return undefined;
|
|
553
|
+
const text = await catalog.section(ctx);
|
|
554
|
+
if (text === undefined) return undefined;
|
|
555
|
+
options.sections[WORKFLOWS_SECTION] = text;
|
|
556
|
+
return appendSectionToForcedPrompt(options, WORKFLOWS_SECTION);
|
|
557
|
+
});
|
|
467
558
|
}
|
|
468
559
|
|
|
469
560
|
/** Result footers advertise the configured triage and inspector shortcuts and pi's current expand key, read at render time. */
|
|
@@ -481,6 +572,7 @@ function registerWorkflowTool(
|
|
|
481
572
|
reviewSessions: ReviewSessionCoordinator,
|
|
482
573
|
backgroundWorkflows: BackgroundWorkflowCoordinator,
|
|
483
574
|
shortcuts: DynamaxShortcuts,
|
|
575
|
+
dynamax: DynamaxHandle,
|
|
484
576
|
): void {
|
|
485
577
|
pi.registerTool({
|
|
486
578
|
name: WORKFLOW_TOOL_NAME,
|
|
@@ -492,31 +584,34 @@ function registerWorkflowTool(
|
|
|
492
584
|
// run would lose its tool row, hit the script's output cap, and parallel runs would each get their own
|
|
493
585
|
// concurrency cap. It also stays declared under `codemode.mode: "only"`, which hides direct tools.
|
|
494
586
|
exposure: "model-only",
|
|
587
|
+
reachesTools: workflowToolReach,
|
|
495
588
|
promptGuidelines: [
|
|
496
|
-
"Use workflow only when the user opted into workflow orchestration via `dynamax`, `/workflow:dynamax
|
|
497
|
-
|
|
498
|
-
"
|
|
499
|
-
"Inline workflow scripts must use the injected `Type`
|
|
500
|
-
"Inline scripts may compose registered workflows in-process via `api.workflow(\"<name>\", args)` (e.g. `await api.workflow(\"code-review\", \"HEAD~3\")`); it returns the sub-workflow's result and nests one level only.",
|
|
501
|
-
"Subagents receive no skills by default. In inline workflows, pass `skills: [\"skill-name\"]` per `agent()` call when the user asks for a skill or a stage should use one; grant only the needed skills.",
|
|
589
|
+
"Use workflow only when the user opted into workflow orchestration via `dynamax`, sticky `/workflow:dynamax`, an explicit request to run/author a workflow, or a command/skill instruction.",
|
|
590
|
+
`Before writing or editing an inline workflow script, read the workflow-authoring skill (${WORKFLOW_AUTHORING_SKILL_PATH}); it is the script API and pattern reference.`,
|
|
591
|
+
"Every workflow tool call must provide exactly one of `name`, `script`, or `scriptPath`.",
|
|
592
|
+
"Inline workflow scripts must use the injected `Type` global for schemas and must not contain imports or dynamic import().",
|
|
502
593
|
"Set `profile: \"small\"`, `\"medium\"`, or `\"big\"` on every `api.agent()` call so the user's central model routes apply; use `model`/`thinkingLevel` only for an intentional override.",
|
|
503
|
-
"Always pass a plain string as the first `api.agent()` argument; build prompts with template strings before calling agent().",
|
|
504
594
|
"When using `isolation: \"worktree\"`, `api.agent()` returns `{ result, patch, changed }`; use `.result` for the answer and `.patch` for the isolated diff.",
|
|
505
|
-
"
|
|
506
|
-
"Add \"codemode\" to an inline agent's `tools` when it should batch or filter many tool calls in one script; its scripts can call only that agent's other allowed tools.",
|
|
507
|
-
"`api.classify({ state, questions }, { label })` answers typed choice/score/bool questions with a classifier model and no chat session; use it for cheap gates such as dedup or triage, and handle its rejection when no classifier is configured.",
|
|
508
|
-
"Host MCP tools reach subagents only in synchronous workflow tool runs: name them in `tools` (e.g. `mcp__server__tool`), or use `toolHints: [\"external-search\"]` to pick up MCP web research tools.",
|
|
509
|
-
"`api.budget` exposes `{ total, spent(), remaining() }` (output tokens). When the run is budgeted, scale fleets from `budget.total` and guard loops with `while (budget.total && budget.remaining() > N) { await api.agent(...) }`; `api.agent()` throws once the ceiling is reached.",
|
|
510
|
-
ADAPTIVE_WORKFLOW_GUIDANCE,
|
|
595
|
+
"Host MCP tools reach subagents only in synchronous workflow tool runs, never in background runs.",
|
|
511
596
|
"Set background: true only when the user explicitly wants the workflow to continue after this tool call; the tool returns a durable run ID and completion is delivered later.",
|
|
512
597
|
"Set autoResumeOnUsageLimit: true only for an explicitly backgrounded workflow when the user wants bounded automatic continuation after a recognized provider usage window.",
|
|
513
598
|
"Set resumeEditedWorkflow: true only with resumeFromRunId when the user explicitly accepts reusing behaviorally identical calls after workflow source edits.",
|
|
514
|
-
"Every workflow tool call must provide exactly one of `name` or `script`, never both.",
|
|
515
599
|
],
|
|
516
600
|
parameters: Type.Object({
|
|
517
|
-
name: Type.Optional(Type.String({ description: "Workflow name, e.g. code-review. Provide exactly one of name or
|
|
518
|
-
script: Type.Optional(Type.String({ description: "Inline workflow script. Provide exactly one of script or
|
|
519
|
-
|
|
601
|
+
name: Type.Optional(Type.String({ description: "Workflow name, e.g. code-review. Provide exactly one of name, script, or scriptPath." })),
|
|
602
|
+
script: Type.Optional(Type.String({ description: "Inline workflow script. Provide exactly one of name, script, or scriptPath." })),
|
|
603
|
+
scriptPath: Type.Optional(
|
|
604
|
+
Type.String({
|
|
605
|
+
description:
|
|
606
|
+
"Path, relative to the working directory, of a file holding an inline workflow script. To iterate, edit in place the persisted .pi/.workflow-runs/<runId>.script.js a result names and rerun it here with resumeFromRunId, which accepts the edits as with resumeEditedWorkflow; editing any other copy in the repository, or a .pi/workflows script, changes repository state, so shared read-only agents rerun live. Provide exactly one of name, script, or scriptPath.",
|
|
607
|
+
}),
|
|
608
|
+
),
|
|
609
|
+
args: Type.Optional(
|
|
610
|
+
Type.Unknown({
|
|
611
|
+
description:
|
|
612
|
+
"Arguments for the workflow: a string (e.g. target or focus) or any JSON value. The workflow reads the value as `api.input` and its text (JSON for non-strings) as `api.args`.",
|
|
613
|
+
}),
|
|
614
|
+
),
|
|
520
615
|
concurrency: Type.Optional(Type.Number({ description: "Optional per-run agent concurrency cap" })),
|
|
521
616
|
parallelSubmissionLimit: Type.Optional(Type.Number({ description: "Optional limit for eagerly submitted parallel thunks" })),
|
|
522
617
|
maxAgents: Type.Optional(
|
|
@@ -551,13 +646,6 @@ function registerWorkflowTool(
|
|
|
551
646
|
description: "Maximum delay accepted from a provider reset hint before automatic resume",
|
|
552
647
|
}),
|
|
553
648
|
),
|
|
554
|
-
budget: Type.Optional(
|
|
555
|
-
Type.Integer({
|
|
556
|
-
minimum: WORKFLOW_BUDGET_MIN,
|
|
557
|
-
maximum: WORKFLOW_BUDGET_MAX,
|
|
558
|
-
description: "Optional output-token ceiling for the run; agent() throws once it is exceeded",
|
|
559
|
-
}),
|
|
560
|
-
),
|
|
561
649
|
perf: Type.Optional(Type.Boolean({ description: "Include workflow performance timing aggregates in the result details" })),
|
|
562
650
|
resumeFromRunId: Type.Optional(Type.String({ minLength: 1, description: "Workflow run id to resume from by replaying matching completed agent results" })),
|
|
563
651
|
resumeEditedWorkflow: Type.Optional(
|
|
@@ -585,6 +673,14 @@ function registerWorkflowTool(
|
|
|
585
673
|
const unavailable = backgroundUnavailableResult(ctx.mode);
|
|
586
674
|
if (unavailable) return unavailable;
|
|
587
675
|
}
|
|
676
|
+
const turnBudget = dynamax.turnBudget(ctx);
|
|
677
|
+
const budget = resolveTurnBudget(turnBudget, { min: WORKFLOW_BUDGET_MIN, max: WORKFLOW_BUDGET_MAX });
|
|
678
|
+
if (budget.kind === "spent") {
|
|
679
|
+
return workflowToolError(
|
|
680
|
+
`The user's +${budget.total} output-token budget for this turn is spent by its earlier workflow runs. Do not start another workflow this turn unless the user raises the budget.`,
|
|
681
|
+
{ error: "turn_budget_spent", total: budget.total },
|
|
682
|
+
);
|
|
683
|
+
}
|
|
588
684
|
|
|
589
685
|
const runOptions = resolveWorkflowRunOptions({
|
|
590
686
|
concurrency: params.concurrency,
|
|
@@ -595,10 +691,11 @@ function registerWorkflowTool(
|
|
|
595
691
|
autoResumeOnUsageLimit: params.autoResumeOnUsageLimit,
|
|
596
692
|
usageLimitMaxAttempts: params.usageLimitMaxAttempts,
|
|
597
693
|
usageLimitMaxDelayMs: params.usageLimitMaxDelayMs,
|
|
598
|
-
budget:
|
|
694
|
+
budget: budget.budget,
|
|
599
695
|
perf: params.perf,
|
|
600
696
|
resumeFromRunId,
|
|
601
|
-
|
|
697
|
+
// Rerunning an edited script file is the point of scriptPath; each call's own replay identity still guards reuse.
|
|
698
|
+
resumeEditedWorkflow: params.resumeEditedWorkflow === true || request.kind === "script-path",
|
|
602
699
|
signal,
|
|
603
700
|
});
|
|
604
701
|
const perfRecorder = await createInvocationPerf(runOptions);
|
|
@@ -606,8 +703,7 @@ function registerWorkflowTool(
|
|
|
606
703
|
let resultName: string;
|
|
607
704
|
|
|
608
705
|
if (request.kind === "named") {
|
|
609
|
-
const
|
|
610
|
-
const workflows = await discoverWorkflows(EXTENSION_DIR, { perf: perfRecorder });
|
|
706
|
+
const workflows = await discoverForSession(ctx, { perf: perfRecorder });
|
|
611
707
|
const named = workflows.get(request.name);
|
|
612
708
|
if (!named) {
|
|
613
709
|
const available = [...workflows.keys()].join(", ") || "(none)";
|
|
@@ -616,23 +712,22 @@ function registerWorkflowTool(
|
|
|
616
712
|
mod = named;
|
|
617
713
|
resultName = request.name;
|
|
618
714
|
} else {
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
} catch (error) {
|
|
623
|
-
if (error instanceof inline.InlineWorkflowCompileError) return inlineCompileErrorResult(error.message);
|
|
624
|
-
throw error;
|
|
625
|
-
}
|
|
715
|
+
const compiled = await compileInlineToolWorkflow(ctx.cwd, request);
|
|
716
|
+
if ("isError" in compiled) return compiled;
|
|
717
|
+
mod = compiled;
|
|
626
718
|
resultName = mod.meta.name;
|
|
627
719
|
}
|
|
628
720
|
|
|
629
|
-
|
|
721
|
+
// Omitted args match a slash command without arguments; any other value, null included, is kept verbatim.
|
|
722
|
+
const resultArgs: unknown = params.args === undefined ? "" : params.args;
|
|
630
723
|
if (params.background) {
|
|
631
724
|
return await startBackgroundWorkflowTool({
|
|
632
725
|
coordinator: backgroundWorkflows,
|
|
633
726
|
ctx,
|
|
634
727
|
name: resultName,
|
|
635
728
|
options: runOptions,
|
|
729
|
+
// Under Dynamax the agent is orchestrating, so its completion starts a turn to act on the result.
|
|
730
|
+
wakeOnComplete: dynamax.isActive(ctx),
|
|
636
731
|
async execute(backgroundCtx, backgroundOptions) {
|
|
637
732
|
const envelope = await executeResolvedWorkflow(
|
|
638
733
|
pi,
|
|
@@ -657,15 +752,34 @@ function registerWorkflowTool(
|
|
|
657
752
|
spent = snapshot;
|
|
658
753
|
return runOptions.onUsageSnapshot?.(snapshot);
|
|
659
754
|
};
|
|
755
|
+
// A failed run still names its run, script and journal, so the host can fix the script and resume.
|
|
756
|
+
let run: WorkflowRunMetadata | undefined;
|
|
757
|
+
const onRunMetadata = (metadata: WorkflowRunMetadata) => {
|
|
758
|
+
run = metadata;
|
|
759
|
+
return runOptions.onRunMetadata?.(metadata);
|
|
760
|
+
};
|
|
660
761
|
let envelope: WorkflowResultEnvelope;
|
|
661
762
|
try {
|
|
662
|
-
envelope = await executeResolvedWorkflow(
|
|
763
|
+
envelope = await executeResolvedWorkflow(
|
|
764
|
+
pi,
|
|
765
|
+
ctx,
|
|
766
|
+
resultName,
|
|
767
|
+
mod,
|
|
768
|
+
resultArgs,
|
|
769
|
+
{ ...runOptions, hostTools, onUsageSnapshot, onRunMetadata },
|
|
770
|
+
perfRecorder,
|
|
771
|
+
);
|
|
663
772
|
} catch (error) {
|
|
664
773
|
const message = unknownErrorMessage(error);
|
|
774
|
+
const lines = [`Workflow ${resultName} failed: ${message}`];
|
|
775
|
+
if (run) lines.push("", formatWorkflowRunLine(run), ...formatWorkflowRunFileLines(run.runId, run.scriptPath !== undefined));
|
|
665
776
|
return {
|
|
666
|
-
...workflowToolError(
|
|
777
|
+
...workflowToolError(lines.join("\n"), { error: "workflow_failed", name: resultName, message }),
|
|
667
778
|
usage: workflowUsageForPi(spent),
|
|
668
779
|
};
|
|
780
|
+
} finally {
|
|
781
|
+
// Only synchronous runs draw the turn pool down; a background run's spend lands after the turn.
|
|
782
|
+
turnBudget?.spend(spent?.totals.output ?? 0);
|
|
669
783
|
}
|
|
670
784
|
reviewSessions.remember(ctx, envelope, runOptions);
|
|
671
785
|
return {
|
|
@@ -41,7 +41,7 @@ export function needsChallenge(finding: AdvisoryVerified): boolean {
|
|
|
41
41
|
|
|
42
42
|
/** A bounded recipe using existing agent/parallel calls, not a new runtime primitive. */
|
|
43
43
|
export async function challengeFindings<T extends AdvisoryVerified>(
|
|
44
|
-
api: Pick<WorkflowApi, "agent" | "parallel">,
|
|
44
|
+
api: Pick<WorkflowApi, "agent" | "agents" | "log" | "parallel">,
|
|
45
45
|
findings: T[],
|
|
46
46
|
context: string,
|
|
47
47
|
options: AdvisoryChallengeOptions,
|
|
@@ -49,7 +49,10 @@ export async function challengeFindings<T extends AdvisoryVerified>(
|
|
|
49
49
|
): Promise<T[]> {
|
|
50
50
|
const limit = Number.isFinite(options.maxChallenges) ? Math.max(0, Math.min(10, Math.trunc(options.maxChallenges))) : 0;
|
|
51
51
|
if (limit === 0) return findings;
|
|
52
|
-
const
|
|
52
|
+
const eligible = findings.filter((finding) => finding.verdict !== "REFUTED" && (options.shouldChallenge ?? needsChallenge)(finding)).slice(0, limit);
|
|
53
|
+
// A challenge takes two agents; keep one admission for the synthesis that follows.
|
|
54
|
+
const selected = eligible.slice(0, Math.max(0, Math.floor((api.agents.remaining() - 1) / 2)));
|
|
55
|
+
if (selected.length < eligible.length) api.log(`Challenging ${selected.length} of ${eligible.length} findings to fit the agent limit`);
|
|
53
56
|
const replacements = new Map<string, T>();
|
|
54
57
|
await collectAdvisoryStage(api, "Challenge", selected.map((finding) => ({ id: finding.candidateId, run: async () => {
|
|
55
58
|
// Preserve the candidate as unresolved if either independent stage fails.
|
|
@@ -38,6 +38,14 @@ export const AdvisoryVerdictSchema = Type.Object({
|
|
|
38
38
|
evidence: Type.Array(Type.String({ description: "Quoted or cited evidence supporting the verdict." })),
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
+
export const AdvisoryBatchVerdictSchema = Type.Object({
|
|
42
|
+
verdicts: Type.Array(Type.Object({
|
|
43
|
+
candidateId: Type.String({ description: "The candidateId of the record this verdict judges." }),
|
|
44
|
+
verdict: AdvisoryVerifierVerdictSchema,
|
|
45
|
+
evidence: Type.Array(Type.String({ description: "Quoted or cited evidence supporting the verdict." })),
|
|
46
|
+
}), { description: "One verdict per candidate record, each judged on its own." }),
|
|
47
|
+
});
|
|
48
|
+
|
|
41
49
|
export const AdvisoryFindingSchema = Type.Object({
|
|
42
50
|
verdict: Type.Optional(AdvisoryVerifierVerdictSchema),
|
|
43
51
|
sourceCandidateIds: Type.Optional(Type.Array(Type.String())),
|
|
@@ -38,10 +38,12 @@ export type CreateAgentSession = (options: CreateAgentSessionOptions) => Promise
|
|
|
38
38
|
/** Every row update after `agentQueued` addresses that row by its id; labels are display text, not unique keys. */
|
|
39
39
|
export interface AgentProgress {
|
|
40
40
|
agentQueued(phase: string | undefined, label: string): number;
|
|
41
|
-
|
|
41
|
+
/** `skip` aborts the row's live agent; the tracker offers it to the inspector only while the row runs. */
|
|
42
|
+
agentStart(id: number, skip?: () => void): void;
|
|
42
43
|
agentTool(id: number, tool: string): void;
|
|
43
44
|
agentDone(id: number): void;
|
|
44
45
|
agentFailed(id: number, error: unknown): void;
|
|
46
|
+
agentSkipped(id: number): void;
|
|
45
47
|
event(event: WorkflowProgressEvent): void;
|
|
46
48
|
log(message: string): void;
|
|
47
49
|
}
|