pi-extensible-workflows 0.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.
Files changed (47) hide show
  1. package/README.md +72 -0
  2. package/dist/src/agent-execution.d.ts +213 -0
  3. package/dist/src/agent-execution.js +537 -0
  4. package/dist/src/ambient-workflow-evals.d.ts +112 -0
  5. package/dist/src/ambient-workflow-evals.js +375 -0
  6. package/dist/src/cli.d.ts +6 -0
  7. package/dist/src/cli.js +26 -0
  8. package/dist/src/doctor.d.ts +59 -0
  9. package/dist/src/doctor.js +269 -0
  10. package/dist/src/eval-capture-extension.d.ts +5 -0
  11. package/dist/src/eval-capture-extension.js +48 -0
  12. package/dist/src/index.d.ts +362 -0
  13. package/dist/src/index.js +2839 -0
  14. package/dist/src/persistence.d.ts +90 -0
  15. package/dist/src/persistence.js +530 -0
  16. package/dist/src/session-inspector.d.ts +59 -0
  17. package/dist/src/session-inspector.js +396 -0
  18. package/dist/src/workflow-evals-child.d.ts +1 -0
  19. package/dist/src/workflow-evals-child.js +13 -0
  20. package/dist/src/workflow-evals.d.ts +290 -0
  21. package/dist/src/workflow-evals.js +1221 -0
  22. package/evals/cases/custom-model-read.yaml +15 -0
  23. package/evals/cases/direct-answer.yaml +6 -0
  24. package/evals/cases/mixed-parallel-pipeline.yaml +18 -0
  25. package/evals/cases/output-schema.yaml +22 -0
  26. package/evals/cases/parallel.yaml +15 -0
  27. package/evals/cases/pipeline.yaml +10 -0
  28. package/evals/cases/ready-for-agent-parallel-merge.yaml +32 -0
  29. package/evals/cases/required-role.yaml +14 -0
  30. package/evals/cases/role-model-mixed.yaml +18 -0
  31. package/evals/cases/two-agents.yaml +10 -0
  32. package/package.json +65 -0
  33. package/skills/pi-extensible-workflows/SKILL.md +109 -0
  34. package/src/agent-execution.ts +529 -0
  35. package/src/ambient-workflow-evals.ts +452 -0
  36. package/src/cli.ts +23 -0
  37. package/src/doctor.ts +285 -0
  38. package/src/eval-capture-extension.ts +54 -0
  39. package/src/index.ts +2519 -0
  40. package/src/persistence.ts +470 -0
  41. package/src/session-inspector.ts +370 -0
  42. package/src/workflow-evals-child.ts +15 -0
  43. package/src/workflow-evals.ts +876 -0
  44. package/test/fixtures/ready-for-agent-tasks.md +9 -0
  45. package/test/fixtures/workflow-eval-roles/developer.md +18 -0
  46. package/test/fixtures/workflow-eval-roles/reviewer.md +18 -0
  47. package/test/fixtures/workflow-eval-roles/scout.md +17 -0
