@vellumai/assistant 0.11.9 → 0.11.10-dev.202609091114.7b6342c

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 (433) hide show
  1. package/ARCHITECTURE.md +2 -2
  2. package/Dockerfile +6 -12
  3. package/docs/architecture/memory.md +17 -9
  4. package/docs/guardian-request-flow.md +7 -4
  5. package/docs/runbook-trusted-contacts.md +13 -2
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +3 -1
  7. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/plan-credit.test.ts +51 -0
  8. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +2 -0
  9. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/desktop-presence.ts +11 -0
  10. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  11. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/plan-credit.ts +66 -0
  12. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +7 -0
  13. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +3 -1
  14. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/plan-credit.test.ts +51 -0
  15. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +2 -0
  16. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/desktop-presence.ts +11 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/plan-credit.ts +66 -0
  19. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/secret-detection.ts +7 -0
  20. package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +72 -0
  21. package/node_modules/@vellumai/service-contracts/package.json +3 -1
  22. package/node_modules/@vellumai/service-contracts/src/__tests__/plan-credit.test.ts +51 -0
  23. package/node_modules/@vellumai/service-contracts/src/__tests__/secret-detection.test.ts +2 -0
  24. package/node_modules/@vellumai/service-contracts/src/desktop-presence.ts +11 -0
  25. package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
  26. package/node_modules/@vellumai/service-contracts/src/plan-credit.ts +66 -0
  27. package/node_modules/@vellumai/service-contracts/src/secret-detection.ts +7 -0
  28. package/openapi.yaml +729 -63
  29. package/package.json +1 -1
  30. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +19 -21
  31. package/src/__tests__/assistant-event-hub-machine-name.test.ts +40 -0
  32. package/src/__tests__/assistant-event-hub.test.ts +45 -0
  33. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +0 -1
  34. package/src/__tests__/compactor-assistant-tail-preview.test.ts +111 -0
  35. package/src/__tests__/compactor-tail-resolution.test.ts +116 -0
  36. package/src/__tests__/computer-use-native-history.test.ts +30 -0
  37. package/src/__tests__/computer-use-window-schema.test.ts +24 -0
  38. package/src/__tests__/config-get-vision-flag.test.ts +36 -0
  39. package/src/__tests__/config-loader-validation-reset-notice.test.ts +47 -15
  40. package/src/__tests__/context-search-conversations-source.test.ts +27 -5
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +189 -195
  42. package/src/__tests__/conversation-agent-loop.test.ts +690 -50
  43. package/src/__tests__/conversation-fork-crud.test.ts +132 -45
  44. package/src/__tests__/conversation-fork-retrospective.test.ts +9 -7
  45. package/src/__tests__/conversation-lifecycle.test.ts +684 -10
  46. package/src/__tests__/conversation-pairing.test.ts +10 -9
  47. package/src/__tests__/conversation-runtime-assembly.test.ts +288 -1
  48. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -4
  49. package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +150 -0
  50. package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +161 -0
  51. package/src/__tests__/copy-composer-tc-templates.test.ts +2 -2
  52. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  53. package/src/__tests__/delivered-post-record.test.ts +107 -0
  54. package/src/__tests__/guardian-action-service-scope.test.ts +53 -0
  55. package/src/__tests__/guardian-question-mode.test.ts +88 -138
  56. package/src/__tests__/headless-browser-mode.test.ts +63 -13
  57. package/src/__tests__/heartbeat-disk-pressure.test.ts +2 -0
  58. package/src/__tests__/heartbeat-service.test.ts +118 -27
  59. package/src/__tests__/host-cu-proxy.test.ts +159 -1
  60. package/src/__tests__/host-proxy-interface.test.ts +15 -0
  61. package/src/__tests__/host-proxy-preactivation.test.ts +61 -0
  62. package/src/__tests__/injector-chain.test.ts +3 -3
  63. package/src/__tests__/injector-registry-order-guard.test.ts +1 -1
  64. package/src/__tests__/injector-v3-suppression.test.ts +587 -45
  65. package/src/__tests__/llm-schema.test.ts +70 -2
  66. package/src/__tests__/logger.test.ts +33 -32
  67. package/src/__tests__/mcp-config-secret-boundary.test.ts +2 -0
  68. package/src/__tests__/memory-config-file-parity.test.ts +18 -0
  69. package/src/__tests__/memory-retrieval-hook.test.ts +53 -3
  70. package/src/__tests__/messages-read-boundary-guard.test.ts +0 -1
  71. package/src/__tests__/messaging-send-tool.test.ts +251 -128
  72. package/src/__tests__/messaging-shared.test.ts +19 -0
  73. package/src/__tests__/notification-decision-fallback.test.ts +98 -76
  74. package/src/__tests__/notification-decision-strategy.test.ts +10 -38
  75. package/src/__tests__/notification-events-source-context-probe.test.ts +211 -0
  76. package/src/__tests__/openai-responses-prompt-cache.test.ts +7 -2
  77. package/src/__tests__/openai-responses-provider.test.ts +42 -0
  78. package/src/__tests__/outlook-messaging-provider.test.ts +9 -0
  79. package/src/__tests__/platform-callback-registration.test.ts +162 -3
  80. package/src/__tests__/plugin-api-webhook-url.test.ts +66 -4
  81. package/src/__tests__/plugin-import-boundary-guard.test.ts +1 -0
  82. package/src/__tests__/pricing.test.ts +29 -0
  83. package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +63 -3
  84. package/src/__tests__/scheduler-result-notification.test.ts +209 -0
  85. package/src/__tests__/strip-memory-injections.test.ts +75 -27
  86. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +4 -13
  87. package/src/acp/auto-install.test.ts +35 -1
  88. package/src/acp/resolve-agent.test.ts +3 -3
  89. package/src/agent/loop.ts +116 -45
  90. package/src/api/constants/personality-sliders.test.ts +21 -0
  91. package/src/api/constants/personality-sliders.ts +64 -0
  92. package/src/api/events/contact-record-request.ts +16 -4
  93. package/src/api/events/contact-request.ts +10 -1
  94. package/src/api/index.ts +12 -0
  95. package/src/api/responses/memory-v3-selection-log.ts +64 -13
  96. package/src/api/surfaces.ts +6 -5
  97. package/src/background-wake/next-wake.test.ts +13 -0
  98. package/src/background-wake/next-wake.ts +4 -2
  99. package/src/calls/__tests__/voice-session-bridge.test.ts +166 -0
  100. package/src/calls/__tests__/voice-turn-sight-hold.test.ts +301 -0
  101. package/src/calls/voice-session-bridge.ts +162 -1
  102. package/src/calls/voice-triage-escalate.ts +1 -1
  103. package/src/channels/types.ts +14 -0
  104. package/src/cli/__tests__/catalog-search-help.test.ts +1 -0
  105. package/src/cli/__tests__/contacts-address-prompt-cli.test.ts +701 -0
  106. package/src/cli/__tests__/contacts-record-prompt-cli.test.ts +276 -15
  107. package/src/cli/__tests__/roadmap-cli.test.ts +313 -0
  108. package/src/cli/__tests__/run-assistant-command.ts +59 -20
  109. package/src/cli/commands/__tests__/conversations-slack.test.ts +12 -0
  110. package/src/cli/commands/__tests__/plugins-search.test.ts +81 -0
  111. package/src/cli/commands/config.help.ts +3 -1
  112. package/src/cli/commands/contacts.help.ts +179 -12
  113. package/src/cli/commands/contacts.ts +442 -47
  114. package/src/cli/commands/memory/worker.ts +1 -1
  115. package/src/cli/commands/platform/__tests__/credits-format.test.ts +152 -0
  116. package/src/cli/commands/platform/__tests__/credits.test.ts +20 -0
  117. package/src/cli/commands/platform/credits-format.ts +108 -0
  118. package/src/cli/commands/platform/index.help.ts +33 -11
  119. package/src/cli/commands/platform/index.ts +7 -18
  120. package/src/cli/commands/plugins.help.ts +4 -1
  121. package/src/cli/commands/plugins.ts +2 -1
  122. package/src/cli/commands/roadmap.help.ts +171 -0
  123. package/src/cli/commands/roadmap.ts +358 -0
  124. package/src/cli/index.help.ts +2 -0
  125. package/src/cli/lib/__tests__/cli-output.test.ts +5 -0
  126. package/src/cli/lib/cli-output.ts +6 -6
  127. package/src/cli/program.ts +2 -0
  128. package/src/config/__tests__/webhook-routing.test.ts +42 -0
  129. package/src/config/acp-defaults.test.ts +3 -3
  130. package/src/config/acp-defaults.ts +5 -5
  131. package/src/config/bundled-skills/acp/SKILL.md +8 -11
  132. package/src/config/bundled-skills/computer-use/SKILL.md +21 -0
  133. package/src/config/bundled-skills/computer-use/TOOLS.json +6 -0
  134. package/src/config/bundled-skills/contacts/SKILL.md +61 -11
  135. package/src/config/bundled-skills/messaging/SKILL.md +2 -0
  136. package/src/config/bundled-skills/messaging/TOOLS.json +4 -4
  137. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +160 -36
  138. package/src/config/bundled-skills/messaging/tools/shared.ts +6 -0
  139. package/src/config/bundled-skills/schedule/SKILL.md +10 -2
  140. package/src/config/bundled-skills/screen-annotation/SKILL.md +80 -0
  141. package/src/config/bundled-skills/screen-annotation/TOOLS.json +73 -0
  142. package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +74 -0
  143. package/src/config/bundled-skills/screen-annotation/tools/screen-clear-marks.ts +33 -0
  144. package/src/config/bundled-skills/screen-annotation/tools/screen-point-at.ts +25 -0
  145. package/src/config/env-registry.ts +2 -0
  146. package/src/config/feature-flag-registry.json +39 -7
  147. package/src/config/input-modalities.test.ts +71 -0
  148. package/src/config/input-modalities.ts +95 -0
  149. package/src/config/loader.ts +2 -0
  150. package/src/config/schemas/__tests__/memory-v3.test.ts +58 -15
  151. package/src/config/schemas/heartbeat.ts +3 -3
  152. package/src/config/schemas/llm.ts +47 -10
  153. package/src/config/schemas/memory-storage.test.ts +39 -0
  154. package/src/config/schemas/memory-storage.ts +21 -0
  155. package/src/config/schemas/memory-v3.ts +73 -46
  156. package/src/config/schemas/services.ts +18 -0
  157. package/src/config/webhook-routing.ts +12 -2
  158. package/src/context/compactor.ts +12 -13
  159. package/src/context/strip-injections.ts +29 -25
  160. package/src/context/token-estimator.ts +2 -2
  161. package/src/daemon/__tests__/embedding-reconcile.test.ts +77 -0
  162. package/src/daemon/__tests__/lifecycle-embedding-reconcile.test.ts +3 -0
  163. package/src/daemon/__tests__/turn-actor.test.ts +68 -0
  164. package/src/daemon/conversation-agent-loop-handlers.ts +152 -22
  165. package/src/daemon/conversation-agent-loop.ts +19 -3
  166. package/src/daemon/conversation-messaging.ts +3 -2
  167. package/src/daemon/conversation-runtime-assembly.ts +191 -69
  168. package/src/daemon/conversation-surfaces.ts +32 -62
  169. package/src/daemon/conversation.ts +153 -46
  170. package/src/daemon/embedding-reconcile.ts +64 -1
  171. package/src/daemon/handlers/config-embeddings.ts +31 -0
  172. package/src/daemon/host-cu-proxy.ts +66 -50
  173. package/src/daemon/host-cu-target.ts +139 -0
  174. package/src/daemon/host-proxy-preactivation.ts +29 -1
  175. package/src/daemon/mcp-reload-service.ts +6 -0
  176. package/src/daemon/providers-setup.ts +5 -26
  177. package/src/daemon/trust-context.ts +1 -1
  178. package/src/daemon/turn-actor.ts +46 -0
  179. package/src/heartbeat/heartbeat-service.ts +72 -13
  180. package/src/inbound/platform-callback-registration.ts +77 -11
  181. package/src/inbound/pod-webhook-claim.ts +35 -0
  182. package/src/inbound/velay-webhooks-gate.ts +20 -0
  183. package/src/ipc/gateway-client.ts +91 -0
  184. package/src/live-voice/__tests__/live-voice-photo.test.ts +174 -0
  185. package/src/live-voice/__tests__/live-voice-session-guardian.test.ts +127 -0
  186. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +42 -2
  187. package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +81 -0
  188. package/src/live-voice/__tests__/protocol.test.ts +67 -0
  189. package/src/live-voice/live-voice-connection.ts +14 -1
  190. package/src/live-voice/live-voice-photo.ts +109 -4
  191. package/src/live-voice/live-voice-session-manager.ts +10 -1
  192. package/src/live-voice/live-voice-session.ts +196 -31
  193. package/src/live-voice/protocol.ts +134 -1
  194. package/src/mcp/__tests__/reload-signal-emission.test.ts +54 -0
  195. package/src/mcp/__tests__/reload-signal.test.ts +57 -0
  196. package/src/mcp/__tests__/startup.test.ts +150 -0
  197. package/src/mcp/reload-signal.ts +50 -0
  198. package/src/mcp/startup.ts +101 -0
  199. package/src/messaging/providers/outlook/adapter.ts +2 -13
  200. package/src/messaging/providers/outlook/client.ts +13 -1
  201. package/src/messaging/providers/outlook/types.ts +3 -0
  202. package/src/messaging/providers/telegram-bot/adapter.ts +9 -2
  203. package/src/messaging/providers/telegram-bot/client.ts +8 -2
  204. package/src/messaging/providers/whatsapp/adapter.ts +9 -2
  205. package/src/messaging/providers/whatsapp/client.ts +8 -2
  206. package/src/notifications/AGENTS.md +5 -0
  207. package/src/notifications/README.md +21 -7
  208. package/src/notifications/__tests__/assistant-reply-producer.test.ts +3 -10
  209. package/src/notifications/__tests__/background-failure-signal.test.ts +96 -0
  210. package/src/notifications/__tests__/broadcaster.test.ts +171 -7
  211. package/src/notifications/__tests__/decision-engine.test.ts +79 -6
  212. package/src/notifications/__tests__/edit-notification.test.ts +59 -0
  213. package/src/notifications/__tests__/resolve-visible-in-source.test.ts +96 -0
  214. package/src/notifications/__tests__/schedule-result-producer.test.ts +397 -0
  215. package/src/notifications/access-request-copy.ts +5 -5
  216. package/src/notifications/approval-card-data.ts +29 -13
  217. package/src/notifications/assistant-reply-producer.ts +0 -4
  218. package/src/notifications/background-failure-signal.ts +13 -1
  219. package/src/notifications/broadcaster.ts +65 -2
  220. package/src/notifications/conversation-pairing.ts +263 -155
  221. package/src/notifications/copy-composer.ts +38 -22
  222. package/src/notifications/decision-engine.ts +89 -61
  223. package/src/notifications/delivered-post-record.ts +81 -0
  224. package/src/notifications/deliveries-store.ts +8 -1
  225. package/src/notifications/edit-notification.ts +16 -0
  226. package/src/notifications/events-store.ts +68 -2
  227. package/src/notifications/guardian-question-mode.ts +24 -232
  228. package/src/notifications/home-feed-side-effect.ts +1 -0
  229. package/src/notifications/notification-utils.ts +13 -5
  230. package/src/notifications/resolve-visible-in-source.ts +56 -0
  231. package/src/notifications/schedule-result-producer.ts +275 -0
  232. package/src/notifications/signal.ts +5 -0
  233. package/src/oauth/__tests__/seed-providers-managed.test.ts +82 -0
  234. package/src/oauth/seed-providers.ts +201 -3
  235. package/src/onboarding/onboarding-events-store.ts +14 -8
  236. package/src/persistence/conversation-crud.ts +68 -19
  237. package/src/persistence/embeddings/embedding-backend.test.ts +162 -0
  238. package/src/persistence/embeddings/embedding-backend.ts +148 -11
  239. package/src/persistence/embeddings/embedding-gemini.test.ts +328 -9
  240. package/src/persistence/embeddings/embedding-gemini.ts +211 -27
  241. package/src/persistence/embeddings/embedding-openai.test.ts +113 -0
  242. package/src/persistence/embeddings/embedding-openai.ts +55 -4
  243. package/src/persistence/embeddings/embedding-types.test.ts +24 -0
  244. package/src/persistence/embeddings/embedding-types.ts +25 -1
  245. package/src/persistence/job-utils.ts +6 -2
  246. package/src/persistence/message-metadata.ts +20 -0
  247. package/src/persistence/migrations/283-memory-v3-selections-message-id-and-sections.ts +2 -1
  248. package/src/persistence/migrations/376-notification-deliveries-canonical-message-id.ts +37 -0
  249. package/src/persistence/schema/memory-injection.ts +31 -9
  250. package/src/persistence/schema/notifications.ts +8 -0
  251. package/src/persistence/steps.ts +2 -0
  252. package/src/plugin-api/index.ts +2 -1
  253. package/src/plugin-api/vision-support.test.ts +81 -0
  254. package/src/plugin-api/vision-support.ts +57 -10
  255. package/src/plugin-api/webhook-url.ts +18 -4
  256. package/src/plugins/AGENTS.md +1 -1
  257. package/src/plugins/defaults/channel/injectors.ts +3 -1
  258. package/src/plugins/defaults/injector-order.ts +2 -2
  259. package/src/plugins/defaults/memory/AGENTS.md +263 -30
  260. package/src/plugins/defaults/memory/__tests__/conversation-memory-orphan-sweep.test.ts +4 -0
  261. package/src/plugins/defaults/memory/__tests__/conversation-memory-purge.test.ts +6 -0
  262. package/src/plugins/defaults/memory/__tests__/db-memory-attach.test.ts +9 -3
  263. package/src/plugins/defaults/memory/__tests__/memory-log-stores-degraded.test.ts +3 -7
  264. package/src/plugins/defaults/memory/__tests__/relocated-memory-test-rows.ts +18 -0
  265. package/src/plugins/defaults/memory/context-search/sources/conversations.ts +16 -2
  266. package/src/plugins/defaults/memory/conversation-memory-purge.ts +2 -0
  267. package/src/plugins/defaults/memory/fork-conversation-memory.ts +13 -13
  268. package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +19 -12
  269. package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +17 -15
  270. package/src/plugins/defaults/memory/graph-topology/build-memory-graph.ts +1 -1
  271. package/src/plugins/defaults/memory/hooks/__tests__/post-compact.test.ts +121 -0
  272. package/src/plugins/defaults/memory/hooks/init.ts +10 -0
  273. package/src/plugins/defaults/memory/hooks/injection-metadata.ts +100 -0
  274. package/src/plugins/defaults/memory/hooks/post-compact.ts +19 -7
  275. package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +11 -83
  276. package/src/plugins/defaults/memory/injectors.ts +4 -4
  277. package/src/plugins/defaults/memory/job-handlers.ts +4 -1
  278. package/src/plugins/defaults/memory/jobs/__tests__/embed-concept-page.test.ts +1 -0
  279. package/src/plugins/defaults/memory/jobs/embed-concept-page.ts +11 -3
  280. package/src/plugins/defaults/memory/memory-db.ts +4 -0
  281. package/src/plugins/defaults/memory/memory-marker.ts +28 -17
  282. package/src/plugins/defaults/memory/src/__tests__/memory-v2-simulate-route.test.ts +0 -1
  283. package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +20 -0
  284. package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +0 -1
  285. package/src/plugins/defaults/memory/src/memory-worker-routes.ts +3 -1
  286. package/src/plugins/defaults/memory/substrate/__tests__/cli-command-store.test.ts +1 -1
  287. package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +66 -0
  288. package/src/plugins/defaults/memory/substrate/__tests__/injected-block-slugs.test.ts +345 -0
  289. package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +0 -1
  290. package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +170 -1
  291. package/src/plugins/defaults/memory/substrate/__tests__/reembed-job.test.ts +1 -0
  292. package/src/plugins/defaults/memory/substrate/capability-slugs.ts +53 -0
  293. package/src/plugins/defaults/memory/substrate/cli-command-store.ts +4 -18
  294. package/src/plugins/defaults/memory/substrate/consolidation-job.ts +40 -1
  295. package/src/plugins/defaults/memory/substrate/ingest.ts +11 -9
  296. package/src/plugins/defaults/memory/substrate/injected-block-slugs.ts +426 -42
  297. package/src/plugins/defaults/memory/substrate/page-index.ts +5 -4
  298. package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +127 -14
  299. package/src/plugins/defaults/memory/substrate/skill-store.ts +1 -18
  300. package/src/plugins/defaults/memory/v2/__tests__/backfill-jobs.test.ts +1 -0
  301. package/src/plugins/defaults/memory/v2/__tests__/injection.test.ts +2 -8
  302. package/src/plugins/defaults/memory/v2/__tests__/router.test.ts +0 -1
  303. package/src/plugins/defaults/memory/v2/injection.ts +4 -4
  304. package/src/plugins/defaults/memory/v3/__tests__/capabilities.test.ts +16 -0
  305. package/src/plugins/defaults/memory/v3/__tests__/carry-integration.test.ts +1004 -319
  306. package/src/plugins/defaults/memory/v3/__tests__/dense.test.ts +227 -4
  307. package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +836 -263
  308. package/src/plugins/defaults/memory/v3/__tests__/live-integration.test.ts +65 -38
  309. package/src/plugins/defaults/memory/v3/__tests__/maintain-job.test.ts +354 -3
  310. package/src/plugins/defaults/memory/v3/__tests__/orchestrate.test.ts +688 -104
  311. package/src/plugins/defaults/memory/v3/__tests__/overlong-sections.test.ts +109 -0
  312. package/src/plugins/defaults/memory/v3/__tests__/page-content.test.ts +144 -0
  313. package/src/plugins/defaults/memory/v3/__tests__/plugin-schema.test.ts +534 -0
  314. package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +449 -0
  315. package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +265 -27
  316. package/src/plugins/defaults/memory/v3/__tests__/render-injection.test.ts +34 -117
  317. package/src/plugins/defaults/memory/v3/__tests__/section-dense-store.test.ts +491 -4
  318. package/src/plugins/defaults/memory/v3/__tests__/section-needle.test.ts +124 -1
  319. package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +264 -4
  320. package/src/plugins/defaults/memory/v3/__tests__/selection-log-store.test.ts +560 -22
  321. package/src/plugins/defaults/memory/v3/__tests__/shadow-integration.test.ts +40 -33
  322. package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +718 -63
  323. package/src/plugins/defaults/memory/v3/__tests__/types.test.ts +83 -2
  324. package/src/plugins/defaults/memory/v3/candidate-match.ts +1 -1
  325. package/src/plugins/defaults/memory/v3/capabilities.ts +90 -14
  326. package/src/plugins/defaults/memory/v3/card.test.ts +15 -16
  327. package/src/plugins/defaults/memory/v3/card.ts +16 -15
  328. package/src/plugins/defaults/memory/v3/dense.ts +10 -0
  329. package/src/plugins/defaults/memory/v3/ever-injected-store.test.ts +643 -140
  330. package/src/plugins/defaults/memory/v3/ever-injected-store.ts +462 -212
  331. package/src/plugins/defaults/memory/v3/hot-set.test.ts +2 -2
  332. package/src/plugins/defaults/memory/v3/injector.ts +366 -278
  333. package/src/plugins/defaults/memory/v3/learned-edges.test.ts +2 -2
  334. package/src/plugins/defaults/memory/v3/maintain-job.ts +299 -73
  335. package/src/plugins/defaults/memory/v3/orchestrate.ts +327 -214
  336. package/src/plugins/defaults/memory/v3/overlong-sections.ts +83 -0
  337. package/src/plugins/defaults/memory/v3/page-content.ts +72 -92
  338. package/src/plugins/defaults/memory/v3/plugin-schema.ts +346 -0
  339. package/src/plugins/defaults/memory/v3/pool-log-store.ts +291 -0
  340. package/src/plugins/defaults/memory/v3/pool-select.test.ts +2 -2
  341. package/src/plugins/defaults/memory/v3/pool-select.ts +160 -60
  342. package/src/plugins/defaults/memory/v3/prune.test.ts +1042 -329
  343. package/src/plugins/defaults/memory/v3/prune.ts +433 -270
  344. package/src/plugins/defaults/memory/v3/rare-term-lane.test.ts +269 -0
  345. package/src/plugins/defaults/memory/v3/rare-term-lane.ts +141 -0
  346. package/src/plugins/defaults/memory/v3/render-injection.ts +43 -82
  347. package/src/plugins/defaults/memory/v3/section-dense-store.ts +316 -28
  348. package/src/plugins/defaults/memory/v3/section-needle.ts +145 -11
  349. package/src/plugins/defaults/memory/v3/sections.ts +103 -16
  350. package/src/plugins/defaults/memory/v3/selection-log-store.ts +243 -78
  351. package/src/plugins/defaults/memory/v3/shadow-plugin.ts +189 -68
  352. package/src/plugins/defaults/memory/v3/tuning-profile.ts +6 -0
  353. package/src/plugins/defaults/memory/v3/types.ts +169 -23
  354. package/src/plugins/defaults/memory/v3-eval/__tests__/eval-packets.test.ts +21 -4
  355. package/src/plugins/defaults/memory/v3-eval/eval-packets.ts +77 -26
  356. package/src/plugins/types.ts +14 -0
  357. package/src/providers/__tests__/connection-model-compat.test.ts +1 -0
  358. package/src/providers/__tests__/provider-secret-catalog.test.ts +4 -0
  359. package/src/providers/__tests__/xml-tool-call-salvage.test.ts +172 -0
  360. package/src/providers/inline-audio-support.test.ts +34 -0
  361. package/src/providers/inline-audio-support.ts +27 -0
  362. package/src/providers/model-catalog.ts +102 -0
  363. package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +51 -0
  364. package/src/providers/openai/__tests__/chat-completions-provider-xml-salvage.test.ts +291 -0
  365. package/src/providers/openai/chat-completions-provider.ts +114 -14
  366. package/src/providers/openai/codex-models.ts +1 -0
  367. package/src/providers/openai/responses-provider.ts +65 -15
  368. package/src/providers/opencode/client.test.ts +24 -0
  369. package/src/providers/opencode/client.ts +6 -1
  370. package/src/providers/provider-secret-catalog.ts +11 -1
  371. package/src/providers/retry.ts +6 -0
  372. package/src/providers/vellum/client.ts +14 -1
  373. package/src/providers/vellum/personality-directions.test.ts +163 -0
  374. package/src/providers/vellum/personality-directions.ts +120 -0
  375. package/src/providers/xml-tool-call-salvage.ts +212 -0
  376. package/src/runtime/AGENTS.md +7 -2
  377. package/src/runtime/__tests__/background-job-runner.test.ts +115 -0
  378. package/src/runtime/__tests__/guardian-form-registry.test.ts +0 -32
  379. package/src/runtime/__tests__/host-observe.test.ts +44 -0
  380. package/src/runtime/assistant-event-hub.ts +14 -9
  381. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -4
  382. package/src/runtime/auth/__tests__/route-policy.test.ts +65 -0
  383. package/src/runtime/auth/same-actor.ts +4 -3
  384. package/src/runtime/background-job-runner.ts +10 -0
  385. package/src/runtime/channel-reply-delivery.ts +1 -162
  386. package/src/runtime/guardian-action-service.ts +20 -1
  387. package/src/runtime/host-observe.ts +56 -6
  388. package/src/runtime/http-server.ts +35 -0
  389. package/src/runtime/outbound-post-reconciliation.ts +183 -0
  390. package/src/runtime/routes/__tests__/contact-address-prompt-route.test.ts +231 -0
  391. package/src/runtime/routes/__tests__/contact-record-prompt-route.test.ts +59 -3
  392. package/src/runtime/routes/__tests__/guardian-form-routes.test.ts +85 -0
  393. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +24 -0
  394. package/src/runtime/routes/__tests__/platform-credits-routes.test.ts +239 -0
  395. package/src/runtime/routes/__tests__/roadmap-routes.test.ts +469 -0
  396. package/src/runtime/routes/__tests__/watch-routes.test.ts +70 -1
  397. package/src/runtime/routes/__tests__/webhook-routes.test.ts +83 -0
  398. package/src/runtime/routes/contact-prompt-routes.ts +177 -28
  399. package/src/runtime/routes/conversation-query-routes.ts +53 -11
  400. package/src/runtime/routes/events-routes.ts +13 -3
  401. package/src/runtime/routes/guardian-action-routes.ts +7 -0
  402. package/src/runtime/routes/guardian-form-routes.ts +6 -4
  403. package/src/runtime/routes/heartbeat-routes.ts +41 -53
  404. package/src/runtime/routes/index.ts +2 -2
  405. package/src/runtime/routes/integrations/a2a.ts +12 -3
  406. package/src/runtime/routes/integrations/vercel.ts +13 -3
  407. package/src/runtime/routes/platform-routes.ts +175 -23
  408. package/src/runtime/routes/roadmap-routes.ts +744 -0
  409. package/src/runtime/routes/watch-routes.ts +50 -0
  410. package/src/runtime/routes/webhook-routes.ts +24 -2
  411. package/src/runtime/web-presence.ts +13 -9
  412. package/src/schedule/__tests__/schedule-timezone.test.ts +21 -3
  413. package/src/schedule/__tests__/worker-mcp-tools.test.ts +88 -0
  414. package/src/schedule/schedule-timezone.ts +25 -0
  415. package/src/schedule/scheduler.ts +19 -0
  416. package/src/schedule/worker.ts +119 -2
  417. package/src/telemetry/__tests__/live-voice-funnel.test.ts +32 -0
  418. package/src/telemetry/live-voice-funnel.ts +33 -0
  419. package/src/tools/acp/list-agents.test.ts +1 -1
  420. package/src/tools/browser/__tests__/browser-status.test.ts +3 -0
  421. package/src/tools/browser/browser-execution.ts +117 -53
  422. package/src/tools/browser/browser-status-constants.ts +6 -6
  423. package/src/tools/capability-offer.test.ts +87 -0
  424. package/src/tools/capability-offer.ts +93 -0
  425. package/src/tools/computer-use/definitions.ts +6 -0
  426. package/src/tools/computer-use/skill-proxy-bridge.ts +10 -0
  427. package/src/tools/terminal/safe-env.ts +2 -0
  428. package/src/watch/__tests__/watch-retro.test.ts +11 -10
  429. package/src/watch/__tests__/watch-session-manager.test.ts +75 -1
  430. package/src/watch/watch-retro.ts +10 -9
  431. package/src/watch/watch-session-manager.ts +17 -0
  432. package/src/runtime/routes/__tests__/sight-frame-routes.test.ts +0 -487
  433. package/src/runtime/routes/sight-frame-routes.ts +0 -136
