@lumenflow/cli 4.23.0 → 5.0.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 (296) hide show
  1. package/README.md +54 -52
  2. package/dist/agent-issues-query.js +10 -2
  3. package/dist/agent-issues-query.js.map +1 -1
  4. package/dist/agent-runtime-enrollment-events.js +44 -0
  5. package/dist/agent-runtime-enrollment-events.js.map +1 -0
  6. package/dist/agent-session-end.js +47 -0
  7. package/dist/agent-session-end.js.map +1 -1
  8. package/dist/agent-session-heartbeat.js +250 -0
  9. package/dist/agent-session-heartbeat.js.map +1 -0
  10. package/dist/agent-session.js +299 -5
  11. package/dist/agent-session.js.map +1 -1
  12. package/dist/capacity-snapshot-emitter.js +73 -0
  13. package/dist/capacity-snapshot-emitter.js.map +1 -0
  14. package/dist/claim-queue.js +276 -0
  15. package/dist/claim-queue.js.map +1 -0
  16. package/dist/config-set.js +22 -3
  17. package/dist/config-set.js.map +1 -1
  18. package/dist/control-plane-sidecar-runner.js +145 -0
  19. package/dist/control-plane-sidecar-runner.js.map +1 -0
  20. package/dist/delegation-list.js +160 -1
  21. package/dist/delegation-list.js.map +1 -1
  22. package/dist/delegation-role-resolver.js +69 -0
  23. package/dist/delegation-role-resolver.js.map +1 -0
  24. package/dist/docs-generate-pack-reference.js +500 -0
  25. package/dist/docs-generate-pack-reference.js.map +1 -0
  26. package/dist/docs-sync.js +116 -1
  27. package/dist/docs-sync.js.map +1 -1
  28. package/dist/file-edit.js +28 -8
  29. package/dist/file-edit.js.map +1 -1
  30. package/dist/file-write.js +29 -5
  31. package/dist/file-write.js.map +1 -1
  32. package/dist/gate-co-change.js +25 -7
  33. package/dist/gate-co-change.js.map +1 -1
  34. package/dist/gate-conditional.js +19 -7
  35. package/dist/gate-conditional.js.map +1 -1
  36. package/dist/gates-runners.js +42 -33
  37. package/dist/gates-runners.js.map +1 -1
  38. package/dist/gates-utils.js +34 -20
  39. package/dist/gates-utils.js.map +1 -1
  40. package/dist/gates.js +79 -7
  41. package/dist/gates.js.map +1 -1
  42. package/dist/hooks/config-resolver.js +10 -1
  43. package/dist/hooks/config-resolver.js.map +1 -1
  44. package/dist/init-package-config.js +1 -1
  45. package/dist/init-package-config.js.map +1 -1
  46. package/dist/init-scaffolding.js +5 -1
  47. package/dist/init-scaffolding.js.map +1 -1
  48. package/dist/init-templates.js +10 -0
  49. package/dist/init-templates.js.map +1 -1
  50. package/dist/init.js +1 -1
  51. package/dist/init.js.map +1 -1
  52. package/dist/initiative-create.js +17 -0
  53. package/dist/initiative-create.js.map +1 -1
  54. package/dist/initiative-remove-wu.js +17 -3
  55. package/dist/initiative-remove-wu.js.map +1 -1
  56. package/dist/kernel-event-sync/emitters.js +104 -0
  57. package/dist/kernel-event-sync/emitters.js.map +1 -0
  58. package/dist/kernel-event-sync/index.js +13 -0
  59. package/dist/kernel-event-sync/index.js.map +1 -0
  60. package/dist/kernel-event-sync/lifecycle-emitters.js +160 -0
  61. package/dist/kernel-event-sync/lifecycle-emitters.js.map +1 -0
  62. package/dist/kernel-event-sync/narrow-emissions.js +89 -0
  63. package/dist/kernel-event-sync/narrow-emissions.js.map +1 -0
  64. package/dist/kernel-event-sync/software-delivery-emitters.js +297 -0
  65. package/dist/kernel-event-sync/software-delivery-emitters.js.map +1 -0
  66. package/dist/lane-lock.js +14 -1
  67. package/dist/lane-lock.js.map +1 -1
  68. package/dist/lane-suggest.js +21 -0
  69. package/dist/lane-suggest.js.map +1 -1
  70. package/dist/lumenflow-upgrade.js +7 -5
  71. package/dist/lumenflow-upgrade.js.map +1 -1
  72. package/dist/mem-context.js +145 -0
  73. package/dist/mem-context.js.map +1 -1
  74. package/dist/mem-create.js +39 -6
  75. package/dist/mem-create.js.map +1 -1
  76. package/dist/mem-inbox.js +16 -0
  77. package/dist/mem-inbox.js.map +1 -1
  78. package/dist/mem-roster.js +95 -0
  79. package/dist/mem-roster.js.map +1 -0
  80. package/dist/mem-signal.js +97 -2
  81. package/dist/mem-signal.js.map +1 -1
  82. package/dist/metrics-cli.js +3 -2
  83. package/dist/metrics-cli.js.map +1 -1
  84. package/dist/metrics-snapshot.js +271 -13
  85. package/dist/metrics-snapshot.js.map +1 -1
  86. package/dist/orchestrate-init-status.js +117 -2
  87. package/dist/orchestrate-init-status.js.map +1 -1
  88. package/dist/orchestrate-initiative.js +83 -10
  89. package/dist/orchestrate-initiative.js.map +1 -1
  90. package/dist/orchestrate-monitor-quality.js +289 -0
  91. package/dist/orchestrate-monitor-quality.js.map +1 -0
  92. package/dist/orchestrate-monitor.js +85 -0
  93. package/dist/orchestrate-monitor.js.map +1 -1
  94. package/dist/pack-validate.js +127 -2
  95. package/dist/pack-validate.js.map +1 -1
  96. package/dist/plan-create.js +18 -0
  97. package/dist/plan-create.js.map +1 -1
  98. package/dist/plan-link.js +13 -0
  99. package/dist/plan-link.js.map +1 -1
  100. package/dist/plan-promote.js +14 -0
  101. package/dist/plan-promote.js.map +1 -1
  102. package/dist/pre-commit-check.js +4 -3
  103. package/dist/pre-commit-check.js.map +1 -1
  104. package/dist/public-manifest.js +17 -3
  105. package/dist/public-manifest.js.map +1 -1
  106. package/dist/release.js +10 -10
  107. package/dist/release.js.map +1 -1
  108. package/dist/session-cross-link.js +139 -0
  109. package/dist/session-cross-link.js.map +1 -0
  110. package/dist/sidecar-manager.js +208 -0
  111. package/dist/sidecar-manager.js.map +1 -0
  112. package/dist/state-path-resolvers.js +18 -0
  113. package/dist/state-path-resolvers.js.map +1 -1
  114. package/dist/stream-heartbeat.js +151 -0
  115. package/dist/stream-heartbeat.js.map +1 -0
  116. package/dist/sync-templates.js +56 -2
  117. package/dist/sync-templates.js.map +1 -1
  118. package/dist/wu-block.js +47 -5
  119. package/dist/wu-block.js.map +1 -1
  120. package/dist/wu-claim-branch.js +8 -4
  121. package/dist/wu-claim-branch.js.map +1 -1
  122. package/dist/wu-claim-state.js +5 -3
  123. package/dist/wu-claim-state.js.map +1 -1
  124. package/dist/wu-claim-worktree.js +5 -3
  125. package/dist/wu-claim-worktree.js.map +1 -1
  126. package/dist/wu-claim.js +261 -9
  127. package/dist/wu-claim.js.map +1 -1
  128. package/dist/wu-done-auto-cleanup.js +3 -2
  129. package/dist/wu-done-auto-cleanup.js.map +1 -1
  130. package/dist/wu-done-git-ops.js +12 -8
  131. package/dist/wu-done-git-ops.js.map +1 -1
  132. package/dist/wu-done-preflight.js +3 -3
  133. package/dist/wu-done-preflight.js.map +1 -1
  134. package/dist/wu-done.js +46 -10
  135. package/dist/wu-done.js.map +1 -1
  136. package/dist/wu-lifecycle-sync/gate-scope-resolver.js +16 -0
  137. package/dist/wu-lifecycle-sync/gate-scope-resolver.js.map +1 -0
  138. package/dist/wu-lifecycle-sync/kernel-event-sync-shim.js +10 -0
  139. package/dist/wu-lifecycle-sync/kernel-event-sync-shim.js.map +1 -0
  140. package/dist/wu-prep.js +363 -22
  141. package/dist/wu-prep.js.map +1 -1
  142. package/dist/wu-prune.js +68 -27
  143. package/dist/wu-prune.js.map +1 -1
  144. package/dist/wu-release.js +34 -3
  145. package/dist/wu-release.js.map +1 -1
  146. package/dist/wu-review.js +167 -0
  147. package/dist/wu-review.js.map +1 -0
  148. package/dist/wu-spawn-prompt-builders.js +296 -40
  149. package/dist/wu-spawn-prompt-builders.js.map +1 -1
  150. package/dist/wu-spawn-strategy-resolver.js +126 -14
  151. package/dist/wu-spawn-strategy-resolver.js.map +1 -1
  152. package/dist/wu-unblock.js +52 -22
  153. package/dist/wu-unblock.js.map +1 -1
  154. package/package.json +13 -8
  155. package/packs/agent-runtime/.turbo/turbo-build.log +1 -1
  156. package/packs/agent-runtime/.turbo/turbo-test.log +25 -0
  157. package/packs/agent-runtime/.turbo/turbo-typecheck.log +4 -0
  158. package/packs/agent-runtime/agent-heartbeat.ts +163 -0
  159. package/packs/agent-runtime/auto-session-integration.ts +874 -0
  160. package/packs/agent-runtime/delegation-registry-schema.ts +220 -0
  161. package/packs/agent-runtime/delegation-registry-store.ts +269 -0
  162. package/packs/agent-runtime/delegation-tree.ts +328 -0
  163. package/packs/agent-runtime/index.ts +9 -0
  164. package/packs/agent-runtime/manifest.ts +103 -19
  165. package/packs/agent-runtime/manifest.yaml +132 -0
  166. package/packs/agent-runtime/memory-coordination-contract.ts +86 -0
  167. package/packs/agent-runtime/memory.d.ts +19 -0
  168. package/packs/agent-runtime/orchestration.ts +238 -23
  169. package/packs/agent-runtime/package.json +11 -2
  170. package/packs/agent-runtime/remote-controls/index.ts +7 -0
  171. package/packs/agent-runtime/remote-controls/operations.ts +399 -0
  172. package/packs/agent-runtime/remote-controls/port.ts +48 -0
  173. package/packs/agent-runtime/remote-controls/state-store.ts +258 -0
  174. package/packs/agent-runtime/remote-controls/types.ts +105 -0
  175. package/packs/agent-runtime/session-schema.ts +423 -0
  176. package/packs/agent-runtime/tool-impl/index.ts +1 -0
  177. package/packs/agent-runtime/tool-impl/remote-controls.mock.ts +252 -0
  178. package/packs/agent-runtime/tool-impl/remote-controls.ts +273 -0
  179. package/packs/agent-runtime/tsconfig.json +1 -1
  180. package/packs/agent-runtime/turn-lifecycle-events.ts +501 -0
  181. package/packs/sidekick/.lumenflow/state/conductor/outbox/sidekick-events.jsonl +213 -0
  182. package/packs/sidekick/.turbo/turbo-build.log +1 -1
  183. package/packs/sidekick/.turbo/turbo-test.log +25 -0
  184. package/packs/sidekick/.turbo/turbo-typecheck.log +4 -0
  185. package/packs/sidekick/channel-ingress.ts +137 -0
  186. package/packs/sidekick/manifest.ts +74 -0
  187. package/packs/sidekick/manifest.yaml +88 -0
  188. package/packs/sidekick/package.json +3 -1
  189. package/packs/sidekick/sidekick-events.ts +517 -0
  190. package/packs/sidekick/src/adapters/cloud-queue.ts +101 -0
  191. package/packs/sidekick/src/adapters/control-plane-bridge.adapter.ts +378 -0
  192. package/packs/sidekick/src/adapters/filesystem-bridge.adapter.ts +224 -0
  193. package/packs/sidekick/src/domain/channel.types.ts +84 -0
  194. package/packs/sidekick/src/ports/channel-bridge.port.ts +75 -0
  195. package/packs/sidekick/src/routines/commit.ts +74 -0
  196. package/packs/sidekick/tool-impl/channel-tools.ts +47 -0
  197. package/packs/sidekick/tool-impl/memory-tools.ts +17 -0
  198. package/packs/sidekick/tool-impl/routine-commit.ts +102 -0
  199. package/packs/sidekick/tool-impl/routine-tools.ts +67 -7
  200. package/packs/sidekick/tool-impl/runtime-context.ts +4 -0
  201. package/packs/sidekick/tool-impl/storage.ts +3 -0
  202. package/packs/sidekick/tool-impl/system-tools.ts +7 -0
  203. package/packs/sidekick/tool-impl/task-tools.ts +46 -0
  204. package/packs/sidekick/tsconfig.json +1 -1
  205. package/packs/software-delivery/.turbo/turbo-build.log +1 -1
  206. package/packs/software-delivery/.turbo/turbo-test.log +63 -0
  207. package/packs/software-delivery/.turbo/turbo-typecheck.log +4 -0
  208. package/packs/software-delivery/manifest-schema.ts +30 -0
  209. package/packs/software-delivery/manifest.ts +99 -1
  210. package/packs/software-delivery/manifest.yaml +46 -0
  211. package/packs/software-delivery/package.json +88 -3
  212. package/packs/software-delivery/src/commands/index.ts +5 -0
  213. package/packs/software-delivery/src/config/delivery-review-contract.ts +20 -0
  214. package/packs/software-delivery/src/config/env-accessors.ts +19 -0
  215. package/packs/software-delivery/src/config/index.ts +8 -0
  216. package/packs/software-delivery/src/config/normalize-config-keys.ts +19 -0
  217. package/packs/software-delivery/src/config/schemas/lumenflow-config-schema-types.ts +436 -0
  218. package/packs/software-delivery/src/config/workspace-reader.ts +310 -0
  219. package/packs/software-delivery/src/constants/backlog-patterns.ts +31 -0
  220. package/packs/software-delivery/src/constants/client-ids.ts +19 -0
  221. package/packs/software-delivery/src/constants/config-contract.ts +7 -0
  222. package/packs/software-delivery/src/constants/docs-layout-presets.ts +50 -0
  223. package/packs/software-delivery/src/constants/duration-constants.ts +20 -0
  224. package/packs/software-delivery/src/constants/gate-constants.ts +32 -0
  225. package/packs/software-delivery/src/constants/index.ts +29 -0
  226. package/packs/software-delivery/src/constants/lock-constants.ts +35 -0
  227. package/packs/software-delivery/src/constants/object-guards.ts +12 -0
  228. package/packs/software-delivery/src/constants/section-headings.ts +107 -0
  229. package/packs/software-delivery/src/constants/wu-cli-constants.ts +485 -0
  230. package/packs/software-delivery/src/constants/wu-domain-constants.ts +466 -0
  231. package/packs/software-delivery/src/constants/wu-git-constants.ts +7 -0
  232. package/packs/software-delivery/src/constants/wu-id-format.ts +327 -0
  233. package/packs/software-delivery/src/constants/wu-paths-constants.ts +358 -0
  234. package/packs/software-delivery/src/constants/wu-statuses.ts +287 -0
  235. package/packs/software-delivery/src/constants/wu-type-helpers.ts +67 -0
  236. package/packs/software-delivery/src/constants/wu-ui-constants.ts +267 -0
  237. package/packs/software-delivery/src/constants/wu-validation-constants.ts +73 -0
  238. package/packs/software-delivery/src/domain/index.ts +5 -0
  239. package/packs/software-delivery/src/domain/orchestration.constants.ts +168 -0
  240. package/packs/software-delivery/src/domain/orchestration.schemas.ts +239 -0
  241. package/packs/software-delivery/src/domain/orchestration.types.ts +178 -0
  242. package/packs/software-delivery/src/methodology/incremental-test.ts +90 -0
  243. package/packs/software-delivery/src/methodology/index.ts +6 -0
  244. package/packs/software-delivery/src/methodology/manual-test-validator.ts +292 -0
  245. package/packs/software-delivery/src/policy/coverage-gate.ts +270 -0
  246. package/packs/software-delivery/src/policy/gates-agent-mode.ts +223 -0
  247. package/packs/software-delivery/src/policy/gates-config-internal.ts +121 -0
  248. package/packs/software-delivery/src/policy/gates-config.ts +293 -0
  249. package/packs/software-delivery/src/policy/gates-coverage.ts +247 -0
  250. package/packs/software-delivery/src/policy/gates-presets.ts +134 -0
  251. package/packs/software-delivery/src/policy/gates-schemas.ts +173 -0
  252. package/packs/software-delivery/src/policy/index.ts +22 -0
  253. package/packs/software-delivery/src/policy/package-manager-resolver.ts +319 -0
  254. package/packs/software-delivery/src/policy/resolve-policy.ts +518 -0
  255. package/packs/software-delivery/src/ports/config.ports.ts +90 -0
  256. package/packs/software-delivery/src/ports/dashboard-renderer.port.ts +125 -0
  257. package/packs/software-delivery/src/ports/index.ts +10 -0
  258. package/packs/software-delivery/src/ports/sync-validator.ports.ts +59 -0
  259. package/packs/software-delivery/src/ports/wu-helpers.ports.ts +168 -0
  260. package/packs/software-delivery/src/ports/wu-state.ports.ts +241 -0
  261. package/packs/software-delivery/src/primitives/index.ts +5 -0
  262. package/packs/software-delivery/src/runtime/index.ts +6 -0
  263. package/packs/software-delivery/src/runtime/work-classifier.ts +561 -0
  264. package/packs/software-delivery/src/sandbox/index.ts +10 -0
  265. package/packs/software-delivery/src/sandbox/sandbox-allowlist.ts +118 -0
  266. package/packs/software-delivery/src/sandbox/sandbox-backend-linux.ts +88 -0
  267. package/packs/software-delivery/src/sandbox/sandbox-backend-macos.ts +154 -0
  268. package/packs/software-delivery/src/sandbox/sandbox-backend-windows.ts +47 -0
  269. package/packs/software-delivery/src/sandbox/sandbox-profile.ts +153 -0
  270. package/packs/software-delivery/src/schemas/index.ts +5 -0
  271. package/packs/software-delivery/src/state/date-utils.ts +158 -0
  272. package/packs/software-delivery/src/state/index.ts +15 -0
  273. package/packs/software-delivery/src/state/state-machine.ts +119 -0
  274. package/packs/software-delivery/src/state/wu-doc-types.ts +51 -0
  275. package/packs/software-delivery/src/state/wu-paths.ts +381 -0
  276. package/packs/software-delivery/src/state/wu-schema.ts +1139 -0
  277. package/packs/software-delivery/src/state/wu-state-schema.ts +255 -0
  278. package/packs/software-delivery/src/state/wu-yaml.ts +338 -0
  279. package/packs/software-delivery/src/types.d.ts +16 -0
  280. package/packs/software-delivery/tool-impl/wu-lifecycle-tools.ts +18 -0
  281. package/packs/software-delivery/tsconfig.json +28 -2
  282. package/templates/core/AGENTS.md.template +76 -17
  283. package/templates/core/LUMENFLOW.md.template +265 -66
  284. package/templates/core/_frameworks/lumenflow/wu-sizing-guide.md.template +180 -116
  285. package/templates/core/ai/onboarding/agent-invocation-guide.md.template +26 -8
  286. package/templates/core/ai/onboarding/existing-project-bootstrap.md.template +171 -0
  287. package/templates/core/ai/onboarding/first-15-mins.md.template +3 -1
  288. package/templates/core/ai/onboarding/first-wu-mistakes.md.template +1 -1
  289. package/templates/core/ai/onboarding/initiative-orchestration.md.template +46 -30
  290. package/templates/core/ai/onboarding/quick-ref-commands.md.template +36 -33
  291. package/templates/core/ai/onboarding/release-process.md.template +8 -7
  292. package/templates/core/ai/onboarding/starting-prompt.md.template +2 -0
  293. package/templates/core/ai/onboarding/troubleshooting-wu-done.md.template +62 -0
  294. package/templates/vendors/claude/.claude/CLAUDE.md.template +29 -54
  295. package/templates/vendors/cursor/.cursor/rules/lumenflow.md.template +24 -52
  296. package/templates/vendors/windsurf/.windsurf/rules/lumenflow.md.template +24 -52
