@parcha/agentrun-pi 0.1.0-beta.26 → 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/extension.js +32 -30
- package/dist/host-session.js +4 -3
- package/dist/operator-error.d.ts +4 -0
- package/dist/operator-error.js +4 -0
- package/dist/sop.js +7 -6
- package/dist/workflow-tool.js +3 -2
- package/package.json +4 -4
package/dist/extension.js
CHANGED
|
@@ -24,6 +24,7 @@ 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,
|
|
@@ -155,7 +156,8 @@ function registerExtension(pi, configuration) {
|
|
|
155
156
|
pi.sendMessage({ customType: 'agentrun', content: safe(content), display: true, details });
|
|
156
157
|
};
|
|
157
158
|
const commandError = (s, error) => {
|
|
158
|
-
|
|
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)
|
|
159
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.';
|
|
160
162
|
show(s, message);
|
|
161
163
|
};
|
|
@@ -166,10 +168,10 @@ function registerExtension(pi, configuration) {
|
|
|
166
168
|
};
|
|
167
169
|
const inspect = (ctx, s, workflow, input) => {
|
|
168
170
|
if (s.busy && (workflow !== undefined || input !== undefined))
|
|
169
|
-
throw new
|
|
171
|
+
throw new OperatorError('A workflow is running. Stop it before changing its definition or input.');
|
|
170
172
|
const candidate = workflow === undefined ? s.draft : workflow;
|
|
171
173
|
if (candidate === undefined)
|
|
172
|
-
throw new
|
|
174
|
+
throw new OperatorError('No workflow yet. Describe a task with /agentrun <task>.');
|
|
173
175
|
// Include active tools available only with operator trust, without granting that trust.
|
|
174
176
|
// Execution rechecks the actual per-run permissions and captures implementations.
|
|
175
177
|
const checker = new WorkflowExtensionService({ allowedTools: toolSet(ctx, true).map(tool => tool.name), limits: serviceLimits });
|
|
@@ -208,7 +210,7 @@ function registerExtension(pi, configuration) {
|
|
|
208
210
|
signal.throwIfAborted();
|
|
209
211
|
toolCalls++;
|
|
210
212
|
if (limits.toolAttempts !== null && toolCalls > limits.toolAttempts)
|
|
211
|
-
throw new
|
|
213
|
+
throw new OperatorError('Workflow tool-call limit reached');
|
|
212
214
|
configuration.onToolAttempt?.();
|
|
213
215
|
signal.throwIfAborted();
|
|
214
216
|
};
|
|
@@ -226,7 +228,7 @@ function registerExtension(pi, configuration) {
|
|
|
226
228
|
signal.throwIfAborted();
|
|
227
229
|
modelCalls++;
|
|
228
230
|
if (limits.modelRequests !== null && modelCalls > limits.modelRequests)
|
|
229
|
-
throw new
|
|
231
|
+
throw new OperatorError('Workflow model-request limit reached');
|
|
230
232
|
return ctx.modelRegistry.streamSimple(model, transcript, { ...options, reasoning: options?.reasoning === 'off' ? undefined : options?.reasoning, signal: options?.signal ? AbortSignal.any([signal, options.signal]) : signal });
|
|
231
233
|
},
|
|
232
234
|
},
|
|
@@ -249,26 +251,26 @@ function registerExtension(pi, configuration) {
|
|
|
249
251
|
judge = createJevRunner({ signal, maxAttempts: 1 });
|
|
250
252
|
}
|
|
251
253
|
catch {
|
|
252
|
-
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.');
|
|
253
255
|
}
|
|
254
256
|
}
|
|
255
257
|
}
|
|
256
258
|
return {
|
|
257
259
|
runNode: request => {
|
|
258
260
|
if (!runner)
|
|
259
|
-
throw new
|
|
261
|
+
throw new OperatorError('Workflow agent adapter was not admitted');
|
|
260
262
|
return runner(request);
|
|
261
263
|
},
|
|
262
264
|
runJudge: request => {
|
|
263
265
|
if (!judge)
|
|
264
|
-
throw new
|
|
266
|
+
throw new OperatorError('Workflow Jev adapter was not admitted');
|
|
265
267
|
return judge(request);
|
|
266
268
|
},
|
|
267
269
|
runEffect: async ({ node, input, signal: effectSignal }) => {
|
|
268
270
|
countTool();
|
|
269
271
|
const tool = tools.find(tool => tool.name === node.tool);
|
|
270
272
|
if (!tool || node.via !== 'tool')
|
|
271
|
-
throw new
|
|
273
|
+
throw new OperatorError('Workflow requested an unavailable tool');
|
|
272
274
|
let result;
|
|
273
275
|
try {
|
|
274
276
|
result = await tool.execute('agentrun-effect', input, effectSignal, undefined, workflowToolContext(ctx));
|
|
@@ -294,21 +296,21 @@ function registerExtension(pi, configuration) {
|
|
|
294
296
|
else if (s.sop) {
|
|
295
297
|
const read = await readSopFile(ctx.cwd, s.sop.file);
|
|
296
298
|
if (read.sha256 !== s.sop.sha256)
|
|
297
|
-
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.`);
|
|
298
300
|
sop = read;
|
|
299
301
|
}
|
|
300
302
|
else
|
|
301
|
-
throw new
|
|
303
|
+
throw new OperatorError(`Workflow requires SOP text for ${needed.map(name => `"${name}"`).join(', ')}. Supply it with /agentrun sop <file>.`);
|
|
302
304
|
const missing = missingSopSections(sop.text, needed);
|
|
303
305
|
if (missing.length)
|
|
304
|
-
throw new
|
|
306
|
+
throw new OperatorError(`SOP text has no "## <section>" heading for ${missing.map(name => `"${name}"`).join(', ')}.`);
|
|
305
307
|
return sop;
|
|
306
308
|
};
|
|
307
309
|
const execute = async (ctx, s, input, options = {}) => {
|
|
308
310
|
if (s.busy)
|
|
309
|
-
throw new
|
|
311
|
+
throw new OperatorError('A workflow is already running. Use /agentrun stop first.');
|
|
310
312
|
if (!s.draft)
|
|
311
|
-
throw new
|
|
313
|
+
throw new OperatorError('Inspect a workflow first. Try /agentrun demo, or describe a task with /agentrun <task>.');
|
|
312
314
|
const scripted = s.demo !== undefined;
|
|
313
315
|
s.busy = true;
|
|
314
316
|
s.controller = new AbortController();
|
|
@@ -427,7 +429,7 @@ function registerExtension(pi, configuration) {
|
|
|
427
429
|
* only when the user's command trusts this dry run. */
|
|
428
430
|
const dryRun = async (ctx, s, input, trusted, signal) => {
|
|
429
431
|
if (!s.draft)
|
|
430
|
-
throw new
|
|
432
|
+
throw new OperatorError('Inspect a workflow first. Try /agentrun demo, or describe a task with /agentrun <task>.');
|
|
431
433
|
const draft = s.draft;
|
|
432
434
|
// /agentrun stop, the tool call's own abort and the session's end each stop it, from before its first await on.
|
|
433
435
|
const controller = new AbortController();
|
|
@@ -454,7 +456,7 @@ function registerExtension(pi, configuration) {
|
|
|
454
456
|
};
|
|
455
457
|
const launch = async (ctx, s, input, trusted = false) => {
|
|
456
458
|
if (s.busy)
|
|
457
|
-
throw new
|
|
459
|
+
throw new OperatorError('A workflow is already running. Use /agentrun stop first.');
|
|
458
460
|
// Capture host selection and permission before yielding the interactive command loop.
|
|
459
461
|
const runContext = { ...ctx, model: ctx.model, thinkingLevel: ctx.thinkingLevel ?? pi.getThinkingLevel() };
|
|
460
462
|
const pending = execute(runContext, s, structuredClone(input), { trusted })
|
|
@@ -489,9 +491,9 @@ function registerExtension(pi, configuration) {
|
|
|
489
491
|
const store = new WorkflowStore(ctx.cwd);
|
|
490
492
|
if (args.action === 'save') {
|
|
491
493
|
if (s.busy)
|
|
492
|
-
throw new
|
|
494
|
+
throw new OperatorError('A workflow is running. Save after it stops.');
|
|
493
495
|
if (!s.draft)
|
|
494
|
-
throw new
|
|
496
|
+
throw new OperatorError('No workflow to save. Inspect a workflow first.');
|
|
495
497
|
const saved = await store.save(args.name ?? '', s.draft);
|
|
496
498
|
s.savedName = saved.name;
|
|
497
499
|
persist(s);
|
|
@@ -585,9 +587,9 @@ function registerExtension(pi, configuration) {
|
|
|
585
587
|
}
|
|
586
588
|
else if (/^save(?:\s|$)/.test(command)) {
|
|
587
589
|
if (s.busy)
|
|
588
|
-
throw new
|
|
590
|
+
throw new OperatorError('A workflow is running. Save after it stops.');
|
|
589
591
|
if (!s.draft)
|
|
590
|
-
throw new
|
|
592
|
+
throw new OperatorError('No workflow to save. Inspect a workflow first.');
|
|
591
593
|
const saved = await new WorkflowStore(ctx.cwd).save(command.slice(4).trim(), s.draft);
|
|
592
594
|
s.savedName = saved.name;
|
|
593
595
|
persist(s);
|
|
@@ -641,23 +643,23 @@ function registerExtension(pi, configuration) {
|
|
|
641
643
|
return;
|
|
642
644
|
}
|
|
643
645
|
if (s.busy)
|
|
644
|
-
throw new
|
|
646
|
+
throw new OperatorError('A workflow is running. Stop it before changing its SOP.');
|
|
645
647
|
if (configuration.sop)
|
|
646
|
-
throw new
|
|
648
|
+
throw new OperatorError('SOP text comes from the host; /agentrun sop is not used.');
|
|
647
649
|
const read = await readSopFile(ctx.cwd, file);
|
|
648
650
|
const needed = s.draft ? inspectWorkflow(s.draft).requires.sopSections : [];
|
|
649
651
|
const missing = missingSopSections(read.text, needed);
|
|
650
652
|
if (missing.length)
|
|
651
|
-
throw new
|
|
653
|
+
throw new OperatorError(`SOP text has no "## <section>" heading for ${missing.map(name => `"${name}"`).join(', ')}. Nothing was changed.`);
|
|
652
654
|
s.sop = { file: read.file, sha256: read.sha256 };
|
|
653
655
|
persist(s);
|
|
654
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.'}`);
|
|
655
657
|
}
|
|
656
658
|
else if (/^input(?:\s|$)/.test(command)) {
|
|
657
659
|
if (!s.draft)
|
|
658
|
-
throw new
|
|
660
|
+
throw new OperatorError('No workflow yet. Load or describe a procedure first.');
|
|
659
661
|
if (s.busy)
|
|
660
|
-
throw new
|
|
662
|
+
throw new OperatorError('A workflow is running. Stop it before changing its input.');
|
|
661
663
|
let supplied = raw.slice(5).trim();
|
|
662
664
|
if (!supplied && ctx.hasUI)
|
|
663
665
|
supplied = await ctx.ui.input('New workflow input', 'Describe the new case in plain language, or paste a JSON object.') ?? '';
|
|
@@ -671,10 +673,10 @@ function registerExtension(pi, configuration) {
|
|
|
671
673
|
input = JSON.parse(supplied);
|
|
672
674
|
}
|
|
673
675
|
catch {
|
|
674
|
-
throw new
|
|
676
|
+
throw new OperatorError('Workflow input must be valid JSON, or describe it in plain language.');
|
|
675
677
|
}
|
|
676
678
|
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
677
|
-
throw new
|
|
679
|
+
throw new OperatorError('Workflow input must be an object.');
|
|
678
680
|
inspect(ctx, s, undefined, input);
|
|
679
681
|
show(s, `Input updated. Nothing was run.\n\n${formatWorkflowView(view(ctx, s))}`);
|
|
680
682
|
}
|
|
@@ -740,12 +742,12 @@ function registerExtension(pi, configuration) {
|
|
|
740
742
|
}
|
|
741
743
|
else {
|
|
742
744
|
if (s.busy)
|
|
743
|
-
throw new
|
|
745
|
+
throw new OperatorError('A workflow is running. Stop it before starting another.');
|
|
744
746
|
if (!pi.getCommands().some(item => item.name === `skill:${AUTHOR_SKILL_NAME}` && item.source === 'skill')) {
|
|
745
|
-
throw new
|
|
747
|
+
throw new OperatorError('AgentRun authoring skill is unavailable. Enable package skills, then /reload. Run /agentrun status to check discovery.');
|
|
746
748
|
}
|
|
747
749
|
if (!readiness(ctx, s).pi)
|
|
748
|
-
throw new
|
|
750
|
+
throw new OperatorError(PI_MODEL_SETUP_MESSAGE);
|
|
749
751
|
pi.sendUserMessage(`/skill:${AUTHOR_SKILL_NAME} ${raw}`, { expandPromptTemplates: true });
|
|
750
752
|
}
|
|
751
753
|
}
|
package/dist/host-session.js
CHANGED
|
@@ -2,16 +2,17 @@ import { Agent } from '@earendil-works/pi-agent-core';
|
|
|
2
2
|
import { SUBMIT_TOOL } from './host-tools.js';
|
|
3
3
|
import { createPiRunner } from './runner.js';
|
|
4
4
|
import { workflowToolContext } from './tool-context.js';
|
|
5
|
+
import { OperatorError } from './operator-error.js';
|
|
5
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.';
|
|
6
7
|
function selectedModel(context) {
|
|
7
8
|
const model = context.model;
|
|
8
9
|
if (!model)
|
|
9
|
-
throw new
|
|
10
|
+
throw new OperatorError(PI_MODEL_SETUP_MESSAGE);
|
|
10
11
|
if (!context.modelRegistry || typeof context.modelRegistry.streamSimple !== 'function' || typeof context.modelRegistry.getAll !== 'function') {
|
|
11
|
-
throw new
|
|
12
|
+
throw new OperatorError('AgentRun requires the host public model registry');
|
|
12
13
|
}
|
|
13
14
|
if (!context.modelRegistry.getAll().some(known => known.id === model.id && known.provider === model.provider && known.api === model.api)) {
|
|
14
|
-
throw new
|
|
15
|
+
throw new OperatorError(PI_MODEL_SETUP_MESSAGE);
|
|
15
16
|
}
|
|
16
17
|
return model;
|
|
17
18
|
}
|
|
@@ -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/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.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Type } from 'typebox';
|
|
|
4
4
|
import { compileTransform, dryRunWorkflow, formatWorkflowTree, inspectWorkflow, loadAuthorReference, renderAuthorHostAddendum, } from '@parcha/agentrun-dsl';
|
|
5
5
|
import { ExtensionServiceError } from './extension-service.js';
|
|
6
6
|
import { PI_HOST_ADDENDUM } from './host-addendum.js';
|
|
7
|
+
import { OperatorError } from './operator-error.js';
|
|
7
8
|
export const WORKFLOW_TOOL_ACTIONS = ['describe', 'inspect', 'dry-run', 'run', 'save', 'load', 'list'];
|
|
8
9
|
/** The tool's fields: one action, and the fields an action reads. A host's tool may add its own. */
|
|
9
10
|
export const workflowToolProperties = () => ({ action: Type.Union(WORKFLOW_TOOL_ACTIONS.map(value => Type.Literal(value))),
|
|
@@ -29,7 +30,7 @@ export const authoringReferences = (host = PI_HOST_ADDENDUM) => ({
|
|
|
29
30
|
* refusal throws, naming why. */
|
|
30
31
|
export function inspectCandidate(checker, candidate, input) {
|
|
31
32
|
if (candidate === null || typeof candidate !== 'object' || Array.isArray(candidate)) {
|
|
32
|
-
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.');
|
|
33
34
|
}
|
|
34
35
|
let inspection;
|
|
35
36
|
try {
|
|
@@ -38,7 +39,7 @@ export function inspectCandidate(checker, candidate, input) {
|
|
|
38
39
|
catch (error) {
|
|
39
40
|
const message = `Cannot inspect workflow: ${error instanceof Error ? error.message : 'Invalid workflow'}`;
|
|
40
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.
|
|
41
|
-
throw error instanceof ExtensionServiceError ? Object.assign(new ExtensionServiceError(error.code, message), { cause: error }) : new
|
|
42
|
+
throw error instanceof ExtensionServiceError ? Object.assign(new ExtensionServiceError(error.code, message), { cause: error }) : new OperatorError(message, { cause: error });
|
|
42
43
|
}
|
|
43
44
|
return { inspection, tree: formatWorkflowTree(inspection) };
|
|
44
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": "*",
|