package/src/doctor.ts ADDED
@@ -0,0 +1,285 @@
1
+ import { readFileSync, readdirSync, realpathSync } from "node:fs";
2
+ import { basename, dirname, extname, join, resolve } from "node:path";
3
+ import { InMemoryCredentialStore, type Credential } from "@earendil-works/pi-ai";
4
+ import {
5
+ ModelRuntime,
6
+ createAgentSessionFromServices,
7
+ createAgentSessionServices,
8
+ getAgentDir,
9
+ hasTrustRequiringProjectResources,
10
+ SessionManager,
11
+ SettingsManager,
12
+ } from "@earendil-works/pi-coding-agent";
13
+ import {
14
+ DEFAULT_SETTINGS,
15
+ loadSettings,
16
+ parseModelReference,
17
+ parseRoleMarkdown,
18
+ preflight,
19
+ registeredWorkflowDefinitions,
20
+ workflowRoleDirectories,
21
+ workflowSettingsPath,
22
+ WorkflowError,
23
+ type WorkflowScriptDefinition,
24
+ type WorkflowSettings,
25
+ } from "./index.js";
26
+ import type { AgentDefinition } from "./agent-execution.js";
27
+
28
+ export type DoctorSeverity = "error" | "warning";
29
+ export interface DoctorDiagnostic { severity: DoctorSeverity; code: string; message: string; source?: string; hint?: string }
30
+ export interface DoctorRole { name: string; path: string; scope: "global" | "project"; active: boolean; overrides?: string; overriddenBy?: string }
31
+ export interface DoctorWorkflow { name: string; description: string; valid: boolean }
32
+ export interface DoctorTrust { required: boolean; trusted: boolean; source: string }
33
+ export interface DoctorPiState {
34
+ trust: DoctorTrust;
35
+ activeTools: readonly string[];
36
+ knownModels: readonly string[];
37
+ availableModels: readonly string[];
38
+ extensionErrors: readonly { path?: string; message: string }[];
39
+ workflows: Readonly<Record<string, WorkflowScriptDefinition>>;
40
+ }
41
+ export interface DoctorReport {
42
+ cwd: string;
43
+ agentDir: string;
44
+ settingsPath: string;
45
+ settings: Readonly<WorkflowSettings>;
46
+ trust: DoctorTrust;
47
+ activeTools: readonly string[];
48
+ roles: readonly DoctorRole[];
49
+ workflows: readonly DoctorWorkflow[];
50
+ diagnostics: readonly DoctorDiagnostic[];
51
+ }
52
+ export interface DoctorOptions {
53
+ cwd?: string;
54
+ agentDir?: string;
55
+ settingsPath?: string;
56
+ discoverPi?: (cwd: string, agentDir: string) => Promise<DoctorPiState>;
57
+ activeTools?: readonly string[];
58
+ }
59
+
60
+ const THINKING_HINT = "Use off, minimal, low, medium, high, xhigh, or max.";
61
+
62
+ function canonical(path: string): string {
63
+ const absolute = resolve(path);
64
+ try { return realpathSync(absolute); } catch { return absolute; }
65
+ }
66
+
67
+ async function readCredentials(agentDir: string): Promise<InMemoryCredentialStore> {
68
+ const credentials = new InMemoryCredentialStore();
69
+ try {
70
+ const parsed: unknown = JSON.parse(readFileSync(join(agentDir, "auth.json"), "utf8"));
71
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("Pi auth.json must be an object");
72
+ await Promise.all(Object.entries(parsed).map(([provider, credential]) => credentials.modify(provider, async () => credential as Credential)));
73
+ } catch (error) {
74
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
75
+ }
76
+ return credentials;
77
+ }
78
+
79
+ function savedTrust(cwd: string, agentDir: string): boolean | undefined {
80
+ let parsed: unknown;
81
+ try { parsed = JSON.parse(readFileSync(join(agentDir, "trust.json"), "utf8")); }
82
+ catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined; throw error; }
83
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("Pi trust.json must be an object");
84
+ let current = canonical(cwd);
85
+ while (current !== dirname(current)) {
86
+ const value = (parsed as Record<string, unknown>)[current];
87
+ if (value === true || value === false) return value;
88
+ current = dirname(current);
89
+ }
90
+ const value = (parsed as Record<string, unknown>)[current];
91
+ return value === true || value === false ? value : undefined;
92
+ }
93
+
94
+ async function discoverPi(cwd: string, agentDir: string): Promise<DoctorPiState> {
95
+ const required = hasTrustRequiringProjectResources(cwd);
96
+ const settingsManager = SettingsManager.create(cwd, agentDir, { projectTrusted: false });
97
+ const saved = required ? savedTrust(cwd, agentDir) : true;
98
+ const fallback = settingsManager.getDefaultProjectTrust();
99
+ const trusted = !required || saved !== undefined ? Boolean(saved) : fallback === "always";
100
+ const source = !required ? "no trust-gated project resources" : saved !== undefined ? "saved Pi trust decision" : `headless defaultProjectTrust=${fallback}`;
101
+ const previousOffline = process.env.PI_OFFLINE;
102
+ process.env.PI_OFFLINE = "1";
103
+ try {
104
+ const modelRuntime = await ModelRuntime.create({ credentials: await readCredentials(agentDir), modelsPath: join(agentDir, "models.json") });
105
+ const services = await createAgentSessionServices({
106
+ cwd,
107
+ agentDir,
108
+ settingsManager,
109
+ modelRuntime,
110
+ resourceLoaderOptions: { noSkills: true, noPromptTemplates: true, noThemes: true, noContextFiles: true },
111
+ resourceLoaderReloadOptions: { resolveProjectTrust: async () => trusted },
112
+ });
113
+ const allModels = services.modelRuntime.getModels();
114
+ const availableModels = await services.modelRuntime.getAvailable();
115
+ const model = availableModels[0] ?? allModels[0];
116
+ if (!model) throw new Error("Pi has no models registered");
117
+ const { session } = await createAgentSessionFromServices({ services, sessionManager: SessionManager.inMemory(), model });
118
+ const activeTools = session.agent.state.tools.map(({ name }) => name).filter((name) => name !== "workflow" && name !== "workflow_respond" && name !== "workflow_catalog");
119
+ const extensions = services.resourceLoader.getExtensions();
120
+ return {
121
+ trust: { required, trusted, source },
122
+ activeTools,
123
+ knownModels: allModels.map(({ provider, id }) => `${provider}/${id}`),
124
+ availableModels: availableModels.map(({ provider, id }) => `${provider}/${id}`),
125
+ extensionErrors: [
126
+ ...extensions.errors.map(({ path, error }) => ({ path, message: error })),
127
+ ...services.diagnostics.filter(({ type }) => type === "error").map(({ message }) => ({ message })),
128
+ ],
129
+ workflows: registeredWorkflowDefinitions(),
130
+ };
131
+ } finally {
132
+ if (previousOffline === undefined) delete process.env.PI_OFFLINE;
133
+ else process.env.PI_OFFLINE = previousOffline;
134
+ }
135
+ }
136
+
137
+ function roleFiles(dir: string): string[] {
138
+ try { return readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && extname(entry.name) === ".md").map((entry) => join(dir, entry.name)).sort(); }
139
+ catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return []; throw error; }
140
+ }
141
+
142
+ function roleFilesFrom(dirs: readonly string[]): string[] {
143
+ const paths = dirs.flatMap((dir) => roleFiles(dir));
144
+ return [...new Map(paths.map((path) => [basename(path, ".md"), path])).values()].sort();
145
+ }
146
+
147
+ function diagnostic(severity: DoctorSeverity, code: string, message: string, source?: string, hint?: string): DoctorDiagnostic {
148
+ return { severity, code, message, ...(source ? { source } : {}), ...(hint ? { hint } : {}) };
149
+ }
150
+
151
+ function validateModel(value: string, known: ReadonlySet<string>, available: ReadonlySet<string>, source: string, diagnostics: DoctorDiagnostic[]): void {
152
+ try {
153
+ const parsed = parseModelReference(value);
154
+ const name = `${parsed.provider}/${parsed.model}`;
155
+ if (!known.has(name) || !available.has(name)) diagnostics.push(diagnostic("warning", "MODEL_UNAVAILABLE", `Model is valid-shaped but unavailable: ${name}`, source));
156
+ } catch (error) {
157
+ diagnostics.push(diagnostic("error", "MODEL_INVALID", (error as Error).message, source, (error as Error).message.includes("thinking") ? THINKING_HINT : "Use provider/model or provider/model:thinking."));
158
+ }
159
+ }
160
+
161
+ function inspectRole(path: string, activeTools: ReadonlySet<string>, knownModels: ReadonlySet<string>, availableModels: ReadonlySet<string>, diagnostics: DoctorDiagnostic[]): AgentDefinition | undefined {
162
+ let definition: AgentDefinition;
163
+ try { definition = parseRoleMarkdown(readFileSync(path, "utf8"), true); }
164
+ catch (error) {
165
+ diagnostics.push(diagnostic("error", "ROLE_FRONTMATTER", (error as Error).message, path, "Fix the role YAML frontmatter."));
166
+ return undefined;
167
+ }
168
+ const body = definition.prompt ?? "";
169
+ if (body.trim() === "") diagnostics.push(diagnostic("warning", "ROLE_BODY_EMPTY", "Role body is empty", path));
170
+ if (Buffer.byteLength(body) > 50 * 1024) diagnostics.push(diagnostic("warning", "ROLE_BODY_LARGE", "Role body exceeds 50KB", path));
171
+ if (/{{\s*[^{}]+\s*}}/.test(body)) diagnostics.push(diagnostic("warning", "ROLE_PLACEHOLDER", "Role body contains an unsupported placeholder-looking token", path));
172
+ if (definition.model) validateModel(definition.model, knownModels, availableModels, path, diagnostics);
173
+ for (const tool of definition.tools ?? []) if (!activeTools.has(tool)) diagnostics.push(diagnostic("error", "ROLE_TOOL_INACTIVE", `Tool is unknown or inactive: ${tool}`, path, "Use a tool listed under Active tools or enable its Pi extension."));
174
+ return definition;
175
+ }
176
+
177
+ class DoctorModelSet extends Set<string> {
178
+ override has(value: string): boolean { parseModelReference(value); return true; }
179
+ }
180
+
181
+ export async function doctor(options: DoctorOptions = {}): Promise<DoctorReport> {
182
+ const cwd = canonical(options.cwd ?? process.cwd());
183
+ const agentDir = canonical(options.agentDir ?? getAgentDir());
184
+ const settingsPath = canonical(options.settingsPath ?? workflowSettingsPath(agentDir));
185
+ const diagnostics: DoctorDiagnostic[] = [];
186
+ let settings = DEFAULT_SETTINGS;
187
+ try { settings = loadSettings(settingsPath); }
188
+ catch (error) { diagnostics.push(diagnostic("error", "SETTINGS_INVALID", (error as Error).message, settingsPath, "Fix or remove the invalid workflow settings file.")); }
189
+
190
+ let pi: DoctorPiState;
191
+ try { pi = await (options.discoverPi ?? discoverPi)(cwd, agentDir); }
192
+ catch (error) {
193
+ diagnostics.push(diagnostic("error", "PI_DISCOVERY", `Pi headless discovery failed: ${(error as Error).message}`, undefined, "Open and trust the project in Pi, fix extension errors, then rerun doctor."));
194
+ pi = { trust: { required: false, trusted: false, source: "discovery failed" }, activeTools: [], knownModels: [], availableModels: [], extensionErrors: [], workflows: {} };
195
+ }
196
+ if (options.activeTools) pi = { ...pi, activeTools: options.activeTools.filter((tool) => tool !== "workflow" && tool !== "workflow_respond" && tool !== "workflow_catalog") };
197
+ if (pi.trust.required && !pi.trust.trusted) diagnostics.push(diagnostic("warning", "PROJECT_UNTRUSTED", "Pi project resources are inactive because the project is not trusted", cwd, "Open this project in Pi, choose Trust, then rerun doctor."));
198
+ for (const error of pi.extensionErrors) diagnostics.push(diagnostic("error", "EXTENSION_LOAD", error.message, error.path, "Fix or disable the failing Pi extension."));
199
+
200
+ const activeTools = new Set(pi.activeTools);
201
+ const knownModels = new Set(pi.knownModels);
202
+ const availableModels = new Set(pi.availableModels);
203
+ const roles: DoctorRole[] = [];
204
+ const definitions = new Map<string, AgentDefinition>();
205
+ const globalPaths = new Map<string, string>();
206
+ const globalRoleDirs = workflowRoleDirectories(agentDir);
207
+ for (const path of roleFilesFrom(globalRoleDirs)) {
208
+ const name = basename(path, ".md");
209
+ roles.push({ name, path, scope: "global", active: true });
210
+ globalPaths.set(name, path);
211
+ const definition = inspectRole(path, activeTools, knownModels, availableModels, diagnostics);
212
+ if (definition) definitions.set(name, definition);
213
+ }
214
+ for (const path of roleFilesFrom([join(cwd, ".pi", "pi-extensible-workflows", "roles")])) {
215
+ const name = basename(path, ".md");
216
+ const globalPath = globalPaths.get(name);
217
+ const active = pi.trust.trusted;
218
+ roles.push({ name, path, scope: "project", active, ...(active && globalPath ? { overrides: globalPath } : {}) });
219
+ if (!active) continue;
220
+ if (globalPath) {
221
+ const global = roles.find((role) => role.path === globalPath);
222
+ if (global) { global.active = false; global.overriddenBy = path; }
223
+ }
224
+ const definition = inspectRole(path, activeTools, knownModels, availableModels, diagnostics);
225
+ if (definition) definitions.set(name, definition); else definitions.delete(name);
226
+ }
227
+
228
+ const workflows: DoctorWorkflow[] = [];
229
+ for (const [name, workflow] of Object.entries(pi.workflows).sort(([left], [right]) => left.localeCompare(right))) {
230
+ let valid = true;
231
+ try {
232
+ const checked = preflight(workflow.script, {
233
+ models: new DoctorModelSet(pi.knownModels),
234
+ tools: activeTools,
235
+ agentTypes: new Set(definitions.keys()),
236
+ }, [], { name, description: workflow.description });
237
+ for (const model of checked.referenced.models) if (!knownModels.has(model) || !availableModels.has(model)) diagnostics.push(diagnostic("warning", "MODEL_UNAVAILABLE", `Model is valid-shaped but unavailable: ${model}`, name));
238
+ } catch (error) {
239
+ valid = false;
240
+ const typed = error instanceof WorkflowError ? error : new WorkflowError("INTERNAL_ERROR", String(error));
241
+ diagnostics.push(diagnostic("error", `WORKFLOW_${typed.code}`, typed.message, name, "Fix the registered workflow source or its referenced role/tool/model/extension."));
242
+ }
243
+ workflows.push({ name, description: workflow.description, valid });
244
+ }
245
+
246
+ const severityOrder: Record<DoctorSeverity, number> = { error: 0, warning: 1 };
247
+ diagnostics.sort((left, right) => severityOrder[left.severity] - severityOrder[right.severity] || (left.source ?? "").localeCompare(right.source ?? "") || left.code.localeCompare(right.code) || left.message.localeCompare(right.message));
248
+ roles.sort((left, right) => left.name.localeCompare(right.name) || left.scope.localeCompare(right.scope));
249
+ return { cwd, agentDir, settingsPath, settings, trust: pi.trust, activeTools: [...activeTools].sort(), roles, workflows, diagnostics };
250
+ }
251
+
252
+ function count(report: DoctorReport, severity: DoctorSeverity): number { return report.diagnostics.filter((item) => item.severity === severity).length; }
253
+
254
+ export function doctorExitCode(report: DoctorReport): 0 | 1 { return count(report, "error") > 0 ? 1 : 0; }
255
+
256
+ export function formatDoctorReport(report: DoctorReport): string {
257
+ const lines = [
258
+ "# pi-extensible-workflows doctor",
259
+ "",
260
+ "## Environment",
261
+ `- CWD: \`${report.cwd}\``,
262
+ `- Agent dir: \`${report.agentDir}\``,
263
+ `- Workflow settings: \`${report.settingsPath}\``,
264
+ `- Limits: concurrency=${String(report.settings.concurrency)}, maxAgentLaunches=${String(report.settings.maxAgentLaunches)}`,
265
+ "",
266
+ "## Trust/resources",
267
+ `- [${report.trust.trusted ? "ok" : "warning"}] ${report.trust.source}`,
268
+ "",
269
+ "## Active tools",
270
+ ...(report.activeTools.length ? report.activeTools.map((tool) => `- \`${tool}\``) : ["- None resolved"]),
271
+ "",
272
+ "## Roles",
273
+ ...(report.roles.length ? report.roles.map((role) => `- \`${role.name}\` (${role.scope}, ${role.active ? "active" : role.overriddenBy ? `overridden by ${role.overriddenBy}` : "inactive: project untrusted"}) - \`${role.path}\`${role.overrides ? `; overrides \`${role.overrides}\`` : ""}`) : ["- None found"]),
274
+ "",
275
+ "## Reusable workflows",
276
+ ...(report.workflows.length ? report.workflows.map((workflow) => `- [${workflow.valid ? "ok" : "error"}] \`${workflow.name}\` - ${workflow.description}`) : ["- None registered"]),
277
+ "",
278
+ "## Diagnostics",
279
+ ...(report.diagnostics.length ? report.diagnostics.map((item) => `- [${item.severity}] ${item.code}${item.source ? ` \`${item.source}\`` : ""}: ${item.message}${item.hint ? ` Fix: ${item.hint}` : ""}`) : ["- [ok] No diagnostics"]),
280
+ "",
281
+ "## Summary",
282
+ `- ${String(count(report, "error"))} error(s), ${String(count(report, "warning"))} warning(s)`,
283
+ ];
284
+ return `${lines.join("\n")}\n`;
285
+ }
@@ -0,0 +1,54 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
+ import { validateWorkflowLaunch, WorkflowError, WORKFLOW_TOOL_DESCRIPTION, WORKFLOW_TOOL_LABEL, WORKFLOW_TOOL_PARAMETERS, WORKFLOW_TOOL_PROMPT_SNIPPET, type WorkflowValidationParameters } from "./index.js";
6
+
7
+ export const CAPTURE_IDENTITY = "pi-extensible-workflows-eval-capture-v1";
8
+ export const CAPTURE_ERROR_PREFIX = `${CAPTURE_IDENTITY}:`;
9
+
10
+ interface CaptureContext {
11
+ cwd: string;
12
+ model?: { provider: string; id: string };
13
+ modelRegistry?: { getAvailable(): Array<{ provider: string; id: string }> };
14
+ isProjectTrusted?: () => boolean;
15
+ }
16
+
17
+ export function resolveWorkflowSkillPath(): string {
18
+ let directory = dirname(fileURLToPath(import.meta.url));
19
+ for (let depth = 0; depth < 6; depth += 1) {
20
+ const candidate = join(directory, "skills", "pi-extensible-workflows", "SKILL.md");
21
+ if (existsSync(candidate)) return candidate;
22
+ const parent = dirname(directory);
23
+ if (parent === directory) break;
24
+ directory = parent;
25
+ }
26
+ throw new Error("Could not resolve skills/pi-extensible-workflows/SKILL.md from the eval extension");
27
+ }
28
+
29
+ export default function evalCaptureExtension(pi: ExtensionAPI): void {
30
+ pi.registerTool({
31
+ name: "workflow",
32
+ label: WORKFLOW_TOOL_LABEL,
33
+ description: WORKFLOW_TOOL_DESCRIPTION,
34
+ promptSnippet: WORKFLOW_TOOL_PROMPT_SNIPPET,
35
+ parameters: WORKFLOW_TOOL_PARAMETERS,
36
+ async execute(_id: string, params: WorkflowValidationParameters, _signal: AbortSignal, _onUpdate: unknown, ctx: CaptureContext) {
37
+ try {
38
+ if (!ctx.model) throw new WorkflowError("UNKNOWN_MODEL", "A launching model is required");
39
+ const rootModel = `${ctx.model.provider}/${ctx.model.id}`;
40
+ const availableModels = new Set((ctx.modelRegistry?.getAvailable() ?? [ctx.model]).map((model) => `${model.provider}/${model.id}`));
41
+ availableModels.add(rootModel);
42
+ const rootTools = new Set(pi.getActiveTools().filter((name) => name !== "workflow" && name !== "workflow_respond" && name !== "workflow_catalog"));
43
+ const validated = validateWorkflowLaunch(params, { cwd: ctx.cwd, projectTrusted: ctx.isProjectTrusted?.() ?? true, availableModels, rootTools });
44
+ return {
45
+ content: [{ type: "text" as const, text: JSON.stringify({ captured: true, validated: true, launchBudget: 0, name: validated.checked.metadata.name }) }],
46
+ details: { captured: true, captureIdentity: CAPTURE_IDENTITY, realWorkflowAgentsLaunched: 0, launchBudget: 0, validation: { valid: true, script: validated.script, metadata: validated.checked.metadata, roles: validated.roleNames } },
47
+ };
48
+ } catch (error) {
49
+ if (error instanceof WorkflowError) throw new WorkflowError(error.code, `${CAPTURE_ERROR_PREFIX}${error.code}: ${error.message}`);
50
+ throw error;
51
+ }
52
+ },
53
+ } as never);
54
+ }