@marcoscale98/pi-extensible-workflows 5.14.1-fork.1 → 5.19.1-fork.1
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/CHANGELOG.md +135 -0
- package/dist/src/agent-execution.js +114 -87
- package/dist/src/background-widget.d.ts +61 -0
- package/dist/src/background-widget.js +23 -16
- package/dist/src/decoders.js +28 -1
- package/dist/src/execution.d.ts +4 -2
- package/dist/src/execution.js +43 -2
- package/dist/src/host-delivery.js +1 -0
- package/dist/src/host-navigator.d.ts +18 -0
- package/dist/src/host-navigator.js +71 -13
- package/dist/src/host-recovery.d.ts +5 -2
- package/dist/src/host-recovery.js +3 -3
- package/dist/src/host-view.d.ts +21 -1
- package/dist/src/host-view.js +138 -47
- package/dist/src/host.d.ts +2 -2
- package/dist/src/host.js +246 -60
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.js +1346 -622
- package/dist/src/index.js.map +3 -3
- package/dist/src/paths.d.ts +2 -0
- package/dist/src/paths.js +2 -0
- package/dist/src/registry.js +3 -2
- package/dist/src/roles.d.ts +70 -0
- package/dist/src/roles.js +326 -0
- package/dist/src/script-tools.d.ts +27 -0
- package/dist/src/script-tools.js +87 -0
- package/dist/src/settings.d.ts +26 -0
- package/dist/src/settings.js +277 -0
- package/dist/src/trajectory.d.ts +39 -4
- package/dist/src/trajectory.js +82 -5
- package/dist/src/types.d.ts +30 -3
- package/dist/src/types.js +1 -1
- package/dist/src/utils.d.ts +21 -1
- package/dist/src/utils.js +40 -0
- package/dist/src/validation.d.ts +10 -17
- package/dist/src/validation.js +29 -458
- package/dist/starter/index.js +0 -2
- package/dist/starter/index.js.map +1 -1
- package/dist/starter/review-loop.d.ts +0 -13
- package/dist/starter/review-loop.js +0 -2
- package/dist/subagents/index.js +1344 -798
- package/dist/subagents/index.js.map +3 -3
- package/dist/subagents/src/contracts.d.ts +49 -29
- package/dist/subagents/src/contracts.js +18 -7
- package/dist/subagents/src/index.d.ts +1 -1
- package/dist/subagents/src/index.js +15 -7
- package/dist/subagents/src/manager.js +32 -13
- package/dist/subagents/src/navigator.js +396 -247
- package/dist/subagents/src/view.d.ts +24 -2
- package/dist/subagents/src/view.js +107 -80
- package/dist/trajectory/assets/index.html +370 -232
- package/dist/trajectory/assets/prism.min.js +13 -0
- package/dist/trajectory/index.js +445 -277
- package/dist/trajectory/index.js.map +3 -3
- package/dist/trajectory/src/assets/index.html +370 -232
- package/dist/trajectory/src/assets/prism.min.js +13 -0
- package/dist/trajectory/src/export.js +4 -2
- package/dist/trajectory/src/index.d.ts +2 -0
- package/dist/trajectory/src/index.js +151 -60
- package/dist/trajectory/src/server.js +138 -11
- package/dist/trajectory/src/server.js.map +2 -2
- package/package.json +7 -6
- package/skills/pi-extensible-workflows/SKILL.md +2 -0
- package/src/agent-execution.ts +110 -80
- package/src/background-widget.ts +26 -19
- package/src/decoders.ts +22 -1
- package/src/execution.ts +41 -4
- package/src/host-delivery.ts +1 -0
- package/src/host-navigator.ts +58 -14
- package/src/host-recovery.ts +8 -7
- package/src/host-view.ts +116 -42
- package/src/host.ts +223 -55
- package/src/index.ts +3 -1
- package/src/paths.ts +3 -0
- package/src/registry.ts +3 -2
- package/src/roles.ts +327 -0
- package/src/script-tools.ts +76 -0
- package/src/settings.ts +224 -0
- package/src/trajectory.ts +97 -9
- package/src/types.ts +12 -5
- package/src/utils.ts +36 -1
- package/src/validation.ts +30 -356
- package/starter/review-loop.ts +0 -2
- package/subagents/README.md +3 -3
- package/subagents/src/contracts.ts +22 -7
- package/subagents/src/index.ts +18 -7
- package/subagents/src/manager.ts +31 -13
- package/subagents/src/navigator.ts +336 -180
- package/subagents/src/view.ts +108 -76
- package/trajectory/src/assets/index.html +370 -232
- package/trajectory/src/assets/prism.min.js +13 -0
- package/trajectory/src/export.ts +4 -2
- package/trajectory/src/index.ts +124 -47
- package/trajectory/src/server.ts +114 -14
|
@@ -25,7 +25,7 @@ const ENTRY_TYPE = "piewf-run-receipt";
|
|
|
25
25
|
* Repaint interval. Fast enough that the spinner reads as motion rather than a
|
|
26
26
|
* stutter; the underlying data is only re-read when an event says it changed.
|
|
27
27
|
*/
|
|
28
|
-
const REPAINT_MS = 125;
|
|
28
|
+
export const REPAINT_MS = 125;
|
|
29
29
|
/**
|
|
30
30
|
* Filesystem rescan interval. Shell-only activity has no event, but it does not
|
|
31
31
|
* need to be polled at the animation cadence.
|
|
@@ -51,7 +51,7 @@ const MAX_ROWS = 10;
|
|
|
51
51
|
* of thinking and far shorter than the ten minutes it takes to be declared
|
|
52
52
|
* stalled.
|
|
53
53
|
*/
|
|
54
|
-
const QUIET_MS = 3 * 60 * 1000;
|
|
54
|
+
export const QUIET_MS = 3 * 60 * 1000;
|
|
55
55
|
/** The core's own threshold for calling an agent stalled outright. */
|
|
56
56
|
const STALL_MS = WORKFLOW_AGENT_STALL_THRESHOLD_MS;
|
|
57
57
|
/**
|
|
@@ -130,7 +130,7 @@ function spinner(now) {
|
|
|
130
130
|
return SPINNER[Math.floor(now / REPAINT_MS) % SPINNER.length] ?? SPINNER[0];
|
|
131
131
|
}
|
|
132
132
|
/** Status mark for a finished-or-running unit of work. */
|
|
133
|
-
function mark(state, now, paint, runState) {
|
|
133
|
+
export function mark(state, now, paint, runState) {
|
|
134
134
|
if (state === "running" && (runState === undefined || runState === "running"))
|
|
135
135
|
return paint(ROLE.live, spinner(now));
|
|
136
136
|
if (state === "completed")
|
|
@@ -139,7 +139,7 @@ function mark(state, now, paint, runState) {
|
|
|
139
139
|
return paint(ROLE.failed, "✗");
|
|
140
140
|
return paint(ROLE.quiet, "·");
|
|
141
141
|
}
|
|
142
|
-
function formatElapsed(ms) {
|
|
142
|
+
export function formatElapsed(ms) {
|
|
143
143
|
const total = Math.max(0, Math.floor(ms / 1000));
|
|
144
144
|
const minutes = Math.floor(total / 60);
|
|
145
145
|
const seconds = total % 60;
|
|
@@ -576,13 +576,6 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
|
|
|
576
576
|
const title = live.length === 1 ? "Workflow" : `Workflows · ${String(live.length)} runs`;
|
|
577
577
|
sizeOut.rows = rows.length;
|
|
578
578
|
const body = collapse(rows, MAX_ROWS - 2, paint, offset, scrolling);
|
|
579
|
-
// Width budget: the rule takes a column and a space on each side, so the
|
|
580
|
-
// text has four fewer than the widget is given. Every piece is measured
|
|
581
|
-
// against `inner` so the right-hand rule lands in one column on every row.
|
|
582
|
-
const inner = Math.max(20, width - 4);
|
|
583
|
-
// Side rules below the lid, and no floor: the box opens downward into the
|
|
584
|
-
// editor rather than closing on itself, which is one more line kept for the
|
|
585
|
-
// run.
|
|
586
579
|
// A scrollbar on the right rule, drawn only when there is more tree than
|
|
587
580
|
// frame. Without it the window's position is a guess: three rows of a
|
|
588
581
|
// twenty-row tree look the same near the top as near the bottom.
|
|
@@ -610,6 +603,25 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
|
|
|
610
603
|
const top = limit === 0 ? 0 : start >= limit ? shown - size : Math.floor((start / limit) * (shown - size));
|
|
611
604
|
return index >= top && index < top + size ? "█" : "░";
|
|
612
605
|
};
|
|
606
|
+
// Only advertise navigation when it can reveal hidden rows: a hint for keys
|
|
607
|
+
// that do nothing is worse than no hint.
|
|
608
|
+
const hint = !__navigationForTests.enabled || maxOffset(total, MAX_ROWS - 2) === 0
|
|
609
|
+
? ""
|
|
610
|
+
: scrolling ? "↑/↓ to scroll · Esc to exit" : "Alt+O to scroll";
|
|
611
|
+
return drawFrame(title, hint, body, width, paint, accent, scrollbar);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* The box around the live rows, shared with the subagent widget so both sit
|
|
615
|
+
* under the editor in one style.
|
|
616
|
+
*/
|
|
617
|
+
export function drawFrame(title, hint, body, width, paint, accent, scrollbar = () => undefined) {
|
|
618
|
+
// Width budget: the rule takes a column and a space on each side, so the
|
|
619
|
+
// text has four fewer than the widget is given. Every piece is measured
|
|
620
|
+
// against `inner` so the right-hand rule lands in one column on every row.
|
|
621
|
+
const inner = Math.max(20, width - 4);
|
|
622
|
+
// Side rules below the lid, and no floor: the box opens downward into the
|
|
623
|
+
// editor rather than closing on itself, which is one more line kept for the
|
|
624
|
+
// run.
|
|
613
625
|
const lines = body.map((row, index) => {
|
|
614
626
|
// The numbers hold the right edge; the name gives way when the two would
|
|
615
627
|
// meet. A truncated workflow name is still recognisable, where a truncated
|
|
@@ -630,11 +642,6 @@ function renderFrame(runs, now, width, offset = 0, theme, scrolling = false, siz
|
|
|
630
642
|
// rather than sitting under a separate run of dashes. In ten rows a line
|
|
631
643
|
// spent on decoration is a line not spent on the run, and the box still
|
|
632
644
|
// reads as closed at the top.
|
|
633
|
-
// Only advertise navigation when it can reveal hidden rows: a hint for keys
|
|
634
|
-
// that do nothing is worse than no hint.
|
|
635
|
-
const hint = !__navigationForTests.enabled || maxOffset(total, MAX_ROWS - 2) === 0
|
|
636
|
-
? ""
|
|
637
|
-
: scrolling ? "↑/↓ to scroll · Esc to exit" : "Alt+O to scroll";
|
|
638
645
|
const label = ` ${title} `;
|
|
639
646
|
const tail = hint === "" ? "" : ` ${hint} `;
|
|
640
647
|
const spare = inner + 2 - visibleLength(label) - visibleLength(tail);
|
package/dist/src/decoders.js
CHANGED
|
@@ -109,9 +109,10 @@ export function decodeWorkflowExtensions(value) {
|
|
|
109
109
|
continue;
|
|
110
110
|
}
|
|
111
111
|
if (namespace === "trajectory") {
|
|
112
|
+
// Snapshots persisted before the themes setting was removed still carry a boolean `themes`; it is accepted and dropped.
|
|
112
113
|
if (!object(raw) || Object.keys(raw).some((key) => key !== "port" && key !== "themes") || raw.port !== undefined && (typeof raw.port !== "number" || !Number.isSafeInteger(raw.port) || raw.port < 1 || raw.port > 65535) || raw.themes !== undefined && typeof raw.themes !== "boolean")
|
|
113
114
|
return undefined;
|
|
114
|
-
normalized.trajectory = { ...(raw.port === undefined ? {} : { port: raw.port })
|
|
115
|
+
normalized.trajectory = { ...(raw.port === undefined ? {} : { port: raw.port }) };
|
|
115
116
|
continue;
|
|
116
117
|
}
|
|
117
118
|
if (!jsonValue(raw))
|
|
@@ -433,6 +434,28 @@ function decodeRunEvent(value) {
|
|
|
433
434
|
return undefined;
|
|
434
435
|
return { type: value.type, message: value.message, ...(timestamp === undefined ? {} : { timestamp }) };
|
|
435
436
|
}
|
|
437
|
+
function decodeScriptCall(value) {
|
|
438
|
+
if (!object(value) || value.kind !== "shell" && value.kind !== "tool" || typeof value.name !== "string" || typeof value.path !== "string" || !finiteNumber(value.startedAt))
|
|
439
|
+
return undefined;
|
|
440
|
+
const shell = value.kind === "shell";
|
|
441
|
+
if (shell && !jsonValue(value.input))
|
|
442
|
+
return undefined;
|
|
443
|
+
const inputBytes = shell ? optionalNumber(value.inputBytes) : undefined;
|
|
444
|
+
const phase = optionalString(value.phase);
|
|
445
|
+
const finishedAt = optionalNumber(value.finishedAt);
|
|
446
|
+
let error;
|
|
447
|
+
if (value.error !== undefined) {
|
|
448
|
+
if (shell)
|
|
449
|
+
error = decodeWorkflowError(value.error);
|
|
450
|
+
else if (object(value.error) && typeof value.error.code === "string")
|
|
451
|
+
error = { code: isWorkflowErrorCode(value.error.code) ? value.error.code : "INTERNAL_ERROR" };
|
|
452
|
+
if (!error)
|
|
453
|
+
return undefined;
|
|
454
|
+
}
|
|
455
|
+
if (inputBytes === INVALID_PERSISTED_VALUE || phase === INVALID_PERSISTED_VALUE || finishedAt === INVALID_PERSISTED_VALUE)
|
|
456
|
+
return undefined;
|
|
457
|
+
return { kind: value.kind, name: value.name, path: value.path, ...(shell && jsonValue(value.input) ? { input: value.input } : {}), ...(inputBytes === undefined ? {} : { inputBytes }), ...(phase === undefined ? {} : { phase }), startedAt: value.startedAt, ...(finishedAt === undefined ? {} : { finishedAt }), ...(error === undefined ? {} : { error }) };
|
|
458
|
+
}
|
|
436
459
|
function decodeDelivery(value) {
|
|
437
460
|
if (!object(value))
|
|
438
461
|
return undefined;
|
|
@@ -466,6 +489,9 @@ export function decodePersistedRun(value, allowLegacyAgentSessions = false) {
|
|
|
466
489
|
const budgetEvents = value.budgetEvents === undefined ? undefined : decodeArray(value.budgetEvents, decodeBudgetEvent);
|
|
467
490
|
const events = value.events === undefined ? undefined : decodeArray(value.events, decodeRunEvent);
|
|
468
491
|
const delivery = value.delivery === undefined ? undefined : decodeDelivery(value.delivery);
|
|
492
|
+
const scriptCalls = value.scriptCalls === undefined ? undefined : decodeArray(value.scriptCalls, decodeScriptCall);
|
|
493
|
+
if (value.scriptCalls !== undefined && !scriptCalls)
|
|
494
|
+
return undefined;
|
|
469
495
|
if (!agentSessions || !agents || parentRunId === INVALID_PERSISTED_VALUE || value.retry !== undefined && !retry || phase === INVALID_PERSISTED_VALUE || value.phaseHistory !== undefined && !phaseHistory || phaseHistoryIndex === INVALID_PERSISTED_VALUE || activeShells === INVALID_PERSISTED_VALUE || activeShellStartedAt === INVALID_PERSISTED_VALUE || value.activeShellsByPhase !== undefined && !activeShellsByPhase || value.error !== undefined && !error || failedAt === INVALID_PERSISTED_VALUE || value.budget !== undefined && !budget || budgetVersion === INVALID_PERSISTED_VALUE || value.budgetVersion !== undefined && !integer(budgetVersion) || value.usage !== undefined && !usage || value.budgetEvents !== undefined && !budgetEvents || value.events !== undefined && !events || value.delivery !== undefined && !delivery)
|
|
470
496
|
return undefined;
|
|
471
497
|
if (value.agentSessions === undefined && !allowLegacyAgentSessions)
|
|
@@ -486,6 +512,7 @@ export function decodePersistedRun(value, allowLegacyAgentSessions = false) {
|
|
|
486
512
|
...(activeShells === undefined ? {} : { activeShells }),
|
|
487
513
|
...(activeShellStartedAt === undefined ? {} : { activeShellStartedAt }),
|
|
488
514
|
...(activeShellsByPhase === undefined ? {} : { activeShellsByPhase }),
|
|
515
|
+
...(scriptCalls === undefined ? {} : { scriptCalls }),
|
|
489
516
|
...(error === undefined ? {} : { error }),
|
|
490
517
|
...(failedAt === undefined ? {} : { failedAt }),
|
|
491
518
|
...(budget === undefined ? {} : { budget }),
|
package/dist/src/execution.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { RunStore } from "./persistence.js";
|
|
2
2
|
import type { AgentAttempt } from "./agent-execution.js";
|
|
3
|
-
import type { AgentIdentity, JsonValue, ShellIdentity, ShellOptions, ShellResult, WorkflowBridge, WorkflowExecution } from "./types.js";
|
|
3
|
+
import type { AgentIdentity, JsonValue, ShellIdentity, ShellOptions, ShellResult, ToolIdentity, WorkflowBridge, WorkflowExecution } from "./types.js";
|
|
4
4
|
export declare const RPC_LIMIT_BYTES: number;
|
|
5
5
|
export declare const HEARTBEAT_TIMEOUT_MS = 5000;
|
|
6
6
|
export declare function encoded(value: JsonValue): string;
|
|
7
7
|
export declare function encoded(value: unknown): asserts value is JsonValue;
|
|
8
|
+
/** The journal path of a script tool call; the arguments' digest keeps calls with different arguments apart whatever order they start in. */
|
|
9
|
+
export declare function toolIdentityPath(identifier: string, args: JsonValue, identity: ToolIdentity): string;
|
|
8
10
|
export declare function agentHandleTurnPath(handle: string, turn: number): string;
|
|
9
11
|
export declare function agentIdentityPath(identity: AgentIdentity): string;
|
|
10
12
|
export declare function shellIdentityPath(identity: ShellIdentity): string;
|
|
@@ -16,4 +18,4 @@ export declare function executeShellCommand(command: string, options: ShellOptio
|
|
|
16
18
|
export declare function runWorkflow(script: string, args?: JsonValue, bridge?: WorkflowBridge, signal?: AbortSignal): WorkflowExecution;
|
|
17
19
|
export declare function persistActiveAgentAttempt(store: RunStore, id: string, active: AgentAttempt): Promise<void>;
|
|
18
20
|
export declare function persistAgentAttempts(store: RunStore, id: string, attempts: readonly AgentAttempt[]): Promise<void>;
|
|
19
|
-
export type { AgentIdentity, FunctionIdentity, ShellIdentity, WorkflowBridge, WorkflowExecution } from "./types.js";
|
|
21
|
+
export type { AgentIdentity, FunctionIdentity, ShellIdentity, ToolIdentity, WorkflowBridge, WorkflowExecution } from "./types.js";
|
package/dist/src/execution.js
CHANGED
|
@@ -5,7 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import { StringDecoder } from "node:string_decoder";
|
|
6
6
|
import { structuralPath as operationPath } from "./persistence.js";
|
|
7
7
|
import { WorkflowError, roleNameOf, sumAccounting, zeroAccounting } from "./types.js";
|
|
8
|
-
import { asWorkflowError, errorText, fail, isWorkflowAuthored, isWorkflowErrorCode, jsonValue, markWorkflowAuthored, object, positiveInteger } from "./utils.js";
|
|
8
|
+
import { asWorkflowError, errorText, fail, isWorkflowAuthored, isWorkflowErrorCode, jsonDigest, jsonValue, markWorkflowAuthored, object, positiveInteger } from "./utils.js";
|
|
9
9
|
import { instrumentWorkflow, validateAgentOptions, validateShellCommand, validateShellOptions } from "./validation.js";
|
|
10
10
|
export const RPC_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
11
11
|
export const HEARTBEAT_TIMEOUT_MS = 5000;
|
|
@@ -268,6 +268,30 @@ const functions = Object.freeze(Object.fromEntries(Object.entries(config.functio
|
|
|
268
268
|
Object.defineProperty(result, "toJSON", { value() { throw workError("INVALID_METADATA", "Workflow function result is a Promise; await it before serialization"); } });
|
|
269
269
|
return result;
|
|
270
270
|
}])));
|
|
271
|
+
// tools.<id>(args) runs a Pi session tool through the host. Any identifier resolves, so a resumed run
|
|
272
|
+
// replays journaled calls even when the tool is gone; the host rejects identifiers it cannot call.
|
|
273
|
+
// A call is the n-th call with the same arguments in its scope, so replay does not depend on the
|
|
274
|
+
// order in which concurrent calls start.
|
|
275
|
+
const NOT_TOOLS = new Set(["then", "toJSON", "toString", "valueOf", "constructor"]);
|
|
276
|
+
const sortedJson = value => Array.isArray(value) ? value.map(sortedJson) : value && typeof value === "object" ? Object.fromEntries(Object.keys(value).sort().map(key => [key, sortedJson(value[key])])) : value;
|
|
277
|
+
const toolOccurrences = new Map();
|
|
278
|
+
const toolCall = id => (...values) => {
|
|
279
|
+
if (worktreeOwners.getStore()) throw workError("INVALID_METADATA", "tools." + id + " is unavailable inside withWorktree: session tools resolve paths against the session working directory");
|
|
280
|
+
if (values.length > 1 || values[0] !== undefined && (!values[0] || typeof values[0] !== "object" || Array.isArray(values[0]))) throw workError("INVALID_METADATA", "tools." + id + " takes one arguments object");
|
|
281
|
+
const args = values[0] === undefined ? {} : values[0];
|
|
282
|
+
const structuralPath = inheritedAgentPath.getStore() || [];
|
|
283
|
+
const key = JSON.stringify([structuralPath, id, sortedJson(args)]);
|
|
284
|
+
const occurrence = (toolOccurrences.get(key) || 0) + 1;
|
|
285
|
+
toolOccurrences.set(key, occurrence);
|
|
286
|
+
return guardedResult(rpc("tool", [id, args, { structuralPath: [...structuralPath], occurrence }]), "tool");
|
|
287
|
+
};
|
|
288
|
+
const tools = new Proxy(Object.create(null), {
|
|
289
|
+
get: (_target, key) => typeof key === "string" && !NOT_TOOLS.has(key) ? toolCall(key) : undefined,
|
|
290
|
+
// Probing would branch on the session that resumed the run; the launch already checks named tools.
|
|
291
|
+
has: () => { throw workError("INVALID_METADATA", "Workflow scripts cannot probe tools with 'in'; call tools.<name>(...) directly, and the launch checks that the tool exists"); },
|
|
292
|
+
set: () => false,
|
|
293
|
+
defineProperty: () => false,
|
|
294
|
+
});
|
|
271
295
|
const recordEntries = (value, kind) => {
|
|
272
296
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw workError("INVALID_METADATA", kind + " must be a record");
|
|
273
297
|
return Object.entries(value);
|
|
@@ -328,7 +352,7 @@ const pipeline = async (operationName, items, stages) => {
|
|
|
328
352
|
return Object.fromEntries(results.map(result => [result.name, result.value]));
|
|
329
353
|
};
|
|
330
354
|
const safeMath = Object.fromEntries(Object.getOwnPropertyNames(Math).filter(name => name !== "random").map(name => [name, Math[name]]));
|
|
331
|
-
const sandbox = { agent, shell, withWorktree: rejectWorktree, prompt, checkpoint, parallel, pipeline, phase, log, args: config.args, Promise, JSON, Math: Object.freeze(safeMath) };
|
|
355
|
+
const sandbox = { agent, shell, withWorktree: rejectWorktree, prompt, checkpoint, parallel, pipeline, phase, log, tools, args: config.args, Promise, JSON, Math: Object.freeze(safeMath) };
|
|
332
356
|
for (const [name, fn] of Object.entries(functions)) Object.defineProperty(sandbox, name, { value: fn, writable: false, configurable: false });
|
|
333
357
|
for (const name of ["Date","eval","Function","WebAssembly","process","require","module","exports","console","fetch","XMLHttpRequest","WebSocket","performance","crypto","setTimeout","setInterval","setImmediate","queueMicrotask","Intl","SharedArrayBuffer","Atomics"]) sandbox[name] = undefined;
|
|
334
358
|
const context = vm.createContext(sandbox, { codeGeneration: { strings: false, wasm: false } });
|
|
@@ -378,6 +402,15 @@ function readShellIdentity(value) {
|
|
|
378
402
|
const identity = readAgentIdentity(value);
|
|
379
403
|
return { structuralPath: identity.structuralPath, callSite: identity.callSite, occurrence: identity.occurrence, ...(identity.worktreeOwner ? { worktreeOwner: identity.worktreeOwner } : {}) };
|
|
380
404
|
}
|
|
405
|
+
function readToolIdentity(value) {
|
|
406
|
+
if (!object(value) || !Array.isArray(value.structuralPath) || !value.structuralPath.every((part) => typeof part === "string" && Boolean(part.trim())) || !positiveInteger(value.occurrence))
|
|
407
|
+
fail("INTERNAL_ERROR", "Invalid workflow tool identity");
|
|
408
|
+
return { structuralPath: [...value.structuralPath], occurrence: value.occurrence };
|
|
409
|
+
}
|
|
410
|
+
/** The journal path of a script tool call; the arguments' digest keeps calls with different arguments apart whatever order they start in. */
|
|
411
|
+
export function toolIdentityPath(identifier, args, identity) {
|
|
412
|
+
return operationPath("tool", ...identity.structuralPath, identifier, `args:${jsonDigest(args)}`, String(identity.occurrence));
|
|
413
|
+
}
|
|
381
414
|
export function agentHandleTurnPath(handle, turn) {
|
|
382
415
|
return operationPath("agent", "handle", handle, `turn:${String(turn)}`);
|
|
383
416
|
}
|
|
@@ -681,6 +714,14 @@ export function runWorkflow(script, args = null, bridge = {}, signal) {
|
|
|
681
714
|
value = branded({ name, ok: false, failedAt: name, error: workerErrorShape(typed) });
|
|
682
715
|
}
|
|
683
716
|
}
|
|
717
|
+
else if (method === "tool") {
|
|
718
|
+
if (typeof values[0] !== "string" || !object(values[1]))
|
|
719
|
+
fail("INTERNAL_ERROR", "tool requires an identifier and object arguments");
|
|
720
|
+
const identity = readToolIdentity(values[2]);
|
|
721
|
+
if (!bridge.tool)
|
|
722
|
+
fail("UNKNOWN_TOOL", `tools.${values[0]} is unavailable: this run has no session tools`);
|
|
723
|
+
value = await bridge.tool(values[0], values[1], controller.signal, identity);
|
|
724
|
+
}
|
|
684
725
|
else if (method === "worktree") {
|
|
685
726
|
if (!bridge.worktree || typeof values[0] !== "string" || !values[0])
|
|
686
727
|
fail("INTERNAL_ERROR", "worktree requires an active host bridge and scope");
|
|
@@ -108,6 +108,7 @@ const WORKFLOW_ERROR_PROSE = {
|
|
|
108
108
|
RUN_NOT_FOUND: (detail) => /^Unknown workflow run\b/.test(detail) ? "The workflow run was not found." : `The workflow run was not found: ${detail}.`,
|
|
109
109
|
RPC_LIMIT_EXCEEDED: (detail) => `The workflow communication data exceeded its size limit: ${detail}.`,
|
|
110
110
|
SHELL_FAILED: (detail) => `The workflow shell command failed: ${detail}.`,
|
|
111
|
+
TOOL_FAILED: (detail) => `The workflow tool call failed: ${detail}.`,
|
|
111
112
|
AGENT_TIMEOUT: (detail) => `The workflow agent timed out: ${detail}.`,
|
|
112
113
|
AGENT_FAILED: (detail) => `The workflow agent failed: ${detail}.`,
|
|
113
114
|
AGENT_RESULT_COLLECTED: (detail) => `The nested agent result was already collected: ${detail}.`,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ExtensionUIContext, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { RunStore, type PersistedRun } from "./persistence.js";
|
|
3
3
|
import { type WorkflowRegistryApi } from "./registry.js";
|
|
4
|
+
import { type WorkflowProgressStyles } from "./host-view.js";
|
|
4
5
|
import { type WorkflowRecoveryContext, type createWorkflowRecovery } from "./host-recovery.js";
|
|
5
6
|
import { type WorkflowRunRecord } from "./host-runtime.js";
|
|
6
7
|
import { type TrajectoryPublisherProvider } from "./trajectory-host-handle.js";
|
|
@@ -34,6 +35,23 @@ export type UiHostCapabilities = {
|
|
|
34
35
|
};
|
|
35
36
|
type ReportBlocked = (active: boolean, label?: string) => void;
|
|
36
37
|
export declare function uiHostCapabilities(ui: unknown): UiHostCapabilities | undefined;
|
|
38
|
+
type WorkflowKeybindings = {
|
|
39
|
+
matches(data: string, binding: string): boolean;
|
|
40
|
+
};
|
|
41
|
+
export declare function workflowKeyMatches(keybindings: WorkflowKeybindings, data: string, binding: string): boolean;
|
|
42
|
+
export declare function workflowKeyLabel(keybindings: unknown, binding: string, fallback: string, labels?: Readonly<Record<string, string>>): string;
|
|
43
|
+
/**
|
|
44
|
+
* A yes/no question drawn inside a navigator dashboard. Pi's confirm dialog replaces a custom component and afterwards
|
|
45
|
+
* restores the editor rather than the component, which leaves the dashboard's promise pending and the session waiting on it.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createInlineConfirm(requestRender: () => void): {
|
|
48
|
+
ask(title: string, message: string): Promise<boolean>;
|
|
49
|
+
active(): boolean;
|
|
50
|
+
rows(width: number, styles: WorkflowProgressStyles): string[];
|
|
51
|
+
hint(keybindings: unknown): string;
|
|
52
|
+
handleInput(keybindings: WorkflowKeybindings, data: string): void;
|
|
53
|
+
cancel(): void;
|
|
54
|
+
};
|
|
37
55
|
export type WorkflowNavigatorDependencies = {
|
|
38
56
|
pi: Pick<ExtensionAPI, "registerCommand">;
|
|
39
57
|
home: string | undefined;
|
|
@@ -110,13 +110,57 @@ function keybindingKeys(keybindings, name) {
|
|
|
110
110
|
return isKeybindingGetKeys(getKeys) ? getKeys.call(keybindings, name) : undefined;
|
|
111
111
|
}
|
|
112
112
|
const WORKFLOW_VIM_KEYS = { "tui.select.up": "k", "tui.select.down": "j", "tui.editor.cursorLeft": "h", "tui.editor.cursorRight": "l" };
|
|
113
|
-
function workflowKeyMatches(keybindings, data, binding) { return keybindings.matches(data, binding) || WORKFLOW_VIM_KEYS[binding] === data; }
|
|
114
|
-
|
|
113
|
+
export function workflowKeyMatches(keybindings, data, binding) { return keybindings.matches(data, binding) || WORKFLOW_VIM_KEYS[binding] === data; }
|
|
114
|
+
const WORKFLOW_KEY_LABELS = { up: "↑", down: "↓", left: "←", right: "→", pageUp: "pgup", pageDown: "pgdn" };
|
|
115
|
+
export function workflowKeyLabel(keybindings, binding, fallback, labels = WORKFLOW_KEY_LABELS) {
|
|
115
116
|
const keys = keybindingKeys(keybindings, binding);
|
|
116
117
|
const configured = keys?.length ? keys.map((key) => labels[key] ?? key) : [fallback];
|
|
117
118
|
const vim = WORKFLOW_VIM_KEYS[binding];
|
|
118
119
|
return [...new Set(vim ? [...configured, vim] : configured)].join("/");
|
|
119
120
|
}
|
|
121
|
+
/**
|
|
122
|
+
* A yes/no question drawn inside a navigator dashboard. Pi's confirm dialog replaces a custom component and afterwards
|
|
123
|
+
* restores the editor rather than the component, which leaves the dashboard's promise pending and the session waiting on it.
|
|
124
|
+
*/
|
|
125
|
+
export function createInlineConfirm(requestRender) {
|
|
126
|
+
let pending;
|
|
127
|
+
const answer = (value) => {
|
|
128
|
+
const current = pending;
|
|
129
|
+
pending = undefined;
|
|
130
|
+
current?.resolve(value);
|
|
131
|
+
requestRender();
|
|
132
|
+
};
|
|
133
|
+
return {
|
|
134
|
+
ask(title, message) {
|
|
135
|
+
answer(false);
|
|
136
|
+
return new Promise((resolve) => { pending = { title, message, yes: true, resolve }; requestRender(); });
|
|
137
|
+
},
|
|
138
|
+
active() { return pending !== undefined; },
|
|
139
|
+
rows(width, styles) {
|
|
140
|
+
if (!pending)
|
|
141
|
+
return [];
|
|
142
|
+
const yes = pending.yes;
|
|
143
|
+
const message = truncateToVisualLines(pending.message, Number.MAX_SAFE_INTEGER, Math.max(1, width), 0).visualLines.map((line) => line.trimEnd());
|
|
144
|
+
return [styles.bold(pending.title), ...message, ...["Yes", "No"].map((option) => (option === "Yes") === yes ? `→ ${styles.accent(option)}` : ` ${option}`)];
|
|
145
|
+
},
|
|
146
|
+
hint(keybindings) {
|
|
147
|
+
return `${workflowKeyLabel(keybindings, "tui.select.up", "↑")}/${workflowKeyLabel(keybindings, "tui.select.down", "↓")} select · ${workflowKeyLabel(keybindings, "tui.select.confirm", "enter")} confirm · ${workflowKeyLabel(keybindings, "tui.select.cancel", "esc")} cancel`;
|
|
148
|
+
},
|
|
149
|
+
handleInput(keybindings, data) {
|
|
150
|
+
if (!pending)
|
|
151
|
+
return;
|
|
152
|
+
if (workflowKeyMatches(keybindings, data, "tui.select.up") || workflowKeyMatches(keybindings, data, "tui.select.down")) {
|
|
153
|
+
pending.yes = !pending.yes;
|
|
154
|
+
requestRender();
|
|
155
|
+
}
|
|
156
|
+
else if (workflowKeyMatches(keybindings, data, "tui.select.confirm"))
|
|
157
|
+
answer(pending.yes);
|
|
158
|
+
else if (workflowKeyMatches(keybindings, data, "tui.select.cancel"))
|
|
159
|
+
answer(false);
|
|
160
|
+
},
|
|
161
|
+
cancel() { answer(false); },
|
|
162
|
+
};
|
|
163
|
+
}
|
|
120
164
|
export function registerWorkflowNavigator(deps) {
|
|
121
165
|
const { pi, home, clipboard, extensionAgentDir, runs, terminalRunStates, hardTerminalRunStates, ensureSessionLease, answerCheckpoint, recovery, stopWorkflowRun, moveForegroundToBackground, isForegroundAttached, liveAgents, registry, projectTrusted, resumeHostContext, resumeSelectedWorkflow, reportBlocked, setNavigatorOpen, trajectoryProvider, coordinateRunMutation: coordinateRunMutationDependency } = deps;
|
|
122
166
|
const coordinateRunMutation = coordinateRunMutationDependency ?? ((task) => task());
|
|
@@ -160,7 +204,7 @@ export function registerWorkflowNavigator(deps) {
|
|
|
160
204
|
const setStatus = uiHostCapabilities(ctx.ui)?.setStatus;
|
|
161
205
|
setStatus?.call(ctx.ui, "workflow-stop", text);
|
|
162
206
|
};
|
|
163
|
-
const runAction = async (actionCommand, status = setWorkflowStatus) => {
|
|
207
|
+
const runAction = async (actionCommand, status = setWorkflowStatus, confirm = (title, message) => confirmWithBlocked(ctx.ui, reportBlocked, title, message)) => {
|
|
164
208
|
const [action, runId, ...rest] = actionCommand.split(/\s+/);
|
|
165
209
|
try {
|
|
166
210
|
const run = runId ? runs.get(runId) : undefined;
|
|
@@ -252,7 +296,7 @@ export function registerWorkflowNavigator(deps) {
|
|
|
252
296
|
}
|
|
253
297
|
if (action === "stop" && run) {
|
|
254
298
|
const workflowName = stored?.loaded.run.workflowName ?? run.metadata.name;
|
|
255
|
-
if (!await
|
|
299
|
+
if (!await confirm("Stop workflow?", `Stop workflow ${workflowName} (${run.store.runId})? This cannot be undone.`))
|
|
256
300
|
return "dashboard";
|
|
257
301
|
status(`Stopping workflow ${workflowName}...`);
|
|
258
302
|
await stopWorkflowRun(run.store.runId);
|
|
@@ -651,8 +695,18 @@ export function registerWorkflowNavigator(deps) {
|
|
|
651
695
|
let selectedNodeId = tree.nodes[0]?.id;
|
|
652
696
|
let expandedNodeIds = new Set(workflowPhaseTreeInitialExpanded(tree));
|
|
653
697
|
const terminalRows = () => Math.max(1, tuiRows(tui) - WORKFLOW_PANEL_FOOTER_ROWS);
|
|
654
|
-
const
|
|
655
|
-
const
|
|
698
|
+
const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback);
|
|
699
|
+
const inlineConfirm = createInlineConfirm(() => { if (!disposed)
|
|
700
|
+
tui.requestRender(); });
|
|
701
|
+
const confirmInDashboard = async (title, message) => {
|
|
702
|
+
reportBlocked?.(true, title);
|
|
703
|
+
try {
|
|
704
|
+
return await inlineConfirm.ask(title, message);
|
|
705
|
+
}
|
|
706
|
+
finally {
|
|
707
|
+
reportBlocked?.(false);
|
|
708
|
+
}
|
|
709
|
+
};
|
|
656
710
|
const progressNow = () => {
|
|
657
711
|
const now = Date.now();
|
|
658
712
|
if (hardTerminalRunStates.has(view.run.state)) {
|
|
@@ -738,7 +792,7 @@ export function registerWorkflowNavigator(deps) {
|
|
|
738
792
|
setWorkflowStatus(status);
|
|
739
793
|
if (!disposed)
|
|
740
794
|
tui.requestRender();
|
|
741
|
-
}).then(async (outcome) => {
|
|
795
|
+
}, confirmInDashboard).then(async (outcome) => {
|
|
742
796
|
if (outcome === "stopped") {
|
|
743
797
|
done("__stopped__");
|
|
744
798
|
return;
|
|
@@ -778,7 +832,8 @@ export function registerWorkflowNavigator(deps) {
|
|
|
778
832
|
const content = [...statusLines, ...phaseLines];
|
|
779
833
|
const rows = terminalRows();
|
|
780
834
|
const hintRows = rows >= 3 ? 1 : 0;
|
|
781
|
-
const
|
|
835
|
+
const confirmRows = inlineConfirm.rows(width, styles);
|
|
836
|
+
const viewport = Math.max(1, rows - hintRows - confirmRows.length);
|
|
782
837
|
const maxOffset = Math.max(0, content.length - viewport);
|
|
783
838
|
dashboardOffset = Math.max(0, Math.min(maxOffset, dashboardOffset));
|
|
784
839
|
if (actionMode) {
|
|
@@ -804,11 +859,15 @@ export function registerWorkflowNavigator(deps) {
|
|
|
804
859
|
dashboardOffset = Math.max(0, Math.min(maxOffset, dashboardOffset));
|
|
805
860
|
const selectedNode = selectedNodeId ? tree.byId.get(selectedNodeId) : undefined;
|
|
806
861
|
const enterAction = selectedNode?.kind === "workflow" ? "run actions" : selectedNode?.kind === "agent" ? "agent actions" : selectedNode?.children.length ? "expand/collapse" : narrow ? "inspect" : "focus details";
|
|
807
|
-
const hint = truncateToVisualLines(theme.fg("dim", actionMode ? `${keyLabel("tui.select.up", "↑")}/${keyLabel("tui.select.down", "↓")} actions · ${keyLabel("tui.select.confirm", "enter")} run · ${keyLabel("tui.editor.cursorLeft", "←")} tree · ${keyLabel("tui.select.cancel", "esc")} tree` : `${keyLabel("tui.select.up", "↑")}/${keyLabel("tui.select.down", "↓")} tree · ${keyLabel("tui.editor.cursorLeft", "←")}/${keyLabel("tui.editor.cursorRight", "→")} collapse/expand · ${keyLabel("tui.select.confirm", "enter")} ${enterAction} · a actions · ${keyLabel("tui.select.cancel", "esc")} ${narrow && detailsMode ? "tree" : "back"}${content.length > viewport ? ` · ${keyLabel("tui.select.pageUp", "pgup")}/${keyLabel("tui.select.pageDown", "pgdn")} scroll` : ""} · auto-refresh 1s`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
|
|
808
|
-
return [...content.slice(dashboardOffset, dashboardOffset + viewport), ...(hintRows ? [hint] : [])];
|
|
862
|
+
const hint = truncateToVisualLines(theme.fg("dim", inlineConfirm.active() ? inlineConfirm.hint(keybindings) : actionMode ? `${keyLabel("tui.select.up", "↑")}/${keyLabel("tui.select.down", "↓")} actions · ${keyLabel("tui.select.confirm", "enter")} run · ${keyLabel("tui.editor.cursorLeft", "←")} tree · ${keyLabel("tui.select.cancel", "esc")} tree` : `${keyLabel("tui.select.up", "↑")}/${keyLabel("tui.select.down", "↓")} tree · ${keyLabel("tui.editor.cursorLeft", "←")}/${keyLabel("tui.editor.cursorRight", "→")} collapse/expand · ${keyLabel("tui.select.confirm", "enter")} ${enterAction} · a actions · ${keyLabel("tui.select.cancel", "esc")} ${narrow && detailsMode ? "tree" : "back"}${content.length > viewport ? ` · ${keyLabel("tui.select.pageUp", "pgup")}/${keyLabel("tui.select.pageDown", "pgdn")} scroll` : ""} · auto-refresh 1s`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
|
|
863
|
+
return [...content.slice(dashboardOffset, dashboardOffset + viewport), ...confirmRows, ...(hintRows ? [hint] : [])];
|
|
809
864
|
},
|
|
810
865
|
invalidate() { },
|
|
811
866
|
handleInput(data) {
|
|
867
|
+
if (inlineConfirm.active()) {
|
|
868
|
+
inlineConfirm.handleInput(keybindings, data);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
812
871
|
if (stopRequested || editorRunning)
|
|
813
872
|
return;
|
|
814
873
|
const narrow = renderedWidth < 80;
|
|
@@ -958,7 +1017,7 @@ export function registerWorkflowNavigator(deps) {
|
|
|
958
1017
|
}
|
|
959
1018
|
tui.requestRender();
|
|
960
1019
|
},
|
|
961
|
-
dispose() { disposed = true; stopTimer(); setWorkflowStatus(undefined); },
|
|
1020
|
+
dispose() { disposed = true; stopTimer(); setWorkflowStatus(undefined); inlineConfirm.cancel(); },
|
|
962
1021
|
};
|
|
963
1022
|
})
|
|
964
1023
|
: await ctx.ui.select(view.dashboard, [...view.actions.keys(), "Back"]);
|
|
@@ -1016,8 +1075,7 @@ export function registerWorkflowNavigator(deps) {
|
|
|
1016
1075
|
const currentLayout = layout();
|
|
1017
1076
|
const maxOffset = Math.max(0, renderedLines.length - currentLayout.contentViewport);
|
|
1018
1077
|
offset = Math.min(offset, maxOffset);
|
|
1019
|
-
const
|
|
1020
|
-
const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback, keyLabels);
|
|
1078
|
+
const keyLabel = (binding, fallback) => workflowKeyLabel(keybindings, binding, fallback);
|
|
1021
1079
|
const hint = truncateToVisualLines(theme.fg("dim", `${keyLabel("tui.select.up", "↑")}/${keyLabel("tui.select.down", "↓")}/pgup/pgdn scroll · enter select · esc cancel`), Number.MAX_SAFE_INTEGER, width, 1).visualLines[0] ?? "";
|
|
1022
1080
|
const controls = currentLayout.compactControls
|
|
1023
1081
|
? [options.map((option, index) => `${index === selectedIndex ? "[" : " "}${option}${index === selectedIndex ? "]" : " "}`).join(" ")]
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
2
|
-
import { type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { type ExtensionAPI, type ExtensionToolContext } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { FairAgentScheduler, WorkflowAgentExecutor, type AgentDefinition, type AgentExecutionRoot, type AgentProviderFailure, type AgentProviderRecovery } from "./agent-execution.js";
|
|
4
4
|
import { RunStore, type PersistedRun } from "./persistence.js";
|
|
5
|
-
import { WorkflowError, type AgentIdentity, type AgentResourcePolicy, type BudgetApprovalRequest, type JsonValue, type LaunchSnapshot, type ModelSpec, type RunState, type ShellIdentity, type ShellOptions, type ShellResult, type WorkflowMetadata, type WorkflowWorktreeReference } from "./types.js";
|
|
5
|
+
import { WorkflowError, type AgentIdentity, type AgentResourcePolicy, type BudgetApprovalRequest, type JsonValue, type LaunchSnapshot, type ModelSpec, type RunState, type ShellIdentity, type ShellOptions, type ShellResult, type ToolIdentity, type WorkflowMetadata, type WorkflowWorktreeReference } from "./types.js";
|
|
6
6
|
import { type WorkflowRegistryApi } from "./registry.js";
|
|
7
7
|
import { RunLifecycle, WorkflowEventPublisher, type WorkflowRunRecord, type WorkflowToolUpdate } from "./host-runtime.js";
|
|
8
8
|
import { type CompletionDeliveryContext, type CompletionDeliveryResult } from "./host-delivery.js";
|
|
@@ -21,6 +21,7 @@ export type WorkflowRecoveryContext = {
|
|
|
21
21
|
} | undefined;
|
|
22
22
|
modelRegistry: ModelRegistryCapability | undefined;
|
|
23
23
|
deliveryContext: CompletionDeliveryContext;
|
|
24
|
+
toolContext?: ExtensionToolContext | undefined;
|
|
24
25
|
signal?: AbortSignal;
|
|
25
26
|
resolvedAliases?: Readonly<Record<string, string>>;
|
|
26
27
|
blockedAliases?: ReadonlySet<string>;
|
|
@@ -70,6 +71,7 @@ export type WorkflowRecoveryDependencies = {
|
|
|
70
71
|
};
|
|
71
72
|
workflowAgentHandler: (store: RunStore, metadata: WorkflowMetadata, lifecycle: RunLifecycle, executor: WorkflowAgentExecutor, cwd: string, runId: string, captureRole?: (role: string, model: ModelSpec) => Promise<void>) => (prompt: string, options: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: AgentIdentity) => Promise<JsonValue>;
|
|
72
73
|
shellForRun: (store: RunStore, metadata: WorkflowMetadata, lifecycle: RunLifecycle, command: string, options: ShellOptions, signal: AbortSignal, identity: ShellIdentity) => Promise<ShellResult>;
|
|
74
|
+
toolForRun: (store: RunStore, lifecycle: RunLifecycle, context: ExtensionToolContext | undefined, identifier: string, args: Readonly<Record<string, JsonValue>>, signal: AbortSignal, identity: ToolIdentity) => Promise<JsonValue>;
|
|
73
75
|
resolveWorktree: (store: RunStore, metadata: WorkflowMetadata, owner: string) => Promise<Readonly<WorkflowWorktreeReference>>;
|
|
74
76
|
checkpointBridge: (runId: string, store: RunStore, metadata: WorkflowMetadata, foreground: boolean, ui?: {
|
|
75
77
|
select?: (prompt: string, options: string[]) => Promise<string | undefined>;
|
|
@@ -104,6 +106,7 @@ export declare function createWorkflowRecovery(deps: WorkflowRecoveryDependencie
|
|
|
104
106
|
} | undefined;
|
|
105
107
|
modelRegistry: WorkflowRecoveryContext["modelRegistry"];
|
|
106
108
|
deliveryContext: CompletionDeliveryContext;
|
|
109
|
+
toolContext?: ExtensionToolContext | undefined;
|
|
107
110
|
signal?: AbortSignal | undefined;
|
|
108
111
|
resolvedAliases?: Readonly<Record<string, string>>;
|
|
109
112
|
blockedAliases?: ReadonlySet<string>;
|
|
@@ -46,7 +46,7 @@ function assertExpectedWorkflowState(expectedState, actualState) {
|
|
|
46
46
|
}
|
|
47
47
|
export function persistedFailure(run, error) { const failedAt = workflowFailedAt(error); return { ...run, error: { code: error.code, message: error.message, ...(failedAt ? { failedAt } : {}) }, ...(failedAt ? { failedAt } : {}) }; }
|
|
48
48
|
export function createWorkflowRecovery(deps) {
|
|
49
|
-
const { pi, home, runs, scheduler, eventPublisher, persistRunState, projectTrusted, resumeHostContext, ensureSessionLease, createAgentExecutor, activeSnapshotTools, frozenResourcePolicy, resolveLaunchPrologue, resumeRoles, workflowAgentHandler, shellForRun, resolveWorktree, checkpointBridge, phaseBridge, logBridge, lifecycleFor, createProviderErrorRecovery, cleanupTerminalRun, deliver, deliverTerminal, workflowToolUpdate, registry, modelSpec } = deps;
|
|
49
|
+
const { pi, home, runs, scheduler, eventPublisher, persistRunState, projectTrusted, resumeHostContext, ensureSessionLease, createAgentExecutor, activeSnapshotTools, frozenResourcePolicy, resolveLaunchPrologue, resumeRoles, workflowAgentHandler, shellForRun, toolForRun, resolveWorktree, checkpointBridge, phaseBridge, logBridge, lifecycleFor, createProviderErrorRecovery, cleanupTerminalRun, deliver, deliverTerminal, workflowToolUpdate, registry, modelSpec } = deps;
|
|
50
50
|
const coordinateRunMutation = deps.coordinateRunMutation ?? ((task) => task());
|
|
51
51
|
const budgetDecisionDelivery = (metadata, request) => `Workflow ${metadata.name} budget adjustment ${request.proposalId} for run ${request.runId} requires approval. Consumed usage: ${JSON.stringify(request.consumed)}. Previous limits: ${JSON.stringify(request.previous)}. Proposed limits: ${JSON.stringify(request.proposed)}. Respond with workflow_respond using proposalId ${request.proposalId}.`;
|
|
52
52
|
const appendBudgetDecisionEvent = async (run, request, type) => {
|
|
@@ -119,7 +119,7 @@ export function createWorkflowRecovery(deps) {
|
|
|
119
119
|
run.executor.setRunContext(runContext);
|
|
120
120
|
await scheduler.cancelRun(run.store.runId);
|
|
121
121
|
await run.lifecycle.resume();
|
|
122
|
-
const execution = runWorkflow(script, loaded.snapshot.args, withWorkflowFunctions({ shell: (command, options, signal, identity) => shellForRun(run.store, run.metadata, run.lifecycle, command, options, signal, identity), agent: workflowAgentHandler(run.store, run.metadata, run.lifecycle, run.executor, run.store.cwd, run.store.runId, roles.capture), worktree: async (owner) => resolveWorktree(run.store, run.metadata, owner), checkpoint: checkpointBridge(run.store.runId, run.store, run.metadata, foreground, hasUI ? ui : undefined), phase: phaseBridge(run.store, run.metadata, run.lifecycle), log: logBridge(run.store, run.lifecycle, run.metadata.name) }, run.store, runContext, registry, snapshot.settings.extensionSettings), controller.signal);
|
|
122
|
+
const execution = runWorkflow(script, loaded.snapshot.args, withWorkflowFunctions({ tool: (identifier, args, signal, identity) => toolForRun(run.store, run.lifecycle, context?.toolContext, identifier, args, signal, identity), shell: (command, options, signal, identity) => shellForRun(run.store, run.metadata, run.lifecycle, command, options, signal, identity), agent: workflowAgentHandler(run.store, run.metadata, run.lifecycle, run.executor, run.store.cwd, run.store.runId, roles.capture), worktree: async (owner) => resolveWorktree(run.store, run.metadata, owner), checkpoint: checkpointBridge(run.store.runId, run.store, run.metadata, foreground, hasUI ? ui : undefined), phase: phaseBridge(run.store, run.metadata, run.lifecycle), log: logBridge(run.store, run.lifecycle, run.metadata.name) }, run.store, runContext, registry, snapshot.settings.extensionSettings), controller.signal);
|
|
123
123
|
run.execution = execution;
|
|
124
124
|
const completion = execution.result.then(async (value) => {
|
|
125
125
|
await scheduler.flush(run.store.runId);
|
|
@@ -333,7 +333,7 @@ export function createWorkflowRecovery(deps) {
|
|
|
333
333
|
await eventPublisher.runStarted(prepared.childRun.store, prepared.childRun.metadata);
|
|
334
334
|
const { hasUI, ui } = recoveryUi(context);
|
|
335
335
|
const recoveryContext = resumeHostContext(context);
|
|
336
|
-
const completed = await coldResumeRun(prepared.childRun, hasUI, ui, projectTrusted(context), { model: prepared.hostModel, modelRegistry: prepared.modelRegistry, deliveryContext: recoveryContext.deliveryContext, resolvedAliases: prepared.currentAliases, blockedAliases: prepared.blockedAliases, blockedAliasTargets: prepared.blockedAliasTargets, ...(signal ? { signal } : {}) }, modeOverride);
|
|
336
|
+
const completed = await coldResumeRun(prepared.childRun, hasUI, ui, projectTrusted(context), { model: prepared.hostModel, modelRegistry: prepared.modelRegistry, deliveryContext: recoveryContext.deliveryContext, toolContext: recoveryContext.toolContext, resolvedAliases: prepared.currentAliases, blockedAliases: prepared.blockedAliases, blockedAliasTargets: prepared.blockedAliasTargets, ...(signal ? { signal } : {}) }, modeOverride);
|
|
337
337
|
const completion = runs.get(prepared.childRun.store.runId)?.completion;
|
|
338
338
|
if (completion) {
|
|
339
339
|
childStarted = true;
|
package/dist/src/host-view.d.ts
CHANGED
|
@@ -12,7 +12,16 @@ export interface WorkflowProgressStyles {
|
|
|
12
12
|
dim(text: string): string;
|
|
13
13
|
bold(text: string): string;
|
|
14
14
|
}
|
|
15
|
-
export declare
|
|
15
|
+
export declare const PLAIN_WORKFLOW_PROGRESS_STYLES: WorkflowProgressStyles;
|
|
16
|
+
export declare function runStateGlyph(state: string, running: string): string;
|
|
17
|
+
export declare function progressStyleForState(state: string, styles: WorkflowProgressStyles): (text: string) => string;
|
|
18
|
+
export declare function formatWorkflowRuntime(durationMs: number): string;
|
|
19
|
+
/**
|
|
20
|
+
* Renders the run; with `maxLines`, a view taller than that limit switches to a compact one so the live block stays on screen.
|
|
21
|
+
* A block taller than the terminal pushes its changing header above the viewport, and pi-tui then clears and redraws the whole
|
|
22
|
+
* scrollback on every update, which snaps the reader back to the bottom.
|
|
23
|
+
*/
|
|
24
|
+
export declare function formatWorkflowProgress(run: PersistedRun, spinner?: string, styles?: WorkflowProgressStyles, now?: number, expanded?: boolean, width?: number, showHint?: boolean, maxLines?: number): string;
|
|
16
25
|
export declare function textBlock(text: string): {
|
|
17
26
|
render(width: number): string[];
|
|
18
27
|
invalidate(): void;
|
|
@@ -75,6 +84,11 @@ export declare function navigatorRunLabels(entries: readonly {
|
|
|
75
84
|
export declare function agentBreadcrumbParts(agent: AgentRecord, byId: Map<string, AgentRecord>, includeStructuralPath?: boolean): string[];
|
|
76
85
|
export declare function agentBreadcrumb(agent: AgentRecord, byId: Map<string, AgentRecord>, includeStructuralPath?: boolean): string;
|
|
77
86
|
export declare function formatStalledDuration(durationMs: number): string;
|
|
87
|
+
export declare function stalledDuration(agent: Pick<AgentDetailPresentation, "state" | "lastEventAt">, now: number): number | undefined;
|
|
88
|
+
export declare function agentActivityLabel(agent: {
|
|
89
|
+
readonly activity?: AgentRecord["activity"];
|
|
90
|
+
readonly toolCalls?: AgentRecord["toolCalls"];
|
|
91
|
+
}): string;
|
|
78
92
|
export interface AgentDetailPresentation {
|
|
79
93
|
readonly state: string;
|
|
80
94
|
readonly activity?: AgentRecord["activity"];
|
|
@@ -93,6 +107,7 @@ export interface AgentDetailPresentation {
|
|
|
93
107
|
readonly message: string;
|
|
94
108
|
};
|
|
95
109
|
}
|
|
110
|
+
export declare function formatAgentError(error: NonNullable<AgentDetailPresentation["error"]>, styles?: WorkflowProgressStyles): string;
|
|
96
111
|
export declare function formatAgentDetail(agent: Readonly<AgentDetailPresentation>, styles?: WorkflowProgressStyles, now?: number, options?: Readonly<{
|
|
97
112
|
includeError?: boolean;
|
|
98
113
|
}>): string[];
|
|
@@ -113,4 +128,9 @@ export declare function formatNavigatorRun(loaded: {
|
|
|
113
128
|
snapshot: Readonly<LaunchSnapshot>;
|
|
114
129
|
}, checkpoints: readonly AwaitingCheckpoint[], worktrees: readonly WorktreeReference[], now?: number): string;
|
|
115
130
|
export declare function formatWorkflowPhaseDashboard(run: PersistedRun, snapshot: Readonly<LaunchSnapshot>, width: number, selection?: WorkflowPhaseSelection, styles?: WorkflowProgressStyles, now?: number): string[];
|
|
131
|
+
/** The navigator layout: a sidebar beside details at 80 columns or wider, otherwise one of them (or both stacked). */
|
|
132
|
+
export declare function formatNavigatorColumns(sidebar: readonly string[], detail: readonly string[], width: number, layout?: Readonly<{
|
|
133
|
+
treeOnly?: boolean | undefined;
|
|
134
|
+
detailsOnly?: boolean | undefined;
|
|
135
|
+
}>): string[];
|
|
116
136
|
export declare function formatCheckpointReview(checkpoint: AwaitingCheckpoint): string;
|