package/ARCHITECTURE.md CHANGED
@@ -145,7 +145,7 @@ All guardian approval decisions, regardless of how they arrive, route through a
145
145
 
146
146
  **Text fallback path (always available):**
147
147
 
148
- - Every prompt includes a `requestCode` (6-char alphanumeric). Guardians can reply with `<requestCode> approve` or `<requestCode> reject` on any channel.
148
+ - Every prompt includes a `requestCode` (6-char alphanumeric). Guardians can reply with `<requestCode> approve` or `<requestCode> reject` on any channel. The instruction saying so travels only in the card's `plainTextFallback`, which a transport appends when it sends text without buttons; composed copy and `questionText` never carry it.
149
149
  - `access_request` prompts carry the request-code verify/trust/reject/block directive in the card's `plainTextFallback`, appended by a transport only when it sends text without buttons; the `"open invite flow"` phrase for starting the Trusted Contacts invite flow stays in the text on every surface because nothing offers a button for it.
150
150
  - `pending_question` prompts (voice-originated) support `<requestCode> <your answer>` for free-text answers.
151
151
  - The `routeGuardianReply` router processes text replies through a priority-ordered pipeline: callback parsing -> request code parsing -> NL classification. All paths converge on `applyGuardianDecision`.
@@ -199,7 +199,7 @@ The guardian request system provides a channel-agnostic, unified domain for all
199
199
 
