@pellux/goodvibes-agent 0.1.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 (398) hide show
  1. package/.goodvibes/GOODVIBES.md +35 -0
  2. package/.goodvibes/agents/reviewer.md +48 -0
  3. package/.goodvibes/skills/add-provider/SKILL.md +199 -0
  4. package/CHANGELOG.md +25 -0
  5. package/README.md +74 -0
  6. package/bin/goodvibes-agent.ts +2 -0
  7. package/docs/README.md +23 -0
  8. package/docs/deployment-and-services.md +57 -0
  9. package/docs/getting-started.md +53 -0
  10. package/docs/release-and-publishing.md +46 -0
  11. package/package.json +134 -0
  12. package/scripts/check-bun.sh +20 -0
  13. package/src/audio/player.ts +156 -0
  14. package/src/audio/spoken-turn-controller.ts +203 -0
  15. package/src/audio/spoken-turn-model-routing.ts +117 -0
  16. package/src/audio/spoken-turn-wiring.ts +44 -0
  17. package/src/audio/text-chunker.ts +110 -0
  18. package/src/cli/bundle-command.ts +227 -0
  19. package/src/cli/completion.ts +90 -0
  20. package/src/cli/config-overrides.ts +159 -0
  21. package/src/cli/endpoints.ts +63 -0
  22. package/src/cli/entrypoint.ts +172 -0
  23. package/src/cli/help.ts +299 -0
  24. package/src/cli/index.ts +11 -0
  25. package/src/cli/management-commands.ts +426 -0
  26. package/src/cli/management.ts +744 -0
  27. package/src/cli/network-posture.ts +46 -0
  28. package/src/cli/package-verification.ts +123 -0
  29. package/src/cli/parser.ts +369 -0
  30. package/src/cli/provider-auth-routes.ts +22 -0
  31. package/src/cli/provider-classification.ts +107 -0
  32. package/src/cli/redaction.ts +105 -0
  33. package/src/cli/service-command.ts +26 -0
  34. package/src/cli/service-posture.ts +482 -0
  35. package/src/cli/status.ts +383 -0
  36. package/src/cli/surface-command.ts +247 -0
  37. package/src/cli/tui-startup.ts +32 -0
  38. package/src/cli/types.ts +69 -0
  39. package/src/cli-flags.ts +21 -0
  40. package/src/config/goodvibes-home-audit.ts +465 -0
  41. package/src/config/index.ts +57 -0
  42. package/src/config/provider-model.ts +23 -0
  43. package/src/config/secret-config.ts +119 -0
  44. package/src/config/secrets.ts +71 -0
  45. package/src/config/surface.ts +1 -0
  46. package/src/core/composer-state.ts +61 -0
  47. package/src/core/conversation-rendering.ts +359 -0
  48. package/src/core/conversation.ts +551 -0
  49. package/src/core/history.ts +45 -0
  50. package/src/core/orchestrator.ts +7 -0
  51. package/src/core/system-message-router.ts +171 -0
  52. package/src/daemon/cli.ts +55 -0
  53. package/src/daemon/safe-serve.ts +61 -0
  54. package/src/input/agent-workspace.ts +428 -0
  55. package/src/input/autocomplete.ts +96 -0
  56. package/src/input/bookmark-modal.ts +115 -0
  57. package/src/input/command-args-hint.ts +36 -0
  58. package/src/input/command-registry.ts +329 -0
  59. package/src/input/commands/agent-externalized-tui.ts +73 -0
  60. package/src/input/commands/agent-workspace-runtime.ts +17 -0
  61. package/src/input/commands/branch-runtime.ts +72 -0
  62. package/src/input/commands/cloudflare-runtime.ts +370 -0
  63. package/src/input/commands/config.ts +18 -0
  64. package/src/input/commands/control-room-runtime.ts +255 -0
  65. package/src/input/commands/conversation-runtime.ts +207 -0
  66. package/src/input/commands/discovery-runtime.ts +52 -0
  67. package/src/input/commands/eval.ts +204 -0
  68. package/src/input/commands/experience-runtime.ts +278 -0
  69. package/src/input/commands/guidance-runtime.ts +106 -0
  70. package/src/input/commands/health-runtime.ts +434 -0
  71. package/src/input/commands/hooks-runtime.ts +148 -0
  72. package/src/input/commands/incident-runtime.ts +95 -0
  73. package/src/input/commands/integration-runtime.ts +394 -0
  74. package/src/input/commands/intelligence-runtime.ts +223 -0
  75. package/src/input/commands/knowledge.ts +531 -0
  76. package/src/input/commands/local-auth-runtime.ts +105 -0
  77. package/src/input/commands/local-provider-runtime.ts +170 -0
  78. package/src/input/commands/local-runtime.ts +392 -0
  79. package/src/input/commands/local-setup-review.ts +199 -0
  80. package/src/input/commands/local-setup-transfer.ts +135 -0
  81. package/src/input/commands/local-setup.ts +282 -0
  82. package/src/input/commands/managed-runtime.ts +209 -0
  83. package/src/input/commands/marketplace-runtime.ts +290 -0
  84. package/src/input/commands/mcp-runtime.ts +432 -0
  85. package/src/input/commands/memory-product-runtime.ts +111 -0
  86. package/src/input/commands/memory.ts +151 -0
  87. package/src/input/commands/notify-runtime.ts +83 -0
  88. package/src/input/commands/onboarding-runtime.ts +14 -0
  89. package/src/input/commands/operator-panel-runtime.ts +146 -0
  90. package/src/input/commands/operator-runtime.ts +392 -0
  91. package/src/input/commands/planning-runtime.ts +205 -0
  92. package/src/input/commands/platform-access-runtime.ts +422 -0
  93. package/src/input/commands/platform-services-runtime.ts +246 -0
  94. package/src/input/commands/policy-dispatch.ts +339 -0
  95. package/src/input/commands/policy.ts +17 -0
  96. package/src/input/commands/product-runtime.ts +351 -0
  97. package/src/input/commands/profile-sync-runtime.ts +99 -0
  98. package/src/input/commands/provider-accounts-runtime.ts +113 -0
  99. package/src/input/commands/provider.ts +363 -0
  100. package/src/input/commands/qrcode-runtime.ts +20 -0
  101. package/src/input/commands/quit-shared.ts +162 -0
  102. package/src/input/commands/recall-bundle.ts +132 -0
  103. package/src/input/commands/recall-capture.ts +152 -0
  104. package/src/input/commands/recall-query.ts +229 -0
  105. package/src/input/commands/recall-review.ts +98 -0
  106. package/src/input/commands/recall-shared.ts +22 -0
  107. package/src/input/commands/remote-runtime-pool.ts +106 -0
  108. package/src/input/commands/remote-runtime-setup.ts +199 -0
  109. package/src/input/commands/remote-runtime.ts +431 -0
  110. package/src/input/commands/replay-runtime.ts +18 -0
  111. package/src/input/commands/runtime-services.ts +291 -0
  112. package/src/input/commands/schedule-runtime.ts +91 -0
  113. package/src/input/commands/services-runtime.ts +209 -0
  114. package/src/input/commands/session-content.ts +408 -0
  115. package/src/input/commands/session-workflow.ts +464 -0
  116. package/src/input/commands/session.ts +375 -0
  117. package/src/input/commands/settings-sync-runtime.ts +174 -0
  118. package/src/input/commands/share-runtime.ts +119 -0
  119. package/src/input/commands/shell-core.ts +307 -0
  120. package/src/input/commands/skills-runtime.ts +221 -0
  121. package/src/input/commands/subscription-runtime.ts +434 -0
  122. package/src/input/commands/tasks-runtime.ts +230 -0
  123. package/src/input/commands/teamwork-runtime.ts +339 -0
  124. package/src/input/commands/teleport-runtime.ts +57 -0
  125. package/src/input/commands/tts-runtime.ts +29 -0
  126. package/src/input/commands/work-plan-runtime.ts +169 -0
  127. package/src/input/commands.ts +131 -0
  128. package/src/input/feed-context-factory.ts +254 -0
  129. package/src/input/file-picker.ts +192 -0
  130. package/src/input/handler-command-route.ts +180 -0
  131. package/src/input/handler-content-actions.ts +497 -0
  132. package/src/input/handler-feed-routes.ts +648 -0
  133. package/src/input/handler-feed.ts +452 -0
  134. package/src/input/handler-interactions.ts +281 -0
  135. package/src/input/handler-modal-routes.ts +418 -0
  136. package/src/input/handler-modal-stack.ts +263 -0
  137. package/src/input/handler-modal-token-routes.ts +329 -0
  138. package/src/input/handler-onboarding-cloudflare.ts +391 -0
  139. package/src/input/handler-onboarding.ts +620 -0
  140. package/src/input/handler-picker-routes.ts +472 -0
  141. package/src/input/handler-prompt-buffer.ts +320 -0
  142. package/src/input/handler-shortcuts.ts +213 -0
  143. package/src/input/handler-ui-state.ts +372 -0
  144. package/src/input/handler.ts +729 -0
  145. package/src/input/input-history.ts +297 -0
  146. package/src/input/keybindings.ts +292 -0
  147. package/src/input/mcp-workspace.ts +554 -0
  148. package/src/input/model-picker-provider-filter.ts +28 -0
  149. package/src/input/model-picker-types.ts +137 -0
  150. package/src/input/model-picker.ts +797 -0
  151. package/src/input/onboarding/handler-onboarding-routes.ts +125 -0
  152. package/src/input/onboarding/onboarding-runtime-status.ts +87 -0
  153. package/src/input/onboarding/onboarding-wizard-apply.ts +277 -0
  154. package/src/input/onboarding/onboarding-wizard-cloudflare-step.ts +494 -0
  155. package/src/input/onboarding/onboarding-wizard-cloudflare.ts +204 -0
  156. package/src/input/onboarding/onboarding-wizard-constants.ts +158 -0
  157. package/src/input/onboarding/onboarding-wizard-external-surface-extra-specs.ts +130 -0
  158. package/src/input/onboarding/onboarding-wizard-external-surfaces.ts +762 -0
  159. package/src/input/onboarding/onboarding-wizard-helpers.ts +167 -0
  160. package/src/input/onboarding/onboarding-wizard-rules.ts +256 -0
  161. package/src/input/onboarding/onboarding-wizard-state.ts +365 -0
  162. package/src/input/onboarding/onboarding-wizard-steps.ts +798 -0
  163. package/src/input/onboarding/onboarding-wizard-types.ts +195 -0
  164. package/src/input/onboarding/onboarding-wizard.ts +711 -0
  165. package/src/input/panel-integration-actions.ts +78 -0
  166. package/src/input/profile-picker-modal.ts +222 -0
  167. package/src/input/search.ts +100 -0
  168. package/src/input/selection-modal.ts +163 -0
  169. package/src/input/selection.ts +135 -0
  170. package/src/input/session-picker-modal.ts +136 -0
  171. package/src/input/settings-modal-behavior.ts +37 -0
  172. package/src/input/settings-modal-secrets.ts +41 -0
  173. package/src/input/settings-modal-subscriptions.ts +95 -0
  174. package/src/input/settings-modal-types.ts +91 -0
  175. package/src/input/settings-modal.ts +793 -0
  176. package/src/input/submission-intent.ts +17 -0
  177. package/src/input/submission-router.ts +59 -0
  178. package/src/input/tts-settings-actions.ts +100 -0
  179. package/src/main.ts +792 -0
  180. package/src/mcp/runtime-reload.ts +81 -0
  181. package/src/panels/agent-inspector-panel.ts +521 -0
  182. package/src/panels/agent-inspector-shared.ts +94 -0
  183. package/src/panels/agent-logs-panel.ts +559 -0
  184. package/src/panels/agent-logs-shared.ts +129 -0
  185. package/src/panels/approval-panel.ts +150 -0
  186. package/src/panels/automation-control-panel.ts +212 -0
  187. package/src/panels/base-panel.ts +254 -0
  188. package/src/panels/builtin/agent.ts +117 -0
  189. package/src/panels/builtin/development.ts +31 -0
  190. package/src/panels/builtin/knowledge.ts +26 -0
  191. package/src/panels/builtin/operations.ts +349 -0
  192. package/src/panels/builtin/session.ts +129 -0
  193. package/src/panels/builtin/shared.ts +274 -0
  194. package/src/panels/builtin-panels.ts +23 -0
  195. package/src/panels/cockpit-panel.ts +183 -0
  196. package/src/panels/communication-panel.ts +153 -0
  197. package/src/panels/confirm-state.ts +61 -0
  198. package/src/panels/context-visualizer-panel.ts +204 -0
  199. package/src/panels/control-plane-panel.ts +211 -0
  200. package/src/panels/cost-tracker-panel.ts +444 -0
  201. package/src/panels/debug-panel.ts +432 -0
  202. package/src/panels/diff-panel.ts +520 -0
  203. package/src/panels/docs-panel.ts +283 -0
  204. package/src/panels/eval-panel.ts +399 -0
  205. package/src/panels/file-explorer-panel.ts +584 -0
  206. package/src/panels/file-preview-panel.ts +434 -0
  207. package/src/panels/forensics-panel.ts +364 -0
  208. package/src/panels/git-panel.ts +638 -0
  209. package/src/panels/hooks-panel.ts +239 -0
  210. package/src/panels/incident-review-panel.ts +197 -0
  211. package/src/panels/index.ts +46 -0
  212. package/src/panels/intelligence-panel.ts +176 -0
  213. package/src/panels/knowledge-panel.ts +345 -0
  214. package/src/panels/local-auth-panel.ts +130 -0
  215. package/src/panels/marketplace-panel.ts +212 -0
  216. package/src/panels/memory-panel.ts +225 -0
  217. package/src/panels/ops-control-panel.ts +150 -0
  218. package/src/panels/ops-strategy-panel.ts +235 -0
  219. package/src/panels/orchestration-panel.ts +273 -0
  220. package/src/panels/panel-list-panel.ts +509 -0
  221. package/src/panels/panel-manager.ts +570 -0
  222. package/src/panels/panel-picker.ts +106 -0
  223. package/src/panels/plan-dashboard-panel.ts +274 -0
  224. package/src/panels/plugins-panel.ts +178 -0
  225. package/src/panels/policy-panel.ts +308 -0
  226. package/src/panels/polish.ts +717 -0
  227. package/src/panels/project-planning-panel.ts +711 -0
  228. package/src/panels/provider-account-snapshot.ts +259 -0
  229. package/src/panels/provider-accounts-panel.ts +218 -0
  230. package/src/panels/provider-health-domains.ts +215 -0
  231. package/src/panels/provider-health-panel.ts +727 -0
  232. package/src/panels/provider-health-tracker.ts +115 -0
  233. package/src/panels/provider-stats-panel.ts +366 -0
  234. package/src/panels/qr-panel.ts +182 -0
  235. package/src/panels/remote-panel.ts +449 -0
  236. package/src/panels/routes-panel.ts +178 -0
  237. package/src/panels/sandbox-panel.ts +283 -0
  238. package/src/panels/schedule-panel.ts +329 -0
  239. package/src/panels/scrollable-list-panel.ts +491 -0
  240. package/src/panels/search-focus.ts +32 -0
  241. package/src/panels/security-panel.ts +295 -0
  242. package/src/panels/services-panel.ts +231 -0
  243. package/src/panels/session-browser-panel.ts +400 -0
  244. package/src/panels/session-maintenance.ts +125 -0
  245. package/src/panels/settings-sync-panel.ts +120 -0
  246. package/src/panels/skills-panel.ts +431 -0
  247. package/src/panels/subscription-panel.ts +263 -0
  248. package/src/panels/symbol-outline-panel.ts +486 -0
  249. package/src/panels/system-messages-panel.ts +230 -0
  250. package/src/panels/tasks-panel.ts +399 -0
  251. package/src/panels/thinking-panel.ts +304 -0
  252. package/src/panels/token-budget-panel.ts +475 -0
  253. package/src/panels/tool-inspector-panel.ts +429 -0
  254. package/src/panels/types.ts +54 -0
  255. package/src/panels/watchers-panel.ts +193 -0
  256. package/src/panels/work-plan-panel.ts +175 -0
  257. package/src/panels/worktree-panel.ts +182 -0
  258. package/src/panels/wrfc-panel.ts +609 -0
  259. package/src/permissions/prompt.ts +165 -0
  260. package/src/planning/project-planning-coordinator.ts +543 -0
  261. package/src/plugins/loader.ts +15 -0
  262. package/src/renderer/agent-detail-modal.ts +331 -0
  263. package/src/renderer/agent-workspace.ts +238 -0
  264. package/src/renderer/ansi-sanitize.ts +76 -0
  265. package/src/renderer/autocomplete-overlay.ts +154 -0
  266. package/src/renderer/block-actions.ts +76 -0
  267. package/src/renderer/bookmark-modal.ts +101 -0
  268. package/src/renderer/bottom-bar.ts +58 -0
  269. package/src/renderer/buffer.ts +113 -0
  270. package/src/renderer/code-block.ts +373 -0
  271. package/src/renderer/compositor.ts +283 -0
  272. package/src/renderer/context-inspector.ts +219 -0
  273. package/src/renderer/conversation-layout.ts +67 -0
  274. package/src/renderer/conversation-overlays.ts +140 -0
  275. package/src/renderer/conversation-surface.ts +260 -0
  276. package/src/renderer/diff-view.ts +132 -0
  277. package/src/renderer/diff.ts +130 -0
  278. package/src/renderer/file-picker-overlay.ts +101 -0
  279. package/src/renderer/file-tree.ts +153 -0
  280. package/src/renderer/fullscreen-primitives.ts +130 -0
  281. package/src/renderer/fullscreen-workspace.ts +199 -0
  282. package/src/renderer/git-status.ts +89 -0
  283. package/src/renderer/help-overlay.ts +267 -0
  284. package/src/renderer/history-search-overlay.ts +73 -0
  285. package/src/renderer/layout-engine.ts +97 -0
  286. package/src/renderer/layout.ts +32 -0
  287. package/src/renderer/live-tail-modal.ts +156 -0
  288. package/src/renderer/markdown.ts +635 -0
  289. package/src/renderer/mcp-workspace.ts +237 -0
  290. package/src/renderer/modal-factory.ts +467 -0
  291. package/src/renderer/modal-utils.ts +24 -0
  292. package/src/renderer/model-picker-overlay.ts +473 -0
  293. package/src/renderer/model-workspace.ts +488 -0
  294. package/src/renderer/onboarding/onboarding-wizard.ts +615 -0
  295. package/src/renderer/overlay-box.ts +146 -0
  296. package/src/renderer/overlay-viewport.ts +104 -0
  297. package/src/renderer/panel-composite.ts +158 -0
  298. package/src/renderer/panel-picker-overlay.ts +202 -0
  299. package/src/renderer/panel-tab-bar.ts +69 -0
  300. package/src/renderer/panel-workspace-bar.ts +42 -0
  301. package/src/renderer/process-indicator.ts +96 -0
  302. package/src/renderer/process-modal.ts +656 -0
  303. package/src/renderer/process-summary.ts +67 -0
  304. package/src/renderer/profile-picker-modal.ts +129 -0
  305. package/src/renderer/progress.ts +98 -0
  306. package/src/renderer/qr-renderer.ts +120 -0
  307. package/src/renderer/search-overlay.ts +54 -0
  308. package/src/renderer/selection-modal-overlay.ts +214 -0
  309. package/src/renderer/semantic-diff.ts +369 -0
  310. package/src/renderer/session-picker-modal.ts +127 -0
  311. package/src/renderer/settings-modal-helpers.ts +193 -0
  312. package/src/renderer/settings-modal.ts +537 -0
  313. package/src/renderer/shell-surface.ts +88 -0
  314. package/src/renderer/status-glyphs.ts +21 -0
  315. package/src/renderer/status-token.ts +67 -0
  316. package/src/renderer/surface-layout.ts +101 -0
  317. package/src/renderer/syntax-highlighter.ts +542 -0
  318. package/src/renderer/system-message.ts +83 -0
  319. package/src/renderer/tab-strip.ts +108 -0
  320. package/src/renderer/text-layout.ts +31 -0
  321. package/src/renderer/thinking.ts +17 -0
  322. package/src/renderer/tool-call.ts +234 -0
  323. package/src/renderer/ui-factory.ts +524 -0
  324. package/src/renderer/ui-primitives.ts +96 -0
  325. package/src/runtime/bootstrap-command-context.ts +278 -0
  326. package/src/runtime/bootstrap-command-parts.ts +386 -0
  327. package/src/runtime/bootstrap-core.ts +540 -0
  328. package/src/runtime/bootstrap-hook-bridge.ts +112 -0
  329. package/src/runtime/bootstrap-shell.ts +283 -0
  330. package/src/runtime/bootstrap.ts +575 -0
  331. package/src/runtime/cloudflare-control-plane.ts +349 -0
  332. package/src/runtime/context.ts +142 -0
  333. package/src/runtime/diagnostics/panels/index.ts +24 -0
  334. package/src/runtime/diagnostics/panels/ops.ts +156 -0
  335. package/src/runtime/diagnostics/panels/panel-resources.ts +118 -0
  336. package/src/runtime/diagnostics/panels/policy.ts +177 -0
  337. package/src/runtime/index.ts +662 -0
  338. package/src/runtime/onboarding/apply.ts +642 -0
  339. package/src/runtime/onboarding/derivation.ts +534 -0
  340. package/src/runtime/onboarding/index.ts +7 -0
  341. package/src/runtime/onboarding/markers.ts +148 -0
  342. package/src/runtime/onboarding/snapshot.ts +406 -0
  343. package/src/runtime/onboarding/state.ts +141 -0
  344. package/src/runtime/onboarding/types.ts +404 -0
  345. package/src/runtime/onboarding/verify.ts +171 -0
  346. package/src/runtime/operator-token-cleanup.ts +27 -0
  347. package/src/runtime/perf/panel-contracts.ts +32 -0
  348. package/src/runtime/perf/panel-health-monitor.ts +18 -0
  349. package/src/runtime/sandbox-public-gaps.ts +358 -0
  350. package/src/runtime/services.ts +670 -0
  351. package/src/runtime/store/domains/domain-read-matrix.ts +15 -0
  352. package/src/runtime/store/domains/index.ts +222 -0
  353. package/src/runtime/store/domains/panels.ts +117 -0
  354. package/src/runtime/store/domains/ui-perf.ts +103 -0
  355. package/src/runtime/store/index.ts +305 -0
  356. package/src/runtime/store/selectors/index.ts +359 -0
  357. package/src/runtime/store/state.ts +145 -0
  358. package/src/runtime/surface-feature-flags.ts +65 -0
  359. package/src/runtime/terminal-output-guard.ts +228 -0
  360. package/src/runtime/ui/index.ts +39 -0
  361. package/src/runtime/ui/model-picker/data-provider.ts +182 -0
  362. package/src/runtime/ui/model-picker/health-enrichment.ts +228 -0
  363. package/src/runtime/ui/model-picker/index.ts +59 -0
  364. package/src/runtime/ui/model-picker/types.ts +149 -0
  365. package/src/runtime/ui/provider-health/data-provider.ts +244 -0
  366. package/src/runtime/ui/provider-health/fallback-visualizer.ts +71 -0
  367. package/src/runtime/ui/provider-health/index.ts +46 -0
  368. package/src/runtime/ui/provider-health/types.ts +146 -0
  369. package/src/runtime/ui-events.ts +1 -0
  370. package/src/runtime/ui-read-model-helpers.ts +1 -0
  371. package/src/runtime/ui-read-models-observability-maintenance.ts +1 -0
  372. package/src/runtime/ui-read-models-observability-options.ts +1 -0
  373. package/src/runtime/ui-read-models-observability-remote.ts +1 -0
  374. package/src/runtime/ui-read-models-observability-security.ts +1 -0
  375. package/src/runtime/ui-read-models-observability-system.ts +1 -0
  376. package/src/runtime/ui-read-models-observability.ts +1 -0
  377. package/src/runtime/ui-read-models.ts +61 -0
  378. package/src/runtime/ui-service-queries.ts +1 -0
  379. package/src/runtime/ui-services.ts +190 -0
  380. package/src/scripts/process-messages.ts +42 -0
  381. package/src/shell/blocking-input.ts +98 -0
  382. package/src/shell/service-settings-sync.ts +273 -0
  383. package/src/shell/ui-openers.ts +352 -0
  384. package/src/tools/index.ts +1 -0
  385. package/src/tools/wrfc-agent-guard.ts +49 -0
  386. package/src/types/grid.ts +48 -0
  387. package/src/types/sql-js.d.ts +15 -0
  388. package/src/utils/clipboard.ts +22 -0
  389. package/src/utils/splash-lines.ts +46 -0
  390. package/src/utils/terminal-width.ts +185 -0
  391. package/src/verification/live-verifier.ts +430 -0
  392. package/src/verification/verification-ledger.ts +242 -0
  393. package/src/version.ts +17 -0
  394. package/src/widget/index.ts +2 -0
  395. package/src/widget/types.ts +9 -0
  396. package/src/widget/widget.ts +8 -0
  397. package/src/work-plans/work-plan-store.ts +374 -0
  398. package/tsconfig.json +18 -0
