@parcha/agentrun-pi 0.1.0-beta.23 → 0.1.0-beta.27
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/dist/durable/index.d.ts +1 -1
- package/dist/durable/node-runner.js +3 -2
- package/dist/durable/readers.d.ts +8 -3
- package/dist/durable/readers.js +7 -6
- package/dist/durable/record-tool.js +2 -1
- package/dist/durable/workflow-tool.js +4 -3
- package/dist/extension.js +38 -34
- package/dist/host-session.js +6 -4
- package/dist/host-tools.d.ts +4 -1
- package/dist/host-tools.js +4 -1
- package/dist/operator-error.d.ts +4 -0
- package/dist/operator-error.js +4 -0
- package/dist/runner.js +8 -6
- package/dist/sop.js +7 -6
- package/dist/workflow-tool.d.ts +1 -1
- package/dist/workflow-tool.js +6 -2
- package/package.json +4 -4
package/dist/durable/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export type { RecoveryBinding, RecoveryEffect, RecoveryJournal, RecoveryNote, RecoveryStore } from "@parcha/agentrun-dsl/recovery";
|
|
2
2
|
export { documentStore, type DocumentStoreHost, type DocumentStoreOptions } from "./store.js";
|
|
3
|
-
export { committedRoutes, durableDecisions, durableDoc, readJournal, type JournalView } from "./readers.js";
|
|
3
|
+
export { committedRoutes, durableDecisions, durableDoc, readJournal, type DurableReadOptions, type JournalView } from "./readers.js";
|
|
4
4
|
export { recordCommits, recordingLines, snapshotLines, type RecordingLine, type RecordingSource } from "./recording.js";
|
|
5
5
|
export { reuseReceipts, seedReceipts, ReceiptIndex, type ReceiptReuse, type ReuseReceiptsOptions, type ReusableTool } from "./receipts.js";
|
|
6
6
|
export { effectResultOf } from "../effect-result.js";
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
13
|
import { configure, defineDoc, defineExtension, GenerationTask, hook, section, } from "@earendil-works/pi-durable";
|
|
14
14
|
import { canonicalSha256, runStopOf } from "@parcha/agentrun-dsl/recovery";
|
|
15
|
+
import { SUBMIT_TOOL } from "../host-tools.js";
|
|
15
16
|
import { nodeThinking } from "../thinking.js";
|
|
16
17
|
import { closeNodeAttempt, runNodeAttempt } from "./node.js";
|
|
17
18
|
import { DELIVERY_ATTEMPTS, lintRecordRoot, submitFooter } from "./record.js";
|
|
@@ -78,7 +79,7 @@ async function underStop(signal, work) {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
const listed = (value) => Array.isArray(value);
|
|
81
|
-
const isSubmit = (tool) => tool.name ===
|
|
82
|
+
const isSubmit = (tool) => tool.name === SUBMIT_TOOL;
|
|
82
83
|
/** The node runner over a host. Install `extension` in the registry before the Harness opens; `on` binds the runner
|
|
83
84
|
* to where its conversations live (`taskScope`, `hostScope`). */
|
|
84
85
|
export function nodeRunner(host, options = {}) {
|
|
@@ -177,7 +178,7 @@ export function nodeRunner(host, options = {}) {
|
|
|
177
178
|
const { agent, record } = await underStop(signal, async () => ({ agent: leveled(await host.agent(node), node), record: await host.record?.(node) ?? {} }));
|
|
178
179
|
const label = record.label ?? `the ${node.label} record`;
|
|
179
180
|
const offered = (listed(agent.tools) ? agent.tools : listed(agent.extensions) ? agent.extensions.flatMap((selected) => selected.tools ?? []) : [])
|
|
180
|
-
.map((offer) => offer.name).filter((name) => name !==
|
|
181
|
+
.map((offer) => offer.name).filter((name) => name !== SUBMIT_TOOL);
|
|
181
182
|
const system = [...node.system, host.stance?.(node, { tools: offered })].filter(Boolean).join("\n\n");
|
|
182
183
|
const input = inputOf(node.user);
|
|
183
184
|
const reviewers = [
|
|
@@ -11,11 +11,16 @@ export type JournalView = {
|
|
|
11
11
|
notes: RecoveryNote[];
|
|
12
12
|
effects: RecoveryEffect[];
|
|
13
13
|
};
|
|
14
|
+
/** How a reader opens the store file. `immutable` opens it as SQLite's immutable file, for a store no process writes any
|
|
15
|
+
* more and with no write-ahead log beside it (an ended run's): nothing beside it is read or made. */
|
|
16
|
+
export type DurableReadOptions = {
|
|
17
|
+
immutable?: boolean;
|
|
18
|
+
};
|
|
14
19
|
/** The run's store `file`, opened read-only for `read`; null when there is no such file. Every query of one `read` runs
|
|
15
20
|
* in one read transaction, so what it returns is one commit's view, whatever a writer commits meanwhile. */
|
|
16
|
-
export declare function withDurableFile<T>(file: string, read: (db: Database) => T): T | null;
|
|
21
|
+
export declare function withDurableFile<T>(file: string, read: (db: Database) => T, options?: DurableReadOptions): T | null;
|
|
17
22
|
/** A Session document of the run's store `file`, read-only; undefined when the file or the document is absent. */
|
|
18
|
-
export declare function durableDoc<T = Record<string, unknown>>(file: string, kind: string, key?: string): T | undefined;
|
|
23
|
+
export declare function durableDoc<T = Record<string, unknown>>(file: string, kind: string, key?: string, options?: DurableReadOptions): T | undefined;
|
|
19
24
|
/** The decision rows (`agentrun.decisions`) of the run's store `file`, in commit order, in whichever form the store holds: the single
|
|
20
25
|
* document's `receipts` list, or the keyed documents (one row each, in the order of their keys). A reader that folds decisions from the
|
|
21
26
|
* store uses this and not `durableDoc(file, "agentrun.decisions").receipts`, which is nothing in the keyed form. */
|
|
@@ -25,7 +30,7 @@ export declare function durableDecisions(file: string): Array<Record<string, unk
|
|
|
25
30
|
* admitted, in either layout: in the keyed one (`agentrun.frozen_run.v4`) the effects a map item owns, nested maps
|
|
26
31
|
* included, are listed by that item's record, not the driver's, and are read from there. `state` is the driver's own
|
|
27
32
|
* record: in the keyed layout what lies in each map item stays in the item's record. */
|
|
28
|
-
export declare function readJournal(file: string, key: string): JournalView | undefined;
|
|
33
|
+
export declare function readJournal(file: string, key: string, options?: DurableReadOptions): JournalView | undefined;
|
|
29
34
|
/** Every committed route decision a frozen snapshot holds (each judge's answer, whole), by execution path. Empty for a
|
|
30
35
|
* state that is no snapshot. */
|
|
31
36
|
export declare function committedRoutes(state: unknown): Record<string, Json>;
|
package/dist/durable/readers.js
CHANGED
|
@@ -4,16 +4,17 @@
|
|
|
4
4
|
// readers take the store file itself (pi-durable's SQLite file, wherever the host keeps it), never a directory.
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
7
8
|
import { apply } from "@earendil-works/chord/delta";
|
|
8
9
|
import { decisionKey } from "./decisions.js";
|
|
9
10
|
import { effectKey } from "./store.js";
|
|
10
11
|
/** The run's store `file`, opened read-only for `read`; null when there is no such file. Every query of one `read` runs
|
|
11
12
|
* in one read transaction, so what it returns is one commit's view, whatever a writer commits meanwhile. */
|
|
12
|
-
export function withDurableFile(file, read) {
|
|
13
|
+
export function withDurableFile(file, read, options = {}) {
|
|
13
14
|
if (!fs.existsSync(file))
|
|
14
15
|
return null;
|
|
15
16
|
const { DatabaseSync } = createRequire(import.meta.url)("node:sqlite");
|
|
16
|
-
const db = new DatabaseSync(file, { readOnly: true, timeout: 5000 });
|
|
17
|
+
const db = new DatabaseSync(options.immutable ? `${pathToFileURL(file).href}?immutable=1` : file, { readOnly: true, timeout: 5000 });
|
|
17
18
|
try {
|
|
18
19
|
db.exec("BEGIN");
|
|
19
20
|
return read(db);
|
|
@@ -67,8 +68,8 @@ function itemEffects(db, key, table, into) {
|
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
/** A Session document of the run's store `file`, read-only; undefined when the file or the document is absent. */
|
|
70
|
-
export function durableDoc(file, kind, key) {
|
|
71
|
-
return (withDurableFile(file, (db) => sessionDoc(db, kind, key)) ?? undefined);
|
|
71
|
+
export function durableDoc(file, kind, key, options = {}) {
|
|
72
|
+
return (withDurableFile(file, (db) => sessionDoc(db, kind, key), options) ?? undefined);
|
|
72
73
|
}
|
|
73
74
|
/** The decision rows (`agentrun.decisions`) of the run's store `file`, in commit order, in whichever form the store holds: the single
|
|
74
75
|
* document's `receipts` list, or the keyed documents (one row each, in the order of their keys). A reader that folds decisions from the
|
|
@@ -94,7 +95,7 @@ export function durableDecisions(file) {
|
|
|
94
95
|
* admitted, in either layout: in the keyed one (`agentrun.frozen_run.v4`) the effects a map item owns, nested maps
|
|
95
96
|
* included, are listed by that item's record, not the driver's, and are read from there. `state` is the driver's own
|
|
96
97
|
* record: in the keyed layout what lies in each map item stays in the item's record. */
|
|
97
|
-
export function readJournal(file, key) {
|
|
98
|
+
export function readJournal(file, key, options = {}) {
|
|
98
99
|
return withDurableFile(file, (db) => {
|
|
99
100
|
const doc = sessionDoc(db, "agentrun.driver", key);
|
|
100
101
|
if (!doc || doc.binding === null)
|
|
@@ -113,7 +114,7 @@ export function readJournal(file, key) {
|
|
|
113
114
|
return { id, name: e?.name ?? "", argsHash: e?.argsHash ?? "", status: e?.status ?? "unknown", session: e?.session ?? null, result: e?.result ?? null, ...(e?.intent ? { intent: e.intent } : {}), ...(e?.args !== undefined ? { args: e.args } : {}), ...(Array.isArray(e?.calls) ? { calls: e.calls } : {}) };
|
|
114
115
|
});
|
|
115
116
|
return { binding: doc.binding, inputs: doc.inputs, generation: doc.generation, revision: doc.revision, state: doc.state, notes: doc.notes, effects };
|
|
116
|
-
}) ?? undefined;
|
|
117
|
+
}, options) ?? undefined;
|
|
117
118
|
}
|
|
118
119
|
/** Every committed route decision a frozen snapshot holds (each judge's answer, whole), by execution path. Empty for a
|
|
119
120
|
* state that is no snapshot. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineDoc, defineTool, GenerationTask, hook, } from "@earendil-works/pi-durable";
|
|
2
2
|
import { DELIVERY_ATTEMPTS, deliver, deliveryTerminates, deliveryText, fileSubmission, mixedFileSubmission, nudgeText, repairRecord, VIOLATIONS_SHOWN, } from "./record.js";
|
|
3
|
+
import { SUBMIT_TOOL } from "../host-tools.js";
|
|
3
4
|
import { toolResult } from "./pi-versions.js";
|
|
4
5
|
/** The record a conversation delivered, or null while it has not, and the delivery attempts it has spent. */
|
|
5
6
|
export const RecordDoc = defineDoc({
|
|
@@ -76,7 +77,7 @@ export function recordTool(options) {
|
|
|
76
77
|
...(deliveryTerminates(delivery) ? { control: { terminate: true } } : {}),
|
|
77
78
|
});
|
|
78
79
|
return defineTool({
|
|
79
|
-
name:
|
|
80
|
+
name: SUBMIT_TOOL,
|
|
80
81
|
description: shown.description,
|
|
81
82
|
parameters: shown.parameters,
|
|
82
83
|
// Every call's arguments are carried to `execute`; one record's are repaired here, before the Harness validates.
|
|
@@ -8,7 +8,7 @@ import { Type } from "typebox";
|
|
|
8
8
|
import { inspectWorkflow, WORKFLOW_NODE_KINDS } from "@parcha/agentrun-dsl";
|
|
9
9
|
import { canonicalHash } from "@parcha/agentrun-dsl/recovery";
|
|
10
10
|
import { extensionStructuralLimits, WorkflowExtensionService } from "../extension-service.js";
|
|
11
|
-
import {
|
|
11
|
+
import { assertHostTools, SUBMIT_TOOL } from "../host-tools.js";
|
|
12
12
|
import { modelJson } from "../presentation.js";
|
|
13
13
|
import { WorkflowStore } from "../workflow-store.js";
|
|
14
14
|
import { progressLines, WorkflowObservation } from "../workflow-view.js";
|
|
@@ -38,7 +38,8 @@ const answer = (value) => toolResult([{ type: "text", text: modelJson(value) }],
|
|
|
38
38
|
* agent definition. `workflow` reads and stops runs from the host's side. */
|
|
39
39
|
export function createWorkflowTool(host) {
|
|
40
40
|
// The host's tools pass the check the coding-agent extension's do; the tool's own name and the node runner's submit are reserved.
|
|
41
|
-
|
|
41
|
+
const name = host.name ?? "agentrun";
|
|
42
|
+
assertHostTools(host.tools ?? [], { reserved: [name, SUBMIT_TOOL], executable: false });
|
|
42
43
|
const names = (host.tools ?? []).map((tool) => tool.name);
|
|
43
44
|
const checker = () => new WorkflowExtensionService({ allowedTools: names });
|
|
44
45
|
/** Inspect's checks, after naming any tool the workflow uses (a call's, or an agent node's) that the host did not
|
|
@@ -86,7 +87,7 @@ export function createWorkflowTool(host) {
|
|
|
86
87
|
},
|
|
87
88
|
});
|
|
88
89
|
const tool = defineTool({
|
|
89
|
-
name
|
|
90
|
+
name,
|
|
90
91
|
description: "Compose and run AgentRun workflows in this session. Describe lists the tools a workflow may use, as a call's tool or an agent node's tools, and the language; a workflow uses only the tools describe lists, which the host registers. Inspect validates a complete workflow and keeps it as this conversation's draft. Dry-run checks the draft on synthetic values (or the given input) with no model, Jev or tool call. Run starts the draft as a durable task of this conversation and answers with its outcome. Running the same draft on the same input again, after a restart too, reaches the same run and makes no effect twice; again: true starts a fresh run of them, which a later run then reaches. Save, load and list keep named definition revisions.",
|
|
91
92
|
parameters: Type.Object({ ...workflowToolProperties(), again: Type.Optional(Type.Boolean({ description: "Run only: start a fresh run of the same draft and input instead of reaching the one already run." })) }),
|
|
92
93
|
executionMode: "sequential",
|
package/dist/extension.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { Compile } from 'typebox/compile';
|
|
4
|
-
import {
|
|
4
|
+
import { assertHostTools, SUBMIT_TOOL } from './host-tools.js';
|
|
5
5
|
import { Text } from '@earendil-works/pi-tui';
|
|
6
6
|
import { createReadToolDefinition, createBashToolDefinition, createEditToolDefinition, createWriteToolDefinition, createGrepToolDefinition, createFindToolDefinition, createLsToolDefinition, VERSION as PI_VERSION, } from '@earendil-works/pi-coding-agent';
|
|
7
7
|
import { formatWorkflowTree, inspectWorkflow, AUTHOR_SKILL_NAME } from '@parcha/agentrun-dsl';
|
|
@@ -24,11 +24,14 @@ import { showWorkflowInspector } from './workflow-inspector.js';
|
|
|
24
24
|
import { authoringReferences, describeTools, dryRunDraft, formatDryRun, inspectCandidate, workflowToolParameters } from './workflow-tool.js';
|
|
25
25
|
import { supportTriageWorkflow, supportTriageInputs, supportTriageTools, scriptedSupportTriageDeps } from './triage-demo.js';
|
|
26
26
|
import { restoreWorkflowSession, workflowRunHistory } from './workflow-session.js';
|
|
27
|
+
import { OperatorError } from './operator-error.js';
|
|
27
28
|
const constructors = {
|
|
28
29
|
read: createReadToolDefinition, bash: createBashToolDefinition, edit: createEditToolDefinition,
|
|
29
30
|
write: createWriteToolDefinition, grep: createGrepToolDefinition, find: createFindToolDefinition, ls: createLsToolDefinition,
|
|
30
31
|
};
|
|
31
32
|
const mutatingTools = new Set(['bash', 'edit', 'write']);
|
|
33
|
+
/** The extension's own tool's name, which a host tool may not take. */
|
|
34
|
+
const toolName = 'agentrun';
|
|
32
35
|
const help = 'Describe a task: /agentrun <what you want done>\nTry support triage: /agentrun triage (fictional, no model calls)\nInspect: /agentrun · Change input: /agentrun input\nSave: /agentrun save <name> · Load: /agentrun load <name>\nLibrary: /agentrun list · History: /agentrun history\nRun: /agentrun run · Dry run: /agentrun dry-run · SOP text: /agentrun sop <file> · Setup: /agentrun status\nStop: /agentrun stop (closing the inspector does not stop a run)\nOther examples: /agentrun demo | demo empty | demo live';
|
|
33
36
|
const textResult = (text, details = {}) => ({ content: [{ type: 'text', text: safe(text) }], details });
|
|
34
37
|
function singleTextInput(workflow) {
|
|
@@ -79,7 +82,7 @@ function registerExtension(pi, configuration) {
|
|
|
79
82
|
const toolSet = (ctx, trusted = false) => {
|
|
80
83
|
if (configuration.hostTools) {
|
|
81
84
|
const supplied = configuration.hostTools(ctx);
|
|
82
|
-
|
|
85
|
+
assertHostTools(supplied, { reserved: [toolName, SUBMIT_TOOL], executable: true });
|
|
83
86
|
return supplied.map(tool => ({ ...tool, parameters: structuredClone(tool.parameters), ...(tool.resultSchema ? { resultSchema: structuredClone(tool.resultSchema) } : {}) }));
|
|
84
87
|
}
|
|
85
88
|
const active = new Set(pi.getActiveTools());
|
|
@@ -153,7 +156,8 @@ function registerExtension(pi, configuration) {
|
|
|
153
156
|
pi.sendMessage({ customType: 'agentrun', content: safe(content), display: true, details });
|
|
154
157
|
};
|
|
155
158
|
const commandError = (s, error) => {
|
|
156
|
-
|
|
159
|
+
// What may reach the operator is decided by the error's type, never by its text.
|
|
160
|
+
const message = error instanceof Error && (error instanceof WorkflowStoreError || error.name === 'ExtensionServiceError' || error instanceof OperatorError)
|
|
157
161
|
? error.message.slice(0, 2000) : 'AgentRun could not complete this operation. Use /agentrun status to check setup and /agentrun to inspect the workflow. No fallback was selected.';
|
|
158
162
|
show(s, message);
|
|
159
163
|
};
|
|
@@ -164,10 +168,10 @@ function registerExtension(pi, configuration) {
|
|
|
164
168
|
};
|
|
165
169
|
const inspect = (ctx, s, workflow, input) => {
|
|
166
170
|
if (s.busy && (workflow !== undefined || input !== undefined))
|
|
167
|
-
throw new
|
|
171
|
+
throw new OperatorError('A workflow is running. Stop it before changing its definition or input.');
|
|
168
172
|
const candidate = workflow === undefined ? s.draft : workflow;
|
|
169
173
|
if (candidate === undefined)
|
|
170
|
-
throw new
|
|
174
|
+
throw new OperatorError('No workflow yet. Describe a task with /agentrun <task>.');
|
|
171
175
|
// Include active tools available only with operator trust, without granting that trust.
|
|
172
176
|
// Execution rechecks the actual per-run permissions and captures implementations.
|
|
173
177
|
const checker = new WorkflowExtensionService({ allowedTools: toolSet(ctx, true).map(tool => tool.name), limits: serviceLimits });
|
|
@@ -206,7 +210,7 @@ function registerExtension(pi, configuration) {
|
|
|
206
210
|
signal.throwIfAborted();
|
|
207
211
|
toolCalls++;
|
|
208
212
|
if (limits.toolAttempts !== null && toolCalls > limits.toolAttempts)
|
|
209
|
-
throw new
|
|
213
|
+
throw new OperatorError('Workflow tool-call limit reached');
|
|
210
214
|
configuration.onToolAttempt?.();
|
|
211
215
|
signal.throwIfAborted();
|
|
212
216
|
};
|
|
@@ -224,7 +228,7 @@ function registerExtension(pi, configuration) {
|
|
|
224
228
|
signal.throwIfAborted();
|
|
225
229
|
modelCalls++;
|
|
226
230
|
if (limits.modelRequests !== null && modelCalls > limits.modelRequests)
|
|
227
|
-
throw new
|
|
231
|
+
throw new OperatorError('Workflow model-request limit reached');
|
|
228
232
|
return ctx.modelRegistry.streamSimple(model, transcript, { ...options, reasoning: options?.reasoning === 'off' ? undefined : options?.reasoning, signal: options?.signal ? AbortSignal.any([signal, options.signal]) : signal });
|
|
229
233
|
},
|
|
230
234
|
},
|
|
@@ -247,26 +251,26 @@ function registerExtension(pi, configuration) {
|
|
|
247
251
|
judge = createJevRunner({ signal, maxAttempts: 1 });
|
|
248
252
|
}
|
|
249
253
|
catch {
|
|
250
|
-
throw new
|
|
254
|
+
throw new OperatorError('AgentRun requires TypeSafe configuration for this workflow. Set TYPESAFE_API_KEY and, for a gateway, TYPESAFE_BASE_URL before running.');
|
|
251
255
|
}
|
|
252
256
|
}
|
|
253
257
|
}
|
|
254
258
|
return {
|
|
255
259
|
runNode: request => {
|
|
256
260
|
if (!runner)
|
|
257
|
-
throw new
|
|
261
|
+
throw new OperatorError('Workflow agent adapter was not admitted');
|
|
258
262
|
return runner(request);
|
|
259
263
|
},
|
|
260
264
|
runJudge: request => {
|
|
261
265
|
if (!judge)
|
|
262
|
-
throw new
|
|
266
|
+
throw new OperatorError('Workflow Jev adapter was not admitted');
|
|
263
267
|
return judge(request);
|
|
264
268
|
},
|
|
265
269
|
runEffect: async ({ node, input, signal: effectSignal }) => {
|
|
266
270
|
countTool();
|
|
267
271
|
const tool = tools.find(tool => tool.name === node.tool);
|
|
268
272
|
if (!tool || node.via !== 'tool')
|
|
269
|
-
throw new
|
|
273
|
+
throw new OperatorError('Workflow requested an unavailable tool');
|
|
270
274
|
let result;
|
|
271
275
|
try {
|
|
272
276
|
result = await tool.execute('agentrun-effect', input, effectSignal, undefined, workflowToolContext(ctx));
|
|
@@ -292,21 +296,21 @@ function registerExtension(pi, configuration) {
|
|
|
292
296
|
else if (s.sop) {
|
|
293
297
|
const read = await readSopFile(ctx.cwd, s.sop.file);
|
|
294
298
|
if (read.sha256 !== s.sop.sha256)
|
|
295
|
-
throw new
|
|
299
|
+
throw new OperatorError(`SOP file ${s.sop.file} changed since it was supplied. Run /agentrun sop ${s.sop.file} again to use the new text.`);
|
|
296
300
|
sop = read;
|
|
297
301
|
}
|
|
298
302
|
else
|
|
299
|
-
throw new
|
|
303
|
+
throw new OperatorError(`Workflow requires SOP text for ${needed.map(name => `"${name}"`).join(', ')}. Supply it with /agentrun sop <file>.`);
|
|
300
304
|
const missing = missingSopSections(sop.text, needed);
|
|
301
305
|
if (missing.length)
|
|
302
|
-
throw new
|
|
306
|
+
throw new OperatorError(`SOP text has no "## <section>" heading for ${missing.map(name => `"${name}"`).join(', ')}.`);
|
|
303
307
|
return sop;
|
|
304
308
|
};
|
|
305
309
|
const execute = async (ctx, s, input, options = {}) => {
|
|
306
310
|
if (s.busy)
|
|
307
|
-
throw new
|
|
311
|
+
throw new OperatorError('A workflow is already running. Use /agentrun stop first.');
|
|
308
312
|
if (!s.draft)
|
|
309
|
-
throw new
|
|
313
|
+
throw new OperatorError('Inspect a workflow first. Try /agentrun demo, or describe a task with /agentrun <task>.');
|
|
310
314
|
const scripted = s.demo !== undefined;
|
|
311
315
|
s.busy = true;
|
|
312
316
|
s.controller = new AbortController();
|
|
@@ -425,7 +429,7 @@ function registerExtension(pi, configuration) {
|
|
|
425
429
|
* only when the user's command trusts this dry run. */
|
|
426
430
|
const dryRun = async (ctx, s, input, trusted, signal) => {
|
|
427
431
|
if (!s.draft)
|
|
428
|
-
throw new
|
|
432
|
+
throw new OperatorError('Inspect a workflow first. Try /agentrun demo, or describe a task with /agentrun <task>.');
|
|
429
433
|
const draft = s.draft;
|
|
430
434
|
// /agentrun stop, the tool call's own abort and the session's end each stop it, from before its first await on.
|
|
431
435
|
const controller = new AbortController();
|
|
@@ -452,7 +456,7 @@ function registerExtension(pi, configuration) {
|
|
|
452
456
|
};
|
|
453
457
|
const launch = async (ctx, s, input, trusted = false) => {
|
|
454
458
|
if (s.busy)
|
|
455
|
-
throw new
|
|
459
|
+
throw new OperatorError('A workflow is already running. Use /agentrun stop first.');
|
|
456
460
|
// Capture host selection and permission before yielding the interactive command loop.
|
|
457
461
|
const runContext = { ...ctx, model: ctx.model, thinkingLevel: ctx.thinkingLevel ?? pi.getThinkingLevel() };
|
|
458
462
|
const pending = execute(runContext, s, structuredClone(input), { trusted })
|
|
@@ -467,10 +471,10 @@ function registerExtension(pi, configuration) {
|
|
|
467
471
|
await pending;
|
|
468
472
|
};
|
|
469
473
|
pi.registerTool({
|
|
470
|
-
name:
|
|
474
|
+
name: toolName, label: 'AgentRun workflow',
|
|
471
475
|
description: `Use the ${AUTHOR_SKILL_NAME} skill to compose workflows.` + ' Describe lists available tools and their schemas. Inspect or run an AgentRun DSL workflow in this Pi session. First inspect to show its graph. Dry-run checks the inspected workflow on synthetic values (or the given input) with no model, Jev or tool call; code runs only with the user command /agentrun dry-run --trusted. Agent nodes capture the active Pi model when execution starts. Code needs the user command /agentrun run --trusted for each run. ' + (configuration.hostTools
|
|
472
476
|
? 'Only the explicit host-configured tools are available, including during trusted runs. Outer permission hooks are not inherited. '
|
|
473
|
-
: 'Enabled read-only Pi built-ins are available to declared steps. Shell/write/edit need the trusted command for each run; custom extension tools and permission hooks are not inherited. The search and support_demo tools use fictional supplied sources only. ') + 'Save stores a named procedure revision, not its input or permission. Load preflights current host capabilities.
|
|
477
|
+
: 'Enabled read-only Pi built-ins are available to declared steps. Shell/write/edit need the trusted command for each run; custom extension tools and permission hooks are not inherited. The search and support_demo tools use fictional supplied sources only. ') + 'Save stores a named procedure revision, not its input or permission. Load preflights current host capabilities. A run this session left interrupted or failed resumes from its journal when its draft, input, SOP and tools are unchanged; any other run starts new. Scripted demos stay scripted on rerun.',
|
|
474
478
|
parameters: workflowToolParameters(),
|
|
475
479
|
executionMode: 'sequential',
|
|
476
480
|
renderResult(result, options) {
|
|
@@ -487,9 +491,9 @@ function registerExtension(pi, configuration) {
|
|
|
487
491
|
const store = new WorkflowStore(ctx.cwd);
|
|
488
492
|
if (args.action === 'save') {
|
|
489
493
|
if (s.busy)
|
|
490
|
-
throw new
|
|
494
|
+
throw new OperatorError('A workflow is running. Save after it stops.');
|
|
491
495
|
if (!s.draft)
|
|
492
|
-
throw new
|
|
496
|
+
throw new OperatorError('No workflow to save. Inspect a workflow first.');
|
|
493
497
|
const saved = await store.save(args.name ?? '', s.draft);
|
|
494
498
|
s.savedName = saved.name;
|
|
495
499
|
persist(s);
|
|
@@ -583,9 +587,9 @@ function registerExtension(pi, configuration) {
|
|
|
583
587
|
}
|
|
584
588
|
else if (/^save(?:\s|$)/.test(command)) {
|
|
585
589
|
if (s.busy)
|
|
586
|
-
throw new
|
|
590
|
+
throw new OperatorError('A workflow is running. Save after it stops.');
|
|
587
591
|
if (!s.draft)
|
|
588
|
-
throw new
|
|
592
|
+
throw new OperatorError('No workflow to save. Inspect a workflow first.');
|
|
589
593
|
const saved = await new WorkflowStore(ctx.cwd).save(command.slice(4).trim(), s.draft);
|
|
590
594
|
s.savedName = saved.name;
|
|
591
595
|
persist(s);
|
|
@@ -639,23 +643,23 @@ function registerExtension(pi, configuration) {
|
|
|
639
643
|
return;
|
|
640
644
|
}
|
|
641
645
|
if (s.busy)
|
|
642
|
-
throw new
|
|
646
|
+
throw new OperatorError('A workflow is running. Stop it before changing its SOP.');
|
|
643
647
|
if (configuration.sop)
|
|
644
|
-
throw new
|
|
648
|
+
throw new OperatorError('SOP text comes from the host; /agentrun sop is not used.');
|
|
645
649
|
const read = await readSopFile(ctx.cwd, file);
|
|
646
650
|
const needed = s.draft ? inspectWorkflow(s.draft).requires.sopSections : [];
|
|
647
651
|
const missing = missingSopSections(read.text, needed);
|
|
648
652
|
if (missing.length)
|
|
649
|
-
throw new
|
|
653
|
+
throw new OperatorError(`SOP text has no "## <section>" heading for ${missing.map(name => `"${name}"`).join(', ')}. Nothing was changed.`);
|
|
650
654
|
s.sop = { file: read.file, sha256: read.sha256 };
|
|
651
655
|
persist(s);
|
|
652
656
|
show(s, `SOP set: ${read.file} (${read.text.length} characters). ${needed.length ? `Covers ${needed.map(name => `"${name}"`).join(', ')}.` : 'The current workflow names no section.'}`);
|
|
653
657
|
}
|
|
654
658
|
else if (/^input(?:\s|$)/.test(command)) {
|
|
655
659
|
if (!s.draft)
|
|
656
|
-
throw new
|
|
660
|
+
throw new OperatorError('No workflow yet. Load or describe a procedure first.');
|
|
657
661
|
if (s.busy)
|
|
658
|
-
throw new
|
|
662
|
+
throw new OperatorError('A workflow is running. Stop it before changing its input.');
|
|
659
663
|
let supplied = raw.slice(5).trim();
|
|
660
664
|
if (!supplied && ctx.hasUI)
|
|
661
665
|
supplied = await ctx.ui.input('New workflow input', 'Describe the new case in plain language, or paste a JSON object.') ?? '';
|
|
@@ -669,10 +673,10 @@ function registerExtension(pi, configuration) {
|
|
|
669
673
|
input = JSON.parse(supplied);
|
|
670
674
|
}
|
|
671
675
|
catch {
|
|
672
|
-
throw new
|
|
676
|
+
throw new OperatorError('Workflow input must be valid JSON, or describe it in plain language.');
|
|
673
677
|
}
|
|
674
678
|
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
675
|
-
throw new
|
|
679
|
+
throw new OperatorError('Workflow input must be an object.');
|
|
676
680
|
inspect(ctx, s, undefined, input);
|
|
677
681
|
show(s, `Input updated. Nothing was run.\n\n${formatWorkflowView(view(ctx, s))}`);
|
|
678
682
|
}
|
|
@@ -738,12 +742,12 @@ function registerExtension(pi, configuration) {
|
|
|
738
742
|
}
|
|
739
743
|
else {
|
|
740
744
|
if (s.busy)
|
|
741
|
-
throw new
|
|
745
|
+
throw new OperatorError('A workflow is running. Stop it before starting another.');
|
|
742
746
|
if (!pi.getCommands().some(item => item.name === `skill:${AUTHOR_SKILL_NAME}` && item.source === 'skill')) {
|
|
743
|
-
throw new
|
|
747
|
+
throw new OperatorError('AgentRun authoring skill is unavailable. Enable package skills, then /reload. Run /agentrun status to check discovery.');
|
|
744
748
|
}
|
|
745
749
|
if (!readiness(ctx, s).pi)
|
|
746
|
-
throw new
|
|
750
|
+
throw new OperatorError(PI_MODEL_SETUP_MESSAGE);
|
|
747
751
|
pi.sendUserMessage(`/skill:${AUTHOR_SKILL_NAME} ${raw}`, { expandPromptTemplates: true });
|
|
748
752
|
}
|
|
749
753
|
}
|
package/dist/host-session.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { Agent } from '@earendil-works/pi-agent-core';
|
|
2
|
+
import { SUBMIT_TOOL } from './host-tools.js';
|
|
2
3
|
import { createPiRunner } from './runner.js';
|
|
3
4
|
import { workflowToolContext } from './tool-context.js';
|
|
5
|
+
import { OperatorError } from './operator-error.js';
|
|
4
6
|
export const PI_MODEL_SETUP_MESSAGE = 'AgentRun requires an active Pi model. Use /login to connect a provider, then /model to select it. Try /agentrun demo without model access.';
|
|
5
7
|
function selectedModel(context) {
|
|
6
8
|
const model = context.model;
|
|
7
9
|
if (!model)
|
|
8
|
-
throw new
|
|
10
|
+
throw new OperatorError(PI_MODEL_SETUP_MESSAGE);
|
|
9
11
|
if (!context.modelRegistry || typeof context.modelRegistry.streamSimple !== 'function' || typeof context.modelRegistry.getAll !== 'function') {
|
|
10
|
-
throw new
|
|
12
|
+
throw new OperatorError('AgentRun requires the host public model registry');
|
|
11
13
|
}
|
|
12
14
|
if (!context.modelRegistry.getAll().some(known => known.id === model.id && known.provider === model.provider && known.api === model.api)) {
|
|
13
|
-
throw new
|
|
15
|
+
throw new OperatorError(PI_MODEL_SETUP_MESSAGE);
|
|
14
16
|
}
|
|
15
17
|
return model;
|
|
16
18
|
}
|
|
@@ -28,7 +30,7 @@ export function createPiHostSessionFactory(context) {
|
|
|
28
30
|
parameters: tool.parameters,
|
|
29
31
|
async execute(id, args, signal, onUpdate) {
|
|
30
32
|
const result = await tool.execute(id, args, signal, onUpdate, toolContext);
|
|
31
|
-
if (tool.name ===
|
|
33
|
+
if (tool.name === SUBMIT_TOOL && result.details?.accepted === true)
|
|
32
34
|
delivered = true;
|
|
33
35
|
return result;
|
|
34
36
|
},
|
package/dist/host-tools.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
/** The tool a workflow node delivers its result with (the runner's and the durable record tool's), so no host tool may
|
|
2
|
+
* take its name. */
|
|
3
|
+
export declare const SUBMIT_TOOL = "submit";
|
|
1
4
|
/**
|
|
2
5
|
* Refuses a host's tool list unless it is an array of at most 100 tools, each with a name of 1 to 200 characters that
|
|
3
6
|
* is not blank, not in `reserved` and not repeated, and an object parameter schema (and result schema, when given) the
|
|
4
7
|
* compiler takes. With `executable`, each tool also needs an `execute` function. Throws on the first problem.
|
|
5
8
|
*/
|
|
6
|
-
export declare function
|
|
9
|
+
export declare function assertHostTools(tools: unknown, options: {
|
|
7
10
|
reserved: readonly string[];
|
|
8
11
|
executable: boolean;
|
|
9
12
|
}): void;
|
package/dist/host-tools.js
CHANGED
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
import { Compile } from 'typebox/compile';
|
|
4
4
|
/** The most tools a host may register. */
|
|
5
5
|
const MAX_HOST_TOOLS = 100;
|
|
6
|
+
/** The tool a workflow node delivers its result with (the runner's and the durable record tool's), so no host tool may
|
|
7
|
+
* take its name. */
|
|
8
|
+
export const SUBMIT_TOOL = 'submit';
|
|
6
9
|
/**
|
|
7
10
|
* Refuses a host's tool list unless it is an array of at most 100 tools, each with a name of 1 to 200 characters that
|
|
8
11
|
* is not blank, not in `reserved` and not repeated, and an object parameter schema (and result schema, when given) the
|
|
9
12
|
* compiler takes. With `executable`, each tool also needs an `execute` function. Throws on the first problem.
|
|
10
13
|
*/
|
|
11
|
-
export function
|
|
14
|
+
export function assertHostTools(tools, options) {
|
|
12
15
|
if (!Array.isArray(tools) || tools.length > MAX_HOST_TOOLS)
|
|
13
16
|
throw new Error(`Host must supply at most ${MAX_HOST_TOOLS} explicit tool definitions`);
|
|
14
17
|
const names = new Set();
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** An error whose message is written for the operator, so the extension shows it as it is. Any other error reaches the
|
|
2
|
+
* operator as a generic message, since its text may carry what an operator should not see. Its name stays `Error`. */
|
|
3
|
+
export declare class OperatorError extends Error {
|
|
4
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** An error whose message is written for the operator, so the extension shows it as it is. Any other error reaches the
|
|
2
|
+
* operator as a generic message, since its text may carry what an operator should not see. Its name stays `Error`. */
|
|
3
|
+
export class OperatorError extends Error {
|
|
4
|
+
}
|
package/dist/runner.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Compile } from "typebox/compile";
|
|
2
2
|
import { createAgentSession, DefaultResourceLoader, SessionManager, SettingsManager, ModelRuntime, } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
import { SUBMIT_TOOL } from "./host-tools.js";
|
|
3
4
|
import { PiRunError } from "./run-error.js";
|
|
4
5
|
import { nodeThinking } from "./thinking.js";
|
|
5
6
|
export { PiRunError } from "./run-error.js";
|
|
@@ -49,7 +50,7 @@ export function createPiRunner(options) {
|
|
|
49
50
|
const timeoutMs = options.timeoutMs === null ? null : positive(options.timeoutMs ?? 120_000, "timeoutMs");
|
|
50
51
|
const names = new Set();
|
|
51
52
|
for (const tool of options.tools ?? []) {
|
|
52
|
-
if (tool.name ===
|
|
53
|
+
if (tool.name === SUBMIT_TOOL || names.has(tool.name))
|
|
53
54
|
throw new Error(`Reserved or duplicate Pi tool: ${tool.name}`);
|
|
54
55
|
names.add(tool.name);
|
|
55
56
|
}
|
|
@@ -96,7 +97,7 @@ export function createPiRunner(options) {
|
|
|
96
97
|
let unsubscribe;
|
|
97
98
|
const result = (text, ok) => ({ content: [{ type: "text", text }], details: { accepted: ok } });
|
|
98
99
|
const submit = {
|
|
99
|
-
name:
|
|
100
|
+
name: SUBMIT_TOOL, label: "Submit result", description: "Deliver a result matching the required JSON Schema. Rejection feedback must be corrected in this session.",
|
|
100
101
|
parameters,
|
|
101
102
|
executionMode: "sequential",
|
|
102
103
|
async execute(id, args) {
|
|
@@ -162,7 +163,7 @@ export function createPiRunner(options) {
|
|
|
162
163
|
cwd, model: options.model, modelRuntime: options.modelRuntime,
|
|
163
164
|
thinkingLevel: nodeThinking(request.thinking, options.thinkingLevel), maxTurns: maxTurns ?? undefined, resourceLoader, settingsManager,
|
|
164
165
|
sessionManager,
|
|
165
|
-
tools: [...requested,
|
|
166
|
+
tools: [...requested, SUBMIT_TOOL],
|
|
166
167
|
customTools: [...(options.tools ?? []).filter(tool => requested.includes(tool.name)).map(tool => ({
|
|
167
168
|
...tool,
|
|
168
169
|
executionMode: "sequential",
|
|
@@ -192,7 +193,7 @@ export function createPiRunner(options) {
|
|
|
192
193
|
unsubscribe = session.subscribe(event => {
|
|
193
194
|
// Native events carry correlation IDs, including SDK-rejected calls. Custom
|
|
194
195
|
// sessions without IDs use execute's counter so one attempt is not counted twice.
|
|
195
|
-
if (event.type === "tool_execution_start" && event.toolName ===
|
|
196
|
+
if (event.type === "tool_execution_start" && event.toolName === SUBMIT_TOOL && event.toolCallId !== undefined && !accepted && !stopped && !fatal) {
|
|
196
197
|
submissions++;
|
|
197
198
|
pendingSubmissions.set(event.toolCallId, {
|
|
198
199
|
...(Object.hasOwn(event, "args") ? { args: structuredClone(event.args) } : {}), executed: false,
|
|
@@ -200,7 +201,7 @@ export function createPiRunner(options) {
|
|
|
200
201
|
if (maxSubmissions !== null && submissions > maxSubmissions)
|
|
201
202
|
stop("submission_limit");
|
|
202
203
|
}
|
|
203
|
-
if (event.type === "tool_execution_end" && event.toolName ===
|
|
204
|
+
if (event.type === "tool_execution_end" && event.toolName === SUBMIT_TOOL) {
|
|
204
205
|
if (event.toolCallId !== undefined)
|
|
205
206
|
pendingSubmissions.delete(event.toolCallId);
|
|
206
207
|
if (!accepted && !stopped && !fatal && maxSubmissions !== null && submissions >= maxSubmissions)
|
|
@@ -228,7 +229,8 @@ export function createPiRunner(options) {
|
|
|
228
229
|
throw new PiRunError(stopped, turns, submissions);
|
|
229
230
|
if (accepted)
|
|
230
231
|
return submission;
|
|
231
|
-
|
|
232
|
+
// The loop ends only on one of the three above or at the turn cap, so what is left is the cap.
|
|
233
|
+
throw new PiRunError("turn_limit", turns, submissions);
|
|
232
234
|
}
|
|
233
235
|
catch (error) {
|
|
234
236
|
if (fatal)
|
package/dist/sop.js
CHANGED
|
@@ -1,25 +1,26 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { lstat, readFile, realpath } from 'node:fs/promises';
|
|
3
3
|
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { OperatorError } from './operator-error.js';
|
|
4
5
|
export const MAX_SOP_BYTES = 512 * 1024;
|
|
5
6
|
export const sopDigest = (text) => createHash('sha256').update(text).digest('hex');
|
|
6
7
|
/** The SOP text of a regular file inside `cwd`; a path that resolves outside it, or a file over the bound, is refused. */
|
|
7
8
|
export async function readSopFile(cwd, file) {
|
|
8
9
|
if (!file.trim() || file.includes('\0'))
|
|
9
|
-
throw new
|
|
10
|
+
throw new OperatorError('Use /agentrun sop <file>, a path inside this project.');
|
|
10
11
|
const root = await realpath(cwd);
|
|
11
|
-
const target = await realpath(resolve(cwd, file)).catch(() => { throw new
|
|
12
|
+
const target = await realpath(resolve(cwd, file)).catch(() => { throw new OperatorError(`SOP file not found: ${file}`); });
|
|
12
13
|
const inside = relative(root, target);
|
|
13
14
|
if (!inside || inside === '..' || inside.startsWith(`..${sep}`) || isAbsolute(inside))
|
|
14
|
-
throw new
|
|
15
|
+
throw new OperatorError('The SOP file must be inside this project.');
|
|
15
16
|
const info = await lstat(target);
|
|
16
17
|
if (!info.isFile())
|
|
17
|
-
throw new
|
|
18
|
+
throw new OperatorError('The SOP must be a regular file.');
|
|
18
19
|
if (info.size > MAX_SOP_BYTES)
|
|
19
|
-
throw new
|
|
20
|
+
throw new OperatorError(`The SOP file is larger than ${MAX_SOP_BYTES / 1024} KiB.`);
|
|
20
21
|
const text = (await readFile(target, 'utf8')).replace(/^/, '');
|
|
21
22
|
if (!text.trim())
|
|
22
|
-
throw new
|
|
23
|
+
throw new OperatorError('The SOP file is empty.');
|
|
23
24
|
return { file: inside.split(sep).join('/'), text, sha256: sopDigest(text) };
|
|
24
25
|
}
|
|
25
26
|
/** The sections a workflow names that the SOP has no `## <section>` heading line for. */
|
package/dist/workflow-tool.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Type } from 'typebox';
|
|
2
2
|
import { type AuthorHostAddendum, type DryRunResult, type WorkflowDeps } from '@parcha/agentrun-dsl';
|
|
3
|
-
import type
|
|
3
|
+
import { type WorkflowExtensionService } from './extension-service.js';
|
|
4
4
|
export declare const WORKFLOW_TOOL_ACTIONS: readonly ["describe", "inspect", "dry-run", "run", "save", "load", "list"];
|
|
5
5
|
/** The tool's fields: one action, and the fields an action reads. A host's tool may add its own. */
|
|
6
6
|
export declare const workflowToolProperties: () => {
|
package/dist/workflow-tool.js
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// and the dry run. Nothing here imports pi's coding agent, its agent core or its TUI, so a pi-durable host can use it.
|
|
3
3
|
import { Type } from 'typebox';
|
|
4
4
|
import { compileTransform, dryRunWorkflow, formatWorkflowTree, inspectWorkflow, loadAuthorReference, renderAuthorHostAddendum, } from '@parcha/agentrun-dsl';
|
|
5
|
+
import { ExtensionServiceError } from './extension-service.js';
|
|
5
6
|
import { PI_HOST_ADDENDUM } from './host-addendum.js';
|
|
7
|
+
import { OperatorError } from './operator-error.js';
|
|
6
8
|
export const WORKFLOW_TOOL_ACTIONS = ['describe', 'inspect', 'dry-run', 'run', 'save', 'load', 'list'];
|
|
7
9
|
/** The tool's fields: one action, and the fields an action reads. A host's tool may add its own. */
|
|
8
10
|
export const workflowToolProperties = () => ({ action: Type.Union(WORKFLOW_TOOL_ACTIONS.map(value => Type.Literal(value))),
|
|
@@ -28,14 +30,16 @@ export const authoringReferences = (host = PI_HOST_ADDENDUM) => ({
|
|
|
28
30
|
* refusal throws, naming why. */
|
|
29
31
|
export function inspectCandidate(checker, candidate, input) {
|
|
30
32
|
if (candidate === null || typeof candidate !== 'object' || Array.isArray(candidate)) {
|
|
31
|
-
throw new
|
|
33
|
+
throw new OperatorError('Cannot inspect workflow: workflow must be a JSON object, not a JSON-encoded string or array. Pass the complete object directly.');
|
|
32
34
|
}
|
|
33
35
|
let inspection;
|
|
34
36
|
try {
|
|
35
37
|
inspection = checker.preflight(candidate, input);
|
|
36
38
|
}
|
|
37
39
|
catch (error) {
|
|
38
|
-
|
|
40
|
+
const message = `Cannot inspect workflow: ${error instanceof Error ? error.message : 'Invalid workflow'}`;
|
|
41
|
+
// The service's refusal keeps its type and code, so a host branches on it as it does for a run; either keeps its cause.
|
|
42
|
+
throw error instanceof ExtensionServiceError ? Object.assign(new ExtensionServiceError(error.code, message), { cause: error }) : new OperatorError(message, { cause: error });
|
|
39
43
|
}
|
|
40
44
|
return { inspection, tree: formatWorkflowTree(inspection) };
|
|
41
45
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parcha/agentrun-pi",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.27",
|
|
4
4
|
"description": "Pi extension for building, running and evaluating AgentRun workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -50,12 +50,12 @@
|
|
|
50
50
|
"@earendil-works/pi-coding-agent": "1.1.0",
|
|
51
51
|
"@earendil-works/pi-durable": "1.1.0",
|
|
52
52
|
"@earendil-works/pi-tui": "1.1.0",
|
|
53
|
-
"@parcha/pi-durable-disk": "0.1.0-beta.
|
|
53
|
+
"@parcha/pi-durable-disk": "0.1.0-beta.27",
|
|
54
54
|
"typebox": "1.3.7"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@parcha/agentrun-dsl": "0.1.0-beta.
|
|
58
|
-
"@parcha/agentrun-jev": "0.1.0-beta.
|
|
57
|
+
"@parcha/agentrun-dsl": "0.1.0-beta.27",
|
|
58
|
+
"@parcha/agentrun-jev": "0.1.0-beta.27"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@earendil-works/pi-agent-core": "*",
|