@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/cli.ts
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { realpathSync } from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
-
import {
|
|
6
|
+
import { WorkflowClient } from "../client/client.js";
|
|
7
|
+
import { materializeRunView } from "../client/materialize.js";
|
|
6
8
|
import { syncHerdrPlugin } from "../herdr/setup.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
formatDuration,
|
|
14
|
-
renderRunDetailLines,
|
|
15
|
-
renderRunListLines,
|
|
16
|
-
runElapsedMs,
|
|
17
|
-
statusLabel,
|
|
18
|
-
} from "./render.js";
|
|
19
|
-
import { runViewer } from "./tui.js";
|
|
9
|
+
import type { JsonValue } from "../state/json.js";
|
|
10
|
+
import { verifyInactiveBackup } from "./backup.js";
|
|
11
|
+
import { renderClientView, runViewer } from "./tui.js";
|
|
12
|
+
|
|
13
|
+
const CLI_CLIENT_ID = "pi-workflows-cli";
|
|
20
14
|
|
|
21
15
|
const USAGE = `pi-workflows — workflow runs and controller resources
|
|
22
16
|
|
|
@@ -25,7 +19,8 @@ Usage:
|
|
|
25
19
|
pi-workflows runs
|
|
26
20
|
pi-workflows controllers
|
|
27
21
|
pi-workflows controller <controller> <key>
|
|
28
|
-
pi-workflows state status
|
|
22
|
+
pi-workflows state status
|
|
23
|
+
pi-workflows state verify [inactive-backup]
|
|
29
24
|
pi-workflows state backup <destination>
|
|
30
25
|
pi-workflows state prune --before <timestamp> --dry-run
|
|
31
26
|
pi-workflows state prune --before <timestamp> --backup <absolute-path> --apply
|
|
@@ -36,7 +31,7 @@ Usage:
|
|
|
36
31
|
pi-workflows herdr sync [--json]
|
|
37
32
|
pi-workflows herdr setup [--json]
|
|
38
33
|
|
|
39
|
-
|
|
34
|
+
Active state is available only through the package-owned workflow host.
|
|
40
35
|
`;
|
|
41
36
|
|
|
42
37
|
export type CliArgs = {
|
|
@@ -48,6 +43,7 @@ export type CliArgs = {
|
|
|
48
43
|
hostAction?: "start" | "status" | "stop" | "run";
|
|
49
44
|
stateAction?: "status" | "verify" | "backup" | "prune";
|
|
50
45
|
backupDestination?: string;
|
|
46
|
+
verifyBackup?: string;
|
|
51
47
|
pruneBefore?: string;
|
|
52
48
|
pruneApply?: boolean;
|
|
53
49
|
once: boolean;
|
|
@@ -58,6 +54,13 @@ export type CliArgs = {
|
|
|
58
54
|
export function parseCliArgs(argv: string[]): CliArgs {
|
|
59
55
|
const args = [...argv];
|
|
60
56
|
const command = args[0] && !args[0].startsWith("-") ? (args.shift() as string) : "view";
|
|
57
|
+
if (
|
|
58
|
+
!["view", "runs", "controllers", "controller", "state", "host", "herdr", "help"].includes(
|
|
59
|
+
command,
|
|
60
|
+
)
|
|
61
|
+
) {
|
|
62
|
+
throw new Error(`Unknown command: ${command}`);
|
|
63
|
+
}
|
|
61
64
|
let once = false;
|
|
62
65
|
let json = false;
|
|
63
66
|
let project: string | undefined;
|
|
@@ -143,12 +146,22 @@ export function parseCliArgs(argv: string[]): CliArgs {
|
|
|
143
146
|
json,
|
|
144
147
|
};
|
|
145
148
|
}
|
|
149
|
+
if (action === "verify") {
|
|
150
|
+
if (positionals.length > 2) throw new Error("state verify accepts one inactive backup path");
|
|
151
|
+
return {
|
|
152
|
+
command,
|
|
153
|
+
stateAction: action,
|
|
154
|
+
...(positionals[1] === undefined ? {} : { verifyBackup: positionals[1] }),
|
|
155
|
+
once,
|
|
156
|
+
json,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
146
159
|
if (positionals.length !== 1) throw new Error(`state ${action} accepts no other arguments`);
|
|
147
160
|
return { command, stateAction: action, once, json };
|
|
148
161
|
}
|
|
149
162
|
if (command === "herdr") {
|
|
150
163
|
if (positionals.length !== 1 || !["sync", "setup"].includes(positionals[0] as string)) {
|
|
151
|
-
throw new Error("herdr requires the sync action");
|
|
164
|
+
throw new Error("herdr requires the sync or setup action");
|
|
152
165
|
}
|
|
153
166
|
return { command, herdrAction: positionals[0] as string, once, json };
|
|
154
167
|
}
|
|
@@ -161,101 +174,12 @@ export function parseCliArgs(argv: string[]): CliArgs {
|
|
|
161
174
|
};
|
|
162
175
|
}
|
|
163
176
|
|
|
164
|
-
function printRuns(databasePath: string): void {
|
|
165
|
-
const store = new WorkflowRunStore(databasePath, { readOnly: true });
|
|
166
|
-
try {
|
|
167
|
-
const runs = store.listRuns();
|
|
168
|
-
if (runs.length === 0) {
|
|
169
|
-
process.stdout.write("No workflow runs found.\n");
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
for (const run of runs) {
|
|
173
|
-
const state = run.state;
|
|
174
|
-
const title = state.runTitle ? ` — ${sanitizeText(state.runTitle)}` : "";
|
|
175
|
-
process.stdout.write(
|
|
176
|
-
`${statusLabel(state.status)} ${sanitizeText(state.workflowName)}${title} ${state.runId} ${formatDuration(runElapsedMs(state))}\n`,
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
} finally {
|
|
180
|
-
store.close();
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function printOnce(databasePath: string, runId: string | undefined): void {
|
|
185
|
-
const store = new WorkflowRunStore(databasePath, { readOnly: true });
|
|
186
|
-
try {
|
|
187
|
-
const size = { width: process.stdout.columns ?? 100, height: 1_000 };
|
|
188
|
-
if (runId === undefined) {
|
|
189
|
-
process.stdout.write(`${renderRunListLines(store.listRuns(), 0, size).join("\n")}\n`);
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
const run = store.readRun(runId, { includeTrace: true });
|
|
193
|
-
if (run === null) throw new Error(`Run not found: ${runId}`);
|
|
194
|
-
process.stdout.write(`${renderRunDetailLines(run, size).join("\n")}\n`);
|
|
195
|
-
} finally {
|
|
196
|
-
store.close();
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function printControllers(databasePath: string): void {
|
|
201
|
-
const store = openControllerStore(databasePath);
|
|
202
|
-
if (store === undefined) {
|
|
203
|
-
process.stdout.write("No controller resources found.\n");
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
try {
|
|
207
|
-
const resources = store.listResources();
|
|
208
|
-
if (resources.length === 0) {
|
|
209
|
-
process.stdout.write("No controller resources found.\n");
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
for (const resource of resources) {
|
|
213
|
-
const condition =
|
|
214
|
-
resource.status.conditions.find((item) => item.type === "Ready") ??
|
|
215
|
-
resource.status.conditions[0];
|
|
216
|
-
const conditionText =
|
|
217
|
-
condition === undefined
|
|
218
|
-
? "unknown"
|
|
219
|
-
: `${String(condition.status)}:${sanitizeText(condition.reason)}`;
|
|
220
|
-
process.stdout.write(
|
|
221
|
-
`${sanitizeText(resource.metadata.controller)} ${sanitizeText(resource.metadata.key)} generation=${resource.metadata.generation} ready=${conditionText}\n`,
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
} finally {
|
|
225
|
-
store.close();
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function printController(databasePath: string, controller: string, key: string): void {
|
|
230
|
-
const store = openControllerStore(databasePath);
|
|
231
|
-
if (store === undefined) throw new Error("Controller state database not found");
|
|
232
|
-
try {
|
|
233
|
-
const resource = store.getResource({ controller, key });
|
|
234
|
-
if (resource === undefined)
|
|
235
|
-
throw new Error(`Controller resource not found: ${controller}/${key}`);
|
|
236
|
-
process.stdout.write(
|
|
237
|
-
`${JSON.stringify(
|
|
238
|
-
{
|
|
239
|
-
resource,
|
|
240
|
-
effects: store.listEffects(resource.metadata.uid),
|
|
241
|
-
workflows: store.listWorkflows(resource.metadata.uid),
|
|
242
|
-
events: store.listEvents({ controller, key, limit: 50 }),
|
|
243
|
-
},
|
|
244
|
-
null,
|
|
245
|
-
2,
|
|
246
|
-
)}\n`,
|
|
247
|
-
);
|
|
248
|
-
} finally {
|
|
249
|
-
store.close();
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
177
|
export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
254
178
|
let args: CliArgs;
|
|
255
179
|
try {
|
|
256
180
|
args = parseCliArgs(argv);
|
|
257
181
|
} catch (error) {
|
|
258
|
-
process.stderr.write(`${
|
|
182
|
+
process.stderr.write(`${errorMessage(error)}\n\n${USAGE}`);
|
|
259
183
|
return 2;
|
|
260
184
|
}
|
|
261
185
|
if (args.command === "help") {
|
|
@@ -263,24 +187,7 @@ export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
|
263
187
|
return 0;
|
|
264
188
|
}
|
|
265
189
|
|
|
266
|
-
const databasePath = workflowStateDatabasePath();
|
|
267
190
|
try {
|
|
268
|
-
if (args.command === "runs") {
|
|
269
|
-
printRuns(databasePath);
|
|
270
|
-
return 0;
|
|
271
|
-
}
|
|
272
|
-
if (args.command === "controllers") {
|
|
273
|
-
printControllers(databasePath);
|
|
274
|
-
return 0;
|
|
275
|
-
}
|
|
276
|
-
if (args.command === "controller") {
|
|
277
|
-
printController(databasePath, args.controllerName as string, args.resourceKey as string);
|
|
278
|
-
return 0;
|
|
279
|
-
}
|
|
280
|
-
if (args.command === "state") {
|
|
281
|
-
await runStateCommand(databasePath, args);
|
|
282
|
-
return 0;
|
|
283
|
-
}
|
|
284
191
|
if (args.command === "host") {
|
|
285
192
|
return await runHost(args.hostAction as "start" | "status" | "stop" | "run", args.piArgs);
|
|
286
193
|
}
|
|
@@ -289,65 +196,106 @@ export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
|
289
196
|
process.stdout.write(args.json ? `${JSON.stringify(result)}\n` : `${result.message}\n`);
|
|
290
197
|
return 0;
|
|
291
198
|
}
|
|
292
|
-
if (
|
|
293
|
-
|
|
294
|
-
|
|
199
|
+
if (
|
|
200
|
+
args.command === "state" &&
|
|
201
|
+
args.stateAction === "verify" &&
|
|
202
|
+
args.verifyBackup !== undefined
|
|
203
|
+
) {
|
|
204
|
+
verifyInactiveBackup(args.verifyBackup);
|
|
205
|
+
process.stdout.write(`Inactive SQLite backup is valid: ${path.resolve(args.verifyBackup)}\n`);
|
|
295
206
|
return 0;
|
|
296
207
|
}
|
|
208
|
+
|
|
209
|
+
if (
|
|
210
|
+
args.command === "state" &&
|
|
211
|
+
args.stateAction === "prune" &&
|
|
212
|
+
args.backupDestination !== undefined &&
|
|
213
|
+
!path.isAbsolute(args.backupDestination)
|
|
214
|
+
) {
|
|
215
|
+
throw new Error("state prune backup path must be absolute");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const client = new WorkflowClient({ clientId: CLI_CLIENT_ID });
|
|
219
|
+
try {
|
|
220
|
+
await client.ensureRunning();
|
|
221
|
+
if (args.command === "runs") {
|
|
222
|
+
const runs = await firstRunsView(client);
|
|
223
|
+
printJsonLines(runs);
|
|
224
|
+
return 0;
|
|
225
|
+
}
|
|
226
|
+
if (args.command === "controllers" || args.command === "controller") {
|
|
227
|
+
const response = await client.request({
|
|
228
|
+
operation: args.command === "controllers" ? "controller.list" : "controller.get",
|
|
229
|
+
payload: {
|
|
230
|
+
projectPath: process.cwd(),
|
|
231
|
+
...(args.command === "controller"
|
|
232
|
+
? { controller: args.controllerName as string, key: args.resourceKey as string }
|
|
233
|
+
: {}),
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
requireAccepted(response);
|
|
237
|
+
process.stdout.write(`${JSON.stringify(response.receipt ?? null, null, 2)}\n`);
|
|
238
|
+
return 0;
|
|
239
|
+
}
|
|
240
|
+
if (args.command === "state") {
|
|
241
|
+
await runStateCommand(client, args);
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
if (args.command === "view") {
|
|
245
|
+
if (args.once || !process.stdout.isTTY) {
|
|
246
|
+
const view =
|
|
247
|
+
args.runId === undefined
|
|
248
|
+
? await firstRunsView(client)
|
|
249
|
+
: await firstRunView(client, args.runId);
|
|
250
|
+
process.stdout.write(
|
|
251
|
+
`${renderClientView(view, process.stdout.columns ?? 100, Number.MAX_SAFE_INTEGER, 0, 0).join("\n")}\n`,
|
|
252
|
+
);
|
|
253
|
+
} else {
|
|
254
|
+
await runViewer({ client, runId: args.runId });
|
|
255
|
+
}
|
|
256
|
+
return 0;
|
|
257
|
+
}
|
|
258
|
+
} finally {
|
|
259
|
+
await client.close();
|
|
260
|
+
}
|
|
297
261
|
process.stderr.write(`Unknown command: ${args.command}\n\n${USAGE}`);
|
|
298
262
|
return 2;
|
|
299
263
|
} catch (error) {
|
|
300
|
-
process.stderr.write(`${
|
|
264
|
+
process.stderr.write(`${errorMessage(error)}\n`);
|
|
301
265
|
return 1;
|
|
302
266
|
}
|
|
303
267
|
}
|
|
304
268
|
|
|
305
|
-
async function runStateCommand(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
(SELECT count(*) FROM runs) AS runs,
|
|
336
|
-
(SELECT count(*) FROM controller_resources) AS controllers,
|
|
337
|
-
(SELECT count(*) FROM human_decisions) AS decisions,
|
|
338
|
-
(SELECT count(*) FROM workflow_settings) AS settingsScopes,
|
|
339
|
-
(SELECT count(*) FROM workflow_follow_ups WHERE status IN ('queued', 'pending_presentation', 'ready')) AS pendingFollowUps,
|
|
340
|
-
(SELECT count(*) FROM effects WHERE status IN ('pending', 'applying', 'ambiguous')) AS unsettledEffects,
|
|
341
|
-
(SELECT count(*) FROM leases
|
|
342
|
-
WHERE owner_id IS NOT NULL AND expires_at > ?) AS activeLeases`,
|
|
343
|
-
)
|
|
344
|
-
.get(Date.now());
|
|
345
|
-
process.stdout.write(
|
|
346
|
-
`Database: ${databasePath}\nSize: ${fs.statSync(databasePath).size} bytes\n${JSON.stringify(counts)}\n`,
|
|
347
|
-
);
|
|
348
|
-
} finally {
|
|
349
|
-
state.close();
|
|
350
|
-
}
|
|
269
|
+
async function runStateCommand(client: WorkflowClient, args: CliArgs): Promise<void> {
|
|
270
|
+
const operation =
|
|
271
|
+
args.stateAction === "status"
|
|
272
|
+
? "state.status"
|
|
273
|
+
: args.stateAction === "verify"
|
|
274
|
+
? "state.verify"
|
|
275
|
+
: args.stateAction === "backup"
|
|
276
|
+
? "state.backup"
|
|
277
|
+
: "state.prune";
|
|
278
|
+
const payload: JsonValue =
|
|
279
|
+
args.stateAction === "backup"
|
|
280
|
+
? { destination: path.resolve(args.backupDestination as string) }
|
|
281
|
+
: args.stateAction === "prune"
|
|
282
|
+
? {
|
|
283
|
+
before: args.pruneBefore as string,
|
|
284
|
+
apply: args.pruneApply === true,
|
|
285
|
+
...(args.backupDestination === undefined
|
|
286
|
+
? {}
|
|
287
|
+
: { backupPath: path.resolve(args.backupDestination) }),
|
|
288
|
+
}
|
|
289
|
+
: {};
|
|
290
|
+
const durable =
|
|
291
|
+
args.stateAction === "backup" || (args.stateAction === "prune" && args.pruneApply === true);
|
|
292
|
+
const idempotencyKey = durable ? `state-${randomUUID()}` : undefined;
|
|
293
|
+
const response =
|
|
294
|
+
idempotencyKey === undefined
|
|
295
|
+
? await client.request({ operation, payload })
|
|
296
|
+
: await client.requestDurable({ operation, payload, idempotencyKey });
|
|
297
|
+
requireAccepted(response);
|
|
298
|
+
process.stdout.write(`${JSON.stringify(response.receipt ?? {})}\n`);
|
|
351
299
|
}
|
|
352
300
|
|
|
353
301
|
async function runHost(
|
|
@@ -368,22 +316,56 @@ async function runHost(
|
|
|
368
316
|
});
|
|
369
317
|
return 0;
|
|
370
318
|
}
|
|
371
|
-
const client = new
|
|
372
|
-
|
|
373
|
-
const response =
|
|
319
|
+
const client = new WorkflowClient({ clientId: CLI_CLIENT_ID });
|
|
320
|
+
try {
|
|
321
|
+
const response =
|
|
322
|
+
action === "start"
|
|
323
|
+
? await client.ensureRunning()
|
|
324
|
+
: await client.request({ operation: action === "status" ? "host.status" : "host.stop" });
|
|
374
325
|
process.stdout.write(`${JSON.stringify(response.receipt ?? {})}\n`);
|
|
375
326
|
return response.outcome === "accepted" || response.outcome === "adopted" ? 0 : 1;
|
|
327
|
+
} finally {
|
|
328
|
+
await client.close();
|
|
376
329
|
}
|
|
377
|
-
|
|
378
|
-
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function firstRunsView(client: WorkflowClient): Promise<JsonValue> {
|
|
333
|
+
return await firstSubscriptionEvent((listener) => client.watchRuns(listener));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export async function firstRunView(client: WorkflowClient, runId: string): Promise<JsonValue> {
|
|
337
|
+
const view = await client.getRun(runId);
|
|
338
|
+
if (view === null) throw new Error(`Workflow run not found: ${runId}`);
|
|
339
|
+
return await materializeRunView(client, view);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function firstSubscriptionEvent(
|
|
343
|
+
subscribe: (listener: (event: { payload: JsonValue }) => void) => Promise<() => Promise<void>>,
|
|
344
|
+
): Promise<JsonValue> {
|
|
345
|
+
let resolveEvent!: (value: JsonValue) => void;
|
|
346
|
+
const event = new Promise<JsonValue>((resolve) => {
|
|
347
|
+
resolveEvent = resolve;
|
|
379
348
|
});
|
|
380
|
-
|
|
381
|
-
|
|
349
|
+
const unsubscribe = await subscribe((received) => resolveEvent(received.payload));
|
|
350
|
+
try {
|
|
351
|
+
return await event;
|
|
352
|
+
} finally {
|
|
353
|
+
await unsubscribe();
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function printJsonLines(value: JsonValue): void {
|
|
358
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
359
|
+
process.stdout.write("No workflow runs found.\n");
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
for (const item of value) process.stdout.write(`${JSON.stringify(item)}\n`);
|
|
382
363
|
}
|
|
383
364
|
|
|
384
|
-
function
|
|
385
|
-
if (
|
|
386
|
-
|
|
365
|
+
function requireAccepted(response: { outcome: string; error?: string }): void {
|
|
366
|
+
if (response.outcome !== "accepted" && response.outcome !== "adopted") {
|
|
367
|
+
throw new Error(response.error ?? `Workflow host returned ${response.outcome}`);
|
|
368
|
+
}
|
|
387
369
|
}
|
|
388
370
|
|
|
389
371
|
function packageRoot(): string {
|
|
@@ -396,6 +378,10 @@ function requiredValue(args: string[], option: string): string {
|
|
|
396
378
|
return value;
|
|
397
379
|
}
|
|
398
380
|
|
|
381
|
+
function errorMessage(error: unknown): string {
|
|
382
|
+
return error instanceof Error ? error.message : String(error);
|
|
383
|
+
}
|
|
384
|
+
|
|
399
385
|
const entryPath = process.argv[1];
|
|
400
386
|
const resolvedEntry = entryPath === undefined ? undefined : realpathSyncSafe(entryPath);
|
|
401
387
|
if (resolvedEntry !== undefined && import.meta.url === pathToFileURL(resolvedEntry).href) {
|
package/src/viewer/render.ts
CHANGED
|
@@ -300,11 +300,7 @@ export function renderRunDetailLines(
|
|
|
300
300
|
|
|
301
301
|
if ((bundle.followUpQueue?.followUps.length ?? 0) > 0) {
|
|
302
302
|
lines.push("");
|
|
303
|
-
lines.push(
|
|
304
|
-
ansi.bold(
|
|
305
|
-
`follow-ups · presentation ${bundle.followUpQueue?.presentationState ?? "unknown"}`,
|
|
306
|
-
),
|
|
307
|
-
);
|
|
303
|
+
lines.push(ansi.bold("follow-ups"));
|
|
308
304
|
for (const followUp of bundle.followUpQueue?.followUps ?? []) {
|
|
309
305
|
lines.push(
|
|
310
306
|
fitWidth(
|