@@ -0,0 +1,75 @@
1
+ // Copyright (c) 2026 Hellmai Ltd
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ /**
5
+ * WU-2735 (INIT-060 WU-7a, ADR-013 §ChannelBridge):
6
+ * ChannelBridge port.
7
+ *
8
+ * Port contract — load-bearing across adapters (ADR-013 §ChannelBridge):
9
+ *
10
+ * - `send` is at-least-once for `queue` envelopes, fail-silent for
11
+ * `ephemeral` envelopes (ADR-013 §4 backpressure split).
12
+ * - `receive` yields envelopes in emit order on a single channel (§3). No
13
+ * cross-channel ordering guarantee.
14
+ * - `register` is idempotent on `BridgeConfig` identity — same
15
+ * `(provider, name)` returns the same `ChannelId`.
16
+ * - `disconnect` flushes queued envelopes before closing; in-flight ephemerals
17
+ * may drop.
18
+ *
19
+ * The port MUST NOT leak filesystem- or network-specific types. Both the
20
+ * filesystem adapter (this WU) and the cloud adapter (WU-2737) implement this
21
+ * interface unmodified.
22
+ */
23
+
24
+ import type {
25
+ BridgeConfig,
26
+ ChannelEnvelope,
27
+ ChannelId,
28
+ SendResult,
29
+ } from '../domain/channel.types.js';
30
+
31
+ export interface ChannelBridge {
32
+ /**
33
+ * Send an envelope on a registered channel.
34
+ *
35
+ * @throws if `channelId` is not registered, or has been disconnected.
36
+ */
37
+ send(channelId: ChannelId, envelope: ChannelEnvelope): Promise<SendResult>;
38
+
39
+ /**
40
+ * Stream envelopes on the given channel in emit order. The iterator
41
+ * terminates after `disconnect(channelId)`; otherwise it is long-lived.
42
+ */
43
+ receive(channelId: ChannelId): AsyncIterable<ChannelEnvelope>;
44
+
45
+ /**
46
+ * Register a channel and return its id. Idempotent on `(provider, name)`.
47
+ */
48
+ register(bridgeConfig: BridgeConfig): Promise<ChannelId>;
49
+
50
+ /**
51
+ * Close the channel. Queued envelopes flush before this resolves; ephemerals
52
+ * in flight may be dropped.
53
+ */
54
+ disconnect(channelId: ChannelId): Promise<void>;
55
+ }
56
+
57
+ /**
58
+ * Structural type guard. Useful for adapter tests and runtime payload
59
+ * validation at the port boundary.
60
+ */
61
+ export function isChannelEnvelope(value: unknown): value is ChannelEnvelope {
62
+ if (!value || typeof value !== 'object') {
63
+ return false;
64
+ }
65
+ const v = value as Record<string, unknown>;
66
+ return (
67
+ typeof v.id === 'string' &&
68
+ (v.kind === 'ephemeral' || v.kind === 'queue') &&
69
+ typeof v.content_type === 'string' &&
70
+ 'body' in v &&
71
+ typeof v.emitted_at === 'string'
72
+ );
73
+ }
74
+
75
+ export type { BridgeConfig, ChannelEnvelope, ChannelId, SendResult };
@@ -0,0 +1,74 @@
1
+ // Copyright (c) 2026 Hellmai Ltd
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ /**
5
+ * WU-2738 (INIT-060, ADR-013 §6 governance): plan-to-commit envelope +
6
+ * content-hash helpers for the governed sidekick:routine:commit_plan
7
+ * tool. The commit envelope is what an agent attestation carries; the
8
+ * content-hash is what lands on the audit trail so cloud can correlate
9
+ * a `sidekick:routine_committed` event with the originating envelope
10
+ * without the PII-bearing body ever leaving the local workspace.
11
+ */
12
+
13
+ import { createHash } from 'node:crypto';
14
+ import type { RoutineRecord } from '../../tool-impl/storage.js';
15
+
16
+ const CONTENT_HASH_ALGORITHM = 'sha256';
17
+ const CONTENT_HASH_ENCODING = 'hex';
18
+
19
+ export interface RoutineCommitAttestation {
20
+ actor: string;
21
+ reason: string;
22
+ // Intentionally untyped extension slot: attestation may carry arbitrary
23
+ // operator-supplied metadata which can contain PII. The envelope hash
24
+ // covers the entire attestation; the body never appears on the audit
25
+ // event surface.
26
+ [key: string]: unknown;
27
+ }
28
+
29
+ export interface RoutineCommitEnvelope {
30
+ plan_id: string;
31
+ routine_name: string;
32
+ step_count: number;
33
+ committed_at: string;
34
+ attestation: RoutineCommitAttestation;
35
+ }
36
+
37
+ export interface BuildRoutineCommitEnvelopeInput {
38
+ routine: RoutineRecord;
39
+ attestation: RoutineCommitAttestation;
40
+ committedAt: string;
41
+ }
42
+
43
+ export function buildRoutineCommitEnvelope(
44
+ input: BuildRoutineCommitEnvelopeInput,
45
+ ): RoutineCommitEnvelope {
46
+ return {
47
+ plan_id: input.routine.id,
48
+ routine_name: input.routine.name,
49
+ step_count: input.routine.steps.length,
50
+ committed_at: input.committedAt,
51
+ attestation: { ...input.attestation },
52
+ };
53
+ }
54
+
55
+ function canonicalize(value: unknown): unknown {
56
+ if (Array.isArray(value)) {
57
+ return value.map((entry) => canonicalize(entry));
58
+ }
59
+ if (value && typeof value === 'object') {
60
+ const record = value as Record<string, unknown>;
61
+ const sortedKeys = Object.keys(record).sort();
62
+ const result: Record<string, unknown> = {};
63
+ for (const key of sortedKeys) {
64
+ result[key] = canonicalize(record[key]);
65
+ }
66
+ return result;
67
+ }
68
+ return value;
69
+ }
70
+
71
+ export function hashRoutineCommitEnvelope(envelope: RoutineCommitEnvelope): string {
72
+ const canonical = JSON.stringify(canonicalize(envelope));
73
+ return createHash(CONTENT_HASH_ALGORITHM).update(canonical).digest(CONTENT_HASH_ENCODING);
74
+ }
@@ -16,6 +16,14 @@ import {
16
16
  type ToolContextLike,
17
17
  type ToolOutput,
18
18
  } from './shared.js';
