@osolmaz/pi-workflows 0.2.0 → 0.3.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 +71 -5
- package/dist/builtins/monitor.workflow.d.ts +71 -0
- package/dist/builtins/monitor.workflow.js +234 -0
- package/dist/builtins/monitor.workflow.js.map +1 -0
- package/dist/controllers/conditions.d.ts +6 -0
- package/dist/controllers/conditions.js +68 -0
- package/dist/controllers/conditions.js.map +1 -0
- package/dist/controllers/definition.d.ts +6 -0
- package/dist/controllers/definition.js +45 -0
- package/dist/controllers/definition.js.map +1 -0
- package/dist/controllers/effects.d.ts +14 -0
- package/dist/controllers/effects.js +104 -0
- package/dist/controllers/effects.js.map +1 -0
- package/dist/controllers/errors.d.ts +12 -0
- package/dist/controllers/errors.js +25 -0
- package/dist/controllers/errors.js.map +1 -0
- package/dist/controllers/index.d.ts +13 -0
- package/dist/controllers/index.js +13 -0
- package/dist/controllers/index.js.map +1 -0
- package/dist/controllers/json.d.ts +5 -0
- package/dist/controllers/json.js +57 -0
- package/dist/controllers/json.js.map +1 -0
- package/dist/controllers/loader.d.ts +23 -0
- package/dist/controllers/loader.js +74 -0
- package/dist/controllers/loader.js.map +1 -0
- package/dist/controllers/manager.d.ts +58 -0
- package/dist/controllers/manager.js +399 -0
- package/dist/controllers/manager.js.map +1 -0
- package/dist/controllers/results.d.ts +5 -0
- package/dist/controllers/results.js +32 -0
- package/dist/controllers/results.js.map +1 -0
- package/dist/controllers/sqlite.d.ts +212 -0
- package/dist/controllers/sqlite.js +1009 -0
- package/dist/controllers/sqlite.js.map +1 -0
- package/dist/controllers/store.d.ts +112 -0
- package/dist/controllers/store.js +32 -0
- package/dist/controllers/store.js.map +1 -0
- package/dist/controllers/types.d.ts +159 -0
- package/dist/controllers/types.js +2 -0
- package/dist/controllers/types.js.map +1 -0
- package/dist/controllers/workflow-engine-scheduler.d.ts +25 -0
- package/dist/controllers/workflow-engine-scheduler.js +93 -0
- package/dist/controllers/workflow-engine-scheduler.js.map +1 -0
- package/dist/controllers/workflows.d.ts +27 -0
- package/dist/controllers/workflows.js +109 -0
- package/dist/controllers/workflows.js.map +1 -0
- package/dist/extension/controller-host.d.ts +47 -0
- package/dist/extension/controller-host.js +110 -0
- package/dist/extension/controller-host.js.map +1 -0
- package/dist/extension/executor.js +1 -1
- package/dist/extension/executor.js.map +1 -1
- package/dist/extension/index.d.ts +7 -0
- package/dist/extension/index.js +1053 -96
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/recorder.d.ts +6 -0
- package/dist/extension/recorder.js +20 -8
- package/dist/extension/recorder.js.map +1 -1
- package/dist/extension/workflow-tool.d.ts +28 -0
- package/dist/extension/workflow-tool.js +33 -0
- package/dist/extension/workflow-tool.js.map +1 -0
- package/dist/host/processes.d.ts +24 -0
- package/dist/host/processes.js +114 -0
- package/dist/host/processes.js.map +1 -0
- package/dist/host/rpc-bridge.d.ts +9 -0
- package/dist/host/rpc-bridge.js +39 -0
- package/dist/host/rpc-bridge.js.map +1 -0
- package/dist/host/rpc-executor.d.ts +38 -0
- package/dist/host/rpc-executor.js +254 -0
- package/dist/host/rpc-executor.js.map +1 -0
- package/dist/host/runner.d.ts +49 -0
- package/dist/host/runner.js +350 -0
- package/dist/host/runner.js.map +1 -0
- package/dist/viewer/cli.d.ts +7 -3
- package/dist/viewer/cli.js +150 -19
- package/dist/viewer/cli.js.map +1 -1
- package/dist/workflows/engine.d.ts +36 -0
- package/dist/workflows/engine.js +244 -14
- package/dist/workflows/engine.js.map +1 -1
- package/dist/workflows/errors.d.ts +23 -0
- package/dist/workflows/errors.js +38 -0
- package/dist/workflows/errors.js.map +1 -1
- package/dist/workflows/graph.js +0 -5
- package/dist/workflows/graph.js.map +1 -1
- package/dist/workflows/loader.d.ts +5 -3
- package/dist/workflows/loader.js +10 -1
- package/dist/workflows/loader.js.map +1 -1
- package/dist/workflows/schema.js +1 -1
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/store.d.ts +50 -6
- package/dist/workflows/store.js +446 -51
- package/dist/workflows/store.js.map +1 -1
- package/dist/workflows/types.d.ts +10 -0
- package/docs/CONTROLLERS.md +215 -0
- package/docs/development.md +12 -9
- package/docs/plans/2026-08-04-controller-runtime-plan.md +169 -0
- package/docs/plans/2026-08-05-always-on-workflows-plan.md +125 -0
- package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +184 -0
- package/docs/run-bundles.md +66 -27
- package/docs/workflows.md +131 -11
- package/examples/controllers/pull-request.controller.ts +215 -0
- package/package.json +10 -2
- package/src/builtins/monitor.workflow.ts +278 -0
- package/src/controllers/conditions.ts +110 -0
- package/src/controllers/definition.ts +65 -0
- package/src/controllers/effects.ts +123 -0
- package/src/controllers/errors.ts +27 -0
- package/src/controllers/index.ts +90 -0
- package/src/controllers/json.ts +62 -0
- package/src/controllers/loader.ts +104 -0
- package/src/controllers/manager.ts +533 -0
- package/src/controllers/results.ts +46 -0
- package/src/controllers/sqlite.ts +1427 -0
- package/src/controllers/store.ts +160 -0
- package/src/controllers/types.ts +183 -0
- package/src/controllers/workflow-engine-scheduler.ts +145 -0
- package/src/controllers/workflows.ts +152 -0
- package/src/extension/controller-host.ts +163 -0
- package/src/extension/executor.ts +1 -1
- package/src/extension/index.ts +1243 -117
- package/src/extension/recorder.ts +65 -36
- package/src/extension/workflow-tool.ts +59 -0
- package/src/host/processes.ts +119 -0
- package/src/host/rpc-bridge.ts +44 -0
- package/src/host/rpc-executor.ts +299 -0
- package/src/host/runner.ts +406 -0
- package/src/viewer/cli.ts +167 -21
- package/src/workflows/engine.ts +327 -13
- package/src/workflows/errors.ts +45 -0
- package/src/workflows/graph.ts +0 -5
- package/src/workflows/loader.ts +13 -3
- package/src/workflows/schema.ts +1 -1
- package/src/workflows/store.ts +555 -46
- package/src/workflows/types.ts +10 -0
package/README.md
CHANGED
|
@@ -71,16 +71,16 @@ Then, from any pi conversation:
|
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
`/workflow` with no arguments lists discovered workflows. `/workflow pause`
|
|
74
|
-
lets the current step finish and then holds the run before the next node
|
|
75
|
-
useful when you want to interject in the conversation mid-workflow
|
|
76
|
-
|
|
74
|
+
lets the current step finish and then holds the run before the next node. This
|
|
75
|
+
is useful when you want to interject in the conversation mid-workflow.
|
|
76
|
+
`/workflow resume` continues it. Pressing escape to interrupt a turn
|
|
77
77
|
pauses the workflow automatically, so the run never nudges the model while
|
|
78
78
|
you have taken the conversation back; `/workflow resume` re-delivers the
|
|
79
79
|
pending step prompt. `/workflow cancel` stops the active run; if the last run
|
|
80
80
|
already ended (for example parked at a checkpoint), it clears the leftover
|
|
81
81
|
widget instead. Trailing text becomes `{ task: "..." }`; pass arbitrary input
|
|
82
|
-
with `--input-json {"key": "value"}`. The names `cancel`, `list`, `pause`,
|
|
83
|
-
|
|
82
|
+
with `--input-json {"key": "value"}`. The names `answer`, `cancel`, `list`, `pause`, `resume`, and `status` are
|
|
83
|
+
reserved and rejected as workflow names.
|
|
84
84
|
|
|
85
85
|
While a run is on screen, the footer status bar shows a compact
|
|
86
86
|
`wf <name> [status] <node>` indicator alongside the widget.
|
|
@@ -90,6 +90,26 @@ structured run ends to request one normal, human-readable assistant response.
|
|
|
90
90
|
Workflows without it remain silent after their final structured output, which
|
|
91
91
|
keeps shell-only and machine-consumed workflows model-free.
|
|
92
92
|
|
|
93
|
+
## Agent-managed workflows
|
|
94
|
+
|
|
95
|
+
The model can use the same `workflow` tool to list, start, inspect, pause,
|
|
96
|
+
resume, cancel, and answer workflows. Step contracts use the tool's `submit`
|
|
97
|
+
action. Slash commands and model actions share one lifecycle implementation.
|
|
98
|
+
|
|
99
|
+
Pi Workflows includes a `monitor` workflow for plain-language requests such as:
|
|
100
|
+
|
|
101
|
+
> Monitor PR 123 every 30 minutes. Report failed checks. Stop when it is merged or closed.
|
|
102
|
+
|
|
103
|
+
The monitor checks immediately, reports only the states requested by the user,
|
|
104
|
+
waits with a normal shell action, and loops until its stop condition or check
|
|
105
|
+
limit. Its input supports `task`, `everyMinutes`, `reportWhen`, `stopWhen`, and
|
|
106
|
+
`maxChecks`. Project and global workflows can replace the built-in `monitor`
|
|
107
|
+
by using the same file name.
|
|
108
|
+
|
|
109
|
+
A monitor occupies the session's one active workflow slot. If its Pi runner
|
|
110
|
+
stops during the shell wait, the run parks and repeats that wait node when a
|
|
111
|
+
runner resumes it.
|
|
112
|
+
|
|
93
113
|
Because the workflow runs in your current conversation, you can have a long
|
|
94
114
|
discussion first and then trigger a workflow that builds on it. The
|
|
95
115
|
`elegant-solution` example does exactly that. It asks the model for the most
|
|
@@ -168,6 +188,48 @@ the model to pick from a fixed set of choices and validates the answer, and
|
|
|
168
188
|
See [docs/workflows.md](docs/workflows.md) for the full authoring reference
|
|
169
189
|
and [docs/run-bundles.md](docs/run-bundles.md) for the on-disk run format.
|
|
170
190
|
|
|
191
|
+
## Controllers
|
|
192
|
+
|
|
193
|
+
Controllers keep long-running automation aligned with current external state. They store desired state in `spec`, report observed state through conditions and `status`, and reconcile a deduplicated resource key whenever an event or retry makes it ready.
|
|
194
|
+
|
|
195
|
+
Put `*.controller.ts` files in `.pi/controllers/` or `~/.pi/agent/controllers/`. Import the API from `@osolmaz/pi-workflows/controllers`:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
import { conditionTrue, defineController } from "@osolmaz/pi-workflows/controllers";
|
|
199
|
+
|
|
200
|
+
export default defineController({
|
|
201
|
+
name: "example",
|
|
202
|
+
initialStatus: () => ({ phase: "new" }),
|
|
203
|
+
reconcile: (ctx, resource) =>
|
|
204
|
+
ctx.settled({
|
|
205
|
+
controllerStatus: { phase: "done" },
|
|
206
|
+
conditions: [conditionTrue("Ready", "Complete")],
|
|
207
|
+
}),
|
|
208
|
+
});
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Apply and inspect resources from Pi:
|
|
212
|
+
|
|
213
|
+
```text
|
|
214
|
+
/controller apply example item-1 {"enabled":true}
|
|
215
|
+
/controller get example item-1
|
|
216
|
+
/controller reconcile example item-1
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The standalone CLI provides read-only views with `pi-workflows controllers` and `pi-workflows controller <controller> <key>`. See [docs/CONTROLLERS.md](docs/CONTROLLERS.md) for reconciliation, queue, effect, and child workflow semantics.
|
|
220
|
+
|
|
221
|
+
## Always-on workflows
|
|
222
|
+
|
|
223
|
+
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, and any runner can **resume** it later at the node it stopped on. Reopening Pi resumes parked runs itself and catches the session up through the shared event feed; a checkpointed run waits durably until you answer it with `/workflow answer <json>`, which continues the graph in a linked run.
|
|
224
|
+
|
|
225
|
+
For runs that must continue while Pi is closed, keep the standalone host running:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
pi-workflows host --project /path/to/project
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
The host claims parked runs and reconciles controllers without a Pi session. Conversation nodes execute in headless `pi --mode rpc` children that expose the same `workflow` tool contract. It is a foreground process — stop it with Ctrl-C; a crashed host's leftovers are reaped by the next one. See [docs/workflows.md](docs/workflows.md#durable-runs-parking-and-resume) for the model and [docs/run-bundles.md](docs/run-bundles.md) for the on-disk rules.
|
|
232
|
+
|
|
171
233
|
## Examples
|
|
172
234
|
|
|
173
235
|
The [examples/workflows/](examples/workflows/) directory mirrors the acpx
|
|
@@ -192,6 +254,10 @@ example set. Copy any of them into `.pi/workflows/` to use them:
|
|
|
192
254
|
result plateaus or a diverse generation all fails, then conclusions are
|
|
193
255
|
written before the winner is promoted out of the loop directory.
|
|
194
256
|
|
|
257
|
+
The controller example at `examples/controllers/pull-request.controller.ts`
|
|
258
|
+
shows child repair work and check polling. It also uses expected-head guards
|
|
259
|
+
and recoverable merge effects.
|
|
260
|
+
|
|
195
261
|
## License
|
|
196
262
|
|
|
197
263
|
[MIT](LICENSE)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
declare const _default: {
|
|
2
|
+
name: string;
|
|
3
|
+
title: ({ input }: {
|
|
4
|
+
input: unknown;
|
|
5
|
+
workflowName: string;
|
|
6
|
+
}) => string;
|
|
7
|
+
presentationPrompt: ({ finalOutput }: import("../workflows/types.js").WorkflowPresentationContext) => string | undefined;
|
|
8
|
+
startAt: string;
|
|
9
|
+
maxSteps: number;
|
|
10
|
+
nodes: {
|
|
11
|
+
prepare: import("../workflows/types.js").ComputeNodeDefinition;
|
|
12
|
+
guard: import("../workflows/types.js").ComputeNodeDefinition;
|
|
13
|
+
continue_guard: import("../workflows/types.js").ComputeNodeDefinition;
|
|
14
|
+
check: import("../workflows/types.js").AgentNodeDefinition;
|
|
15
|
+
report_continue: import("../workflows/types.js").AgentNodeDefinition;
|
|
16
|
+
report_stop: import("../workflows/types.js").AgentNodeDefinition;
|
|
17
|
+
sleep: import("../workflows/types.js").ShellActionNodeDefinition;
|
|
18
|
+
finish: import("../workflows/types.js").ComputeNodeDefinition;
|
|
19
|
+
};
|
|
20
|
+
edges: ({
|
|
21
|
+
from: string;
|
|
22
|
+
to: string;
|
|
23
|
+
switch?: never;
|
|
24
|
+
} | {
|
|
25
|
+
to?: never;
|
|
26
|
+
from: string;
|
|
27
|
+
switch: {
|
|
28
|
+
on: string;
|
|
29
|
+
cases: {
|
|
30
|
+
check: string;
|
|
31
|
+
stop: string;
|
|
32
|
+
continue_quiet?: never;
|
|
33
|
+
continue_report?: never;
|
|
34
|
+
stop_quiet?: never;
|
|
35
|
+
stop_report?: never;
|
|
36
|
+
sleep?: never;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
} | {
|
|
40
|
+
to?: never;
|
|
41
|
+
from: string;
|
|
42
|
+
switch: {
|
|
43
|
+
on: string;
|
|
44
|
+
cases: {
|
|
45
|
+
check?: never;
|
|
46
|
+
continue_quiet: string;
|
|
47
|
+
continue_report: string;
|
|
48
|
+
stop_quiet: string;
|
|
49
|
+
stop_report: string;
|
|
50
|
+
sleep?: never;
|
|
51
|
+
stop?: never;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
} | {
|
|
55
|
+
to?: never;
|
|
56
|
+
from: string;
|
|
57
|
+
switch: {
|
|
58
|
+
on: string;
|
|
59
|
+
cases: {
|
|
60
|
+
check?: never;
|
|
61
|
+
continue_quiet?: never;
|
|
62
|
+
continue_report?: never;
|
|
63
|
+
stop_quiet?: never;
|
|
64
|
+
stop_report?: never;
|
|
65
|
+
sleep: string;
|
|
66
|
+
stop: string;
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
})[];
|
|
70
|
+
};
|
|
71
|
+
export default _default;
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { agent, compute, defineWorkflow, shell } from "../workflows/index.js";
|
|
2
|
+
const MIN_INTERVAL_MINUTES = 1;
|
|
3
|
+
const MAX_INTERVAL_MINUTES = 24 * 60;
|
|
4
|
+
const DEFAULT_MAX_CHECKS = 1_000;
|
|
5
|
+
const MAX_CHECKS = 1_000;
|
|
6
|
+
const MAX_OBSERVATION_CHARS = 8_000;
|
|
7
|
+
const MAX_REPORT_CHARS = 4_000;
|
|
8
|
+
const MAX_REASON_CHARS = 2_000;
|
|
9
|
+
const SLEEP_TIMEOUT_MARGIN_MS = 60_000;
|
|
10
|
+
const NODE_TIMEOUT_MARGIN_MS = 2 * 60_000;
|
|
11
|
+
const MONITOR_ROUTES = new Set([
|
|
12
|
+
"continue_quiet",
|
|
13
|
+
"continue_report",
|
|
14
|
+
"stop_quiet",
|
|
15
|
+
"stop_report",
|
|
16
|
+
]);
|
|
17
|
+
function requireRecord(value, label) {
|
|
18
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
19
|
+
throw new Error(`${label} must be an object`);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
function requireBoundedString(value, label, maxChars) {
|
|
24
|
+
if (typeof value !== "string") {
|
|
25
|
+
throw new Error(`${label} must be a string`);
|
|
26
|
+
}
|
|
27
|
+
const trimmed = value.trim();
|
|
28
|
+
if (trimmed.length === 0) {
|
|
29
|
+
throw new Error(`${label} must not be empty`);
|
|
30
|
+
}
|
|
31
|
+
if (trimmed.length > maxChars) {
|
|
32
|
+
throw new Error(`${label} must be at most ${maxChars} characters`);
|
|
33
|
+
}
|
|
34
|
+
return trimmed;
|
|
35
|
+
}
|
|
36
|
+
function prepareInput(input) {
|
|
37
|
+
const value = requireRecord(input, "monitor input");
|
|
38
|
+
const task = requireBoundedString(value.task, "task", 8_000);
|
|
39
|
+
if (typeof value.everyMinutes !== "number" ||
|
|
40
|
+
!Number.isInteger(value.everyMinutes) ||
|
|
41
|
+
value.everyMinutes < MIN_INTERVAL_MINUTES ||
|
|
42
|
+
value.everyMinutes > MAX_INTERVAL_MINUTES) {
|
|
43
|
+
throw new Error(`everyMinutes must be an integer from ${MIN_INTERVAL_MINUTES} through ${MAX_INTERVAL_MINUTES}`);
|
|
44
|
+
}
|
|
45
|
+
const maxChecks = value.maxChecks ?? DEFAULT_MAX_CHECKS;
|
|
46
|
+
if (!Number.isInteger(maxChecks) || maxChecks <= 0 || maxChecks > MAX_CHECKS) {
|
|
47
|
+
throw new Error(`maxChecks must be an integer from 1 through ${MAX_CHECKS}`);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
task,
|
|
51
|
+
everyMinutes: value.everyMinutes,
|
|
52
|
+
reportWhen: value.reportWhen === undefined
|
|
53
|
+
? "The observed state changes materially or needs the user's attention."
|
|
54
|
+
: requireBoundedString(value.reportWhen, "reportWhen", 4_000),
|
|
55
|
+
stopWhen: value.stopWhen === undefined
|
|
56
|
+
? "The user cancels the monitor or it reaches its maximum check count."
|
|
57
|
+
: requireBoundedString(value.stopWhen, "stopWhen", 4_000),
|
|
58
|
+
maxChecks,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function configFrom(outputs) {
|
|
62
|
+
return outputs.prepare;
|
|
63
|
+
}
|
|
64
|
+
function completedChecks(context) {
|
|
65
|
+
return context.state.steps.filter((step) => step.nodeId === "check" && step.outcome === "ok")
|
|
66
|
+
.length;
|
|
67
|
+
}
|
|
68
|
+
function validateCheck(output) {
|
|
69
|
+
const value = requireRecord(output, "monitor check output");
|
|
70
|
+
if (typeof value.route !== "string" || !MONITOR_ROUTES.has(value.route)) {
|
|
71
|
+
throw new Error(`route must be one of ${[...MONITOR_ROUTES].join(", ")}`);
|
|
72
|
+
}
|
|
73
|
+
const route = value.route;
|
|
74
|
+
const observation = requireBoundedString(value.observation, "observation", MAX_OBSERVATION_CHARS);
|
|
75
|
+
const reason = requireBoundedString(value.reason, "reason", MAX_REASON_CHARS);
|
|
76
|
+
const reports = route === "continue_report" || route === "stop_report";
|
|
77
|
+
const report = value.report === undefined
|
|
78
|
+
? undefined
|
|
79
|
+
: requireBoundedString(value.report, "report", MAX_REPORT_CHARS);
|
|
80
|
+
if (reports && report === undefined) {
|
|
81
|
+
throw new Error(`route ${route} requires a report`);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
route,
|
|
85
|
+
observation,
|
|
86
|
+
...(report !== undefined ? { report } : {}),
|
|
87
|
+
reason,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function validateReportAck(output) {
|
|
91
|
+
const value = requireRecord(output, "report acknowledgement");
|
|
92
|
+
if (value.reported !== true) {
|
|
93
|
+
throw new Error("report acknowledgement must set reported to true");
|
|
94
|
+
}
|
|
95
|
+
return { reported: true };
|
|
96
|
+
}
|
|
97
|
+
function reportPrompt(outputs) {
|
|
98
|
+
const check = outputs.check;
|
|
99
|
+
return [
|
|
100
|
+
"Write one concise normal assistant message to the user with this monitoring update:",
|
|
101
|
+
check.report ?? check.observation,
|
|
102
|
+
"Do not add unrelated detail.",
|
|
103
|
+
"After writing the update, submit the acknowledgement required by the workflow step contract.",
|
|
104
|
+
].join("\n\n");
|
|
105
|
+
}
|
|
106
|
+
export default defineWorkflow({
|
|
107
|
+
name: "monitor",
|
|
108
|
+
title: ({ input }) => {
|
|
109
|
+
try {
|
|
110
|
+
const task = prepareInput(input).task;
|
|
111
|
+
return `monitor: ${task.slice(0, 80)}`;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return "monitor";
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
presentationPrompt: ({ finalOutput }) => {
|
|
118
|
+
const result = requireRecord(finalOutput, "monitor result");
|
|
119
|
+
if (result.reported === true) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
return `Tell the user concisely why this monitor stopped: ${String(result.reason ?? "monitor ended")}`;
|
|
123
|
+
},
|
|
124
|
+
startAt: "prepare",
|
|
125
|
+
maxSteps: 5_010,
|
|
126
|
+
nodes: {
|
|
127
|
+
prepare: compute({
|
|
128
|
+
run: ({ input }) => prepareInput(input),
|
|
129
|
+
}),
|
|
130
|
+
guard: compute({
|
|
131
|
+
run: (context) => {
|
|
132
|
+
const config = configFrom(context.outputs);
|
|
133
|
+
const checks = completedChecks(context);
|
|
134
|
+
return checks >= config.maxChecks
|
|
135
|
+
? { route: "stop", checks, reason: `Reached the ${config.maxChecks}-check limit.` }
|
|
136
|
+
: { route: "check", checks };
|
|
137
|
+
},
|
|
138
|
+
}),
|
|
139
|
+
continue_guard: compute({
|
|
140
|
+
run: (context) => {
|
|
141
|
+
const config = configFrom(context.outputs);
|
|
142
|
+
const checks = completedChecks(context);
|
|
143
|
+
return checks >= config.maxChecks
|
|
144
|
+
? { route: "stop", checks, reason: `Reached the ${config.maxChecks}-check limit.` }
|
|
145
|
+
: { route: "sleep", checks };
|
|
146
|
+
},
|
|
147
|
+
}),
|
|
148
|
+
check: agent({
|
|
149
|
+
statusDetail: "checking monitored target",
|
|
150
|
+
prompt: (context) => {
|
|
151
|
+
const config = configFrom(context.outputs);
|
|
152
|
+
const previous = context.outputs.check;
|
|
153
|
+
const checkNumber = completedChecks(context) + 1;
|
|
154
|
+
return [
|
|
155
|
+
`Perform monitoring check ${checkNumber} of at most ${config.maxChecks}.`,
|
|
156
|
+
`Task: ${config.task}`,
|
|
157
|
+
`Report when: ${config.reportWhen}`,
|
|
158
|
+
`Stop when: ${config.stopWhen}`,
|
|
159
|
+
previous === undefined
|
|
160
|
+
? "There is no previous observation. Report the initial state only when the report condition calls for it."
|
|
161
|
+
: `Previous accepted observation: ${previous.observation}`,
|
|
162
|
+
"Use available tools to inspect the current state. Observe only unless the task explicitly authorizes a mutation.",
|
|
163
|
+
"Choose continue_quiet, continue_report, stop_quiet, or stop_report. A report route requires concise report text.",
|
|
164
|
+
].join("\n\n");
|
|
165
|
+
},
|
|
166
|
+
expectedOutput: '{ "route": "continue_quiet" | "continue_report" | "stop_quiet" | "stop_report", "observation": "current factual state", "report": "required for report routes", "reason": "short reason" }',
|
|
167
|
+
validate: (output) => validateCheck(output),
|
|
168
|
+
}),
|
|
169
|
+
report_continue: agent({
|
|
170
|
+
statusDetail: "reporting monitor update",
|
|
171
|
+
prompt: ({ outputs }) => reportPrompt(outputs),
|
|
172
|
+
expectedOutput: '{ "reported": true }',
|
|
173
|
+
validate: (output) => validateReportAck(output),
|
|
174
|
+
}),
|
|
175
|
+
report_stop: agent({
|
|
176
|
+
statusDetail: "reporting final monitor update",
|
|
177
|
+
prompt: ({ outputs }) => reportPrompt(outputs),
|
|
178
|
+
expectedOutput: '{ "reported": true }',
|
|
179
|
+
validate: (output) => validateReportAck(output),
|
|
180
|
+
}),
|
|
181
|
+
sleep: shell({
|
|
182
|
+
statusDetail: "waiting for next monitor check",
|
|
183
|
+
timeoutMs: MAX_INTERVAL_MINUTES * 60_000 + NODE_TIMEOUT_MARGIN_MS,
|
|
184
|
+
exec: ({ outputs }) => {
|
|
185
|
+
const config = configFrom(outputs);
|
|
186
|
+
const sleepMs = config.everyMinutes * 60_000;
|
|
187
|
+
return {
|
|
188
|
+
command: process.execPath,
|
|
189
|
+
args: ["-e", "setTimeout(() => {}, Number(process.argv[1]))", String(sleepMs)],
|
|
190
|
+
timeoutMs: sleepMs + SLEEP_TIMEOUT_MARGIN_MS,
|
|
191
|
+
maxOutputChars: 1_024,
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
parse: (_result, { outputs }) => ({ waitedMinutes: configFrom(outputs).everyMinutes }),
|
|
195
|
+
}),
|
|
196
|
+
finish: compute({
|
|
197
|
+
run: ({ outputs }) => {
|
|
198
|
+
const check = outputs.check;
|
|
199
|
+
const guard = outputs.guard;
|
|
200
|
+
const continueGuard = outputs.continue_guard;
|
|
201
|
+
return {
|
|
202
|
+
reason: continueGuard?.reason ?? guard?.reason ?? check?.reason ?? "Monitor finished.",
|
|
203
|
+
observation: check?.observation ?? null,
|
|
204
|
+
reported: outputs.report_stop !== undefined ||
|
|
205
|
+
(check !== undefined && check.route === "stop_report"),
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
}),
|
|
209
|
+
},
|
|
210
|
+
edges: [
|
|
211
|
+
{ from: "prepare", to: "guard" },
|
|
212
|
+
{ from: "guard", switch: { on: "$.route", cases: { check: "check", stop: "finish" } } },
|
|
213
|
+
{
|
|
214
|
+
from: "check",
|
|
215
|
+
switch: {
|
|
216
|
+
on: "$.route",
|
|
217
|
+
cases: {
|
|
218
|
+
continue_quiet: "continue_guard",
|
|
219
|
+
continue_report: "report_continue",
|
|
220
|
+
stop_quiet: "finish",
|
|
221
|
+
stop_report: "report_stop",
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
{ from: "report_continue", to: "continue_guard" },
|
|
226
|
+
{ from: "report_stop", to: "finish" },
|
|
227
|
+
{
|
|
228
|
+
from: "continue_guard",
|
|
229
|
+
switch: { on: "$.route", cases: { sleep: "sleep", stop: "finish" } },
|
|
230
|
+
},
|
|
231
|
+
{ from: "sleep", to: "guard" },
|
|
232
|
+
],
|
|
233
|
+
});
|
|
234
|
+
//# sourceMappingURL=monitor.workflow.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"monitor.workflow.js","sourceRoot":"","sources":["../../src/builtins/monitor.workflow.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ControllerCondition, ControllerConditionInput, ControllerResourceStatus, ControllerStatusPatch } from "./types.js";
|
|
2
|
+
export declare function conditionTrue(type: string, reason: string, message?: string): ControllerConditionInput;
|
|
3
|
+
export declare function conditionFalse(type: string, reason: string, message?: string): ControllerConditionInput;
|
|
4
|
+
export declare function conditionUnknown(type: string, reason: string, message?: string): ControllerConditionInput;
|
|
5
|
+
export declare function applyStatusPatch<TStatus>(current: ControllerResourceStatus<TStatus>, patch: ControllerStatusPatch<TStatus> | undefined, generation: number, now: string): ControllerResourceStatus<TStatus>;
|
|
6
|
+
export declare function mergeConditions(current: ControllerCondition[], updates: ControllerConditionInput[], generation: number, now: string): ControllerCondition[];
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export function conditionTrue(type, reason, message) {
|
|
2
|
+
return condition(type, true, reason, message);
|
|
3
|
+
}
|
|
4
|
+
export function conditionFalse(type, reason, message) {
|
|
5
|
+
return condition(type, false, reason, message);
|
|
6
|
+
}
|
|
7
|
+
export function conditionUnknown(type, reason, message) {
|
|
8
|
+
return condition(type, "unknown", reason, message);
|
|
9
|
+
}
|
|
10
|
+
export function applyStatusPatch(current, patch, generation, now) {
|
|
11
|
+
return {
|
|
12
|
+
observedGeneration: generation,
|
|
13
|
+
conditions: mergeConditions(current.conditions, patch?.conditions ?? [], generation, now),
|
|
14
|
+
controllerStatus: patch !== undefined && Object.hasOwn(patch, "controllerStatus")
|
|
15
|
+
? patch.controllerStatus
|
|
16
|
+
: current.controllerStatus,
|
|
17
|
+
...(patch?.workflowRun === null
|
|
18
|
+
? {}
|
|
19
|
+
: patch?.workflowRun !== undefined
|
|
20
|
+
? { workflowRun: patch.workflowRun }
|
|
21
|
+
: current.workflowRun !== undefined
|
|
22
|
+
? { workflowRun: current.workflowRun }
|
|
23
|
+
: {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function mergeConditions(current, updates, generation, now) {
|
|
27
|
+
const byType = new Map(current.map((item) => [item.type, item]));
|
|
28
|
+
const seen = new Set();
|
|
29
|
+
for (const update of updates) {
|
|
30
|
+
validateCondition(update);
|
|
31
|
+
if (seen.has(update.type)) {
|
|
32
|
+
throw new Error(`Condition ${JSON.stringify(update.type)} was updated more than once`);
|
|
33
|
+
}
|
|
34
|
+
seen.add(update.type);
|
|
35
|
+
const previous = byType.get(update.type);
|
|
36
|
+
const next = {
|
|
37
|
+
...update,
|
|
38
|
+
observedGeneration: generation,
|
|
39
|
+
lastTransitionTime: previous !== undefined && previous.status === update.status
|
|
40
|
+
? previous.lastTransitionTime
|
|
41
|
+
: now,
|
|
42
|
+
};
|
|
43
|
+
byType.set(update.type, next);
|
|
44
|
+
}
|
|
45
|
+
return [...byType.values()];
|
|
46
|
+
}
|
|
47
|
+
function condition(type, status, reason, message) {
|
|
48
|
+
const value = {
|
|
49
|
+
type,
|
|
50
|
+
status,
|
|
51
|
+
reason,
|
|
52
|
+
...(message !== undefined ? { message } : {}),
|
|
53
|
+
};
|
|
54
|
+
validateCondition(value);
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
function validateCondition(value) {
|
|
58
|
+
if (value.type.trim().length === 0) {
|
|
59
|
+
throw new Error("Condition type must not be empty");
|
|
60
|
+
}
|
|
61
|
+
if (value.reason.trim().length === 0) {
|
|
62
|
+
throw new Error("Condition reason must not be empty");
|
|
63
|
+
}
|
|
64
|
+
if (value.status !== true && value.status !== false && value.status !== "unknown") {
|
|
65
|
+
throw new Error(`Invalid condition status for ${JSON.stringify(value.type)}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=conditions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conditions.js","sourceRoot":"","sources":["../../src/controllers/conditions.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,aAAa,CAC3B,IAAY,EACZ,MAAc,EACd,OAAgB;IAEhB,OAAO,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,IAAY,EACZ,MAAc,EACd,OAAgB;IAEhB,OAAO,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,MAAc,EACd,OAAgB;IAEhB,OAAO,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,OAA0C,EAC1C,KAAiD,EACjD,UAAkB,EAClB,GAAW;IAEX,OAAO;QACL,kBAAkB,EAAE,UAAU;QAC9B,UAAU,EAAE,eAAe,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,EAAE,UAAU,IAAI,EAAE,EAAE,UAAU,EAAE,GAAG,CAAC;QACzF,gBAAgB,EACd,KAAK,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,kBAAkB,CAAC;YAC7D,CAAC,CAAE,KAAK,CAAC,gBAA4B;YACrC,CAAC,CAAC,OAAO,CAAC,gBAAgB;QAC9B,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI;YAC7B,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,KAAK,EAAE,WAAW,KAAK,SAAS;gBAChC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE;gBACpC,CAAC,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS;oBACjC,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAE;oBACtC,CAAC,CAAC,EAAE,CAAC;KACZ,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,OAA8B,EAC9B,OAAmC,EACnC,UAAkB,EAClB,GAAW;IAEX,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;IACjE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC1B,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,aAAa,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;QACzF,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACtB,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzC,MAAM,IAAI,GAAwB;YAChC,GAAG,MAAM;YACT,kBAAkB,EAAE,UAAU;YAC9B,kBAAkB,EAChB,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,MAAM,CAAC,MAAM;gBACzD,CAAC,CAAC,QAAQ,CAAC,kBAAkB;gBAC7B,CAAC,CAAC,GAAG;SACV,CAAC;QACF,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;AAC9B,CAAC;AAED,SAAS,SAAS,CAChB,IAAY,EACZ,MAAiC,EACjC,MAAc,EACd,OAA2B;IAE3B,MAAM,KAAK,GAA6B;QACtC,IAAI;QACJ,MAAM;QACN,MAAM;QACN,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC9C,CAAC;IACF,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACzB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,iBAAiB,CAAC,KAA+B;IACxD,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAClF,MAAM,IAAI,KAAK,CAAC,gCAAgC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChF,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AnyControllerDefinition, ControllerDefinition, ControllerResource } from "./types.js";
|
|
2
|
+
export declare function defineController<TSpec, TStatus>(definition: ControllerDefinition<TSpec, TStatus>): ControllerDefinition<TSpec, TStatus>;
|
|
3
|
+
export declare function isControllerDefinition(value: unknown): value is AnyControllerDefinition;
|
|
4
|
+
export declare function assertValidControllerDefinition<TSpec, TStatus>(definition: ControllerDefinition<TSpec, TStatus>): void;
|
|
5
|
+
export declare function asAnyControllerDefinition<TSpec, TStatus>(definition: ControllerDefinition<TSpec, TStatus>): AnyControllerDefinition;
|
|
6
|
+
export declare function asTypedResource<TSpec, TStatus>(resource: ControllerResource): ControllerResource<TSpec, TStatus>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const CONTROLLER_DEFINITION_BRAND = Symbol.for("pi-workflows.controller-definition");
|
|
2
|
+
const CONTROLLER_NAME_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
3
|
+
export function defineController(definition) {
|
|
4
|
+
assertValidControllerDefinition(definition);
|
|
5
|
+
if (isControllerDefinition(definition)) {
|
|
6
|
+
return definition;
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(definition, CONTROLLER_DEFINITION_BRAND, {
|
|
9
|
+
value: true,
|
|
10
|
+
enumerable: false,
|
|
11
|
+
configurable: false,
|
|
12
|
+
writable: false,
|
|
13
|
+
});
|
|
14
|
+
return definition;
|
|
15
|
+
}
|
|
16
|
+
export function isControllerDefinition(value) {
|
|
17
|
+
return (value !== null &&
|
|
18
|
+
typeof value === "object" &&
|
|
19
|
+
value[CONTROLLER_DEFINITION_BRAND] === true);
|
|
20
|
+
}
|
|
21
|
+
export function assertValidControllerDefinition(definition) {
|
|
22
|
+
if (definition === null || typeof definition !== "object") {
|
|
23
|
+
throw new Error("Invalid controller definition: expected an object");
|
|
24
|
+
}
|
|
25
|
+
if (!CONTROLLER_NAME_PATTERN.test(definition.name)) {
|
|
26
|
+
throw new Error(`Invalid controller definition: name ${JSON.stringify(definition.name)} must match ${CONTROLLER_NAME_PATTERN.source}`);
|
|
27
|
+
}
|
|
28
|
+
if (typeof definition.initialStatus !== "function") {
|
|
29
|
+
throw new Error("Invalid controller definition: initialStatus must be a function");
|
|
30
|
+
}
|
|
31
|
+
if (typeof definition.reconcile !== "function") {
|
|
32
|
+
throw new Error("Invalid controller definition: reconcile must be a function");
|
|
33
|
+
}
|
|
34
|
+
if (definition.timeoutMs !== undefined &&
|
|
35
|
+
(!Number.isSafeInteger(definition.timeoutMs) || definition.timeoutMs <= 0)) {
|
|
36
|
+
throw new Error("Invalid controller definition: timeoutMs must be a positive safe integer");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function asAnyControllerDefinition(definition) {
|
|
40
|
+
return definition;
|
|
41
|
+
}
|
|
42
|
+
export function asTypedResource(resource) {
|
|
43
|
+
return resource;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=definition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definition.js","sourceRoot":"","sources":["../../src/controllers/definition.ts"],"names":[],"mappings":"AAEA,MAAM,2BAA2B,GAAG,MAAM,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;AACrF,MAAM,uBAAuB,GAAG,mBAAmB,CAAC;AAEpD,MAAM,UAAU,gBAAgB,CAC9B,UAAgD;IAEhD,+BAA+B,CAAC,UAAU,CAAC,CAAC;IAC5C,IAAI,sBAAsB,CAAC,UAAU,CAAC,EAAE,CAAC;QACvC,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,2BAA2B,EAAE;QAC7D,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,KAAK;QACjB,YAAY,EAAE,KAAK;QACnB,QAAQ,EAAE,KAAK;KAChB,CAAC,CAAC;IACH,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,KAAc;IACnD,OAAO,CACL,KAAK,KAAK,IAAI;QACd,OAAO,KAAK,KAAK,QAAQ;QACxB,KAAsC,CAAC,2BAA2B,CAAC,KAAK,IAAI,CAC9E,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,+BAA+B,CAC7C,UAAgD;IAEhD,IAAI,UAAU,KAAK,IAAI,IAAI,OAAO,UAAU,KAAK,QAAQ,EAAE,CAAC;QAC1D,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAC;IACvE,CAAC;IACD,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CACb,uCAAuC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,eAAe,uBAAuB,CAAC,MAAM,EAAE,CACtH,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,UAAU,CAAC,aAAa,KAAK,UAAU,EAAE,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;IACrF,CAAC;IACD,IAAI,OAAO,UAAU,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;IACjF,CAAC;IACD,IACE,UAAU,CAAC,SAAS,KAAK,SAAS;QAClC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,UAAU,CAAC,SAAS,IAAI,CAAC,CAAC,EAC1E,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,0EAA0E,CAAC,CAAC;IAC9F,CAAC;AACH,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,UAAgD;IAEhD,OAAO,UAA+D,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,QAA4B;IAE5B,OAAO,QAA8C,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ControllerStore } from "./store.js";
|
|
2
|
+
import type { ControllerEffects, ControllerResource, EffectDefinition, EffectRecord } from "./types.js";
|
|
3
|
+
export declare class ControllerEffectService implements ControllerEffects {
|
|
4
|
+
private readonly store;
|
|
5
|
+
private readonly resource;
|
|
6
|
+
private readonly signal;
|
|
7
|
+
private used;
|
|
8
|
+
constructor(store: ControllerStore, resource: ControllerResource, signal: AbortSignal);
|
|
9
|
+
ensure<TRequest>(definition: EffectDefinition<TRequest>): Promise<EffectRecord>;
|
|
10
|
+
private observe;
|
|
11
|
+
private applyObservation;
|
|
12
|
+
private apply;
|
|
13
|
+
private recordEvent;
|
|
14
|
+
}
|