@vellumai/assistant 0.5.9 → 0.5.11

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 (278) hide show
  1. package/AGENTS.md +9 -1
  2. package/ARCHITECTURE.md +48 -48
  3. package/Dockerfile +2 -0
  4. package/README.md +1 -1
  5. package/docs/architecture/integrations.md +6 -13
  6. package/docs/architecture/memory.md +7 -12
  7. package/docs/architecture/security.md +5 -5
  8. package/docs/credential-execution-service.md +9 -9
  9. package/docs/skills.md +1 -1
  10. package/node_modules/@vellumai/credential-storage/src/index.ts +2 -2
  11. package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +1 -1
  12. package/openapi.yaml +7130 -0
  13. package/package.json +2 -1
  14. package/scripts/generate-openapi.ts +562 -0
  15. package/src/__tests__/acp-session.test.ts +239 -44
  16. package/src/__tests__/assistant-feature-flag-guard.test.ts +8 -8
  17. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +5 -86
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -14
  19. package/src/__tests__/browser-skill-endstate.test.ts +1 -1
  20. package/src/__tests__/btw-routes.test.ts +8 -0
  21. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +10 -10
  22. package/src/__tests__/channel-approvals.test.ts +7 -7
  23. package/src/__tests__/channel-readiness-service.test.ts +41 -0
  24. package/src/__tests__/config-schema.test.ts +10 -2
  25. package/src/__tests__/context-memory-e2e.test.ts +2 -6
  26. package/src/__tests__/conversation-skill-tools.test.ts +1 -3
  27. package/src/__tests__/conversation-title-service.test.ts +2 -15
  28. package/src/__tests__/credential-execution-feature-gates.test.ts +4 -8
  29. package/src/__tests__/credential-execution-managed-contract.test.ts +8 -8
  30. package/src/__tests__/credential-security-e2e.test.ts +4 -4
  31. package/src/__tests__/credential-security-invariants.test.ts +3 -3
  32. package/src/__tests__/credentials-cli.test.ts +3 -3
  33. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -1
  34. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  35. package/src/__tests__/heartbeat-service.test.ts +35 -0
  36. package/src/__tests__/host-shell-tool.test.ts +1 -1
  37. package/src/__tests__/inline-skill-load-permissions.test.ts +3 -3
  38. package/src/__tests__/llm-request-log-turn-query.test.ts +64 -0
  39. package/src/__tests__/log-export-workspace.test.ts +1 -1
  40. package/src/__tests__/mcp-client-auth.test.ts +1 -1
  41. package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
  42. package/src/__tests__/memory-recall-log-store.test.ts +182 -0
  43. package/src/__tests__/memory-recall-quality.test.ts +6 -8
  44. package/src/__tests__/memory-regressions.test.ts +53 -42
  45. package/src/__tests__/memory-retrieval.benchmark.test.ts +5 -9
  46. package/src/__tests__/messaging-skill-split.test.ts +2 -17
  47. package/src/__tests__/oauth-cli.test.ts +98 -551
  48. package/src/__tests__/platform-callback-registration.test.ts +119 -0
  49. package/src/__tests__/secret-ingress-channel.test.ts +261 -0
  50. package/src/__tests__/secret-ingress-cli.test.ts +201 -0
  51. package/src/__tests__/secret-ingress-http.test.ts +312 -0
  52. package/src/__tests__/secret-ingress.test.ts +283 -0
  53. package/src/__tests__/secret-onetime-send.test.ts +4 -4
  54. package/src/__tests__/skill-feature-flags-integration.test.ts +4 -4
  55. package/src/__tests__/skill-feature-flags.test.ts +11 -19
  56. package/src/__tests__/skill-load-feature-flag.test.ts +1 -1
  57. package/src/__tests__/skill-load-inline-command.test.ts +3 -3
  58. package/src/__tests__/skill-load-inline-includes.test.ts +2 -2
  59. package/src/__tests__/skill-memory.test.ts +2 -4
  60. package/src/__tests__/skill-projection-feature-flag.test.ts +2 -4
  61. package/src/__tests__/skill-projection.benchmark.test.ts +1 -3
  62. package/src/__tests__/skills.test.ts +16 -2
  63. package/src/__tests__/slack-channel-config.test.ts +1 -1
  64. package/src/__tests__/slack-skill.test.ts +5 -69
  65. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -1
  66. package/src/__tests__/workspace-migration-015-migrate-credentials-to-keychain.test.ts +5 -238
  67. package/src/__tests__/workspace-migration-016-migrate-credentials-from-keychain.test.ts +5 -206
  68. package/src/__tests__/workspace-migration-018-rekey-compound-credential-keys.test.ts +181 -0
  69. package/src/__tests__/workspace-migrations-runner.test.ts +15 -7
  70. package/src/acp/client-handler.ts +113 -31
  71. package/src/acp/session-manager.ts +29 -27
  72. package/src/approvals/guardian-request-resolvers.ts +1 -1
  73. package/src/cli/AGENTS.md +73 -0
  74. package/src/cli/commands/autonomy.ts +3 -5
  75. package/src/cli/commands/credential-execution.ts +1 -2
  76. package/src/cli/commands/credentials.ts +4 -4
  77. package/src/cli/commands/memory.ts +2 -3
  78. package/src/cli/commands/oauth/__tests__/connect.test.ts +785 -0
  79. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +760 -0
  80. package/src/cli/commands/oauth/__tests__/mode.test.ts +672 -0
  81. package/src/cli/commands/oauth/__tests__/ping.test.ts +690 -0
  82. package/src/cli/commands/oauth/__tests__/status.test.ts +579 -0
  83. package/src/cli/commands/oauth/__tests__/token.test.ts +467 -0
  84. package/src/cli/commands/oauth/apps.ts +29 -11
  85. package/src/cli/commands/oauth/connect.ts +373 -0
  86. package/src/cli/commands/oauth/connections.ts +14 -493
  87. package/src/cli/commands/oauth/disconnect.ts +333 -0
  88. package/src/cli/commands/oauth/index.ts +62 -10
  89. package/src/cli/commands/oauth/mode.ts +263 -0
  90. package/src/cli/commands/oauth/ping.ts +222 -0
  91. package/src/cli/commands/oauth/providers.ts +30 -3
  92. package/src/cli/commands/oauth/request.ts +576 -0
  93. package/src/cli/commands/oauth/shared.ts +132 -0
  94. package/src/cli/commands/oauth/status.ts +202 -0
  95. package/src/cli/commands/oauth/token.ts +159 -0
  96. package/src/cli/commands/platform.ts +20 -14
  97. package/src/cli.ts +82 -17
  98. package/src/config/assistant-feature-flags.ts +74 -11
  99. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  100. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -1
  101. package/src/config/bundled-skills/messaging/SKILL.md +13 -36
  102. package/src/config/bundled-skills/messaging/TOOLS.json +9 -9
  103. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
  104. package/src/config/bundled-skills/notifications/SKILL.md +1 -1
  105. package/src/config/bundled-skills/schedule/SKILL.md +2 -2
  106. package/src/config/bundled-skills/settings/SKILL.md +5 -3
  107. package/src/config/bundled-skills/settings/TOOLS.json +17 -0
  108. package/src/config/bundled-skills/settings/tools/avatar-get.ts +50 -0
  109. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +7 -0
  110. package/src/config/bundled-skills/settings/tools/avatar-update.ts +6 -1
  111. package/src/config/bundled-skills/settings/tools/identity-avatar.ts +55 -0
  112. package/src/config/bundled-skills/skills-catalog/SKILL.md +3 -3
  113. package/src/config/bundled-skills/slack/SKILL.md +58 -44
  114. package/src/config/bundled-tool-registry.ts +2 -19
  115. package/src/config/env.ts +5 -1
  116. package/src/config/feature-flag-registry.json +57 -41
  117. package/src/config/loader.ts +4 -0
  118. package/src/config/schemas/platform.ts +0 -8
  119. package/src/config/schemas/security.ts +9 -1
  120. package/src/config/schemas/services.ts +1 -1
  121. package/src/config/skill-state.ts +1 -3
  122. package/src/config/skills.ts +2 -4
  123. package/src/credential-execution/feature-gates.ts +9 -16
  124. package/src/credential-execution/process-manager.ts +12 -0
  125. package/src/daemon/config-watcher.ts +4 -0
  126. package/src/daemon/conversation-agent-loop-handlers.ts +10 -0
  127. package/src/daemon/conversation-agent-loop.ts +49 -2
  128. package/src/daemon/conversation-memory.ts +0 -1
  129. package/src/daemon/handlers/config-slack-channel.ts +43 -1
  130. package/src/daemon/handlers/conversations.ts +41 -33
  131. package/src/daemon/lifecycle.ts +28 -5
  132. package/src/daemon/message-types/acp.ts +0 -15
  133. package/src/daemon/message-types/memory.ts +0 -1
  134. package/src/daemon/message-types/messages.ts +9 -1
  135. package/src/daemon/message-types/schedules.ts +9 -0
  136. package/src/daemon/server.ts +19 -7
  137. package/src/email/feature-gate.ts +3 -3
  138. package/src/heartbeat/heartbeat-service.ts +48 -0
  139. package/src/inbound/platform-callback-registration.ts +61 -7
  140. package/src/mcp/mcp-oauth-provider.ts +3 -3
  141. package/src/memory/app-store.ts +3 -3
  142. package/src/memory/conversation-crud.ts +124 -0
  143. package/src/memory/conversation-title-service.ts +7 -17
  144. package/src/memory/db-init.ts +8 -0
  145. package/src/memory/embedding-local.ts +47 -2
  146. package/src/memory/indexer.ts +13 -10
  147. package/src/memory/items-extractor.ts +12 -4
  148. package/src/memory/job-utils.ts +5 -0
  149. package/src/memory/jobs-store.ts +10 -2
  150. package/src/memory/journal-memory.ts +6 -2
  151. package/src/memory/llm-request-log-store.ts +88 -21
  152. package/src/memory/memory-recall-log-store.ts +128 -0
  153. package/src/memory/migrations/194-memory-recall-logs.ts +50 -0
  154. package/src/memory/migrations/195-oauth-providers-ping-config.ts +23 -0
  155. package/src/memory/migrations/index.ts +2 -0
  156. package/src/memory/migrations/validate-migration-state.ts +14 -1
  157. package/src/memory/retriever.test.ts +4 -5
  158. package/src/memory/schema/infrastructure.ts +31 -0
  159. package/src/memory/schema/oauth.ts +3 -0
  160. package/src/messaging/providers/telegram-bot/adapter.ts +1 -1
  161. package/src/oauth/connect-orchestrator.ts +54 -0
  162. package/src/oauth/manual-token-connection.ts +5 -5
  163. package/src/oauth/oauth-store.ts +26 -5
  164. package/src/oauth/seed-providers.ts +10 -1
  165. package/src/permissions/checker.ts +2 -2
  166. package/src/permissions/trust-client.ts +2 -2
  167. package/src/platform/client.ts +2 -2
  168. package/src/prompts/journal-context.ts +6 -1
  169. package/src/providers/anthropic/client.ts +143 -1
  170. package/src/runtime/auth/__tests__/middleware.test.ts +19 -0
  171. package/src/runtime/auth/route-policy.ts +0 -1
  172. package/src/runtime/btw-sidechain.ts +7 -1
  173. package/src/runtime/channel-approvals.ts +2 -2
  174. package/src/runtime/channel-readiness-service.ts +30 -7
  175. package/src/runtime/http-router.ts +31 -0
  176. package/src/runtime/http-server.ts +21 -4
  177. package/src/runtime/http-types.ts +2 -0
  178. package/src/runtime/pending-interactions.ts +21 -3
  179. package/src/runtime/routes/acp-routes.ts +46 -28
  180. package/src/runtime/routes/app-management-routes.ts +123 -0
  181. package/src/runtime/routes/app-routes.ts +31 -0
  182. package/src/runtime/routes/approval-routes.ts +108 -3
  183. package/src/runtime/routes/attachment-routes.ts +45 -0
  184. package/src/runtime/routes/avatar-routes.ts +16 -0
  185. package/src/runtime/routes/brain-graph-routes.ts +18 -0
  186. package/src/runtime/routes/btw-routes.ts +20 -0
  187. package/src/runtime/routes/call-routes.ts +81 -0
  188. package/src/runtime/routes/channel-readiness-routes.ts +48 -7
  189. package/src/runtime/routes/channel-routes.ts +18 -0
  190. package/src/runtime/routes/channel-verification-routes.ts +49 -1
  191. package/src/runtime/routes/contact-routes.ts +77 -0
  192. package/src/runtime/routes/conversation-attention-routes.ts +37 -0
  193. package/src/runtime/routes/conversation-management-routes.ts +94 -0
  194. package/src/runtime/routes/conversation-query-routes.ts +78 -0
  195. package/src/runtime/routes/conversation-routes.ts +115 -38
  196. package/src/runtime/routes/conversation-starter-routes.ts +29 -0
  197. package/src/runtime/routes/debug-routes.ts +23 -0
  198. package/src/runtime/routes/diagnostics-routes.ts +30 -0
  199. package/src/runtime/routes/documents-routes.ts +42 -0
  200. package/src/runtime/routes/events-routes.ts +10 -0
  201. package/src/runtime/routes/global-search-routes.ts +35 -0
  202. package/src/runtime/routes/guardian-action-routes.ts +47 -2
  203. package/src/runtime/routes/guardian-approval-prompt.ts +77 -2
  204. package/src/runtime/routes/heartbeat-routes.ts +278 -0
  205. package/src/runtime/routes/host-bash-routes.ts +16 -1
  206. package/src/runtime/routes/host-cu-routes.ts +23 -1
  207. package/src/runtime/routes/host-file-routes.ts +18 -1
  208. package/src/runtime/routes/identity-routes.ts +35 -0
  209. package/src/runtime/routes/inbound-message-handler.ts +46 -25
  210. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +30 -2
  211. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -2
  212. package/src/runtime/routes/integrations/twilio.ts +32 -22
  213. package/src/runtime/routes/invite-routes.ts +83 -0
  214. package/src/runtime/routes/log-export-routes.ts +14 -0
  215. package/src/runtime/routes/memory-item-routes.ts +99 -1
  216. package/src/runtime/routes/migration-rollback-routes.ts +25 -0
  217. package/src/runtime/routes/migration-routes.ts +40 -0
  218. package/src/runtime/routes/notification-routes.ts +20 -0
  219. package/src/runtime/routes/oauth-apps.ts +11 -3
  220. package/src/runtime/routes/pairing-routes.ts +15 -0
  221. package/src/runtime/routes/recording-routes.ts +72 -0
  222. package/src/runtime/routes/schedule-routes.ts +77 -5
  223. package/src/runtime/routes/secret-routes.ts +63 -1
  224. package/src/runtime/routes/settings-routes.ts +91 -1
  225. package/src/runtime/routes/skills-routes.ts +98 -16
  226. package/src/runtime/routes/subagents-routes.ts +38 -3
  227. package/src/runtime/routes/surface-action-routes.ts +66 -24
  228. package/src/runtime/routes/surface-content-routes.ts +20 -0
  229. package/src/runtime/routes/telemetry-routes.ts +12 -0
  230. package/src/runtime/routes/trace-event-routes.ts +25 -0
  231. package/src/runtime/routes/trust-rules-routes.ts +46 -0
  232. package/src/runtime/routes/tts-routes.ts +15 -4
  233. package/src/runtime/routes/upgrade-broadcast-routes.ts +38 -0
  234. package/src/runtime/routes/usage-routes.ts +59 -0
  235. package/src/runtime/routes/watch-routes.ts +28 -0
  236. package/src/runtime/routes/work-items-routes.ts +59 -0
  237. package/src/runtime/routes/workspace-commit-routes.ts +12 -0
  238. package/src/runtime/routes/workspace-routes.ts +102 -0
  239. package/src/schedule/scheduler.ts +7 -1
  240. package/src/security/AGENTS.md +7 -0
  241. package/src/security/credential-backend.ts +1 -1
  242. package/src/security/encrypted-store.ts +3 -3
  243. package/src/security/oauth2.ts +55 -0
  244. package/src/security/secret-ingress.ts +174 -0
  245. package/src/security/secret-patterns.ts +133 -0
  246. package/src/security/secret-scanner.ts +28 -117
  247. package/src/signals/confirm.ts +12 -8
  248. package/src/signals/user-message.ts +18 -3
  249. package/src/skills/skill-memory.ts +1 -2
  250. package/src/tasks/task-runner.ts +7 -1
  251. package/src/tools/credentials/broker.ts +1 -1
  252. package/src/tools/credentials/metadata-store.ts +1 -1
  253. package/src/tools/credentials/vault.ts +2 -3
  254. package/src/tools/memory/definitions.ts +1 -1
  255. package/src/tools/memory/handlers.test.ts +2 -4
  256. package/src/tools/skills/load.ts +1 -1
  257. package/src/tools/terminal/safe-env.ts +7 -0
  258. package/src/tools/tool-manifest.ts +1 -1
  259. package/src/util/log-redact.ts +9 -34
  260. package/src/workspace/migrations/015-migrate-credentials-to-keychain.ts +13 -148
  261. package/src/workspace/migrations/016-migrate-credentials-from-keychain.ts +7 -145
  262. package/src/workspace/migrations/AGENTS.md +11 -0
  263. package/src/workspace/migrations/runner.ts +16 -6
  264. package/src/workspace/migrations/types.ts +7 -0
  265. package/docs/architecture/keychain-broker.md +0 -69
  266. package/src/__tests__/keychain-broker-client.test.ts +0 -800
  267. package/src/cli/commands/oauth/platform.ts +0 -525
  268. package/src/config/bundled-skills/slack/TOOLS.json +0 -272
  269. package/src/config/bundled-skills/slack/tools/shared.ts +0 -34
  270. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +0 -27
  271. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +0 -38
  272. package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +0 -146
  273. package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +0 -105
  274. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +0 -26
  275. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +0 -27
  276. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +0 -25
  277. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +0 -372
  278. package/src/security/keychain-broker-client.ts +0 -446