19
+ import {
20
+ buildChannelBridgeConnectedEvent,
21
+ buildChannelBridgeDisconnectedEvent,
22
+ buildChannelMessageReceivedEvent,
23
+ buildChannelMessageSentEvent,
24
+ emitSidekickEvent,
25
+ serializeLocalChannelMessage,
26
+ } from '../sidekick-events.js';
19
27
 
20
28
  // ---------------------------------------------------------------------------
21
29
  // Constants
@@ -115,6 +123,8 @@ async function channelConfigureTool(
115
123
  }
116
124
  });
117
125
 
126
+ await emitSidekickEvent(buildChannelBridgeConnectedEvent(resolvedChannel));
127
+
118
128
  return success({ channel: resolvedChannel as unknown as Record<string, unknown> });
119
129
  }
120
130
 
@@ -205,6 +215,13 @@ async function channelDeleteTool(input: unknown, context?: ToolContextLike): Pro
205
215
  );
206
216
  });
207
217
 
218
+ await emitSidekickEvent(
219
+ buildChannelBridgeDisconnectedEvent({
220
+ bridge_id: id,
221
+ deleted_message_count: deletedMessages.length,
222
+ }),
223
+ );
224
+
208
225
  return success({
209
226
  deleted_id: id,
210
227
  deleted_message_count: deletedMessages.length,
@@ -283,6 +300,17 @@ async function channelSendViaTransport(
283
300
  };
284
301
  }
285
302
 
303
+ await emitSidekickEvent(
304
+ buildChannelMessageSentEvent({
305
+ provider,
306
+ channel,
307
+ content,
308
+ ...(transportResult.externalMessageId !== undefined
309
+ ? { external_message_id: transportResult.externalMessageId }
310
+ : {}),
311
+ }),
312
+ );
313
+
286
314
  return success(outputData);
287
315
  }
