@vellumai/assistant 0.5.5 → 0.5.7

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 (382) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +4 -5
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docs/architecture/keychain-broker.md +45 -240
  7. package/docs/architecture/security.md +0 -17
  8. package/docs/credential-execution-service.md +2 -2
  9. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  10. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +119 -0
  11. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  12. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  13. package/package.json +2 -3
  14. package/src/__tests__/actor-token-service.test.ts +1 -2
  15. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  16. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  17. package/src/__tests__/btw-routes.test.ts +0 -39
  18. package/src/__tests__/call-domain.test.ts +0 -128
  19. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  20. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  21. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  22. package/src/__tests__/checker.test.ts +4 -2
  23. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  24. package/src/__tests__/config-schema-cmd.test.ts +0 -1
  25. package/src/__tests__/config-schema.test.ts +3 -3
  26. package/src/__tests__/context-window-manager.test.ts +78 -0
  27. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  28. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  29. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  30. package/src/__tests__/conversation-title-service.test.ts +117 -1
  31. package/src/__tests__/credential-execution-feature-gates.test.ts +28 -14
  32. package/src/__tests__/credential-execution-managed-contract.test.ts +33 -18
  33. package/src/__tests__/credential-security-e2e.test.ts +0 -66
  34. package/src/__tests__/credential-security-invariants.test.ts +4 -45
  35. package/src/__tests__/credentials-cli.test.ts +78 -0
  36. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  37. package/src/__tests__/docker-signing-key-bootstrap.test.ts +98 -0
  38. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  39. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  40. package/src/__tests__/host-shell-tool.test.ts +6 -7
  41. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  42. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  43. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  44. package/src/__tests__/intent-routing.test.ts +0 -13
  45. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  46. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  47. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  48. package/src/__tests__/memory-regressions.test.ts +8 -30
  49. package/src/__tests__/migration-export-http.test.ts +2 -2
  50. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  51. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  52. package/src/__tests__/migration-validate-http.test.ts +2 -2
  53. package/src/__tests__/non-member-access-request.test.ts +0 -5
  54. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  55. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  56. package/src/__tests__/permission-types.test.ts +1 -0
  57. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  58. package/src/__tests__/qdrant-manager.test.ts +28 -2
  59. package/src/__tests__/registry.test.ts +0 -6
  60. package/src/__tests__/require-fresh-approval.test.ts +4 -0
  61. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  62. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  63. package/src/__tests__/secure-keys.test.ts +83 -263
  64. package/src/__tests__/shell-identity.test.ts +96 -6
  65. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  66. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  67. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  68. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  69. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  70. package/src/__tests__/skill-load-tool.test.ts +0 -2
  71. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  72. package/src/__tests__/skills.test.ts +0 -2
  73. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  74. package/src/__tests__/suggestion-routes.test.ts +1 -32
  75. package/src/__tests__/system-prompt.test.ts +0 -1
  76. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
  77. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  78. package/src/__tests__/tool-executor.test.ts +4 -0
  79. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  80. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  81. package/src/__tests__/update-bulletin.test.ts +0 -2
  82. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  83. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -6
  84. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  85. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +218 -0
  86. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  87. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  88. package/src/calls/audio-store.test.ts +97 -0
  89. package/src/calls/audio-store.ts +205 -0
  90. package/src/calls/call-controller.ts +85 -7
  91. package/src/calls/call-domain.ts +3 -0
  92. package/src/calls/call-store.ts +10 -3
  93. package/src/calls/fish-audio-client.ts +117 -0
  94. package/src/calls/relay-server.ts +27 -0
  95. package/src/calls/twilio-routes.ts +2 -1
  96. package/src/calls/types.ts +1 -0
  97. package/src/calls/voice-ingress-preflight.ts +0 -42
  98. package/src/calls/voice-quality.ts +26 -5
  99. package/src/calls/voice-session-bridge.ts +6 -12
  100. package/src/cli/commands/config.ts +1 -4
  101. package/src/cli/commands/conversations.ts +0 -18
  102. package/src/cli/commands/credentials.ts +34 -4
  103. package/src/cli/commands/oauth/index.ts +7 -0
  104. package/src/cli/commands/oauth/platform.ts +179 -0
  105. package/src/cli/commands/platform.ts +3 -3
  106. package/src/config/assistant-feature-flags.ts +186 -5
  107. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  108. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  109. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  110. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  111. package/src/config/bundled-tool-registry.ts +1 -11
  112. package/src/config/env-registry.ts +1 -1
  113. package/src/config/env.ts +16 -16
  114. package/src/config/feature-flag-registry.json +48 -16
  115. package/src/config/loader.ts +98 -31
  116. package/src/config/schema.ts +4 -25
  117. package/src/config/schemas/calls.ts +13 -0
  118. package/src/config/schemas/fish-audio.ts +39 -0
  119. package/src/config/schemas/memory.ts +0 -4
  120. package/src/config/schemas/platform.ts +1 -1
  121. package/src/config/schemas/security.ts +4 -4
  122. package/src/config/types.ts +0 -1
  123. package/src/contacts/contact-store.ts +39 -0
  124. package/src/contacts/types.ts +2 -0
  125. package/src/context/window-manager.ts +53 -2
  126. package/src/credential-execution/approval-bridge.ts +1 -0
  127. package/src/credential-execution/executable-discovery.ts +28 -4
  128. package/src/credential-execution/feature-gates.ts +16 -0
  129. package/src/credential-execution/process-manager.ts +38 -0
  130. package/src/daemon/assistant-attachments.ts +9 -0
  131. package/src/daemon/config-watcher.ts +6 -4
  132. package/src/daemon/conversation-agent-loop.ts +0 -60
  133. package/src/daemon/conversation-memory.ts +0 -117
  134. package/src/daemon/conversation-runtime-assembly.ts +0 -2
  135. package/src/daemon/conversation-tool-setup.ts +0 -105
  136. package/src/daemon/conversation.ts +10 -1
  137. package/src/daemon/handlers/config-vercel.ts +92 -0
  138. package/src/daemon/handlers/conversations.ts +0 -11
  139. package/src/daemon/handlers/skills.ts +2 -15
  140. package/src/daemon/install-symlink.ts +195 -0
  141. package/src/daemon/lifecycle.ts +229 -96
  142. package/src/daemon/message-types/conversations.ts +3 -4
  143. package/src/daemon/message-types/diagnostics.ts +3 -22
  144. package/src/daemon/message-types/messages.ts +0 -2
  145. package/src/daemon/message-types/upgrades.ts +8 -0
  146. package/src/daemon/server.ts +30 -92
  147. package/src/events/domain-events.ts +2 -1
  148. package/src/followups/followup-store.ts +5 -2
  149. package/src/inbound/platform-callback-registration.ts +3 -3
  150. package/src/instrument.ts +8 -5
  151. package/src/memory/conversation-crud.ts +0 -236
  152. package/src/memory/conversation-title-service.ts +76 -11
  153. package/src/memory/db-init.ts +15 -11
  154. package/src/memory/indexer.ts +15 -106
  155. package/src/memory/items-extractor.ts +15 -1
  156. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  157. package/src/memory/job-handlers/embedding.ts +0 -79
  158. package/src/memory/job-utils.ts +1 -1
  159. package/src/memory/jobs-store.ts +30 -13
  160. package/src/memory/jobs-worker.ts +31 -27
  161. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  162. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  163. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  164. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  165. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  166. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  167. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  168. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  169. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  170. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  171. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  172. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  173. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  174. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  175. package/src/memory/migrations/116-messages-fts.ts +106 -1
  176. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  177. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  178. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  179. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  180. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  181. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  182. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  183. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  184. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  185. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  186. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  187. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  188. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  189. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  190. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  191. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  192. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  193. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  194. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  195. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  196. package/src/memory/migrations/189-drop-simplified-memory.ts +42 -0
  197. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  198. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  199. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  200. package/src/memory/migrations/index.ts +5 -3
  201. package/src/memory/migrations/registry.ts +90 -0
  202. package/src/memory/migrations/validate-migration-state.ts +137 -11
  203. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  204. package/src/memory/qdrant-client.ts +4 -6
  205. package/src/memory/qdrant-manager.ts +64 -7
  206. package/src/memory/schema/calls.ts +1 -0
  207. package/src/memory/schema/contacts.ts +1 -0
  208. package/src/memory/schema/conversations.ts +0 -3
  209. package/src/memory/schema/index.ts +0 -2
  210. package/src/messaging/draft-store.ts +2 -2
  211. package/src/notifications/decision-engine.ts +4 -1
  212. package/src/oauth/connection-resolver.ts +6 -4
  213. package/src/permissions/checker.ts +0 -38
  214. package/src/permissions/defaults.ts +3 -3
  215. package/src/permissions/shell-identity.ts +76 -22
  216. package/src/permissions/trust-client.ts +2 -13
  217. package/src/permissions/trust-store.ts +8 -3
  218. package/src/permissions/types.ts +4 -2
  219. package/src/platform/client.ts +35 -7
  220. package/src/prompts/persona-resolver.ts +138 -0
  221. package/src/prompts/system-prompt.ts +36 -4
  222. package/src/prompts/templates/users/default.md +1 -0
  223. package/src/providers/registry.ts +27 -40
  224. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  225. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  226. package/src/runtime/auth/external-assistant-id.ts +13 -59
  227. package/src/runtime/auth/route-policy.ts +29 -1
  228. package/src/runtime/auth/token-service.ts +53 -15
  229. package/src/runtime/channel-readiness-service.ts +1 -16
  230. package/src/runtime/http-server.ts +29 -2
  231. package/src/runtime/middleware/error-handler.ts +1 -9
  232. package/src/runtime/routes/audio-routes.ts +40 -0
  233. package/src/runtime/routes/btw-routes.ts +0 -17
  234. package/src/runtime/routes/conversation-management-routes.ts +0 -36
  235. package/src/runtime/routes/conversation-query-routes.ts +106 -2
  236. package/src/runtime/routes/conversation-routes.ts +4 -43
  237. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  238. package/src/runtime/routes/identity-routes.ts +18 -29
  239. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  240. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  241. package/src/runtime/routes/integrations/vercel.ts +89 -0
  242. package/src/runtime/routes/log-export-routes.ts +5 -0
  243. package/src/runtime/routes/memory-item-routes.test.ts +221 -3
  244. package/src/runtime/routes/memory-item-routes.ts +144 -4
  245. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  246. package/src/runtime/routes/migration-routes.ts +17 -1
  247. package/src/runtime/routes/notification-routes.ts +58 -0
  248. package/src/runtime/routes/schedule-routes.ts +65 -0
  249. package/src/runtime/routes/settings-routes.ts +41 -1
  250. package/src/runtime/routes/tts-routes.ts +86 -0
  251. package/src/runtime/routes/upgrade-broadcast-routes.ts +175 -0
  252. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  253. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  254. package/src/runtime/routes/workspace-routes.ts +1 -1
  255. package/src/runtime/routes/workspace-utils.ts +86 -2
  256. package/src/schedule/schedule-store.ts +0 -21
  257. package/src/security/ces-credential-client.ts +59 -22
  258. package/src/security/ces-rpc-credential-backend.ts +85 -0
  259. package/src/security/credential-backend.ts +12 -88
  260. package/src/security/keychain-broker-client.ts +10 -2
  261. package/src/security/secure-keys.ts +94 -113
  262. package/src/skills/catalog-install.ts +13 -7
  263. package/src/skills/inline-command-render.ts +5 -1
  264. package/src/skills/inline-command-runner.ts +30 -2
  265. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  266. package/src/tools/calls/call-start.ts +1 -0
  267. package/src/tools/executor.ts +0 -4
  268. package/src/tools/memory/handlers.ts +1 -129
  269. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  270. package/src/tools/network/web-fetch.ts +3 -1
  271. package/src/tools/permission-checker.ts +18 -0
  272. package/src/tools/skills/execute.ts +1 -1
  273. package/src/tools/skills/load.ts +9 -2
  274. package/src/tools/types.ts +0 -8
  275. package/src/util/errors.ts +0 -12
  276. package/src/util/platform.ts +8 -55
  277. package/src/util/xml.ts +8 -0
  278. package/src/workspace/git-service.ts +5 -2
  279. package/src/workspace/heartbeat-service.ts +5 -24
  280. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  281. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  282. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  283. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  284. package/src/workspace/migrations/006-services-config.ts +49 -0
  285. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  286. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  287. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  288. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  289. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  290. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  291. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  292. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  293. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  294. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  295. package/src/workspace/migrations/017-seed-persona-dirs.ts +95 -0
  296. package/src/workspace/migrations/migrate-to-workspace-volume.ts +23 -1
  297. package/src/workspace/migrations/registry.ts +8 -0
  298. package/src/workspace/migrations/runner.ts +106 -2
  299. package/src/workspace/migrations/types.ts +4 -0
  300. package/src/__tests__/archive-recall.test.ts +0 -560
  301. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  302. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  303. package/src/__tests__/conversation-memory-dirty-tail.test.ts +0 -150
  304. package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
  305. package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
  306. package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
  307. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
  308. package/src/__tests__/diagnostics-export.test.ts +0 -288
  309. package/src/__tests__/local-gateway-health.test.ts +0 -209
  310. package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
  311. package/src/__tests__/memory-brief-time.test.ts +0 -285
  312. package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
  313. package/src/__tests__/memory-chunk-archive.test.ts +0 -400
  314. package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
  315. package/src/__tests__/memory-episode-archive.test.ts +0 -370
  316. package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
  317. package/src/__tests__/memory-observation-archive.test.ts +0 -375
  318. package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
  319. package/src/__tests__/memory-reducer-job.test.ts +0 -538
  320. package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
  321. package/src/__tests__/memory-reducer-store.test.ts +0 -728
  322. package/src/__tests__/memory-reducer-types.test.ts +0 -707
  323. package/src/__tests__/memory-reducer.test.ts +0 -704
  324. package/src/__tests__/memory-simplified-config.test.ts +0 -281
  325. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  326. package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
  327. package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
  328. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  329. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  330. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  331. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  332. package/src/__tests__/swarm-recursion.test.ts +0 -197
  333. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  334. package/src/__tests__/swarm-tool.test.ts +0 -185
  335. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  336. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  337. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  338. package/src/commands/cc-command-registry.ts +0 -248
  339. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  340. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  341. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  342. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  343. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  344. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  345. package/src/config/schemas/memory-simplified.ts +0 -101
  346. package/src/config/schemas/swarm.ts +0 -82
  347. package/src/logfire.ts +0 -135
  348. package/src/memory/archive-recall.ts +0 -516
  349. package/src/memory/archive-store.ts +0 -400
  350. package/src/memory/brief-formatting.ts +0 -33
  351. package/src/memory/brief-open-loops.ts +0 -266
  352. package/src/memory/brief-time.ts +0 -162
  353. package/src/memory/brief.ts +0 -75
  354. package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
  355. package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
  356. package/src/memory/migrations/185-memory-brief-state.ts +0 -52
  357. package/src/memory/migrations/186-memory-archive.ts +0 -109
  358. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
  359. package/src/memory/reducer-scheduler.ts +0 -242
  360. package/src/memory/reducer-store.ts +0 -271
  361. package/src/memory/reducer-types.ts +0 -106
  362. package/src/memory/reducer.ts +0 -467
  363. package/src/memory/schema/memory-archive.ts +0 -121
  364. package/src/memory/schema/memory-brief.ts +0 -55
  365. package/src/runtime/local-gateway-health.ts +0 -275
  366. package/src/security/secret-ingress.ts +0 -68
  367. package/src/swarm/backend-claude-code.ts +0 -225
  368. package/src/swarm/checkpoint.ts +0 -137
  369. package/src/swarm/graph-utils.ts +0 -53
  370. package/src/swarm/index.ts +0 -55
  371. package/src/swarm/limits.ts +0 -66
  372. package/src/swarm/orchestrator.ts +0 -424
  373. package/src/swarm/plan-validator.ts +0 -117
  374. package/src/swarm/router-planner.ts +0 -162
  375. package/src/swarm/router-prompts.ts +0 -39
  376. package/src/swarm/synthesizer.ts +0 -81
  377. package/src/swarm/types.ts +0 -72
  378. package/src/swarm/worker-backend.ts +0 -131
  379. package/src/swarm/worker-prompts.ts +0 -80
  380. package/src/swarm/worker-runner.ts +0 -170
  381. package/src/tools/claude-code/claude-code.ts +0 -610
  382. package/src/tools/swarm/delegate.ts +0 -205
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Migration rollback endpoint — rolls back DB and/or workspace migrations
3
+ * to a specified target version/migration ID.
4
+ *
5
+ * Protected by a route policy restricting access to gateway service
6
+ * principals only (`svc_gateway` with `internal.write` scope), following
7
+ * the same pattern as other gateway-forwarded control-plane endpoints.
8
+ */
9
+
10
+ import { getDb } from "../../memory/db-connection.js";
11
+ import { getMaxMigrationVersion } from "../../memory/migrations/registry.js";
12
+ import { rollbackMemoryMigration } from "../../memory/migrations/validate-migration-state.js";
13
+ import { getWorkspaceDir } from "../../util/platform.js";
14
+ import { WORKSPACE_MIGRATIONS } from "../../workspace/migrations/registry.js";
15
+ import {
16
+ getLastWorkspaceMigrationId,
17
+ loadCheckpoints,
18
+ rollbackWorkspaceMigrations,
19
+ } from "../../workspace/migrations/runner.js";
20
+ import { httpError } from "../http-errors.js";
21
+ import type { RouteDefinition } from "../http-router.js";
22
+
23
+ export function migrationRollbackRouteDefinitions(): RouteDefinition[] {
24
+ return [
25
+ {
26
+ endpoint: "admin/rollback-migrations",
27
+ method: "POST",
28
+ handler: async ({ req }) => {
29
+ let body: unknown;
30
+ try {
31
+ body = await req.json();
32
+ } catch {
33
+ return httpError("BAD_REQUEST", "Invalid JSON body", 400);
34
+ }
35
+
36
+ if (!body || typeof body !== "object") {
37
+ return httpError(
38
+ "BAD_REQUEST",
39
+ "Request body must be a JSON object",
40
+ 400,
41
+ );
42
+ }
43
+
44
+ const {
45
+ targetDbVersion,
46
+ targetWorkspaceMigrationId,
47
+ rollbackToRegistryCeiling,
48
+ } = body as {
49
+ targetDbVersion?: unknown;
50
+ targetWorkspaceMigrationId?: unknown;
51
+ rollbackToRegistryCeiling?: unknown;
52
+ };
53
+
54
+ // When rollbackToRegistryCeiling is true, auto-determine targets
55
+ // from this daemon's own migration registry ceilings.
56
+ let effectiveDbVersion = targetDbVersion as number | undefined;
57
+ let effectiveWorkspaceMigrationId = targetWorkspaceMigrationId as
58
+ | string
59
+ | undefined;
60
+
61
+ if (rollbackToRegistryCeiling === true) {
62
+ if (effectiveDbVersion === undefined)
63
+ effectiveDbVersion = getMaxMigrationVersion();
64
+ if (effectiveWorkspaceMigrationId === undefined)
65
+ effectiveWorkspaceMigrationId =
66
+ getLastWorkspaceMigrationId(WORKSPACE_MIGRATIONS) ?? undefined;
67
+ }
68
+
69
+ // At least one rollback target must be specified.
70
+ if (
71
+ effectiveDbVersion === undefined &&
72
+ effectiveWorkspaceMigrationId === undefined
73
+ ) {
74
+ return httpError(
75
+ "BAD_REQUEST",
76
+ "At least one of targetDbVersion or targetWorkspaceMigrationId must be provided",
77
+ 400,
78
+ );
79
+ }
80
+
81
+ // Validate effectiveDbVersion when provided.
82
+ if (effectiveDbVersion !== undefined) {
83
+ if (
84
+ typeof effectiveDbVersion !== "number" ||
85
+ !Number.isInteger(effectiveDbVersion) ||
86
+ effectiveDbVersion < 0
87
+ ) {
88
+ return httpError(
89
+ "BAD_REQUEST",
90
+ "targetDbVersion must be a non-negative integer",
91
+ 400,
92
+ );
93
+ }
94
+ }
95
+
96
+ // Validate effectiveWorkspaceMigrationId when provided.
97
+ if (effectiveWorkspaceMigrationId !== undefined) {
98
+ if (
99
+ typeof effectiveWorkspaceMigrationId !== "string" ||
100
+ effectiveWorkspaceMigrationId.length === 0
101
+ ) {
102
+ return httpError(
103
+ "BAD_REQUEST",
104
+ "targetWorkspaceMigrationId must be a non-empty string",
105
+ 400,
106
+ );
107
+ }
108
+ }
109
+
110
+ // Preflight: validate that the workspace migration ID exists in the
111
+ // registry BEFORE executing any mutations. This prevents the DB
112
+ // rollback from committing when the workspace target is invalid.
113
+ let resolvedTargetIndex = -1;
114
+ if (effectiveWorkspaceMigrationId !== undefined) {
115
+ const targetId = effectiveWorkspaceMigrationId as string;
116
+ resolvedTargetIndex = WORKSPACE_MIGRATIONS.findIndex(
117
+ (m) => m.id === targetId,
118
+ );
119
+ if (resolvedTargetIndex === -1) {
120
+ return httpError(
121
+ "BAD_REQUEST",
122
+ `Target workspace migration "${targetId}" not found in the registry`,
123
+ 400,
124
+ );
125
+ }
126
+ }
127
+
128
+ const rolledBack: { db: string[]; workspace: string[] } = {
129
+ db: [],
130
+ workspace: [],
131
+ };
132
+
133
+ // Roll back DB migrations if requested.
134
+ if (effectiveDbVersion !== undefined) {
135
+ try {
136
+ rolledBack.db = rollbackMemoryMigration(
137
+ getDb(),
138
+ effectiveDbVersion,
139
+ );
140
+ } catch (err) {
141
+ const detail = err instanceof Error ? err.message : "Unknown error";
142
+ return httpError(
143
+ "INTERNAL_ERROR",
144
+ `DB migration rollback failed: ${detail}`,
145
+ 500,
146
+ );
147
+ }
148
+ }
149
+
150
+ // Roll back workspace migrations if requested.
151
+ if (effectiveWorkspaceMigrationId !== undefined) {
152
+ const workspaceDir = getWorkspaceDir();
153
+
154
+ // Compute which migrations are candidates for rollback before
155
+ // executing, since rollbackWorkspaceMigrations returns void.
156
+ const targetId = effectiveWorkspaceMigrationId;
157
+
158
+ const checkpointsBefore = loadCheckpoints(workspaceDir);
159
+ const candidateIds = WORKSPACE_MIGRATIONS.slice(
160
+ resolvedTargetIndex + 1,
161
+ )
162
+ .filter((m) => {
163
+ const entry = checkpointsBefore.applied[m.id];
164
+ return (
165
+ entry &&
166
+ entry.status !== "started" &&
167
+ entry.status !== "rolling_back"
168
+ );
169
+ })
170
+ .map((m) => m.id);
171
+
172
+ try {
173
+ await rollbackWorkspaceMigrations(
174
+ workspaceDir,
175
+ WORKSPACE_MIGRATIONS,
176
+ targetId,
177
+ );
178
+
179
+ rolledBack.workspace = candidateIds;
180
+ } catch (err) {
181
+ // Re-read checkpoints to determine which migrations were actually
182
+ // rolled back before the error occurred. A candidate whose entry
183
+ // is no longer present in the checkpoint file was successfully
184
+ // reverted.
185
+ const checkpointsAfter = loadCheckpoints(workspaceDir);
186
+ const actuallyRolledBack = candidateIds.filter(
187
+ (id) => !checkpointsAfter.applied[id],
188
+ );
189
+
190
+ const detail = err instanceof Error ? err.message : "Unknown error";
191
+ return httpError(
192
+ "INTERNAL_ERROR",
193
+ `Workspace migration rollback failed: ${detail}`,
194
+ 500,
195
+ {
196
+ partialRolledBack: {
197
+ db: rolledBack.db,
198
+ workspace: actuallyRolledBack,
199
+ },
200
+ },
201
+ );
202
+ }
203
+ }
204
+
205
+ return Response.json({ ok: true, rolledBack });
206
+ },
207
+ },
208
+ ];
209
+ }
@@ -15,7 +15,8 @@ import { join } from "node:path";
15
15
  import { Database } from "bun:sqlite";