200
200
  4. **Deterministic API (prompt listing and decision endpoints):** Desktop clients and API consumers use `GET /v1/guardian-actions/pending` and `POST /v1/guardian-actions/decision` (HTTP). These endpoints surface guardian requests alongside legacy pending interactions and channel approval records, with deduplication to avoid double-rendering.
201
201
 
202
- 5. **Buttons first, text fallback:** All request kinds (`tool_approval`, `pending_question`, `access_request`) are rendered as structured button cards when displayed in macOS guardian conversations. Each prompt also embeds deterministic text fallback instructions (request-code-based approve/reject directives, and for `access_request` the "open invite flow" phrase) so text-based channels and manual fallback always work. Code-only messages (just a request code without decision text) return clarification instead of auto-approving. Disambiguation with multiple pending requests stays fail-closed — no auto-resolve when the target is ambiguous.
202
+ 5. **Buttons first, text fallback:** All request kinds (`tool_approval`, `pending_question`, `access_request`) are rendered as structured button cards when displayed in macOS guardian conversations. Each card also carries deterministic text fallback instructions (request-code-based directives, and for `access_request` the "open invite flow" phrase) in its `plainTextFallback`, appended by a transport only when it sends text without buttons, so text-based channels and manual fallback always work without the instructions duplicating the buttons. Code-only messages (just a request code without decision text) return clarification instead of auto-approving. Disambiguation with multiple pending requests stays fail-closed: no auto-resolve when the target is ambiguous.
203
203
 
