@osolmaz/pi-workflows 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -31
- package/dist/builtins/autodoc.workflow.d.ts +4 -4
- package/dist/builtins/autoimplement.workflow.d.ts +797 -142
- package/dist/builtins/autoimplement.workflow.js +17 -105
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/autoplan.workflow.d.ts +126 -13
- package/dist/builtins/autoplan.workflow.js +200 -29
- package/dist/builtins/autoplan.workflow.js.map +1 -1
- package/dist/builtins/catalog.js +7 -5
- package/dist/builtins/catalog.js.map +1 -1
- package/dist/builtins/index.d.ts +4 -2
- package/dist/builtins/index.js +3 -1
- package/dist/builtins/index.js.map +1 -1
- package/dist/builtins/monitor.workflow.d.ts +56 -23
- package/dist/builtins/monitor.workflow.js +508 -329
- package/dist/builtins/monitor.workflow.js.map +1 -1
- package/dist/builtins/pi-agent-group.d.ts +72 -0
- package/dist/builtins/pi-agent-group.js +1087 -0
- package/dist/builtins/pi-agent-group.js.map +1 -0
- package/dist/builtins/plain-summary.workflow.d.ts +32 -0
- package/dist/builtins/plain-summary.workflow.js +134 -0
- package/dist/builtins/plain-summary.workflow.js.map +1 -0
- package/dist/builtins/plan-approval.workflow.d.ts +39 -5
- package/dist/builtins/plan-approval.workflow.js +92 -14
- package/dist/builtins/plan-approval.workflow.js.map +1 -1
- package/dist/builtins/plan-change.workflow.d.ts +449 -0
- package/dist/builtins/plan-change.workflow.js +256 -0
- package/dist/builtins/plan-change.workflow.js.map +1 -0
- package/dist/builtins/plan-presentation.js +2 -2
- package/dist/builtins/plan-presentation.js.map +1 -1
- package/dist/builtins/sanity-check.workflow.d.ts +50 -6
- package/dist/builtins/sanity-check.workflow.js +150 -28
- package/dist/builtins/sanity-check.workflow.js.map +1 -1
- package/dist/controllers/effects.d.ts +3 -2
- package/dist/controllers/effects.js +8 -1
- package/dist/controllers/effects.js.map +1 -1
- package/dist/controllers/index.d.ts +1 -1
- package/dist/controllers/index.js +1 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/manager.d.ts +1 -0
- package/dist/controllers/manager.js +41 -17
- package/dist/controllers/manager.js.map +1 -1
- package/dist/controllers/sqlite.d.ts +107 -85
- package/dist/controllers/sqlite.js +1611 -1486
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/controllers/store.d.ts +9 -8
- package/dist/controllers/store.js +1 -31
- package/dist/controllers/store.js.map +1 -1
- package/dist/controllers/types.d.ts +3 -0
- package/dist/controllers/workflow-engine-scheduler.d.ts +1 -1
- package/dist/controllers/workflow-engine-scheduler.js +3 -5
- package/dist/controllers/workflow-engine-scheduler.js.map +1 -1
- package/dist/controllers/workflows.d.ts +2 -2
- package/dist/controllers/workflows.js +11 -10
- package/dist/controllers/workflows.js.map +1 -1
- package/dist/extension/controller-host.js +2 -2
- package/dist/extension/controller-host.js.map +1 -1
- package/dist/extension/decision-channels.d.ts +4 -2
- package/dist/extension/decision-channels.js +333 -146
- package/dist/extension/decision-channels.js.map +1 -1
- package/dist/extension/executor.d.ts +9 -2
- package/dist/extension/executor.js +89 -2
- package/dist/extension/executor.js.map +1 -1
- package/dist/extension/herdr-viewer.d.ts +0 -1
- package/dist/extension/herdr-viewer.js +0 -4
- package/dist/extension/herdr-viewer.js.map +1 -1
- package/dist/extension/index.js +209 -127
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +3 -10
- package/dist/extension/recorder.js +13 -22
- package/dist/extension/recorder.js.map +1 -1
- package/dist/extension/session-events.d.ts +2 -2
- package/dist/extension/step-message.d.ts +7 -2
- package/dist/extension/step-message.js +94 -2
- package/dist/extension/step-message.js.map +1 -1
- package/dist/extension/widget.js +29 -3
- package/dist/extension/widget.js.map +1 -1
- package/dist/host/rpc-executor.d.ts +3 -0
- package/dist/host/rpc-executor.js +2 -0
- package/dist/host/rpc-executor.js.map +1 -1
- package/dist/host/runner.d.ts +3 -6
- package/dist/host/runner.js +83 -32
- package/dist/host/runner.js.map +1 -1
- package/dist/render/graph-render.js +15 -10
- package/dist/render/graph-render.js.map +1 -1
- package/dist/state/database.d.ts +35 -0
- package/dist/state/database.js +287 -0
- package/dist/state/database.js.map +1 -0
- package/dist/state/index.d.ts +4 -0
- package/dist/state/index.js +5 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/json.d.ts +6 -0
- package/dist/state/json.js +38 -0
- package/dist/state/json.js.map +1 -0
- package/dist/state/mutation.d.ts +57 -0
- package/dist/state/mutation.js +255 -0
- package/dist/state/mutation.js.map +1 -0
- package/dist/state/schema.d.ts +6 -0
- package/dist/state/schema.js +473 -0
- package/dist/state/schema.js.map +1 -0
- package/dist/viewer/cli.d.ts +2 -2
- package/dist/viewer/cli.js +142 -122
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/render.d.ts +4 -4
- package/dist/viewer/render.js +9 -23
- package/dist/viewer/render.js.map +1 -1
- package/dist/viewer/session-reducer.d.ts +1 -1
- package/dist/viewer/session-reducer.js +1 -1
- package/dist/viewer/tui.d.ts +3 -3
- package/dist/viewer/tui.js +15 -13
- package/dist/viewer/tui.js.map +1 -1
- package/dist/viewer/watch.d.ts +2 -5
- package/dist/viewer/watch.js +13 -13
- package/dist/viewer/watch.js.map +1 -1
- package/dist/workflows/catalog.js +7 -2
- package/dist/workflows/catalog.js.map +1 -1
- package/dist/workflows/composition.js +20 -2
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/decision-presentation.d.ts +1 -1
- package/dist/workflows/decision-presentation.js +51 -38
- package/dist/workflows/decision-presentation.js.map +1 -1
- package/dist/workflows/definition.d.ts +6 -2
- package/dist/workflows/definition.js +17 -0
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.d.ts +7 -7
- package/dist/workflows/engine.js +196 -115
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/errors.d.ts +16 -3
- package/dist/workflows/errors.js +18 -3
- package/dist/workflows/errors.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +45 -8
- package/dist/workflows/human-decision.js +641 -274
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +5 -6
- package/dist/workflows/index.js +4 -5
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/progress.d.ts +1 -0
- package/dist/workflows/progress.js +15 -3
- package/dist/workflows/progress.js.map +1 -1
- package/dist/workflows/schema.js +27 -1
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/store.d.ts +83 -103
- package/dist/workflows/store.js +907 -951
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +88 -126
- package/docs/CONTROLLERS.md +10 -8
- package/docs/DEFERRED_TURNS.md +2 -2
- package/docs/DESIGN_PHILOSOPHY.md +4 -2
- package/docs/HUMAN_DECISIONS.md +36 -47
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +14 -24
- package/docs/MONITOR.md +179 -237
- package/docs/SQLITE_STATE.md +219 -0
- package/docs/WORKFLOW_COMPOSITION.md +27 -26
- package/docs/WORKFLOW_STEP_MESSAGES.md +20 -18
- package/docs/WORKFLOW_UPDATES.md +10 -9
- package/docs/development.md +9 -9
- package/docs/live-replay-protocol.md +28 -37
- package/docs/plans/2026-08-16-workflow-updates-plan.md +1 -1
- package/docs/plans/2026-08-19-human-decision-gates-plan.md +1 -1
- package/docs/plans/2026-08-21-autoimplement-timeout-fallback-plan.md +1 -1
- package/docs/plans/2026-08-21-plan-change-approval-policy-plan.md +322 -0
- package/docs/plans/2026-08-21-sanity-check-plan.md +217 -95
- package/docs/plans/2026-08-22-goal-finishing-monitor-plan.md +200 -0
- package/docs/plans/2026-08-23-assistant-agent-completion-plan.md +701 -0
- package/docs/plans/2026-08-23-sqlite-state-plan.md +970 -0
- package/docs/plans/replayable-run-bundles-implementation-plan.md +2 -2
- package/docs/plans/session-event-replay-implementation-plan.md +1 -1
- package/docs/plans/tui-viewer-implementation-plan.md +1 -1
- package/docs/session-event-journal.md +63 -434
- package/docs/tui-viewer.md +13 -15
- package/docs/workflows.md +127 -53
- package/examples/workflows/approved-plan.workflow.ts +19 -46
- package/examples/workflows/plain-summary.workflow.ts +1 -0
- package/herdr-plugin.toml +1 -1
- package/package.json +7 -7
- package/plugins/herdr/viewer.mjs +1 -11
- package/schemas/human-decision-accepted-v1.schema.json +15 -3
- package/schemas/human-decision-continuation-v1.schema.json +10 -1
- package/schemas/human-decision-delivery-v1.schema.json +8 -0
- package/schemas/human-decision-receipt-v1.schema.json +8 -0
- package/schemas/human-decision-request-v1.schema.json +24 -4
- package/skills/autoimplement/SKILL.md +27 -0
- package/skills/autoplan/SKILL.md +5 -4
- package/skills/monitor/SKILL.md +91 -135
- package/skills/pi-workflows/SKILL.md +9 -8
- package/skills/sanity-check/SKILL.md +44 -0
- package/src/builtins/autoimplement.workflow.ts +19 -118
- package/src/builtins/autoplan.workflow.ts +290 -40
- package/src/builtins/catalog.ts +7 -5
- package/src/builtins/index.ts +18 -1
- package/src/builtins/monitor.workflow.ts +673 -382
- package/src/builtins/pi-agent-group.ts +1407 -0
- package/src/builtins/plain-summary.workflow.ts +185 -0
- package/src/builtins/plan-approval.workflow.ts +157 -24
- package/src/builtins/plan-change.workflow.ts +321 -0
- package/src/builtins/plan-presentation.ts +2 -2
- package/src/builtins/sanity-check.workflow.ts +248 -48
- package/src/controllers/effects.ts +7 -0
- package/src/controllers/index.ts +0 -6
- package/src/controllers/manager.ts +72 -32
- package/src/controllers/sqlite.ts +2420 -1948
- package/src/controllers/store.ts +17 -38
- package/src/controllers/types.ts +3 -0
- package/src/controllers/workflow-engine-scheduler.ts +4 -8
- package/src/controllers/workflows.ts +45 -21
- package/src/extension/controller-host.ts +1 -2
- package/src/extension/decision-channels.ts +456 -203
- package/src/extension/executor.ts +105 -2
- package/src/extension/herdr-viewer.ts +0 -5
- package/src/extension/index.ts +251 -151
- package/src/extension/recorder.ts +13 -22
- package/src/extension/session-events.ts +2 -2
- package/src/extension/step-message.ts +106 -4
- package/src/extension/widget.ts +32 -5
- package/src/host/rpc-executor.ts +4 -0
- package/src/host/runner.ts +93 -41
- package/src/render/graph-render.ts +12 -6
- package/src/state/database.ts +375 -0
- package/src/state/index.ts +29 -0
- package/src/state/json.ts +42 -0
- package/src/state/mutation.ts +435 -0
- package/src/state/schema.ts +475 -0
- package/src/viewer/cli.ts +151 -144
- package/src/viewer/render.ts +19 -30
- package/src/viewer/session-reducer.ts +1 -1
- package/src/viewer/tui.ts +18 -17
- package/src/viewer/watch.ts +13 -17
- package/src/workflows/catalog.ts +7 -2
- package/src/workflows/composition.ts +21 -3
- package/src/workflows/decision-presentation.ts +56 -43
- package/src/workflows/definition.ts +36 -3
- package/src/workflows/engine.ts +228 -130
- package/src/workflows/errors.ts +27 -3
- package/src/workflows/human-decision.ts +921 -349
- package/src/workflows/index.ts +20 -30
- package/src/workflows/progress.ts +18 -3
- package/src/workflows/schema.ts +36 -1
- package/src/workflows/store.ts +1300 -1163
- package/src/workflows/types.ts +100 -137
- package/dist/builtins/sanity-check-session.d.ts +0 -17
- package/dist/builtins/sanity-check-session.js +0 -168
- package/dist/builtins/sanity-check-session.js.map +0 -1
- package/dist/workflows/artifacts.d.ts +0 -40
- package/dist/workflows/artifacts.js +0 -155
- package/dist/workflows/artifacts.js.map +0 -1
- package/dist/workflows/migrate-sources.d.ts +0 -42
- package/dist/workflows/migrate-sources.js +0 -133
- package/dist/workflows/migrate-sources.js.map +0 -1
- package/docs/run-bundles.md +0 -481
- package/schemas/human-decision-accepted-v2.schema.json +0 -50
- package/schemas/human-decision-delivery-v2.schema.json +0 -36
- package/schemas/human-decision-receipt-v2.schema.json +0 -39
- package/schemas/human-decision-request-v2.schema.json +0 -69
- package/schemas/human-decision-resolution-v2.schema.json +0 -27
- package/src/builtins/sanity-check-session.ts +0 -205
- package/src/workflows/artifacts.ts +0 -188
- package/src/workflows/migrate-sources.ts +0 -178
package/src/viewer/tui.ts
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { LoadedRunBundle } from "../workflows/store.js";
|
|
1
|
+
import { WorkflowRunStore, type LoadedWorkflowRun } from "../workflows/store.js";
|
|
3
2
|
import {
|
|
4
3
|
maxDetailScroll,
|
|
5
4
|
renderRunDetailLines,
|
|
6
5
|
renderRunListLines,
|
|
7
6
|
type ViewportSize,
|
|
8
7
|
} from "./render.js";
|
|
9
|
-
import {
|
|
8
|
+
import { watchStateDatabase } from "./watch.js";
|
|
10
9
|
|
|
11
10
|
const ALT_SCREEN_ON = "\u001b[?1049h\u001b[?25l";
|
|
12
11
|
const ALT_SCREEN_OFF = "\u001b[?25h\u001b[?1049l";
|
|
13
12
|
const CLEAR = "\u001b[2J\u001b[H";
|
|
14
13
|
|
|
15
|
-
type ViewerMode = { view: "list" } | { view: "detail";
|
|
14
|
+
type ViewerMode = { view: "list" } | { view: "detail"; runId: string };
|
|
16
15
|
|
|
17
16
|
export type ViewerOptions = {
|
|
18
|
-
|
|
17
|
+
databasePath: string;
|
|
19
18
|
runId?: string | undefined;
|
|
20
19
|
/** Redraw interval for elapsed timers while a run is active. */
|
|
21
20
|
tickMs?: number;
|
|
@@ -29,12 +28,13 @@ function viewportSize(): ViewportSize {
|
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
/**
|
|
32
|
-
* Interactive live viewer. Watches the
|
|
33
|
-
*
|
|
31
|
+
* Interactive live viewer. Watches the SQLite database and re-renders after
|
|
32
|
+
* committed run changes. Returns when the user quits.
|
|
34
33
|
*/
|
|
35
34
|
export async function runViewer(options: ViewerOptions): Promise<void> {
|
|
35
|
+
const store = new WorkflowRunStore(options.databasePath, { readOnly: true });
|
|
36
36
|
let mode: ViewerMode = { view: "list" };
|
|
37
|
-
let bundles:
|
|
37
|
+
let bundles: LoadedWorkflowRun[] = [];
|
|
38
38
|
let selectedIndex = 0;
|
|
39
39
|
let detailScroll = 0;
|
|
40
40
|
/** Replay position; null follows the latest step live. */
|
|
@@ -42,29 +42,29 @@ export async function runViewer(options: ViewerOptions): Promise<void> {
|
|
|
42
42
|
let detailStepCount = 0;
|
|
43
43
|
|
|
44
44
|
if (options.runId) {
|
|
45
|
-
bundles =
|
|
45
|
+
bundles = store.listRuns();
|
|
46
46
|
const match = bundles.find((bundle) => bundle.state.runId === options.runId);
|
|
47
47
|
if (!match) {
|
|
48
48
|
throw new Error(`Run not found: ${options.runId}`);
|
|
49
49
|
}
|
|
50
|
-
mode = { view: "detail",
|
|
50
|
+
mode = { view: "detail", runId: match.runId };
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
const draw = async () => {
|
|
54
|
-
bundles =
|
|
54
|
+
bundles = store.listRuns();
|
|
55
55
|
selectedIndex = Math.min(selectedIndex, Math.max(0, bundles.length - 1));
|
|
56
56
|
const size = viewportSize();
|
|
57
57
|
const lines =
|
|
58
58
|
mode.view === "list"
|
|
59
59
|
? renderRunListLines(bundles, selectedIndex, size)
|
|
60
|
-
: await renderDetail(mode.
|
|
60
|
+
: await renderDetail(mode.runId, size);
|
|
61
61
|
process.stdout.write(CLEAR + lines.join("\n"));
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
-
const renderDetail = async (
|
|
65
|
-
const bundle =
|
|
64
|
+
const renderDetail = async (runId: string, size: ViewportSize): Promise<string[]> => {
|
|
65
|
+
const bundle = store.readRun(runId, { includeTrace: true });
|
|
66
66
|
if (!bundle) {
|
|
67
|
-
return ["
|
|
67
|
+
return ["SQLite run state disappeared. Press q to go back."];
|
|
68
68
|
}
|
|
69
69
|
detailStepCount = bundle.state.steps.length;
|
|
70
70
|
if (selectedStep !== null && selectedStep >= detailStepCount - 1) {
|
|
@@ -76,7 +76,7 @@ export async function runViewer(options: ViewerOptions): Promise<void> {
|
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
process.stdout.write(ALT_SCREEN_ON);
|
|
79
|
-
const stopWatching =
|
|
79
|
+
const stopWatching = watchStateDatabase(options.databasePath, () => {
|
|
80
80
|
void draw();
|
|
81
81
|
});
|
|
82
82
|
const ticker = setInterval(() => {
|
|
@@ -136,7 +136,7 @@ export async function runViewer(options: ViewerOptions): Promise<void> {
|
|
|
136
136
|
} else if (key === "\r" || key === "\n") {
|
|
137
137
|
const selected = bundles[selectedIndex];
|
|
138
138
|
if (selected) {
|
|
139
|
-
mode = { view: "detail",
|
|
139
|
+
mode = { view: "detail", runId: selected.runId };
|
|
140
140
|
detailScroll = 0;
|
|
141
141
|
selectedStep = null;
|
|
142
142
|
void draw();
|
|
@@ -150,6 +150,7 @@ export async function runViewer(options: ViewerOptions): Promise<void> {
|
|
|
150
150
|
} finally {
|
|
151
151
|
clearInterval(ticker);
|
|
152
152
|
stopWatching();
|
|
153
|
+
store.close();
|
|
153
154
|
if (rawModeSupported) {
|
|
154
155
|
process.stdin.setRawMode(false);
|
|
155
156
|
}
|
package/src/viewer/watch.ts
CHANGED
|
@@ -1,28 +1,25 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
|
|
3
4
|
export type Unsubscribe = () => void;
|
|
4
5
|
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
9
|
-
export function watchRunsDir(
|
|
10
|
-
dir: string,
|
|
6
|
+
/** Watch the canonical database and its WAL with a polling fallback. */
|
|
7
|
+
export function watchStateDatabase(
|
|
8
|
+
databasePath: string,
|
|
11
9
|
onChange: () => void,
|
|
12
10
|
options: { pollMs?: number; debounceMs?: number } = {},
|
|
13
11
|
): Unsubscribe {
|
|
14
12
|
const pollMs = options.pollMs ?? 1_000;
|
|
15
13
|
const debounceMs = options.debounceMs ?? 80;
|
|
14
|
+
const directory = path.dirname(databasePath);
|
|
15
|
+
const base = path.basename(databasePath);
|
|
16
|
+
const relevant = new Set([base, `${base}-wal`, `${base}-shm`]);
|
|
16
17
|
let debounceTimer: NodeJS.Timeout | null = null;
|
|
17
18
|
let closed = false;
|
|
18
19
|
|
|
19
20
|
const fire = () => {
|
|
20
|
-
if (closed)
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
if (debounceTimer) {
|
|
24
|
-
clearTimeout(debounceTimer);
|
|
25
|
-
}
|
|
21
|
+
if (closed) return;
|
|
22
|
+
if (debounceTimer !== null) clearTimeout(debounceTimer);
|
|
26
23
|
debounceTimer = setTimeout(() => {
|
|
27
24
|
debounceTimer = null;
|
|
28
25
|
onChange();
|
|
@@ -31,7 +28,9 @@ export function watchRunsDir(
|
|
|
31
28
|
|
|
32
29
|
let watcher: fs.FSWatcher | null = null;
|
|
33
30
|
try {
|
|
34
|
-
watcher = fs.watch(
|
|
31
|
+
watcher = fs.watch(directory, (_event, filename) => {
|
|
32
|
+
if (filename === null || relevant.has(filename.toString())) fire();
|
|
33
|
+
});
|
|
35
34
|
watcher.on("error", () => {
|
|
36
35
|
watcher?.close();
|
|
37
36
|
watcher = null;
|
|
@@ -40,15 +39,12 @@ export function watchRunsDir(
|
|
|
40
39
|
watcher = null;
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
// Polling fallback covers platforms without recursive fs.watch and missed events.
|
|
44
42
|
const poller = setInterval(fire, pollMs);
|
|
45
43
|
poller.unref?.();
|
|
46
44
|
|
|
47
45
|
return () => {
|
|
48
46
|
closed = true;
|
|
49
|
-
if (debounceTimer)
|
|
50
|
-
clearTimeout(debounceTimer);
|
|
51
|
-
}
|
|
47
|
+
if (debounceTimer !== null) clearTimeout(debounceTimer);
|
|
52
48
|
clearInterval(poller);
|
|
53
49
|
watcher?.close();
|
|
54
50
|
};
|
package/src/workflows/catalog.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isWorkflowDefinition } from "./definition.js";
|
|
2
|
-
import {
|
|
2
|
+
import { BuiltinWorkflowRevisionChangedError } from "./errors.js";
|
|
3
3
|
import type { WorkflowDefinition, WorkflowSource } from "./types.js";
|
|
4
4
|
|
|
5
5
|
const BUILTIN_ID_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
|
|
@@ -102,7 +102,12 @@ export class BuiltinWorkflowCatalog {
|
|
|
102
102
|
throw new Error(`Unknown built-in workflow: ${source.id}`);
|
|
103
103
|
}
|
|
104
104
|
if (entry.revision !== source.revision) {
|
|
105
|
-
throw new
|
|
105
|
+
throw new BuiltinWorkflowRevisionChangedError({
|
|
106
|
+
runId,
|
|
107
|
+
workflowId: entry.id,
|
|
108
|
+
previousRevision: source.revision,
|
|
109
|
+
currentRevision: entry.revision,
|
|
110
|
+
});
|
|
106
111
|
}
|
|
107
112
|
return entry.definition;
|
|
108
113
|
}
|
|
@@ -3,7 +3,6 @@ import { readFileSync } from "node:fs";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import type {
|
|
5
5
|
ActionNodeDefinition,
|
|
6
|
-
AgentNodeDefinition,
|
|
7
6
|
CheckpointNodeDefinition,
|
|
8
7
|
ComputeNodeDefinition,
|
|
9
8
|
FunctionActionNodeDefinition,
|
|
@@ -451,11 +450,20 @@ function wrapNode(
|
|
|
451
450
|
...(node.statusDetail !== undefined ? { statusDetail: node.statusDetail } : {}),
|
|
452
451
|
};
|
|
453
452
|
switch (node.nodeType) {
|
|
454
|
-
case "agent":
|
|
453
|
+
case "agent": {
|
|
454
|
+
const prompt = (context: WorkflowNodeContext) => node.prompt(project(context));
|
|
455
|
+
if (typeof node.expectedOutput === "object") {
|
|
456
|
+
return {
|
|
457
|
+
...common,
|
|
458
|
+
nodeType: "agent",
|
|
459
|
+
prompt,
|
|
460
|
+
expectedOutput: node.expectedOutput,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
455
463
|
return {
|
|
456
464
|
...common,
|
|
457
465
|
nodeType: "agent",
|
|
458
|
-
prompt
|
|
466
|
+
prompt,
|
|
459
467
|
...(node.expectedOutput !== undefined ? { expectedOutput: node.expectedOutput } : {}),
|
|
460
468
|
...(node.validate !== undefined
|
|
461
469
|
? {
|
|
@@ -464,6 +472,7 @@ function wrapNode(
|
|
|
464
472
|
}
|
|
465
473
|
: {}),
|
|
466
474
|
};
|
|
475
|
+
}
|
|
467
476
|
case "compute":
|
|
468
477
|
return {
|
|
469
478
|
...common,
|
|
@@ -480,6 +489,7 @@ function wrapNode(
|
|
|
480
489
|
case "checkpoint": {
|
|
481
490
|
const human = node.humanDecision;
|
|
482
491
|
const audience = human?.audience;
|
|
492
|
+
const onTimeout = human?.onTimeout;
|
|
483
493
|
return {
|
|
484
494
|
...common,
|
|
485
495
|
nodeType: "checkpoint",
|
|
@@ -496,6 +506,14 @@ function wrapNode(
|
|
|
496
506
|
: audience!,
|
|
497
507
|
choices: human.choices,
|
|
498
508
|
request: (context: WorkflowNodeContext) => human.request(project(context)),
|
|
509
|
+
...(onTimeout !== undefined
|
|
510
|
+
? {
|
|
511
|
+
onTimeout:
|
|
512
|
+
typeof onTimeout === "function"
|
|
513
|
+
? (context: WorkflowNodeContext) => onTimeout(project(context))
|
|
514
|
+
: onTimeout,
|
|
515
|
+
}
|
|
516
|
+
: {}),
|
|
499
517
|
},
|
|
500
518
|
}
|
|
501
519
|
: {}),
|
|
@@ -56,7 +56,18 @@ export function decisionPresentationDigest(presentation: DecisionPresentation):
|
|
|
56
56
|
export function validateHumanDecisionRequestIntegrity(
|
|
57
57
|
request: HumanDecisionRequest,
|
|
58
58
|
): HumanDecisionRequest {
|
|
59
|
-
if (
|
|
59
|
+
if (
|
|
60
|
+
request.schema !== "pi-workflows.human-decision-request.v1" ||
|
|
61
|
+
!Object.hasOwn(request, "subject") ||
|
|
62
|
+
!Object.hasOwn(request, "presentation") ||
|
|
63
|
+
!Object.hasOwn(request, "subjectDigest") ||
|
|
64
|
+
!Object.hasOwn(request, "presentationDigest") ||
|
|
65
|
+
!Object.hasOwn(request, "revision")
|
|
66
|
+
) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
"Human decision state uses an incompatible alpha contract; reset the affected workflow run and decision state.",
|
|
69
|
+
);
|
|
70
|
+
}
|
|
60
71
|
const presentation = normalizeDecisionPresentation(request.presentation);
|
|
61
72
|
const subjectDigest = digestCanonical(request.subject);
|
|
62
73
|
const presentationDigest = digestCanonical(presentation);
|
|
@@ -70,6 +81,7 @@ export function validateHumanDecisionRequestIntegrity(
|
|
|
70
81
|
title: request.title,
|
|
71
82
|
choices: request.choices,
|
|
72
83
|
...(request.expiresAt !== undefined ? { expiresAt: request.expiresAt } : {}),
|
|
84
|
+
...(request.defaultResponse !== undefined ? { defaultResponse: request.defaultResponse } : {}),
|
|
73
85
|
subject: request.subject,
|
|
74
86
|
presentation,
|
|
75
87
|
revision: request.revision,
|
|
@@ -90,10 +102,7 @@ export function humanDecisionChannelRequest(
|
|
|
90
102
|
request: HumanDecisionRequest,
|
|
91
103
|
): HumanDecisionChannelRequest {
|
|
92
104
|
validateHumanDecisionRequestIntegrity(request);
|
|
93
|
-
const presentation =
|
|
94
|
-
request.schema === "pi-workflows.human-decision-request.v2"
|
|
95
|
-
? normalizeDecisionPresentation(request.presentation)
|
|
96
|
-
: legacyDecisionPresentation(request.body);
|
|
105
|
+
const presentation = normalizeDecisionPresentation(request.presentation);
|
|
97
106
|
const presentationDigest = decisionPresentationDigest(presentation);
|
|
98
107
|
return {
|
|
99
108
|
schema: "pi-workflows.human-decision-channel-request.v1",
|
|
@@ -108,25 +117,26 @@ export function humanDecisionChannelRequest(
|
|
|
108
117
|
title: request.title,
|
|
109
118
|
presentation,
|
|
110
119
|
presentationDigest,
|
|
111
|
-
revision: request.
|
|
120
|
+
revision: request.revision,
|
|
112
121
|
choices: request.choices,
|
|
113
122
|
createdAt: request.createdAt,
|
|
114
123
|
...(request.expiresAt !== undefined ? { expiresAt: request.expiresAt } : {}),
|
|
124
|
+
...(request.defaultResponse !== undefined ? { defaultResponse: request.defaultResponse } : {}),
|
|
115
125
|
};
|
|
116
126
|
}
|
|
117
127
|
|
|
118
|
-
export function
|
|
128
|
+
export function valueDecisionPresentation(value: unknown): DecisionPresentation {
|
|
119
129
|
const blocks: DecisionPresentationBlock[] = [];
|
|
120
|
-
if (typeof
|
|
121
|
-
|
|
130
|
+
if (typeof value === "string") {
|
|
131
|
+
appendValueText(blocks, value, "Decision details");
|
|
122
132
|
} else {
|
|
123
|
-
|
|
133
|
+
appendValue(blocks, value, undefined);
|
|
124
134
|
}
|
|
125
135
|
const summary =
|
|
126
|
-
typeof
|
|
136
|
+
typeof value === "string"
|
|
127
137
|
? "Review the decision message below."
|
|
128
138
|
: "Review the decision details below.";
|
|
129
|
-
return normalizeDecisionPresentation(
|
|
139
|
+
return normalizeDecisionPresentation(boundValuePresentation(summary, blocks, value));
|
|
130
140
|
}
|
|
131
141
|
|
|
132
142
|
export function decisionDocumentSegments(
|
|
@@ -150,6 +160,13 @@ export function decisionDocumentSegments(
|
|
|
150
160
|
segments.push({ kind: block.kind, text: block.text });
|
|
151
161
|
}
|
|
152
162
|
}
|
|
163
|
+
if (request.expiresAt !== undefined && request.defaultResponse !== undefined) {
|
|
164
|
+
const selected = request.choices[request.defaultResponse.choice];
|
|
165
|
+
segments.push({
|
|
166
|
+
kind: "paragraph",
|
|
167
|
+
text: `If no answer is accepted by ${request.expiresAt}, this decision continues automatically with: ${selected?.label ?? request.defaultResponse.choice}.`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
153
170
|
const choices = Object.values(request.choices).map((choice) =>
|
|
154
171
|
choice.input === undefined
|
|
155
172
|
? `• ${choice.label}`
|
|
@@ -251,10 +268,10 @@ function normalizeString(value: unknown, label: string): string {
|
|
|
251
268
|
return text;
|
|
252
269
|
}
|
|
253
270
|
|
|
254
|
-
function
|
|
271
|
+
function boundValuePresentation(
|
|
255
272
|
summary: string,
|
|
256
273
|
blocks: DecisionPresentationBlock[],
|
|
257
|
-
|
|
274
|
+
value: unknown,
|
|
258
275
|
): DecisionPresentation {
|
|
259
276
|
const candidate: DecisionPresentation = {
|
|
260
277
|
schema: "pi-workflows.decision-presentation.v1",
|
|
@@ -263,13 +280,13 @@ function boundLegacyPresentation(
|
|
|
263
280
|
};
|
|
264
281
|
if (
|
|
265
282
|
blocks.length <= MAX_PRESENTATION_BLOCKS &&
|
|
266
|
-
blocks.every(
|
|
283
|
+
blocks.every(valueBlockFitsSchema) &&
|
|
267
284
|
presentationCodeUnits(candidate) <= MAX_PRESENTATION_CODE_UNITS
|
|
268
285
|
) {
|
|
269
286
|
return candidate;
|
|
270
287
|
}
|
|
271
|
-
const originalCodeUnits = canonicalJson(
|
|
272
|
-
const omission = `Some
|
|
288
|
+
const originalCodeUnits = canonicalJson(value).length;
|
|
289
|
+
const omission = `Some decision content is not shown because it exceeds the readable presentation limit. Inspect the canonical source value before answering. Value digest: ${digestCanonical(value)}. Original value: ${originalCodeUnits} UTF-16 code units. Generated blocks: ${blocks.length}.`;
|
|
273
290
|
const budget = MAX_PRESENTATION_CODE_UNITS - summary.length - omission.length;
|
|
274
291
|
const kept: DecisionPresentationBlock[] = [];
|
|
275
292
|
let used = 0;
|
|
@@ -278,7 +295,7 @@ function boundLegacyPresentation(
|
|
|
278
295
|
const units = presentationBlockCodeUnits(block);
|
|
279
296
|
if (
|
|
280
297
|
kept.length >= MAX_PRESENTATION_BLOCKS ||
|
|
281
|
-
!
|
|
298
|
+
!valueBlockFitsSchema(block) ||
|
|
282
299
|
used + units > budget
|
|
283
300
|
) {
|
|
284
301
|
omitted = true;
|
|
@@ -298,7 +315,7 @@ function boundLegacyPresentation(
|
|
|
298
315
|
};
|
|
299
316
|
}
|
|
300
317
|
|
|
301
|
-
function
|
|
318
|
+
function valueBlockFitsSchema(block: DecisionPresentationBlock): boolean {
|
|
302
319
|
if (block.kind === "paragraph" || block.kind === "preformatted") {
|
|
303
320
|
return block.text.length <= MAX_PRESENTATION_STRING_CODE_UNITS;
|
|
304
321
|
}
|
|
@@ -332,47 +349,43 @@ function presentationCodeUnits(presentation: DecisionPresentation): number {
|
|
|
332
349
|
return total;
|
|
333
350
|
}
|
|
334
351
|
|
|
335
|
-
function
|
|
352
|
+
function appendValue(
|
|
336
353
|
blocks: DecisionPresentationBlock[],
|
|
337
354
|
value: unknown,
|
|
338
355
|
label: string | undefined,
|
|
339
356
|
): void {
|
|
340
357
|
if (isScalar(value)) {
|
|
341
|
-
const scalar =
|
|
358
|
+
const scalar = valueScalar(value);
|
|
342
359
|
if (label === undefined || scalar.length > MAX_PRESENTATION_STRING_CODE_UNITS) {
|
|
343
|
-
|
|
344
|
-
blocks,
|
|
345
|
-
scalar,
|
|
346
|
-
label === undefined ? "Decision details" : legacyLabel(label),
|
|
347
|
-
);
|
|
360
|
+
appendValueText(blocks, scalar, label === undefined ? "Decision details" : valueLabel(label));
|
|
348
361
|
} else {
|
|
349
|
-
blocks.push({ kind: "fields", items: [{ label:
|
|
362
|
+
blocks.push({ kind: "fields", items: [{ label: valueLabel(label), value: scalar }] });
|
|
350
363
|
}
|
|
351
364
|
return;
|
|
352
365
|
}
|
|
353
366
|
if (Array.isArray(value)) {
|
|
354
|
-
if (label !== undefined) blocks.push({ kind: "section", title:
|
|
367
|
+
if (label !== undefined) blocks.push({ kind: "section", title: valueLabel(label) });
|
|
355
368
|
if (value.length === 0) {
|
|
356
369
|
blocks.push({ kind: "paragraph", text: "None" });
|
|
357
370
|
return;
|
|
358
371
|
}
|
|
359
372
|
if (
|
|
360
373
|
value.every(isScalar) &&
|
|
361
|
-
value.every((item) =>
|
|
374
|
+
value.every((item) => valueScalar(item).length <= MAX_PRESENTATION_STRING_CODE_UNITS)
|
|
362
375
|
) {
|
|
363
|
-
const items = value.map(
|
|
376
|
+
const items = value.map(valueScalar);
|
|
364
377
|
for (let index = 0; index < items.length; index += MAX_PRESENTATION_ITEMS) {
|
|
365
378
|
blocks.push({ kind: "bullets", items: items.slice(index, index + MAX_PRESENTATION_ITEMS) });
|
|
366
379
|
}
|
|
367
380
|
return;
|
|
368
381
|
}
|
|
369
|
-
value.forEach((item, index) =>
|
|
382
|
+
value.forEach((item, index) => appendValue(blocks, item, `Item ${index + 1}`));
|
|
370
383
|
return;
|
|
371
384
|
}
|
|
372
385
|
const entries = Object.entries(value as Record<string, unknown>).sort(([left], [right]) =>
|
|
373
386
|
compareKeys(left, right),
|
|
374
387
|
);
|
|
375
|
-
if (label !== undefined) blocks.push({ kind: "section", title:
|
|
388
|
+
if (label !== undefined) blocks.push({ kind: "section", title: valueLabel(label) });
|
|
376
389
|
if (entries.length === 0) {
|
|
377
390
|
blocks.push({ kind: "paragraph", text: "None" });
|
|
378
391
|
return;
|
|
@@ -380,34 +393,34 @@ function appendLegacyValue(
|
|
|
380
393
|
const fields = entries
|
|
381
394
|
.filter(
|
|
382
395
|
([, child]) =>
|
|
383
|
-
isScalar(child) &&
|
|
396
|
+
isScalar(child) && valueScalar(child).length <= MAX_PRESENTATION_STRING_CODE_UNITS,
|
|
384
397
|
)
|
|
385
|
-
.map(([key, child]) => ({ label:
|
|
398
|
+
.map(([key, child]) => ({ label: valueLabel(key), value: valueScalar(child) }));
|
|
386
399
|
for (let index = 0; index < fields.length; index += MAX_PRESENTATION_ITEMS) {
|
|
387
400
|
blocks.push({ kind: "fields", items: fields.slice(index, index + MAX_PRESENTATION_ITEMS) });
|
|
388
401
|
}
|
|
389
402
|
for (const [key, child] of entries.filter(
|
|
390
403
|
([, child]) =>
|
|
391
|
-
!isScalar(child) ||
|
|
404
|
+
!isScalar(child) || valueScalar(child).length > MAX_PRESENTATION_STRING_CODE_UNITS,
|
|
392
405
|
)) {
|
|
393
|
-
|
|
406
|
+
appendValue(blocks, child, key);
|
|
394
407
|
}
|
|
395
408
|
}
|
|
396
409
|
|
|
397
|
-
function
|
|
410
|
+
function appendValueText(
|
|
398
411
|
blocks: DecisionPresentationBlock[],
|
|
399
412
|
value: string,
|
|
400
413
|
section: string,
|
|
401
414
|
): void {
|
|
402
|
-
const text =
|
|
415
|
+
const text = sanitizeValueText(value);
|
|
403
416
|
blocks.push({ kind: "section", title: section });
|
|
404
417
|
for (const part of splitByCodeUnits(text, MAX_PRESENTATION_STRING_CODE_UNITS)) {
|
|
405
418
|
blocks.push({ kind: "paragraph", text: part.trim().length === 0 ? "None" : part });
|
|
406
419
|
}
|
|
407
420
|
}
|
|
408
421
|
|
|
409
|
-
function
|
|
410
|
-
const sanitized =
|
|
422
|
+
function valueLabel(value: string): string {
|
|
423
|
+
const sanitized = sanitizeValueText(value)
|
|
411
424
|
.replaceAll(/[_-]+/gu, " ")
|
|
412
425
|
.replaceAll(/([a-z0-9])([A-Z])/gu, "$1 $2")
|
|
413
426
|
.replaceAll(/\s+/gu, " ")
|
|
@@ -416,13 +429,13 @@ function legacyLabel(value: string): string {
|
|
|
416
429
|
return `${sanitized[0]!.toUpperCase()}${sanitized.slice(1)}`;
|
|
417
430
|
}
|
|
418
431
|
|
|
419
|
-
function
|
|
432
|
+
function valueScalar(value: unknown): string {
|
|
420
433
|
if (value === null) return "None";
|
|
421
434
|
if (typeof value === "boolean") return value ? "Yes" : "No";
|
|
422
|
-
return
|
|
435
|
+
return sanitizeValueText(String(value));
|
|
423
436
|
}
|
|
424
437
|
|
|
425
|
-
function
|
|
438
|
+
function sanitizeValueText(value: string): string {
|
|
426
439
|
return value
|
|
427
440
|
.replaceAll("\r\n", "\n")
|
|
428
441
|
.replaceAll("\r", "\n")
|
|
@@ -9,6 +9,9 @@ import {
|
|
|
9
9
|
} from "./schema.js";
|
|
10
10
|
import type {
|
|
11
11
|
AgentNodeDefinition,
|
|
12
|
+
AssistantAgentNodeDefinition,
|
|
13
|
+
AssistantMessageOutput,
|
|
14
|
+
SubmittedAgentNodeDefinition,
|
|
12
15
|
ActionNodeDefinition,
|
|
13
16
|
CheckpointNodeDefinition,
|
|
14
17
|
ComputeNodeDefinition,
|
|
@@ -154,11 +157,41 @@ export function isWorkflowDefinition(value: unknown): value is WorkflowDefinitio
|
|
|
154
157
|
);
|
|
155
158
|
}
|
|
156
159
|
|
|
160
|
+
export function assistantMessage(options: { maxChars?: number } = {}): AssistantMessageOutput {
|
|
161
|
+
if (options === null || typeof options !== "object" || Array.isArray(options)) {
|
|
162
|
+
throw new Error("Invalid workflow definition: assistantMessage options must be an object");
|
|
163
|
+
}
|
|
164
|
+
const unknown = Object.keys(options).filter((key) => key !== "maxChars");
|
|
165
|
+
if (unknown.length > 0) {
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Invalid workflow definition: assistantMessage has unknown option ${JSON.stringify(unknown[0])}`,
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (
|
|
171
|
+
options.maxChars !== undefined &&
|
|
172
|
+
(!Number.isInteger(options.maxChars) || options.maxChars <= 0)
|
|
173
|
+
) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
"Invalid workflow definition: assistantMessage maxChars must be a positive integer",
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
return Object.freeze({
|
|
179
|
+
kind: "assistant-message" as const,
|
|
180
|
+
...(options.maxChars !== undefined ? { maxChars: options.maxChars } : {}),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function agent(
|
|
185
|
+
definition: Omit<SubmittedAgentNodeDefinition, "nodeType">,
|
|
186
|
+
): SubmittedAgentNodeDefinition;
|
|
187
|
+
export function agent(
|
|
188
|
+
definition: Omit<AssistantAgentNodeDefinition, "nodeType">,
|
|
189
|
+
): AssistantAgentNodeDefinition;
|
|
157
190
|
export function agent(definition: Omit<AgentNodeDefinition, "nodeType">): AgentNodeDefinition {
|
|
158
|
-
const node
|
|
159
|
-
nodeType: "agent",
|
|
191
|
+
const node = {
|
|
192
|
+
nodeType: "agent" as const,
|
|
160
193
|
...definition,
|
|
161
|
-
};
|
|
194
|
+
} as AgentNodeDefinition;
|
|
162
195
|
assertValidAgentNode(node);
|
|
163
196
|
return node;
|
|
164
197
|
}
|