@vellumai/assistant 0.8.12 → 0.9.0-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (442) hide show
  1. package/AGENTS.md +0 -14
  2. package/ARCHITECTURE.md +45 -45
  3. package/README.md +1 -1
  4. package/bun.lock +200 -154
  5. package/docs/architecture/integrations.md +3 -3
  6. package/docs/architecture/memory.md +2 -2
  7. package/docs/architecture/security.md +10 -10
  8. package/docs/runbook-trusted-contacts.md +12 -12
  9. package/docs/skills.md +6 -6
  10. package/docs/workflows-testing.md +221 -0
  11. package/docs/workflows.md +510 -0
  12. package/examples/plugins/echo/README.md +5 -5
  13. package/knip.json +2 -0
  14. package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +105 -0
  15. package/node_modules/@vellumai/gateway-client/src/index.ts +12 -0
  16. package/openapi.yaml +7197 -5708
  17. package/package.json +8 -4
  18. package/scripts/generate-openapi.ts +66 -114
  19. package/src/__tests__/access-request-seed-content-blocks.test.ts +213 -0
  20. package/src/__tests__/adaptive-thinking-repair.test.ts +32 -3
  21. package/src/__tests__/agent-loop-output-hooks.test.ts +183 -0
  22. package/src/__tests__/agent-loop-regrowth-guard.test.ts +506 -0
  23. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
  24. package/src/__tests__/agent-wake-override-profile.test.ts +77 -0
  25. package/src/__tests__/app-compiler.test.ts +7 -1
  26. package/src/__tests__/app-dir-path-guard.test.ts +27 -3
  27. package/src/__tests__/app-executors.test.ts +43 -0
  28. package/src/__tests__/approval-cascade.test.ts +0 -5
  29. package/src/__tests__/approval-routes-http.test.ts +91 -0
  30. package/src/__tests__/assistant-stream-state.test.ts +107 -0
  31. package/src/__tests__/browser-fill-credential.test.ts +3 -3
  32. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +1 -1
  33. package/src/__tests__/compaction-events.test.ts +63 -7
  34. package/src/__tests__/compaction-trail-store.test.ts +74 -1
  35. package/src/__tests__/compaction.benchmark.test.ts +63 -41
  36. package/src/__tests__/compactor-low-watermark-cut.test.ts +349 -0
  37. package/src/__tests__/context-window-manager-compact-retry.test.ts +64 -0
  38. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -5
  39. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -5
  40. package/src/__tests__/conversation-history-web-search.test.ts +7 -0
  41. package/src/__tests__/conversation-process-callsite.test.ts +0 -5
  42. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -5
  43. package/src/__tests__/conversation-queue.test.ts +0 -5
  44. package/src/__tests__/conversation-slash-queue.test.ts +0 -5
  45. package/src/__tests__/conversation-slash-unknown.test.ts +0 -5
  46. package/src/__tests__/conversation-speed-override.test.ts +0 -5
  47. package/src/__tests__/conversation-surfaces-data-persist.test.ts +97 -0
  48. package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
  49. package/src/__tests__/conversation-usage.test.ts +2 -0
  50. package/src/__tests__/conversation-workspace-injection.test.ts +0 -5
  51. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -5
  52. package/src/__tests__/credential-broker-browser-fill.test.ts +2 -2
  53. package/src/__tests__/credential-broker-server-use.test.ts +2 -2
  54. package/src/__tests__/credential-broker.test.ts +1 -1
  55. package/src/__tests__/credential-prompt-route.test.ts +417 -0
  56. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  57. package/src/__tests__/credential-vault.test.ts +37 -0
  58. package/src/__tests__/db-schedule-syntax-migration.test.ts +24 -0
  59. package/src/__tests__/dynamic-page-surface.test.ts +219 -0
  60. package/src/__tests__/empty-state-greeting-cache.test.ts +94 -0
  61. package/src/__tests__/gateway-flag-listener.test.ts +24 -7
  62. package/src/__tests__/guardian-action-sweep.test.ts +56 -219
  63. package/src/__tests__/guardian-routing-invariants.test.ts +138 -0
  64. package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
  65. package/src/__tests__/list-messages-hidden-metadata.test.ts +99 -0
  66. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +87 -1
  67. package/src/__tests__/llm-resolver.test.ts +115 -0
  68. package/src/__tests__/managed-profile-guard.test.ts +6 -5
  69. package/src/__tests__/max-tokens-continue-hook.test.ts +184 -0
  70. package/src/__tests__/media-generate-image.test.ts +20 -9
  71. package/src/__tests__/mock-gateway-ipc.ts +23 -0
  72. package/src/__tests__/model-intents.test.ts +1 -1
  73. package/src/__tests__/normalize-onboarding.test.ts +26 -0
  74. package/src/__tests__/notification-decision-strategy.test.ts +4 -2
  75. package/src/__tests__/notification-telegram-adapter.test.ts +21 -3
  76. package/src/__tests__/pending-interactions-resolved-event.test.ts +62 -0
  77. package/src/__tests__/post-turn-tool-result-truncation.test.ts +72 -18
  78. package/src/__tests__/require-fresh-approval.test.ts +425 -1
  79. package/src/__tests__/resolve-app-id.test.ts +56 -0
  80. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -0
  81. package/src/__tests__/schedule-routes-workflow-validation.test.ts +408 -0
  82. package/src/__tests__/schedule-routes.test.ts +257 -4
  83. package/src/__tests__/schedule-store.test.ts +60 -0
  84. package/src/__tests__/schedule-tools.test.ts +247 -2
  85. package/src/__tests__/skill-execute-input.test.ts +85 -0
  86. package/src/__tests__/skill-secret-handling-guard.test.ts +21 -20
  87. package/src/__tests__/skills.test.ts +3 -3
  88. package/src/__tests__/slack-app-setup-skill-regression.test.ts +1 -1
  89. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  90. package/src/__tests__/subagent-tool-gate-mode.test.ts +547 -0
  91. package/src/__tests__/system-prompt.test.ts +1 -1
  92. package/src/__tests__/task-progress-nudge-hook.test.ts +372 -0
  93. package/src/__tests__/task-scheduler.test.ts +299 -0
  94. package/src/__tests__/tool-approval-seed-content-blocks.test.ts +209 -0
  95. package/src/__tests__/tool-result-spool.test.ts +3 -1
  96. package/src/__tests__/workspace-migration-102-preserve-heartbeat-enabled-for-existing-workspaces.test.ts +181 -0
  97. package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +174 -0
  98. package/src/agent/compaction-circuit.ts +11 -0
  99. package/src/agent/loop.ts +181 -12
  100. package/src/api/constants/call-sites.ts +12 -0
  101. package/src/api/events/assistant-thinking-delta.ts +10 -0
  102. package/src/api/events/usage-update.ts +7 -0
  103. package/src/api/index.ts +4 -1
  104. package/src/api/responses/memory-v3-selection-log.ts +18 -11
  105. package/src/approvals/approval-primitive.ts +2 -2
  106. package/src/background-wake/background-wake-routes.test.ts +5 -2
  107. package/src/bundler/compiler-tools.ts +1 -1
  108. package/src/bundler/package-resolver.ts +0 -1
  109. package/src/calls/call-domain.ts +1 -1
  110. package/src/calls/guardian-action-sweep.ts +16 -93
  111. package/src/cli/AGENTS.md +4 -0
  112. package/src/cli/commands/__tests__/schedules.test.ts +430 -1
  113. package/src/cli/commands/credentials.ts +28 -24
  114. package/src/cli/commands/image-generation.ts +23 -9
  115. package/src/cli/commands/notifications.ts +1 -1
  116. package/src/cli/commands/plugins.ts +89 -46
  117. package/src/cli/commands/schedules.ts +384 -11
  118. package/src/cli/lib/__tests__/inspect-plugin.test.ts +69 -5
  119. package/src/cli/lib/__tests__/install-from-github.test.ts +15 -0
  120. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +81 -4
  121. package/src/cli/lib/inspect-plugin.ts +62 -1
  122. package/src/cli/lib/install-from-github.ts +52 -5
  123. package/src/cli/lib/upgrade-plugin.ts +18 -0
  124. package/src/config/__tests__/workflows-schema.test.ts +60 -0
  125. package/src/config/bundled-skills/acp/SKILL.md +2 -2
  126. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  127. package/src/config/bundled-skills/app-builder/tools/app-create.ts +6 -1
  128. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +7 -1
  129. package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +7 -1
  130. package/src/config/bundled-skills/app-builder/tools/app-update.ts +10 -1
  131. package/src/config/bundled-skills/image-studio/SKILL.md +66 -19
  132. package/src/config/bundled-skills/image-studio/TOOLS.json +1 -6
  133. package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +22 -3
  134. package/src/config/bundled-skills/personal-page/SKILL.md +57 -0
  135. package/src/config/bundled-skills/personal-page/TOOLS.json +27 -0
  136. package/src/config/bundled-skills/personal-page/tools/app-refresh.ts +17 -0
  137. package/src/config/bundled-skills/schedule/SKILL.md +7 -2
  138. package/src/config/bundled-skills/schedule/TOOLS.json +48 -4
  139. package/src/config/bundled-skills/workflows/SKILL.md +214 -0
  140. package/src/config/bundled-skills/workflows/TOOLS.json +84 -0
  141. package/src/config/bundled-skills/workflows/tools/manage-workflows.ts +12 -0
  142. package/src/config/bundled-skills/workflows/tools/run-workflow.ts +12 -0
  143. package/src/config/bundled-tool-registry.ts +14 -2
  144. package/src/config/call-site-defaults.ts +5 -0
  145. package/src/config/feature-flag-registry.json +12 -4
  146. package/src/config/llm-context-resolution.ts +8 -0
  147. package/src/config/llm-resolver.ts +30 -0
  148. package/src/config/preloaded-apps/personal-page/src/components/About.tsx +22 -0
  149. package/src/config/preloaded-apps/personal-page/src/components/App.tsx +16 -0
  150. package/src/config/preloaded-apps/personal-page/src/components/Features.tsx +77 -0
  151. package/src/config/preloaded-apps/personal-page/src/components/Hero.tsx +57 -0
  152. package/src/config/preloaded-apps/personal-page/src/components/Pending.tsx +28 -0
  153. package/src/config/preloaded-apps/personal-page/src/components/animations.tsx +234 -0
  154. package/src/config/preloaded-apps/personal-page/src/components/icons.tsx +48 -0
  155. package/src/config/preloaded-apps/personal-page/src/components/media.ts +16 -0
  156. package/src/config/preloaded-apps/personal-page/src/index.html +20 -0
  157. package/src/config/preloaded-apps/personal-page/src/main.tsx +7 -0
  158. package/src/config/preloaded-apps/personal-page/src/profile-data.ts +82 -0
  159. package/src/config/preloaded-apps/personal-page/src/styles.css +759 -0
  160. package/src/config/schema.ts +2 -0
  161. package/src/config/schemas/call-site-catalog.ts +7 -0
  162. package/src/config/schemas/heartbeat.ts +4 -1
  163. package/src/config/schemas/llm.ts +33 -26
  164. package/src/config/schemas/memory-retrospective.ts +19 -0
  165. package/src/config/schemas/platform.ts +8 -0
  166. package/src/config/schemas/services.ts +5 -2
  167. package/src/config/schemas/workflows.ts +42 -0
  168. package/src/config/skills.ts +3 -3
  169. package/src/context/compactor.ts +273 -39
  170. package/src/context/post-turn-tool-result-truncation.ts +23 -6
  171. package/src/context/tool-result-spool.ts +12 -17
  172. package/src/credential-execution/executable-discovery.ts +1 -1
  173. package/src/credential-execution/process-manager.ts +37 -3
  174. package/src/credential-execution/prompted-credential.ts +205 -0
  175. package/src/daemon/conversation-agent-loop-handlers.ts +14 -0
  176. package/src/daemon/conversation-process.ts +11 -2
  177. package/src/daemon/conversation-surfaces.ts +167 -3
  178. package/src/daemon/conversation-tool-setup.ts +103 -26
  179. package/src/daemon/conversation-usage.ts +2 -0
  180. package/src/daemon/conversation.ts +115 -11
  181. package/src/daemon/handlers/shared.ts +26 -14
  182. package/src/daemon/host-cu-proxy.ts +15 -12
  183. package/src/daemon/host-file-proxy.ts +15 -12
  184. package/src/daemon/host-transfer-proxy.ts +30 -24
  185. package/src/daemon/lifecycle.ts +40 -3
  186. package/src/daemon/message-protocol.ts +3 -0
  187. package/src/daemon/message-types/messages.ts +2 -10
  188. package/src/daemon/message-types/workflows.ts +49 -0
  189. package/src/daemon/parse-actual-tokens-from-error.test.ts +62 -1
  190. package/src/daemon/parse-actual-tokens-from-error.ts +43 -4
  191. package/src/daemon/process-message.ts +6 -0
  192. package/src/daemon/tool-setup-types.ts +57 -0
  193. package/src/daemon/wake-conversation-ops.ts +18 -0
  194. package/src/heartbeat/heartbeat-run-store.ts +8 -2
  195. package/src/home/feed-types.ts +1 -1
  196. package/src/ipc/gateway-flag-listener.ts +28 -6
  197. package/src/mcp/mcp-auth-state.ts +8 -20
  198. package/src/media/__tests__/image-models.test.ts +57 -0
  199. package/src/media/image-models.ts +66 -0
  200. package/src/memory/__tests__/auto-analysis-enqueue.test.ts +38 -0
  201. package/src/memory/__tests__/find-most-recent-retrospective-for.test.ts +12 -2
  202. package/src/memory/__tests__/memory-retrospective-job.test.ts +911 -34
  203. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +227 -5
  204. package/src/memory/__tests__/memory-retrospective-state.test.ts +195 -0
  205. package/src/memory/__tests__/preloaded-apps.test.ts +85 -0
  206. package/src/memory/auto-analysis-enqueue.ts +14 -1
  207. package/src/memory/compaction-log-store-clickhouse.ts +6 -4
  208. package/src/memory/conversation-crud.ts +9 -2
  209. package/src/memory/conversation-disk-view.ts +1 -1
  210. package/src/memory/conversation-queries.ts +22 -7
  211. package/src/memory/db-init.ts +20 -0
  212. package/src/memory/db-maintenance.ts +16 -0
  213. package/src/memory/embedding-runtime-manager.ts +1 -1
  214. package/src/memory/llm-request-log-source-clickhouse.ts +112 -14
  215. package/src/memory/llm-request-log-source-local.ts +19 -1
  216. package/src/memory/llm-request-log-source.ts +35 -6
  217. package/src/memory/llm-request-log-store.ts +90 -2
  218. package/src/memory/memory-retrospective-constants.ts +9 -0
  219. package/src/memory/memory-retrospective-enqueue.ts +3 -6
  220. package/src/memory/memory-retrospective-fork-boundary.ts +94 -0
  221. package/src/memory/memory-retrospective-job.ts +500 -208
  222. package/src/memory/memory-retrospective-startup-cleanup.ts +97 -19
  223. package/src/memory/memory-retrospective-state.ts +85 -2
  224. package/src/memory/migrations/281-memory-retrospective-remembered-log.ts +40 -0
  225. package/src/memory/migrations/282-schedule-inference-profile.test.ts +77 -0
  226. package/src/memory/migrations/282-schedule-inference-profile.ts +26 -0
  227. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.test.ts +102 -0
  228. package/src/memory/migrations/283-memory-v3-selections-message-id-and-sections.ts +53 -0
  229. package/src/memory/migrations/284-workflow-runs.ts +51 -0
  230. package/src/memory/migrations/285-schedule-workflow-mode.ts +26 -0
  231. package/src/memory/migrations/286-workflow-run-trust.ts +27 -0
  232. package/src/memory/migrations/287-conversation-origin-channel-index.ts +15 -0
  233. package/src/memory/migrations/288-backfill-origin-channel-from-bindings.ts +43 -0
  234. package/src/memory/migrations/289-contact-channels-unique-ext-user.ts +115 -0
  235. package/src/memory/migrations/290-schedule-capabilities.test.ts +77 -0
  236. package/src/memory/migrations/290-schedule-capabilities.ts +25 -0
  237. package/src/memory/migrations/__tests__/281-memory-retrospective-remembered-log.test.ts +96 -0
  238. package/src/memory/migrations/__tests__/289-contact-channels-unique-ext-user.test.ts +571 -0
  239. package/src/memory/migrations/index.ts +10 -0
  240. package/src/memory/preloaded-apps.ts +116 -0
  241. package/src/memory/schema/infrastructure.ts +4 -0
  242. package/src/memory/schema/memory-core.ts +4 -0
  243. package/src/memory/v2/__tests__/concept-page-frontmatter-schema.test.ts +45 -0
  244. package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +11 -7
  245. package/src/memory/v2/__tests__/page-store.test.ts +13 -2
  246. package/src/memory/v2/__tests__/qdrant.test.ts +24 -0
  247. package/src/memory/v2/frontmatter-sweep.ts +7 -6
  248. package/src/memory/v2/page-store.ts +4 -3
  249. package/src/memory/v2/qdrant.ts +42 -3
  250. package/src/memory/v2/types.ts +16 -10
  251. package/src/messaging/draft-store.ts +1 -1
  252. package/src/notifications/access-request-copy.ts +200 -113
  253. package/src/notifications/adapters/slack.ts +250 -111
  254. package/src/notifications/adapters/telegram.ts +7 -44
  255. package/src/notifications/approval-card-builder.ts +93 -0
  256. package/src/notifications/broadcaster.ts +74 -0
  257. package/src/notifications/conversation-pairing.ts +8 -6
  258. package/src/notifications/copy-composer.ts +32 -26
  259. package/src/notifications/decision-engine.ts +59 -7
  260. package/src/notifications/guardian-question-mode.ts +145 -155
  261. package/src/notifications/home-feed-side-effect.ts +28 -11
  262. package/src/notifications/notification-utils.ts +66 -0
  263. package/src/notifications/signal.ts +6 -0
  264. package/src/notifications/tool-approval-copy.ts +142 -0
  265. package/src/notifications/types.ts +19 -0
  266. package/src/permissions/threshold.ts +11 -0
  267. package/src/plugin-api/types.ts +16 -4
  268. package/src/plugins/defaults/compaction/window-manager.ts +44 -0
  269. package/src/plugins/defaults/index.ts +46 -0
  270. package/src/plugins/defaults/max-tokens-continue/continue-state-store.ts +53 -0
  271. package/src/plugins/defaults/max-tokens-continue/hooks/post-model-call.ts +80 -0
  272. package/src/plugins/defaults/max-tokens-continue/hooks/stop.ts +20 -0
  273. package/src/plugins/defaults/max-tokens-continue/package.json +14 -0
  274. package/src/plugins/defaults/memory-retrieval/hooks/__tests__/user-prompt-submit.test.ts +37 -0
  275. package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit.ts +29 -1
  276. package/src/plugins/defaults/memory-v3-shadow/__tests__/carry-integration.test.ts +8 -3
  277. package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +4 -2
  278. package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +28 -18
  279. package/src/plugins/defaults/memory-v3-shadow/__tests__/section-dense-store.test.ts +67 -0
  280. package/src/plugins/defaults/memory-v3-shadow/__tests__/selection-log-store.test.ts +122 -22
  281. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-integration.test.ts +2 -0
  282. package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +63 -1
  283. package/src/plugins/defaults/memory-v3-shadow/injector.ts +61 -18
  284. package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +1 -1
  285. package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +39 -10
  286. package/src/plugins/defaults/memory-v3-shadow/section-dense-store.ts +34 -1
  287. package/src/plugins/defaults/memory-v3-shadow/selection-log-store.ts +112 -47
  288. package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +78 -15
  289. package/src/plugins/defaults/task-progress-nudge/hooks/post-tool-use.ts +206 -0
  290. package/src/plugins/defaults/task-progress-nudge/package.json +15 -0
  291. package/src/prompts/__tests__/system-prompt.test.ts +100 -1
  292. package/src/prompts/__tests__/task-progress-hint-section.test.ts +5 -7
  293. package/src/prompts/normalize-onboarding.ts +2 -0
  294. package/src/prompts/persona-resolver.ts +3 -0
  295. package/src/prompts/system-prompt.ts +51 -2
  296. package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +3 -1
  297. package/src/prompts/templates/system-sections.ts +8 -3
  298. package/src/providers/call-site-routing.ts +6 -3
  299. package/src/providers/fireworks/client.ts +3 -0
  300. package/src/providers/inference/auth.ts +52 -46
  301. package/src/providers/model-intents.ts +2 -2
  302. package/src/providers/openai/__tests__/coerce-object-args.test.ts +105 -0
  303. package/src/providers/openai/chat-completions-provider.ts +47 -9
  304. package/src/providers/openai/coerce-object-args.ts +104 -0
  305. package/src/providers/retry.ts +8 -5
  306. package/src/providers/types.ts +10 -0
  307. package/src/runtime/__tests__/agent-wake.test.ts +629 -7
  308. package/src/runtime/access-request-helper.ts +16 -9
  309. package/src/runtime/agent-wake.ts +302 -51
  310. package/src/runtime/assistant-stream-state.ts +141 -8
  311. package/src/runtime/background-job-runner.ts +9 -0
  312. package/src/runtime/channel-approval-types.ts +1 -0
  313. package/src/runtime/channel-invite-transports/telegram.ts +6 -5
  314. package/src/runtime/channel-invite-transports/voice.ts +2 -2
  315. package/src/runtime/channel-invite-types.ts +4 -2
  316. package/src/runtime/channel-retry-sweep.ts +19 -41
  317. package/src/runtime/finalize-event-delivery.ts +72 -0
  318. package/src/runtime/guardian-action-message-composer.ts +0 -54
  319. package/src/runtime/http-server.ts +6 -14
  320. package/src/runtime/http-types.ts +0 -1
  321. package/src/runtime/message-composer-types.ts +0 -9
  322. package/src/runtime/middleware/__tests__/rate-limiter.test.ts +63 -0
  323. package/src/runtime/middleware/auth.ts +27 -3
  324. package/src/runtime/middleware/rate-limiter.ts +28 -1
  325. package/src/runtime/migrations/vbundle-builder.ts +6 -5
  326. package/src/runtime/pending-interactions.ts +20 -1
  327. package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +232 -173
  328. package/src/runtime/routes/__tests__/plugins-routes.test.ts +18 -0
  329. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +436 -0
  330. package/src/runtime/routes/__tests__/surface-action-routes.test.ts +11 -0
  331. package/src/runtime/routes/approval-routes.ts +35 -8
  332. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +2 -1
  333. package/src/runtime/routes/btw-routes.ts +37 -1
  334. package/src/runtime/routes/channel-delivery-routes.ts +11 -7
  335. package/src/runtime/routes/channel-route-definitions.ts +3 -0
  336. package/src/runtime/routes/channel-route-shared.ts +3 -1
  337. package/src/runtime/routes/consolidation-routes.ts +17 -13
  338. package/src/runtime/routes/conversation-compaction-routes.ts +159 -119
  339. package/src/runtime/routes/conversation-list-routes.ts +41 -4
  340. package/src/runtime/routes/conversation-query-routes.ts +196 -11
  341. package/src/runtime/routes/conversation-routes.ts +15 -1
  342. package/src/runtime/routes/credential-prompt-routes.ts +39 -17
  343. package/src/runtime/routes/empty-state-greeting-cache.ts +65 -0
  344. package/src/runtime/routes/heartbeat-routes.ts +18 -13
  345. package/src/runtime/routes/image-generation-routes.ts +20 -2
  346. package/src/runtime/routes/inbound-message-handler.ts +13 -12
  347. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +32 -31
  348. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +7 -5
  349. package/src/runtime/routes/inbound-stages/background-dispatch.ts +7 -21
  350. package/src/runtime/routes/inbound-stages/escalation-intercept.ts +5 -5
  351. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +6 -15
  352. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +1 -1
  353. package/src/runtime/routes/index.ts +6 -0
  354. package/src/runtime/routes/log-export/AGENTS.md +1 -1
  355. package/src/runtime/routes/log-export/workspace-allowlist.ts +1 -1
  356. package/src/runtime/routes/migration-routes.ts +5 -9
  357. package/src/runtime/routes/plugins-routes.ts +26 -0
  358. package/src/runtime/routes/ps-routes.ts +10 -8
  359. package/src/runtime/routes/retrospective-routes.ts +235 -0
  360. package/src/runtime/routes/runs-pagination.ts +75 -0
  361. package/src/runtime/routes/schedule-routes.ts +367 -59
  362. package/src/runtime/routes/sounds-config-routes.ts +239 -0
  363. package/src/runtime/routes/surface-action-routes.ts +84 -4
  364. package/src/runtime/routes/workflow-routes.test.ts +372 -0
  365. package/src/runtime/routes/workflow-routes.ts +363 -0
  366. package/src/runtime/routes/workspace-routes.test.ts +61 -1
  367. package/src/runtime/routes/workspace-routes.ts +26 -1
  368. package/src/runtime/services/__tests__/analyze-conversation.test.ts +38 -0
  369. package/src/runtime/services/analyze-conversation.ts +26 -13
  370. package/src/schedule/inference-profile.ts +28 -0
  371. package/src/schedule/schedule-store.ts +152 -4
  372. package/src/schedule/scheduler-types.ts +6 -0
  373. package/src/schedule/scheduler.ts +96 -0
  374. package/src/security/secret-allowlist.ts +1 -1
  375. package/src/skills/path-classifier.ts +1 -1
  376. package/src/tools/apps/executors.ts +23 -0
  377. package/src/tools/apps/resolve-app-id.ts +42 -0
  378. package/src/tools/browser/browser-execution.ts +9 -11
  379. package/src/tools/credentials/broker.ts +4 -4
  380. package/src/tools/credentials/vault.ts +26 -137
  381. package/src/tools/executor.ts +69 -0
  382. package/src/tools/flag-gated-tools.test.ts +76 -0
  383. package/src/tools/permission-checker.ts +8 -1
  384. package/src/tools/registry.ts +51 -0
  385. package/src/tools/schedule/create.ts +77 -1
  386. package/src/tools/schedule/list.ts +1 -0
  387. package/src/tools/schedule/update.ts +73 -1
  388. package/src/tools/skills/execute.ts +56 -0
  389. package/src/tools/terminal/shell.ts +1 -1
  390. package/src/tools/ui-surface/definitions.ts +91 -2
  391. package/src/tools/workflows/manage-workflows.ts +183 -0
  392. package/src/tools/workflows/run-workflow.test.ts +442 -0
  393. package/src/tools/workflows/run-workflow.ts +88 -0
  394. package/src/types/onboarding-context.ts +2 -0
  395. package/src/usage/attribution.ts +24 -0
  396. package/src/util/canonicalize-identity.ts +12 -3
  397. package/src/util/platform.ts +17 -17
  398. package/src/watcher/__tests__/engine.test.ts +24 -0
  399. package/src/watcher/__tests__/telemetry.test.ts +135 -0
  400. package/src/watcher/engine.ts +7 -0
  401. package/src/watcher/telemetry.ts +74 -0
  402. package/src/workflows/capabilities.test.ts +365 -0
  403. package/src/workflows/capabilities.ts +359 -0
  404. package/src/workflows/deterministic-stringify.ts +27 -0
  405. package/src/workflows/engine-integration.test.ts +656 -0
  406. package/src/workflows/engine.test.ts +1144 -0
  407. package/src/workflows/engine.ts +1078 -0
  408. package/src/workflows/fanout-load.test.ts +168 -0
  409. package/src/workflows/journal-store.test.ts +369 -0
  410. package/src/workflows/journal-store.ts +470 -0
  411. package/src/workflows/leaf-runner.test.ts +704 -0
  412. package/src/workflows/leaf-runner.ts +589 -0
  413. package/src/workflows/library.test.ts +134 -0
  414. package/src/workflows/library.ts +124 -0
  415. package/src/workflows/run-manager.test.ts +711 -0
  416. package/src/workflows/run-manager.ts +593 -0
  417. package/src/workflows/sandbox-escape.test.ts +339 -0
  418. package/src/workflows/sandbox.test.ts +251 -0
  419. package/src/workflows/sandbox.ts +447 -0
  420. package/src/workspace/adaptive-thinking-repair.ts +33 -11
  421. package/src/workspace/migrations/021-move-signals-to-workspace.ts +1 -1
  422. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +1 -1
  423. package/src/workspace/migrations/026-backfill-install-meta.ts +1 -1
  424. package/src/workspace/migrations/030-seed-pkb-autoinject.ts +2 -1
  425. package/src/workspace/migrations/031-drop-user-md.ts +1 -4
  426. package/src/workspace/migrations/048-remove-workspace-hooks.ts +1 -1
  427. package/src/workspace/migrations/056-release-notes-inference-profile-reordering.ts +5 -2
  428. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +1 -1
  429. package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +8 -2
  430. package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +41 -14
  431. package/src/workspace/migrations/102-preserve-heartbeat-enabled-for-existing-workspaces.ts +69 -0
  432. package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +83 -0
  433. package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +133 -0
  434. package/src/workspace/migrations/registry.ts +6 -0
  435. package/src/workspace/migrations/runner.ts +1 -1
  436. package/tsconfig.json +1 -1
  437. package/src/__tests__/guardian-action-copy-generator.test.ts +0 -200
  438. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -579
  439. package/src/__tests__/guardian-action-store.test.ts +0 -106
  440. package/src/daemon/guardian-action-generators.ts +0 -71
  441. package/src/memory/guardian-action-store.ts +0 -484
  442. package/src/runtime/guardian-action-grant-minter.ts +0 -150
