@osolmaz/pi-workflows 0.13.2 → 0.13.4
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 +5 -3
- package/dist/builtins/autodoc.workflow.d.ts +5 -5
- package/dist/builtins/autoimplement.workflow.d.ts +77 -73
- package/dist/builtins/autoimplement.workflow.js +47 -8
- package/dist/builtins/autoimplement.workflow.js.map +1 -1
- package/dist/builtins/autoplan.workflow.d.ts +8 -8
- package/dist/builtins/autoplan.workflow.js +44 -35
- package/dist/builtins/autoplan.workflow.js.map +1 -1
- package/dist/builtins/catalog.js +2 -2
- package/dist/builtins/change-verification.workflow.d.ts +1 -1
- package/dist/builtins/plain-summary.workflow.d.ts +1 -1
- package/dist/builtins/plan-approval.workflow.d.ts +1 -1
- package/dist/builtins/plan-change.workflow.d.ts +29 -29
- package/dist/builtins/sanity-check.workflow.d.ts +3 -3
- package/dist/builtins/workspace-preparation.workflow.d.ts +1 -1
- package/dist/controllers/index.d.ts +2 -2
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/sqlite.js +85 -23
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/controllers/types.d.ts +34 -0
- package/dist/controllers/workflow-engine-scheduler.d.ts +5 -1
- package/dist/controllers/workflow-engine-scheduler.js +83 -1
- package/dist/controllers/workflow-engine-scheduler.js.map +1 -1
- package/dist/controllers/workflows.d.ts +8 -1
- package/dist/controllers/workflows.js +36 -0
- package/dist/controllers/workflows.js.map +1 -1
- package/dist/extension/controller-host.d.ts +2 -1
- package/dist/extension/controller-host.js +1 -1
- package/dist/extension/controller-host.js.map +1 -1
- package/dist/extension/follow-up-coordinator.d.ts +27 -0
- package/dist/extension/follow-up-coordinator.js +131 -0
- package/dist/extension/follow-up-coordinator.js.map +1 -0
- package/dist/extension/index.d.ts +9 -0
- package/dist/extension/index.js +460 -75
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/session-events.d.ts +2 -3
- package/dist/extension/session-events.js +11 -10
- package/dist/extension/session-events.js.map +1 -1
- package/dist/extension/widget.js +9 -0
- package/dist/extension/widget.js.map +1 -1
- package/dist/state/database.d.ts +2 -1
- package/dist/state/database.js +11 -13
- package/dist/state/database.js.map +1 -1
- package/dist/state/json.js +6 -1
- package/dist/state/json.js.map +1 -1
- package/dist/state/mutation.d.ts +1 -1
- package/dist/state/mutation.js.map +1 -1
- package/dist/state/prune.d.ts +18 -0
- package/dist/state/prune.js +373 -0
- package/dist/state/prune.js.map +1 -0
- package/dist/state/schema.d.ts +1 -1
- package/dist/state/schema.js +116 -18
- package/dist/state/schema.js.map +1 -1
- package/dist/viewer/cli.d.ts +3 -1
- package/dist/viewer/cli.js +55 -4
- package/dist/viewer/cli.js.map +1 -1
- package/dist/viewer/render.js +19 -1
- package/dist/viewer/render.js.map +1 -1
- package/dist/workflows/composition.d.ts +2 -0
- package/dist/workflows/composition.js +15 -0
- package/dist/workflows/composition.js.map +1 -1
- package/dist/workflows/definition.d.ts +6 -3
- package/dist/workflows/definition.js +3 -0
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.d.ts +6 -1
- package/dist/workflows/engine.js +301 -35
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/human-decision.d.ts +1 -0
- package/dist/workflows/human-decision.js +25 -9
- package/dist/workflows/human-decision.js.map +1 -1
- package/dist/workflows/index.d.ts +3 -1
- package/dist/workflows/index.js +2 -0
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/json-patch.d.ts +47 -0
- package/dist/workflows/json-patch.js +298 -0
- package/dist/workflows/json-patch.js.map +1 -0
- package/dist/workflows/schema.js +29 -1
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/settings.d.ts +128 -0
- package/dist/workflows/settings.js +199 -0
- package/dist/workflows/settings.js.map +1 -0
- package/dist/workflows/store.d.ts +75 -2
- package/dist/workflows/store.js +1349 -89
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/tool-input.d.ts +22 -0
- package/dist/workflows/tool-input.js +43 -0
- package/dist/workflows/tool-input.js.map +1 -1
- package/dist/workflows/types.d.ts +40 -5
- package/docs/2026-08-25-workflow-follow-ups.md +132 -0
- package/docs/2026-08-25-workflow-settings.md +169 -0
- package/docs/DEFERRED_TURNS.md +6 -0
- package/docs/DESIGN_PHILOSOPHY.md +2 -0
- package/docs/SQLITE_STATE.md +31 -18
- package/docs/WORKFLOW_COMPOSITION.md +16 -1
- package/docs/WORKFLOW_STEP_MESSAGES.md +4 -2
- package/docs/plans/2026-08-25-live-workflow-settings-plan.md +532 -0
- package/docs/plans/2026-08-25-workflow-run-storage-plan.md +67 -0
- package/docs/tui-viewer.md +7 -6
- package/docs/workflows.md +22 -1
- package/examples/workflows/live-settings.workflow.ts +93 -0
- package/herdr-plugin.toml +1 -1
- package/package.json +1 -1
- package/skills/autoplan/SKILL.md +6 -6
- package/src/builtins/autoimplement.workflow.ts +56 -8
- package/src/builtins/autoplan.workflow.ts +45 -37
- package/src/builtins/catalog.ts +2 -2
- package/src/controllers/index.ts +6 -0
- package/src/controllers/sqlite.ts +129 -35
- package/src/controllers/types.ts +40 -0
- package/src/controllers/workflow-engine-scheduler.ts +103 -1
- package/src/controllers/workflows.ts +68 -0
- package/src/extension/controller-host.ts +6 -1
- package/src/extension/follow-up-coordinator.ts +151 -0
- package/src/extension/index.ts +538 -88
- package/src/extension/session-events.ts +15 -13
- package/src/extension/widget.ts +8 -0
- package/src/state/database.ts +12 -12
- package/src/state/json.ts +6 -1
- package/src/state/mutation.ts +4 -1
- package/src/state/prune.ts +502 -0
- package/src/state/schema.ts +116 -18
- package/src/viewer/cli.ts +52 -5
- package/src/viewer/render.ts +43 -1
- package/src/workflows/composition.ts +19 -0
- package/src/workflows/definition.ts +19 -5
- package/src/workflows/engine.ts +363 -35
- package/src/workflows/human-decision.ts +41 -11
- package/src/workflows/index.ts +43 -0
- package/src/workflows/json-patch.ts +375 -0
- package/src/workflows/schema.ts +31 -1
- package/src/workflows/settings.ts +430 -0
- package/src/workflows/store.ts +1977 -184
- package/src/workflows/tool-input.ts +58 -0
- package/src/workflows/types.ts +43 -4
package/src/state/schema.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
export const STATE_APPLICATION_ID = 0x50495746;
|
|
4
4
|
export const STATE_SCHEMA_NAME = "pi-workflows-state";
|
|
5
5
|
export const STATE_SCHEMA_VERSION = 1;
|
|
6
|
-
export const STATE_APP_VERSION = "0.
|
|
6
|
+
export const STATE_APP_VERSION = "0.13.3";
|
|
7
7
|
|
|
8
8
|
export const STATE_SCHEMA_SQL = String.raw`
|
|
9
9
|
CREATE TABLE schema_meta (
|
|
@@ -34,7 +34,8 @@ CREATE TABLE blobs (
|
|
|
34
34
|
CREATE TABLE resources (
|
|
35
35
|
resource_id TEXT PRIMARY KEY,
|
|
36
36
|
resource_type TEXT NOT NULL CHECK (resource_type IN (
|
|
37
|
-
'run', 'session', 'decision', 'controller', 'effect', 'channel', 'notification', 'turn_intent'
|
|
37
|
+
'run', 'session', 'decision', 'controller', 'effect', 'channel', 'notification', 'turn_intent',
|
|
38
|
+
'settings', 'follow_up_queue', 'follow_up'
|
|
38
39
|
)),
|
|
39
40
|
aggregate_key TEXT NOT NULL,
|
|
40
41
|
revision INTEGER NOT NULL DEFAULT 0 CHECK (revision >= 0),
|
|
@@ -94,11 +95,7 @@ CREATE TABLE runs (
|
|
|
94
95
|
parent_run_id TEXT REFERENCES runs(run_id),
|
|
95
96
|
definition_digest BLOB NOT NULL REFERENCES workflow_definitions(definition_digest),
|
|
96
97
|
workflow_ref TEXT NOT NULL,
|
|
97
|
-
workflow_source_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
98
98
|
launch_options_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
99
|
-
source_type TEXT NOT NULL CHECK (source_type IN ('builtin', 'file')),
|
|
100
|
-
source_ref TEXT NOT NULL,
|
|
101
|
-
source_revision TEXT NOT NULL,
|
|
102
99
|
title TEXT,
|
|
103
100
|
status TEXT NOT NULL CHECK (status IN (
|
|
104
101
|
'queued', 'running', 'waiting', 'completed', 'failed', 'timed_out', 'cancelled'
|
|
@@ -106,15 +103,8 @@ CREATE TABLE runs (
|
|
|
106
103
|
paused INTEGER NOT NULL DEFAULT 0 CHECK (paused IN (0, 1)),
|
|
107
104
|
status_detail TEXT,
|
|
108
105
|
input_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
109
|
-
workflow_sources_hash BLOB REFERENCES blobs(blob_hash),
|
|
110
|
-
human_decision_hash BLOB REFERENCES blobs(blob_hash),
|
|
111
106
|
final_output_hash BLOB REFERENCES blobs(blob_hash),
|
|
112
107
|
error_hash BLOB REFERENCES blobs(blob_hash),
|
|
113
|
-
carried_step_count INTEGER NOT NULL DEFAULT 0 CHECK (carried_step_count >= 0),
|
|
114
|
-
current_node TEXT,
|
|
115
|
-
current_attempt_id TEXT,
|
|
116
|
-
current_node_started_at INTEGER,
|
|
117
|
-
waiting_on TEXT,
|
|
118
108
|
created_at INTEGER NOT NULL,
|
|
119
109
|
updated_at INTEGER NOT NULL,
|
|
120
110
|
finished_at INTEGER,
|
|
@@ -125,6 +115,15 @@ CREATE INDEX runs_project_idx ON runs(project_id, created_at DESC);
|
|
|
125
115
|
CREATE INDEX runs_status_idx ON runs(status, updated_at DESC);
|
|
126
116
|
CREATE INDEX runs_parent_idx ON runs(parent_run_id);
|
|
127
117
|
|
|
118
|
+
CREATE TABLE run_sources (
|
|
119
|
+
run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
120
|
+
mount_path TEXT NOT NULL,
|
|
121
|
+
source_type TEXT NOT NULL CHECK (source_type IN ('builtin', 'file')),
|
|
122
|
+
source_ref TEXT NOT NULL,
|
|
123
|
+
source_revision TEXT NOT NULL,
|
|
124
|
+
PRIMARY KEY (run_id, mount_path)
|
|
125
|
+
) STRICT;
|
|
126
|
+
|
|
128
127
|
CREATE TABLE run_bindings (
|
|
129
128
|
run_id TEXT PRIMARY KEY REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
130
129
|
origin_session_id TEXT NOT NULL,
|
|
@@ -134,6 +133,92 @@ CREATE TABLE run_bindings (
|
|
|
134
133
|
|
|
135
134
|
CREATE INDEX run_bindings_session_idx ON run_bindings(origin_session_id, created_at DESC);
|
|
136
135
|
|
|
136
|
+
CREATE TABLE workflow_settings (
|
|
137
|
+
scope_id TEXT PRIMARY KEY,
|
|
138
|
+
resource_id TEXT NOT NULL UNIQUE REFERENCES resources(resource_id) ON DELETE CASCADE,
|
|
139
|
+
origin_run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
140
|
+
active_run_id TEXT NOT NULL REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
141
|
+
mount_path TEXT NOT NULL,
|
|
142
|
+
invocation INTEGER NOT NULL CHECK (invocation > 0),
|
|
143
|
+
initial_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
144
|
+
current_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
145
|
+
created_at INTEGER NOT NULL,
|
|
146
|
+
updated_at INTEGER NOT NULL,
|
|
147
|
+
UNIQUE (origin_run_id, mount_path, invocation)
|
|
148
|
+
) STRICT;
|
|
149
|
+
|
|
150
|
+
CREATE INDEX workflow_settings_active_run_idx ON workflow_settings(active_run_id, mount_path, invocation);
|
|
151
|
+
|
|
152
|
+
CREATE TABLE workflow_setting_changes (
|
|
153
|
+
change_id TEXT PRIMARY KEY,
|
|
154
|
+
scope_id TEXT NOT NULL REFERENCES workflow_settings(scope_id) ON DELETE CASCADE,
|
|
155
|
+
request_id TEXT NOT NULL,
|
|
156
|
+
change_number INTEGER NOT NULL CHECK (change_number > 0),
|
|
157
|
+
actor_type TEXT NOT NULL CHECK (actor_type IN (
|
|
158
|
+
'session', 'host', 'controller', 'channel', 'human', 'policy', 'control', 'system'
|
|
159
|
+
)),
|
|
160
|
+
actor_id TEXT,
|
|
161
|
+
source_type TEXT NOT NULL,
|
|
162
|
+
patch_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
163
|
+
before_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
164
|
+
after_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
165
|
+
accepted_at INTEGER NOT NULL,
|
|
166
|
+
UNIQUE (scope_id, request_id),
|
|
167
|
+
UNIQUE (scope_id, change_number)
|
|
168
|
+
) STRICT;
|
|
169
|
+
|
|
170
|
+
CREATE TABLE workflow_follow_up_queues (
|
|
171
|
+
run_id TEXT PRIMARY KEY REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
172
|
+
resource_id TEXT NOT NULL UNIQUE REFERENCES resources(resource_id) ON DELETE CASCADE,
|
|
173
|
+
origin_session_id TEXT,
|
|
174
|
+
presentation_state TEXT NOT NULL DEFAULT 'none' CHECK (presentation_state IN (
|
|
175
|
+
'none', 'not-needed', 'pending', 'settled', 'unavailable'
|
|
176
|
+
)),
|
|
177
|
+
presentation_entry_id TEXT,
|
|
178
|
+
presentation_assistant_entry_id TEXT,
|
|
179
|
+
presentation_reason_hash BLOB REFERENCES blobs(blob_hash),
|
|
180
|
+
presentation_updated_at INTEGER,
|
|
181
|
+
created_at INTEGER NOT NULL,
|
|
182
|
+
updated_at INTEGER NOT NULL,
|
|
183
|
+
CHECK (
|
|
184
|
+
(presentation_state IN ('none', 'not-needed', 'pending') AND presentation_assistant_entry_id IS NULL AND presentation_reason_hash IS NULL)
|
|
185
|
+
OR
|
|
186
|
+
(presentation_state = 'unavailable' AND presentation_assistant_entry_id IS NULL AND presentation_reason_hash IS NOT NULL)
|
|
187
|
+
OR
|
|
188
|
+
(presentation_state = 'settled' AND presentation_entry_id IS NOT NULL AND presentation_assistant_entry_id IS NOT NULL AND presentation_reason_hash IS NULL)
|
|
189
|
+
)
|
|
190
|
+
) STRICT;
|
|
191
|
+
|
|
192
|
+
CREATE TABLE workflow_follow_ups (
|
|
193
|
+
follow_up_id TEXT PRIMARY KEY,
|
|
194
|
+
resource_id TEXT NOT NULL UNIQUE REFERENCES resources(resource_id) ON DELETE CASCADE,
|
|
195
|
+
queue_resource_id TEXT NOT NULL REFERENCES resources(resource_id),
|
|
196
|
+
run_id TEXT NOT NULL REFERENCES workflow_follow_up_queues(run_id) ON DELETE CASCADE,
|
|
197
|
+
request_id TEXT NOT NULL,
|
|
198
|
+
order_number INTEGER NOT NULL CHECK (order_number > 0),
|
|
199
|
+
target_session_id TEXT NOT NULL,
|
|
200
|
+
actor_type TEXT NOT NULL CHECK (actor_type IN (
|
|
201
|
+
'session', 'host', 'controller', 'channel', 'human', 'policy', 'control', 'system'
|
|
202
|
+
)),
|
|
203
|
+
actor_id TEXT,
|
|
204
|
+
source_type TEXT NOT NULL,
|
|
205
|
+
prompt_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
206
|
+
status TEXT NOT NULL CHECK (status IN (
|
|
207
|
+
'queued', 'pending_presentation', 'ready', 'sent', 'removed', 'cancelled'
|
|
208
|
+
)),
|
|
209
|
+
session_entry_id TEXT,
|
|
210
|
+
reason_hash BLOB REFERENCES blobs(blob_hash),
|
|
211
|
+
created_at INTEGER NOT NULL,
|
|
212
|
+
updated_at INTEGER NOT NULL,
|
|
213
|
+
sent_at INTEGER,
|
|
214
|
+
UNIQUE (run_id, request_id),
|
|
215
|
+
UNIQUE (run_id, order_number),
|
|
216
|
+
CHECK ((status = 'sent') = (session_entry_id IS NOT NULL AND sent_at IS NOT NULL)),
|
|
217
|
+
CHECK ((status IN ('removed', 'cancelled')) = (reason_hash IS NOT NULL))
|
|
218
|
+
) STRICT;
|
|
219
|
+
|
|
220
|
+
CREATE INDEX workflow_follow_ups_delivery_idx ON workflow_follow_ups(target_session_id, status, order_number);
|
|
221
|
+
|
|
137
222
|
CREATE TABLE run_queue (
|
|
138
223
|
run_id TEXT PRIMARY KEY REFERENCES runs(run_id) ON DELETE CASCADE,
|
|
139
224
|
status TEXT NOT NULL CHECK (status IN (
|
|
@@ -162,14 +247,16 @@ CREATE TABLE node_attempts (
|
|
|
162
247
|
attempt_number INTEGER NOT NULL CHECK (attempt_number > 0),
|
|
163
248
|
node_type TEXT NOT NULL,
|
|
164
249
|
status TEXT NOT NULL CHECK (status IN (
|
|
165
|
-
'pending', 'running', 'waiting', '
|
|
250
|
+
'pending', 'running', 'waiting', 'interrupted',
|
|
251
|
+
'completed', 'failed', 'timed_out', 'cancelled'
|
|
166
252
|
)),
|
|
167
|
-
input_hash BLOB REFERENCES blobs(blob_hash),
|
|
168
|
-
contract_hash BLOB REFERENCES blobs(blob_hash),
|
|
169
253
|
prompt_hash BLOB REFERENCES blobs(blob_hash),
|
|
170
254
|
output_hash BLOB REFERENCES blobs(blob_hash),
|
|
171
|
-
|
|
255
|
+
receipt_hash BLOB REFERENCES blobs(blob_hash),
|
|
172
256
|
error_hash BLOB REFERENCES blobs(blob_hash),
|
|
257
|
+
settings_scope_id TEXT REFERENCES workflow_settings(scope_id),
|
|
258
|
+
settings_change_number INTEGER CHECK (settings_change_number IS NULL OR settings_change_number >= 0),
|
|
259
|
+
settings_hash BLOB REFERENCES blobs(blob_hash),
|
|
173
260
|
started_at INTEGER,
|
|
174
261
|
deadline_at INTEGER,
|
|
175
262
|
finished_at INTEGER,
|
|
@@ -235,6 +322,18 @@ CREATE TABLE session_entries (
|
|
|
235
322
|
UNIQUE (segment_id, entry_id)
|
|
236
323
|
) STRICT;
|
|
237
324
|
|
|
325
|
+
CREATE TABLE attempt_entries (
|
|
326
|
+
attempt_id TEXT NOT NULL REFERENCES node_attempts(attempt_id) ON DELETE CASCADE,
|
|
327
|
+
role TEXT NOT NULL CHECK (role IN ('prompt', 'response', 'first', 'last')),
|
|
328
|
+
segment_id TEXT NOT NULL,
|
|
329
|
+
entry_id TEXT NOT NULL,
|
|
330
|
+
PRIMARY KEY (attempt_id, role),
|
|
331
|
+
FOREIGN KEY (segment_id, entry_id)
|
|
332
|
+
REFERENCES session_entries(segment_id, entry_id) ON DELETE CASCADE
|
|
333
|
+
) STRICT;
|
|
334
|
+
|
|
335
|
+
CREATE INDEX attempt_entries_entry_idx ON attempt_entries(segment_id, entry_id);
|
|
336
|
+
|
|
238
337
|
CREATE TABLE session_events (
|
|
239
338
|
segment_id TEXT NOT NULL REFERENCES session_segments(segment_id) ON DELETE CASCADE,
|
|
240
339
|
event_seq INTEGER NOT NULL CHECK (event_seq > 0),
|
|
@@ -306,7 +405,6 @@ CREATE TABLE continuations (
|
|
|
306
405
|
decision_id TEXT PRIMARY KEY REFERENCES human_decisions(decision_id) ON DELETE CASCADE,
|
|
307
406
|
parent_run_id TEXT NOT NULL UNIQUE REFERENCES runs(run_id),
|
|
308
407
|
continuation_run_id TEXT NOT NULL UNIQUE REFERENCES runs(run_id),
|
|
309
|
-
response_hash BLOB NOT NULL REFERENCES blobs(blob_hash),
|
|
310
408
|
created_at INTEGER NOT NULL
|
|
311
409
|
) STRICT;
|
|
312
410
|
|
package/src/viewer/cli.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { SqliteControllerStore } from "../controllers/sqlite.js";
|
|
|
6
6
|
import { syncHerdrPlugin } from "../herdr/setup.js";
|
|
7
7
|
import { sanitizeText } from "../render/ansi.js";
|
|
8
8
|
import { StateDatabase } from "../state/database.js";
|
|
9
|
+
import { pruneState } from "../state/prune.js";
|
|
9
10
|
import { WorkflowRunStore, workflowStateDatabasePath } from "../workflows/store.js";
|
|
10
11
|
import {
|
|
11
12
|
formatDuration,
|
|
@@ -25,6 +26,8 @@ Usage:
|
|
|
25
26
|
pi-workflows controller <controller> <key>
|
|
26
27
|
pi-workflows state status|verify
|
|
27
28
|
pi-workflows state backup <destination>
|
|
29
|
+
pi-workflows state prune --before <timestamp> --dry-run
|
|
30
|
+
pi-workflows state prune --before <timestamp> --backup <absolute-path> --apply
|
|
28
31
|
pi-workflows host [--project <dir>] [-- <extra pi args>]
|
|
29
32
|
pi-workflows herdr sync [--json]
|
|
30
33
|
pi-workflows herdr setup [--json]
|
|
@@ -38,8 +41,10 @@ export type CliArgs = {
|
|
|
38
41
|
controllerName?: string;
|
|
39
42
|
resourceKey?: string;
|
|
40
43
|
herdrAction?: string;
|
|
41
|
-
stateAction?: "status" | "verify" | "backup";
|
|
44
|
+
stateAction?: "status" | "verify" | "backup" | "prune";
|
|
42
45
|
backupDestination?: string;
|
|
46
|
+
pruneBefore?: string;
|
|
47
|
+
pruneApply?: boolean;
|
|
43
48
|
once: boolean;
|
|
44
49
|
json: boolean;
|
|
45
50
|
project?: string | undefined;
|
|
@@ -52,12 +57,20 @@ export function parseCliArgs(argv: string[]): CliArgs {
|
|
|
52
57
|
let once = false;
|
|
53
58
|
let json = false;
|
|
54
59
|
let project: string | undefined;
|
|
60
|
+
let pruneBefore: string | undefined;
|
|
61
|
+
let backupDestination: string | undefined;
|
|
62
|
+
let pruneApply = false;
|
|
63
|
+
let pruneDryRun = false;
|
|
55
64
|
const positionals: string[] = [];
|
|
56
65
|
const piArgs: string[] = [];
|
|
57
66
|
|
|
58
67
|
while (args.length > 0) {
|
|
59
68
|
const arg = args.shift() as string;
|
|
60
69
|
if (arg === "--project") project = requiredValue(args, "--project");
|
|
70
|
+
else if (arg === "--before") pruneBefore = requiredValue(args, "--before");
|
|
71
|
+
else if (arg === "--backup") backupDestination = requiredValue(args, "--backup");
|
|
72
|
+
else if (arg === "--apply") pruneApply = true;
|
|
73
|
+
else if (arg === "--dry-run") pruneDryRun = true;
|
|
61
74
|
else if (arg === "--once") once = true;
|
|
62
75
|
else if (arg === "--json") json = true;
|
|
63
76
|
else if (arg === "--help" || arg === "-h") return { command: "help", once, json };
|
|
@@ -80,8 +93,30 @@ export function parseCliArgs(argv: string[]): CliArgs {
|
|
|
80
93
|
}
|
|
81
94
|
if (command === "state") {
|
|
82
95
|
const action = positionals[0];
|
|
83
|
-
if (action !== "status" && action !== "verify" && action !== "backup") {
|
|
84
|
-
throw new Error("state requires status, verify, or
|
|
96
|
+
if (action !== "status" && action !== "verify" && action !== "backup" && action !== "prune") {
|
|
97
|
+
throw new Error("state requires status, verify, backup, or prune");
|
|
98
|
+
}
|
|
99
|
+
if (action === "prune") {
|
|
100
|
+
if (positionals.length !== 1) throw new Error("state prune accepts no positional arguments");
|
|
101
|
+
if (pruneBefore === undefined) throw new Error("state prune requires --before <timestamp>");
|
|
102
|
+
if (pruneApply === pruneDryRun) {
|
|
103
|
+
throw new Error("state prune requires exactly one of --dry-run or --apply");
|
|
104
|
+
}
|
|
105
|
+
if (pruneApply && backupDestination === undefined) {
|
|
106
|
+
throw new Error("state prune --apply requires --backup <absolute-path>");
|
|
107
|
+
}
|
|
108
|
+
if (pruneDryRun && backupDestination !== undefined) {
|
|
109
|
+
throw new Error("state prune --dry-run does not accept --backup");
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
command,
|
|
113
|
+
stateAction: action,
|
|
114
|
+
pruneBefore,
|
|
115
|
+
pruneApply,
|
|
116
|
+
...(backupDestination === undefined ? {} : { backupDestination }),
|
|
117
|
+
once,
|
|
118
|
+
json,
|
|
119
|
+
};
|
|
85
120
|
}
|
|
86
121
|
if (action === "backup") {
|
|
87
122
|
if (positionals.length !== 2) throw new Error("state backup requires <destination>");
|
|
@@ -251,6 +286,15 @@ export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
|
251
286
|
}
|
|
252
287
|
|
|
253
288
|
async function runStateCommand(databasePath: string, args: CliArgs): Promise<void> {
|
|
289
|
+
if (args.stateAction === "prune") {
|
|
290
|
+
const report = await pruneState(databasePath, {
|
|
291
|
+
before: args.pruneBefore as string,
|
|
292
|
+
apply: args.pruneApply === true,
|
|
293
|
+
...(args.backupDestination === undefined ? {} : { backupPath: args.backupDestination }),
|
|
294
|
+
});
|
|
295
|
+
process.stdout.write(`${JSON.stringify(report)}\n`);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
254
298
|
if (args.stateAction === "backup") {
|
|
255
299
|
const state = new StateDatabase({ filePath: databasePath });
|
|
256
300
|
try {
|
|
@@ -274,10 +318,13 @@ async function runStateCommand(databasePath: string, args: CliArgs): Promise<voi
|
|
|
274
318
|
(SELECT count(*) FROM runs) AS runs,
|
|
275
319
|
(SELECT count(*) FROM controller_resources) AS controllers,
|
|
276
320
|
(SELECT count(*) FROM human_decisions) AS decisions,
|
|
321
|
+
(SELECT count(*) FROM workflow_settings) AS settingsScopes,
|
|
322
|
+
(SELECT count(*) FROM workflow_follow_ups WHERE status IN ('queued', 'pending_presentation', 'ready')) AS pendingFollowUps,
|
|
277
323
|
(SELECT count(*) FROM effects WHERE status IN ('pending', 'applying', 'ambiguous')) AS unsettledEffects,
|
|
278
|
-
(SELECT count(*) FROM leases
|
|
324
|
+
(SELECT count(*) FROM leases
|
|
325
|
+
WHERE owner_id IS NOT NULL AND expires_at > ?) AS activeLeases`,
|
|
279
326
|
)
|
|
280
|
-
.get();
|
|
327
|
+
.get(Date.now());
|
|
281
328
|
process.stdout.write(
|
|
282
329
|
`Database: ${databasePath}\nSize: ${fs.statSync(databasePath).size} bytes\n${JSON.stringify(counts)}\n`,
|
|
283
330
|
);
|
package/src/viewer/render.ts
CHANGED
|
@@ -102,8 +102,10 @@ function stepLine(
|
|
|
102
102
|
step.error !== undefined
|
|
103
103
|
? ansi.red(previewValue(step.error, 60))
|
|
104
104
|
: ansi.dim(previewValue(step.output, 60));
|
|
105
|
+
const settings =
|
|
106
|
+
step.settingsChangeNumber === undefined ? "" : `, settings ${step.settingsChangeNumber}`;
|
|
105
107
|
return fitWidth(
|
|
106
|
-
` ${marker}${glyph} ${step.nodeId} ${ansi.dim(`(${step.nodeType}, ${formatDuration(durationMs)})`)} ${preview}`,
|
|
108
|
+
` ${marker}${glyph} ${step.nodeId} ${ansi.dim(`(${step.nodeType}, ${formatDuration(durationMs)}${settings})`)} ${preview}`,
|
|
107
109
|
width,
|
|
108
110
|
);
|
|
109
111
|
}
|
|
@@ -181,6 +183,16 @@ function inspectorLines(step: WorkflowStepRecord, width: number): string[] {
|
|
|
181
183
|
for (const raw of (rendered ?? "null").split("\n")) {
|
|
182
184
|
lines.push(fitWidth(` ${sanitizeText(raw)}`, width));
|
|
183
185
|
}
|
|
186
|
+
if (step.settingsScopeId !== undefined) {
|
|
187
|
+
lines.push(
|
|
188
|
+
fitWidth(
|
|
189
|
+
ansi.dim(
|
|
190
|
+
` settings ${step.settingsChangeNumber ?? 0} · ${sanitizeText(step.settingsScopeId)} · ${step.settingsHash?.slice(0, 12) ?? "unknown hash"}`,
|
|
191
|
+
),
|
|
192
|
+
width,
|
|
193
|
+
),
|
|
194
|
+
);
|
|
195
|
+
}
|
|
184
196
|
if (step.action) {
|
|
185
197
|
const receipt = [
|
|
186
198
|
step.action.actionType,
|
|
@@ -273,6 +285,36 @@ export function renderRunDetailLines(
|
|
|
273
285
|
}
|
|
274
286
|
}
|
|
275
287
|
|
|
288
|
+
if ((bundle.settingsScopes?.length ?? 0) > 0) {
|
|
289
|
+
lines.push("");
|
|
290
|
+
lines.push(ansi.bold("workflow settings"));
|
|
291
|
+
for (const scope of bundle.settingsScopes ?? []) {
|
|
292
|
+
lines.push(
|
|
293
|
+
fitWidth(
|
|
294
|
+
` ${sanitizeText(scope.mountPath || "root")} #${scope.invocation} · change ${scope.changeNumber} · ${scope.settingsHash.slice(0, 12)}`,
|
|
295
|
+
size.width,
|
|
296
|
+
),
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if ((bundle.followUpQueue?.followUps.length ?? 0) > 0) {
|
|
302
|
+
lines.push("");
|
|
303
|
+
lines.push(
|
|
304
|
+
ansi.bold(
|
|
305
|
+
`follow-ups · presentation ${bundle.followUpQueue?.presentationState ?? "unknown"}`,
|
|
306
|
+
),
|
|
307
|
+
);
|
|
308
|
+
for (const followUp of bundle.followUpQueue?.followUps ?? []) {
|
|
309
|
+
lines.push(
|
|
310
|
+
fitWidth(
|
|
311
|
+
` ${followUp.order}. ${followUp.state} · ${followUp.followUpId} · ${sanitizeText(followUp.source)}`,
|
|
312
|
+
size.width,
|
|
313
|
+
),
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
276
318
|
if (steps.length > 0) {
|
|
277
319
|
lines.push("");
|
|
278
320
|
lines.push(ansi.bold("steps"));
|
|
@@ -31,6 +31,7 @@ export type WorkflowScopeMetadata = {
|
|
|
31
31
|
path: string;
|
|
32
32
|
workflowName: string;
|
|
33
33
|
maxSteps?: number;
|
|
34
|
+
settings?: WorkflowDefinition["settings"];
|
|
34
35
|
authoredNodes: Record<string, string>;
|
|
35
36
|
childMounts: Record<string, string>;
|
|
36
37
|
};
|
|
@@ -41,6 +42,7 @@ export type WorkflowEntryMetadata = {
|
|
|
41
42
|
parentPath: string;
|
|
42
43
|
workflowName: string;
|
|
43
44
|
input: WorkflowIncludeDefinition["input"];
|
|
45
|
+
settings?: WorkflowIncludeDefinition["settings"];
|
|
44
46
|
normalizeInput?: WorkflowDefinition["input"];
|
|
45
47
|
};
|
|
46
48
|
|
|
@@ -125,6 +127,7 @@ export function compileWorkflowDefinition<TWorkflow extends WorkflowDefinition<a
|
|
|
125
127
|
path: scopePath,
|
|
126
128
|
workflowName: current.name,
|
|
127
129
|
...(current.maxSteps !== undefined ? { maxSteps: current.maxSteps } : {}),
|
|
130
|
+
...(current.settings !== undefined ? { settings: current.settings } : {}),
|
|
128
131
|
authoredNodes,
|
|
129
132
|
childMounts,
|
|
130
133
|
};
|
|
@@ -155,6 +158,11 @@ export function compileWorkflowDefinition<TWorkflow extends WorkflowDefinition<a
|
|
|
155
158
|
`Workflow include ${qualify(scopePath, name)} is unresolved: ${include.workflow}`,
|
|
156
159
|
);
|
|
157
160
|
}
|
|
161
|
+
if (include.settings !== undefined && child.settings === undefined) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`Workflow include ${qualify(scopePath, name)} maps settings but child ${child.name} declares none`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
158
166
|
const mountPath = qualify(scopePath, name);
|
|
159
167
|
const childExits = child.exits ?? {};
|
|
160
168
|
if (Object.keys(childExits).length === 0) {
|
|
@@ -167,6 +175,7 @@ export function compileWorkflowDefinition<TWorkflow extends WorkflowDefinition<a
|
|
|
167
175
|
parentPath: scopePath,
|
|
168
176
|
workflowName: child.name,
|
|
169
177
|
input: include.input,
|
|
178
|
+
...(include.settings !== undefined ? { settings: include.settings } : {}),
|
|
170
179
|
normalizeInput: child.input,
|
|
171
180
|
};
|
|
172
181
|
nodes[entryNode] = {
|
|
@@ -176,10 +185,14 @@ export function compileWorkflowDefinition<TWorkflow extends WorkflowDefinition<a
|
|
|
176
185
|
const parentContext = projectWorkflowContext(context, scopePath, scopes, entries, exits);
|
|
177
186
|
const mapped = include.input ? await include.input(parentContext) : parentContext.input;
|
|
178
187
|
const normalized = child.input ? await child.input(mapped) : mapped;
|
|
188
|
+
const initialSettings = include.settings
|
|
189
|
+
? await include.settings(parentContext)
|
|
190
|
+
: undefined;
|
|
179
191
|
return {
|
|
180
192
|
schema: "pi-workflows.include-entry.v1",
|
|
181
193
|
invocation: countCompletedEntries(context.state, entryNode) + 1,
|
|
182
194
|
input: normalized === undefined ? null : normalized,
|
|
195
|
+
...(initialSettings !== undefined ? { settings: initialSettings } : {}),
|
|
183
196
|
};
|
|
184
197
|
},
|
|
185
198
|
};
|
|
@@ -478,6 +491,7 @@ function wrapNode(
|
|
|
478
491
|
...common,
|
|
479
492
|
nodeType: "compute",
|
|
480
493
|
run: (context) => (node as ComputeNodeDefinition).run(project(context)),
|
|
494
|
+
...(node.settingsRoute === true ? { settingsRoute: true as const } : {}),
|
|
481
495
|
};
|
|
482
496
|
case "notify":
|
|
483
497
|
return {
|
|
@@ -637,6 +651,11 @@ export function projectWorkflowContext(
|
|
|
637
651
|
outputs,
|
|
638
652
|
results,
|
|
639
653
|
state,
|
|
654
|
+
...(context.settings !== undefined ? { settings: context.settings } : {}),
|
|
655
|
+
...(context.settingsScopeId !== undefined ? { settingsScopeId: context.settingsScopeId } : {}),
|
|
656
|
+
...(context.settingsChangeNumber !== undefined
|
|
657
|
+
? { settingsChangeNumber: context.settingsChangeNumber }
|
|
658
|
+
: {}),
|
|
640
659
|
signal: context.signal,
|
|
641
660
|
};
|
|
642
661
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
assertValidShellActionNode,
|
|
8
8
|
assertValidWorkflowDefinitionShape,
|
|
9
9
|
} from "./schema.js";
|
|
10
|
+
import type { WorkflowSettingsDefinition } from "./settings.js";
|
|
10
11
|
import type {
|
|
11
12
|
AgentNodeDefinition,
|
|
12
13
|
AssistantAgentNodeDefinition,
|
|
@@ -26,6 +27,7 @@ import type {
|
|
|
26
27
|
WorkflowInputOf,
|
|
27
28
|
WorkflowNodeContext,
|
|
28
29
|
WorkflowNodeDefinition,
|
|
30
|
+
WorkflowSettingsOf,
|
|
29
31
|
WorkflowTypedEdge,
|
|
30
32
|
WorkflowValueParser,
|
|
31
33
|
} from "./types.js";
|
|
@@ -37,11 +39,13 @@ type WorkflowDefinitionInput<
|
|
|
37
39
|
TNodes extends Record<string, WorkflowNodeDefinition>,
|
|
38
40
|
TIncludes extends WorkflowIncludeMap,
|
|
39
41
|
TExits extends WorkflowExitMap,
|
|
42
|
+
TSettings,
|
|
40
43
|
> = Omit<
|
|
41
|
-
WorkflowDefinition<TInput, TExits, TIncludes>,
|
|
42
|
-
"input" | "nodes" | "includes" | "exits" | "edges"
|
|
44
|
+
WorkflowDefinition<TInput, TExits, TIncludes, TSettings>,
|
|
45
|
+
"input" | "settings" | "nodes" | "includes" | "exits" | "edges"
|
|
43
46
|
> & {
|
|
44
47
|
input?: WorkflowValueParser<TInput>;
|
|
48
|
+
settings?: WorkflowSettingsDefinition<TSettings, TInput>;
|
|
45
49
|
nodes: TNodes;
|
|
46
50
|
includes?: TIncludes;
|
|
47
51
|
exits?: TExits;
|
|
@@ -56,15 +60,16 @@ export function defineWorkflow<
|
|
|
56
60
|
>,
|
|
57
61
|
const TIncludes extends WorkflowIncludeMap = Record<never, never>,
|
|
58
62
|
const TExits extends WorkflowExitMap = Record<never, never>,
|
|
63
|
+
TSettings = unknown,
|
|
59
64
|
>(
|
|
60
|
-
definition: WorkflowDefinitionInput<TInput, TNodes, TIncludes, TExits>,
|
|
61
|
-
): WorkflowDefinition<TInput, TExits, TIncludes> & {
|
|
65
|
+
definition: WorkflowDefinitionInput<TInput, TNodes, TIncludes, TExits, TSettings>,
|
|
66
|
+
): WorkflowDefinition<TInput, TExits, TIncludes, TSettings> & {
|
|
62
67
|
nodes: TNodes;
|
|
63
68
|
includes?: TIncludes;
|
|
64
69
|
exits?: TExits;
|
|
65
70
|
} {
|
|
66
71
|
assertValidWorkflowDefinitionShape(definition as WorkflowDefinition);
|
|
67
|
-
const typed = definition as WorkflowDefinition<TInput, TExits, TIncludes> & {
|
|
72
|
+
const typed = definition as WorkflowDefinition<TInput, TExits, TIncludes, TSettings> & {
|
|
68
73
|
nodes: TNodes;
|
|
69
74
|
includes?: TIncludes;
|
|
70
75
|
exits?: TExits;
|
|
@@ -87,6 +92,9 @@ export function includeWorkflow<TWorkflow extends WorkflowDefinition<any, any, a
|
|
|
87
92
|
input?: (
|
|
88
93
|
context: WorkflowNodeContext,
|
|
89
94
|
) => Promise<WorkflowInputOf<TWorkflow>> | WorkflowInputOf<TWorkflow>;
|
|
95
|
+
settings?: (
|
|
96
|
+
context: WorkflowNodeContext,
|
|
97
|
+
) => Promise<WorkflowSettingsOf<TWorkflow>> | WorkflowSettingsOf<TWorkflow>;
|
|
90
98
|
},
|
|
91
99
|
): WorkflowIncludeDefinition<TWorkflow>;
|
|
92
100
|
export function includeWorkflow<TWorkflow extends WorkflowDefinition<any, any, any>>(
|
|
@@ -98,6 +106,9 @@ export function includeWorkflow<TWorkflow extends WorkflowDefinition<any, any, a
|
|
|
98
106
|
input?: (
|
|
99
107
|
context: WorkflowNodeContext,
|
|
100
108
|
) => Promise<WorkflowInputOf<TWorkflow>> | WorkflowInputOf<TWorkflow>;
|
|
109
|
+
settings?: (
|
|
110
|
+
context: WorkflowNodeContext,
|
|
111
|
+
) => Promise<WorkflowSettingsOf<TWorkflow>> | WorkflowSettingsOf<TWorkflow>;
|
|
101
112
|
} = {},
|
|
102
113
|
): WorkflowIncludeDefinition<TWorkflow> {
|
|
103
114
|
const definition = isWorkflowDefinition(workflowOrDefinition)
|
|
@@ -109,6 +120,9 @@ export function includeWorkflow<TWorkflow extends WorkflowDefinition<any, any, a
|
|
|
109
120
|
if (definition.input !== undefined && typeof definition.input !== "function") {
|
|
110
121
|
throw new Error("Included workflow input must be a function");
|
|
111
122
|
}
|
|
123
|
+
if (definition.settings !== undefined && typeof definition.settings !== "function") {
|
|
124
|
+
throw new Error("Included workflow settings must be a function");
|
|
125
|
+
}
|
|
112
126
|
if (definition.contract !== undefined && !isWorkflowDefinition(definition.contract)) {
|
|
113
127
|
throw new Error("Included workflow contract must be defined with defineWorkflow");
|
|
114
128
|
}
|