204
204
  **Resolver registry:** Kind-specific resolvers (`src/approvals/guardian-request-resolvers.ts`) handle side effects after CAS resolution. Built-in resolvers: `tool_approval` (channel/desktop approval path), `pending_question` (voice call question path), and `access_request` (trusted-contact verification session creation). New request kinds register resolvers without touching the core primitive.
205
205
 
package/Dockerfile CHANGED
@@ -30,19 +30,10 @@ COPY assistant/package.json ./assistant/
30
30
  COPY assistant/src/api/package.json ./assistant/src/api/
31
31
  COPY --parents packages/*/package.json ./
32
32
 
33
- RUN bun install --frozen-lockfile --ignore-scripts \
33
+ RUN --mount=type=cache,id=bun-install-cache,target=/root/.bun/install/cache \
34
+ bun install --frozen-lockfile --ignore-scripts \
34
35
  --filter='./assistant' --filter='./assistant/src/api'
35
36
 
36
- # The repo-root .dockerignore decides which skills (and files) ship.
37
- COPY skills ./skills
38
- RUN set -eu; for pkg in /app/skills/*/package.json; do \
39
- [ -e "$pkg" ] || continue; \
40
- dir="$(dirname "$pkg")"; \
41
- echo "Installing dependencies for $dir"; \
42
- (cd "$dir" && (bun install --frozen-lockfile 2>/dev/null || bun install)); \
43
- done
44
-
45
-
46
37
  # Final stage
