@vellumai/assistant 0.5.6 → 0.5.8

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 (442) hide show
  1. package/.env.example +16 -2
  2. package/ARCHITECTURE.md +6 -75
  3. package/Dockerfile +3 -2
  4. package/README.md +0 -2
  5. package/bun.lock +0 -414
  6. package/docker-entrypoint.sh +9 -0
  7. package/docs/architecture/keychain-broker.md +45 -240
  8. package/docs/architecture/memory.md +13 -11
  9. package/docs/architecture/security.md +0 -17
  10. package/docs/credential-execution-service.md +2 -2
  11. package/node_modules/@vellumai/ces-contracts/package.json +1 -0
  12. package/node_modules/@vellumai/ces-contracts/src/error.ts +1 -1
  13. package/node_modules/@vellumai/ces-contracts/src/grants.ts +1 -1
  14. package/node_modules/@vellumai/ces-contracts/src/handles.ts +1 -1
  15. package/node_modules/@vellumai/ces-contracts/src/index.ts +1 -1
  16. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +120 -1
  17. package/node_modules/@vellumai/credential-storage/package.json +1 -0
  18. package/node_modules/@vellumai/egress-proxy/package.json +1 -0
  19. package/package.json +2 -3
  20. package/src/__tests__/actor-token-service.test.ts +0 -114
  21. package/src/__tests__/approval-cascade.test.ts +0 -1
  22. package/src/__tests__/assistant-feature-flags-integration.test.ts +30 -29
  23. package/src/__tests__/browser-fill-credential.test.ts +1 -1
  24. package/src/__tests__/browser-skill-endstate.test.ts +6 -5
  25. package/src/__tests__/btw-routes.test.ts +0 -39
  26. package/src/__tests__/call-controller.test.ts +0 -1
  27. package/src/__tests__/call-domain.test.ts +0 -128
  28. package/src/__tests__/ces-rpc-credential-backend.test.ts +199 -0
  29. package/src/__tests__/ces-startup-timeout.test.ts +40 -0
  30. package/src/__tests__/channel-approval-routes.test.ts +0 -5
  31. package/src/__tests__/channel-readiness-service.test.ts +1 -60
  32. package/src/__tests__/checker.test.ts +4 -2
  33. package/src/__tests__/cli-command-risk-guard.test.ts +112 -0
  34. package/src/__tests__/config-schema-cmd.test.ts +0 -2
  35. package/src/__tests__/config-schema.test.ts +3 -1
  36. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -1
  37. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
  38. package/src/__tests__/conversation-agent-loop.test.ts +2 -4
  39. package/src/__tests__/conversation-attention-telegram.test.ts +0 -5
  40. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -1
  41. package/src/__tests__/conversation-error.test.ts +15 -1
  42. package/src/__tests__/conversation-init.benchmark.test.ts +0 -2
  43. package/src/__tests__/conversation-messaging-secret-redirect.test.ts +1 -1
  44. package/src/__tests__/conversation-pre-run-repair.test.ts +0 -1
  45. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -1
  46. package/src/__tests__/conversation-queue.test.ts +0 -1
  47. package/src/__tests__/conversation-skill-tools.test.ts +0 -54
  48. package/src/__tests__/conversation-slash-queue.test.ts +0 -1
  49. package/src/__tests__/conversation-slash-unknown.test.ts +0 -1
  50. package/src/__tests__/conversation-title-service.test.ts +87 -0
  51. package/src/__tests__/conversation-workspace-injection.test.ts +0 -1
  52. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -1
  53. package/src/__tests__/credential-execution-client.test.ts +5 -2
  54. package/src/__tests__/credential-execution-feature-gates.test.ts +59 -30
  55. package/src/__tests__/credential-execution-managed-contract.test.ts +35 -20
  56. package/src/__tests__/credential-security-e2e.test.ts +1 -67
  57. package/src/__tests__/credential-security-invariants.test.ts +6 -50
  58. package/src/__tests__/credentials-cli.test.ts +82 -3
  59. package/src/__tests__/daemon-credential-client.test.ts +123 -0
  60. package/src/__tests__/db-migration-rollback.test.ts +2015 -1
  61. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -0
  62. package/src/__tests__/docker-signing-key-bootstrap.test.ts +34 -143
  63. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +6 -4
  64. package/src/__tests__/gateway-client-managed-outbound.test.ts +79 -1
  65. package/src/__tests__/guardian-routing-state.test.ts +0 -5
  66. package/src/__tests__/host-shell-tool.test.ts +6 -7
  67. package/src/__tests__/http-user-message-parity.test.ts +3 -103
  68. package/src/__tests__/inbound-invite-redemption.test.ts +0 -4
  69. package/src/__tests__/inline-skill-load-permissions.test.ts +6 -8
  70. package/src/__tests__/intent-routing.test.ts +0 -13
  71. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +178 -0
  72. package/src/__tests__/journal-context.test.ts +335 -0
  73. package/src/__tests__/keychain-broker-client.test.ts +161 -22
  74. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -3
  75. package/src/__tests__/memory-jobs-worker-backoff.test.ts +150 -0
  76. package/src/__tests__/memory-lifecycle-e2e.test.ts +70 -25
  77. package/src/__tests__/memory-recall-quality.test.ts +48 -17
  78. package/src/__tests__/memory-regressions.test.ts +408 -363
  79. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -3
  80. package/src/__tests__/migration-export-http.test.ts +2 -2
  81. package/src/__tests__/migration-import-commit-http.test.ts +2 -2
  82. package/src/__tests__/migration-import-preflight-http.test.ts +2 -2
  83. package/src/__tests__/migration-validate-http.test.ts +2 -2
  84. package/src/__tests__/non-member-access-request.test.ts +2 -7
  85. package/src/__tests__/notification-decision-fallback.test.ts +4 -0
  86. package/src/__tests__/notification-decision-identity.test.ts +4 -0
  87. package/src/__tests__/notification-decision-strategy.test.ts +71 -0
  88. package/src/__tests__/oauth-cli.test.ts +5 -1
  89. package/src/__tests__/permission-types.test.ts +1 -0
  90. package/src/__tests__/provider-commit-message-generator.test.ts +0 -37
  91. package/src/__tests__/provider-error-scenarios.test.ts +0 -267
  92. package/src/__tests__/provider-managed-proxy-integration.test.ts +5 -6
  93. package/src/__tests__/provider-streaming.benchmark.test.ts +2 -81
  94. package/src/__tests__/qdrant-manager.test.ts +28 -2
  95. package/src/__tests__/registry.test.ts +0 -6
  96. package/src/__tests__/relay-server.test.ts +1 -2
  97. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -4
  98. package/src/__tests__/script-proxy-injection-runtime.test.ts +1 -1
  99. package/src/__tests__/secret-onetime-send.test.ts +1 -1
  100. package/src/__tests__/secret-routes-managed-proxy.test.ts +0 -4
  101. package/src/__tests__/secure-keys.test.ts +95 -272
  102. package/src/__tests__/shell-identity.test.ts +96 -6
  103. package/src/__tests__/skill-feature-flags-integration.test.ts +22 -14
  104. package/src/__tests__/skill-feature-flags.test.ts +46 -45
  105. package/src/__tests__/skill-load-feature-flag.test.ts +7 -10
  106. package/src/__tests__/skill-load-inline-command.test.ts +8 -12
  107. package/src/__tests__/skill-load-inline-includes.test.ts +6 -10
  108. package/src/__tests__/skill-load-tool.test.ts +0 -2
  109. package/src/__tests__/skill-memory.test.ts +17 -3
  110. package/src/__tests__/skill-projection-feature-flag.test.ts +33 -29
  111. package/src/__tests__/skills.test.ts +0 -2
  112. package/src/__tests__/slack-inbound-verification.test.ts +0 -4
  113. package/src/__tests__/stale-approval-dedup.test.ts +171 -0
  114. package/src/__tests__/stt-hints.test.ts +437 -0
  115. package/src/__tests__/suggestion-routes.test.ts +1 -32
  116. package/src/__tests__/system-prompt.test.ts +0 -1
  117. package/src/__tests__/task-memory-cleanup.test.ts +14 -0
  118. package/src/__tests__/tool-executor-shell-integration.test.ts +5 -3
  119. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +0 -5
  120. package/src/__tests__/trusted-contact-multichannel.test.ts +0 -4
  121. package/src/__tests__/twilio-routes-twiml.test.ts +139 -1
  122. package/src/__tests__/update-bulletin.test.ts +0 -2
  123. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +6 -9
  124. package/src/__tests__/voice-quality.test.ts +58 -0
  125. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -7
  126. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +252 -0
  127. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +220 -0
  128. package/src/__tests__/workspace-migration-down-functions.test.ts +1009 -0
  129. package/src/__tests__/workspace-migrations-runner.test.ts +114 -0
  130. package/src/acp/agent-process.ts +9 -1
  131. package/src/agent/loop.ts +1 -1
  132. package/src/approvals/guardian-request-resolvers.ts +164 -38
  133. package/src/calls/__tests__/tts-text-sanitizer.test.ts +254 -0
  134. package/src/calls/audio-store.test.ts +97 -0
  135. package/src/calls/audio-store.ts +205 -0
  136. package/src/calls/call-controller.ts +90 -8
  137. package/src/calls/call-domain.ts +3 -0
  138. package/src/calls/call-store.ts +10 -3
  139. package/src/calls/fish-audio-client.ts +129 -0
  140. package/src/calls/relay-server.ts +27 -0
  141. package/src/calls/stt-hints.ts +189 -0
  142. package/src/calls/tts-text-sanitizer.ts +61 -0
  143. package/src/calls/twilio-routes.ts +34 -5
  144. package/src/calls/types.ts +1 -0
  145. package/src/calls/voice-ingress-preflight.ts +0 -42
  146. package/src/calls/voice-quality.ts +38 -5
  147. package/src/calls/voice-session-bridge.ts +7 -12
  148. package/src/cli/commands/avatar.ts +2 -2
  149. package/src/cli/commands/config.ts +1 -4
  150. package/src/cli/commands/credentials.ts +128 -82
  151. package/src/cli/commands/doctor.ts +2 -2
  152. package/src/cli/commands/keys.ts +7 -7
  153. package/src/cli/commands/memory.ts +1 -1
  154. package/src/cli/commands/oauth/connections.ts +11 -29
  155. package/src/cli/commands/oauth/index.ts +7 -0
  156. package/src/cli/commands/oauth/platform.ts +525 -0
  157. package/src/cli/commands/platform.ts +3 -3
  158. package/src/cli/lib/daemon-credential-client.ts +284 -0
  159. package/src/cli.ts +1 -1
  160. package/src/config/assistant-feature-flags.ts +186 -5
  161. package/src/config/bundled-skills/AGENTS.md +34 -0
  162. package/src/config/bundled-skills/acp/SKILL.md +10 -0
  163. package/src/config/bundled-skills/app-builder/SKILL.md +0 -4
  164. package/src/config/bundled-skills/messaging/SKILL.md +5 -5
  165. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  166. package/src/config/bundled-skills/phone-calls/TOOLS.json +4 -0
  167. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +1 -0
  168. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +1 -0
  169. package/src/config/bundled-skills/settings/SKILL.md +15 -2
  170. package/src/config/bundled-skills/settings/TOOLS.json +47 -2
  171. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +59 -0
  172. package/src/config/bundled-skills/settings/tools/avatar-update.ts +80 -0
  173. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +42 -0
  174. package/src/config/bundled-skills/slack/SKILL.md +1 -1
  175. package/src/config/bundled-tool-registry.ts +5 -11
  176. package/src/config/defaults.ts +0 -2
  177. package/src/config/env-registry.ts +5 -5
  178. package/src/config/env.ts +21 -14
  179. package/src/config/feature-flag-registry.json +49 -9
  180. package/src/config/loader.ts +106 -42
  181. package/src/config/schema.ts +9 -29
  182. package/src/config/schemas/calls.ts +30 -0
  183. package/src/config/schemas/fish-audio.ts +39 -0
  184. package/src/config/schemas/inference.ts +2 -2
  185. package/src/config/schemas/journal.ts +16 -0
  186. package/src/config/schemas/memory-processing.ts +2 -2
  187. package/src/config/schemas/security.ts +0 -4
  188. package/src/config/types.ts +1 -1
  189. package/src/contacts/contact-store.ts +39 -0
  190. package/src/contacts/types.ts +2 -0
  191. package/src/credential-execution/approval-bridge.ts +1 -0
  192. package/src/credential-execution/executable-discovery.ts +28 -4
  193. package/src/credential-execution/feature-gates.ts +16 -0
  194. package/src/credential-execution/process-manager.ts +38 -0
  195. package/src/credential-execution/startup-timeout.ts +36 -0
  196. package/src/daemon/approval-generators.ts +3 -9
  197. package/src/daemon/assistant-attachments.ts +9 -0
  198. package/src/daemon/config-watcher.ts +5 -0
  199. package/src/daemon/conversation-error.ts +13 -1
  200. package/src/daemon/conversation-memory.ts +1 -2
  201. package/src/daemon/conversation-process.ts +18 -1
  202. package/src/daemon/conversation-surfaces.ts +30 -1
  203. package/src/daemon/conversation-tool-setup.ts +0 -105
  204. package/src/daemon/conversation.ts +21 -1
  205. package/src/daemon/guardian-action-generators.ts +3 -9
  206. package/src/daemon/handlers/config-vercel.ts +92 -0
  207. package/src/daemon/handlers/skills.ts +2 -15
  208. package/src/daemon/install-symlink.ts +195 -0
  209. package/src/daemon/lifecycle.ts +234 -51
  210. package/src/daemon/message-types/conversations.ts +4 -4
  211. package/src/daemon/message-types/diagnostics.ts +3 -22
  212. package/src/daemon/message-types/messages.ts +0 -2
  213. package/src/daemon/message-types/upgrades.ts +8 -0
  214. package/src/daemon/server.ts +32 -95
  215. package/src/events/domain-events.ts +2 -1
  216. package/src/inbound/platform-callback-registration.ts +3 -3
  217. package/src/instrument.ts +8 -5
  218. package/src/memory/app-store.ts +31 -0
  219. package/src/memory/conversation-title-service.ts +50 -1
  220. package/src/memory/db-init.ts +16 -0
  221. package/src/memory/indexer.ts +19 -10
  222. package/src/memory/items-extractor.ts +328 -321
  223. package/src/memory/job-handlers/conversation-starters.ts +4 -1
  224. package/src/memory/job-handlers/summarization.ts +26 -16
  225. package/src/memory/jobs-store.ts +63 -6
  226. package/src/memory/jobs-worker.ts +31 -7
  227. package/src/memory/journal-memory.ts +214 -0
  228. package/src/memory/migrations/001-job-deferrals.ts +19 -0
  229. package/src/memory/migrations/004-entity-relation-dedup.ts +10 -0
  230. package/src/memory/migrations/005-fingerprint-scope-unique.ts +76 -0
  231. package/src/memory/migrations/006-scope-salted-fingerprints.ts +50 -0
  232. package/src/memory/migrations/007-assistant-id-to-self.ts +10 -0
  233. package/src/memory/migrations/008-remove-assistant-id-columns.ts +34 -0
  234. package/src/memory/migrations/009-llm-usage-events-drop-assistant-id.ts +26 -0
  235. package/src/memory/migrations/014-backfill-inbox-thread-state.ts +10 -0
  236. package/src/memory/migrations/015-drop-active-search-index.ts +17 -0
  237. package/src/memory/migrations/019-notification-tables-schema-migration.ts +12 -0
  238. package/src/memory/migrations/020-rename-macos-ios-channel-to-vellum.ts +121 -0
  239. package/src/memory/migrations/024-embedding-vector-blob.ts +74 -0
  240. package/src/memory/migrations/026a-embeddings-nullable-vector-json.ts +82 -0
  241. package/src/memory/migrations/036-normalize-phone-identities.ts +11 -0
  242. package/src/memory/migrations/116-messages-fts.ts +106 -1
  243. package/src/memory/migrations/126-backfill-guardian-principal-id.ts +52 -0
  244. package/src/memory/migrations/127-guardian-principal-id-not-null.ts +77 -0
  245. package/src/memory/migrations/134-contacts-notes-column.ts +13 -0
  246. package/src/memory/migrations/135-backfill-contact-interaction-stats.ts +20 -0
  247. package/src/memory/migrations/136-drop-assistant-id-columns.ts +52 -0
  248. package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +13 -0
  249. package/src/memory/migrations/141-rename-verification-table.ts +54 -0
  250. package/src/memory/migrations/142-rename-verification-session-id-column.ts +25 -0
  251. package/src/memory/migrations/143-rename-guardian-verification-values.ts +35 -0
  252. package/src/memory/migrations/144-rename-voice-to-phone.ts +136 -0
  253. package/src/memory/migrations/145-drop-accounts-table.ts +32 -0
  254. package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +14 -1
  255. package/src/memory/migrations/148-drop-reminders-table.ts +35 -1
  256. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +69 -1
  257. package/src/memory/migrations/162-guardian-timestamps-epoch-ms.ts +290 -0
  258. package/src/memory/migrations/169-rename-gmail-provider-key-to-google.ts +51 -1
  259. package/src/memory/migrations/174-rename-thread-starters-table.ts +47 -1
  260. package/src/memory/migrations/176-drop-capability-card-state.ts +13 -0
  261. package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +16 -0
  262. package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +28 -1
  263. package/src/memory/migrations/190-call-session-skip-disclosure.ts +15 -0
  264. package/src/memory/migrations/191-backfill-audio-attachment-mime-types.ts +64 -0
  265. package/src/memory/migrations/192-contacts-user-file-column.ts +15 -0
  266. package/src/memory/migrations/193-add-source-type-columns.ts +81 -0
  267. package/src/memory/migrations/index.ts +5 -0
  268. package/src/memory/migrations/registry.ts +98 -0
  269. package/src/memory/migrations/validate-migration-state.ts +137 -11
  270. package/src/memory/qdrant-circuit-breaker.ts +9 -0
  271. package/src/memory/qdrant-manager.ts +64 -7
  272. package/src/memory/retriever.test.ts +37 -25
  273. package/src/memory/retriever.ts +24 -49
  274. package/src/memory/schema/calls.ts +1 -0
  275. package/src/memory/schema/contacts.ts +1 -0
  276. package/src/memory/schema/memory-core.ts +2 -0
  277. package/src/memory/search/formatting.ts +7 -44
  278. package/src/memory/search/staleness.ts +4 -0
  279. package/src/memory/search/tier-classifier.ts +10 -2
  280. package/src/memory/search/types.ts +2 -5
  281. package/src/memory/task-memory-cleanup.ts +4 -3
  282. package/src/notifications/adapters/slack.ts +168 -6
  283. package/src/notifications/broadcaster.ts +1 -0
  284. package/src/notifications/copy-composer.ts +59 -2
  285. package/src/notifications/decision-engine.ts +4 -1
  286. package/src/notifications/signal.ts +2 -0
  287. package/src/notifications/types.ts +2 -0
  288. package/src/oauth/connection-resolver.ts +6 -4
  289. package/src/permissions/checker.ts +0 -38
  290. package/src/permissions/shell-identity.ts +76 -22
  291. package/src/permissions/types.ts +4 -2
  292. package/src/platform/client.ts +35 -7
  293. package/src/prompts/journal-context.ts +133 -0
  294. package/src/prompts/persona-resolver.ts +194 -0
  295. package/src/prompts/system-prompt.ts +44 -4
  296. package/src/prompts/templates/SOUL.md +10 -0
  297. package/src/prompts/templates/users/default.md +1 -0
  298. package/src/providers/provider-send-message.ts +3 -32
  299. package/src/providers/registry.ts +29 -179
  300. package/src/providers/types.ts +1 -1
  301. package/src/runtime/access-request-helper.ts +4 -0
  302. package/src/runtime/auth/__tests__/credential-service.test.ts +0 -1
  303. package/src/runtime/auth/__tests__/external-assistant-id.test.ts +13 -68
  304. package/src/runtime/auth/__tests__/guard-tests.test.ts +9 -50
  305. package/src/runtime/auth/external-assistant-id.ts +13 -59
  306. package/src/runtime/auth/route-policy.ts +17 -1
  307. package/src/runtime/auth/token-service.ts +43 -138
  308. package/src/runtime/channel-readiness-service.ts +1 -16
  309. package/src/runtime/gateway-client.ts +47 -4
  310. package/src/runtime/guardian-decision-types.ts +45 -4
  311. package/src/runtime/http-server.ts +31 -3
  312. package/src/runtime/middleware/error-handler.ts +1 -9
  313. package/src/runtime/routes/access-request-decision.ts +2 -2
  314. package/src/runtime/routes/app-management-routes.ts +2 -1
  315. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +219 -30
  316. package/src/runtime/routes/approval-strategies/guardian-text-engine-strategy.ts +37 -14
  317. package/src/runtime/routes/audio-routes.ts +40 -0
  318. package/src/runtime/routes/btw-routes.ts +0 -17
  319. package/src/runtime/routes/channel-readiness-routes.ts +9 -4
  320. package/src/runtime/routes/conversation-query-routes.ts +63 -1
  321. package/src/runtime/routes/conversation-routes.ts +4 -44
  322. package/src/runtime/routes/debug-routes.ts +12 -9
  323. package/src/runtime/routes/diagnostics-routes.ts +1 -477
  324. package/src/runtime/routes/guardian-approval-interception.ts +168 -11
  325. package/src/runtime/routes/guardian-approval-prompt.ts +6 -1
  326. package/src/runtime/routes/guardian-approval-reply-helpers.ts +103 -21
  327. package/src/runtime/routes/identity-routes.ts +19 -30
  328. package/src/runtime/routes/inbound-message-handler.ts +31 -1
  329. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +64 -5
  330. package/src/runtime/routes/inbound-stages/background-dispatch.ts +52 -40
  331. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +4 -33
  332. package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
  333. package/src/runtime/routes/integrations/twilio.ts +52 -10
  334. package/src/runtime/routes/integrations/vercel.ts +89 -0
  335. package/src/runtime/routes/log-export-routes.ts +5 -0
  336. package/src/runtime/routes/memory-item-routes.test.ts +3 -3
  337. package/src/runtime/routes/memory-item-routes.ts +46 -14
  338. package/src/runtime/routes/migration-rollback-routes.ts +209 -0
  339. package/src/runtime/routes/migration-routes.ts +17 -1
  340. package/src/runtime/routes/notification-routes.ts +58 -0
  341. package/src/runtime/routes/schedule-routes.ts +65 -0
  342. package/src/runtime/routes/secret-routes.ts +141 -10
  343. package/src/runtime/routes/settings-routes.ts +41 -1
  344. package/src/runtime/routes/tts-routes.ts +96 -0
  345. package/src/runtime/routes/upgrade-broadcast-routes.ts +26 -2
  346. package/src/runtime/routes/workspace-commit-routes.ts +62 -0
  347. package/src/runtime/routes/workspace-routes.test.ts +22 -1
  348. package/src/runtime/routes/workspace-routes.ts +1 -1
  349. package/src/runtime/routes/workspace-utils.ts +86 -2
  350. package/src/security/ces-credential-client.ts +75 -29
  351. package/src/security/ces-rpc-credential-backend.ts +86 -0
  352. package/src/security/credential-backend.ts +22 -92
  353. package/src/security/keychain-broker-client.ts +10 -2
  354. package/src/security/secure-keys.ts +113 -115
  355. package/src/skills/catalog-install.ts +6 -32
  356. package/src/skills/skill-memory.ts +1 -0
  357. package/src/subagent/manager.ts +2 -5
  358. package/src/telemetry/usage-telemetry-reporter.ts +4 -2
  359. package/src/tools/acp/spawn.ts +78 -1
  360. package/src/tools/calls/call-start.ts +1 -0
  361. package/src/tools/credentials/vault.ts +5 -3
  362. package/src/tools/executor.ts +0 -4
  363. package/src/tools/memory/definitions.ts +3 -2
  364. package/src/tools/memory/handlers.ts +10 -7
  365. package/src/tools/network/script-proxy/session-manager.ts +19 -4
  366. package/src/tools/network/web-fetch.ts +3 -1
  367. package/src/tools/skills/execute.ts +1 -1
  368. package/src/tools/terminal/safe-env.ts +1 -0
  369. package/src/tools/types.ts +0 -8
  370. package/src/util/browser.ts +15 -0
  371. package/src/util/errors.ts +0 -12
  372. package/src/util/platform.ts +4 -51
  373. package/src/workspace/git-service.ts +5 -2
  374. package/src/workspace/migrations/001-avatar-rename.ts +15 -0
  375. package/src/workspace/migrations/003-seed-device-id.ts +17 -1
  376. package/src/workspace/migrations/004-extract-collect-usage-data.ts +33 -0
  377. package/src/workspace/migrations/005-add-send-diagnostics.ts +3 -0
  378. package/src/workspace/migrations/006-services-config.ts +49 -0
  379. package/src/workspace/migrations/007-web-search-provider-rename.ts +27 -0
  380. package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +3 -0
  381. package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +4 -0
  382. package/src/workspace/migrations/010-app-dir-rename.ts +78 -0
  383. package/src/workspace/migrations/011-backfill-installation-id.ts +11 -0
  384. package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +44 -0
  385. package/src/workspace/migrations/013-repair-conversation-disk-view.ts +5 -0
  386. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +153 -0
  387. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +156 -0
  388. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +150 -0
  389. package/src/workspace/migrations/017-seed-persona-dirs.ts +96 -0
  390. package/src/workspace/migrations/018-rekey-compound-credential-keys.ts +184 -0
  391. package/src/workspace/migrations/019-scope-journal-to-guardian.ts +103 -0
  392. package/src/workspace/migrations/migrate-to-workspace-volume.ts +27 -5
  393. package/src/workspace/migrations/registry.ts +12 -0
  394. package/src/workspace/migrations/runner.ts +106 -2
  395. package/src/workspace/migrations/types.ts +4 -0
  396. package/src/workspace/provider-commit-message-generator.ts +12 -21
  397. package/src/__tests__/claude-code-skill-regression.test.ts +0 -206
  398. package/src/__tests__/claude-code-tool-profiles.test.ts +0 -99
  399. package/src/__tests__/diagnostics-export.test.ts +0 -288
  400. package/src/__tests__/local-gateway-health.test.ts +0 -209
  401. package/src/__tests__/provider-fail-open-selection.test.ts +0 -271
  402. package/src/__tests__/provider-failover-actual-provider.test.ts +0 -66
  403. package/src/__tests__/secret-ingress-handler.test.ts +0 -120
  404. package/src/__tests__/swarm-conversation-integration.test.ts +0 -358
  405. package/src/__tests__/swarm-dag-pathological.test.ts +0 -547
  406. package/src/__tests__/swarm-orchestrator.test.ts +0 -463
  407. package/src/__tests__/swarm-plan-validator.test.ts +0 -384
  408. package/src/__tests__/swarm-recursion.test.ts +0 -197
  409. package/src/__tests__/swarm-router-planner.test.ts +0 -234
  410. package/src/__tests__/swarm-tool.test.ts +0 -185
  411. package/src/__tests__/swarm-worker-backend.test.ts +0 -144
  412. package/src/__tests__/swarm-worker-runner.test.ts +0 -288
  413. package/src/commands/__tests__/cc-command-registry.test.ts +0 -396
  414. package/src/commands/cc-command-registry.ts +0 -248
  415. package/src/config/bundled-skills/claude-code/SKILL.md +0 -53
  416. package/src/config/bundled-skills/claude-code/TOOLS.json +0 -47
  417. package/src/config/bundled-skills/claude-code/tools/claude-code.ts +0 -12
  418. package/src/config/bundled-skills/orchestration/SKILL.md +0 -33
  419. package/src/config/bundled-skills/orchestration/TOOLS.json +0 -35
  420. package/src/config/bundled-skills/orchestration/tools/swarm-delegate.ts +0 -12
  421. package/src/config/schemas/swarm.ts +0 -82
  422. package/src/logfire.ts +0 -135
  423. package/src/memory/search/lexical.ts +0 -48
  424. package/src/providers/failover.ts +0 -186
  425. package/src/runtime/local-gateway-health.ts +0 -275
  426. package/src/security/secret-ingress.ts +0 -68
  427. package/src/swarm/backend-claude-code.ts +0 -225
  428. package/src/swarm/checkpoint.ts +0 -137
  429. package/src/swarm/graph-utils.ts +0 -53
  430. package/src/swarm/index.ts +0 -55
  431. package/src/swarm/limits.ts +0 -66
  432. package/src/swarm/orchestrator.ts +0 -424
  433. package/src/swarm/plan-validator.ts +0 -117
  434. package/src/swarm/router-planner.ts +0 -162
  435. package/src/swarm/router-prompts.ts +0 -39
  436. package/src/swarm/synthesizer.ts +0 -81
  437. package/src/swarm/types.ts +0 -72
  438. package/src/swarm/worker-backend.ts +0 -131
  439. package/src/swarm/worker-prompts.ts +0 -80
  440. package/src/swarm/worker-runner.ts +0 -170
  441. package/src/tools/claude-code/claude-code.ts +0 -610
  442. package/src/tools/swarm/delegate.ts +0 -205