288
316
 
@@ -361,6 +389,10 @@ async function channelSendTool(input: unknown, context?: ToolContextLike): Promi
361
389
  );
362
390
  });
363
391
 
392
+ await emitSidekickEvent(
393
+ buildChannelMessageSentEvent(serializeLocalChannelMessage(resolvedMessage, channelName)),
394
+ );
395
+
364
396
  return success({ message: resolvedMessage as unknown as Record<string, unknown> });
365
397
  }
366
398
 
@@ -430,6 +462,14 @@ async function channelReceiveViaTransport(
430
462
  };
431
463
  }
432
464
 
465
+ await emitSidekickEvent(
466
+ buildChannelMessageReceivedEvent({
467
+ provider,
468
+ channel,
469
+ count: transportResult.records?.length ?? 0,
470
+ }),
471
+ );
472
+
433
473
  return success(outputData);
434
474
  }
435
475
 
@@ -468,6 +508,13 @@ async function channelReceiveTool(input: unknown, _context?: ToolContextLike): P
468
508
 
469
509
  const items = limit && limit > 0 ? sorted.slice(-limit) : sorted;
470
510
 
511
+ await emitSidekickEvent(
512
+ buildChannelMessageReceivedEvent({
513
+ channel: channelName ?? DEFAULT_CHANNEL_NAME,
514
+ count: items.length,
515
+ }),
516
+ );
517
+
471
518
  return success({
472
519
  items: items as unknown as Record<string, unknown>,
473
520
  count: items.length,
@@ -18,6 +18,12 @@ import {
18
18
  type ToolContextLike,
19
19
  type ToolOutput,
20
20
  } from './shared.js';
21
+ import {
22
+ buildMemoryForgottenEvent,
23
+ buildMemoryRecalledEvent,
24
+ buildMemoryStoredEvent,
25
+ emitSidekickEvent,
26
+ } from '../sidekick-events.js';
21
27
 
22
28
  // ---------------------------------------------------------------------------
23
29
  // Constants
@@ -87,6 +93,8 @@ async function memoryStoreTool(input: unknown, context?: ToolContextLike): Promi
87
93
  );
88
94
  });