47
38
  FROM debian:trixie-slim@sha256:4ffb3a1511099754cddc70eb1b12e50ffdb67619aa0ab6c13fcd800a78ef7c7a AS runner
48
39
 
@@ -114,7 +105,7 @@ RUN apt-get update && apt-get install -y \
114
105
  COPY --from=builder /root/.bun/bin/bun /usr/local/bin/bun
115
106
  RUN ln -sf /usr/local/bin/bun /usr/local/bin/bunx
116
107
 
117
- COPY --from=builder /app /app
108
+ COPY --link --from=builder /app /app
118
109
 
119
110
  # Copy source after installing dependencies for better layer caching.
120
111
  COPY packages/avatar-catalog /app/packages/avatar-catalog
@@ -131,6 +122,9 @@ COPY packages/twilio-client /app/packages/twilio-client
131
122
 
132
123
  COPY assistant /app/assistant
133
124
 
125
+ # The repo-root .dockerignore decides which skills (and files) ship.
126
+ COPY skills /app/skills
127
+
134
128
  # Materialize the git-ignored offline plugin manifest from its canonical source.
135
129
  # `assistant/src/cli/lib/plugin-catalog-local.ts` statically imports this JSON
136
130
  # (loaded even when platform features are enabled), so it must exist in the image
@@ -13,7 +13,7 @@ An assistant runs exactly one memory tier, derived by `memoryTier()` in
13
13
  | Tier | Selected when | Injection source |
14
14
  | ----- | -------------------------------------------- | ------------------------------------------------ |
15
15
  | `off` | `memory.enabled === false` | none |
16
- | `v3` | `memory.v3.live === true` | v3 lanes + card (`v3/injector.ts`) |
16
+ | `v3` | `memory.v3.live === true` | v3 lanes + sections (`v3/injector.ts`) |
17
17
  | `v2` | `memory.v2.enabled === true` and v3 not live | v2 activation/router engine (`v2/`) |
18
18
  | `v1` | otherwise | PKB `<knowledge_base>` block (`v1/pkb/`, legacy) |
19
19
 
@@ -172,12 +172,20 @@ Ingested pages carry provenance frontmatter with distinct consumers:
172
172
 
173
173
  - **v3 (live)**: per-turn lane selection over concept pages (dense/sparse
174
174
  retrieval via `substrate/sim.ts` over the concept-page collection,
175
- learned edges, entity/hot/fresh/core sets) rendered as the `<memory>`
176
- card by `v3/injector.ts`. Frozen cards stay on historical user messages
177
- so the provider prefix stays cacheable. Each turn's `<memory_spotlight>`
178
- stays on the user message that was sent with it. A new spotlight is
179
- added only on the new tail, so older messages are not rewritten. The
180
- static `<info>` block
175
+ learned edges, entity/hot/fresh/core sets). The injection unit is the
176
+ SECTION: the selector pool lists a page once per matched section, and
177
+ `v3/injector.ts` renders each selected page's selected sections (its lead
178
+ when it was selected with none) into a `<memory>` block,
179
+ net-new sections only, deduped per `(page, section)` through
180
+ `memory_v3_injected_sections` and bounded by a recency prune valve with no
181
+ lane exemptions. Section bodies are backslash-escaped where a line would
182
+ read as a block header, so page text never forges a section boundary, and a
183
+ pruned section is dropped from any pointer that named it. Frozen section
184
+ blocks stay on historical user messages so
185
+ the provider prefix stays cacheable. Re-selected sections that are already
186
+ resident are listed, paths only, in a per-turn `<memory_pointer>` block
187
+ that stays on the user message it was sent with; a new pointer is added
188
+ only on the new tail, so older messages are not rewritten. The static `<info>` block
181
189
  (`substrate/static-context.ts`: essentials/threads/recent/buffer) also
182
190
  injects whenever the substrate is active.
183
191
  - **v2 (transitional)**: activation/router engine in `v2/`
@@ -192,8 +200,8 @@ Ingested pages carry provenance frontmatter with distinct consumers:
192
200
 
193
201
  The live voice front door does not await current-turn memory retrieval. Its
194
202
  prompt hook skips legacy graph retrieval, and both v3 injectors skip
195
- orchestration for `voiceFrontDoor`. Frozen cards from prior turns and the static
196
- substrate context remain available. When the answer depends on a saved personal
203
+ orchestration for `voiceFrontDoor`. Frozen sections from prior turns and the
204
+ static substrate context remain available. When the answer depends on a saved personal
197
205
  fact that is absent from that context, the front-door rule escalates instead of
198
206
  guessing.
199
207
 
@@ -131,10 +131,13 @@ routing after a restart even though the card is absent from the model's history.
131
131
  Two instruction modes exist per request kind (`notifications/guardian-question-mode.ts`):
132
132
  **approval** ("CODE approve" / approve–reject buttons) and **answer**
133
133
  ("CODE <your answer>" / option buttons). `pending_question` is answer-mode.
134
- Whether a channel's copy carries the "CODE <reply>" instruction at all is one
135
- rule, `guardianCopyCarriesReplyMechanics`: text chats do, while the vellum
136
- bell and banner, the platform push, and Slack approval cards act through the
137
- card and have it stripped.
134
+ The "CODE <reply>" instruction lives in exactly one place: the
135
+ `plainTextFallback` the broadcaster builds beside the card's actions. Composed
136
+ copy never carries it (the decision engine strips a model's echo of it on
137
+ every channel), and a transport appends it only when it sends text without
138
+ buttons: a rich delivery that failed, or a request with no actions to draw,
139
+ which is how an option-less voice question stays answerable. Every surface
140
+ with buttons, and the bell, banner, and push, show the ask alone.
138
141
 
