@osolmaz/pi-workflows 0.4.0 → 0.5.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 +8 -5
- package/dist/builtins/catalog.js +12 -2
- package/dist/builtins/catalog.js.map +1 -1
- package/dist/builtins/monitor.workflow.d.ts +2 -2
- package/dist/builtins/monitor.workflow.js +11 -27
- package/dist/builtins/monitor.workflow.js.map +1 -1
- package/dist/controllers/index.d.ts +1 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/sqlite.d.ts +43 -5
- package/dist/controllers/sqlite.js +128 -32
- package/dist/controllers/sqlite.js.map +1 -1
- package/dist/extension/index.js +74 -80
- package/dist/extension/index.js.map +1 -1
- package/dist/host/runner.js +20 -1
- package/dist/host/runner.js.map +1 -1
- package/dist/render/graph-render.js +3 -0
- package/dist/render/graph-render.js.map +1 -1
- package/dist/workflows/definition.d.ts +2 -1
- package/dist/workflows/definition.js +9 -1
- package/dist/workflows/definition.js.map +1 -1
- package/dist/workflows/engine.d.ts +1 -0
- package/dist/workflows/engine.js +22 -0
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/index.d.ts +2 -2
- package/dist/workflows/index.js +1 -1
- package/dist/workflows/index.js.map +1 -1
- package/dist/workflows/migrate-sources.d.ts +1 -0
- package/dist/workflows/migrate-sources.js +4 -0
- package/dist/workflows/migrate-sources.js.map +1 -1
- package/dist/workflows/schema.d.ts +2 -1
- package/dist/workflows/schema.js +12 -0
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/store.js +3 -0
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +26 -1
- package/docs/plans/2026-08-13-session-addressed-workflow-notifications-plan.md +95 -0
- package/docs/workflows.md +23 -4
- package/package.json +1 -1
- package/src/builtins/catalog.ts +12 -2
- package/src/builtins/monitor.workflow.ts +11 -28
- package/src/controllers/index.ts +1 -0
- package/src/controllers/sqlite.ts +225 -33
- package/src/extension/index.ts +76 -104
- package/src/host/runner.ts +20 -1
- package/src/render/graph-render.ts +3 -0
- package/src/workflows/definition.ts +11 -0
- package/src/workflows/engine.ts +26 -0
- package/src/workflows/index.ts +5 -0
- package/src/workflows/migrate-sources.ts +7 -0
- package/src/workflows/schema.ts +14 -0
- package/src/workflows/store.ts +3 -0
- package/src/workflows/types.ts +30 -0
package/README.md
CHANGED
|
@@ -179,10 +179,11 @@ finishes with a JSON output, and edges decide what runs next.
|
|
|
179
179
|
|
|
180
180
|
An `agent` node sends a prompt into the pi conversation and waits for the
|
|
181
181
|
model to submit its output through the `workflow` tool. A `compute` node runs
|
|
182
|
-
a pure TypeScript function.
|
|
183
|
-
|
|
184
|
-
(`
|
|
185
|
-
|
|
182
|
+
a pure TypeScript function. A `notify` node writes a durable message for the
|
|
183
|
+
Pi session that started the run. An `action` node performs a side effect,
|
|
184
|
+
either a TypeScript function (`action({ run })`) or a runtime-owned shell
|
|
185
|
+
command (`shell({ exec, parse })`). A `checkpoint` node ends the run in a
|
|
186
|
+
`waiting` state so a human can pick it up. On top of `agent`, the `decision` helper asks
|
|
186
187
|
the model to pick from a fixed set of choices and validates the answer, and
|
|
187
188
|
`decisionEdge` routes on the result with compile-time case checking.
|
|
188
189
|
|
|
@@ -221,7 +222,9 @@ The standalone CLI provides read-only views with `pi-workflows controllers` and
|
|
|
221
222
|
|
|
222
223
|
## Always-on workflows
|
|
223
224
|
|
|
224
|
-
Runs do not depend on the Pi window. Every `/workflow` run is claimed through a durable queue, so closing Pi mid-run **parks** the run instead of cancelling it
|
|
225
|
+
Runs do not depend on the Pi window. Every `/workflow` run is claimed through a durable queue, so closing Pi mid-run **parks** the run instead of cancelling it. Another interactive session cannot claim it. Reopening the exact session that started the run resumes it. A standalone host can also resume it without changing where reports go. A checkpointed run waits durably until you answer it with `/workflow answer <json>`, which continues the graph in a linked run.
|
|
226
|
+
|
|
227
|
+
Workflow reports use a durable session-addressed outbox. A report waits while its starting session is closed and is delivered only to that session when it opens again. Runs in the same directory do not broadcast messages to each other's conversations.
|
|
225
228
|
|
|
226
229
|
For runs that must continue while Pi is closed, keep the standalone host running:
|
|
227
230
|
|
package/dist/builtins/catalog.js
CHANGED
|
@@ -3,12 +3,12 @@ import monitorWorkflow from "./monitor.workflow.js";
|
|
|
3
3
|
export const builtinWorkflowCatalog = new BuiltinWorkflowCatalog([
|
|
4
4
|
{
|
|
5
5
|
id: "monitor",
|
|
6
|
-
revision: "
|
|
6
|
+
revision: "2",
|
|
7
7
|
definition: monitorWorkflow,
|
|
8
8
|
legacySources: [
|
|
9
9
|
{
|
|
10
10
|
workflowHash: "7a22158da94d18ec1c9fe42e70d72017a4e0620d5e5142ae839d0cd6eea55c06",
|
|
11
|
-
revision: "
|
|
11
|
+
revision: "2",
|
|
12
12
|
pathSuffixes: [
|
|
13
13
|
"/src/builtins/monitor.workflow.ts",
|
|
14
14
|
"/dist/builtins/monitor.workflow.js",
|
|
@@ -16,6 +16,16 @@ export const builtinWorkflowCatalog = new BuiltinWorkflowCatalog([
|
|
|
16
16
|
"/dist/workflows/monitor.workflow.js",
|
|
17
17
|
],
|
|
18
18
|
},
|
|
19
|
+
{
|
|
20
|
+
workflowHash: "352fc09c88922c7375281b52f049c1039d05441ce37f2082f5ff07fea66d5318",
|
|
21
|
+
revision: "2",
|
|
22
|
+
pathSuffixes: ["/dist/builtins/monitor.workflow.js"],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
workflowHash: "dc601e2323a8213f5d52fa555e804ae8ed0846f809b3a1e9e5073a3c9c3a114e",
|
|
26
|
+
revision: "2",
|
|
27
|
+
pathSuffixes: ["/dist/builtins/monitor.workflow.js"],
|
|
28
|
+
},
|
|
19
29
|
],
|
|
20
30
|
},
|
|
21
31
|
]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/builtins/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,eAAe,MAAM,uBAAuB,CAAC;AAEpD,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,sBAAsB,CAAC;IAC/D;QACE,EAAE,EAAE,SAAS;QACb,QAAQ,EAAE,GAAG;QACb,UAAU,EAAE,eAAe;QAC3B,aAAa,EAAE;YACb;gBACE,YAAY,EAAE,kEAAkE;gBAChF,QAAQ,EAAE,GAAG;gBACb,YAAY,EAAE;oBACZ,mCAAmC;oBACnC,oCAAoC;oBACpC,oCAAoC;oBACpC,qCAAqC;iBACtC;aACF;SACF;KACF;CACF,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/builtins/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,eAAe,MAAM,uBAAuB,CAAC;AAEpD,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,sBAAsB,CAAC;IAC/D;QACE,EAAE,EAAE,SAAS;QACb,QAAQ,EAAE,GAAG;QACb,UAAU,EAAE,eAAe;QAC3B,aAAa,EAAE;YACb;gBACE,YAAY,EAAE,kEAAkE;gBAChF,QAAQ,EAAE,GAAG;gBACb,YAAY,EAAE;oBACZ,mCAAmC;oBACnC,oCAAoC;oBACpC,oCAAoC;oBACpC,qCAAqC;iBACtC;aACF;YACD;gBACE,YAAY,EAAE,kEAAkE;gBAChF,QAAQ,EAAE,GAAG;gBACb,YAAY,EAAE,CAAC,oCAAoC,CAAC;aACrD;YACD;gBACE,YAAY,EAAE,kEAAkE;gBAChF,QAAQ,EAAE,GAAG;gBACb,YAAY,EAAE,CAAC,oCAAoC,CAAC;aACrD;SACF;KACF;CACF,CAAC,CAAC"}
|
|
@@ -12,8 +12,8 @@ declare const _default: {
|
|
|
12
12
|
guard: import("../workflows/types.js").ComputeNodeDefinition;
|
|
13
13
|
continue_guard: import("../workflows/types.js").ComputeNodeDefinition;
|
|
14
14
|
check: import("../workflows/types.js").AgentNodeDefinition;
|
|
15
|
-
report_continue: import("../workflows/types.js").
|
|
16
|
-
report_stop: import("../workflows/types.js").
|
|
15
|
+
report_continue: import("../workflows/types.js").NotifyNodeDefinition;
|
|
16
|
+
report_stop: import("../workflows/types.js").NotifyNodeDefinition;
|
|
17
17
|
sleep: import("../workflows/types.js").ShellActionNodeDefinition;
|
|
18
18
|
finish: import("../workflows/types.js").ComputeNodeDefinition;
|
|
19
19
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { agent, compute, defineWorkflow, shell } from "../workflows/definition.js";
|
|
1
|
+
import { agent, compute, defineWorkflow, notify, shell } from "../workflows/definition.js";
|
|
2
2
|
const MIN_INTERVAL_MINUTES = 1;
|
|
3
3
|
const MAX_INTERVAL_MINUTES = 24 * 60;
|
|
4
4
|
const MIN_CHECK_TIMEOUT_MINUTES = 5;
|
|
@@ -100,21 +100,9 @@ function validateCheck(output) {
|
|
|
100
100
|
reason,
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
|
-
function
|
|
104
|
-
const value = requireRecord(output, "report acknowledgement");
|
|
105
|
-
if (value.reported !== true) {
|
|
106
|
-
throw new Error("report acknowledgement must set reported to true");
|
|
107
|
-
}
|
|
108
|
-
return { reported: true };
|
|
109
|
-
}
|
|
110
|
-
function reportPrompt(outputs) {
|
|
103
|
+
function reportMessage(outputs) {
|
|
111
104
|
const check = outputs.check;
|
|
112
|
-
return
|
|
113
|
-
"Write one concise normal assistant message to the user with this monitoring update:",
|
|
114
|
-
check.report ?? check.observation,
|
|
115
|
-
"Do not add unrelated detail.",
|
|
116
|
-
"After writing the update, submit the acknowledgement required by the workflow step contract.",
|
|
117
|
-
].join("\n\n");
|
|
105
|
+
return check.report ?? check.observation;
|
|
118
106
|
}
|
|
119
107
|
export default defineWorkflow({
|
|
120
108
|
name: "monitor",
|
|
@@ -180,19 +168,15 @@ export default defineWorkflow({
|
|
|
180
168
|
expectedOutput: '{ "route": "continue_quiet" | "continue_report" | "stop_quiet" | "stop_report", "observation": "current factual state", "report": "required for report routes", "reason": "short reason" }',
|
|
181
169
|
validate: (output) => validateCheck(output),
|
|
182
170
|
}),
|
|
183
|
-
report_continue:
|
|
184
|
-
statusDetail: "
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
expectedOutput: '{ "reported": true }',
|
|
188
|
-
validate: (output) => validateReportAck(output),
|
|
171
|
+
report_continue: notify({
|
|
172
|
+
statusDetail: "queueing monitor update",
|
|
173
|
+
message: ({ outputs }) => reportMessage(outputs),
|
|
174
|
+
kind: "progress",
|
|
189
175
|
}),
|
|
190
|
-
report_stop:
|
|
191
|
-
statusDetail: "
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
expectedOutput: '{ "reported": true }',
|
|
195
|
-
validate: (output) => validateReportAck(output),
|
|
176
|
+
report_stop: notify({
|
|
177
|
+
statusDetail: "queueing final monitor update",
|
|
178
|
+
message: ({ outputs }) => reportMessage(outputs),
|
|
179
|
+
kind: "final",
|
|
196
180
|
}),
|
|
197
181
|
sleep: shell({
|
|
198
182
|
statusDetail: "waiting for next monitor check",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"monitor.workflow.js","sourceRoot":"","sources":["../../src/builtins/monitor.workflow.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"monitor.workflow.js","sourceRoot":"","sources":["../../src/builtins/monitor.workflow.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ export { ControllerEffectService } from "./effects.js";
|
|
|
5
5
|
export { canonicalJson, jsonFingerprint } from "./json.js";
|
|
6
6
|
export { ControllerManager, type ControllerManagerOptions } from "./manager.js";
|
|
7
7
|
export { createResultHelpers, requeue, requeueAfter, settled } from "./results.js";
|
|
8
|
-
export { SqliteControllerStore, type RunEventRecord, type WorkflowRunQueueRecord, } from "./sqlite.js";
|
|
8
|
+
export { SqliteControllerStore, type RunEventRecord, type WorkflowNotificationRecord, type WorkflowRunQueueRecord, } from "./sqlite.js";
|
|
9
9
|
export { CONTROLLER_STORE_SCHEMA, controllerProjectScope, controllerStoreBaseDir, controllerStorePath, projectControllerStoreBaseDir, projectControllerStorePath, type ControllerStore, type EffectReservation, type QueueItem, type QueueRequeueOptions, type WorkflowRecordUpdate, type WorkflowReservation, } from "./store.js";
|
|
10
10
|
export { controllerFileStem, controllerSearchDirs, discoverControllers, loadControllerFile, loadDiscoveredControllers, type DiscoveredController, } from "./loader.js";
|
|
11
11
|
export { WorkflowEngineScheduler, type ResolvedChildWorkflow, type WorkflowEngineSchedulerOptions, } from "./workflow-engine-scheduler.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/controllers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,+BAA+B,EAC/B,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,EACrB,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAiC,MAAM,cAAc,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACnF,OAAO,EACL,qBAAqB,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/controllers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,+BAA+B,EAC/B,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,eAAe,GAChB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,qBAAqB,EACrB,4BAA4B,GAC7B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAiC,MAAM,cAAc,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACnF,OAAO,EACL,qBAAqB,GAItB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,sBAAsB,EACtB,mBAAmB,EACnB,6BAA6B,EAC7B,0BAA0B,GAO3B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,kBAAkB,EAClB,yBAAyB,GAE1B,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,uBAAuB,GAGxB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,6BAA6B,GAI9B,MAAM,gBAAgB,CAAC"}
|
|
@@ -13,10 +13,25 @@ export type WorkflowRunQueueRecord = {
|
|
|
13
13
|
claimToken: string | null;
|
|
14
14
|
claimExpiresAt: string | null;
|
|
15
15
|
affinityRunnerId: string | null;
|
|
16
|
+
/** Pi session that owns delivery and interactive execution, or null for detached runs. */
|
|
17
|
+
originSessionId: string | null;
|
|
16
18
|
parentRunId: string | null;
|
|
17
19
|
createdAt: string;
|
|
18
20
|
updatedAt: string;
|
|
19
21
|
};
|
|
22
|
+
export type WorkflowNotificationRecord = {
|
|
23
|
+
notificationId: string;
|
|
24
|
+
runId: string;
|
|
25
|
+
nodeId: string;
|
|
26
|
+
attemptId: string;
|
|
27
|
+
notificationIndex: number;
|
|
28
|
+
targetSessionId: string;
|
|
29
|
+
kind: "progress" | "final";
|
|
30
|
+
content: string;
|
|
31
|
+
createdAt: string;
|
|
32
|
+
deliveryClaimExpiresAt: string | null;
|
|
33
|
+
deliveredAt: string | null;
|
|
34
|
+
};
|
|
20
35
|
/** One run lifecycle transition in the cross-session event feed. */
|
|
21
36
|
export type RunEventRecord = {
|
|
22
37
|
seq: number;
|
|
@@ -137,6 +152,7 @@ export declare class SqliteControllerStore implements ControllerStore {
|
|
|
137
152
|
claimToken: string;
|
|
138
153
|
leaseMs: number;
|
|
139
154
|
affinityRunnerId?: string;
|
|
155
|
+
originSessionId?: string;
|
|
140
156
|
parentRunId?: string;
|
|
141
157
|
now?: string;
|
|
142
158
|
}): WorkflowRunQueueRecord;
|
|
@@ -155,6 +171,8 @@ export declare class SqliteControllerStore implements ControllerStore {
|
|
|
155
171
|
claimToken: string;
|
|
156
172
|
leaseMs: number;
|
|
157
173
|
excludeRunIds?: string[];
|
|
174
|
+
/** Interactive sessions provide their id; detached hosts omit it. */
|
|
175
|
+
sessionId?: string;
|
|
158
176
|
now?: string;
|
|
159
177
|
}): WorkflowRunQueueRecord | undefined;
|
|
160
178
|
/** Extend a live claim. Rejects expired or foreign claims. */
|
|
@@ -225,11 +243,31 @@ export declare class SqliteControllerStore implements ControllerStore {
|
|
|
225
243
|
listRunEventsAfter(seq: number, options?: {
|
|
226
244
|
limit?: number;
|
|
227
245
|
}): RunEventRecord[];
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
246
|
+
enqueueWorkflowNotification(options: {
|
|
247
|
+
runId: string;
|
|
248
|
+
nodeId: string;
|
|
249
|
+
attemptId: string;
|
|
250
|
+
notificationIndex: number;
|
|
251
|
+
targetSessionId: string;
|
|
252
|
+
kind: "progress" | "final";
|
|
253
|
+
content: string;
|
|
254
|
+
notificationId?: string;
|
|
255
|
+
now?: string;
|
|
256
|
+
}): WorkflowNotificationRecord;
|
|
257
|
+
claimPendingWorkflowNotifications(options: {
|
|
258
|
+
targetSessionId: string;
|
|
259
|
+
claimToken: string;
|
|
260
|
+
leaseMs: number;
|
|
261
|
+
limit?: number;
|
|
262
|
+
now?: string;
|
|
263
|
+
}): WorkflowNotificationRecord[];
|
|
264
|
+
markWorkflowNotificationDelivered(options: {
|
|
265
|
+
notificationId: string;
|
|
266
|
+
targetSessionId: string;
|
|
267
|
+
claimToken: string;
|
|
268
|
+
now?: string;
|
|
269
|
+
}): boolean;
|
|
270
|
+
setWorkflowRunOriginSession(runId: string, originSessionId: string): boolean;
|
|
233
271
|
private requireWorkflowRun;
|
|
234
272
|
private requireEvent;
|
|
235
273
|
}
|
|
@@ -474,6 +474,11 @@ export class SqliteControllerStore {
|
|
|
474
474
|
.prepare("INSERT OR IGNORE INTO schema_info (singleton, schema_id) VALUES (1, ?)")
|
|
475
475
|
.run(CONTROLLER_STORE_SCHEMA);
|
|
476
476
|
this.database.exec(SCHEMA_SQL);
|
|
477
|
+
const queueColumns = this.database.pragma("table_info(workflow_run_queue)");
|
|
478
|
+
if (!queueColumns.some((column) => column.name === "origin_session_id")) {
|
|
479
|
+
this.database.exec("ALTER TABLE workflow_run_queue ADD COLUMN origin_session_id TEXT");
|
|
480
|
+
}
|
|
481
|
+
this.database.exec("DROP TABLE IF EXISTS session_watermarks");
|
|
477
482
|
});
|
|
478
483
|
}
|
|
479
484
|
transaction(task) {
|
|
@@ -557,6 +562,9 @@ export class SqliteControllerStore {
|
|
|
557
562
|
validateKey(options.runnerId, "runner id");
|
|
558
563
|
validateKey(options.claimToken, "claim token");
|
|
559
564
|
validateDuration(options.leaseMs, "leaseMs");
|
|
565
|
+
if (options.originSessionId !== undefined) {
|
|
566
|
+
validateKey(options.originSessionId, "origin session id");
|
|
567
|
+
}
|
|
560
568
|
const inputJson = canonicalJson(options.input ?? null, "workflow run input");
|
|
561
569
|
validateJsonSize(inputJson, "Workflow run input", MAX_RESOURCE_VALUE_BYTES);
|
|
562
570
|
const now = validTimestamp(options.now);
|
|
@@ -565,9 +573,9 @@ export class SqliteControllerStore {
|
|
|
565
573
|
.prepare(`INSERT INTO workflow_run_queue (
|
|
566
574
|
run_id, workflow_ref, workflow_path, input_json, status,
|
|
567
575
|
runner_id, claim_token, claim_expires_at, affinity_runner_id,
|
|
568
|
-
parent_run_id, created_at, updated_at
|
|
569
|
-
) VALUES (?, ?, ?, ?, 'claimed', ?, ?, ?, ?, ?, ?, ?)`)
|
|
570
|
-
.run(options.runId, options.workflowName, options.workflowSourceRef, inputJson, options.runnerId, options.claimToken, expiresAt, options.affinityRunnerId ?? options.runnerId, options.parentRunId ?? null, now, now);
|
|
576
|
+
origin_session_id, parent_run_id, created_at, updated_at
|
|
577
|
+
) VALUES (?, ?, ?, ?, 'claimed', ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
578
|
+
.run(options.runId, options.workflowName, options.workflowSourceRef, inputJson, options.runnerId, options.claimToken, expiresAt, options.affinityRunnerId ?? options.runnerId, options.originSessionId ?? null, options.parentRunId ?? null, now, now);
|
|
571
579
|
return this.requireWorkflowRun(options.runId);
|
|
572
580
|
}
|
|
573
581
|
getWorkflowRun(runId) {
|
|
@@ -597,6 +605,8 @@ export class SqliteControllerStore {
|
|
|
597
605
|
validateKey(options.runnerId, "runner id");
|
|
598
606
|
validateKey(options.claimToken, "claim token");
|
|
599
607
|
validateDuration(options.leaseMs, "leaseMs");
|
|
608
|
+
if (options.sessionId !== undefined)
|
|
609
|
+
validateKey(options.sessionId, "session id");
|
|
600
610
|
const now = validTimestamp(options.now);
|
|
601
611
|
const nowMs = epoch(now);
|
|
602
612
|
const expiresAt = nowMs + options.leaseMs;
|
|
@@ -605,6 +615,9 @@ export class SqliteControllerStore {
|
|
|
605
615
|
validateRunId(runId);
|
|
606
616
|
}
|
|
607
617
|
const exclusion = excluded.length === 0 ? "" : `AND run_id NOT IN (${excluded.map(() => "?").join(", ")})`;
|
|
618
|
+
const sessionFilter = options.sessionId === undefined
|
|
619
|
+
? ""
|
|
620
|
+
: "AND (origin_session_id IS NULL OR origin_session_id = ?)";
|
|
608
621
|
const claimable = `(
|
|
609
622
|
status = 'parked'
|
|
610
623
|
OR (status = 'claimed' AND claim_expires_at IS NOT NULL AND claim_expires_at <= ?)
|
|
@@ -612,12 +625,12 @@ export class SqliteControllerStore {
|
|
|
612
625
|
return this.transaction(() => {
|
|
613
626
|
const candidate = this.database
|
|
614
627
|
.prepare(`SELECT run_id FROM workflow_run_queue
|
|
615
|
-
WHERE ${claimable} ${exclusion}
|
|
628
|
+
WHERE ${claimable} ${exclusion} ${sessionFilter}
|
|
616
629
|
ORDER BY
|
|
617
630
|
CASE WHEN affinity_runner_id = ? THEN 0 ELSE 1 END,
|
|
618
631
|
created_at ASC
|
|
619
632
|
LIMIT 1`)
|
|
620
|
-
.get(nowMs, ...excluded, options.runnerId);
|
|
633
|
+
.get(nowMs, ...excluded, ...(options.sessionId === undefined ? [] : [options.sessionId]), options.runnerId);
|
|
621
634
|
if (candidate === undefined) {
|
|
622
635
|
return undefined;
|
|
623
636
|
}
|
|
@@ -802,31 +815,84 @@ export class SqliteControllerStore {
|
|
|
802
815
|
payload: parseStoredJson(row.payload_json, "run event payload"),
|
|
803
816
|
}));
|
|
804
817
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
getSessionWatermark(sessionId) {
|
|
812
|
-
validateKey(sessionId, "session id");
|
|
813
|
-
const row = this.database
|
|
814
|
-
.prepare("SELECT last_event_seq FROM session_watermarks WHERE session_id = ?")
|
|
815
|
-
.get(sessionId);
|
|
816
|
-
return row?.last_event_seq ?? 0;
|
|
817
|
-
}
|
|
818
|
-
setSessionWatermark(sessionId, seq, now) {
|
|
819
|
-
validateKey(sessionId, "session id");
|
|
820
|
-
if (!Number.isSafeInteger(seq) || seq < 0) {
|
|
821
|
-
throw new Error(`Invalid run event watermark: ${seq}`);
|
|
818
|
+
enqueueWorkflowNotification(options) {
|
|
819
|
+
validateRunId(options.runId);
|
|
820
|
+
validateKey(options.nodeId, "workflow node id");
|
|
821
|
+
validateKey(options.attemptId, "workflow attempt id");
|
|
822
|
+
if (!Number.isSafeInteger(options.notificationIndex) || options.notificationIndex <= 0) {
|
|
823
|
+
throw new Error("Workflow notification index must be a positive safe integer");
|
|
822
824
|
}
|
|
825
|
+
validateKey(options.targetSessionId, "target session id");
|
|
826
|
+
if (options.content.trim().length === 0 || options.content.length > MAX_EVENT_BYTES) {
|
|
827
|
+
throw new Error(`Workflow notification content must be 1-${MAX_EVENT_BYTES} characters`);
|
|
828
|
+
}
|
|
829
|
+
const notificationId = options.notificationId ?? randomUUID();
|
|
830
|
+
validateKey(notificationId, "notification id");
|
|
823
831
|
this.database
|
|
824
|
-
.prepare(`INSERT INTO
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
.run(
|
|
832
|
+
.prepare(`INSERT INTO workflow_notifications (
|
|
833
|
+
notification_id, run_id, node_id, attempt_id, notification_index,
|
|
834
|
+
target_session_id, kind, content, created_at, delivered_at
|
|
835
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL)
|
|
836
|
+
ON CONFLICT(run_id, node_id, notification_index) DO NOTHING`)
|
|
837
|
+
.run(notificationId, options.runId, options.nodeId, options.attemptId, options.notificationIndex, options.targetSessionId, options.kind, options.content.trim(), validTimestamp(options.now));
|
|
838
|
+
const row = this.database
|
|
839
|
+
.prepare("SELECT * FROM workflow_notifications WHERE run_id = ? AND node_id = ? AND notification_index = ?")
|
|
840
|
+
.get(options.runId, options.nodeId, options.notificationIndex);
|
|
841
|
+
return workflowNotificationFromRow(row);
|
|
842
|
+
}
|
|
843
|
+
claimPendingWorkflowNotifications(options) {
|
|
844
|
+
validateKey(options.targetSessionId, "target session id");
|
|
845
|
+
validateKey(options.claimToken, "notification claim token");
|
|
846
|
+
validateDuration(options.leaseMs, "notification leaseMs");
|
|
847
|
+
const limit = options.limit ?? 20;
|
|
848
|
+
if (!Number.isSafeInteger(limit) || limit <= 0 || limit > 100) {
|
|
849
|
+
throw new Error("Workflow notification limit must be an integer from 1 through 100");
|
|
850
|
+
}
|
|
851
|
+
const now = validTimestamp(options.now);
|
|
852
|
+
const nowMs = epoch(now);
|
|
853
|
+
return this.transaction(() => {
|
|
854
|
+
const ids = this.database
|
|
855
|
+
.prepare(`SELECT notification_id FROM workflow_notifications
|
|
856
|
+
WHERE target_session_id = ? AND delivered_at IS NULL
|
|
857
|
+
AND (delivery_claim_expires_at IS NULL OR delivery_claim_expires_at <= ?)
|
|
858
|
+
ORDER BY created_at, notification_id LIMIT ?`)
|
|
859
|
+
.all(options.targetSessionId, nowMs, limit);
|
|
860
|
+
if (ids.length === 0)
|
|
861
|
+
return [];
|
|
862
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
863
|
+
this.database
|
|
864
|
+
.prepare(`UPDATE workflow_notifications
|
|
865
|
+
SET delivery_claim_token = ?, delivery_claim_expires_at = ?
|
|
866
|
+
WHERE notification_id IN (${placeholders}) AND delivered_at IS NULL
|
|
867
|
+
AND (delivery_claim_expires_at IS NULL OR delivery_claim_expires_at <= ?)`)
|
|
868
|
+
.run(options.claimToken, nowMs + options.leaseMs, ...ids.map((row) => row.notification_id), nowMs);
|
|
869
|
+
const rows = this.database
|
|
870
|
+
.prepare(`SELECT * FROM workflow_notifications WHERE delivery_claim_token = ?
|
|
871
|
+
ORDER BY created_at, notification_id`)
|
|
872
|
+
.all(options.claimToken);
|
|
873
|
+
return rows.map(workflowNotificationFromRow);
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
markWorkflowNotificationDelivered(options) {
|
|
877
|
+
validateKey(options.notificationId, "notification id");
|
|
878
|
+
validateKey(options.targetSessionId, "target session id");
|
|
879
|
+
validateKey(options.claimToken, "notification claim token");
|
|
880
|
+
const result = this.database
|
|
881
|
+
.prepare(`UPDATE workflow_notifications
|
|
882
|
+
SET delivered_at = ?, delivery_claim_token = NULL, delivery_claim_expires_at = NULL
|
|
883
|
+
WHERE notification_id = ? AND target_session_id = ?
|
|
884
|
+
AND delivery_claim_token = ? AND delivered_at IS NULL`)
|
|
885
|
+
.run(validTimestamp(options.now), options.notificationId, options.targetSessionId, options.claimToken);
|
|
886
|
+
return result.changes === 1;
|
|
887
|
+
}
|
|
888
|
+
setWorkflowRunOriginSession(runId, originSessionId) {
|
|
889
|
+
validateRunId(runId);
|
|
890
|
+
validateKey(originSessionId, "origin session id");
|
|
891
|
+
const result = this.database
|
|
892
|
+
.prepare(`UPDATE workflow_run_queue SET origin_session_id = ?
|
|
893
|
+
WHERE run_id = ? AND origin_session_id IS NULL`)
|
|
894
|
+
.run(originSessionId, runId);
|
|
895
|
+
return result.changes === 1;
|
|
830
896
|
}
|
|
831
897
|
requireWorkflowRun(runId) {
|
|
832
898
|
const record = this.getWorkflowRun(runId);
|
|
@@ -885,6 +951,7 @@ const SCHEMA_SQL = `
|
|
|
885
951
|
claim_token TEXT,
|
|
886
952
|
claim_expires_at INTEGER,
|
|
887
953
|
affinity_runner_id TEXT,
|
|
954
|
+
origin_session_id TEXT,
|
|
888
955
|
parent_run_id TEXT,
|
|
889
956
|
created_at TEXT NOT NULL,
|
|
890
957
|
updated_at TEXT NOT NULL
|
|
@@ -907,11 +974,24 @@ const SCHEMA_SQL = `
|
|
|
907
974
|
);
|
|
908
975
|
CREATE INDEX IF NOT EXISTS run_events_run ON run_events(run_id);
|
|
909
976
|
|
|
910
|
-
CREATE TABLE IF NOT EXISTS
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
977
|
+
CREATE TABLE IF NOT EXISTS workflow_notifications (
|
|
978
|
+
notification_id TEXT PRIMARY KEY,
|
|
979
|
+
run_id TEXT NOT NULL,
|
|
980
|
+
node_id TEXT NOT NULL,
|
|
981
|
+
attempt_id TEXT NOT NULL,
|
|
982
|
+
notification_index INTEGER NOT NULL CHECK (notification_index > 0),
|
|
983
|
+
target_session_id TEXT NOT NULL,
|
|
984
|
+
kind TEXT NOT NULL CHECK (kind IN ('progress', 'final')),
|
|
985
|
+
content TEXT NOT NULL,
|
|
986
|
+
created_at TEXT NOT NULL,
|
|
987
|
+
delivery_claim_token TEXT,
|
|
988
|
+
delivery_claim_expires_at INTEGER,
|
|
989
|
+
delivered_at TEXT,
|
|
990
|
+
UNIQUE (run_id, node_id, notification_index)
|
|
914
991
|
);
|
|
992
|
+
CREATE INDEX IF NOT EXISTS workflow_notifications_pending
|
|
993
|
+
ON workflow_notifications(target_session_id, delivery_claim_expires_at, created_at)
|
|
994
|
+
WHERE delivered_at IS NULL;
|
|
915
995
|
|
|
916
996
|
CREATE TABLE IF NOT EXISTS effects (
|
|
917
997
|
effect_key TEXT NOT NULL,
|
|
@@ -999,6 +1079,21 @@ function workflowFromRow(row) {
|
|
|
999
1079
|
...(row.error === null ? {} : { error: row.error }),
|
|
1000
1080
|
};
|
|
1001
1081
|
}
|
|
1082
|
+
function workflowNotificationFromRow(row) {
|
|
1083
|
+
return {
|
|
1084
|
+
notificationId: row.notification_id,
|
|
1085
|
+
runId: row.run_id,
|
|
1086
|
+
nodeId: row.node_id,
|
|
1087
|
+
attemptId: row.attempt_id,
|
|
1088
|
+
notificationIndex: row.notification_index,
|
|
1089
|
+
targetSessionId: row.target_session_id,
|
|
1090
|
+
kind: row.kind,
|
|
1091
|
+
content: row.content,
|
|
1092
|
+
createdAt: row.created_at,
|
|
1093
|
+
deliveryClaimExpiresAt: row.delivery_claim_expires_at === null ? null : iso(row.delivery_claim_expires_at),
|
|
1094
|
+
deliveredAt: row.delivered_at,
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1002
1097
|
function workflowRunFromRow(row) {
|
|
1003
1098
|
return {
|
|
1004
1099
|
runId: row.run_id,
|
|
@@ -1010,6 +1105,7 @@ function workflowRunFromRow(row) {
|
|
|
1010
1105
|
claimToken: row.claim_token,
|
|
1011
1106
|
claimExpiresAt: row.claim_expires_at === null ? null : iso(row.claim_expires_at),
|
|
1012
1107
|
affinityRunnerId: row.affinity_runner_id,
|
|
1108
|
+
originSessionId: row.origin_session_id,
|
|
1013
1109
|
parentRunId: row.parent_run_id,
|
|
1014
1110
|
createdAt: row.created_at,
|
|
1015
1111
|
updatedAt: row.updated_at,
|