@@ -4,12 +4,16 @@
4
4
  * POST /v1/surface-actions — dispatch a surface action to an active conversation.
5
5
  * Requires the conversation to already exist (does not create new conversations).
6
6
  */
7
+ import { z } from "zod";
8
+
9
+ import { isHttpAuthDisabled } from "../../config/env.js";
7
10
  import { getLogger } from "../../util/logger.js";
8
11
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../assistant-scope.js";
9
12
  import type { AuthContext } from "../auth/types.js";
10
13
  import { healGuardianBindingDrift } from "../guardian-vellum-migration.js";
11
14
  import { httpError } from "../http-errors.js";
12
15
  import type { RouteDefinition } from "../http-router.js";
16
+ import { resolveLocalTrustContext } from "../local-actor-identity.js";
13
17
  import {
14
18
  resolveTrustContext,
15
19
  withSourceChannel,
@@ -55,32 +59,39 @@ function applyTrustContext(
55
59
  const sourceChannel = "vellum";
56
60
 
57
61
  if (authContext.actorPrincipalId) {
58
- const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
59
- let trustCtx = resolveTrustContext({
60
- assistantId,
61
- sourceChannel,
62
- conversationExternalId: "local",
63
- actorExternalId: authContext.actorPrincipalId,
64
- });
65
- if (trustCtx.trustClass === "unknown") {
66
- const healed = healGuardianBindingDrift(authContext.actorPrincipalId);
67
- if (healed) {
68
- trustCtx = resolveTrustContext({
69
- assistantId,
70
- sourceChannel,
71
- conversationExternalId: "local",
72
- actorExternalId: authContext.actorPrincipalId,
73
- });
74
- log.info(
75
- {
76
- actorPrincipalId: authContext.actorPrincipalId,
77
- trustClass: trustCtx.trustClass,
78
- },
79
- "Trust re-resolved after guardian binding drift heal (surface action)",
80
- );
62
+ // Dev bypass (HTTP auth disabled): the synthetic "dev-bypass" principal
63
+ // won't match any guardian binding. Resolve from the local guardian
64
+ // binding instead, which produces the correct guardian trust context.
65
+ if (isHttpAuthDisabled() && authContext.actorPrincipalId === "dev-bypass") {
66
+ conversation.setTrustContext(resolveLocalTrustContext(sourceChannel));
67
+ } else {
68
+ const assistantId = DAEMON_INTERNAL_ASSISTANT_ID;
69
+ let trustCtx = resolveTrustContext({
70
+ assistantId,
71
+ sourceChannel,
72
+ conversationExternalId: "local",
73
+ actorExternalId: authContext.actorPrincipalId,
74
+ });
75
+ if (trustCtx.trustClass === "unknown") {
76
+ const healed = healGuardianBindingDrift(authContext.actorPrincipalId);
77
+ if (healed) {
78
+ trustCtx = resolveTrustContext({
79
+ assistantId,
80
+ sourceChannel,
81
+ conversationExternalId: "local",
82
+ actorExternalId: authContext.actorPrincipalId,
83
+ });
84
+ log.info(
85
+ {
86
+ actorPrincipalId: authContext.actorPrincipalId,
87
+ trustClass: trustCtx.trustClass,
88
+ },
89
+ "Trust re-resolved after guardian binding drift heal (surface action)",
90
+ );
91
+ }
81
92
  }
93
+ conversation.setTrustContext(withSourceChannel(sourceChannel, trustCtx));
82
94
  }
83
- conversation.setTrustContext(withSourceChannel(sourceChannel, trustCtx));
84
95
  } else {
85
96
  // Service principals or tokens without an actor ID get guardian context.
86
97
  conversation.setTrustContext({ trustClass: "guardian", sourceChannel });
@@ -209,6 +220,26 @@ export function surfaceActionRouteDefinitions(deps: {
209
220
  {
210
221
  endpoint: "surface-actions",
211
222
  method: "POST",
223
+ summary: "Trigger a surface action",
224
+ description:
225
+ "Execute an interactive action on a surface (e.g. button click, form submit).",
226
+ tags: ["surfaces"],
227
+ requestBody: z.object({
228
+ conversationId: z
229
+ .string()
230
+ .describe("Conversation that owns the surface")
231
+ .optional(),
232
+ surfaceId: z.string().describe("Surface to act on"),
233
+ actionId: z.string().describe("Action identifier"),
234
+ data: z
235
+ .object({})
236
+ .passthrough()
237
+ .describe("Action-specific payload")
238
+ .optional(),
239
+ }),
240
+ responseBody: z.object({
241
+ ok: z.boolean(),
242
+ }),
212
243
  handler: async ({ req, authContext }) => {
213
244
  if (!deps.findConversation) {
214
245
  return httpError(
@@ -228,6 +259,17 @@ export function surfaceActionRouteDefinitions(deps: {
228
259
  {
229
260
  endpoint: "surfaces/:id/undo",
230
261
  method: "POST",
262
+ summary: "Undo last surface action",
263
+ description: "Revert the most recent action on a surface.",
264
+ tags: ["surfaces"],
265
+ requestBody: z.object({
266
+ conversationId: z
267
+ .string()
268
+ .describe("Conversation that owns the surface"),
269
+ }),
270
+ responseBody: z.object({
271
+ ok: z.boolean(),
272
+ }),
231
273
  handler: async ({ req, params }) => {
232
274
  if (!deps.findConversation) {
233
275
  return httpError(
@@ -5,6 +5,8 @@
5
5
  * conversation's in-memory surface state. Used by clients to re-hydrate surfaces
6
6
  * whose data was stripped during memory compaction.
7
7
  */
8
+ import { z } from "zod";
9
+
8
10
  import type {
9
11
  SurfaceData,
10
12
  SurfaceType,
@@ -45,6 +47,24 @@ export function surfaceContentRouteDefinitions(deps: {
45
47
  {
46
48
  endpoint: "surfaces/:surfaceId",
47
49
  method: "GET",
50
+ summary: "Get surface content",
51
+ description:
52
+ "Return the full surface payload from the conversation's in-memory surface state.",
53
+ tags: ["surfaces"],
54
+ queryParams: [
55
+ {
56
+ name: "conversationId",
57
+ schema: { type: "string" },
58
+ required: true,
59
+ description: "Conversation that owns the surface",
60
+ },
61
+ ],
62
+ responseBody: z.object({
63
+ surfaceId: z.string(),
64
+ surfaceType: z.string(),
65
+ title: z.string(),
66
+ data: z.object({}).passthrough().describe("Surface data payload"),
67
+ }),
48
68
  handler: ({ url, params }) => {
49
69
  if (!deps.findConversation) {
50
70
  return httpError(
@@ -4,6 +4,8 @@
4
4
  * POST /v1/telemetry/lifecycle — record a lifecycle event (app_open, hatch).
5
5
  */
6
6
 
7
+ import { z } from "zod";
8
+
7
9
  import { recordLifecycleEvent } from "../../memory/lifecycle-events-store.js";
8
10
  import { getLogger } from "../../util/logger.js";
9
11
  import { httpError } from "../http-errors.js";
@@ -47,6 +49,16 @@ export function telemetryRouteDefinitions(): RouteDefinition[] {
47
49
  {
48
50
  endpoint: "telemetry/lifecycle",
49
51
  method: "POST",
52
+ summary: "Record lifecycle event",
53
+ description: "Record a telemetry lifecycle event (app_open, hatch).",
54
+ tags: ["telemetry"],
55
+ requestBody: z.object({
56
+ event_name: z.string().describe("Event name: app_open or hatch"),
57
+ }),
58
+ responseBody: z.object({
59
+ id: z.string().describe("Event ID"),
60
+ event_name: z.string(),
61
+ }),
50
62
  handler: async ({ req }) => handleRecordLifecycleEvent(req),
51
63
  },
52
64
  ];
@@ -4,6 +4,8 @@
4
4
  * GET /v1/trace-events — Returns persisted trace events for a conversation.
5
5
  */
6
6
 
7
+ import { z } from "zod";
8
+
7
9
  import { getTraceEvents } from "../../memory/trace-event-store.js";
8
10
  import { httpError } from "../http-errors.js";
9
11
  import type { RouteDefinition } from "../http-router.js";
@@ -17,6 +19,29 @@ export function traceEventRouteDefinitions(): RouteDefinition[] {
17
19
  {
18
20
  endpoint: "trace-events",
19
21
  method: "GET",
22
+ summary: "List trace events",
23
+ description: "Return persisted trace events for a conversation.",
24
+ tags: ["trace"],
25
+ queryParams: [
26
+ {
27
+ name: "conversationId",
28
+ schema: { type: "string" },
29
+ description: "Conversation ID (required)",
30
+ },
31
+ {
32
+ name: "limit",
33
+ schema: { type: "integer" },
34
+ description: "Max events to return",
35
+ },
36
+ {
37
+ name: "afterSequence",
38
+ schema: { type: "integer" },
39
+ description: "Return events after this sequence number",
40
+ },
41
+ ],
42
+ responseBody: z.object({
43
+ events: z.array(z.unknown()).describe("Trace event objects"),
44
+ }),
20
45
  handler: ({ url }) => {
21
46
  const conversationId = url.searchParams.get("conversationId");
22
47
  if (!conversationId) {
@@ -5,6 +5,8 @@
5
5
  * the approval-flow trust-rule endpoint in approval-routes.ts.
6
6
  * All endpoints are bearer-token authenticated (standard runtime auth).
7
7
  */
8
+ import { z } from "zod";
9
+
8
10
  import {
9
11
  addRule,
10
12
  getAllRules,
@@ -176,21 +178,65 @@ export function trustRulesRouteDefinitions(): RouteDefinition[] {
176
178
  {
177
179
  endpoint: "trust-rules/manage",
178
180
  method: "GET",
181
+ summary: "List all trust rules",
182
+ description: "Return all persistent trust rules.",
183
+ tags: ["trust-rules"],
184
+ responseBody: z.object({
185
+ type: z.string(),
186
+ rules: z.array(z.unknown()).describe("Trust rule objects"),
187
+ }),
179
188
  handler: () => handleListTrustRules(),
180
189
  },
181
190
  {
182
191
  endpoint: "trust-rules/manage",
183
192
  method: "POST",
193
+ summary: "Add a trust rule",
194
+ description:
195
+ "Create a new persistent trust rule (standalone, not approval-flow).",
196
+ tags: ["trust-rules"],
197
+ requestBody: z.object({
198
+ toolName: z.string().describe("Tool name"),
199
+ pattern: z.string().describe("Allowlist pattern"),
200
+ scope: z.string().describe("Scope"),
201
+ decision: z.string().describe("allow, deny, or ask"),
202
+ allowHighRisk: z
203
+ .boolean()
204
+ .describe("Allow high-risk invocations")
205
+ .optional(),
206
+ executionTarget: z.string().describe("Execution target").optional(),
207
+ }),
208
+ responseBody: z.object({
209
+ ok: z.boolean(),
210
+ }),
184
211
  handler: async ({ req }) => handleAddTrustRuleManage(req),
185
212
  },
186
213
  {
187
214
  endpoint: "trust-rules/manage/:id",
188
215
  method: "DELETE",
216
+ summary: "Remove a trust rule",
217
+ description: "Delete a trust rule by ID.",
218
+ tags: ["trust-rules"],
219
+ responseBody: z.object({
220
+ ok: z.boolean(),
221
+ }),
189
222
  handler: ({ params }) => handleRemoveTrustRuleManage(params.id),
190
223
  },
191
224
  {
192
225
  endpoint: "trust-rules/manage/:id",
193
226
  method: "PATCH",
227
+ summary: "Update a trust rule",
228
+ description: "Partially update fields on an existing trust rule.",
229
+ tags: ["trust-rules"],
230
+ requestBody: z.object({
231
+ tool: z.string().describe("Tool name"),
232
+ pattern: z.string().describe("Allowlist pattern"),
233
+ scope: z.string().describe("Scope"),
234
+ decision: z.string().describe("allow, deny, or ask"),
235
+ priority: z.number().describe("Rule priority"),
236
+ }),
237
+ responseBody: z.object({
238
+ ok: z.boolean(),
239
+ }),
194
240
  handler: async ({ req, params }) =>
195
241
  handleUpdateTrustRuleManage(req, params.id),
196
242
  },
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * POST /v1/messages/:id/tts?conversationId=... — synthesize message text to audio
5
5
  *
6
- * Gated behind the `feature_flags.message-tts.enabled` assistant feature flag.
6
+ * Gated behind the `message-tts` assistant feature flag.
7
7
  * Uses Fish Audio for synthesis when configured.
8
8
  */
9
9
 
@@ -18,7 +18,7 @@ import type { RouteDefinition } from "../http-router.js";
18
18
 
19
19
  const log = getLogger("tts-routes");
20
20
 
21
- const MESSAGE_TTS_FLAG = "feature_flags.message-tts.enabled" as const;
21
+ const MESSAGE_TTS_FLAG = "message-tts" as const;
22
22
 
23
23
  // ---------------------------------------------------------------------------
24
24
  // Route definitions
@@ -30,6 +30,17 @@ export function ttsRouteDefinitions(): RouteDefinition[] {
30
30
  endpoint: "messages/:id/tts",
31
31
  method: "POST",
32
32
  policyKey: "messages/tts",
33
+ summary: "Synthesize message to speech",
34
+ description:
35
+ "Synthesize a message's text content to audio using Fish Audio TTS.",
36
+ tags: ["messages"],
37
+ queryParams: [
38
+ {
39
+ name: "conversationId",
40
+ schema: { type: "string" },
41
+ description: "Conversation that contains the message",
42
+ },
43
+ ],
33
44
  handler: async ({ url, params }) => {
34
45
  const config = getConfig();
35
46
 
@@ -50,8 +61,8 @@ export function ttsRouteDefinitions(): RouteDefinition[] {
50
61
  return httpError("BAD_REQUEST", "Message has no text content", 400);
51
62
  }
52
63
 
53
- const sanitizedText = sanitizeForTts(result.text);
54
- if (!sanitizedText.trim()) {
64
+ const sanitizedText = sanitizeForTts(result.text).trim();
65
+ if (!sanitizedText) {
55
66
  return httpError(
56
67
  "BAD_REQUEST",
57
68
  "Message has no speakable text content",
@@ -9,6 +9,8 @@
9
9
  * minted service token.
10
10
  */
11
11
 
12
+ import { z } from "zod";
13
+
12
14
  import type {
13
15
  ServiceGroupUpdateComplete,
14
16
  ServiceGroupUpdateProgress,
@@ -25,6 +27,42 @@ export function upgradeBroadcastRouteDefinitions(): RouteDefinition[] {
25
27
  {
26
28
  endpoint: "admin/upgrade-broadcast",
27
29
  method: "POST",
30
+ summary: "Broadcast upgrade lifecycle event",
31
+ description:
32
+ "Publish a service group update lifecycle event (starting, progress, or complete) to all connected SSE clients.",
33
+ tags: ["admin"],
34
+ requestBody: z.object({
35
+ type: z
36
+ .string()
37
+ .describe('Event type: "starting", "progress", or "complete"'),
38
+ targetVersion: z
39
+ .string()
40
+ .describe("Target version (required for starting)")
41
+ .optional(),
42
+ expectedDowntimeSeconds: z
43
+ .number()
44
+ .describe("Expected downtime in seconds (starting, default 60)")
45
+ .optional(),
46
+ statusMessage: z
47
+ .string()
48
+ .describe("Status message (required for progress)")
49
+ .optional(),
50
+ installedVersion: z
51
+ .string()
52
+ .describe("Installed version (required for complete)")
53
+ .optional(),
54
+ success: z
55
+ .boolean()
56
+ .describe("Whether upgrade succeeded (required for complete)")
57
+ .optional(),
58
+ rolledBackToVersion: z
59
+ .string()
60
+ .describe("Version rolled back to, if any (complete)")
61
+ .optional(),
62
+ }),
63
+ responseBody: z.object({
64
+ ok: z.boolean(),
65
+ }),
28
66
  handler: async ({ req }) => {
29
67
  let body: unknown;
30
68
  try {
@@ -6,6 +6,8 @@
6
6
  * GET /v1/usage/breakdown?from=&to=&groupBy= — grouped breakdown (actor, provider, model)
7
7
  */
8
8
 
9
+ import { z } from "zod";
10
+
9
11
  import {
10
12
  getUsageDayBuckets,
11
13
  getUsageGroupBreakdown,
@@ -63,6 +65,21 @@ export function usageRouteDefinitions(): RouteDefinition[] {
63
65
  {
64
66
  endpoint: "usage/totals",
65
67
  method: "GET",
68
+ summary: "Get usage totals",
69
+ description: "Return aggregate usage totals for a time range.",
70
+ tags: ["usage"],
71
+ queryParams: [
72
+ {
73
+ name: "from",
74
+ schema: { type: "integer" },
75
+ description: "Start epoch millis (required)",
76
+ },
77
+ {
78
+ name: "to",
79
+ schema: { type: "integer" },
80
+ description: "End epoch millis (required)",
81
+ },
82
+ ],
66
83
  handler: ({ url }) => {
67
84
  const range = parseTimeRange(url);
68
85
  if (range instanceof Response) return range;
@@ -73,6 +90,24 @@ export function usageRouteDefinitions(): RouteDefinition[] {
73
90
  {
74
91
  endpoint: "usage/daily",
75
92
  method: "GET",
93
+ summary: "Get daily usage",
94
+ description: "Return per-day usage buckets for a time range.",
95
+ tags: ["usage"],
96
+ queryParams: [
97
+ {
98
+ name: "from",
99
+ schema: { type: "integer" },
100
+ description: "Start epoch millis (required)",
101
+ },
102
+ {
103
+ name: "to",
104
+ schema: { type: "integer" },
105
+ description: "End epoch millis (required)",
106
+ },
107
+ ],
108
+ responseBody: z.object({
109
+ buckets: z.array(z.unknown()).describe("Daily usage bucket objects"),
110
+ }),
76
111
  handler: ({ url }) => {
77
112
  const range = parseTimeRange(url);
78
113
  if (range instanceof Response) return range;
@@ -83,6 +118,30 @@ export function usageRouteDefinitions(): RouteDefinition[] {
83
118
  {
84
119
  endpoint: "usage/breakdown",
85
120
  method: "GET",
121
+ summary: "Get usage breakdown",
122
+ description:
123
+ "Return grouped usage breakdown (by actor, provider, or model).",
124
+ tags: ["usage"],
125
+ queryParams: [
126
+ {
127
+ name: "from",
128
+ schema: { type: "integer" },
129
+ description: "Start epoch millis (required)",
130
+ },
131
+ {
132
+ name: "to",
133
+ schema: { type: "integer" },
134
+ description: "End epoch millis (required)",
135
+ },
136
+ {
137
+ name: "groupBy",
138
+ schema: { type: "string" },
139
+ description: "Group by: actor, provider, or model (required)",
140
+ },
141
+ ],
142
+ responseBody: z.object({
143
+ breakdown: z.array(z.unknown()).describe("Grouped usage entries"),
144
+ }),
86
145
  handler: ({ url }) => {
87
146
  const range = parseTimeRange(url);
88
147
  if (range instanceof Response) return range;
@@ -5,6 +5,8 @@
5
5
  * zero dependency on CU session state.
6
6
  */
7
7
 
8
+ import { z } from "zod";
9
+
8
10
  import { getLogger } from "../../util/logger.js";
9
11
  import { httpError } from "../http-errors.js";
10
12
  import type { RouteDefinition } from "../http-router.js";
@@ -122,7 +124,33 @@ export function watchRouteDefinitions(deps: {
122
124
  endpoint: "computer-use/watch",
123
125
  method: "POST",
124
126
  policyKey: "computer-use/watch",
127
+ summary: "Submit watch observation",
128
+ description: "Send a screen observation from ambient watch mode.",
129
+ tags: ["computer-use"],
125
130
  handler: async ({ req }) => handleWatchObservationRoute(req, getDeps()),
131
+ requestBody: z.object({
132
+ watchId: z.string().describe("Watch session ID"),
133
+ conversationId: z.string().describe("Conversation to associate with"),
134
+ ocrText: z.string().describe("OCR text from screen capture"),
135
+ appName: z.string().describe("Active application name").optional(),
136
+ windowTitle: z.string().describe("Active window title").optional(),
137
+ bundleIdentifier: z
138
+ .string()
139
+ .describe("Application bundle identifier")
140
+ .optional(),
141
+ timestamp: z.number().describe("Capture timestamp (epoch ms)"),
142
+ captureIndex: z
143
+ .number()
144
+ .int()
145
+ .describe("Index of this capture in the batch"),
146
+ totalExpected: z
147
+ .number()
148
+ .int()
149
+ .describe("Total captures expected in the batch"),
150
+ }),
151
+ responseBody: z.object({
152
+ ok: z.boolean(),
153
+ }),
126
154
  },
127
155
  ];
128
156
  }
@@ -5,6 +5,8 @@
5
5
  * sharing business logic with the handlers in
6
6
  * `daemon/handlers/work-items.ts`.
7
7
  */
8
+ import { z } from "zod";
9
+
8
10
  import type { Conversation } from "../../daemon/conversation.js";
9
11
  import type { ServerMessage } from "../../daemon/message-protocol.js";
10
12
  import { getMessages } from "../../memory/conversation-crud.js";
@@ -417,6 +419,12 @@ export function workItemRouteDefinitions(
417
419
  endpoint: "work-items",
418
420
  method: "GET",
419
421
  policyKey: "work-items",
422
+ summary: "List work items",
423
+ description: "Return work items, optionally filtered by status.",
424
+ tags: ["work-items"],
425
+ responseBody: z.object({
426
+ items: z.array(z.unknown()),
427
+ }),
420
428
  handler: ({ url }) => {
421
429
  const status = url.searchParams.get("status") ?? undefined;
422
430
  const items = listWorkItems(
@@ -431,6 +439,9 @@ export function workItemRouteDefinitions(
431
439
  endpoint: "work-items/:id",
432
440
  method: "GET",
433
441
  policyKey: "work-items",
442
+ summary: "Get a work item",
443
+ description: "Return a single work item by ID.",
444
+ tags: ["work-items"],
434
445
  handler: ({ params }) => {
435
446
  const item = getWorkItem(params.id) ?? null;
436
447
  if (!item) {
@@ -445,6 +456,17 @@ export function workItemRouteDefinitions(
445
456
  endpoint: "work-items/:id",
446
457
  method: "PATCH",
447
458
  policyKey: "work-items",
459
+ summary: "Update a work item",
460
+ description:
461
+ "Partially update a work item's title, notes, status, or priority.",
462
+ tags: ["work-items"],
463
+ requestBody: z.object({
464
+ title: z.string(),
465
+ notes: z.string(),
466
+ status: z.string(),
467
+ priorityTier: z.number().int(),
468
+ sortIndex: z.number().int(),
469
+ }),
448
470
  handler: async ({ req, params }) => {
449
471
  const body = (await req.json()) as {
450
472
  title?: string;
@@ -490,6 +512,9 @@ export function workItemRouteDefinitions(
490
512
  endpoint: "work-items/:id/complete",
491
513
  method: "POST",
492
514
  policyKey: "work-items/complete",
515
+ summary: "Complete a work item",
516
+ description: "Transition a work item from awaiting_review to done.",
517
+ tags: ["work-items"],
493
518
  handler: ({ params }) => {
494
519
  const existing = getWorkItem(params.id);
495
520
  if (!existing) {
@@ -521,6 +546,9 @@ export function workItemRouteDefinitions(
521
546
  endpoint: "work-items/:id",
522
547
  method: "DELETE",
523
548
  policyKey: "work-items",
549
+ summary: "Delete a work item",
550
+ description: "Permanently remove a work item.",
551
+ tags: ["work-items"],
524
552
  handler: ({ params }) => {
525
553
  const existing = getWorkItem(params.id);
526
554
  if (!existing) {
@@ -537,6 +565,9 @@ export function workItemRouteDefinitions(
537
565
  endpoint: "work-items/:id/cancel",
538
566
  method: "POST",
539
567
  policyKey: "work-items/cancel",
568
+ summary: "Cancel a running work item",
569
+ description: "Abort a running work item and set its status to cancelled.",
570
+ tags: ["work-items"],
540
571
  handler: ({ params }) => {
541
572
  const workItem = getWorkItem(params.id);
542
573
  if (!workItem) {
@@ -577,6 +608,14 @@ export function workItemRouteDefinitions(
577
608
  endpoint: "work-items/:id/approve-permissions",
578
609
  method: "POST",
579
610
  policyKey: "work-items/approve-permissions",
611
+ summary: "Approve tool permissions",
612
+ description: "Pre-approve a set of tools for a work item before it runs.",
613
+ tags: ["work-items"],
614
+ requestBody: z.object({
615
+ approvedTools: z
616
+ .array(z.unknown())
617
+ .describe("Array of tool names to approve"),
618
+ }),
580
619
  handler: async ({ req, params }) => {
581
620
  const body = (await req.json()) as { approvedTools?: string[] };
582
621
  if (!Array.isArray(body.approvedTools)) {
@@ -602,6 +641,14 @@ export function workItemRouteDefinitions(
602
641
  endpoint: "work-items/:id/preflight",
603
642
  method: "POST",
604
643
  policyKey: "work-items/preflight",
644
+ summary: "Preflight check",
645
+ description: "Check tool permissions needed before running a work item.",
646
+ tags: ["work-items"],
647
+ responseBody: z.object({
648
+ id: z.string(),
649
+ success: z.boolean(),
650
+ permissions: z.object({}).passthrough(),
651
+ }),
605
652
  handler: async ({ params }) => {
606
653
  const result = await preflightWorkItem(params.id);
607
654
  if (!result.success) {
@@ -620,6 +667,10 @@ export function workItemRouteDefinitions(
620
667
  endpoint: "work-items/:id/run",
621
668
  method: "POST",
622
669
  policyKey: "work-items/run",
670
+ summary: "Run a work item",
671
+ description:
672
+ "Execute the task associated with a work item. Returns immediately; execution happens in the background.",
673
+ tags: ["work-items"],
623
674
  handler: async ({ params }) => {
624
675
  const workItem = getWorkItem(params.id);
625
676
  if (!workItem) {
@@ -782,6 +833,14 @@ export function workItemRouteDefinitions(
782
833
  endpoint: "work-items/:id/output",
783
834
  method: "GET",
784
835
  policyKey: "work-items/output",
836
+ summary: "Get work item output",
837
+ description: "Return the final output of a completed work item run.",
838
+ tags: ["work-items"],
839
+ responseBody: z.object({
840
+ id: z.string(),
841
+ success: z.boolean(),
842
+ output: z.object({}).passthrough(),
843
+ }),
785
844
  handler: ({ params }) => {
786
845
  try {
787
846
  const result = getWorkItemOutput(params.id);