16
16
 
17
17
  import { invalidateConfigCache } from "../../config/loader.js";
18
- import { resetDb } from "../../memory/db-connection.js";
18
+ import { getDb, resetDb } from "../../memory/db-connection.js";
19
+ import { validateMigrationState } from "../../memory/migrations/validate-migration-state.js";
19
20
  import { clearCache as clearTrustCache } from "../../permissions/trust-store.js";
20
21
  import { getLogger } from "../../util/logger.js";
21
22
  import {
@@ -438,6 +439,21 @@ export async function handleMigrationImport(req: Request): Promise<Response> {
438
439
  invalidateConfigCache();
439
440
  clearTrustCache();
440
441
 
442
+ // Check whether the imported database contains migration checkpoints from
443
+ // a newer version. This is non-blocking — the import has already
444
+ // succeeded — but we surface a warning so the caller knows some data may
445
+ // not be fully compatible with this daemon's schema.
446
+ try {
447
+ const migrationValidation = validateMigrationState(getDb());
448
+ if (migrationValidation.unknownCheckpoints.length > 0) {
449
+ result.report.warnings.push(
450
+ `Imported data contains ${migrationValidation.unknownCheckpoints.length} migration(s) from a newer version. Some data may not be fully compatible.`,
451
+ );
452
+ }
453
+ } catch {
454
+ // Don't fail the import if validation itself errors
455
+ }
456
+
441
457
  return Response.json(result.report);
442
458
  } catch (err) {
443
459
  log.error({ err }, "Unexpected error during import commit");
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Route handlers for notification delivery acknowledgments.
3
+ *
4
+ * Provides a REST endpoint for clients to report the outcome of
5
+ * local notification delivery (UNUserNotificationCenter.add).
6
+ */
7
+
8
+ import { eq } from "drizzle-orm";
9
+
10
+ import { getDb } from "../../memory/db.js";
11
+ import { notificationDeliveries } from "../../memory/schema.js";
12
+ import { httpError } from "../http-errors.js";
13
+ import type { RouteDefinition } from "../http-router.js";
14
+
15
+ export function notificationRouteDefinitions(): RouteDefinition[] {
16
+ return [
17
+ // POST /v1/notification-intent-result — client ack for notification delivery
18
+ {
19
+ endpoint: "notification-intent-result",
20
+ method: "POST",
21
+ policyKey: "notification-intent-result",
22
+ handler: async ({ req }) => {
23
+ const body = (await req.json()) as {
24
+ deliveryId?: string;
25
+ success?: boolean;
26
+ errorMessage?: string;
27
+ errorCode?: string;
28
+ };
29
+
30
+ if (!body.deliveryId || typeof body.deliveryId !== "string") {
31
+ return httpError("BAD_REQUEST", "deliveryId is required", 400);
32
+ }
33
+
34
+ const db = getDb();
35
+ const now = Date.now();
36
+
37
+ const updates: Record<string, unknown> = {
38
+ clientDeliveryStatus: body.success ? "delivered" : "client_failed",
39
+ clientDeliveryAt: now,
40
+ updatedAt: now,
41
+ };
42
+ if (body.errorMessage) {
43
+ updates.clientDeliveryError = body.errorMessage;
44
+ }
45
+ if (body.errorCode) {
46
+ updates.errorCode = body.errorCode;
47
+ }
48
+
49
+ db.update(notificationDeliveries)
50
+ .set(updates)
51
+ .where(eq(notificationDeliveries.id, body.deliveryId))
52
+ .run();
53
+
54
+ return Response.json({ ok: true });
55
+ },
56
+ },
57
+ ];
58
+ }
@@ -99,6 +99,59 @@ function handleCancelSchedule(id: string): Response {
99
99
  return handleListSchedules();
100
100
  }
101
101
 
102
+ const VALID_MODES = ["notify", "execute"] as const;
103
+ const VALID_ROUTING_INTENTS = [
104
+ "single_channel",
105
+ "multi_channel",
106
+ "all_channels",
107
+ ] as const;
108
+
109
+ function handleUpdateSchedule(
110
+ id: string,
111
+ body: Record<string, unknown>,
112
+ ): Response {
113
+ const updates: Record<string, unknown> = {};
114
+
115
+ if ("mode" in body && !VALID_MODES.includes(body.mode as (typeof VALID_MODES)[number])) {
116
+ return httpError("BAD_REQUEST", `Invalid mode: must be one of ${VALID_MODES.join(", ")}`, 400);
117
+ }
118
+ if ("routingIntent" in body && !VALID_ROUTING_INTENTS.includes(body.routingIntent as (typeof VALID_ROUTING_INTENTS)[number])) {
119
+ return httpError("BAD_REQUEST", `Invalid routingIntent: must be one of ${VALID_ROUTING_INTENTS.join(", ")}`, 400);
120
+ }
121
+
122
+ for (const key of [
123
+ "name",
124
+ "expression",
125
+ "timezone",
126
+ "message",
127
+ "mode",
128
+ "routingIntent",
129
+ "quiet",
130
+ ] as const) {
131
+ if (key in body) {
132
+ updates[key] = body[key];
133
+ }
134
+ }
135
+
136
+ try {
137
+ const updated = updateSchedule(id, updates);
138
+ if (!updated) {
139
+ return httpError("NOT_FOUND", "Schedule not found", 404);
140
+ }
141
+ log.info({ id, updates }, "Schedule updated via HTTP");
142
+ } catch (err) {
143
+ if (
144
+ err instanceof Error &&
145
+ (err.message.includes("Invalid") || err.message.includes("invalid"))
146
+ ) {
147
+ return httpError("BAD_REQUEST", err.message, 400);
148
+ }
149
+ log.error({ err }, "Failed to update schedule");
150
+ return httpError("INTERNAL_ERROR", "Failed to update schedule", 500);
151
+ }
152
+ return handleListSchedules();
153
+ }
154
+
102
155
  async function handleRunScheduleNow(
103
156
  id: string,
104
157
  sendMessageDeps?: SendMessageDeps,
@@ -243,6 +296,18 @@ export function scheduleRouteDefinitions(deps: {
243
296
  policyKey: "schedules",
244
297
  handler: ({ params }) => handleDeleteSchedule(params.id),
245
298
  },
299
+ {
300
+ endpoint: "schedules/:id",
301
+ method: "PATCH",
302
+ policyKey: "schedules",
303
+ handler: async ({ req, params }) => {
304
+ const body: unknown = await req.json();
305
+ if (typeof body !== "object" || !body || Array.isArray(body)) {
306
+ return httpError("BAD_REQUEST", "Request body must be a JSON object", 400);
307
+ }
308
+ return handleUpdateSchedule(params.id, body as Record<string, unknown>);
309
+ },
310
+ },
246
311
  {
247
312
  endpoint: "schedules/:id/run",
248
313
  method: "POST",
@@ -10,7 +10,10 @@
10
10
  import { readFileSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
 
13
- import { setIngressPublicBaseUrl } from "../../config/env.js";
13
+ import {
14
+ getPlatformBaseUrl,
15
+ setIngressPublicBaseUrl,
16
+ } from "../../config/env.js";
14
17
  import { loadRawConfig, saveRawConfig } from "../../config/loader.js";
15
18
  import { loadSkillCatalog } from "../../config/skills.js";
16
19
  import {
@@ -728,6 +731,43 @@ export function settingsRouteDefinitions(): RouteDefinition[] {
728
731
  handler: () => handleEnvVars(),
729
732
  },
730
733
 
734
+ // Platform config (GET / PUT)
735
+ {
736
+ endpoint: "config/platform",
737
+ method: "GET",
738
+ policyKey: "config/platform:GET",
739
+ handler: () => {
740
+ const raw = loadRawConfig();
741
+ const platform = (raw?.platform ?? {}) as Record<string, unknown>;
742
+ const baseUrl =
743
+ (platform.baseUrl as string | undefined) || getPlatformBaseUrl();
744
+ return Response.json({ baseUrl, success: true });
745
+ },
746
+ },
747
+ {
748
+ endpoint: "config/platform",
749
+ method: "PUT",
750
+ policyKey: "config/platform",
751
+ handler: async ({ req }) => {
752
+ try {
753
+ const body = (await req.json()) as { baseUrl?: string };
754
+ const value = (body.baseUrl ?? "").trim().replace(/\/+$/, "");
755
+ const raw = loadRawConfig();
756
+ const platform = (raw?.platform ?? {}) as Record<string, unknown>;
757
+ platform.baseUrl = value || undefined;
758
+ saveRawConfig({ ...raw, platform });
759
+ return Response.json({ baseUrl: value, success: true });
760
+ } catch (err) {
761
+ const message = err instanceof Error ? err.message : String(err);
762
+ log.error({ err }, "Failed to update platform config via HTTP");
763
+ return Response.json(
764
+ { baseUrl: "", success: false, error: message },
765
+ { status: 500 },
766
+ );
767
+ }
768
+ },
769
+ },
770
+
731
771
  // Ingress config (GET / PUT)
732
772
  {
733
773
  endpoint: "integrations/ingress/config",
@@ -0,0 +1,86 @@
1
+ /**
2
+ * HTTP route definitions for message text-to-speech synthesis.
3
+ *
4
+ * POST /v1/messages/:id/tts?conversationId=... — synthesize message text to audio
5
+ *
6
+ * Gated behind the `feature_flags.message-tts.enabled` assistant feature flag.
7
+ * Uses Fish Audio for synthesis when configured.
8
+ */
9
+
10
+ import { synthesizeWithFishAudio } from "../../calls/fish-audio-client.js";
11
+ import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
12
+ import { getConfig } from "../../config/loader.js";
13
+ import { getMessageContent } from "../../daemon/handlers/conversation-history.js";
14
+ import { getLogger } from "../../util/logger.js";
15
+ import { httpError } from "../http-errors.js";
16
+ import type { RouteDefinition } from "../http-router.js";
17
+
18
+ const log = getLogger("tts-routes");
19
+
20
+ const MESSAGE_TTS_FLAG = "feature_flags.message-tts.enabled" as const;
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Route definitions
24
+ // ---------------------------------------------------------------------------
25
+
26
+ export function ttsRouteDefinitions(): RouteDefinition[] {
27
+ return [
28
+ {
29
+ endpoint: "messages/:id/tts",
30
+ method: "POST",
31
+ policyKey: "messages/tts",
32
+ handler: async ({ url, params }) => {
33
+ const config = getConfig();
34
+
35
+ if (!isAssistantFeatureFlagEnabled(MESSAGE_TTS_FLAG, config)) {
36
+ return httpError("FORBIDDEN", "Message TTS is not enabled", 403);
37
+ }
38
+
39
+ const messageId = params.id;
40
+ const conversationId =
41
+ url.searchParams.get("conversationId") ?? undefined;
42
+
43
+ const result = getMessageContent(messageId, conversationId);
44
+ if (!result) {
45
+ return httpError("NOT_FOUND", `Message ${messageId} not found`, 404);
46
+ }
47
+
48
+ if (!result.text) {
49
+ return httpError("BAD_REQUEST", "Message has no text content", 400);
50
+ }
51
+
52
+ const { fishAudio } = config;
53
+ if (!fishAudio?.referenceId) {
54
+ return httpError(
55
+ "SERVICE_UNAVAILABLE",
56
+ "Fish Audio TTS is not configured",
57
+ 503,
58
+ );
59
+ }
60
+
61
+ try {
62
+ const audioBuffer = await synthesizeWithFishAudio(
63
+ result.text,
64
+ fishAudio,
65
+ );
66
+
67
+ const format = fishAudio.format ?? "mp3";
68
+ const contentType =
69
+ format === "wav"
70
+ ? "audio/wav"
71
+ : format === "opus"
72
+ ? "audio/opus"
73
+ : "audio/mpeg";
74
+
75
+ return new Response(new Uint8Array(audioBuffer), {
76
+ status: 200,
77
+ headers: { "Content-Type": contentType },
78
+ });
79
+ } catch (err) {
80
+ log.error({ err, messageId }, "TTS synthesis failed");
81
+ return httpError("INTERNAL_ERROR", "TTS synthesis failed", 502);
82
+ }
83
+ },
84
+ },
85
+ ];
86
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Upgrade broadcast endpoint — publishes service group update lifecycle
3
+ * events (starting / progress / complete) to all connected SSE clients.
4
+ *
5
+ * Protected by a route policy restricting access to gateway service
6
+ * principals only (`svc_gateway` with `internal.write` scope), following
7
+ * the same pattern as other gateway-forwarded control-plane endpoints.
8
+ * The gateway requires a valid edge JWT and forwards the request with a
9
+ * minted service token.
10
+ */
11
+
12
+ import type {
13
+ ServiceGroupUpdateComplete,
14
+ ServiceGroupUpdateProgress,
15
+ ServiceGroupUpdateStarting,
16
+ } from "../../daemon/message-types/upgrades.js";
17
+ import { buildAssistantEvent } from "../assistant-event.js";
18
+ import { assistantEventHub } from "../assistant-event-hub.js";
19
+ import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
20
+ import { httpError } from "../http-errors.js";
21
+ import type { RouteDefinition } from "../http-router.js";
22
+
23
+ export function upgradeBroadcastRouteDefinitions(): RouteDefinition[] {
24
+ return [
25
+ {
26
+ endpoint: "admin/upgrade-broadcast",
27
+ method: "POST",
28
+ handler: async ({ req }) => {
29
+ let body: unknown;
30
+ try {
31
+ body = await req.json();
32
+ } catch {
33
+ return httpError("BAD_REQUEST", "Invalid JSON body", 400);
34
+ }
35
+
36
+ if (!body || typeof body !== "object") {
37
+ return httpError(
38
+ "BAD_REQUEST",
39
+ "Request body must be a JSON object",
40
+ 400,
41
+ );
42
+ }
43
+
44
+ const { type } = body as { type?: unknown };
45
+
46
+ if (type === "starting") {
47
+ const { targetVersion, expectedDowntimeSeconds } = body as {
48
+ targetVersion?: unknown;
49
+ expectedDowntimeSeconds?: unknown;
50
+ };
51
+
52
+ if (typeof targetVersion !== "string" || targetVersion.length === 0) {
53
+ return httpError(
54
+ "BAD_REQUEST",
55
+ "targetVersion is required and must be a non-empty string",
56
+ 400,
57
+ );
58
+ }
59
+
60
+ const downtime =
61
+ expectedDowntimeSeconds === undefined
62
+ ? 60
63
+ : expectedDowntimeSeconds;
64
+
65
+ if (
66
+ typeof downtime !== "number" ||
67
+ !isFinite(downtime) ||
68
+ downtime < 0
69
+ ) {
70
+ return httpError(
71
+ "BAD_REQUEST",
72
+ "expectedDowntimeSeconds must be a non-negative number",
73
+ 400,
74
+ );
75
+ }
76
+
77
+ const message: ServiceGroupUpdateStarting = {
78
+ type: "service_group_update_starting",
79
+ targetVersion,
80
+ expectedDowntimeSeconds: downtime,
81
+ };
82
+
83
+ await assistantEventHub.publish(
84
+ buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, message),
85
+ );
86
+
87
+ return Response.json({ ok: true });
88
+ }
89
+
90
+ if (type === "progress") {
91
+ const { statusMessage } = body as { statusMessage?: unknown };
92
+
93
+ if (typeof statusMessage !== "string" || statusMessage.length === 0) {
94
+ return httpError(
95
+ "BAD_REQUEST",
96
+ "statusMessage is required and must be a non-empty string",
97
+ 400,
98
+ );
99
+ }
100
+
101
+ const message: ServiceGroupUpdateProgress = {
102
+ type: "service_group_update_progress",
103
+ statusMessage,
104
+ };
105
+
106
+ await assistantEventHub.publish(
107
+ buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, message),
108
+ );
109
+
110
+ return Response.json({ ok: true });
111
+ }
112
+
113
+ if (type === "complete") {
114
+ const { installedVersion, success, rolledBackToVersion } = body as {
115
+ installedVersion?: unknown;
116
+ success?: unknown;
117
+ rolledBackToVersion?: unknown;
118
+ };
119
+
120
+ if (
121
+ typeof installedVersion !== "string" ||
122
+ installedVersion.length === 0
123
+ ) {
124
+ return httpError(
125
+ "BAD_REQUEST",
126
+ "installedVersion is required and must be a non-empty string",
127
+ 400,
128
+ );
129
+ }
130
+
131
+ if (typeof success !== "boolean") {
132
+ return httpError(
133
+ "BAD_REQUEST",
134
+ "success is required and must be a boolean",
135
+ 400,
136
+ );
137
+ }
138
+
139
+ if (
140
+ rolledBackToVersion !== undefined &&
141
+ (typeof rolledBackToVersion !== "string" ||
142
+ rolledBackToVersion.length === 0)
143
+ ) {
144
+ return httpError(
145
+ "BAD_REQUEST",
146
+ "rolledBackToVersion must be a non-empty string when provided",
147
+ 400,
148
+ );
149
+ }
150
+
151
+ const message: ServiceGroupUpdateComplete = {
152
+ type: "service_group_update_complete",
153
+ installedVersion,
154
+ success,
155
+ ...(typeof rolledBackToVersion === "string"
156
+ ? { rolledBackToVersion }
157
+ : {}),
158
+ };
159
+
160
+ await assistantEventHub.publish(
161
+ buildAssistantEvent(DAEMON_INTERNAL_ASSISTANT_ID, message),
162
+ );
163
+
164
+ return Response.json({ ok: true });
165
+ }
166
+
167
+ return httpError(
168
+ "BAD_REQUEST",
169
+ 'type must be "starting", "progress", or "complete"',
170
+ 400,
171
+ );
172
+ },
173
+ },
174
+ ];
175
+ }