@osolmaz/pi-workflows 0.15.3 → 0.16.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/README.md +24 -8
- package/dist/channels/adapter-entry.d.ts +3 -0
- package/dist/channels/adapter-entry.js +203 -0
- package/dist/channels/adapter-entry.js.map +1 -0
- package/dist/channels/config.d.ts +43 -0
- package/dist/channels/config.js +234 -0
- package/dist/channels/config.js.map +1 -0
- package/dist/channels/protocol.d.ts +94 -0
- package/dist/channels/protocol.js +219 -0
- package/dist/channels/protocol.js.map +1 -0
- package/dist/channels/telegram.d.ts +46 -0
- package/dist/channels/telegram.js +285 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/client/activity.d.ts +2 -0
- package/dist/client/activity.js +6 -0
- package/dist/client/activity.js.map +1 -0
- package/dist/client/client.d.ts +102 -0
- package/dist/client/client.js +737 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.js +3 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/materialize.d.ts +7 -0
- package/dist/client/materialize.js +177 -0
- package/dist/client/materialize.js.map +1 -0
- package/dist/client/protocol.d.ts +60 -0
- package/dist/client/protocol.js +275 -0
- package/dist/client/protocol.js.map +1 -0
- package/dist/client/resolver.d.ts +23 -0
- package/dist/client/resolver.js +2 -0
- package/dist/client/resolver.js.map +1 -0
- package/dist/client/view.d.ts +144 -0
- package/dist/client/view.js +3 -0
- package/dist/client/view.js.map +1 -0
- package/dist/controllers/index.d.ts +1 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/sqlite.d.ts +47 -157
- package/dist/controllers/sqlite.js +230 -406
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/index.d.ts +23 -1
- package/dist/extension/index.js +734 -555
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +95 -0
- package/dist/extension/recorder.js +530 -0
- package/dist/extension/recorder.js.map +1 -0
- package/dist/extension/remote-recorder-store.d.ts +25 -0
- package/dist/extension/remote-recorder-store.js +81 -0
- package/dist/extension/remote-recorder-store.js.map +1 -0
- package/dist/extension/session-events.d.ts +134 -0
- package/dist/extension/session-events.js +60 -0
- package/dist/extension/session-events.js.map +1 -0
- package/dist/extension/session-view.d.ts +9 -2
- package/dist/extension/session-view.js +117 -51
- package/dist/extension/session-view.js.map +1 -1
- package/dist/extension/step-message.d.ts +6 -4
- package/dist/extension/step-message.js +12 -6
- package/dist/extension/step-message.js.map +1 -1
- package/dist/extension/widget.d.ts +3 -2
- package/dist/extension/widget.js +41 -24
- package/dist/extension/widget.js.map +1 -1
- package/dist/extension/workflow-message-coordinator.d.ts +27 -0
- package/dist/extension/workflow-message-coordinator.js +294 -0
- package/dist/extension/workflow-message-coordinator.js.map +1 -0
- package/dist/host/channel-effects.d.ts +72 -0
- package/dist/host/channel-effects.js +271 -0
- package/dist/host/channel-effects.js.map +1 -0
- package/dist/host/channel-supervisor.d.ts +21 -0
- package/dist/host/channel-supervisor.js +54 -0
- package/dist/host/channel-supervisor.js.map +1 -0
- package/dist/host/child-worker-supervisor.js +1 -1
- package/dist/host/child-worker-supervisor.js.map +1 -1
- package/dist/host/resolver-entry.d.ts +2 -23
- package/dist/host/resolver-entry.js +1 -1
- package/dist/host/resolver-entry.js.map +1 -1
- package/dist/host/runner.d.ts +54 -8
- package/dist/host/runner.js +1997 -305
- package/dist/host/runner.js.map +1 -1
- package/dist/host/state.d.ts +25 -19
- package/dist/host/state.js +303 -89
- package/dist/host/state.js.map +1 -1
- package/dist/host/view.d.ts +83 -0
- package/dist/host/view.js +975 -0
- package/dist/host/view.js.map +1 -0
- package/dist/host/worker-protocol.js +1 -1
- package/dist/host/worker-protocol.js.map +1 -1
- package/dist/state/database.d.ts +1 -0
- package/dist/state/database.js +15 -0
- package/dist/state/database.js.map +1 -1
- package/dist/state/prune.d.ts +3 -1
- package/dist/state/prune.js +9 -33
- package/dist/state/prune.js.map +1 -1
- package/dist/state/schema.js +88 -98
- package/dist/state/schema.js.map +1 -1
- package/dist/state/workflow-messages.d.ts +96 -0
- package/dist/state/workflow-messages.js +335 -0
- package/dist/state/workflow-messages.js.map +1 -0
- package/dist/viewer/backup.d.ts +2 -0
- package/dist/viewer/backup.js +28 -0
- package/dist/viewer/backup.js.map +1 -0
- package/dist/viewer/cli.d.ts +4 -0
- package/dist/viewer/cli.js +150 -170
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/render.js +1 -1
- package/dist/viewer/render.js.map +1 -1
- package/dist/viewer/tui.d.ts +5 -7
- package/dist/viewer/tui.js +245 -108
- package/dist/viewer/tui.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +2 -0
- package/dist/workflows/human-decision.js +8 -2
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +1 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/settings.d.ts +1 -8
- package/dist/workflows/settings.js.map +1 -1
- package/dist/workflows/store.d.ts +67 -20
- package/dist/workflows/store.js +451 -378
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +21 -0
- package/dist/workflows/tool-input.js +23 -1
- package/dist/workflows/tool-input.js.map +1 -1
- package/dist/workflows/types.d.ts +5 -5
- package/dist/workflows/workflow-message-content.d.ts +38 -0
- package/dist/workflows/workflow-message-content.js +157 -0
- package/dist/workflows/workflow-message-content.js.map +1 -0
- package/docs/2026-08-18-herdr-piw-plan.md +2 -1
- package/docs/2026-08-20-durable-workflow-launch-plan.md +1 -1
- package/docs/2026-08-25-workflow-follow-ups.md +26 -50
- package/docs/2026-08-30-out-of-process-workflow-host-plan.md +3 -3
- package/docs/2026-09-01-restore-session-delivery-controls-plan.md +4 -0
- package/docs/2026-09-01-unified-workflow-client-plan.md +385 -0
- package/docs/2026-09-02-installed-live-e2e-plan.md +239 -0
- package/docs/2026-09-02-unify-workflow-messages-plan.md +562 -0
- package/docs/DEFERRED_TURNS.md +66 -294
- package/docs/HUMAN_DECISIONS.md +29 -30
- package/docs/HUMAN_DECISION_PRESENTATIONS.md +4 -11
- package/docs/SQLITE_STATE.md +31 -25
- package/docs/WORKFLOW_HOST.md +155 -77
- package/docs/WORKFLOW_STEP_MESSAGES.md +166 -75
- package/docs/development.md +2 -1
- package/docs/live-replay-protocol.md +74 -132
- package/docs/tui-viewer.md +12 -14
- package/docs/workflows.md +78 -28
- package/herdr-plugin.toml +1 -1
- package/package.json +9 -3
- package/plugins/herdr/viewer.mjs +14 -5
- package/protocol/client.v1.schema.json +137 -0
- package/protocol/fixtures/client-v1.json +23 -0
- package/src/channels/adapter-entry.ts +220 -0
- package/src/channels/config.ts +296 -0
- package/src/channels/protocol.ts +333 -0
- package/src/channels/telegram.ts +335 -0
- package/src/client/activity.ts +6 -0
- package/src/client/client.ts +939 -0
- package/src/client/index.ts +25 -0
- package/src/client/materialize.ts +228 -0
- package/src/client/protocol.ts +333 -0
- package/src/client/resolver.ts +26 -0
- package/src/client/view.ts +163 -0
- package/src/controllers/index.ts +0 -1
- package/src/controllers/sqlite.ts +339 -808
- package/src/extension/index.ts +853 -665
- package/src/extension/recorder.ts +687 -0
- package/src/extension/remote-recorder-store.ts +126 -0
- package/src/extension/session-events.ts +119 -0
- package/src/extension/session-view.ts +157 -51
- package/src/extension/step-message.ts +19 -9
- package/src/extension/widget.ts +53 -25
- package/src/extension/workflow-message-coordinator.ts +334 -0
- package/src/host/channel-effects.ts +465 -0
- package/src/host/channel-supervisor.ts +73 -0
- package/src/host/child-worker-supervisor.ts +1 -1
- package/src/host/resolver-entry.ts +11 -26
- package/src/host/runner.ts +2639 -450
- package/src/host/state.ts +404 -137
- package/src/host/view.ts +1212 -0
- package/src/host/worker-protocol.ts +1 -1
- package/src/state/database.ts +13 -0
- package/src/state/prune.ts +14 -42
- package/src/state/schema.ts +88 -98
- package/src/state/workflow-messages.ts +548 -0
- package/src/viewer/backup.ts +29 -0
- package/src/viewer/cli.ts +171 -185
- package/src/viewer/render.ts +1 -5
- package/src/viewer/tui.ts +261 -124
- package/src/workflows/human-decision.ts +18 -2
- package/src/workflows/index.ts +0 -1
- package/src/workflows/settings.ts +1 -20
- package/src/workflows/store.ts +650 -511
- package/src/workflows/tool-input.ts +36 -1
- package/src/workflows/types.ts +5 -5
- package/src/workflows/workflow-message-content.ts +197 -0
- package/dist/extension/decision-channels.d.ts +0 -134
- package/dist/extension/decision-channels.js +0 -1307
- package/dist/extension/decision-channels.js.map +0 -1
- package/dist/host/client.d.ts +0 -48
- package/dist/host/client.js +0 -216
- package/dist/host/client.js.map +0 -1
- package/dist/host/protocol.d.ts +0 -38
- package/dist/host/protocol.js +0 -156
- package/dist/host/protocol.js.map +0 -1
- package/dist/viewer/watch.d.ts +0 -6
- package/dist/viewer/watch.js +0 -46
- package/dist/viewer/watch.js.map +0 -1
- package/src/extension/decision-channels.ts +0 -1826
- package/src/host/client.ts +0 -293
- package/src/host/protocol.ts +0 -196
- package/src/viewer/watch.ts +0 -51
package/src/viewer/tui.ts
CHANGED
|
@@ -1,160 +1,297 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
type ViewportSize,
|
|
7
|
-
} from "./render.js";
|
|
8
|
-
import { watchStateDatabase } from "./watch.js";
|
|
1
|
+
import type { WorkflowClient } from "../client/client.js";
|
|
2
|
+
import { materializeRunView } from "../client/materialize.js";
|
|
3
|
+
import { sanitizeText } from "../render/ansi.js";
|
|
4
|
+
import { formatDuration } from "../render/format.js";
|
|
5
|
+
import type { JsonValue } from "../state/json.js";
|
|
9
6
|
|
|
10
7
|
const ALT_SCREEN_ON = "\u001b[?1049h\u001b[?25l";
|
|
11
8
|
const ALT_SCREEN_OFF = "\u001b[?25h\u001b[?1049l";
|
|
12
9
|
const CLEAR = "\u001b[2J\u001b[H";
|
|
13
10
|
|
|
14
|
-
type ViewerMode = { view: "list" } | { view: "detail"; runId: string };
|
|
15
|
-
|
|
16
11
|
export type ViewerOptions = {
|
|
17
|
-
|
|
12
|
+
client: WorkflowClient;
|
|
18
13
|
runId?: string | undefined;
|
|
19
|
-
/** Redraw interval for elapsed timers while a run is active. */
|
|
20
|
-
tickMs?: number;
|
|
21
14
|
};
|
|
22
15
|
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
width: process.stdout.columns ?? 80,
|
|
26
|
-
height: process.stdout.rows ?? 24,
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Interactive live viewer. Watches the SQLite database and re-renders after
|
|
32
|
-
* committed run changes. Returns when the user quits.
|
|
33
|
-
*/
|
|
16
|
+
/** Interactive client view. All durable reads stay in the workflow host. */
|
|
34
17
|
export async function runViewer(options: ViewerOptions): Promise<void> {
|
|
35
|
-
|
|
36
|
-
let
|
|
37
|
-
let
|
|
38
|
-
let
|
|
39
|
-
let
|
|
40
|
-
|
|
41
|
-
let
|
|
42
|
-
let detailStepCount = 0;
|
|
43
|
-
|
|
44
|
-
if (options.runId) {
|
|
45
|
-
bundles = store.listRuns();
|
|
46
|
-
const match = bundles.find((bundle) => bundle.state.runId === options.runId);
|
|
47
|
-
if (!match) {
|
|
48
|
-
throw new Error(`Run not found: ${options.runId}`);
|
|
49
|
-
}
|
|
50
|
-
mode = { view: "detail", runId: match.runId };
|
|
51
|
-
}
|
|
18
|
+
let value: JsonValue = options.runId === undefined ? [] : null;
|
|
19
|
+
let selected = 0;
|
|
20
|
+
let scroll = 0;
|
|
21
|
+
let selectedStepIndex: number | null = null;
|
|
22
|
+
let mode: "runs" | "run" = options.runId === undefined ? "runs" : "run";
|
|
23
|
+
let unsubscribe: (() => Promise<void>) | undefined;
|
|
24
|
+
let runMaterializationGeneration = 0;
|
|
52
25
|
|
|
53
|
-
const draw =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
26
|
+
const draw = () => {
|
|
27
|
+
const width = process.stdout.columns ?? 100;
|
|
28
|
+
const height = Math.max(1, (process.stdout.rows ?? 24) - 1);
|
|
29
|
+
const lines = renderClientView(
|
|
30
|
+
value,
|
|
31
|
+
width,
|
|
32
|
+
height,
|
|
33
|
+
mode === "runs" ? selected : undefined,
|
|
34
|
+
scroll,
|
|
35
|
+
new Date(),
|
|
36
|
+
selectedStepIndex,
|
|
37
|
+
);
|
|
38
|
+
process.stdout.write(`${CLEAR}${lines.join("\n")}`);
|
|
62
39
|
};
|
|
63
40
|
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return renderRunDetailLines(bundle, size, new Date(), detailScroll, selectedStep);
|
|
41
|
+
const watchRuns = async () => {
|
|
42
|
+
runMaterializationGeneration += 1;
|
|
43
|
+
await unsubscribe?.();
|
|
44
|
+
mode = "runs";
|
|
45
|
+
scroll = 0;
|
|
46
|
+
selectedStepIndex = null;
|
|
47
|
+
unsubscribe = await options.client.watchRuns((event) => {
|
|
48
|
+
value = event.payload;
|
|
49
|
+
selected = Math.min(selected, Math.max(0, arrayLength(value) - 1));
|
|
50
|
+
draw();
|
|
51
|
+
});
|
|
76
52
|
};
|
|
77
53
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
54
|
+
const watchRun = async (runId: string) => {
|
|
55
|
+
runMaterializationGeneration += 1;
|
|
56
|
+
await unsubscribe?.();
|
|
57
|
+
mode = "run";
|
|
58
|
+
scroll = 0;
|
|
59
|
+
selectedStepIndex = null;
|
|
60
|
+
unsubscribe = await options.client.watchRun(runId, (event) => {
|
|
61
|
+
const generation = ++runMaterializationGeneration;
|
|
62
|
+
value = event.payload;
|
|
63
|
+
draw();
|
|
64
|
+
void materializeRunView(options.client, event.payload)
|
|
65
|
+
.then((materialized) => {
|
|
66
|
+
if (generation !== runMaterializationGeneration || mode !== "run") return;
|
|
67
|
+
value = materialized;
|
|
68
|
+
draw();
|
|
69
|
+
})
|
|
70
|
+
.catch(() => {
|
|
71
|
+
// A newer revision event retries the complete view from its own stable snapshot.
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
};
|
|
85
75
|
|
|
76
|
+
process.stdout.write(ALT_SCREEN_ON);
|
|
86
77
|
const rawModeSupported = process.stdin.isTTY === true;
|
|
87
|
-
if (rawModeSupported) {
|
|
88
|
-
process.stdin.setRawMode(true);
|
|
89
|
-
}
|
|
90
|
-
process.stdin.resume();
|
|
91
|
-
|
|
92
78
|
try {
|
|
79
|
+
if (options.runId === undefined) await watchRuns();
|
|
80
|
+
else await watchRun(options.runId);
|
|
81
|
+
draw();
|
|
82
|
+
if (rawModeSupported) process.stdin.setRawMode(true);
|
|
83
|
+
process.stdin.resume();
|
|
84
|
+
const redrawTimer = setInterval(draw, 1_000);
|
|
85
|
+
redrawTimer.unref?.();
|
|
93
86
|
await new Promise<void>((resolve) => {
|
|
94
87
|
const onKey = (data: Buffer) => {
|
|
95
88
|
const key = data.toString("utf8");
|
|
96
|
-
if (key === "
|
|
97
|
-
if (mode.view === "detail" && key === "q") {
|
|
98
|
-
mode = { view: "list" };
|
|
99
|
-
void draw();
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
89
|
+
if (key === "\u0003" || key === "\u001b") {
|
|
102
90
|
resolve();
|
|
103
91
|
return;
|
|
104
92
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const handleNavigationKey = (key: string) => {
|
|
109
|
-
if (mode.view !== "list") {
|
|
110
|
-
if (key === "r") {
|
|
111
|
-
void draw();
|
|
112
|
-
} else if (key === "\u001b[A" || key === "k") {
|
|
113
|
-
detailScroll = Math.max(0, detailScroll - 1);
|
|
114
|
-
void draw();
|
|
115
|
-
} else if (key === "\u001b[B" || key === "j") {
|
|
116
|
-
// Clamped against the content height in renderDetail.
|
|
117
|
-
detailScroll += 1;
|
|
118
|
-
void draw();
|
|
119
|
-
} else if (key === "\u001b[D" || key === "h") {
|
|
120
|
-
const current = selectedStep ?? detailStepCount - 1;
|
|
121
|
-
selectedStep = Math.max(0, current - 1);
|
|
122
|
-
void draw();
|
|
123
|
-
} else if (key === "\u001b[C" || key === "l") {
|
|
124
|
-
// renderDetail snaps back to live once this reaches the end.
|
|
125
|
-
selectedStep = selectedStep === null ? null : selectedStep + 1;
|
|
126
|
-
void draw();
|
|
127
|
-
}
|
|
93
|
+
if (key === "q") {
|
|
94
|
+
if (mode === "run" && options.runId === undefined) void watchRuns();
|
|
95
|
+
else resolve();
|
|
128
96
|
return;
|
|
129
97
|
}
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
98
|
+
if (mode === "run") {
|
|
99
|
+
const page = Math.max(1, (process.stdout.rows ?? 24) - 2);
|
|
100
|
+
if (key === "\u001b[A" || key === "k") {
|
|
101
|
+
scroll = Math.max(0, scroll - 1);
|
|
102
|
+
draw();
|
|
103
|
+
} else if (key === "\u001b[B" || key === "j") {
|
|
104
|
+
scroll += 1;
|
|
105
|
+
draw();
|
|
106
|
+
} else if (key === "\u001b[5~") {
|
|
107
|
+
scroll = Math.max(0, scroll - page);
|
|
108
|
+
draw();
|
|
109
|
+
} else if (key === "\u001b[6~") {
|
|
110
|
+
scroll += page;
|
|
111
|
+
draw();
|
|
112
|
+
} else if (key === "[") {
|
|
113
|
+
const total = runStepCount(value);
|
|
114
|
+
if (total > 0) {
|
|
115
|
+
selectedStepIndex = Math.max(0, (selectedStepIndex ?? total - 1) - 1);
|
|
116
|
+
scroll = 0;
|
|
117
|
+
draw();
|
|
118
|
+
}
|
|
119
|
+
} else if (key === "]") {
|
|
120
|
+
const total = runStepCount(value);
|
|
121
|
+
if (total > 0) {
|
|
122
|
+
const next = (selectedStepIndex ?? total - 1) + 1;
|
|
123
|
+
selectedStepIndex = next >= total - 1 ? null : next;
|
|
124
|
+
scroll = 0;
|
|
125
|
+
draw();
|
|
126
|
+
}
|
|
127
|
+
} else if (key === "\u001b[F" || key === "G" || key === "L") {
|
|
128
|
+
selectedStepIndex = null;
|
|
129
|
+
scroll = 0;
|
|
130
|
+
draw();
|
|
131
|
+
} else if (key === "\u001b[H" || key === "g") {
|
|
132
|
+
selectedStepIndex = runStepCount(value) > 0 ? 0 : null;
|
|
133
|
+
scroll = 0;
|
|
134
|
+
draw();
|
|
135
|
+
}
|
|
136
|
+
} else if (mode === "runs") {
|
|
137
|
+
if (key === "\u001b[A" || key === "k") {
|
|
138
|
+
selected = Math.max(0, selected - 1);
|
|
139
|
+
draw();
|
|
140
|
+
} else if (key === "\u001b[B" || key === "j") {
|
|
141
|
+
selected = Math.min(Math.max(0, arrayLength(value) - 1), selected + 1);
|
|
142
|
+
draw();
|
|
143
|
+
} else if (key === "\r" || key === "\n") {
|
|
144
|
+
const runId = selectedRunId(value, selected);
|
|
145
|
+
if (runId !== undefined) void watchRun(runId);
|
|
143
146
|
}
|
|
144
147
|
}
|
|
145
148
|
};
|
|
146
|
-
|
|
147
149
|
process.stdin.on("data", onKey);
|
|
148
|
-
void draw();
|
|
149
150
|
});
|
|
151
|
+
clearInterval(redrawTimer);
|
|
150
152
|
} finally {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (rawModeSupported) {
|
|
155
|
-
process.stdin.setRawMode(false);
|
|
156
|
-
}
|
|
153
|
+
runMaterializationGeneration += 1;
|
|
154
|
+
await unsubscribe?.();
|
|
155
|
+
if (rawModeSupported) process.stdin.setRawMode(false);
|
|
157
156
|
process.stdin.pause();
|
|
158
157
|
process.stdout.write(ALT_SCREEN_OFF);
|
|
159
158
|
}
|
|
160
159
|
}
|
|
160
|
+
|
|
161
|
+
export function renderClientView(
|
|
162
|
+
value: JsonValue,
|
|
163
|
+
width: number,
|
|
164
|
+
height: number,
|
|
165
|
+
selected: number | undefined,
|
|
166
|
+
scroll: number,
|
|
167
|
+
now: Date = new Date(),
|
|
168
|
+
selectedStepIndex: number | null = null,
|
|
169
|
+
): string[] {
|
|
170
|
+
if (Array.isArray(value)) {
|
|
171
|
+
if (value.length === 0) return ["No workflow runs found."];
|
|
172
|
+
const selectedIndex = selected ?? 0;
|
|
173
|
+
const start = Math.min(
|
|
174
|
+
Math.max(0, selectedIndex - height + 1),
|
|
175
|
+
Math.max(0, value.length - height),
|
|
176
|
+
);
|
|
177
|
+
return value.slice(start, start + height).map((item, offset) => {
|
|
178
|
+
const index = start + offset;
|
|
179
|
+
const prefix = index === selected ? "> " : " ";
|
|
180
|
+
return truncate(`${prefix}${summary(item)}`, width);
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const lines = renderRun(value, now, selectedStepIndex);
|
|
184
|
+
const start = Math.min(scroll, Math.max(0, lines.length - height));
|
|
185
|
+
return lines.slice(start, start + height).map((line) => truncate(line, width));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function renderRun(value: JsonValue, now: Date, selectedStepIndex: number | null): string[] {
|
|
189
|
+
if (!isRecord(value) || value.schema !== "pi-workflows.run-view.v1") {
|
|
190
|
+
return JSON.stringify(value, null, 2).split("\n");
|
|
191
|
+
}
|
|
192
|
+
const manifest = isRecord(value.manifest) ? value.manifest : {};
|
|
193
|
+
const state = isRecord(value.state) ? value.state : {};
|
|
194
|
+
const display = isRecord(value.display) ? value.display : {};
|
|
195
|
+
const workflowName =
|
|
196
|
+
typeof manifest.workflowName === "string"
|
|
197
|
+
? sanitizeText(manifest.workflowName)
|
|
198
|
+
: typeof state.workflowName === "string"
|
|
199
|
+
? sanitizeText(state.workflowName)
|
|
200
|
+
: "unknown";
|
|
201
|
+
const runId = typeof value.runId === "string" ? sanitizeText(value.runId) : "unknown";
|
|
202
|
+
const status = typeof display.status === "string" ? display.status : "unknown";
|
|
203
|
+
const steps = Array.isArray(state.steps) ? state.steps : [];
|
|
204
|
+
const selected =
|
|
205
|
+
steps.length === 0
|
|
206
|
+
? -1
|
|
207
|
+
: Math.min(Math.max(0, selectedStepIndex ?? steps.length - 1), steps.length - 1);
|
|
208
|
+
const startedAt = typeof manifest.startedAt === "string" ? Date.parse(manifest.startedAt) : NaN;
|
|
209
|
+
const finishedAt =
|
|
210
|
+
typeof manifest.finishedAt === "string" ? Date.parse(manifest.finishedAt) : NaN;
|
|
211
|
+
const elapsed = Number.isFinite(startedAt)
|
|
212
|
+
? ` · elapsed ${formatDuration(Math.max(0, (Number.isFinite(finishedAt) ? finishedAt : now.getTime()) - startedAt))}`
|
|
213
|
+
: "";
|
|
214
|
+
const position =
|
|
215
|
+
selectedStepIndex === null || selected < 0 ? "" : ` · step ${selected + 1}/${steps.length}`;
|
|
216
|
+
const lines = [
|
|
217
|
+
`workflow ${workflowName}`,
|
|
218
|
+
`${statusGlyph(status)} ${sanitizeText(status)} · run ${runId}${elapsed}${position}`,
|
|
219
|
+
];
|
|
220
|
+
if (typeof display.reason === "string") lines.push(`reason: ${sanitizeText(display.reason)}`);
|
|
221
|
+
|
|
222
|
+
if (steps.length > 0) {
|
|
223
|
+
lines.push("", "steps");
|
|
224
|
+
for (const [index, step] of steps.entries()) {
|
|
225
|
+
if (!isRecord(step)) continue;
|
|
226
|
+
const nodeId = typeof step.nodeId === "string" ? sanitizeText(step.nodeId) : "unknown";
|
|
227
|
+
const outcome = typeof step.outcome === "string" ? step.outcome : "unknown";
|
|
228
|
+
lines.push(
|
|
229
|
+
`${index === selected ? ">" : " "} ${stepGlyph(outcome)} ${nodeId} · ${sanitizeText(outcome)}`,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
const inspected = steps[selected];
|
|
233
|
+
if (isRecord(inspected)) {
|
|
234
|
+
const nodeId =
|
|
235
|
+
typeof inspected.nodeId === "string" ? sanitizeText(inspected.nodeId) : "unknown";
|
|
236
|
+
const output = Object.hasOwn(inspected, "error") ? inspected.error : inspected.output;
|
|
237
|
+
lines.push("", `step output — ${nodeId}`);
|
|
238
|
+
lines.push(
|
|
239
|
+
...JSON.stringify(output ?? null, null, 2)
|
|
240
|
+
.split("\n")
|
|
241
|
+
.map((line) => ` ${sanitizeText(line)}`),
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (status === "completed" && Object.hasOwn(state, "finalOutput")) {
|
|
246
|
+
lines.push("", `output ${JSON.stringify(state.finalOutput)}`);
|
|
247
|
+
} else if (typeof state.error === "string") {
|
|
248
|
+
lines.push("", `error: ${sanitizeText(state.error)}`);
|
|
249
|
+
}
|
|
250
|
+
return lines;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function summary(value: JsonValue): string {
|
|
254
|
+
if (!isRecord(value)) return JSON.stringify(value);
|
|
255
|
+
const status = isRecord(value.display) ? value.display.status : undefined;
|
|
256
|
+
const name = typeof value.workflowName === "string" ? value.workflowName : "workflow";
|
|
257
|
+
const runId = typeof value.runId === "string" ? value.runId : "unknown";
|
|
258
|
+
return `${typeof status === "string" ? sanitizeText(status) : "unknown"} ${sanitizeText(name)} ${sanitizeText(runId)}`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function statusGlyph(status: string): string {
|
|
262
|
+
if (status === "completed") return "✓";
|
|
263
|
+
if (status === "failed" || status === "timed_out" || status === "cancelled") return "✗";
|
|
264
|
+
if (status === "paused") return "‖";
|
|
265
|
+
if (status === "waiting") return "○";
|
|
266
|
+
if (status === "queued") return "…";
|
|
267
|
+
if (status === "ambiguous") return "!";
|
|
268
|
+
return "●";
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function stepGlyph(outcome: string): string {
|
|
272
|
+
return outcome === "ok" ? "✓" : outcome === "failed" || outcome === "timed_out" ? "✗" : "○";
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function selectedRunId(value: JsonValue, index: number): string | undefined {
|
|
276
|
+
if (!Array.isArray(value)) return undefined;
|
|
277
|
+
const item = value[index];
|
|
278
|
+
return isRecord(item) && typeof item.runId === "string" ? item.runId : undefined;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function runStepCount(value: JsonValue): number {
|
|
282
|
+
if (!isRecord(value) || !isRecord(value.state) || !Array.isArray(value.state.steps)) return 0;
|
|
283
|
+
return value.state.steps.length;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function arrayLength(value: JsonValue): number {
|
|
287
|
+
return Array.isArray(value) ? value.length : 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function truncate(value: string, width: number): string {
|
|
291
|
+
if (value.length <= width) return value;
|
|
292
|
+
return width <= 1 ? value.slice(0, width) : `${value.slice(0, width - 1)}…`;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function isRecord(value: unknown): value is Record<string, JsonValue> {
|
|
296
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
297
|
+
}
|
|
@@ -468,6 +468,14 @@ export class HumanDecisionStore {
|
|
|
468
468
|
channel: string,
|
|
469
469
|
value: HumanDecisionDeliveryRecord,
|
|
470
470
|
): Promise<"created" | "adopted"> {
|
|
471
|
+
return this.recordDeliverySync(request, channel, value);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
recordDeliverySync(
|
|
475
|
+
request: HumanDecisionRequest | HumanDecisionChannelRequest,
|
|
476
|
+
channel: string,
|
|
477
|
+
value: HumanDecisionDeliveryRecord,
|
|
478
|
+
): "created" | "adopted" {
|
|
471
479
|
return this.recordChannelMessage(request.decisionId, channel, "delivery", value);
|
|
472
480
|
}
|
|
473
481
|
|
|
@@ -570,6 +578,14 @@ export class HumanDecisionStore {
|
|
|
570
578
|
channel: string,
|
|
571
579
|
value: HumanDecisionSettlementRecord,
|
|
572
580
|
): Promise<"created" | "adopted"> {
|
|
581
|
+
return this.recordSettlementSync(decisionId, channel, value);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
recordSettlementSync(
|
|
585
|
+
decisionId: string,
|
|
586
|
+
channel: string,
|
|
587
|
+
value: HumanDecisionSettlementRecord,
|
|
588
|
+
): "created" | "adopted" {
|
|
573
589
|
return this.recordChannelMessage(decisionId, channel, "settlement", value);
|
|
574
590
|
}
|
|
575
591
|
|
|
@@ -1066,7 +1082,7 @@ export class HumanDecisionStore {
|
|
|
1066
1082
|
purpose: "delivery" | "settlement",
|
|
1067
1083
|
value: T,
|
|
1068
1084
|
): "created" | "adopted" {
|
|
1069
|
-
const safeChannel =
|
|
1085
|
+
const safeChannel = requireChannelId(channel, "Human decision channel");
|
|
1070
1086
|
assertId(value.attemptId, `${purpose} attempt id`);
|
|
1071
1087
|
return this.state.transaction(() => {
|
|
1072
1088
|
if (this.readRequestRow(decisionId) === undefined) {
|
|
@@ -1120,7 +1136,7 @@ export class HumanDecisionStore {
|
|
|
1120
1136
|
channel: string,
|
|
1121
1137
|
purpose: "delivery" | "settlement",
|
|
1122
1138
|
): T[] {
|
|
1123
|
-
const channelId = channelIdFor(
|
|
1139
|
+
const channelId = channelIdFor(requireChannelId(channel, "Human decision channel"));
|
|
1124
1140
|
const rows = this.state.connection
|
|
1125
1141
|
.prepare(
|
|
1126
1142
|
`SELECT content_hash AS contentHash
|
package/src/workflows/index.ts
CHANGED
|
@@ -101,20 +101,7 @@ export type WorkflowSettingsChangeResult = {
|
|
|
101
101
|
adopted: boolean;
|
|
102
102
|
};
|
|
103
103
|
|
|
104
|
-
export type WorkflowFollowUpState =
|
|
105
|
-
| "queued"
|
|
106
|
-
| "pending_presentation"
|
|
107
|
-
| "ready"
|
|
108
|
-
| "sent"
|
|
109
|
-
| "removed"
|
|
110
|
-
| "cancelled";
|
|
111
|
-
|
|
112
|
-
export type WorkflowPresentationState =
|
|
113
|
-
| "none"
|
|
114
|
-
| "not-needed"
|
|
115
|
-
| "pending"
|
|
116
|
-
| "settled"
|
|
117
|
-
| "unavailable";
|
|
104
|
+
export type WorkflowFollowUpState = "queued" | "removed" | "cancelled";
|
|
118
105
|
|
|
119
106
|
export type WorkflowFollowUpRecord = {
|
|
120
107
|
followUpId: string;
|
|
@@ -126,20 +113,14 @@ export type WorkflowFollowUpRecord = {
|
|
|
126
113
|
source: string;
|
|
127
114
|
prompt: string;
|
|
128
115
|
state: WorkflowFollowUpState;
|
|
129
|
-
sessionEntryId?: string;
|
|
130
116
|
reason?: string;
|
|
131
117
|
createdAt: string;
|
|
132
118
|
updatedAt: string;
|
|
133
|
-
sentAt?: string;
|
|
134
119
|
};
|
|
135
120
|
|
|
136
121
|
export type WorkflowFollowUpQueueRecord = {
|
|
137
122
|
runId: string;
|
|
138
123
|
originSessionId?: string;
|
|
139
|
-
presentationState: WorkflowPresentationState;
|
|
140
|
-
presentationEntryId?: string;
|
|
141
|
-
presentationAssistantEntryId?: string;
|
|
142
|
-
presentationReason?: string;
|
|
143
124
|
followUps: WorkflowFollowUpRecord[];
|
|
144
125
|
};
|
|
145
126
|
|