89
95
 
96
+ await emitSidekickEvent(buildMemoryStoredEvent(memory));
97
+
90
98
  return success({ memory: memory as unknown as Record<string, unknown> });
91
99
  }
92
100
 
@@ -121,6 +129,13 @@ async function memoryRecallTool(input: unknown, _context?: ToolContextLike): Pro
121
129
 
122
130
  const items = limit && limit > 0 ? sorted.slice(0, limit) : sorted;
123
131
 
132
+ await emitSidekickEvent(
133
+ buildMemoryRecalledEvent({
134
+ query,
135
+ memories: items,
136
+ }),
137
+ );
138
+
124
139
  return success({
125
140
  items: items as unknown as Record<string, unknown>,
126
141
  count: items.length,
@@ -247,6 +262,8 @@ async function memoryForgetTool(input: unknown, context?: ToolContextLike): Prom
247
262
  );
248
263
  });
249
264
 
265
+ await emitSidekickEvent(buildMemoryForgottenEvent(id));
266
+
250
267
  return success({ deleted_id: id });
251
268
  }
252
269
 
@@ -0,0 +1,102 @@
1
+ // Copyright (c) 2026 Hellmai Ltd
2
+ // SPDX-License-Identifier: AGPL-3.0-only
3
+
4
+ /**
5
+ * WU-2738 (INIT-060, ADR-013 §6 governance): `sidekick:routine:commit_plan`
6
+ * is the only path from a planned routine to a committed one. It is
7
+ * registered as a runtime-callable tool (not a top-level surface) so the
8
+ * kernel's governed dispatch emits `agent-runtime:tool_called` inside a
9
+ * turn, and the commit itself emits `sidekick:routine_committed` carrying
10
+ * a content-hash of the commit envelope. No fast-path bypasses this tool.
11
+ */
12
+
13
+ import {
14
+ buildRoutineCommitEnvelope,
15
+ hashRoutineCommitEnvelope,
16
+ type RoutineCommitAttestation,
17
+ } from '../src/routines/commit.js';
18
+ import { buildRoutineCommittedEvent, emitSidekickEvent } from '../sidekick-events.js';
19
+ import { getStoragePort } from './storage.js';
20
+ import {
21
+ asNonEmptyString,
22
+ buildAuditEvent,
23
+ failure,
24
+ nowIso,
25
+ success,
26
+ toRecord,
27
+ type ToolContextLike,
28
+ type ToolOutput,
29
+ } from './shared.js';
30
+
31
+ const ROUTINE_COMMIT_PLAN_TOOL_NAME = 'sidekick:routine:commit_plan';
32
+
33
+ function readAttestation(value: unknown): RoutineCommitAttestation | null {
34
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
35
+ return null;
36
+ }
37
+ const record = value as Record<string, unknown>;
38
+ const actor = asNonEmptyString(record.actor);
39
+ const reason = asNonEmptyString(record.reason);
40
+ if (!actor || !reason) {
41
+ return null;
42
+ }
43
+ return {
44
+ ...record,
45
+ actor,
46
+ reason,
47
+ };
48
+ }
49
+
50
+ export default async function routineCommitPlanTool(
51
+ input: unknown,
52
+ context?: ToolContextLike,
53
+ ): Promise<ToolOutput> {
54
+ const parsed = toRecord(input);
55
+ const planId = asNonEmptyString(parsed.plan_id);
56
+ const attestation = readAttestation(parsed.attestation);
57
+
58
+ if (!planId) {
59
+ return failure('INVALID_INPUT', 'plan_id is required.');
60
+ }
61
+ if (!attestation) {
62
+ return failure(
63
+ 'INVALID_INPUT',
64
+ 'attestation must be an object with non-empty actor and reason fields.',
65
+ );
66
+ }
67
+
68
+ const storage = getStoragePort();
69
+ const routines = await storage.readStore('routines');
70
+ const routine = routines.find((entry) => entry.id === planId);
71
+ if (!routine) {
72
+ return failure('NOT_FOUND', `routine ${planId} was not found.`);
73
+ }
74
+
75
+ const committedAt = nowIso();
76
+ const envelope = buildRoutineCommitEnvelope({ routine, attestation, committedAt });
77
+ const envelopeContentHash = hashRoutineCommitEnvelope(envelope);
78
+
79
+ await storage.appendAudit(
80
+ buildAuditEvent({
81
+ tool: ROUTINE_COMMIT_PLAN_TOOL_NAME,
82
+ op: 'execute',
83
+ context,
84
+ ids: [routine.id],
85
+ details: {
86
+ plan_id: routine.id,
87
+ envelope_content_hash: envelopeContentHash,
88
+ },
89
+ }),
90
+ );
91
+
92
+ await emitSidekickEvent(
93
+ buildRoutineCommittedEvent(routine, { envelope_content_hash: envelopeContentHash }),
94
+ );
95
+
96
+ return success({
97
+ plan_id: routine.id,
98
+ routine_id: routine.id,
99
+ envelope_content_hash: envelopeContentHash,
100
+ committed_at: committedAt,
101
+ });
102
+ }
@@ -15,6 +15,13 @@ import {
15
15
  type ToolContextLike,
16
16
  type ToolOutput,
17
17
  } from './shared.js';