package/src/agent/loop.ts CHANGED
@@ -60,6 +60,28 @@ const MID_LOOP_YIELD_THRESHOLD_RATIO = 0.85;
60
60
  const LONG_HISTORY_MESSAGE_THRESHOLD = 50;
61
61
  const LONG_HISTORY_SAFETY_MARGIN_FLOOR = 0.15;
62
62
 
63
+ /**
64
+ * Minimum token regrowth, measured from the post-compaction watermark, before
65
+ * the budget gate will compact again. A compaction pass that just ran already
66
+ * proved how far the history can shrink; if the estimate has not climbed at
67
+ * least this far past that watermark, another pass cannot free more than it
68
+ * already did and would only thrash (the production failure mode: each pass
69
+ * lands a hair under the trigger, one tick pushes it back over, repeat).
70
+ *
71
+ * Sized as `max(2048, 2% of maxInputTokens)` so it scales with the window but
72
+ * never collapses to a trivial value on small budgets. Overflow-driven
73
+ * compaction bypasses this guard entirely — a provider-confirmed overflow must
74
+ * always be allowed to compact.
75
+ */
76
+ const MIN_REGROWTH_FLOOR_TOKENS = 2048;
77
+ const MIN_REGROWTH_WINDOW_RATIO = 0.02;
78
+ function minRegrowthTokens(maxInputTokens: number): number {
79
+ return Math.max(
80
+ MIN_REGROWTH_FLOOR_TOKENS,
81
+ Math.floor(maxInputTokens * MIN_REGROWTH_WINDOW_RATIO),
82
+ );
83
+ }
84
+
63
85
  export interface AgentLoopConfig {
64
86
  maxTokens: number;
65
87
  maxInputTokens?: number; // context window size for tool result truncation
@@ -493,6 +515,16 @@ export interface AgentLoopRunOptions {
493
515
  * call-site named profile. Missing profile names silently fall through.
494
516
  */
495
517
  overrideProfile?: string;
518
+ /**
519
+ * Float the override profile above the call-site layers (named site
520
+ * profile + call-site override) for non-main-agent call sites — the
521
+ * resolver's `forceOverrideProfile` escape hatch. Threaded onto each
522
+ * send's `SendMessageOptions.config` alongside `overrideProfile`. Used by
523
+ * wakes that must run a background call site under a specific
524
+ * conversation's inference profile (e.g. fork-based memory
525
+ * retrospectives).
526
+ */
527
+ forceOverrideProfile?: boolean;
496
528
  resolveOverrideProfile?: () => string | undefined;
497
529
  /**
498
530
  * Resolves the orchestrator's effective context window for this turn: the
@@ -856,6 +888,7 @@ export class AgentLoop {
856
888
  callSite,
857
889
  trust,
858
890
  overrideProfile,
891
+ forceOverrideProfile = false,
859
892
  resolveOverrideProfile,
860
893
  resolveContextWindow,
861
894
  compactInPlace = false,
@@ -899,6 +932,28 @@ export class AgentLoop {
899
932
  // `context_too_large`) instead of looping.
900
933
  let overflowLadderExhausted = false;
901
934
  let overflowAutoCompressApplied = false;
935
+ // Per-turn suppression for floor-dominated proactive-compaction thrash.
936
+ // Set when a proactive (non-overflow) pass completes WITHOUT clearing the
937
+ // mid-loop gate (the manager returned `exhausted` — it could not get below
938
+ // its success threshold). During a tool-heavy turn each tool round grows the
939
+ // PROTECTED in-flight region past the regrowth guard's re-arm delta, but
940
+ // that region is exactly what compaction cannot touch, so every subsequent
941
+ // gate check would fire another futile full-context pass. Once set, the
942
+ // budget gate skips proactive compaction for the rest of THIS turn; it
943
+ // clears when a later proactive pass succeeds (non-exhausted). Overflow-
944
+ // driven compaction always bypasses it — a provider-confirmed overflow must
945
+ // always compact.
946
+ //
947
+ // Lifetime is exactly one turn: this is a `run()`-local (like
948
+ // `budgetGateArmed` / `pendingOverflowSignal` / `overflowLadderExhausted`),
949
+ // not an instance field. The AgentLoop instance is constructed once per
950
+ // Conversation and `run()` is invoked once per turn (a checkpoint handoff
951
+ // breaks out of the loop and the queued message resumes in a fresh `run()`),
952
+ // so a `run()`-local resets implicitly at every turn start — no manual reset
953
+ // point is needed, and suppression can never leak across turns the way an
954
+ // instance field would. (The regrowth watermark, by contrast, lives on the
955
+ // cross-turn `compactionCircuit` precisely because it must persist.)
956
+ let proactiveCompactionFutileThisTurn = false;
902
957
  const rlog = log.child({ requestId });
903
958
 
904
959
  // Conversation directory for the result-time tool-result spool/stub pass.
@@ -1052,7 +1107,47 @@ export class AgentLoop {
1052
1107
  overflowDriven ||
1053
1108
  !isFirstCallGate ||
1054
1109
  !(await this.compactionCircuit.isOpen());
1055
- if (shouldCompact && compactionAllowed) {
1110
+ // Regrowth hysteresis: a proactive pass that just ran proved how
1111
+ // far this history can shrink. If the estimate has not climbed at
1112
+ // least `minRegrowth` past that watermark, another pass cannot free
1113
+ // more and would only thrash — skip it and let the provider call
1114
+ // proceed (overflow recovery remains the safety net). Overflow-
1115
+ // driven compaction always bypasses the guard.
1116
+ const watermark = this.compactionCircuit.lastPostCompactionEstimate;
1117
+ const minRegrowth = minRegrowthTokens(maxInputTokens);
1118
+ const regrowthGuardSkip =
1119
+ !overflowDriven &&
1120
+ watermark !== null &&
1121
+ estimated - watermark < minRegrowth;
1122
+ // Floor-dominated thrash guard: a proactive pass earlier this turn
1123
+ // already exhausted the compactor (couldn't clear the gate because
1124
+ // the over-budget region is the protected in-flight turn). The
1125
+ // regrowth guard cannot catch this — each tool round's growth lands
1126
+ // in that protected region and re-arms the regrowth delta — so this
1127
+ // per-turn latch is what stops the repeated futile passes. Overflow-
1128
+ // driven compaction bypasses it.
1129
+ const proactiveFutileSkip =
1130
+ !overflowDriven && proactiveCompactionFutileThisTurn;
1131
+ if (
1132
+ shouldCompact &&
1133
+ compactionAllowed &&
1134
+ (regrowthGuardSkip || proactiveFutileSkip)
1135
+ ) {
1136
+ rlog.info(
1137
+ {
1138
+ turn: toolUseTurns,
1139
+ estimated,
1140
+ postCompactionWatermark: watermark,
1141
+ minRegrowth,
1142
+ reason: proactiveFutileSkip
1143
+ ? "proactive_compaction_exhausted_this_turn"
1144
+ : "history_not_regrown",
1145
+ },
1146
+ proactiveFutileSkip
1147
+ ? "Skipping compaction: a proactive pass already exhausted the compactor this turn — the over-budget region is the protected in-flight turn, so re-compacting would free nothing"
1148
+ : "Skipping compaction: history has not regrown past the post-compaction watermark — re-compacting would not free more",
1149
+ );
1150
+ } else if (shouldCompact && compactionAllowed) {
1056
1151
  rlog.info(
1057
1152
  {
1058
1153
  turn: toolUseTurns,
@@ -1078,6 +1173,11 @@ export class AgentLoop {
1078
1173
  // The compacted, re-injected array is the new base; output
1079
1174
  // produced after this point is what the wrapper persists.
1080
1175
  newMessagesStart = history.length;
1176
+ // Record the post-compaction estimate so the regrowth guard can
1177
+ // tell, on a later gate crossing, whether the history has grown
1178
+ // enough to be worth compacting again.
1179
+ this.compactionCircuit.lastPostCompactionEstimate =
1180
+ this.estimateTokens(history);
1081
1181
  }
1082
1182
  if (overflowDriven) {
1083
1183
  // Carry the ladder's terminal state to the catch: if the
@@ -1085,6 +1185,12 @@ export class AgentLoop {
1085
1185
  // ends instead of looping.
1086
1186
  overflowLadderExhausted = attempt.exhausted;
1087
1187
  overflowAutoCompressApplied = attempt.autoCompressApplied;
1188
+ } else {
1189
+ // Proactive (non-overflow) pass. If it exhausted the compactor
1190
+ // without clearing the gate, latch suppression so later gate
1191
+ // checks this turn skip the futile re-pass; a pass that DID
1192
+ // clear the gate (non-exhausted) releases the latch.
1193
+ proactiveCompactionFutileThisTurn = attempt.exhausted;
1088
1194
  }
1089
1195
  }
1090
1196
  }
@@ -1195,6 +1301,9 @@ export class AgentLoop {
1195
1301
  const effectiveOverrideProfile = resolveEffectiveOverrideProfile();
1196
1302
  if (effectiveOverrideProfile) {
1197
1303
  providerConfig.overrideProfile = effectiveOverrideProfile;
1304
+ if (forceOverrideProfile) {
1305
+ providerConfig.forceOverrideProfile = true;
1306
+ }
1198
1307
  }
1199
1308
 
1200
1309
  // Rate-limit consecutive LLM calls to prevent spin when tools return instantly
@@ -1504,8 +1613,10 @@ export class AgentLoop {
1504
1613
  content: response.content,
1505
1614
  };
1506
1615
 
1507
- // Check for tool use
1508
- toolUseBlocks = response.content.filter(
1616
+ // The model's own tool calls. The executable set is finalized after
1617
+ // the `post-model-call` hook below, which may add or drop tool calls;
1618
+ // this raw set drives only the completion log and the max-tokens branch.
1619
+ const modelToolUseBlocks = response.content.filter(
1509
1620
  (block): block is Extract<ContentBlock, { type: "tool_use" }> =>
1510
1621
  block.type === "tool_use",
1511
1622
  );
@@ -1515,7 +1626,7 @@ export class AgentLoop {
1515
1626
  turn: toolUseTurns,
1516
1627
  stopReason: response.stopReason,
1517
1628
  contentBlocks: response.content.length,
1518
- toolUseCount: toolUseBlocks.length,
1629
+ toolUseCount: modelToolUseBlocks.length,
1519
1630
  durationMs: providerDurationMs,
1520
1631
  },
1521
1632
  "LLM call complete",
@@ -1534,20 +1645,59 @@ export class AgentLoop {
1534
1645
  stopReason: response.stopReason,
1535
1646
  contentBlocks: response.content.length,
1536
1647
  safeContentBlocks: safeContent.length,
1537
- toolUseCount: toolUseBlocks.length,
1648
+ toolUseCount: modelToolUseBlocks.length,
1538
1649
  },
1539
1650
  "LLM response reached output token limit",
1540
1651
  );
1541
1652
  // Run the hook on the truncated reply so output-filter plugins still
1542
1653
  // see it, and so a turn that streamed nothing live gets its final
1543
- // emit (without this the client would see nothing). The retry decision
1544
- // is ignored here: a max-tokens stop is terminal.
1545
- const { finalized: safeAssistantMessage } =
1546
- await finalizeAssistantMessage({
1547
- role: "assistant",
1548
- content: safeContent,
1549
- });
1654
+ // emit (without this the client would see nothing). A recovery hook
1655
+ // (max-tokens-continue) may set `decision: "continue"` to resume the
1656
+ // truncated turn: it leaves `messages` as the next history (the
1657
+ // truncated turn followed by a continuation nudge), so unlike the
1658
+ // no-tool retry below the partial output is kept, not discarded.
1659
+ // Otherwise the stop is terminal and the continuation card surfaces.
1660
+ const {
1661
+ finalized: rawSafeAssistantMessage,
1662
+ decision: maxTokensDecision,
1663
+ messages: maxTokensMessages,
1664
+ } = await finalizeAssistantMessage({
1665
+ role: "assistant",
1666
+ content: safeContent,
1667
+ });
1668
+ // A truncated turn never executes tools — the model's own tool calls
1669
+ // were stripped into `safeContent` above. The hook can still append a
1670
+ // `tool_use` block while transforming the reply, but this branch
1671
+ // short-circuits without an executor pass, so honoring it would
1672
+ // persist a tool call with no matching `tool_result`. Drop hook-added
1673
+ // tool calls here too: tool injection is supported only on the
1674
+ // non-truncated path below, which runs the executor.
1675
+ const safeAssistantMessage: Message = {
1676
+ ...rawSafeAssistantMessage,
1677
+ content: rawSafeAssistantMessage.content.filter(
1678
+ (block) =>
1679
+ block.type !== "tool_use" &&
1680
+ block.type !== "server_tool_use" &&
1681
+ block.type !== "web_search_tool_result",
1682
+ ),
1683
+ };
1550
1684
  emitFinalAssistantText(safeAssistantMessage.content);
1685
+ if (
1686
+ maxTokensDecision === "continue" &&
1687
+ postModelCallContinues < MAX_POST_MODEL_CALL_CONTINUES
1688
+ ) {
1689
+ postModelCallContinues++;
1690
+ rlog.warn(
1691
+ { turn: toolUseTurns, retry: postModelCallContinues },
1692
+ "max-tokens stop — auto-continuing the truncated turn",
1693
+ );
1694
+ await onEvent({
1695
+ type: "message_complete",
1696
+ message: safeAssistantMessage,
1697
+ });
1698
+ history = maxTokensMessages;
1699
+ continue;
1700
+ }
1551
1701
  history.push(safeAssistantMessage);
1552
1702
  await onEvent({
1553
1703
  type: "max_tokens_reached",
@@ -1575,6 +1725,25 @@ export class AgentLoop {
1575
1725
  } = await finalizeAssistantMessage(assistantMessage);
1576
1726
  assistantMessage = finalizedAssistantMessage;
1577
1727
 
1728
+ // Execution follows the FINALIZED content, not the raw reply. A
1729
+ // `post-model-call` hook may append a `tool_use` block to run a tool as
1730
+ // if the model had called it (the supported way for a plugin to surface
1731
+ // a card or take a follow-up action), or drop one the model emitted, so
1732
+ // the loop runs whatever the assistant message ends up carrying.
1733
+ // Normalize ids so the executor and tool_result correlation stay 1:1 —
1734
+ // a hook-added block may carry an empty or duplicate id.
1735
+ toolUseBlocks = assistantMessage.content.filter(
1736
+ (block): block is Extract<ContentBlock, { type: "tool_use" }> =>
1737
+ block.type === "tool_use",
1738
+ );
1739
+ const seenToolUseIds = new Set<string>();
1740
+ for (const block of toolUseBlocks) {
1741
+ if (block.id.length === 0 || seenToolUseIds.has(block.id)) {
1742
+ block.id = crypto.randomUUID();
1743
+ }
1744
+ seenToolUseIds.add(block.id);
1745
+ }
1746
+
1578
1747
  // At the no-tool stop boundary the retry decision is actionable: a
1579
1748
  // recovery hook may repair history and ask to re-query (a tool-bearing
1580
1749
  // turn already continues, so its decision is ignored). A re-query
@@ -25,3 +25,15 @@
25
25
  */
26
26
  export const CALL_SITE_SYNTHETIC_AGENT_ERROR_MESSAGE =
27
27
  "syntheticAgentErrorMessage";
28
+
29
+ /**
30
+ * Marks the summarizer LLM call the agent loop makes to compact a
31
+ * conversation's history when it approaches the context limit. The row
32
+ * lands in the inspector rail like any other call, so the frontend
33
+ * branches on this value to tag it as a compaction rather than a
34
+ * main-agent call.
35
+ *
36
+ * Mirrors the `compactionAgent` member of the backend's `LLMCallSite`
37
+ * enum (the value stamped on the row at write time).
38
+ */
39
+ export const CALL_SITE_COMPACTION_AGENT = "compactionAgent";
@@ -12,6 +12,13 @@
12
12
  * Absent on streams produced by older daemons that pre-date the anchor
13
13
  * protocol.
14
14
  *
15
+ * `timestampMs` is the daemon's wall-clock emission time for the delta. The
16
+ * transport envelope (`AssistantEvent.emittedAt`) carries the same instant,
17
+ * but it is dropped once the inner message is unwrapped for persistence,
18
+ * debug buffers, and backend storage — so the timestamp is carried on the
19
+ * payload itself to make per-delta timing observable wherever the message
20
+ * travels.
21
+ *
15
22
  * Only emitted when thinking streaming is enabled for the turn; turns that
16
23
  * suppress reasoning output produce none.
17
24
  *
@@ -26,6 +33,9 @@ export const AssistantThinkingDeltaEventSchema = z.object({
26
33
  thinking: z.string(),
27
34
  messageId: z.string().optional(),
28
35
  conversationId: z.string().optional(),
36
+ /** Epoch milliseconds (`Date.now()`) at which the daemon emitted this
37
+ * delta. Absent on streams produced by daemons that pre-date this field. */
38
+ timestampMs: z.number().optional(),
29
39
  });
30
40
 
31
41
  export type AssistantThinkingDeltaEvent = z.infer<
@@ -23,6 +23,13 @@ export const UsageUpdateEventSchema = z.object({
23
23
  conversationId: z.string(),
24
24
  inputTokens: z.number(),
25
25
  outputTokens: z.number(),
26
+ /**
27
+ * Per-call prompt-cache token counts, as reported by the provider.
28
+ * Optional: older daemons omit them, and providers without prompt
29
+ * caching never supply them. `inputTokens` already includes these.
30
+ */
31
+ cacheCreationInputTokens: z.number().optional(),
32
+ cacheReadInputTokens: z.number().optional(),
26
33
  totalInputTokens: z.number(),
27
34
  totalOutputTokens: z.number(),
28
35
  estimatedCost: z.number(),
package/src/api/index.ts CHANGED
@@ -53,7 +53,10 @@ import { UsageProgressEventSchema } from "./events/usage-progress.js";
53
53
  import { UsageUpdateEventSchema } from "./events/usage-update.js";
54
54
  import { UserMessageEchoEventSchema } from "./events/user-message-echo.js";
55
55
 
56
- export { CALL_SITE_SYNTHETIC_AGENT_ERROR_MESSAGE } from "./constants/call-sites.js";
56
+ export {
57
+ CALL_SITE_COMPACTION_AGENT,
58
+ CALL_SITE_SYNTHETIC_AGENT_ERROR_MESSAGE,
59
+ } from "./constants/call-sites.js";
57
60
  export { DEFAULT_TOOL_EXECUTION_TIMEOUT_SEC } from "./constants/tool-execution.js";
58
61
  export {
59
62
  type AssistantActivityAnchor,
@@ -15,14 +15,13 @@
15
15
  * `injectedText` reflects the live selection; when only `shadow` is true it is
16
16
  * the block that WOULD have been injected.
17
17
  *
18
- * `injectedText` is APPROXIMATE, not a verbatim record. It is re-rendered from
19
- * the persisted selection rows, which store slug/source/pinned but NOT the
20
- * matched-section identity. A page that live injection sent as a single matched
21
- * section is re-rendered here as its FULL page (the section ordinal isn't
22
- * persisted), so the text can differ from what was actually sent to the model.
23
- * The selected slug set is exact only the rendered text is full-page
24
- * approximate. Persisting the per-turn injected section is a documented
25
- * follow-up.
18
+ * `injectedText` is inspector-only, not a verbatim record of the live cards +
19
+ * spotlight. It re-renders each selection's matched section resolved from the
20
+ * persisted `(slug, sectionOrdinal)` against the CURRENT page falling back to
21
+ * the full page when no section was recorded. Because the section text is
22
+ * re-derived from the current page, it reflects bounded page-drift if the page
23
+ * changed since the turn (the same approximation the v2 inspector accepts). The
24
+ * selected slug set and each row's matched-section heading are exact as logged.
26
25
  */
27
26
 
28
27
  import { z } from "zod";
@@ -33,20 +32,28 @@ import { z } from "zod";
33
32
  * rows may carry retired labels) — but the schema stays a permissive string
34
33
  * so a new lane label (or a historical pre-lane row) doesn't break parsing on
35
34
  * the FE. `pinned` marks a page the turn was centrally about.
35
+ * `sectionOrdinal`/`sectionHeading` identify the matched section a finder lane
36
+ * surfaced (null for core/hot/fresh/edge selections and pre-migration rows).
36
37
  */
37
38
  export const MemoryV3SelectionRowSchema = z.object({
38
39
  slug: z.string(),
39
40
  source: z.string(),
40
41
  pinned: z.boolean(),
42
+ // The matched section a finder lane surfaced for this selection. Null for
43
+ // core/hot/fresh/edge selections with no matched section, and for rows
44
+ // written before the section columns existed. Optional + nullable so older
45
+ // clients and pre-migration rows round-trip.
46
+ sectionOrdinal: z.number().nullable().optional(),
47
+ sectionHeading: z.string().nullable().optional(),
41
48
  });
42
49
 
43
50
  export type MemoryV3SelectionRow = z.infer<typeof MemoryV3SelectionRowSchema>;
44
51
 
45
52
  /**
46
53
  * Memory v3 selection log shape. `injectedText` is the rendered
47
- * `<memory>…</memory>` block for the selection — APPROXIMATE: re-rendered from
48
- * the persisted rows, with full pages where live injection used a matched
49
- * section (the section identity isn't persisted). See the file header.
54
+ * `<memory>…</memory>` block for the selection — re-rendered from the persisted
55
+ * rows, with each selection's matched section resolved from its
56
+ * `(slug, sectionOrdinal)` (full-page fallback when none). See the file header.
50
57
  */
51
58
  export const MemoryV3SelectionLogSchema = z.object({
52
59
  turn: z.number(),
@@ -289,8 +289,8 @@ const GRANT_RETRY_MAX_WAIT_MS = 10_000;
289
289
  * polling to handle the voice pipeline race condition where the grant
290
290
  * may still be in-flight: `answerCall()` triggers the voice turn as
291
291
  * fire-and-forget, and the voice LLM can attempt tool execution before
292
- * `tryMintGuardianActionGrant`'s classifier finishes minting the
293
- * grant. Polling bridges this timing gap without changing the
292
+ * the guardian-decision
293
+ * classifier finishes minting the grant. Polling bridges this timing gap without changing the
294
294
  * fire-and-forget architecture.
295
295
  */
296
296
  export async function consumeGrantForInvocation(
@@ -648,7 +648,10 @@ describe("background wake runtime routes", () => {
648
648
  },
649
649
  });
650
650
  const handler = findHandler("drainDueBackgroundWake");
651
- const deadlineAt = Date.now() + 10;
651
+ // Keep the deadline far enough ahead that the synchronous start-check always
652
+ // sees it as live; the wait below outlasts it, so completion is verified to
653
+ // hold until the work settles rather than fire when the deadline elapses.
654
+ const deadlineAt = Date.now() + 200;
652
655
 
653
656
  const firstResponse = await handler({
654
657
  body: drainBodyFixture({
@@ -658,7 +661,7 @@ describe("background wake runtime routes", () => {
658
661
  });
659
662
 
660
663
  try {
661
- await new Promise((resolve) => setTimeout(resolve, 25));
664
+ await new Promise((resolve) => setTimeout(resolve, 280));
662
665
  expect(mockCompleteBackgroundWakeLease).not.toHaveBeenCalled();
663
666
 
664
667
  const duplicateResponse = await handler({
@@ -2,7 +2,7 @@
2
2
  * JIT download and cache of esbuild binary + preact for app compilation.
3
3
  *
4
4
  * Instead of shipping these in the .app bundle (~11 MB), we download them
5
- * on first compile to ~/.vellum/workspace/compiler-tools/. Follows the
5
+ * on first compile to $VELLUM_WORKSPACE_DIR/compiler-tools/. Follows the
6
6
  * same pattern as EmbeddingRuntimeManager.
7
7
  */
8
8
 
@@ -22,7 +22,6 @@ export const ALLOWED_PACKAGES: readonly string[] = [
22
22
  "lodash-es",
23
23
  "zod",
24
24
  "clsx",
25
- "lucide",
26
25
  ] as const;
27
26
 
28
27
  const INSTALL_TIMEOUT_MS = 10_000;
@@ -212,7 +212,7 @@ export async function resolveCallerIdentity(
212
212
  return {
213
213
  ok: false,
214
214
  error:
215
- "user_number mode requires a user phone number. Set calls.callerIdentity.userNumber in config or store credential/twilio/user_phone_number via the credential_store tool.",
215
+ "user_number mode requires a user phone number. Set calls.callerIdentity.userNumber in config or store credential/twilio/user_phone_number via `assistant credentials set`.",
216
216
  };
217
217
  }
218
218
 
@@ -1,42 +1,18 @@
1
1
  /**
2
- * Periodic sweep for expired guardian action requests.
2
+ * Guardian action expiry notices.
3
3
  *
4
- * Runs on a 60-second interval. When a request has passed its expiresAt
5
- * timestamp:
6
- * 1. Expires the request and all its deliveries in the store
7
- * 2. Expires the associated pending question so the call-side timeout fires
8
- * 3. Sends expiry notices to external delivery destinations (telegram)
9
- * 4. Adds an expiry message to mac guardian conversations
4
+ * Sends "this request has expired" notices to guardian delivery
5
+ * destinations — vellum conversations get an assistant message, external
6
+ * channels (telegram, slack) get a direct channel reply.
10
7
  */
11
8
 
12
9
  import { addMessage } from "../memory/conversation-crud.js";
13
- import {
14
- expireGuardianActionRequest,
15
- getDeliveriesByRequestId,
16
- getExpiredGuardianActionRequests,
17
- } from "../memory/guardian-action-store.js";
18
- import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
19
10
  import { deliverChannelReply } from "../runtime/gateway-client.js";
20
11
  import { composeGuardianActionMessageGenerative } from "../runtime/guardian-action-message-composer.js";
21
- import type { GuardianActionCopyGenerator } from "../runtime/http-types.js";
22
12
  import { getLogger } from "../util/logger.js";
23
- import { expirePendingQuestions } from "./call-store.js";
24
13
 
25
14
  const log = getLogger("guardian-action-sweep");
26
15
 
27
- const SWEEP_INTERVAL_MS = 60_000;
28
-
29
- let sweepTimer: ReturnType<typeof setInterval> | null = null;
30
- let sweepInProgress = false;
31
-
32
- /**
33
- * Send expiry notices to all delivery destinations for a guardian action
34
- * request. Handles both vellum/mac conversation messages and external channel
35
- * replies (telegram, slack).
36
- *
37
- * Deliveries must be captured *before* their status is changed to 'expired'
38
- * so the sent/pending filter still matches.
39
- */
40
16
  /** Minimal delivery shape used by the expiry notice sender. */
41
17
  export interface ExpiryDeliveryInfo {
42
18
  id: string;
@@ -46,23 +22,26 @@ export interface ExpiryDeliveryInfo {
46
22
  destinationChatId: string | null;
47
23
  }
48
24
 
25
+ /**
26
+ * Send expiry notices to all delivery destinations for a guardian action
27
+ * request. Handles both vellum/mac conversation messages and external channel
28
+ * replies (telegram, slack).
29
+ *
30
+ * Deliveries must be captured *before* their status is changed to 'expired'
31
+ * so the sent/pending filter still matches.
32
+ */
49
33
  export async function sendGuardianExpiryNotices(
50
34
  deliveries: ExpiryDeliveryInfo[],
51
35
  assistantId: string,
52
- guardianActionCopyGenerator?: GuardianActionCopyGenerator,
53
36
  ): Promise<void> {
54
37
  for (const delivery of deliveries) {
55
38
  if (delivery.status !== "sent" && delivery.status !== "pending") continue;
56
39
 
57
40
  try {
58
- const expiryText = await composeGuardianActionMessageGenerative(
59
- {
60
- scenario: "guardian_stale_expired",
61
- channel: delivery.destinationChannel,
62
- },
63
- {},
64
- guardianActionCopyGenerator,
65
- );
41
+ const expiryText = await composeGuardianActionMessageGenerative({
42
+ scenario: "guardian_stale_expired",
43
+ channel: delivery.destinationChannel,
44
+ });
66
45
 
67
46
  if (
68
47
  delivery.destinationChannel === "vellum" &&
@@ -99,59 +78,3 @@ export async function sendGuardianExpiryNotices(
99
78
  }
100
79
  }
101
80
  }
102
-
103
- /**
104
- * Sweep expired guardian action requests and clean up.
105
- */
106
- export async function sweepExpiredGuardianActions(
107
- guardianActionCopyGenerator?: GuardianActionCopyGenerator,
108
- ): Promise<void> {
109
- const expired = getExpiredGuardianActionRequests();
110
-
111
- for (const request of expired) {
112
- // Capture deliveries before expiring (since expiry changes their status)
113
- const deliveries = getDeliveriesByRequestId(request.id);
114
-
115
- // Expire the request and all deliveries
116
- expireGuardianActionRequest(request.id, "sweep_timeout");
117
-
118
- // Expire associated pending questions
119
- expirePendingQuestions(request.callSessionId);
120
-
121
- log.info(
122
- { requestId: request.id, callSessionId: request.callSessionId },
123
- "Expired guardian action request",
124
- );
125
-
126
- await sendGuardianExpiryNotices(
127
- deliveries,
128
- DAEMON_INTERNAL_ASSISTANT_ID,
129
- guardianActionCopyGenerator,
130
- );
131
- }
132
- }
133
-
134
- export function startGuardianActionSweep(
135
- guardianActionCopyGenerator?: GuardianActionCopyGenerator,
136
- ): void {
137
- if (sweepTimer) return;
138
- sweepTimer = setInterval(async () => {
139
- if (sweepInProgress) return;
140
- sweepInProgress = true;
141
- try {
142
- await sweepExpiredGuardianActions(guardianActionCopyGenerator);
143
- } catch (err) {
144
- log.error({ err }, "Guardian action sweep failed");
145
- } finally {
146
- sweepInProgress = false;
147
- }
148
- }, SWEEP_INTERVAL_MS);
149
- }
150
-
151
- export function stopGuardianActionSweep(): void {
152
- if (sweepTimer) {
153
- clearInterval(sweepTimer);
154
- sweepTimer = null;
155
- }
156
- sweepInProgress = false;
157
- }
package/src/cli/AGENTS.md CHANGED
@@ -14,6 +14,10 @@ For commands that manage the **lifecycle of assistant instances** (create, start
14
14
 
15
15
  Examples: `config`, `contacts`, `memory`, `autonomy`, `conversations` belong here. `hatch`, `wake`, `sleep`, `retire`, `ps`, `ssh` belong in `cli/`.
16
16
 
17
+ ## Full CRUD for Resource Namespaces
18
+
19
+ Every namespace that manages a resource (schedules, contacts, tasks, …) must expose the full CRUD surface — create, get/list, update, delete — each wired to a daemon route. Intentional exceptions (e.g. create restricted to one mode) must be documented inline in the namespace help text so agents know the gap is deliberate. Partial surfaces are not a smaller scope, they are a hazard: when a CRUD verb is missing, the model bypasses the CLI and hand-writes SQLite rows instead.
20
+
17
21
  ## Conventions
18
22
 
19
23
  - Commands use [Commander.js](https://github.com/tj/commander.js) and follow the `registerXCommand(program: Command)` pattern.