139
142
  ## Worked example: `ask_question` on a channel
140
143
 
@@ -146,9 +146,13 @@ sqlite3 "file:$GW_DB?mode=ro" \
146
146
 
147
147
  ## 3b. Contact writes
148
148
 
149
- All write operations on contacts are gateway-owned. Use the gateway CLI, not
149
+ Contact writes are gateway-owned, and they reach it two ways. An operator ACL
150
+ write (a risk ceiling) goes through the gateway CLI directly, not through
150
151
  `assistant contacts`. From the host, run the same command via
151
- `vellum exec --service gateway --`.
152
+ `vellum exec --service gateway --`. A write that changes the contact graph
153
+ itself (the record, or a channel binding) goes through `assistant contacts`,
154
+ which opens a form in the guardian's app and writes nothing until they submit
155
+ it; the gateway still performs the write.
152
156
 
153
157
  ```bash
154
158
  gateway contacts set-risk-threshold <contactId> --threshold high
@@ -156,6 +160,13 @@ gateway contacts set-risk-threshold <contactId> --threshold high
156
160
 
157
161
  `inherit` clears the risk ceiling so the contact follows room and trust-class settings. Stored vocabulary is `none`, `low`, `medium`, or `high`.
158
162
 
163
+ Binding an address to a contact goes through the guardian instead of the operator. The command below opens a form in the guardian's app naming that contact, and the gateway writes the channel only when they submit it. An address already held by a different contact is refused, naming that contact.
164
+
165
+ ```bash
166
+ assistant contacts channels add <contactId> --channel <type> --address <addr>
167
+ assistant contacts channels add <contactId> --channel email --address alice@example.com
168
+ ```
169
+
159
170
  ## 4. Force-Revoke a Trusted Contact
160
171
 
161
172
  ### Via CLI (preferred)
@@ -10,6 +10,7 @@
10
10
  "./conversation-handle": "./src/conversation-handle.ts",
11
11
  "./client-metadata": "./src/client-metadata.ts",
12
12
  "./credential-rpc": "./src/credential-rpc.ts",
13
+ "./desktop-presence": "./src/desktop-presence.ts",
13
14
  "./ingress": "./src/ingress.ts",
14
15
  "./no-response": "./src/no-response.ts",
15
16
  "./remote-web-pairing": "./src/remote-web-pairing.ts",
@@ -25,7 +26,8 @@
25
26
  "./error": "./src/error.ts",
26
27
  "./reactions": "./src/reactions.ts",
27
28
  "./guardian-requests": "./src/guardian-requests.ts",
28
- "./platform-credential": "./src/platform-credential.ts"
29
+ "./platform-credential": "./src/platform-credential.ts",
30
+ "./plan-credit": "./src/plan-credit.ts"
29
31
  },
30
32
  "scripts": {
31
33
  "typecheck": "bunx tsc --noEmit",
@@ -0,0 +1,51 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ extraCreditUsd,
5
+ planCreditUsedFraction,
6
+ usageGrantRatio,
7
+ } from "../plan-credit.js";
8
+
9
+ describe("usageGrantRatio", () => {
10
+ test("reads the used share and clamps to 0..1", () => {
11
+ expect(usageGrantRatio(20, 9.1)).toBeCloseTo(0.545);
12
+ expect(usageGrantRatio(20, 0)).toBe(1);
13
+ expect(usageGrantRatio(20, 25)).toBe(0);
14
+ });
15
+
16
+ test("has no reading without both figures or with nothing granted", () => {
17
+ expect(usageGrantRatio(null, 5)).toBeNull();
18
+ expect(usageGrantRatio(20, null)).toBeNull();
19
+ expect(usageGrantRatio(0, 0)).toBeNull();
20
+ });
21
+ });
22
+
23
+ describe("planCreditUsedFraction", () => {
24
+ test("prefers the ratio whatever the plan", () => {
25
+ expect(planCreditUsedFraction(20, 9.1, "base")).toBeCloseTo(0.545);
26
+ expect(planCreditUsedFraction(20, 9.1, null)).toBeCloseTo(0.545);
27
+ });
28
+
29
+ test("treats a Pro plan with nothing granted as fully spent", () => {
30
+ expect(planCreditUsedFraction(0, 0, "pro")).toBe(1);
31
+ expect(planCreditUsedFraction(0, null, "pro")).toBe(1);
32
+ });
33
+
34
+ test("gives a base or unknown plan with nothing granted no reading", () => {
35
+ expect(planCreditUsedFraction(0, 0, "base")).toBeNull();
36
+ expect(planCreditUsedFraction(0, 0, null)).toBeNull();
37
+ expect(planCreditUsedFraction(null, null, "pro")).toBeNull();
38
+ });
39
+ });
40
+
41
+ describe("extraCreditUsd", () => {
42
+ test("nets unused grant credit out of the balance to the cent", () => {
43
+ expect(extraCreditUsd(42.17, 9.1)).toBe(33.07);
44
+ expect(extraCreditUsd(34.65, 9.1)).toBe(25.55);
45
+ });
46
+
47
+ test("never goes below zero", () => {
48
+ expect(extraCreditUsd(5, 9.1)).toBe(0);
49
+ expect(extraCreditUsd(-2.5, 0)).toBe(0);
50
+ });
51
+ });
@@ -38,6 +38,8 @@ const FIXTURES: Record<string, string> = {
38
38
  "GitLab Token": `glpat-${filler(20)}`,
39
39
  "Stripe Secret Key": `sk_live_${filler(24)}`,
40
40
  "Stripe Restricted Key": `rk_live_${filler(24)}`,
41
+ "Link Access Token": `liwltoken_${filler(24)}`,
42
+ "Link Refresh Token": `liwlrefresh_${filler(24)}`,
41
43
  "Slack Bot Token": `xoxb-1234567890-1234567890-${filler(24)}`,
42
44
  "Slack User Token": `xoxp-1234567890-1234567890-1234567890-${"0123456789abcdef".repeat(2)}`,
43
45
  "Slack App Token": "xapp-1-abc123-4567-defghij890",
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The presence states a desktop client may report. Single runtime source for
3
+ * the daemon's stored type, the client route's wire enum, and the Electron
4
+ * presence monitor.
5
+ *
6
+ * Member order is load-bearing: the route feeds this tuple to `z.enum`, so
7
+ * reordering rewrites the generated `assistant/openapi.yaml`.
8
+ */
9
+ export const DESKTOP_PRESENCE_STATES = ["active", "idle", "away"] as const;
10
+
11
+ export type DesktopPresenceState = (typeof DESKTOP_PRESENCE_STATES)[number];
@@ -28,6 +28,7 @@ export * from "./rpc.js";
28
28
  export * from "./trust-rules.js";
29
29
  export * from "./ingress.js";
30
30
  export * from "./no-response.js";
31
+ export * from "./plan-credit.js";
31
32
  export * from "./platform-credential.js";
32
33
  export * from "./remote-web-pairing.js";
33
34
  export * from "./twilio-ingress.js";
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Plan-credit derivations shared by the assistant runtime's `platform credits`
3
+ * route and the web client's usage meter, so both read the billing summary's
4
+ * grant figures the same way and can never quote different readings.
5
+ */
6
+
7
+ export type BillingPlanId = "base" | "pro";
8
+
9
+ function clamp01(value: number): number {
10
+ if (!Number.isFinite(value) || value < 0) {
11
+ return 0;
12
+ }
13
+ return value > 1 ? 1 : value;
14
+ }
15
+
16
+ /**
17
+ * How much of the usage credit an account was granted it has already used:
18
+ * the granted total less what is still unused, over that total. The initial
19
+ * $5 grant burns 0 to 100% as it is spent, and a further grant grows the total
20
+ * so the bar drops back. Null when nothing was ever granted, or when the
21
+ * platform reports neither figure, which has no honest reading rather than a
22
+ * full or empty bar.
23
+ */
24
+ export function usageGrantRatio(
25
+ totalUsd: number | null,
26
+ availableUsd: number | null,
27
+ ): number | null {
28
+ if (totalUsd == null || availableUsd == null || totalUsd <= 0) {
29
+ return null;
30
+ }
31
+ return clamp01((totalUsd - availableUsd) / totalUsd);
32
+ }
33
+
34
+ /**
35
+ * The plan-credit reading with the plan's fallback applied. The grant figures
36
+ * count only unexpired grants, so a Pro subscription whose grants total
37
+ * nothing has used or expired everything it was granted (a full bar, with
38
+ * nothing scheduled to refill it), while a base plan in that position was
39
+ * never granted anything (no reading). An unknown plan gets the ratio when
40
+ * the figures allow one and no fallback otherwise.
41
+ */
42
+ export function planCreditUsedFraction(
43
+ totalUsd: number | null,
44
+ availableUsd: number | null,
45
+ planId: BillingPlanId | null,
46
+ ): number | null {
47
+ const ratio = usageGrantRatio(totalUsd, availableUsd);
48
+ if (ratio != null) {
49
+ return ratio;
50
+ }
51
+ if (planId === "pro" && totalUsd != null && totalUsd <= 0) {
52
+ return 1;
53
+ }
54
+ return null;
55
+ }
56
+
57
+ /**
58
+ * Credit bought or earned on top of the plan-included grants: the balance less
59
+ * whatever is still unused on those grants, never below zero, to the cent.
60
+ */
61
+ export function extraCreditUsd(
62
+ balanceUsd: number,
63
+ availableUsd: number,
64
+ ): number {
65
+ return Math.round(Math.max(0, balanceUsd - availableUsd) * 100) / 100;
66
+ }
@@ -143,6 +143,13 @@ export const PREFIX_PATTERNS: SecretPrefixPattern[] = [
143
143
  // -- Stripe --
144
144
  { label: "Stripe Secret Key", regex: /sk_live_[A-Za-z0-9]{24,}/ },
145
145
  { label: "Stripe Restricted Key", regex: /rk_live_[A-Za-z0-9]{24,}/ },
146
+ // Link (Stripe's wallet) issues prefixed OAuth tokens rather than opaque
147
+ // ones, and its own docs tell users to export the access token as
148
+ // LINK_ACCESS_TOKEN to drive link-cli, so these do get pasted by hand. A
149
+ // leaked one can spend from the wallet. The `lwlpk_` client id is public
150
+ // (it ships in the CLI bundle) and is deliberately not matched here.
151
+ { label: "Link Access Token", regex: /liwltoken_[A-Za-z0-9]{16,}/ },
152
+ { label: "Link Refresh Token", regex: /liwlrefresh_[A-Za-z0-9]{16,}/ },
146
153
 
147
154
  // -- Slack --
148
155
  {
@@ -10,6 +10,7 @@
10
10
  "./conversation-handle": "./src/conversation-handle.ts",
11
11
  "./client-metadata": "./src/client-metadata.ts",
12
12
  "./credential-rpc": "./src/credential-rpc.ts",
13
+ "./desktop-presence": "./src/desktop-presence.ts",
13
14
  "./ingress": "./src/ingress.ts",
14
15
  "./no-response": "./src/no-response.ts",
15
16
  "./remote-web-pairing": "./src/remote-web-pairing.ts",
@@ -25,7 +26,8 @@
25
26
  "./error": "./src/error.ts",
26
27
  "./reactions": "./src/reactions.ts",
27
28
  "./guardian-requests": "./src/guardian-requests.ts",
28
- "./platform-credential": "./src/platform-credential.ts"
29
+ "./platform-credential": "./src/platform-credential.ts",
30
+ "./plan-credit": "./src/plan-credit.ts"
29
31
  },
30
32
  "scripts": {
31
33
  "typecheck": "bunx tsc --noEmit",
@@ -0,0 +1,51 @@
1
+ import { describe, expect, test } from "bun:test";
2
+
3
+ import {
4
+ extraCreditUsd,
5
+ planCreditUsedFraction,
6
+ usageGrantRatio,
7
+ } from "../plan-credit.js";
8
+
9
+ describe("usageGrantRatio", () => {
10
+ test("reads the used share and clamps to 0..1", () => {
11
+ expect(usageGrantRatio(20, 9.1)).toBeCloseTo(0.545);
12
+ expect(usageGrantRatio(20, 0)).toBe(1);
13
+ expect(usageGrantRatio(20, 25)).toBe(0);
14
+ });
15
+
16
+ test("has no reading without both figures or with nothing granted", () => {
17
+ expect(usageGrantRatio(null, 5)).toBeNull();
18
+ expect(usageGrantRatio(20, null)).toBeNull();
19
+ expect(usageGrantRatio(0, 0)).toBeNull();
20
+ });
21
+ });
22
+
23
+ describe("planCreditUsedFraction", () => {
24
+ test("prefers the ratio whatever the plan", () => {
25
+ expect(planCreditUsedFraction(20, 9.1, "base")).toBeCloseTo(0.545);
26
+ expect(planCreditUsedFraction(20, 9.1, null)).toBeCloseTo(0.545);
27
+ });
28
+
29
+ test("treats a Pro plan with nothing granted as fully spent", () => {
30
+ expect(planCreditUsedFraction(0, 0, "pro")).toBe(1);
31
+ expect(planCreditUsedFraction(0, null, "pro")).toBe(1);
32
+ });
33
+
34
+ test("gives a base or unknown plan with nothing granted no reading", () => {
35
+ expect(planCreditUsedFraction(0, 0, "base")).toBeNull();
36
+ expect(planCreditUsedFraction(0, 0, null)).toBeNull();
37
+ expect(planCreditUsedFraction(null, null, "pro")).toBeNull();
38
+ });
39
+ });
40
+
41
+ describe("extraCreditUsd", () => {
42
+ test("nets unused grant credit out of the balance to the cent", () => {
43
+ expect(extraCreditUsd(42.17, 9.1)).toBe(33.07);
44
+ expect(extraCreditUsd(34.65, 9.1)).toBe(25.55);
45
+ });
46
+
47
+ test("never goes below zero", () => {
48
+ expect(extraCreditUsd(5, 9.1)).toBe(0);
49
+ expect(extraCreditUsd(-2.5, 0)).toBe(0);
50
+ });
51
+ });
@@ -38,6 +38,8 @@ const FIXTURES: Record<string, string> = {
38
38
  "GitLab Token": `glpat-${filler(20)}`,
39
39
  "Stripe Secret Key": `sk_live_${filler(24)}`,
40
40
  "Stripe Restricted Key": `rk_live_${filler(24)}`,
41
+ "Link Access Token": `liwltoken_${filler(24)}`,
42
+ "Link Refresh Token": `liwlrefresh_${filler(24)}`,
41
43
  "Slack Bot Token": `xoxb-1234567890-1234567890-${filler(24)}`,
42
44
  "Slack User Token": `xoxp-1234567890-1234567890-1234567890-${"0123456789abcdef".repeat(2)}`,
43
45
  "Slack App Token": "xapp-1-abc123-4567-defghij890",
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The presence states a desktop client may report. Single runtime source for
3
+ * the daemon's stored type, the client route's wire enum, and the Electron
4
+ * presence monitor.
5
+ *
6
+ * Member order is load-bearing: the route feeds this tuple to `z.enum`, so
7
+ * reordering rewrites the generated `assistant/openapi.yaml`.
8
+ */
9
+ export const DESKTOP_PRESENCE_STATES = ["active", "idle", "away"] as const;
10
+
11
+ export type DesktopPresenceState = (typeof DESKTOP_PRESENCE_STATES)[number];
@@ -28,6 +28,7 @@ export * from "./rpc.js";
28
28
  export * from "./trust-rules.js";
29
29
  export * from "./ingress.js";
30
30
  export * from "./no-response.js";
31
+ export * from "./plan-credit.js";
31
32
  export * from "./platform-credential.js";
32
33
  export * from "./remote-web-pairing.js";
33
34
  export * from "./twilio-ingress.js";
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Plan-credit derivations shared by the assistant runtime's `platform credits`
3
+ * route and the web client's usage meter, so both read the billing summary's
4
+ * grant figures the same way and can never quote different readings.
5
+ */
6
+
7
+ export type BillingPlanId = "base" | "pro";
8
+
9
+ function clamp01(value: number): number {
10
+ if (!Number.isFinite(value) || value < 0) {
11
+ return 0;
12
+ }
13
+ return value > 1 ? 1 : value;
14
+ }
15
+
16
+ /**
17
+ * How much of the usage credit an account was granted it has already used:
18
+ * the granted total less what is still unused, over that total. The initial
19
+ * $5 grant burns 0 to 100% as it is spent, and a further grant grows the total
20
+ * so the bar drops back. Null when nothing was ever granted, or when the
21
+ * platform reports neither figure, which has no honest reading rather than a
22
+ * full or empty bar.
23
+ */
24
+ export function usageGrantRatio(
25
+ totalUsd: number | null,
26
+ availableUsd: number | null,
27
+ ): number | null {
28
+ if (totalUsd == null || availableUsd == null || totalUsd <= 0) {
29
+ return null;
30
+ }
31
+ return clamp01((totalUsd - availableUsd) / totalUsd);
32
+ }
33
+
34
+ /**
35
+ * The plan-credit reading with the plan's fallback applied. The grant figures
36
+ * count only unexpired grants, so a Pro subscription whose grants total
37
+ * nothing has used or expired everything it was granted (a full bar, with
38
+ * nothing scheduled to refill it), while a base plan in that position was
39
+ * never granted anything (no reading). An unknown plan gets the ratio when
40
+ * the figures allow one and no fallback otherwise.
41
+ */
42
+ export function planCreditUsedFraction(
43
+ totalUsd: number | null,
44
+ availableUsd: number | null,
45
+ planId: BillingPlanId | null,
46
+ ): number | null {
47
+ const ratio = usageGrantRatio(totalUsd, availableUsd);
48
+ if (ratio != null) {
49
+ return ratio;
50
+ }
51
+ if (planId === "pro" && totalUsd != null && totalUsd <= 0) {
52
+ return 1;
53
+ }
54
+ return null;
55
+ }
56
+
57
+ /**
58
+ * Credit bought or earned on top of the plan-included grants: the balance less
59
+ * whatever is still unused on those grants, never below zero, to the cent.
60
+ */
61
+ export function extraCreditUsd(
62
+ balanceUsd: number,
63
+ availableUsd: number,
64
+ ): number {
65
+ return Math.round(Math.max(0, balanceUsd - availableUsd) * 100) / 100;
66
+ }
@@ -143,6 +143,13 @@ export const PREFIX_PATTERNS: SecretPrefixPattern[] = [
143
143
  // -- Stripe --
144
144
  { label: "Stripe Secret Key", regex: /sk_live_[A-Za-z0-9]{24,}/ },
145
145
  { label: "Stripe Restricted Key", regex: /rk_live_[A-Za-z0-9]{24,}/ },
146
+ // Link (Stripe's wallet) issues prefixed OAuth tokens rather than opaque
147
+ // ones, and its own docs tell users to export the access token as
148
+ // LINK_ACCESS_TOKEN to drive link-cli, so these do get pasted by hand. A
149
+ // leaked one can spend from the wallet. The `lwlpk_` client id is public
150
+ // (it ships in the CLI bundle) and is deliberately not matched here.
151
+ { label: "Link Access Token", regex: /liwltoken_[A-Za-z0-9]{16,}/ },
152
+ { label: "Link Refresh Token", regex: /liwlrefresh_[A-Za-z0-9]{16,}/ },
146
153
 
147
154
  // -- Slack --
148
155
  {
@@ -401,6 +401,78 @@ export type ContactsIdentitySnapshotIpcResponse = z.infer<
401
401
  typeof ContactsIdentitySnapshotIpcResponseSchema
402
402
  >;
403
403
 
404
+ // ── webhook ingress routes ───────────────────────────────────────────────────
405
+ // The gateway owns the registry of subpaths this assistant answers from
406
+ // outside; the daemon claims, drops, and inspects them over IPC. Both sides
407
+ // read these schemas so a change to the stored row has to travel through the
408
+ // contract.
409
+
410
+ /**
411
+ * Feature flag gating the whole webhook ingress registry. The gateway reads it
412
+ * to decide whether to accept a claim and how to advertise its allowed paths,
413
+ * and the daemon reads it to decide whether to claim at all, so both sides have
414
+ * to name the same key.
415
+ */
416
+ export const WebhookIngressRouteSchema = z.object({
417
+ path: z.string(),
418
+ type: z.string(),
419
+ source: z.string().nullable(),
420
+ match: z.literal("exact"),
421
+ createdAt: z.number(),
422
+ lastRegisteredAt: z.number(),
423
+ });
424
+
425
+ export type WebhookIngressRoute = z.infer<typeof WebhookIngressRouteSchema>;
426
+
427
+ export const RegisterWebhookRouteIpcParamsSchema = z.object({
428
+ /** Exact subpath, leading slash included, under `/webhooks/`. */
429
+ path: z.string().min(1),
430
+ type: z.string().min(1),
431
+ source: z.string().nullish(),
432
+ });
433
+
434
+ export type RegisterWebhookRouteIpcParams = z.infer<
435
+ typeof RegisterWebhookRouteIpcParamsSchema
436
+ >;
437
+
438
+ /**
439
+ * A refusal is a normal result rather than an error: the daemon reads
440
+ * `disabled` as "this assistant is not serving its own webhooks" and falls
441
+ * back to the platform's callback routes.
442
+ */
443
+ export const RegisterWebhookRouteIpcResponseSchema = z.union([
444
+ z.object({ disabled: z.literal(true) }),
445
+ z.object({ disabled: z.literal(false), route: WebhookIngressRouteSchema }),
446
+ ]);
447
+
448
+ export type RegisterWebhookRouteIpcResponse = z.infer<
449
+ typeof RegisterWebhookRouteIpcResponseSchema
450
+ >;
451
+
452
+ export const UnregisterWebhookRouteIpcParamsSchema = z.object({
453
+ path: z.string().min(1),
454
+ });
455
+
456
+ export type UnregisterWebhookRouteIpcParams = z.infer<
457
+ typeof UnregisterWebhookRouteIpcParamsSchema
458
+ >;
459
+
460
+ export const UnregisterWebhookRouteIpcResponseSchema = z.object({
461
+ removed: z.boolean(),
462
+ });
463
+
464
+ export type UnregisterWebhookRouteIpcResponse = z.infer<
465
+ typeof UnregisterWebhookRouteIpcResponseSchema
466
+ >;
467
+
468
+ export const ListWebhookRoutesIpcResponseSchema = z.object({
469
+ routes: z.array(WebhookIngressRouteSchema),
470
+ });
471
+
472
+ export type ListWebhookRoutesIpcResponse = z.infer<
473
+ typeof ListWebhookRoutesIpcResponseSchema
474
+ >;
475
+
404
476
  // ── classify_risk ────────────────────────────────────────────────────────────
405
477
  // Risk classification is gateway-owned; the assistant sends one request per
406
478
  // tool invocation and reads the whole answer back. The gateway validates the
@@ -10,6 +10,7 @@
10
10
  "./conversation-handle": "./src/conversation-handle.ts",
11
11
  "./client-metadata": "./src/client-metadata.ts",
12
12
  "./credential-rpc": "./src/credential-rpc.ts",
13
+ "./desktop-presence": "./src/desktop-presence.ts",
13
14
  "./ingress": "./src/ingress.ts",
14
15
  "./no-response": "./src/no-response.ts",
15
16
  "./remote-web-pairing": "./src/remote-web-pairing.ts",
@@ -25,7 +26,8 @@
25
26
  "./error": "./src/error.ts",
26
27
  "./reactions": "./src/reactions.ts",
27
28
  "./guardian-requests": "./src/guardian-requests.ts",
28
- "./platform-credential": "./src/platform-credential.ts"
29
+ "./platform-credential": "./src/platform-credential.ts",
30
+ "./plan-credit": "./src/plan-credit.ts"
29
31
  },
30
32
  "scripts": {
31
33
  "typecheck": "bunx tsc --noEmit",