18
+ import {
19
+ buildRoutineCommittedEvent,
20
+ buildRoutineExecutedEvent,
21
+ buildRoutinePlannedEvent,
22
+ buildRoutineStepFailedEvent,
23
+ emitSidekickEvent,
24
+ } from '../sidekick-events.js';
18
25
 
19
26
  // ---------------------------------------------------------------------------
20
27
  // Constants
@@ -36,15 +43,17 @@ const ROUTINE_ID_REQUIRED_MESSAGE = 'id is required.';
36
43
  interface NormalizeStepsResult {
37
44
  steps: RoutineStepRecord[];
38
45
  warnings: string[];
46
+ failed_steps: Array<{ step_index: number; reason: string }>;
39
47
  }
40
48
 
41
49
  function normalizeSteps(value: unknown): NormalizeStepsResult {
42
50
  if (!Array.isArray(value)) {
43
- return { steps: [], warnings: [] };
51
+ return { steps: [], warnings: [], failed_steps: [] };
44
52
  }
45
53
 
46
54
  const steps: RoutineStepRecord[] = [];
47
55
  const warnings: string[] = [];
56
+ const failedSteps: Array<{ step_index: number; reason: string }> = [];
48
57
 
49
58
  for (let i = 0; i < value.length; i++) {
50
59
  const candidate = value[i];
@@ -55,20 +64,26 @@ function normalizeSteps(value: unknown): NormalizeStepsResult {
55
64
  if (tool) {
56
65
  steps.push({ tool, input: {} });
57
66
  } else {
58
- warnings.push(`steps[${i}] invalid: expected non-empty string or object with "tool".`);
67
+ const reason = `steps[${i}] invalid: expected non-empty string or object with "tool".`;
68
+ warnings.push(reason);
69
+ failedSteps.push({ step_index: i, reason });
59
70
  }
60
71
  continue;
61
72
  }
62
73
 
63
74
  if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
64
- warnings.push(`steps[${i}] invalid: expected object with "tool" or a tool name string.`);
75
+ const reason = `steps[${i}] invalid: expected object with "tool" or a tool name string.`;
76
+ warnings.push(reason);
77
+ failedSteps.push({ step_index: i, reason });
65
78
  continue;
66
79
  }
67
80
 
68
81
  const record = candidate as Record<string, unknown>;
69
82
  const tool = asNonEmptyString(record.tool);
70
83
  if (!tool) {
71
- warnings.push(`steps[${i}] invalid: missing or empty "tool" property.`);
84
+ const reason = `steps[${i}] invalid: missing or empty "tool" property.`;
85
+ warnings.push(reason);
86
+ failedSteps.push({ step_index: i, reason });
72
87
  continue;
73
88
  }
74
89
 
@@ -80,7 +95,7 @@ function normalizeSteps(value: unknown): NormalizeStepsResult {
80
95
  steps.push({ tool, input });
81
96
  }
82
97
 
83
- return { steps, warnings };
98
+ return { steps, warnings, failed_steps: failedSteps };
84
99
  }
