@llblab/pi-actors 0.43.0 → 0.44.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.
Files changed (109) hide show
  1. package/AGENTS.md +16 -10
  2. package/CHANGELOG.md +425 -536
  3. package/README.md +13 -11
  4. package/dist/index.js +1 -1
  5. package/dist/lib/async-runs.d.ts +2 -1
  6. package/dist/lib/async-runs.js +24 -34
  7. package/dist/lib/automatic-review-runtime.d.ts +1 -1
  8. package/dist/lib/automatic-review-runtime.js +5 -5
  9. package/dist/lib/command-templates.d.ts +2 -0
  10. package/dist/lib/command-templates.js +38 -4
  11. package/dist/lib/control-projection.d.ts +20 -0
  12. package/dist/lib/control-projection.js +66 -0
  13. package/dist/lib/control.d.ts +3 -0
  14. package/dist/lib/control.js +27 -14
  15. package/dist/lib/draft-sleep.js +3 -3
  16. package/dist/lib/file-state.d.ts +4 -1
  17. package/dist/lib/file-state.js +118 -44
  18. package/dist/lib/inspector-overlay.d.ts +2 -0
  19. package/dist/lib/inspector-overlay.js +124 -69
  20. package/dist/lib/limits.d.ts +15 -3
  21. package/dist/lib/limits.js +15 -3
  22. package/dist/lib/observability.d.ts +4 -2
  23. package/dist/lib/observability.js +43 -36
  24. package/dist/lib/prompts.d.ts +1 -1
  25. package/dist/lib/prompts.js +1 -1
  26. package/dist/lib/recipe-control.js +6 -2
  27. package/dist/lib/review-control.d.ts +1 -1
  28. package/dist/lib/review-control.js +4 -5
  29. package/dist/lib/run-evidence-policy.d.ts +95 -0
  30. package/dist/lib/run-evidence-policy.js +177 -0
  31. package/dist/lib/run-ui-runtime.js +2 -0
  32. package/dist/lib/runs-control-delivery.d.ts +8 -1
  33. package/dist/lib/runs-control-delivery.js +38 -15
  34. package/dist/lib/runs-controls.d.ts +8 -4
  35. package/dist/lib/runs-controls.js +189 -50
  36. package/dist/lib/runs-retention.js +27 -14
  37. package/dist/lib/runs-trace.d.ts +26 -2
  38. package/dist/lib/runs-trace.js +411 -18
  39. package/dist/lib/runtime-identity.d.ts +7 -0
  40. package/dist/lib/runtime-identity.js +35 -0
  41. package/dist/lib/runtime-triage.d.ts +29 -0
  42. package/dist/lib/runtime-triage.js +60 -0
  43. package/dist/lib/tool-review-scheduler.js +7 -7
  44. package/dist/lib/tools-inspect.js +91 -18
  45. package/dist/lib/tools-message.d.ts +1 -2
  46. package/dist/lib/tools-message.js +6 -6
  47. package/dist/lib/tools-response.d.ts +0 -1
  48. package/dist/lib/tools-response.js +0 -9
  49. package/dist/lib/tools.d.ts +1 -1
  50. package/dist/lib/tools.js +1 -1
  51. package/dist/lib/trace-projection.js +107 -41
  52. package/dist/scripts/conformance.mjs +5 -0
  53. package/dist/scripts/locker.mjs +40 -90
  54. package/dist/scripts/music-player.mjs +48 -142
  55. package/dist/scripts/release-gates.mjs +56 -3
  56. package/dist/scripts/validate-recipe.mjs +5 -4
  57. package/dist/skills/actors/SKILL.md +17 -11
  58. package/dist/skills/swarm/SKILL.md +2 -4
  59. package/docs/README.md +1 -4
  60. package/docs/actor-inspector.md +6 -5
  61. package/docs/async-runs.md +11 -9
  62. package/docs/command-templates.md +6 -116
  63. package/docs/recipe-library.md +4 -6
  64. package/docs/releasing.md +28 -0
  65. package/docs/template-recipes.md +1 -1
  66. package/docs/tool-registry.md +2 -2
  67. package/index.ts +1 -1
  68. package/lib/async-runs.ts +26 -50
  69. package/lib/automatic-review-runtime.ts +7 -7
  70. package/lib/command-templates.ts +44 -4
  71. package/lib/control-projection.ts +105 -0
  72. package/lib/control.ts +33 -18
  73. package/lib/draft-sleep.ts +3 -3
  74. package/lib/file-state.ts +91 -63
  75. package/lib/inspector-overlay.ts +108 -61
  76. package/lib/limits.ts +15 -3
  77. package/lib/observability.ts +55 -57
  78. package/lib/prompts.ts +1 -1
  79. package/lib/recipe-control.ts +9 -2
  80. package/lib/review-control.ts +4 -5
  81. package/lib/run-evidence-policy.ts +242 -0
  82. package/lib/run-ui-runtime.ts +2 -0
  83. package/lib/runs-control-delivery.ts +45 -17
  84. package/lib/runs-controls.ts +180 -102
  85. package/lib/runs-retention.ts +28 -20
  86. package/lib/runs-trace.ts +499 -20
  87. package/lib/runtime-identity.ts +39 -0
  88. package/lib/runtime-triage.ts +106 -0
  89. package/lib/tool-review-scheduler.ts +7 -7
  90. package/lib/tools-inspect.ts +94 -20
  91. package/lib/tools-message.ts +7 -8
  92. package/lib/tools-response.ts +0 -12
  93. package/lib/tools.ts +4 -4
  94. package/lib/trace-projection.ts +156 -71
  95. package/package.json +1 -1
  96. package/scripts/conformance.mjs +5 -0
  97. package/scripts/locker.mjs +40 -90
  98. package/scripts/music-player.mjs +48 -142
  99. package/scripts/release-gates.mjs +56 -3
  100. package/scripts/validate-recipe.mjs +5 -4
  101. package/skills/actors/SKILL.md +17 -11
  102. package/skills/swarm/SKILL.md +2 -4
  103. package/dist/lib/runtime-notifier.d.ts +0 -48
  104. package/dist/lib/runtime-notifier.js +0 -138
  105. package/docs/0.43-baseline.md +0 -44
  106. package/docs/actors-deep-reference.md +0 -108
  107. package/docs/component-recipes.md +0 -45
  108. package/docs/task-first-recipes.md +0 -261
  109. package/lib/runtime-notifier.ts +0 -211