@@ -0,0 +1,642 @@
1
+ import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { isSecretRefInput } from '@pellux/goodvibes-sdk/platform/config';
4
+ import { CONFIG_SCHEMA, DEFAULT_CONFIG } from '../../config/index.ts';
5
+ import type { FeatureFlagConfigKey } from '../surface-feature-flags.ts';
6
+ import {
7
+ getOnboardingRuntimeStatePath,
8
+ readOnboardingRuntimeState,
9
+ writeOnboardingAcknowledgementState,
10
+ } from './state.ts';
11
+ import { verifyOnboardingRequest } from './verify.ts';
12
+ import { GOODVIBES_AGENT_SURFACE_ROOT } from '../../config/surface.ts';
13
+ import type {
14
+ OnboardingApplyDependencies,
15
+ OnboardingAppliedOperation,
16
+ OnboardingApplyError,
17
+ OnboardingApplyOperation,
18
+ OnboardingApplyRequest,
19
+ OnboardingApplyResult,
20
+ } from './types.ts';
21
+
22
+ function getNow(deps: Pick<OnboardingApplyDependencies, 'clock'>): number {
23
+ return deps.clock?.() ?? Date.now();
24
+ }
25
+
26
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
27
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
28
+ }
29
+
30
+ function readJsonObject(path: string): Record<string, unknown> {
31
+ if (!existsSync(path)) return {};
32
+ const parsed = JSON.parse(readFileSync(path, 'utf-8')) as unknown;
33
+ if (!isPlainObject(parsed)) throw new Error(`Expected an object JSON payload at ${path}.`);
34
+ return parsed;
35
+ }
36
+
37
+ function writeJsonObject(path: string, payload: Record<string, unknown>): void {
38
+ mkdirSync(dirname(path), { recursive: true });
39
+ writeFileSync(path, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
40
+ }
41
+
42
+ function setNestedValue(root: Record<string, unknown>, key: string, value: unknown): Record<string, unknown> {
43
+ const parts = key.split('.');
44
+ const next = structuredClone(root);
45
+ let cursor: Record<string, unknown> = next;
46
+
47
+ for (let index = 0; index < parts.length - 1; index += 1) {
48
+ const part = parts[index]!;
49
+ const existing = cursor[part];
50
+ if (!isPlainObject(existing)) cursor[part] = {};
51
+ cursor = cursor[part] as Record<string, unknown>;
52
+ }
53
+
54
+ cursor[parts[parts.length - 1]!] = structuredClone(value);
55
+ return next;
56
+ }
57
+
58
+ type RollbackAction = () => Promise<void> | void;
59
+
60
+ interface BootstrapCredential {
61
+ readonly username: string;
62
+ readonly password: string;
63
+ }
64
+
65
+ interface PersistedAuthUser {
66
+ readonly username: string;
67
+ readonly passwordHash: string;
68
+ readonly roles?: readonly string[];
69
+ }
70
+
71
+ interface MutableAuthManager {
72
+ readonly users?: Map<string, PersistedAuthUser>;
73
+ readonly sessions?: Map<string, { readonly token: string; readonly username: string; readonly expiresAt: number }>;
74
+ }
75
+
76
+ function restoreFile(path: string, previous: string | null, reload?: () => void): void {
77
+ if (previous === null) {
78
+ if (existsSync(path)) unlinkSync(path);
79
+ } else {
80
+ mkdirSync(dirname(path), { recursive: true });
81
+ writeFileSync(path, previous, 'utf-8');
82
+ }
83
+ reload?.();
84
+ }
85
+
86
+ function parseBootstrapCredential(content: string | null): BootstrapCredential | null {
87
+ if (content === null) return null;
88
+ let username = '';
89
+ let password = '';
90
+ for (const rawLine of content.split('\n')) {
91
+ const line = rawLine.trim();
92
+ if (line.startsWith('username=')) username = line.slice('username='.length);
93
+ if (line.startsWith('password=')) password = line.slice('password='.length);
94
+ }
95
+ return username.length > 0 && password.length > 0 ? { username, password } : null;
96
+ }
97
+
98
+ function parsePersistedAuthUsers(content: string): readonly PersistedAuthUser[] {
99
+ const parsed = JSON.parse(content) as unknown;
100
+ if (!isPlainObject(parsed) || parsed.version !== 1 || !Array.isArray(parsed.users)) {
101
+ throw new Error('Expected a version 1 local auth user store.');
102
+ }
103
+ return parsed.users.filter((user): user is PersistedAuthUser => (
104
+ isPlainObject(user)
105
+ && typeof user.username === 'string'
106
+ && typeof user.passwordHash === 'string'
107
+ && (user.roles === undefined || (Array.isArray(user.roles) && user.roles.every((role) => typeof role === 'string')))
108
+ ));
109
+ }
110
+
111
+ function snapshotFileRollback(path: string, reload?: () => void): RollbackAction {
112
+ const previous = existsSync(path) ? readFileSync(path, 'utf-8') : null;
113
+ return () => restoreFile(path, previous, reload);
114
+ }
115
+
116
+ async function runRollbacks(rollbacks: readonly RollbackAction[]): Promise<readonly string[]> {
117
+ const errors: string[] = [];
118
+ for (const rollback of [...rollbacks].reverse()) {
119
+ try {
120
+ await rollback();
121
+ } catch (error) {
122
+ errors.push(error instanceof Error ? error.message : String(error));
123
+ }
124
+ }
125
+ return errors;
126
+ }
127
+
128
+ function isGoodVibesSecretReferenceValue(value: string): boolean {
129
+ const normalized = value.trim();
130
+ return normalized.startsWith('goodvibes://secrets/') && isSecretRefInput(normalized);
131
+ }
132
+
133
+ function isMalformedGoodVibesSecretReferenceValue(value: string): boolean {
134
+ const normalized = value.trim();
135
+ return normalized.startsWith('goodvibes://') && !isGoodVibesSecretReferenceValue(normalized);
136
+ }
137
+
138
+ function isFeatureFlagConfigKey(key: string): key is FeatureFlagConfigKey {
139
+ return key === 'featureFlags' || key.startsWith('featureFlags.');
140
+ }
141
+
142
+ function validateFeatureFlagConfigValue(operation: Extract<OnboardingApplyOperation, { kind: 'set-config' }>): boolean {
143
+ if (!isFeatureFlagConfigKey(operation.key)) return false;
144
+
145
+ if (operation.key === 'featureFlags') {
146
+ if (!isPlainObject(operation.value)) throw new Error('featureFlags expects an object value.');
147
+ for (const [flagId, state] of Object.entries(operation.value)) {
148
+ if (flagId.trim().length === 0) throw new Error('featureFlags cannot contain an empty feature id.');
149
+ if (state !== 'enabled' && state !== 'disabled') {
150
+ throw new Error(`featureFlags.${flagId} expects enabled or disabled.`);
151
+ }
152
+ }
153
+ return true;
154
+ }
155
+
156
+ const flagId = operation.key.slice('featureFlags.'.length);
157
+ if (flagId.trim().length === 0) throw new Error('featureFlags requires a feature id.');
158
+ if (operation.value !== 'enabled' && operation.value !== 'disabled') {
159
+ throw new Error(`Config key ${operation.key} expects enabled or disabled.`);
160
+ }
161
+ return true;
162
+ }
163
+
164
+ function validateConfigValue(operation: Extract<OnboardingApplyOperation, { kind: 'set-config' }>): void {
165
+ if (typeof operation.value === 'string' && isMalformedGoodVibesSecretReferenceValue(operation.value)) {
166
+ throw new Error(`Config key ${operation.key} only accepts goodvibes://secrets/... secret references.`);
167
+ }
168
+
169
+ if (validateFeatureFlagConfigValue(operation)) return;
170
+
171
+ const schema = CONFIG_SCHEMA.find((entry) => entry.key === operation.key);
172
+ if (!schema) {
173
+ const defaultValue = operation.key.split('.').reduce<unknown>((cursor, part) => (
174
+ isPlainObject(cursor) ? cursor[part] : undefined
175
+ ), DEFAULT_CONFIG);
176
+ if (defaultValue === undefined) throw new Error(`Unknown config key: ${operation.key}`);
177
+ if (typeof defaultValue === 'boolean' && typeof operation.value !== 'boolean') {
178
+ throw new Error(`Config key ${operation.key} expects a boolean value.`);
179
+ }
180
+ if (typeof defaultValue === 'number' && typeof operation.value !== 'number') {
181
+ throw new Error(`Config key ${operation.key} expects a numeric value.`);
182
+ }
183
+ if (typeof defaultValue === 'string' && typeof operation.value !== 'string') {
184
+ throw new Error(`Config key ${operation.key} expects a string value.`);
185
+ }
186
+ return;
187
+ }
188
+ const stringValue = typeof operation.value === 'string' ? operation.value : null;
189
+
190
+ if (schema.type === 'boolean' && typeof operation.value !== 'boolean') {
191
+ throw new Error(`Config key ${operation.key} expects a boolean value.`);
192
+ }
193
+
194
+ if (schema.type === 'number' && typeof operation.value !== 'number') {
195
+ throw new Error(`Config key ${operation.key} expects a numeric value.`);
196
+ }
197
+
198
+ if ((schema.type === 'string' || schema.type === 'enum') && stringValue === null) {
199
+ throw new Error(`Config key ${operation.key} expects a string value.`);
200
+ }
201
+
202
+ if (schema.type === 'enum' && schema.enumValues && stringValue !== null && !schema.enumValues.includes(stringValue)) {
203
+ throw new Error(`Invalid value for ${operation.key}: ${String(operation.value)}.`);
204
+ }
205
+
206
+ if (schema.validate && !schema.validate(operation.value)) {
207
+ throw new Error(`Invalid value for ${operation.key}: ${String(operation.value)}.`);
208
+ }
209
+ }
210
+
211
+ function validateSecretOperation(
212
+ deps: OnboardingApplyDependencies,
213
+ operation: Extract<OnboardingApplyOperation, { kind: 'set-secret' }>,
214
+ ): void {
215
+ if (!deps.secrets) throw new Error('Secret persistence is unavailable.');
216
+ if (operation.key.trim().length === 0) throw new Error('Secret key is required.');
217
+ if (operation.value.length === 0) throw new Error(`Secret value for ${operation.key} is required.`);
218
+ if (!operation.medium) throw new Error(`Secret storage medium for ${operation.key} is required.`);
219
+ if (isMalformedGoodVibesSecretReferenceValue(operation.value)) {
220
+ throw new Error(`Secret value for ${operation.key} only accepts goodvibes://secrets/... secret references.`);
221
+ }
222
+ }
223
+
224
+ function validateAuthOperation(
225
+ deps: OnboardingApplyDependencies,
226
+ operation: Extract<OnboardingApplyOperation, { kind: 'ensure-auth-user' }>,
227
+ ): void {
228
+ if (!deps.auth) throw new Error('Local auth management is unavailable.');
229
+ if (operation.username.trim().length === 0) throw new Error('Local auth username is required.');
230
+ if (operation.password.length === 0) throw new Error(`Local auth password for ${operation.username} is required.`);
231
+ const username = operation.username.trim();
232
+ const existing = deps.auth.inspect().users.find((user) => user.username === username);
233
+ const requiredRoles = operation.roles ?? ['admin'];
234
+ if (existing && !requiredRoles.every((role) => existing.roles.includes(role))) {
235
+ throw new Error(`Existing local auth user ${username} is missing required role(s): ${requiredRoles.join(', ')}.`);
236
+ }
237
+ }
238
+
239
+ function validateAcknowledgementOperation(
240
+ deps: OnboardingApplyDependencies,
241
+ operation: Extract<OnboardingApplyOperation, { kind: 'acknowledge' }>,
242
+ ): void {
243
+ if (typeof operation.acknowledged !== 'boolean') {
244
+ throw new Error(`${operation.target} acknowledgement must be boolean.`);
245
+ }
246
+
247
+ const state = readOnboardingRuntimeState(deps.shellPaths, deps.acknowledgementScope ?? 'project');
248
+ if (state.parseError) {
249
+ throw new Error(`Existing onboarding acknowledgement state could not be parsed: ${state.parseError}`);
250
+ }
251
+ }
252
+
253
+ function applyConfigOperation(
254
+ deps: OnboardingApplyDependencies,
255
+ operation: Extract<OnboardingApplyOperation, { kind: 'set-config' }>,
256
+ ): OnboardingAppliedOperation {
257
+ validateConfigValue(operation);
258
+
259
+ if ((operation.scope ?? 'global') === 'project') {
260
+ const path = deps.shellPaths.resolveProjectPath(GOODVIBES_AGENT_SURFACE_ROOT, 'settings.json');
261
+ const existing = readJsonObject(path);
262
+ const updated = setNestedValue(existing, operation.key, operation.value);
263
+ writeJsonObject(path, updated);
264
+ deps.config.load();
265
+
266
+ return {
267
+ kind: operation.kind,
268
+ summary: `Persisted ${operation.key} in project onboarding settings.`,
269
+ };
270
+ }
271
+
272
+ deps.config.setDynamic(operation.key as never, operation.value);
273
+ return {
274
+ kind: operation.kind,
275
+ summary: `Updated ${operation.key} in global onboarding settings.`,
276
+ };
277
+ }
278
+
279
+ async function applySecretOperation(
280
+ deps: OnboardingApplyDependencies,
281
+ operation: Extract<OnboardingApplyOperation, { kind: 'set-secret' }>,
282
+ ): Promise<OnboardingAppliedOperation> {
283
+ validateSecretOperation(deps, operation);
284
+ await deps.secrets!.set(operation.key, operation.value, {
285
+ scope: operation.scope ?? 'project',
286
+ ...(operation.medium ? { medium: operation.medium } : {}),
287
+ });
288
+
289
+ return {
290
+ kind: operation.kind,
291
+ summary: `Stored ${operation.key} through the configured secret manager.`,
292
+ };
293
+ }
294
+
295
+ function applyAuthOperation(
296
+ deps: OnboardingApplyDependencies,
297
+ operation: Extract<OnboardingApplyOperation, { kind: 'ensure-auth-user' }>,
298
+ ): OnboardingAppliedOperation {
299
+ validateAuthOperation(deps, operation);
300
+ const auth = deps.auth!;
301
+ const username = operation.username.trim();
302
+ const before = auth.inspect();
303
+ const existing = before.users.find((user) => user.username === username);
304
+ const bootstrapCredential = before.bootstrapCredentialPresent
305
+ ? parseBootstrapCredential(readFileSync(before.bootstrapCredentialPath, 'utf-8'))
306
+ : null;
307
+
308
+ if (existing) {
309
+ auth.rotatePassword(username, operation.password);
310
+ } else {
311
+ auth.addUser(username, operation.password, operation.roles ?? ['admin']);
312
+ }
313
+
314
+ if (operation.retireBootstrapCredential) {
315
+ if (bootstrapCredential && bootstrapCredential.username !== username && auth.getUser(bootstrapCredential.username)) {
316
+ auth.deleteUser(bootstrapCredential.username);
317
+ }
318
+ auth.clearBootstrapCredentialFile();
319
+ }
320
+
321
+ if (operation.createSession ?? true) {
322
+ auth.createSession(username);
323
+ }
324
+
325
+ return {
326
+ kind: operation.kind,
327
+ summary: existing
328
+ ? `Updated local auth user ${username}.`
329
+ : `Created local auth user ${username}.`,
330
+ };
331
+ }
332
+
333
+ async function buildSecretRollbackAction(
334
+ deps: OnboardingApplyDependencies,
335
+ operation: Extract<OnboardingApplyOperation, { kind: 'set-secret' }>,
336
+ ): Promise<RollbackAction> {
337
+ validateSecretOperation(deps, operation);
338
+ const scope = operation.scope ?? 'project';
339
+ const review = await deps.secrets!.inspect();
340
+ const locations = review.locations.filter((entry) => entry.source.startsWith(`${scope}-`));
341
+ if (locations.length === 0) throw new Error(`Secret storage locations for ${scope} scope are unavailable.`);
342
+ const snapshots = locations.map((location) => ({
343
+ path: location.path,
344
+ previous: existsSync(location.path) ? readFileSync(location.path, 'utf-8') : null,
345
+ }));
346
+ return () => {
347
+ for (const snapshot of snapshots) restoreFile(snapshot.path, snapshot.previous);
348
+ };
349
+ }
350
+
351
+ function buildAuthRollbackAction(
352
+ deps: OnboardingApplyDependencies,
353
+ operation: Extract<OnboardingApplyOperation, { kind: 'ensure-auth-user' }>,
354
+ ): RollbackAction {
355
+ validateAuthOperation(deps, operation);
356
+ const auth = deps.auth!;
357
+ const mutable = auth as unknown as MutableAuthManager;
358
+ const username = operation.username.trim();
359
+ const before = auth.inspect();
360
+ const existingUser = before.users.find((user) => user.username === username);
361
+ const existingSessionFingerprints = new Set(before.sessions
362
+ .filter((session) => session.username === username)
363
+ .map((session) => session.tokenFingerprint));
364
+ const userStoreSnapshot = existsSync(before.userStorePath) ? readFileSync(before.userStorePath, 'utf-8') : null;
365
+ const bootstrapCredentialSnapshot = existsSync(before.bootstrapCredentialPath)
366
+ ? readFileSync(before.bootstrapCredentialPath, 'utf-8')
367
+ : null;
368
+ const bootstrapCredential = parseBootstrapCredential(bootstrapCredentialSnapshot);
369
+ const beforeSessions = mutable.sessions instanceof Map
370
+ ? [...mutable.sessions.entries()].map(([token, session]) => [token, { ...session }] as const)
371
+ : [];
372
+
373
+ return () => {
374
+ for (const session of auth.inspect().sessions) {
375
+ if (session.username === username && !existingSessionFingerprints.has(session.tokenFingerprint)) {
376
+ auth.revokeSession(session.tokenFingerprint);
377
+ }
378
+ }
379
+
380
+ if (bootstrapCredential && !auth.getUser(bootstrapCredential.username)) {
381
+ auth.addUser(bootstrapCredential.username, bootstrapCredential.password, ['admin']);
382
+ }
383
+
384
+ if (!existingUser && auth.getUser(username)) {
385
+ try {
386
+ auth.deleteUser(username);
387
+ } catch (error) {
388
+ if (mutable.users instanceof Map) mutable.users.delete(username);
389
+ else throw error;
390
+ }
391
+ }
392
+
393
+ restoreFile(before.bootstrapCredentialPath, bootstrapCredentialSnapshot);
394
+ restoreFile(before.userStorePath, userStoreSnapshot);
395
+
396
+ if (mutable.users instanceof Map) {
397
+ if (userStoreSnapshot === null) {
398
+ if (before.users.length === 0) mutable.users.clear();
399
+ } else {
400
+ mutable.users.clear();
401
+ for (const user of parsePersistedAuthUsers(userStoreSnapshot)) mutable.users.set(user.username, user);
402
+ }
403
+ }
404
+
405
+ if (mutable.sessions instanceof Map) {
406
+ mutable.sessions.clear();
407
+ for (const [token, session] of beforeSessions) mutable.sessions.set(token, session);
408
+ }
409
+ };
410
+ }
411
+
412
+ async function buildRollbackAction(
413
+ deps: OnboardingApplyDependencies,
414
+ operation: OnboardingApplyOperation,
415
+ ): Promise<RollbackAction> {
416
+ if (operation.kind === 'set-config') {
417
+ if ((operation.scope ?? 'global') === 'project') {
418
+ return snapshotFileRollback(
419
+ deps.shellPaths.resolveProjectPath(GOODVIBES_AGENT_SURFACE_ROOT, 'settings.json'),
420
+ () => deps.config.load(),
421
+ );
422
+ }
423
+
424
+ const previous = deps.config.get(operation.key as never);
425
+ return () => {
426
+ deps.config.setDynamic(operation.key as never, previous);
427
+ };
428
+ }
429
+
430
+ if (operation.kind === 'set-secret') {
431
+ return buildSecretRollbackAction(deps, operation);
432
+ }
433
+
434
+ if (operation.kind === 'ensure-auth-user') {
435
+ return buildAuthRollbackAction(deps, operation);
436
+ }
437
+
438
+ if (operation.kind === 'acknowledge') {
439
+ return snapshotFileRollback(
440
+ getOnboardingRuntimeStatePath(deps.shellPaths, deps.acknowledgementScope ?? 'project'),
441
+ );
442
+ }
443
+
444
+ const neverOperation: never = operation;
445
+ throw new Error(`Unsupported onboarding operation: ${JSON.stringify(neverOperation)}`);
446
+ }
447
+
448
+ function applyAcknowledgementOperation(
449
+ deps: OnboardingApplyDependencies,
450
+ request: OnboardingApplyRequest,
451
+ operation: Extract<OnboardingApplyOperation, { kind: 'acknowledge' }>,
452
+ ): OnboardingAppliedOperation {
453
+ writeOnboardingAcknowledgementState(deps.shellPaths, {
454
+ scope: deps.acknowledgementScope ?? 'project',
455
+ target: operation.target,
456
+ acknowledged: operation.acknowledged,
457
+ updatedAt: getNow(deps),
458
+ source: request.source,
459
+ mode: request.mode,
460
+ });
461
+
462
+ return {
463
+ kind: operation.kind,
464
+ summary: `${operation.target} acknowledgement set to ${operation.acknowledged ? 'accepted' : 'pending'}.`,
465
+ };
466
+ }
467
+
468
+ function orderApplyOperations(
469
+ operations: readonly OnboardingApplyOperation[],
470
+ ): readonly OnboardingApplyOperation[] {
471
+ const secretPolicyOperations = operations.filter((operation) => (
472
+ operation.kind === 'set-config' && operation.key === 'storage.secretPolicy'
473
+ ));
474
+ const authOperations = operations.filter((operation) => operation.kind === 'ensure-auth-user');
475
+ const secretOperations = operations.filter((operation) => operation.kind === 'set-secret');
476
+ const configOperations = operations.filter((operation) => (
477
+ operation.kind === 'set-config' && operation.key !== 'storage.secretPolicy'
478
+ ));
479
+ const finalOperations = operations.filter((operation) => (
480
+ operation.kind === 'acknowledge'
481
+ ));
482
+
483
+ return [
484
+ ...secretPolicyOperations,
485
+ ...authOperations,
486
+ ...secretOperations,
487
+ ...configOperations,
488
+ ...finalOperations,
489
+ ];
490
+ }
491
+
492
+ function prevalidateApplyRequest(
493
+ deps: OnboardingApplyDependencies,
494
+ request: OnboardingApplyRequest,
495
+ ): OnboardingApplyError[] {
496
+ const errors: OnboardingApplyError[] = [];
497
+ const orderedOperations = orderApplyOperations(request.operations);
498
+
499
+ for (const operation of orderedOperations) {
500
+ try {
501
+ if (operation.kind === 'set-config') {
502
+ validateConfigValue(operation);
503
+ if ((operation.scope ?? 'global') === 'project') {
504
+ readJsonObject(deps.shellPaths.resolveProjectPath(GOODVIBES_AGENT_SURFACE_ROOT, 'settings.json'));
505
+ }
506
+ continue;
507
+ }
508
+
509
+ if (operation.kind === 'set-secret') {
510
+ validateSecretOperation(deps, operation);
511
+ continue;
512
+ }
513
+
514
+ if (operation.kind === 'ensure-auth-user') {
515
+ validateAuthOperation(deps, operation);
516
+ continue;
517
+ }
518
+
519
+ if (operation.kind === 'acknowledge') {
520
+ validateAcknowledgementOperation(deps, operation);
521
+ continue;
522
+ }
523
+
524
+ const neverOperation: never = operation;
525
+ throw new Error(`Unsupported onboarding operation: ${JSON.stringify(neverOperation)}`);
526
+ } catch (error) {
527
+ errors.push({
528
+ kind: operation.kind,
529
+ message: error instanceof Error ? error.message : String(error),
530
+ });
531
+ }
532
+ }
533
+
534
+ return errors;
535
+ }
536
+
537
+ function getVerificationFailureKind(itemId: string): OnboardingApplyOperation['kind'] {
538
+ if (itemId.startsWith('config:')) return 'set-config';
539
+ if (itemId.startsWith('secret:')) return 'set-secret';
540
+ if (itemId.startsWith('auth:')) return 'ensure-auth-user';
541
+ if (itemId.startsWith('acknowledge:')) return 'acknowledge';
542
+ return 'set-config';
543
+ }
544
+
545
+ export async function applyOnboardingRequest(
546
+ deps: OnboardingApplyDependencies,
547
+ request: OnboardingApplyRequest,
548
+ ): Promise<OnboardingApplyResult> {
549
+ const applied: OnboardingAppliedOperation[] = [];
550
+ const skipped: never[] = [];
551
+ const errors: OnboardingApplyError[] = prevalidateApplyRequest(deps, request);
552
+ if (errors.length > 0) {
553
+ return {
554
+ ok: false,
555
+ applied,
556
+ skipped,
557
+ errors,
558
+ };
559
+ }
560
+
561
+ const orderedOperations = orderApplyOperations(request.operations);
562
+ const rollbacks: RollbackAction[] = [];
563
+
564
+ const applyOperations = async (operations: readonly OnboardingApplyOperation[]): Promise<boolean> => {
565
+ for (const operation of operations) {
566
+ let rollback: RollbackAction = () => {};
567
+ try {
568
+ rollback = await buildRollbackAction(deps, operation);
569
+ if (operation.kind === 'set-config') {
570
+ applied.push(applyConfigOperation(deps, operation));
571
+ rollbacks.push(rollback);
572
+ continue;
573
+ }
574
+
575
+ if (operation.kind === 'set-secret') {
576
+ applied.push(await applySecretOperation(deps, operation));
577
+ rollbacks.push(rollback);
578
+ continue;
579
+ }
580
+
581
+ if (operation.kind === 'ensure-auth-user') {
582
+ applied.push(applyAuthOperation(deps, operation));
583
+ rollbacks.push(rollback);
584
+ continue;
585
+ }
586
+
587
+ if (operation.kind === 'acknowledge') {
588
+ applied.push(applyAcknowledgementOperation(deps, request, operation));
589
+ rollbacks.push(rollback);
590
+ continue;
591
+ }
592
+
593
+ const neverOperation: never = operation;
594
+ throw new Error(`Unsupported onboarding operation: ${JSON.stringify(neverOperation)}`);
595
+ } catch (error) {
596
+ const rollbackErrors = await runRollbacks([...rollbacks, rollback]);
597
+ applied.length = 0;
598
+ errors.push({
599
+ kind: operation.kind,
600
+ message: [
601
+ error instanceof Error ? error.message : String(error),
602
+ ...rollbackErrors.map((rollbackError) => `rollback: ${rollbackError}`),
603
+ ].join('; '),
604
+ });
605
+ return false;
606
+ }
607
+ }
608
+ return true;
609
+ };
610
+
611
+ const verifyOrRollback = async (operations: readonly OnboardingApplyOperation[]): Promise<boolean> => {
612
+ const verification = await verifyOnboardingRequest(deps, { ...request, operations });
613
+ const failures = verification.items.filter((item) => item.status !== 'pass');
614
+ if (failures.length === 0) return true;
615
+
616
+ const rollbackErrors = await runRollbacks(rollbacks);
617
+ applied.length = 0;
618
+ errors.push(...failures.map((item, index) => ({
619
+ kind: getVerificationFailureKind(item.id),
620
+ message: [
621
+ `verify ${item.id}: ${item.message}`,
622
+ ...(index === 0 ? rollbackErrors.map((rollbackError) => `rollback: ${rollbackError}`) : []),
623
+ ].join('; '),
624
+ })));
625
+ return false;
626
+ };
627
+
628
+ if (!await applyOperations(orderedOperations)) {
629
+ return { ok: false, applied, skipped, errors };
630
+ }
631
+
632
+ if (!await verifyOrRollback(orderedOperations)) {
633
+ return { ok: false, applied, skipped, errors };
634
+ }
635
+
636
+ return {
637
+ ok: errors.length === 0,
638
+ applied,
639
+ skipped,
640
+ errors,
641
+ };
642
+ }