85
100
 
86
101
  function asOptionalBoolean(value: unknown): boolean | null {
@@ -90,6 +105,22 @@ function asOptionalBoolean(value: unknown): boolean | null {
90
105
  return null;
91
106
  }
92
107
 
108
+ async function emitRoutineStepFailures(
109
+ failedSteps: Array<{ step_index: number; reason: string }>,
110
+ metadata: { routine_id?: string; routine_name?: string },
111
+ ): Promise<void> {
112
+ for (const failedStep of failedSteps) {
113
+ await emitSidekickEvent(
114
+ buildRoutineStepFailedEvent({
115
+ step_index: failedStep.step_index,
116
+ reason: failedStep.reason,
117
+ ...(metadata.routine_id ? { routine_id: metadata.routine_id } : {}),
118
+ ...(metadata.routine_name ? { routine_name: metadata.routine_name } : {}),
119
+ }),
120
+ );
121
+ }
122
+ }
123
+
93
124
  // ---------------------------------------------------------------------------
94
125
  // routine:create
95
126
  // ---------------------------------------------------------------------------
@@ -97,12 +128,15 @@ function asOptionalBoolean(value: unknown): boolean | null {
97
128
  async function routineCreateTool(input: unknown, context?: ToolContextLike): Promise<ToolOutput> {
98
129
  const parsed = toRecord(input);
99
130
  const name = asNonEmptyString(parsed.name);
100
- const { steps, warnings } = normalizeSteps(parsed.steps);
131
+ const { steps, warnings, failed_steps } = normalizeSteps(parsed.steps);
101
132
 
102
133
  if (!name) {
103
134
  return failure('INVALID_INPUT', 'name is required.');
104
135
  }
105
136
  if (steps.length === 0) {
137
+ if (!isDryRun(parsed)) {
138
+ await emitRoutineStepFailures(failed_steps, { routine_name: name ?? undefined });
139
+ }
106
140
  const detail =
107
141
  warnings.length > 0
108
142
  ? `steps must include at least one tool step. Issues: ${warnings.join('; ')}`
@@ -144,6 +178,12 @@ async function routineCreateTool(input: unknown, context?: ToolContextLike): Pro
144
178
  );
145
179
  });
146
180
 
181
+ await emitRoutineStepFailures(failed_steps, {
182
+ routine_id: routine.id,
183
+ routine_name: routine.name,
184
+ });
185
+ await emitSidekickEvent(buildRoutinePlannedEvent(routine));
186
+
147
187
  return success({
148
188
  routine: routine as unknown as Record<string, unknown>,
149
189
  ...(warnings.length > 0 ? { warnings } : {}),
@@ -180,6 +220,7 @@ async function routineListTool(input: unknown, _context?: ToolContextLike): Prom
180
220
  async function routineUpdateTool(input: unknown, context?: ToolContextLike): Promise<ToolOutput> {
181
221
  const parsed = toRecord(input);
182
222
  const id = asNonEmptyString(parsed.id);
223
+ let patchFailedSteps: Array<{ step_index: number; reason: string }> = [];
183
224
 
184
225
  if (!id) {
185
226
  return failure('INVALID_INPUT', ROUTINE_ID_REQUIRED_MESSAGE);
@@ -201,8 +242,12 @@ async function routineUpdateTool(input: unknown, context?: ToolContextLike): Pro
201
242
  patch.enabled = enabled;
202
243
  }
203
244
  if (parsed.steps !== undefined) {
204
- const { steps, warnings } = normalizeSteps(parsed.steps);
245
+ const { steps, warnings, failed_steps } = normalizeSteps(parsed.steps);
246
+ patchFailedSteps = failed_steps;
205
247
  if (steps.length === 0) {
248
+ if (!isDryRun(parsed)) {
249
+ await emitRoutineStepFailures(failed_steps, { routine_id: id });
250
+ }
206
251
  const detail =
207
252
  warnings.length > 0
208
253
  ? `steps must include at least one tool step. Issues: ${warnings.join('; ')}`
@@ -260,6 +305,13 @@ async function routineUpdateTool(input: unknown, context?: ToolContextLike): Pro
260
305
 
261
306
  const updated = await storage.readStore('routines');
262
307
  const updatedRoutine = updated.find((entry) => entry.id === id);
308
+ await emitRoutineStepFailures(patchFailedSteps, {
309
+ routine_id: id,
310
+ routine_name: updatedRoutine?.name,
311
+ });
312
+ if (updatedRoutine) {
313
+ await emitSidekickEvent(buildRoutineCommittedEvent(updatedRoutine));
314
+ }
263
315
  return success({ routine: updatedRoutine as unknown as Record<string, unknown> });
264
316
  }
265
317
 
@@ -339,6 +391,14 @@ async function routineRunTool(input: unknown, context?: ToolContextLike): Promis
339
391
  }),
340
392
  );
341
393
 
394
+ await emitSidekickEvent(
395
+ buildRoutineExecutedEvent({
396
+ routine_id: routine.id,
397
+ name: routine.name,
398
+ step_count: routine.steps.length,
399
+ }),
400
+ );
401
+
342
402
  return success({
343
403
  routine_id: routine.id,
344
404
  name: routine.name,
@@ -4,10 +4,14 @@
4
4
  import { AsyncLocalStorage } from 'node:async_hooks';
5
5
  import type { ChannelTransport } from './channel-transports.js';
6
6
  import type { StoragePort } from './storage.js';
7
+ import type { SidekickEvent } from '../sidekick-events.js';
7
8
 
8
9
  export interface SidekickRuntimeContext {
9
10
  storagePort: StoragePort;
10
11
  channelTransports: Map<string, ChannelTransport>;
12
+ workspaceRoot?: string;
13
+ workspaceConfig?: unknown;
14
+ eventSink?: (event: SidekickEvent) => void | Promise<void>;
11
15
  }
12
16
 
13
17
  const runtimeContext = new AsyncLocalStorage<SidekickRuntimeContext>();
@@ -316,6 +316,9 @@ export async function runWithStoragePort<T>(port: StoragePort, fn: () => Promise
316
316
  {
317
317
  storagePort: port,
318
318
  channelTransports: existingContext?.channelTransports ?? new Map(),
319
+ workspaceRoot: existingContext?.workspaceRoot,
320
+ workspaceConfig: existingContext?.workspaceConfig,
321
+ eventSink: existingContext?.eventSink,
319
322
  },
320
323
  fn,
321
324
  );
@@ -12,6 +12,11 @@ import {
12
12
  type ToolContextLike,
13
13
  type ToolOutput,
14
14
  } from './shared.js';
15
+ import {
16
+ buildStateRehydratedEvent,
17
+ emitSidekickEvent,
18
+ snapshotSidekickState,
19
+ } from '../sidekick-events.js';
15
20
 
16
21
  // ---------------------------------------------------------------------------
17
22
  // Constants
@@ -46,6 +51,8 @@ async function initTool(context?: ToolContextLike): Promise<ToolOutput> {
46
51
  }),
47
52
  );
48
53
 
54
+ await emitSidekickEvent(buildStateRehydratedEvent(await snapshotSidekickState()));
55
+
49
56
  return success({
50
57
  initialized: true,
51
58
  root_dir: storage.getRootDir(),