@@ -1,44 +0,0 @@
1
- # 0.43 Migration Baseline
2
-
3
- Release `0.43.0` uses commit `14e46931899347f08b3f1db94bc03a4f260e75a6` (`0.42.3`) as the frozen preservation and compression baseline.
4
-
5
- Commit `261297a2250711ca7f412a489cce4477ef899e89` has the same Git tree, so the two commits contain no product-code delta. The release gate may use the former as the canonical baseline while `dev` starts from the latter topology.
6
-
7
- ## Shipped-line ceiling
8
-
9
- The baseline contains 35,077 lines under the shipped surfaces measured by `scripts/release-gates.mjs`:
10
-
11
- - `lib/`
12
- - `scripts/`
13
- - `recipes/`
14
- - `docs/`
15
- - `skills/`
16
-
17
- Release validation fails when the retained shipped tree exceeds that ceiling. The ceiling guards the breaking communication-plane deletion against replacement bloat; it does not grant deleted communication behavior preservation status.
18
-
19
- ## Retained invariants
20
-
21
- The preservation suite keeps evidence for the safety properties that survive the migration:
22
-
23
- - owner-filtered Run discovery;
24
- - immutable `run_instance_id` fencing;
25
- - process identity checks and canonical lifecycle locking;
26
- - shutdown kill and parent teardown;
27
- - terminal reconciliation and bounded captures;
28
- - owned Pi session provenance;
29
- - path containment and structured redaction;
30
- - automatic-review retry, reset, transaction, and lineage safety;
31
- - generation-bound Control and canonical Trace evidence.
32
-
33
- Rooms, peer routing, messages, mailboxes, communication topology, and their tests do not belong to the retained baseline.
34
-
35
- ## Validation
36
-
37
- Run:
38
-
39
- ```bash
40
- npm run test:preservation
41
- npm run release:validate
42
- ```
43
-
44
- The release gate also rejects removed-surface residue, Domain DAG violations, ABCd context drift, stale package contents, and shipped-line growth above the frozen ceiling.
@@ -1,108 +0,0 @@
1
- # Actors Deep Reference
2
-
3
- ## Kernel
4
-
5
- ```text
6
- Recipe --spawn--> Run
7
- Run = Recipe + Trace + Control
8
- ```
9
-
10
- - Recipe owns executable definition and declared actor-local actions.
11
- - Run owns one generation of execution and evidence.
12
- - Trace owns observations.
13
- - Control owns actor-local inputs.
14
-
15
- `register_tool` persists capabilities but does not participate in running Control.
16
-
17
- ## Choosing Execution
18
-
19
- Use a foreground tool for short work with one natural response. Use a Run when execution may outlive the turn, needs later inspection or steering, produces artifacts, runs as a service, or coordinates repeated/parallel command-template cells.
20
-
21
- Do not background shell processes outside the Run lifecycle.
22
-
23
- ## Recipe Resolution
24
-
25
- Active user Recipes shadow packaged Recipes by name. Invalid active shadowing fails with both active and blocked fallback paths instead of silently executing another definition. Imports resolve under Recipe-root priority, enforce a 1 MiB file limit and depth limit 32, reject cycles, and act as local definitions inside one Run.
26
-
27
- Current model/thinking placeholders resolve from Pi context before launch and persist provenance in `run.json`.
28
-
29
- ## Command Templates
30
-
31
- String leaves execute without shell parsing. Arrays sequence commands. Objects add `parallel`, `concurrency`, `min_successful`, `when`, `timeout`, `delay`, `retry`, `failure`, `recover`, `repeat`, `accept_output`, and `output` behavior.
32
-
33
- Placeholders support typed args, defaults, fallback, and conditional expansion. Prefer explicit scripts when shell semantics or a maintained service loop matters.
34
-
35
- ## Control Discipline
36
-
37
- Public shape:
38
-
39
- ```json
40
- {"target":"run:<id>","action":"action","input":{},"verbose":false}
41
- ```
42
-
43
- Recipe actions use lowercase stable names. Do not declare runtime-reserved lifecycle actions. Inputs must remain bounded JSON. A controlled service publishes readiness only after its consumer can read the endpoint and includes its immutable generation id.
44
-
45
- Controls persist before transport and keep durable outcome evidence. Never infer owner identity from caller-provided input.
46
-
47
- ## Trace Discipline
48
-
49
- Trace events use stable `kind` names and concise summaries. Put structured bounded evidence in `data`; put large evidence in artifacts. Use attention sparingly:
50
-
51
- - omitted/`log`: inspectable only;
52
- - `notify`: visible status;
53
- - `followup`: semantic coordinator follow-up.
54
-
55
- Trace has no address or response semantics.
56
-
57
- ## Lifecycle Discipline
58
-
59
- Retain these invariants:
60
-
61
- - owner filtering;
62
- - immutable generation fencing;
63
- - process identity verification;
64
- - canonical lifecycle locks;
65
- - shutdown and parent-teardown kill;
66
- - terminal notification reconciliation;
67
- - bounded logs and complete captures;
68
- - owned Pi session provenance;
69
- - path containment and redaction;
70
- - review retry/reset safety.
71
-
72
- A lifecycle operation that cannot prove identity or ownership fails closed.
73
-
74
- ## Operating Patterns
75
-
76
- ### One-shot pipeline
77
-
78
- Spawn the Recipe, wait for terminal follow-up when needed, inspect Trace/result/artifacts, and validate outputs. Do not send Controls the process does not implement.
79
-
80
- ### Controlled service
81
-
82
- Spawn, inspect `view=control` for endpoint readiness, send only declared actions, inspect Trace for outcomes, then use the declared actor-local stop action or runtime lifecycle termination as appropriate.
83
-
84
- ### Parallel review
85
-
86
- Use maintained review Recipes with explicit model/thinking and bounded concurrency. Preflight provider/model policy before fanout. Keep reviewer artifacts immutable and run merge/judge stages only after required evidence succeeds.
87
-
88
- ### Resource locking
89
-
90
- Use `resource-locker` only when methodology needs lease-backed resource exclusion. Include owner/resource identity in Control input and treat lock Trace as coordination evidence, not kernel authority.
91
-
92
- ## Diagnostics
93
-
94
- - `inspect target=runtime` for failed Runs, stale Controls, and attention Trace.
95
- - `inspect target=recipes` for active/shadowed/invalid Recipe state.
96
- - `inspect target=tool:<name>` for registered capability schema.
97
- - `inspect target=run:<id> view=recipe|trace|control` for generation evidence.
98
- - `/actor-inspector` for owner-filtered actor-instance navigation.
99
-
100
- Avoid repeated polling. Deferred terminal results arrive as Pi follow-ups.
101
-
102
- ## Related
103
-
104
- - [Runs](./async-runs.md)
105
- - [Recipe library](./recipe-library.md)
106
- - [Command templates](./command-templates.md)
107
- - [Template Recipes](./template-recipes.md)
108
- - [Actor Inspector](./actor-inspector.md)
@@ -1,45 +0,0 @@
1
- # Component Recipes
2
-
3
- Component Recipes are weakly coupled command-template cells used inside higher-level Recipes. They do not create a social protocol or become independently addressable peers.
4
-
5
- ## Contract
6
-
7
- A useful component has:
8
-
9
- - explicit typed inputs and caller-owned policy knobs;
10
- - one narrow responsibility;
11
- - deterministic output shape or declared artifact;
12
- - bounded failure behavior;
13
- - no hidden model/provider assumption;
14
- - no actor-local Control unless it owns a real service loop.
15
-
16
- ## Families
17
-
18
- - normalization and prompt shaping;
19
- - planning and task-card generation;
20
- - evidence maps, contradiction maps, and criticism;
21
- - review, verification, merge, judge, and quorum stages;
22
- - artifact generation, manifesting, and deterministic writes;
23
- - validation and package/skill summaries.
24
-
25
- ## Composition
26
-
27
- Import components by alias and call named nodes in a template array/object. Parent command-template flags own sequence, parallelism, concurrency, retries, failure scope, recovery, repetition, and output acceptance.
28
-
29
- Variable branch output should converge into stable JSON, Markdown, artifacts, or command results before downstream stages. Large evidence belongs in artifacts or complete captures; concise milestones belong in Trace.
30
-
31
- ## Boundaries
32
-
33
- - Imports are definitions inside one Run.
34
- - One-shot components omit Control.
35
- - Components never infer caller identity or lifecycle authority.
36
- - A parent may declare artifacts that imported cells write.
37
- - A child failure follows explicit parent failure/recovery policy.
38
-
39
- Use the packaged Recipe library before creating a new component. Add one only when at least two stable compositions need the same narrow behavior.
40
-
41
- ## Related
42
-
43
- - [Recipe library](./recipe-library.md)
44
- - [Template Recipes](./template-recipes.md)
45
- - [Command templates](./command-templates.md)
@@ -1,261 +0,0 @@
1
- # Task-First Recipe Design
2
-
3
- Task-first recipe design starts from a high-level operator or coordinator task, then derives the component cells, utility recipes, helper scripts, and runtime semantics needed to make that task reusable.
4
-
5
- This complements atom-first growth. Atom-first asks: "What small capability can we expose?" Task-first asks: "What complete work pattern should an agent/operator be able to invoke, and which atoms must exist to support it?"
6
-
7
- ## Method
8
-
9
- For each high-level recipe candidate:
10
-
11
- 1. Name the task in operator language.
12
- 2. Define the trigger and expected output artifact.
13
- 3. Sketch the recipe pipeline at the highest useful abstraction.
14
- 4. Identify missing component cells.
15
- 5. Decide which cells are subagent components, local utilities, or helper-backed transforms.
16
- 6. Keep domain policy knobs public: models, tools, paths, evidence/risk policy, output shape, actual Control actions, and validation gates.
17
- 7. Add only the next smallest recipe/helper slice that validates the design.
18
-
19
- ## High-Level Recipe Cells
20
-
21
- ### Release Readiness Cell
22
-
23
- Purpose: decide whether a repo/package is ready for release.
24
-
25
- Pipeline:
26
-
27
- ```text
28
- scope snapshot → changelog/package check → release lens reviews → risk verifier → readiness report → release checklist artifact
29
- ```
30
-
31
- Likely needed cells:
32
-
33
- - Package metadata reader
34
- - Changelog section extractor
35
- - Package contents summarizer
36
- - Validation command wrapper
37
- - Release-risk reviewer
38
- - Readiness merger/judge
39
- - Release checklist artifact writer
40
-
41
- Existing seeds:
42
-
43
- - `utility-changelog-section`
44
- - `utility-package-summary`
45
- - `utility-validation-wrapper`
46
- - `pipeline-review-readiness`
47
- - `subagent-judge`
48
- - `subagent-artifact`
49
-
50
- Implemented seed:
51
-
52
- - `pipeline-release-readiness`: changelog section → package summary → packaged skill summary → validation wrapper → release review coordinator → artifact report.
53
-
54
- ### Repository Health Cell
55
-
56
- Purpose: summarize repo state for the next coordinator turn or release prep.
57
-
58
- Pipeline:
59
-
60
- ```text
61
- git status/log → package/docs/backlog snapshot → validation summary → health report → next action recommendation
62
- ```
63
-
64
- Likely needed cells:
65
-
66
- - Git status/log utility
67
- - Package version reader
68
- - Backlog open/blocked extractor
69
- - Docs index checker
70
- - Validation summary normalizer
71
- - Next-action recommender
72
-
73
- Existing seeds:
74
-
75
- - `utility-markdown-index`
76
- - `utility-validation-wrapper`
77
- - `subagent-normalize`
78
- - `subagent-plan`
79
-
80
- Implemented seed:
81
-
82
- - `pipeline-repo-health`: git status/log → docs index → validation wrapper → normalized artifact report.
83
-
84
- ### Async Run Operations Cell
85
-
86
- Purpose: inspect, summarize, and decide actions for local async runs.
87
-
88
- Pipeline:
89
-
90
- ```text
91
- Run summary → Trace tail → stale/active classification → recommended Inspect or Control action
92
- ```
93
-
94
- Likely needed cells:
95
-
96
- - Run summary helper
97
- - bounded Trace tail reader
98
- - Stale-run classifier
99
- - Inspect/Control action recommender
100
- - Run report artifact
101
-
102
- Existing seeds:
103
-
104
- - `utility-run-summary`
105
- - `utility-jsonl-tail`
106
- - `pipeline-artifact-report`
107
-
108
- Implemented seed:
109
-
110
- - `pipeline-async-run-ops`: structured run operations snapshot → normalized operations report → artifact report. The snapshot combines Run summary, Trace tail, and recommended Inspect/Control actions before the LLM normalization step.
111
-
112
- ### Research Brief Cell
113
-
114
- Purpose: turn a question and source set into a bounded evidence-backed brief.
115
-
116
- Pipeline:
117
-
118
- ```text
119
- question framing → evidence map → contradiction map → claim verification → synthesis → evidence gaps → next evidence slice
120
- ```
121
-
122
- Likely needed cells:
123
-
124
- - Question framer
125
- - Source inventory utility
126
- - Evidence mapper
127
- - Contradiction mapper
128
- - Verifier
129
- - Synthesis merger
130
- - Limitations normalizer
131
-
132
- Existing seeds:
133
-
134
- - `pipeline-research-synthesis`
135
- - `subagent-evidence-map`
136
- - `subagent-contradiction-map`
137
- - `subagent-verify`
138
-
139
- ### Consensus-First Build Cell
140
-
141
- Purpose: turn several expert proposals into one coherent artifact without parallel writers fragmenting the result.
142
-
143
- Pipeline:
144
-
145
- ```text
146
- mission → lens proposers in room → consensus transcript → named implementer writes artifact → QA reviewer checks artifact + transcript → finalizer applies fixes → artifact assertions
147
- ```
148
-
149
- Use this for creative demos, single-file artifacts, specs, docs, prompt packs, and product/UX deliverables where broad input matters but one owner should shape the final file. Proposers remain read-only. The implementer becomes the first stage with write tools. QA inspects without mutation, and the finalizer writes only after reading immutable QA evidence.
150
-
151
- Required gates:
152
-
153
- - Artifact path, report path, and minimum acceptance checks are explicit inputs.
154
- - The workflow fails if the requested artifact is missing, too small, or not self-contained enough for the task.
155
- - The Run completes only after QA/finalizer acceptance, not merely after proposal generation.
156
-
157
- Existing seeds:
158
-
159
- - `subagent-review` / `subagent-verify` for QA stages.
160
- - `pipeline-quorum-review` for independent proposals and synthesis.
161
- - `pipeline-artifact-write` or a small helper script for deterministic artifact assertion.
162
-
163
- Next recipe direction:
164
-
165
- - Add a generic packaged consensus-build pipeline once the interface stabilizes around proposer roles, implementer prompt, QA prompt, artifact assertions, and public model/tool knobs.
166
-
167
- ### Implementation Tasking Cell
168
-
169
- Purpose: prepare bounded work for one or more implementation agents.
170
-
171
- Pipeline:
172
-
173
- ```text
174
- goal → mutation zones → task cards → validation gates → conflict risks → integrator handoff
175
- ```
176
-
177
- Likely needed cells:
178
-
179
- - Mutation-zone planner
180
- - Task-card generator
181
- - Ownership/conflict checker
182
- - Validation-gate normalizer
183
- - Integrator handoff artifact
184
-
185
- Existing seeds:
186
-
187
- - `pipeline-development-tasking`
188
- - `subagent-task-card`
189
- - `subagent-conflict-report`
190
-
191
- ### Documentation Maintenance Cell
192
-
193
- Purpose: keep docs/index/readme surfaces coherent after changes.
194
-
195
- Pipeline:
196
-
197
- ```text
198
- doc file inventory → index diff → stale link/routing review → rewrite suggestion → docs maintenance artifact
199
- ```
200
-
201
- Likely needed cells:
202
-
203
- - Markdown index utility
204
- - Link checker wrapper
205
- - Docs consistency reviewer
206
- - Docs update planner
207
- - Docs artifact writer
208
-
209
- Existing seeds:
210
-
211
- - `utility-markdown-index`
212
- - `subagent-review`
213
- - `subagent-plan`
214
- - `subagent-artifact`
215
-
216
- Implemented seed:
217
-
218
- - `pipeline-docs-maintenance`: docs index → documentation review → maintenance plan → artifact report.
219
-
220
- ### Media/Playlist Operations Cell
221
-
222
- Purpose: convert local media directories into controllable playback workflows.
223
-
224
- Pipeline:
225
-
226
- ```text
227
- media scan → playlist build → playback start → message summary → controls
228
- ```
229
-
230
- Likely needed cells:
231
-
232
- - Playlist builder
233
- - Music player
234
- - Run/message summary
235
- - Control recommender
236
-
237
- Existing seeds:
238
-
239
- - `utility-playlist-build`
240
- - `music-player`
241
- - `utility-run-summary`
242
- - `utility-jsonl-tail`
243
-
244
- Implemented seed:
245
-
246
- - `pipeline-media-library`: playlist build → media-library artifact report.
247
-
248
- ## Selection Rule
249
-
250
- Prefer adding a high-level recipe when at least three cells already exist and the missing cells are small. Prefer adding an atom when multiple high-level recipes need the same missing cell.
251
-
252
- ## Near-Term Candidates
253
-
254
- Good next candidates for the standard library after the first task-first wave:
255
-
256
- 1. Package/release metadata enrichment: implemented in `pipeline-release-readiness` by adding `utility-package-summary` and `utility-skill-summary` between changelog extraction and validation, making release-readiness reports more evidence-rich without adding publish automation.
257
- 2. Evidence-only release summary: implemented as `pipeline-release-summary`, which composes changelog/package/skill/validation evidence into a release summary, risk checklist, and PR body draft artifact while leaving commit, PR, merge, tag, and publish actions to explicit release gates.
258
- 3. Artifact packaging and manifesting: implemented as `pipeline-artifact-bundle`, which composes optional validation, `pipeline-artifact-write`, `utility-artifact-manifest`, deterministic manifest writing, and final artifact evidence when the caller explicitly requests filesystem writes.
259
- 4. Async run cleanup planning: extend async-run operations with stale-run classification and recommended `message`, `cancel`, or `kill` controls, keeping actual control execution operator-gated.
260
-
261
- Each candidate should land with the minimum missing cells rather than a broad one-shot framework. Already implemented task-first seeds include `pipeline-release-readiness`, `pipeline-release-summary`, `pipeline-repo-health`, `pipeline-async-run-ops`, `pipeline-docs-maintenance`, `pipeline-media-library`, and `pipeline-artifact-bundle`.
@@ -1,211 +0,0 @@
1
- /**
2
- * Runtime wake notifications for actor state.
3
- * Zones: advisory wake layer, file-backed runtime state, cross-platform notification boundary
4
- * Owns best-effort live wake signals while durable Run state remains canonical.
5
- */
6
-
7
- import { randomUUID } from "node:crypto";
8
- import {
9
- appendFileSync,
10
- existsSync,
11
- mkdirSync,
12
- readFileSync,
13
- statSync,
14
- watch,
15
- type FSWatcher,
16
- } from "node:fs";
17
- import { basename, dirname, join } from "node:path";
18
-
19
- import { readJsonlFileResilient } from "./state-readers.ts";
20
-
21
- export interface RuntimeWakeEvent {
22
- actor: string;
23
- id: string;
24
- metadata?: Record<string, unknown>;
25
- reason: string;
26
- state_dir: string;
27
- ts: string;
28
- }
29
-
30
- export interface RuntimeNotifierSubscription {
31
- close(): void;
32
- }
33
-
34
- export type RuntimeReconcileReason = "initial" | "poll" | "wake";
35
-
36
- export interface RuntimeReconcileEvent {
37
- actor: string;
38
- reason: RuntimeReconcileReason;
39
- state_dir: string;
40
- ts: string;
41
- }
42
-
43
- export interface RuntimeNotifierSubscribeOptions {
44
- onReconcile?: (event: RuntimeReconcileEvent) => void;
45
- }
46
-
47
- export interface FileRuntimeNotifierOptions {
48
- pollIntervalMs?: number;
49
- replay?: boolean;
50
- watch?: boolean;
51
- }
52
-
53
- export interface RuntimeNotifier {
54
- notify(event: { actor: string; metadata?: Record<string, unknown>; reason: string }): RuntimeWakeEvent;
55
- subscribe(
56
- actor: string,
57
- onWake: (event: RuntimeWakeEvent) => void,
58
- options?: RuntimeNotifierSubscribeOptions,
59
- ): RuntimeNotifierSubscription;
60
- }
61
-
62
- const DEFAULT_POLL_INTERVAL_MS = 1000;
63
-
64
- export function runtimeWakeFile(stateDir: string): string {
65
- return join(stateDir, "wake.jsonl");
66
- }
67
-
68
- function normalizeWakeEvent(
69
- stateDir: string,
70
- event: { actor: string; metadata?: Record<string, unknown>; reason: string },
71
- ): RuntimeWakeEvent {
72
- const actor = event.actor.trim();
73
- const reason = event.reason.trim();
74
- if (!actor) throw new Error("Runtime wake event requires actor.");
75
- if (!reason) throw new Error("Runtime wake event requires reason.");
76
- return {
77
- actor,
78
- id: randomUUID(),
79
- ...(event.metadata ? { metadata: event.metadata } : {}),
80
- reason,
81
- state_dir: stateDir,
82
- ts: new Date().toISOString(),
83
- };
84
- }
85
-
86
- export function notifyRuntimeWake(
87
- stateDir: string,
88
- event: { actor: string; metadata?: Record<string, unknown>; reason: string },
89
- ): RuntimeWakeEvent {
90
- const normalized = normalizeWakeEvent(stateDir, event);
91
- const file = runtimeWakeFile(stateDir);
92
- mkdirSync(dirname(file), { recursive: true });
93
- appendFileSync(file, `${JSON.stringify(normalized)}\n`, "utf8");
94
- return normalized;
95
- }
96
-
97
- export function parseRuntimeWakeEventLine(
98
- line: string,
99
- ): RuntimeWakeEvent | undefined {
100
- try {
101
- const record = JSON.parse(line) as Record<string, unknown>;
102
- if (
103
- typeof record.actor !== "string" ||
104
- typeof record.id !== "string" ||
105
- typeof record.reason !== "string" ||
106
- typeof record.state_dir !== "string" ||
107
- typeof record.ts !== "string"
108
- ) {
109
- return undefined;
110
- }
111
- return {
112
- actor: record.actor,
113
- id: record.id,
114
- ...(record.metadata &&
115
- typeof record.metadata === "object" &&
116
- !Array.isArray(record.metadata)
117
- ? { metadata: record.metadata as Record<string, unknown> }
118
- : {}),
119
- reason: record.reason,
120
- state_dir: record.state_dir,
121
- ts: record.ts,
122
- };
123
- } catch {
124
- return undefined;
125
- }
126
- }
127
-
128
- export function readRuntimeWakeEvents(stateDir: string): RuntimeWakeEvent[] {
129
- return readJsonlFileResilient<Record<string, unknown>>(runtimeWakeFile(stateDir))
130
- .records.map((record) => parseRuntimeWakeEventLine(JSON.stringify(record)))
131
- .filter((event): event is RuntimeWakeEvent => Boolean(event));
132
- }
133
-
134
- export function createFileRuntimeNotifier(
135
- stateDir: string,
136
- options: FileRuntimeNotifierOptions = {},
137
- ): RuntimeNotifier {
138
- const file = runtimeWakeFile(stateDir);
139
- const pollIntervalMs = Math.max(
140
- 25,
141
- Number(options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS),
142
- );
143
-
144
- return {
145
- notify: (event) => notifyRuntimeWake(stateDir, event),
146
- subscribe: (actor, onWake, subscribeOptions = {}) => {
147
- mkdirSync(dirname(file), { recursive: true });
148
- let position =
149
- options.replay || !existsSync(file) ? 0 : statSync(file).size;
150
- let pending = "";
151
- let closed = false;
152
- const reconcile = (reason: RuntimeReconcileReason): void => {
153
- if (closed) return;
154
- subscribeOptions.onReconcile?.({
155
- actor,
156
- reason,
157
- state_dir: stateDir,
158
- ts: new Date().toISOString(),
159
- });
160
- };
161
- const drain = (): void => {
162
- if (closed || !existsSync(file)) return;
163
- const buffer = readFileSync(file);
164
- if (position > buffer.length) {
165
- position = 0;
166
- pending = "";
167
- }
168
- const chunk = pending + buffer.subarray(position).toString("utf8");
169
- position = buffer.length;
170
- const lines = chunk.split("\n");
171
- pending = chunk.endsWith("\n") ? "" : (lines.pop() ?? "");
172
- for (const line of lines) {
173
- if (!line.trim()) continue;
174
- const event = parseRuntimeWakeEventLine(line);
175
- if (event && event.actor === actor) {
176
- onWake(event);
177
- reconcile("wake");
178
- }
179
- }
180
- };
181
-
182
- let watcher: FSWatcher | undefined;
183
- if (options.watch !== false) {
184
- try {
185
- watcher = watch(
186
- dirname(file),
187
- { persistent: false },
188
- (_eventType, changedFile) => {
189
- if (!changedFile || String(changedFile) === basename(file)) drain();
190
- },
191
- );
192
- } catch {
193
- // fs.watch availability varies by platform/filesystem; polling below is the fallback.
194
- }
195
- }
196
- reconcile("initial");
197
- const timer = setInterval(() => {
198
- drain();
199
- reconcile("poll");
200
- }, pollIntervalMs);
201
- timer.unref?.();
202
- return {
203
- close: () => {
204
- closed = true;
205
- clearInterval(timer);
206
- watcher?.close();
207
- },
208
- };
209
- },
210
- };
211
- }