@osolmaz/pi-workflows 0.15.3 → 0.16.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 +6 -6
- 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 +101 -0
- package/dist/client/client.js +733 -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 +269 -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 +118 -0
- package/dist/client/view.js +3 -0
- package/dist/client/view.js.map +1 -0
- package/dist/controllers/sqlite.d.ts +43 -0
- package/dist/controllers/sqlite.js +137 -2
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/index.d.ts +1 -0
- package/dist/extension/index.js +278 -183
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/session-view.d.ts +7 -2
- package/dist/extension/session-view.js +60 -52
- package/dist/extension/session-view.js.map +1 -1
- package/dist/extension/widget.d.ts +2 -1
- package/dist/extension/widget.js +14 -7
- package/dist/extension/widget.js.map +1 -1
- 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 +11 -0
- package/dist/host/runner.js +473 -17
- package/dist/host/runner.js.map +1 -1
- package/dist/host/state.d.ts +6 -3
- package/dist/host/state.js +52 -26
- package/dist/host/state.js.map +1 -1
- package/dist/host/view.d.ts +73 -0
- package/dist/host/view.js +871 -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 +6 -8
- package/dist/state/prune.js.map +1 -1
- package/dist/state/schema.js +12 -1
- package/dist/state/schema.js.map +1 -1
- 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/tui.d.ts +5 -7
- package/dist/viewer/tui.js +188 -108
- package/dist/viewer/tui.js.map +1 -1
- package/dist/workflows/store.d.ts +62 -1
- package/dist/workflows/store.js +350 -44
- package/dist/workflows/store.js.map +1 -1
- package/docs/2026-09-01-unified-workflow-client-plan.md +381 -0
- package/docs/2026-09-02-installed-live-e2e-plan.md +225 -0
- package/docs/SQLITE_STATE.md +13 -11
- package/docs/WORKFLOW_HOST.md +74 -61
- package/docs/development.md +2 -1
- package/docs/live-replay-protocol.md +70 -132
- package/docs/tui-viewer.md +10 -14
- package/docs/workflows.md +44 -1
- package/herdr-plugin.toml +1 -1
- package/package.json +9 -3
- package/protocol/client.v1.schema.json +137 -0
- package/protocol/fixtures/client-v1.json +23 -0
- package/src/client/activity.ts +6 -0
- package/src/client/client.ts +935 -0
- package/src/client/index.ts +24 -0
- package/src/client/materialize.ts +228 -0
- package/src/client/protocol.ts +327 -0
- package/src/client/resolver.ts +26 -0
- package/src/client/view.ts +138 -0
- package/src/controllers/sqlite.ts +213 -2
- package/src/extension/index.ts +349 -208
- package/src/extension/session-view.ts +73 -50
- package/src/extension/widget.ts +16 -8
- package/src/host/child-worker-supervisor.ts +1 -1
- package/src/host/resolver-entry.ts +11 -26
- package/src/host/runner.ts +648 -48
- package/src/host/state.ts +71 -43
- package/src/host/view.ts +1084 -0
- package/src/host/worker-protocol.ts +1 -1
- package/src/state/database.ts +13 -0
- package/src/state/prune.ts +11 -11
- package/src/state/schema.ts +12 -1
- package/src/viewer/backup.ts +29 -0
- package/src/viewer/cli.ts +171 -185
- package/src/viewer/tui.ts +196 -124
- package/src/workflows/store.ts +500 -45
- 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/host/client.ts +0 -293
- package/src/host/protocol.ts +0 -196
- package/src/viewer/watch.ts +0 -51
|
@@ -1,32 +1,51 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { WorkflowRunStore, type LoadedWorkflowRun } from "../workflows/store.js";
|
|
2
|
+
import type { WorkflowSessionView } from "../client/view.js";
|
|
3
|
+
import type { WorkflowDefinitionSnapshot, WorkflowRunState } from "../workflows/types.js";
|
|
5
4
|
import { buildWidgetView } from "./widget.js";
|
|
6
5
|
|
|
7
6
|
const WIDGET_KEY = "pi-workflows";
|
|
8
7
|
const WIDGET_SCROLL_STEP = 3;
|
|
9
8
|
|
|
10
|
-
/**
|
|
9
|
+
/** Client-backed projection of the host-owned run into the origin Pi session. */
|
|
11
10
|
export class SessionWorkflowView {
|
|
12
|
-
private
|
|
11
|
+
private session: WorkflowSessionView | null = null;
|
|
13
12
|
private scroll: number | null = null;
|
|
14
13
|
private shownScroll = 0;
|
|
15
14
|
private maxScroll = 0;
|
|
16
15
|
private stepCount = 0;
|
|
17
16
|
private visible = false;
|
|
17
|
+
private actionHint: string | undefined;
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
if (
|
|
22
|
-
|
|
19
|
+
update(session: WorkflowSessionView, ctx: ExtensionContext): void {
|
|
20
|
+
const run = session.run;
|
|
21
|
+
if (
|
|
22
|
+
run === null ||
|
|
23
|
+
!isWorkflowRunState(run.state) ||
|
|
24
|
+
!isWorkflowDefinitionSnapshot(run.workflow)
|
|
25
|
+
) {
|
|
26
|
+
this.session = session;
|
|
27
|
+
this.clearWidget(ctx);
|
|
23
28
|
return;
|
|
24
29
|
}
|
|
25
|
-
|
|
30
|
+
const previousRun = this.session?.run;
|
|
31
|
+
if (previousRun?.runId !== run.runId || this.stepCount !== run.state.steps.length) {
|
|
26
32
|
this.scroll = null;
|
|
27
|
-
this.stepCount =
|
|
33
|
+
this.stepCount = run.state.steps.length;
|
|
34
|
+
}
|
|
35
|
+
this.session = session;
|
|
36
|
+
this.render(ctx);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setActionHint(hint: string | undefined, ctx: ExtensionContext): void {
|
|
40
|
+
this.actionHint = hint;
|
|
41
|
+
if (this.session?.run !== null) this.render(ctx);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
refresh(ctx: ExtensionContext): void {
|
|
45
|
+
if (this.session?.run === null || this.session === null) {
|
|
46
|
+
this.clearWidget(ctx);
|
|
47
|
+
return;
|
|
28
48
|
}
|
|
29
|
-
this.loaded = loaded;
|
|
30
49
|
this.render(ctx);
|
|
31
50
|
}
|
|
32
51
|
|
|
@@ -39,42 +58,51 @@ export class SessionWorkflowView {
|
|
|
39
58
|
}
|
|
40
59
|
|
|
41
60
|
clear(ctx: ExtensionContext): void {
|
|
42
|
-
this.
|
|
61
|
+
this.session = null;
|
|
43
62
|
this.scroll = null;
|
|
44
63
|
this.shownScroll = 0;
|
|
45
64
|
this.maxScroll = 0;
|
|
46
65
|
this.stepCount = 0;
|
|
47
|
-
|
|
48
|
-
this.visible = false;
|
|
49
|
-
safelyUpdateUi(ctx, () => {
|
|
50
|
-
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
51
|
-
ctx.ui.setStatus(WIDGET_KEY, undefined);
|
|
52
|
-
});
|
|
66
|
+
this.clearWidget(ctx);
|
|
53
67
|
}
|
|
54
68
|
|
|
55
69
|
private scrollBy(ctx: ExtensionContext, delta: number): void {
|
|
56
|
-
if (this.
|
|
70
|
+
if (this.session?.run === null || this.session === null) return;
|
|
57
71
|
const current = this.scroll ?? this.shownScroll;
|
|
58
72
|
this.scroll = Math.max(0, Math.min(this.maxScroll, current + delta));
|
|
59
73
|
this.render(ctx);
|
|
60
74
|
}
|
|
61
75
|
|
|
76
|
+
private clearWidget(ctx: ExtensionContext): void {
|
|
77
|
+
if (!this.visible) return;
|
|
78
|
+
this.visible = false;
|
|
79
|
+
safelyUpdateUi(ctx, () => {
|
|
80
|
+
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
81
|
+
ctx.ui.setStatus(WIDGET_KEY, undefined);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
62
85
|
private render(ctx: ExtensionContext): void {
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
86
|
+
const run = this.session?.run;
|
|
87
|
+
if (run === null || run === undefined) return;
|
|
88
|
+
if (!isWorkflowRunState(run.state) || !isWorkflowDefinitionSnapshot(run.workflow)) return;
|
|
89
|
+
const state = run.state;
|
|
90
|
+
const snapshot = run.workflow;
|
|
65
91
|
const render = (
|
|
66
92
|
width = Number.POSITIVE_INFINITY,
|
|
67
93
|
theme?: Parameters<typeof buildWidgetView>[6],
|
|
68
94
|
) => {
|
|
69
95
|
const view = buildWidgetView(
|
|
70
|
-
|
|
71
|
-
|
|
96
|
+
state,
|
|
97
|
+
snapshot,
|
|
72
98
|
new Date(),
|
|
73
99
|
this.scroll,
|
|
74
|
-
|
|
100
|
+
run.display.status === "paused",
|
|
75
101
|
width,
|
|
76
102
|
theme,
|
|
77
103
|
undefined,
|
|
104
|
+
this.actionHint,
|
|
105
|
+
run.display.status,
|
|
78
106
|
);
|
|
79
107
|
this.shownScroll = view.scroll;
|
|
80
108
|
this.maxScroll = view.maxScroll;
|
|
@@ -90,42 +118,37 @@ export class SessionWorkflowView {
|
|
|
90
118
|
} else {
|
|
91
119
|
ctx.ui.setWidget(WIDGET_KEY, render());
|
|
92
120
|
}
|
|
93
|
-
|
|
94
|
-
ctx.ui.setStatus(WIDGET_KEY, `${loaded.state.workflowName} [${label}]`);
|
|
121
|
+
ctx.ui.setStatus(WIDGET_KEY, `${state.workflowName} [${run.display.status}]`);
|
|
95
122
|
this.visible = true;
|
|
96
123
|
});
|
|
97
124
|
}
|
|
98
125
|
}
|
|
99
126
|
|
|
100
|
-
function
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
queue.close();
|
|
111
|
-
}
|
|
112
|
-
if (runId === undefined) return undefined;
|
|
127
|
+
function isWorkflowRunState(value: unknown): value is WorkflowRunState {
|
|
128
|
+
return (
|
|
129
|
+
typeof value === "object" &&
|
|
130
|
+
value !== null &&
|
|
131
|
+
!Array.isArray(value) &&
|
|
132
|
+
(value as { schema?: unknown }).schema === "pi-workflows.run-state.v1" &&
|
|
133
|
+
typeof (value as { workflowName?: unknown }).workflowName === "string" &&
|
|
134
|
+
Array.isArray((value as { steps?: unknown }).steps)
|
|
135
|
+
);
|
|
136
|
+
}
|
|
113
137
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
138
|
+
function isWorkflowDefinitionSnapshot(value: unknown): value is WorkflowDefinitionSnapshot {
|
|
139
|
+
return (
|
|
140
|
+
typeof value === "object" &&
|
|
141
|
+
value !== null &&
|
|
142
|
+
!Array.isArray(value) &&
|
|
143
|
+
(value as { schema?: unknown }).schema === "pi-workflows.definition-snapshot.v1" &&
|
|
144
|
+
typeof (value as { nodes?: unknown }).nodes === "object"
|
|
145
|
+
);
|
|
123
146
|
}
|
|
124
147
|
|
|
125
148
|
function safelyUpdateUi(ctx: ExtensionContext, update: () => void): void {
|
|
126
149
|
try {
|
|
127
150
|
if (ctx.hasUI) update();
|
|
128
151
|
} catch {
|
|
129
|
-
// A session replacement can make a captured context stale between
|
|
152
|
+
// A session replacement can make a captured context stale between updates.
|
|
130
153
|
}
|
|
131
154
|
}
|
package/src/extension/widget.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Theme, ThemeColor } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
import type { WorkflowDisplayStatus } from "../client/view.js";
|
|
3
4
|
import { formatDuration } from "../render/format.js";
|
|
4
5
|
import { nodeTypeGlyph } from "../render/node-type.js";
|
|
5
6
|
import {
|
|
@@ -14,17 +15,19 @@ import { sanitizeText } from "../workflows/text.js";
|
|
|
14
15
|
import type {
|
|
15
16
|
WorkflowDefinitionSnapshot,
|
|
16
17
|
WorkflowRunState,
|
|
17
|
-
WorkflowRunStatus,
|
|
18
18
|
WorkflowUpdateRecord,
|
|
19
19
|
} from "../workflows/types.js";
|
|
20
20
|
|
|
21
|
-
const STATUS_GLYPHS: Record<
|
|
21
|
+
const STATUS_GLYPHS: Record<WorkflowDisplayStatus, string> = {
|
|
22
|
+
queued: "…",
|
|
22
23
|
running: "◐",
|
|
23
|
-
waiting: "
|
|
24
|
+
waiting: "○",
|
|
25
|
+
paused: "⏸",
|
|
24
26
|
completed: "✓",
|
|
25
27
|
failed: "✗",
|
|
26
28
|
timed_out: "✗",
|
|
27
29
|
cancelled: "✗",
|
|
30
|
+
ambiguous: "!",
|
|
28
31
|
};
|
|
29
32
|
|
|
30
33
|
/**
|
|
@@ -79,26 +82,28 @@ export function buildWidgetView(
|
|
|
79
82
|
theme?: WidgetTheme,
|
|
80
83
|
updateHistory?: WorkflowUpdateRecord[],
|
|
81
84
|
actionHint?: string,
|
|
85
|
+
displayStatus?: WorkflowDisplayStatus,
|
|
82
86
|
): WidgetView {
|
|
83
87
|
const availableWidth = Number.isFinite(width) ? Math.max(0, Math.floor(width)) : width;
|
|
84
88
|
if (availableWidth === 0) return { lines: [], scroll: 0, maxScroll: 0 };
|
|
85
89
|
|
|
86
90
|
// `held` covers pauses the state cannot see yet: an escape-interrupted
|
|
87
91
|
// step or a pause requested while the current node is still finishing.
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
const
|
|
92
|
+
const status = displayStatus ?? (held || state.paused === true ? "paused" : state.status);
|
|
93
|
+
const paused = status === "paused";
|
|
94
|
+
const glyph = STATUS_GLYPHS[status];
|
|
95
|
+
const statusText = status;
|
|
91
96
|
// Titles, status details, and errors can carry model- or shell-controlled
|
|
92
97
|
// text; never let escape sequences or newlines reach the terminal.
|
|
93
98
|
const title = state.runTitle ? ` — ${sanitizeText(state.runTitle)}` : "";
|
|
94
|
-
const headerTone = statusTone(
|
|
99
|
+
const headerTone = statusTone(status);
|
|
95
100
|
const header = `${paint(theme, headerTone, glyph)} workflow ${sanitizeText(state.workflowName)}${title} ${paint(theme, headerTone, `[${statusText}]`)}`;
|
|
96
101
|
|
|
97
102
|
const footer: string[] = [];
|
|
98
103
|
if (state.error) {
|
|
99
104
|
footer.push(paint(theme, "error", ` error: ${truncate(sanitizeText(state.error), 120)}`));
|
|
100
105
|
}
|
|
101
|
-
if (
|
|
106
|
+
if (status === "waiting" && state.waitingOn) {
|
|
102
107
|
footer.push(
|
|
103
108
|
paint(theme, "warning", ` waiting on checkpoint: ${sanitizeText(state.waitingOn)}`),
|
|
104
109
|
);
|
|
@@ -407,7 +412,10 @@ function statusTone(status: string): ThemeColor {
|
|
|
407
412
|
case "cancelled":
|
|
408
413
|
return "error";
|
|
409
414
|
case "waiting":
|
|
415
|
+
case "paused":
|
|
410
416
|
return "warning";
|
|
417
|
+
case "ambiguous":
|
|
418
|
+
return "error";
|
|
411
419
|
case "running":
|
|
412
420
|
default:
|
|
413
421
|
return "accent";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
2
|
import { once } from "node:events";
|
|
3
|
+
import { NdjsonFrameDecoder } from "../client/protocol.js";
|
|
3
4
|
import { errorMessage } from "../workflows/errors.js";
|
|
4
5
|
import { HostProcessRegistry, type ProcessIdentity } from "./processes.js";
|
|
5
|
-
import { NdjsonFrameDecoder } from "./protocol.js";
|
|
6
6
|
import type { WorkerOutcome } from "./state.js";
|
|
7
7
|
|
|
8
8
|
const DEFAULT_STARTUP_TIMEOUT_MS = 15_000;
|
|
@@ -3,6 +3,12 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { builtinWorkflowCatalog } from "../builtins/catalog.js";
|
|
6
|
+
import { MAX_PROTOCOL_MESSAGE_BYTES } from "../client/protocol.js";
|
|
7
|
+
import type {
|
|
8
|
+
ResolvedControllerInitialization,
|
|
9
|
+
ResolvedSettingsChange,
|
|
10
|
+
ResolvedWorkflowLaunch,
|
|
11
|
+
} from "../client/resolver.js";
|
|
6
12
|
import { discoverControllers, loadControllerFile } from "../controllers/loader.js";
|
|
7
13
|
import { canonicalJson, parseJson, type JsonValue } from "../state/json.js";
|
|
8
14
|
import { compositionMetadata } from "../workflows/composition.js";
|
|
@@ -12,32 +18,11 @@ import { resolveWorkflowRef } from "../workflows/loader.js";
|
|
|
12
18
|
import { applyWorkflowSettingsPatch } from "../workflows/settings.js";
|
|
13
19
|
import { createDefinitionSnapshot } from "../workflows/store.js";
|
|
14
20
|
import type { WorkflowDefinition, WorkflowSource } from "../workflows/types.js";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
workflowSourceRef: string;
|
|
21
|
-
workflowSource: JsonValue;
|
|
22
|
-
definitionDigest: string;
|
|
23
|
-
definitionSnapshot: JsonValue;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export type ResolvedControllerInitialization = {
|
|
27
|
-
schema: "pi-workflows.resolved-controller-initialization.v1";
|
|
28
|
-
controllerName: string;
|
|
29
|
-
controllerPath: string;
|
|
30
|
-
sourceHash: string;
|
|
31
|
-
initialStatus: JsonValue;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
export type ResolvedSettingsChange = {
|
|
35
|
-
schema: "pi-workflows.resolved-settings-change.v1";
|
|
36
|
-
definitionDigest: string;
|
|
37
|
-
patch: JsonValue;
|
|
38
|
-
settings: JsonValue;
|
|
39
|
-
paths: JsonValue;
|
|
40
|
-
};
|
|
21
|
+
export type {
|
|
22
|
+
ResolvedControllerInitialization,
|
|
23
|
+
ResolvedSettingsChange,
|
|
24
|
+
ResolvedWorkflowLaunch,
|
|
25
|
+
} from "../client/resolver.js";
|
|
41
26
|
|
|
42
27
|
type ResolverRequest = {
|
|
43
28
|
schema: "pi-workflows.resolve-request.v1";
|