@@ -28,7 +28,6 @@ import {
28
28
  memoryItems,
29
29
  } from "../../memory/schema.js";
30
30
  import { getLogger } from "../../util/logger.js";
31
- import { truncate } from "../../util/truncate.js";
32
31
  import { httpError } from "../http-errors.js";
33
32
  import type { RouteContext, RouteDefinition } from "../http-router.js";
34
33
 
@@ -45,6 +44,8 @@ const VALID_KINDS = [
45
44
  "decision",
46
45
  "constraint",
47
46
  "event",
47
+ "capability",
48
+ "journal",
48
49
  ] as const;
49
50
 
50
51
  type MemoryItemKind = (typeof VALID_KINDS)[number];
@@ -154,7 +155,7 @@ async function searchItemsSemantic(
154
155
  const sparse = generateSparseEmbedding(query);
155
156
  const sparseVector = { indices: sparse.indices, values: sparse.values };
156
157
 
157
- // Build Qdrant filter — items only, exclude capability kind and sentinel
158
+ // Build Qdrant filter — items only, exclude sentinel
158
159
  const mustConditions: Array<Record<string, unknown>> = [
159
160
  { key: "target_type", match: { value: "item" } },
160
161
  ];
@@ -167,10 +168,7 @@ async function searchItemsSemantic(
167
168
 
168
169
  const filter = {
169
170
  must: mustConditions,
170
- must_not: [
171
- { key: "kind", match: { value: "capability" } },
172
- { key: "_meta", match: { value: true } },
173
- ],
171
+ must_not: [{ key: "_meta", match: { value: true } }],
174
172
  };
175
173
 
176
174
  const qdrant = getQdrantClient();
@@ -185,6 +183,11 @@ async function searchItemsSemantic(
185
183
  );
186
184
 
187
185
  const ids = results.map((r) => r.payload.target_id);
186
+
187
+ // Use the vector search result count as the pagination total.
188
+ // A DB-wide COUNT would include items with no embedding yet (lagging) and
189
+ // items irrelevant to the search query, inflating the total and causing
190
+ // clients to paginate into empty pages.
188
191
  return { ids, total: ids.length };
189
192
  } catch (err) {
190
193
  log.warn({ err }, "Semantic memory search failed, falling back to SQL");
@@ -249,11 +252,24 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
249
252
  offsetParam + limitParam,
250
253
  );
251
254
 
252
- // Batch-fetch full rows from SQLite
255
+ if (pageIds.length === 0) {
256
+ return Response.json({ items: [], total: semanticResult.total });
257
+ }
258
+
259
+ // Re-apply the same DB-side filters used in the SQL path as defense-
260
+ // in-depth against stale Qdrant payloads leaking deleted/mismatched rows.
261
+ const hydrationConditions = [inArray(memoryItems.id, pageIds)];
262
+ if (statusParam && statusParam !== "all") {
263
+ hydrationConditions.push(eq(memoryItems.status, statusParam));
264
+ }
265
+ if (kindParam) {
266
+ hydrationConditions.push(eq(memoryItems.kind, kindParam));
267
+ }
268
+
253
269
  const rows = db
254
270
  .select()
255
271
  .from(memoryItems)
256
- .where(inArray(memoryItems.id, pageIds))
272
+ .where(and(...hydrationConditions))
257
273
  .all();
258
274
 
259
275
  // Preserve Qdrant relevance ordering
@@ -279,8 +295,6 @@ export async function handleListMemoryItems(url: URL): Promise<Response> {
279
295
 
280
296
  // ── SQL path (default or fallback) ──────────────────────────────────
281
297
  const conditions = [];
282
- // Hide system-managed capability memories (skill announcements) from the UI
283
- conditions.push(ne(memoryItems.kind, "capability"));
284
298
  if (statusParam && statusParam !== "all") {
285
299
  conditions.push(eq(memoryItems.status, statusParam));
286
300
  }
@@ -428,8 +442,8 @@ export async function handleCreateMemoryItem(
428
442
  );
429
443
  }
430
444
 
431
- const trimmedSubject = truncate(subject.trim(), 80, "");
432
- const trimmedStatement = truncate(statement.trim(), 500, "");
445
+ const trimmedSubject = subject.trim();
446
+ const trimmedStatement = statement.trim();
433
447
 
434
448
  const scopeId = "default";
435
449
  const fingerprint = computeMemoryFingerprint(
@@ -474,6 +488,7 @@ export async function handleCreateMemoryItem(
474
488
  confidence: 0.95,
475
489
  importance: importance ?? 0.8,
476
490
  fingerprint,
491
+ sourceType: "tool",
477
492
  verificationState: "user_confirmed",
478
493
  scopeId,
479
494
  firstSeenAt: now,
@@ -516,6 +531,7 @@ export async function handleUpdateMemoryItem(
516
531
  kind?: string;
517
532
  status?: string;
518
533
  importance?: number;
534
+ sourceType?: string;
519
535
  verificationState?: string;
520
536
  };
521
537
 
@@ -540,13 +556,13 @@ export async function handleUpdateMemoryItem(
540
556
  if (typeof body.subject !== "string") {
541
557
  return httpError("BAD_REQUEST", "subject must be a string", 400);
542
558
  }
543
- set.subject = truncate(body.subject.trim(), 80, "");
559
+ set.subject = body.subject.trim();
544
560
  }
545
561
  if (body.statement !== undefined) {
546
562
  if (typeof body.statement !== "string") {
547
563
  return httpError("BAD_REQUEST", "statement must be a string", 400);
548
564
  }
549
- set.statement = truncate(body.statement.trim(), 500, "");
565
+ set.statement = body.statement.trim();
550
566
  }
551
567
  if (body.kind !== undefined) {
552
568
  if (!isValidKind(body.kind)) {
@@ -564,8 +580,24 @@ export async function handleUpdateMemoryItem(
564
580
  if (body.importance !== undefined) {
565
581
  set.importance = body.importance;
566
582
  }
583
+ if (body.sourceType !== undefined) {
584
+ set.sourceType = body.sourceType;
585
+ }
586
+
587
+ // Accept verificationState from clients that haven't migrated to sourceType yet.
588
+ // Map verificationState → sourceType for forward compat, and write both fields.
567
589
  if (body.verificationState !== undefined) {
568
590
  set.verificationState = body.verificationState;
591
+ // Map verificationState to sourceType if sourceType wasn't explicitly provided
592
+ if (body.sourceType === undefined) {
593
+ set.sourceType =
594
+ body.verificationState === "user_confirmed" ? "tool" : "extraction";
595
+ }
596
+ }
597
+ // If sourceType was set (either directly or via mapping), also write verificationState
598
+ if (body.sourceType !== undefined && body.verificationState === undefined) {
599
+ set.verificationState =
600
+ body.sourceType === "tool" ? "user_confirmed" : "assistant_inferred";
569
601
  }
570
602
 
571
603
  // If subject, statement, or kind changed, recompute fingerprint
@@ -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",