@vellumai/assistant 0.5.16 → 0.6.1

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 (592) hide show
  1. package/AGENTS.md +4 -0
  2. package/ARCHITECTURE.md +69 -16
  3. package/Dockerfile +2 -5
  4. package/bun.lock +6 -2
  5. package/docker-entrypoint.sh +32 -1
  6. package/docs/architecture/integrations.md +1 -1
  7. package/docs/architecture/memory.md +21 -24
  8. package/knip.json +2 -1
  9. package/openapi.yaml +1198 -83
  10. package/package.json +5 -1
  11. package/src/__tests__/actor-token-service.test.ts +68 -0
  12. package/src/__tests__/agent-loop.test.ts +0 -32
  13. package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
  14. package/src/__tests__/anthropic-provider.test.ts +217 -98
  15. package/src/__tests__/app-compiler.test.ts +120 -0
  16. package/src/__tests__/app-dir-path-guard.test.ts +1 -0
  17. package/src/__tests__/app-executors.test.ts +47 -1
  18. package/src/__tests__/app-source-watcher.test.ts +159 -0
  19. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -2
  20. package/src/__tests__/call-conversation-messages.test.ts +2 -6
  21. package/src/__tests__/call-domain.test.ts +2 -6
  22. package/src/__tests__/call-pointer-messages.test.ts +2 -14
  23. package/src/__tests__/call-recovery.test.ts +2 -6
  24. package/src/__tests__/call-routes-http.test.ts +2 -6
  25. package/src/__tests__/call-store.test.ts +2 -6
  26. package/src/__tests__/cancel-resolves-conversation-key.test.ts +2 -6
  27. package/src/__tests__/canonical-guardian-store.test.ts +2 -6
  28. package/src/__tests__/channel-delivery-store.test.ts +2 -6
  29. package/src/__tests__/channel-retry-sweep.test.ts +2 -6
  30. package/src/__tests__/checker.test.ts +63 -9
  31. package/src/__tests__/clawhub.test.ts +54 -24
  32. package/src/__tests__/cli-command-risk-guard.test.ts +14 -0
  33. package/src/__tests__/config-schema.test.ts +6 -1
  34. package/src/__tests__/config-set-platform-guard.test.ts +302 -0
  35. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +2 -6
  36. package/src/__tests__/contacts-tools.test.ts +31 -0
  37. package/src/__tests__/context-overflow-reducer.test.ts +86 -0
  38. package/src/__tests__/context-token-estimator.test.ts +175 -10
  39. package/src/__tests__/conversation-agent-loop-overflow.test.ts +13 -6
  40. package/src/__tests__/conversation-agent-loop.test.ts +13 -51
  41. package/src/__tests__/conversation-attachments.test.ts +2 -6
  42. package/src/__tests__/conversation-attention-store.test.ts +2 -6
  43. package/src/__tests__/conversation-clear-safety.test.ts +2 -6
  44. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +4 -10
  45. package/src/__tests__/conversation-disk-view-integration.test.ts +2 -6
  46. package/src/__tests__/conversation-disk-view.test.ts +2 -6
  47. package/src/__tests__/conversation-error.test.ts +33 -2
  48. package/src/__tests__/conversation-fork-crud.test.ts +2 -6
  49. package/src/__tests__/conversation-history-web-search.test.ts +6 -1
  50. package/src/__tests__/conversation-load-history-repair.test.ts +5 -1
  51. package/src/__tests__/conversation-media-retry.test.ts +91 -0
  52. package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
  53. package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
  54. package/src/__tests__/conversation-starter-routes.test.ts +20 -11
  55. package/src/__tests__/conversation-store.test.ts +2 -6
  56. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
  57. package/src/__tests__/conversation-usage.test.ts +2 -6
  58. package/src/__tests__/conversation-wipe.test.ts +13 -414
  59. package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
  60. package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
  61. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  62. package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
  63. package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
  64. package/src/__tests__/credential-execution-shell-lockdown.test.ts +2 -2
  65. package/src/__tests__/credential-security-e2e.test.ts +2 -0
  66. package/src/__tests__/date-context.test.ts +76 -210
  67. package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
  68. package/src/__tests__/file-list-tool.test.ts +219 -0
  69. package/src/__tests__/first-greeting.test.ts +1 -1
  70. package/src/__tests__/followup-tools.test.ts +2 -6
  71. package/src/__tests__/graph-extraction-event-date.test.ts +186 -0
  72. package/src/__tests__/guardian-action-conversation-turn.test.ts +2 -6
  73. package/src/__tests__/guardian-action-followup-executor.test.ts +2 -6
  74. package/src/__tests__/guardian-action-followup-store.test.ts +2 -6
  75. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +2 -6
  76. package/src/__tests__/guardian-action-late-reply.test.ts +2 -6
  77. package/src/__tests__/guardian-action-store.test.ts +2 -6
  78. package/src/__tests__/guardian-binding-drift-heal.test.ts +2 -6
  79. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +8 -8
  80. package/src/__tests__/guardian-dispatch.test.ts +2 -6
  81. package/src/__tests__/guardian-grant-minting.test.ts +2 -14
  82. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +2 -6
  83. package/src/__tests__/guardian-routing-invariants.test.ts +192 -6
  84. package/src/__tests__/guardian-routing-state.test.ts +2 -6
  85. package/src/__tests__/guardian-verification-voice-binding.test.ts +2 -6
  86. package/src/__tests__/heartbeat-service.test.ts +180 -3
  87. package/src/__tests__/identity-routes.test.ts +328 -0
  88. package/src/__tests__/inbound-invite-redemption.test.ts +2 -6
  89. package/src/__tests__/injection-block.test.ts +178 -0
  90. package/src/__tests__/install-meta.test.ts +506 -0
  91. package/src/__tests__/install-skill-routing.test.ts +293 -0
  92. package/src/__tests__/invite-redemption-service.test.ts +2 -6
  93. package/src/__tests__/invite-routes-http.test.ts +2 -6
  94. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +17 -28
  95. package/src/__tests__/list-messages-attachments.test.ts +2 -6
  96. package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
  97. package/src/__tests__/llm-context-normalization.test.ts +18 -18
  98. package/src/__tests__/llm-context-route-provider.test.ts +103 -6
  99. package/src/__tests__/llm-request-log-turn-query.test.ts +164 -6
  100. package/src/__tests__/llm-usage-store.test.ts +2 -6
  101. package/src/__tests__/log-export-workspace.test.ts +74 -111
  102. package/src/__tests__/managed-store.test.ts +38 -11
  103. package/src/__tests__/mcp-abort-signal.test.ts +5 -0
  104. package/src/__tests__/mcp-client-auth.test.ts +5 -0
  105. package/src/__tests__/memory-jobs-worker-backoff.test.ts +2 -8
  106. package/src/__tests__/memory-recall-log-store.test.ts +134 -6
  107. package/src/__tests__/memory-upsert-concurrency.test.ts +4 -112
  108. package/src/__tests__/migration-export-streaming.test.ts +304 -0
  109. package/src/__tests__/migration-import-commit-http.test.ts +11 -10
  110. package/src/__tests__/mock-fetch.ts +87 -0
  111. package/src/__tests__/non-member-access-request.test.ts +2 -6
  112. package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
  113. package/src/__tests__/notification-guardian-path.test.ts +2 -6
  114. package/src/__tests__/oauth-cli.test.ts +364 -2
  115. package/src/__tests__/oauth2-gateway-transport.test.ts +18 -3
  116. package/src/__tests__/onboarding-template-contract.test.ts +62 -14
  117. package/src/__tests__/outlook-attachments.test.ts +301 -0
  118. package/src/__tests__/outlook-automation-tools.test.ts +425 -0
  119. package/src/__tests__/outlook-categories.test.ts +212 -0
  120. package/src/__tests__/outlook-client-automation.test.ts +246 -0
  121. package/src/__tests__/outlook-compose-tools.test.ts +325 -0
  122. package/src/__tests__/outlook-declutter-tools.test.ts +585 -0
  123. package/src/__tests__/outlook-email-watcher.test.ts +322 -0
  124. package/src/__tests__/outlook-follow-up.test.ts +196 -0
  125. package/src/__tests__/outlook-messaging-provider.test.ts +498 -3
  126. package/src/__tests__/outlook-trash.test.ts +77 -0
  127. package/src/__tests__/outlook-unsubscribe.test.ts +250 -0
  128. package/src/__tests__/parser.test.ts +32 -0
  129. package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
  130. package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
  131. package/src/__tests__/permission-mode-sse.test.ts +418 -0
  132. package/src/__tests__/permission-mode-store.test.ts +277 -0
  133. package/src/__tests__/permission-mode.test.ts +101 -0
  134. package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
  135. package/src/__tests__/platform-callback-registration.test.ts +4 -4
  136. package/src/__tests__/playbook-execution.test.ts +76 -80
  137. package/src/__tests__/playbook-tools.test.ts +5 -7
  138. package/src/__tests__/profiler-routes.test.ts +502 -0
  139. package/src/__tests__/profiler-run-store.test.ts +441 -0
  140. package/src/__tests__/provider-error-scenarios.test.ts +21 -0
  141. package/src/__tests__/proxy-approval-callback.test.ts +4 -75
  142. package/src/__tests__/rebuild-index-graph-nodes.test.ts +273 -0
  143. package/src/__tests__/registry.test.ts +3 -3
  144. package/src/__tests__/require-fresh-approval.test.ts +64 -2
  145. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -6
  146. package/src/__tests__/runtime-events-sse.test.ts +2 -6
  147. package/src/__tests__/sandbox-host-parity.test.ts +5 -4
  148. package/src/__tests__/schedule-store.test.ts +2 -6
  149. package/src/__tests__/schedule-tools.test.ts +2 -6
  150. package/src/__tests__/scheduler-recurrence.test.ts +1 -5
  151. package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
  152. package/src/__tests__/scoped-approval-grants.test.ts +2 -6
  153. package/src/__tests__/scoped-grant-security-matrix.test.ts +2 -6
  154. package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
  155. package/src/__tests__/search-skills-unified.test.ts +422 -0
  156. package/src/__tests__/secret-onetime-send.test.ts +2 -0
  157. package/src/__tests__/send-endpoint-busy.test.ts +44 -9
  158. package/src/__tests__/sequence-store.test.ts +2 -6
  159. package/src/__tests__/server-history-render.test.ts +2 -6
  160. package/src/__tests__/set-permission-mode.test.ts +274 -0
  161. package/src/__tests__/skill-feature-flags-integration.test.ts +38 -31
  162. package/src/__tests__/skill-feature-flags.test.ts +6 -6
  163. package/src/__tests__/skill-load-feature-flag.test.ts +23 -11
  164. package/src/__tests__/skill-memory.test.ts +2 -741
  165. package/src/__tests__/skills-uninstall.test.ts +2 -2
  166. package/src/__tests__/skills.test.ts +1 -1
  167. package/src/__tests__/slack-inbound-verification.test.ts +2 -6
  168. package/src/__tests__/strip-memory-injections.test.ts +187 -0
  169. package/src/__tests__/subagent-detail.test.ts +84 -0
  170. package/src/__tests__/subagent-disposal.test.ts +308 -0
  171. package/src/__tests__/subagent-manager-notify.test.ts +19 -10
  172. package/src/__tests__/subagent-notify-parent.test.ts +390 -0
  173. package/src/__tests__/subagent-role-registry.test.ts +108 -0
  174. package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
  175. package/src/__tests__/subagent-tools.test.ts +464 -4
  176. package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
  177. package/src/__tests__/task-compiler.test.ts +2 -6
  178. package/src/__tests__/task-management-tools.test.ts +2 -6
  179. package/src/__tests__/task-memory-cleanup.test.ts +185 -241
  180. package/src/__tests__/task-runner.test.ts +2 -6
  181. package/src/__tests__/task-scheduler.test.ts +2 -6
  182. package/src/__tests__/terminal-tools.test.ts +17 -27
  183. package/src/__tests__/test-preload.ts +7 -0
  184. package/src/__tests__/tool-approval-handler.test.ts +2 -6
  185. package/src/__tests__/tool-executor.test.ts +4 -26
  186. package/src/__tests__/tool-grant-request-escalation.test.ts +2 -6
  187. package/src/__tests__/tool-side-effects-slack-dm.test.ts +277 -0
  188. package/src/__tests__/top-level-renderer.test.ts +10 -13
  189. package/src/__tests__/trust-store.test.ts +1 -1
  190. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +2 -6
  191. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +118 -8
  192. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -6
  193. package/src/__tests__/trusted-contact-verification.test.ts +2 -6
  194. package/src/__tests__/turn-boundary-resolution.test.ts +2 -6
  195. package/src/__tests__/usage-cache-backfill-migration.test.ts +1 -6
  196. package/src/__tests__/usage-routes.test.ts +2 -6
  197. package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
  198. package/src/__tests__/voice-invite-redemption.test.ts +2 -6
  199. package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -6
  200. package/src/__tests__/voice-session-bridge.test.ts +2 -6
  201. package/src/__tests__/volume-security-guard.test.ts +2 -0
  202. package/src/__tests__/workspace-lifecycle.test.ts +29 -1
  203. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +2 -6
  204. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +2 -6
  205. package/src/__tests__/workspace-migration-026-backfill-install-meta.test.ts +558 -0
  206. package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
  207. package/src/__tests__/workspace-policy.test.ts +1 -1
  208. package/src/agent/attachments.ts +7 -2
  209. package/src/agent/image-optimize.ts +165 -0
  210. package/src/agent/loop.ts +7 -15
  211. package/src/approvals/guardian-request-resolvers.ts +24 -0
  212. package/src/avatar/traits-png-sync.ts +3 -3
  213. package/src/bundler/app-compiler.ts +179 -2
  214. package/src/bundler/package-resolver.ts +3 -5
  215. package/src/cli/__tests__/notifications.test.ts +1 -2
  216. package/src/cli/__tests__/run-assistant-command.ts +29 -0
  217. package/src/cli/commands/__tests__/email-download.test.ts +245 -0
  218. package/src/cli/commands/__tests__/email-list.test.ts +192 -0
  219. package/src/cli/commands/__tests__/email-register.test.ts +186 -0
  220. package/src/cli/commands/__tests__/email-send.test.ts +291 -0
  221. package/src/cli/commands/__tests__/email-status.test.ts +181 -0
  222. package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
  223. package/src/cli/commands/__tests__/routes.test.ts +562 -0
  224. package/src/cli/commands/avatar.ts +3 -3
  225. package/src/cli/commands/config.ts +26 -13
  226. package/src/cli/commands/conversations.ts +1 -8
  227. package/src/cli/commands/doctor.ts +2 -2
  228. package/src/cli/commands/email.ts +584 -835
  229. package/src/cli/commands/memory.ts +37 -84
  230. package/src/cli/commands/notifications.ts +7 -2
  231. package/src/cli/commands/oauth/__tests__/connect.test.ts +2 -2
  232. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +2 -2
  233. package/src/cli/commands/oauth/__tests__/mode.test.ts +8 -1
  234. package/src/cli/commands/oauth/__tests__/status.test.ts +2 -2
  235. package/src/cli/commands/oauth/connect.ts +25 -11
  236. package/src/cli/commands/oauth/mode.ts +7 -0
  237. package/src/cli/commands/oauth/shared.ts +39 -3
  238. package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
  239. package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
  240. package/src/cli/commands/platform/__tests__/status.test.ts +5 -5
  241. package/src/cli/commands/platform/index.ts +16 -16
  242. package/src/cli/commands/routes.ts +396 -0
  243. package/src/cli/commands/skills.ts +218 -36
  244. package/src/cli/commands/trust.ts +2 -2
  245. package/src/cli/lib/daemon-credential-client.ts +2 -3
  246. package/src/cli/program.ts +2 -0
  247. package/src/cli.ts +1 -120
  248. package/src/config/bundled-skills/acp/TOOLS.json +1 -1
  249. package/src/config/bundled-skills/app-builder/SKILL.md +4 -1
  250. package/src/config/bundled-skills/contacts/SKILL.md +0 -1
  251. package/src/config/bundled-skills/contacts/TOOLS.json +0 -8
  252. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +0 -4
  253. package/src/config/bundled-skills/gmail/SKILL.md +4 -12
  254. package/src/config/bundled-skills/google-calendar/SKILL.md +1 -9
  255. package/src/config/bundled-skills/messaging/SKILL.md +17 -18
  256. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +40 -33
  257. package/src/config/bundled-skills/outlook/SKILL.md +189 -0
  258. package/src/config/bundled-skills/outlook/TOOLS.json +530 -0
  259. package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +85 -0
  260. package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +77 -0
  261. package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +84 -0
  262. package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +94 -0
  263. package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +49 -0
  264. package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +237 -0
  265. package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +161 -0
  266. package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +32 -0
  267. package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +272 -0
  268. package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +29 -0
  269. package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +129 -0
  270. package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +87 -0
  271. package/src/config/bundled-skills/outlook/tools/shared.ts +20 -0
  272. package/src/config/bundled-skills/outlook-calendar/SKILL.md +51 -0
  273. package/src/config/bundled-skills/outlook-calendar/TOOLS.json +221 -0
  274. package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +252 -0
  275. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +53 -0
  276. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +74 -0
  277. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +18 -0
  278. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +46 -0
  279. package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +36 -0
  280. package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +17 -0
  281. package/src/config/bundled-skills/outlook-calendar/types.ts +120 -0
  282. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +47 -40
  283. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +16 -29
  284. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +16 -18
  285. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +39 -47
  286. package/src/config/bundled-skills/schedule/SKILL.md +22 -2
  287. package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
  288. package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
  289. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
  290. package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
  291. package/src/config/bundled-skills/slack/SKILL.md +3 -7
  292. package/src/config/bundled-skills/subagent/SKILL.md +43 -3
  293. package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
  294. package/src/config/bundled-tool-registry.ts +56 -4
  295. package/src/config/env-registry.ts +78 -8
  296. package/src/config/feature-flag-registry.json +38 -125
  297. package/src/config/schema.ts +8 -0
  298. package/src/config/schemas/filing.ts +51 -0
  299. package/src/config/schemas/heartbeat.ts +15 -12
  300. package/src/config/schemas/memory-lifecycle.ts +12 -0
  301. package/src/config/schemas/platform.ts +8 -0
  302. package/src/config/schemas/security.ts +14 -0
  303. package/src/config/schemas/timeouts.ts +1 -1
  304. package/src/config/skills.ts +18 -7
  305. package/src/context/token-estimator.ts +25 -18
  306. package/src/context/window-manager.ts +6 -2
  307. package/src/credential-execution/process-manager.ts +3 -1
  308. package/src/daemon/app-source-watcher.ts +93 -0
  309. package/src/daemon/config-watcher.ts +79 -1
  310. package/src/daemon/context-overflow-reducer.ts +46 -2
  311. package/src/daemon/conversation-agent-loop-handlers.ts +143 -82
  312. package/src/daemon/conversation-agent-loop.ts +236 -108
  313. package/src/daemon/conversation-error.ts +31 -8
  314. package/src/daemon/conversation-history.ts +4 -19
  315. package/src/daemon/conversation-lifecycle.ts +36 -9
  316. package/src/daemon/conversation-media-retry.ts +85 -7
  317. package/src/daemon/conversation-notifiers.ts +4 -1
  318. package/src/daemon/conversation-process.ts +13 -7
  319. package/src/daemon/conversation-runtime-assembly.ts +305 -306
  320. package/src/daemon/conversation-tool-setup.ts +44 -14
  321. package/src/daemon/conversation-workspace.ts +1 -2
  322. package/src/daemon/conversation.ts +59 -2
  323. package/src/daemon/daemon-control.ts +8 -2
  324. package/src/daemon/date-context.ts +26 -53
  325. package/src/daemon/first-greeting.ts +1 -1
  326. package/src/daemon/handlers/conversations.ts +4 -7
  327. package/src/daemon/handlers/shared.test.ts +143 -0
  328. package/src/daemon/handlers/shared.ts +85 -17
  329. package/src/daemon/handlers/skills.ts +416 -209
  330. package/src/daemon/lifecycle.ts +212 -131
  331. package/src/daemon/main.ts +5 -1
  332. package/src/daemon/message-types/conversations.ts +29 -7
  333. package/src/daemon/message-types/messages.ts +12 -2
  334. package/src/daemon/message-types/schedules.ts +1 -0
  335. package/src/daemon/message-types/settings.ts +6 -0
  336. package/src/daemon/message-types/skills.ts +97 -36
  337. package/src/daemon/profiler-run-store.ts +557 -0
  338. package/src/daemon/providers-setup.ts +5 -0
  339. package/src/daemon/server.ts +100 -11
  340. package/src/daemon/shutdown-handlers.ts +5 -0
  341. package/src/daemon/tool-side-effects.ts +50 -8
  342. package/src/export/transcript-formatter.ts +148 -0
  343. package/src/filing/filing-service.ts +228 -0
  344. package/src/heartbeat/heartbeat-service.ts +97 -7
  345. package/src/hooks/cli.ts +2 -2
  346. package/src/hooks/runner.ts +15 -38
  347. package/src/inbound/platform-callback-registration.ts +14 -14
  348. package/src/mcp/client.ts +6 -0
  349. package/src/mcp/mcp-oauth-provider.ts +149 -27
  350. package/src/memory/admin.ts +42 -75
  351. package/src/memory/app-store.ts +69 -0
  352. package/src/memory/conversation-bootstrap.ts +3 -1
  353. package/src/memory/conversation-crud.ts +211 -288
  354. package/src/memory/conversation-group-migration.ts +157 -0
  355. package/src/memory/conversation-queries.ts +61 -13
  356. package/src/memory/conversation-title-service.ts +1 -0
  357. package/src/memory/db-init.ts +194 -361
  358. package/src/memory/embed.ts +73 -0
  359. package/src/memory/embedding-backend.ts +8 -14
  360. package/src/memory/embedding-runtime-manager.ts +12 -114
  361. package/src/memory/fingerprint.ts +2 -2
  362. package/src/memory/graph/bootstrap.ts +521 -0
  363. package/src/memory/graph/capability-seed.ts +449 -0
  364. package/src/memory/graph/consolidation.ts +725 -0
  365. package/src/memory/graph/conversation-graph-memory.ts +659 -0
  366. package/src/memory/graph/decay.test.ts +208 -0
  367. package/src/memory/graph/decay.ts +195 -0
  368. package/src/memory/graph/extraction-job.ts +74 -0
  369. package/src/memory/graph/extraction.test.ts +936 -0
  370. package/src/memory/graph/extraction.ts +1297 -0
  371. package/src/memory/graph/graph-memory-state-store.ts +37 -0
  372. package/src/memory/graph/graph-search.ts +280 -0
  373. package/src/memory/graph/image-ref-utils.ts +29 -0
  374. package/src/memory/graph/injection.test.ts +513 -0
  375. package/src/memory/graph/injection.ts +469 -0
  376. package/src/memory/graph/inspect.ts +543 -0
  377. package/src/memory/graph/narrative.ts +267 -0
  378. package/src/memory/graph/pattern-scan.ts +269 -0
  379. package/src/memory/graph/retriever.ts +1111 -0
  380. package/src/memory/graph/scoring.test.ts +548 -0
  381. package/src/memory/graph/scoring.ts +232 -0
  382. package/src/memory/graph/serendipity.ts +65 -0
  383. package/src/memory/graph/store.test.ts +1098 -0
  384. package/src/memory/graph/store.ts +838 -0
  385. package/src/memory/graph/tool-handlers.ts +301 -0
  386. package/src/memory/graph/tools.ts +97 -0
  387. package/src/memory/graph/triggers.test.ts +487 -0
  388. package/src/memory/graph/triggers.ts +223 -0
  389. package/src/memory/graph/types.ts +295 -0
  390. package/src/memory/group-crud.ts +191 -0
  391. package/src/memory/indexer.ts +37 -19
  392. package/src/memory/job-handlers/cleanup.ts +32 -42
  393. package/src/memory/job-handlers/conversation-starters.ts +91 -53
  394. package/src/memory/job-handlers/embedding.ts +5 -31
  395. package/src/memory/job-handlers/index-maintenance.ts +23 -11
  396. package/src/memory/job-handlers/summarization.ts +32 -17
  397. package/src/memory/job-utils.ts +1 -1
  398. package/src/memory/jobs-store.ts +21 -31
  399. package/src/memory/jobs-worker.ts +180 -129
  400. package/src/memory/llm-request-log-store.ts +96 -12
  401. package/src/memory/memory-recall-log-store.ts +49 -5
  402. package/src/memory/message-content.ts +1 -0
  403. package/src/memory/migrations/202-memory-graph-tables.ts +130 -0
  404. package/src/memory/migrations/203-drop-memory-items-tables.ts +55 -0
  405. package/src/memory/migrations/204-rename-memory-graph-type-values.ts +46 -0
  406. package/src/memory/migrations/205-memory-graph-image-refs.ts +11 -0
  407. package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
  408. package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
  409. package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
  410. package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
  411. package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
  412. package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
  413. package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
  414. package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
  415. package/src/memory/migrations/index.ts +12 -0
  416. package/src/memory/migrations/registry.ts +16 -0
  417. package/src/memory/qdrant-client.ts +44 -17
  418. package/src/memory/schema/conversations.ts +14 -0
  419. package/src/memory/schema/index.ts +1 -0
  420. package/src/memory/schema/infrastructure.ts +8 -1
  421. package/src/memory/schema/memory-core.ts +0 -51
  422. package/src/memory/schema/memory-graph.ts +154 -0
  423. package/src/memory/search/semantic.ts +47 -91
  424. package/src/memory/task-memory-cleanup.ts +58 -61
  425. package/src/messaging/providers/outlook/adapter.ts +8 -1
  426. package/src/messaging/providers/outlook/client.ts +299 -0
  427. package/src/messaging/providers/outlook/types.ts +118 -0
  428. package/src/notifications/adapters/macos.ts +1 -0
  429. package/src/notifications/copy-composer.ts +95 -0
  430. package/src/notifications/decision-engine.ts +35 -0
  431. package/src/notifications/signal.ts +16 -0
  432. package/src/oauth/seed-providers.ts +2 -1
  433. package/src/permissions/checker.ts +36 -4
  434. package/src/permissions/defaults.ts +4 -4
  435. package/src/permissions/permission-mode-store.ts +180 -0
  436. package/src/permissions/permission-mode.ts +31 -0
  437. package/src/permissions/workspace-policy.ts +10 -1
  438. package/src/playbooks/playbook-compiler.ts +19 -18
  439. package/src/playbooks/types.ts +4 -3
  440. package/src/prompts/system-prompt.ts +62 -36
  441. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
  442. package/src/prompts/templates/BOOTSTRAP.md +70 -165
  443. package/src/prompts/templates/HEARTBEAT.md +3 -1
  444. package/src/prompts/templates/SOUL.md +25 -4
  445. package/src/prompts/templates/UPDATES.md +8 -0
  446. package/src/providers/anthropic/client.ts +136 -220
  447. package/src/providers/gemini/client.ts +1 -1
  448. package/src/providers/openai/client.ts +1 -1
  449. package/src/providers/registry.ts +1 -1
  450. package/src/providers/retry.ts +19 -3
  451. package/src/runtime/actor-trust-resolver.ts +5 -1
  452. package/src/runtime/auth/route-policy.ts +30 -0
  453. package/src/runtime/guardian-reply-router.ts +5 -1
  454. package/src/runtime/http-server.ts +55 -5
  455. package/src/runtime/http-types.ts +12 -1
  456. package/src/runtime/middleware/auth.ts +20 -0
  457. package/src/runtime/migrations/vbundle-builder.ts +389 -3
  458. package/src/runtime/migrations/vbundle-importer.ts +8 -6
  459. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
  460. package/src/runtime/routes/app-management-routes.ts +1 -11
  461. package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
  462. package/src/runtime/routes/archive-utils.ts +29 -0
  463. package/src/runtime/routes/attachment-routes.test.ts +106 -0
  464. package/src/runtime/routes/attachment-routes.ts +106 -16
  465. package/src/runtime/routes/avatar-routes.ts +2 -9
  466. package/src/runtime/routes/brain-graph-routes.ts +21 -22
  467. package/src/runtime/routes/btw-routes.ts +22 -1
  468. package/src/runtime/routes/conversation-analysis-routes.ts +173 -0
  469. package/src/runtime/routes/conversation-management-routes.ts +3 -14
  470. package/src/runtime/routes/conversation-query-routes.ts +49 -3
  471. package/src/runtime/routes/conversation-routes.ts +264 -44
  472. package/src/runtime/routes/conversation-starter-routes.ts +2 -2
  473. package/src/runtime/routes/debug-routes.ts +1 -1
  474. package/src/runtime/routes/global-search-routes.ts +21 -19
  475. package/src/runtime/routes/group-routes.ts +207 -0
  476. package/src/runtime/routes/guardian-action-routes.ts +21 -10
  477. package/src/runtime/routes/guardian-bootstrap-routes.ts +23 -19
  478. package/src/runtime/routes/heartbeat-routes.ts +4 -10
  479. package/src/runtime/routes/identity-routes.ts +53 -18
  480. package/src/runtime/routes/inbound-message-handler.ts +19 -0
  481. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +292 -0
  482. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +207 -0
  483. package/src/runtime/routes/llm-context-normalization.ts +14 -10
  484. package/src/runtime/routes/log-export-routes.ts +23 -275
  485. package/src/runtime/routes/memory-item-routes.test.ts +170 -247
  486. package/src/runtime/routes/memory-item-routes.ts +341 -388
  487. package/src/runtime/routes/migration-routes.ts +18 -7
  488. package/src/runtime/routes/profiler-routes.ts +350 -0
  489. package/src/runtime/routes/schedule-routes.ts +28 -11
  490. package/src/runtime/routes/settings-routes.ts +95 -8
  491. package/src/runtime/routes/skills-routes.ts +103 -37
  492. package/src/runtime/routes/subagents-routes.ts +28 -7
  493. package/src/runtime/routes/user-route-dispatcher.ts +223 -0
  494. package/src/runtime/routes/user-routes.ts +41 -0
  495. package/src/runtime/routes/work-items-routes.test.ts +2 -6
  496. package/src/runtime/routes/workspace-routes.ts +0 -1
  497. package/src/schedule/schedule-store.ts +30 -0
  498. package/src/schedule/scheduler.ts +52 -18
  499. package/src/security/oauth2.ts +1 -1
  500. package/src/security/secure-keys.ts +4 -8
  501. package/src/shared/provider-env-vars.ts +19 -0
  502. package/src/skills/catalog-cache.ts +5 -0
  503. package/src/skills/catalog-install.ts +25 -16
  504. package/src/skills/clawhub.ts +134 -154
  505. package/src/skills/install-meta.ts +208 -0
  506. package/src/skills/managed-store.ts +29 -18
  507. package/src/skills/skill-memory.ts +12 -229
  508. package/src/skills/skillssh-registry.ts +19 -17
  509. package/src/subagent/index.ts +13 -3
  510. package/src/subagent/manager.ts +308 -29
  511. package/src/subagent/types.ts +68 -0
  512. package/src/tasks/task-runner.ts +7 -5
  513. package/src/telemetry/usage-telemetry-reporter.test.ts +3 -5
  514. package/src/tools/apps/executors.ts +29 -4
  515. package/src/tools/browser/runtime-check.ts +3 -1
  516. package/src/tools/filesystem/list.ts +93 -0
  517. package/src/tools/memory/register.ts +63 -46
  518. package/src/tools/permission-checker.ts +85 -1
  519. package/src/tools/registry.ts +4 -0
  520. package/src/tools/schedule/create.ts +3 -0
  521. package/src/tools/schedule/list.ts +1 -0
  522. package/src/tools/schedule/update.ts +6 -0
  523. package/src/tools/shared/filesystem/errors.ts +5 -0
  524. package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
  525. package/src/tools/shared/filesystem/image-read.ts +22 -85
  526. package/src/tools/shared/filesystem/types.ts +17 -0
  527. package/src/tools/shared/shell-output.ts +31 -2
  528. package/src/tools/subagent/abort.ts +12 -2
  529. package/src/tools/subagent/message.ts +9 -2
  530. package/src/tools/subagent/notify-parent.ts +79 -0
  531. package/src/tools/subagent/read.ts +29 -8
  532. package/src/tools/subagent/resolve.ts +21 -0
  533. package/src/tools/subagent/spawn.ts +2 -0
  534. package/src/tools/subagent/status.ts +11 -1
  535. package/src/tools/system/avatar-generator.ts +3 -3
  536. package/src/tools/system/register.ts +23 -0
  537. package/src/tools/system/set-permission-mode.ts +103 -0
  538. package/src/tools/terminal/parser.ts +30 -5
  539. package/src/tools/terminal/safe-env.ts +17 -1
  540. package/src/tools/tool-manifest.ts +9 -3
  541. package/src/tools/types.ts +2 -0
  542. package/src/util/browser.ts +25 -10
  543. package/src/util/bun-runtime.ts +172 -0
  544. package/src/util/logger.ts +1 -1
  545. package/src/util/platform.ts +50 -17
  546. package/src/watcher/providers/outlook-calendar.ts +343 -0
  547. package/src/watcher/providers/outlook.ts +198 -0
  548. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
  549. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
  550. package/src/workspace/migrations/025-remove-oauth-app-setup-skills.ts +76 -0
  551. package/src/workspace/migrations/026-backfill-install-meta.ts +325 -0
  552. package/src/workspace/migrations/027-remove-orphaned-optimized-images-cache.ts +42 -0
  553. package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
  554. package/src/workspace/migrations/029-seed-pkb.ts +84 -0
  555. package/src/workspace/migrations/registry.ts +10 -0
  556. package/src/workspace/top-level-renderer.ts +5 -9
  557. package/src/__tests__/cli-memory.test.ts +0 -372
  558. package/src/__tests__/clipboard.test.ts +0 -88
  559. package/src/__tests__/context-memory-e2e.test.ts +0 -415
  560. package/src/__tests__/journal-context.test.ts +0 -268
  561. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -297
  562. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -459
  563. package/src/__tests__/memory-query-builder.test.ts +0 -59
  564. package/src/__tests__/memory-recall-quality.test.ts +0 -1046
  565. package/src/__tests__/memory-regressions.experimental.test.ts +0 -629
  566. package/src/__tests__/memory-regressions.test.ts +0 -3696
  567. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -295
  568. package/src/cli/cli-memory.ts +0 -176
  569. package/src/daemon/conversation-memory.ts +0 -207
  570. package/src/memory/conversation-starters-cadence.ts +0 -74
  571. package/src/memory/items-extractor.ts +0 -860
  572. package/src/memory/job-handlers/batch-extraction.ts +0 -753
  573. package/src/memory/job-handlers/extraction.ts +0 -40
  574. package/src/memory/job-handlers/journal-carry-forward.test.ts +0 -355
  575. package/src/memory/job-handlers/journal-carry-forward.ts +0 -255
  576. package/src/memory/journal-memory.ts +0 -224
  577. package/src/memory/query-builder.ts +0 -47
  578. package/src/memory/query-expansion.ts +0 -83
  579. package/src/memory/retriever.test.ts +0 -1592
  580. package/src/memory/retriever.ts +0 -1331
  581. package/src/memory/search/formatting.test.ts +0 -140
  582. package/src/memory/search/formatting.ts +0 -262
  583. package/src/memory/search/mmr.ts +0 -139
  584. package/src/memory/search/ranking.ts +0 -15
  585. package/src/memory/search/staleness.ts +0 -40
  586. package/src/memory/search/tier-classifier.ts +0 -18
  587. package/src/memory/search/types.ts +0 -121
  588. package/src/prompts/journal-context.ts +0 -154
  589. package/src/tools/memory/definitions.ts +0 -69
  590. package/src/tools/memory/handlers.test.ts +0 -562
  591. package/src/tools/memory/handlers.ts +0 -434
  592. package/src/util/clipboard.ts +0 -34
@@ -0,0 +1,335 @@
1
+ /**
2
+ * Tests for the trusted contact lifecycle fallback copy templates.
3
+ *
4
+ * Verifies that `ingress.trusted_contact.guardian_decision` and
5
+ * `ingress.trusted_contact.denied` templates in copy-composer.ts render
6
+ * display names when available and fall back to Slack <@ID> mention format
7
+ * for raw user IDs on the Slack source channel.
8
+ */
9
+ import { describe, expect, test } from "bun:test";
10
+
11
+ import { composeFallbackCopy } from "../notifications/copy-composer.js";
12
+ import type { NotificationSignal } from "../notifications/signal.js";
13
+
14
+ // ── Helpers ──────────────────────────────────────────────────────────────────
15
+
16
+ function buildGuardianDecisionSignal(
17
+ payloadOverrides: Record<string, unknown> = {},
18
+ sourceChannel: "slack" | "telegram" | "vellum" = "slack",
19
+ ): NotificationSignal {
20
+ return {
21
+ signalId: "test-signal-gd",
22
+ createdAt: Date.now(),
23
+ sourceChannel,
24
+ sourceContextId: "test-ctx-1",
25
+ sourceEventName: "ingress.trusted_contact.guardian_decision",
26
+ contextPayload: {
27
+ sourceChannel,
28
+ requesterExternalUserId: "U07CLDQ4TB3",
29
+ requesterChatId: "D0AQ9C5PPPF",
30
+ decidedByExternalUserId: "U099H19C0KA",
31
+ requesterDisplayName: null,
32
+ decidedByDisplayName: null,
33
+ decision: "denied",
34
+ ...payloadOverrides,
35
+ },
36
+ attentionHints: {
37
+ requiresAction: false,
38
+ urgency: "medium",
39
+ isAsyncBackground: false,
40
+ visibleInSourceNow: false,
41
+ },
42
+ };
43
+ }
44
+
45
+ function buildDeniedSignal(
46
+ payloadOverrides: Record<string, unknown> = {},
47
+ sourceChannel: "slack" | "telegram" | "vellum" = "slack",
48
+ ): NotificationSignal {
49
+ return {
50
+ signalId: "test-signal-denied",
51
+ createdAt: Date.now(),
52
+ sourceChannel,
53
+ sourceContextId: "test-ctx-2",
54
+ sourceEventName: "ingress.trusted_contact.denied",
55
+ contextPayload: {
56
+ sourceChannel,
57
+ requesterExternalUserId: "U07CLDQ4TB3",
58
+ requesterChatId: "D0AQ9C5PPPF",
59
+ decidedByExternalUserId: "U099H19C0KA",
60
+ requesterDisplayName: null,
61
+ decidedByDisplayName: null,
62
+ decision: "denied",
63
+ ...payloadOverrides,
64
+ },
65
+ attentionHints: {
66
+ requiresAction: false,
67
+ urgency: "low",
68
+ isAsyncBackground: false,
69
+ visibleInSourceNow: false,
70
+ },
71
+ };
72
+ }
73
+
74
+ // ── guardian_decision template ────────────────────────────────────────────────
75
+
76
+ describe("guardian_decision fallback copy", () => {
77
+ test("uses display names when both are present", () => {
78
+ const signal = buildGuardianDecisionSignal({
79
+ requesterDisplayName: "Alice",
80
+ decidedByDisplayName: "Bob",
81
+ decision: "denied",
82
+ });
83
+ const result = composeFallbackCopy(signal, ["vellum"]);
84
+ const copy = result.vellum!;
85
+
86
+ expect(copy.title).toBe("Trusted Contact Decision");
87
+ expect(copy.body).toBe("Alice's access request has been denied by Bob.");
88
+ });
89
+
90
+ test("falls back to Slack <@ID> mention format when display names are absent on Slack", () => {
91
+ const signal = buildGuardianDecisionSignal({
92
+ requesterDisplayName: null,
93
+ decidedByDisplayName: null,
94
+ requesterExternalUserId: "U07CLDQ4TB3",
95
+ decidedByExternalUserId: "U099H19C0KA",
96
+ sourceChannel: "slack",
97
+ decision: "denied",
98
+ });
99
+ const result = composeFallbackCopy(signal, ["vellum"]);
100
+ const copy = result.vellum!;
101
+
102
+ expect(copy.body).toBe(
103
+ "<@U07CLDQ4TB3>'s access request has been denied by <@U099H19C0KA>.",
104
+ );
105
+ });
106
+
107
+ test("uses raw user IDs without Slack formatting on non-Slack channels", () => {
108
+ const signal = buildGuardianDecisionSignal(
109
+ {
110
+ requesterDisplayName: null,
111
+ decidedByDisplayName: null,
112
+ requesterExternalUserId: "U07CLDQ4TB3",
113
+ decidedByExternalUserId: "U099H19C0KA",
114
+ sourceChannel: "telegram",
115
+ decision: "denied",
116
+ },
117
+ "telegram",
118
+ );
119
+ const result = composeFallbackCopy(signal, ["vellum"]);
120
+ const copy = result.vellum!;
121
+
122
+ expect(copy.body).toBe(
123
+ "U07CLDQ4TB3's access request has been denied by U099H19C0KA.",
124
+ );
125
+ expect(copy.body).not.toContain("<@");
126
+ });
127
+
128
+ test("produces distinct copy for approved vs denied decisions", () => {
129
+ const deniedSignal = buildGuardianDecisionSignal({
130
+ requesterDisplayName: "Alice",
131
+ decidedByDisplayName: "Bob",
132
+ decision: "denied",
133
+ });
134
+ const approvedSignal = buildGuardianDecisionSignal({
135
+ requesterDisplayName: "Alice",
136
+ decidedByDisplayName: "Bob",
137
+ decision: "approved",
138
+ });
139
+
140
+ const deniedCopy = composeFallbackCopy(deniedSignal, ["vellum"]).vellum!;
141
+ const approvedCopy = composeFallbackCopy(approvedSignal, [
142
+ "vellum",
143
+ ]).vellum!;
144
+
145
+ expect(deniedCopy.body).toContain("denied");
146
+ expect(deniedCopy.body).not.toContain("approved");
147
+ expect(approvedCopy.body).toContain("approved");
148
+ expect(approvedCopy.body).not.toContain("denied");
149
+ });
150
+
151
+ test("does not expose raw conversation IDs (requesterChatId) in output", () => {
152
+ const signal = buildGuardianDecisionSignal({
153
+ requesterDisplayName: null,
154
+ decidedByDisplayName: null,
155
+ requesterChatId: "D0AQ9C5PPPF",
156
+ requesterExternalUserId: "U07CLDQ4TB3",
157
+ decidedByExternalUserId: "U099H19C0KA",
158
+ });
159
+ const result = composeFallbackCopy(signal, ["vellum"]);
160
+ const copy = result.vellum!;
161
+
162
+ expect(copy.body).not.toContain("D0AQ9C5PPPF");
163
+ });
164
+
165
+ test("falls back to 'Someone' when requester identity is entirely absent", () => {
166
+ const signal = buildGuardianDecisionSignal({
167
+ requesterDisplayName: null,
168
+ requesterExternalUserId: null,
169
+ decidedByDisplayName: "Bob",
170
+ decision: "denied",
171
+ });
172
+ const result = composeFallbackCopy(signal, ["vellum"]);
173
+ const copy = result.vellum!;
174
+
175
+ expect(copy.body).toBe("Someone's access request has been denied by Bob.");
176
+ });
177
+
178
+ test("falls back to 'a guardian' when decider identity is entirely absent", () => {
179
+ const signal = buildGuardianDecisionSignal({
180
+ requesterDisplayName: "Alice",
181
+ decidedByDisplayName: null,
182
+ decidedByExternalUserId: null,
183
+ decision: "approved",
184
+ });
185
+ const result = composeFallbackCopy(signal, ["vellum"]);
186
+ const copy = result.vellum!;
187
+
188
+ expect(copy.body).toBe(
189
+ "Alice's access request has been approved by a guardian.",
190
+ );
191
+ });
192
+
193
+ test("prefers display name over Slack user ID when both are present", () => {
194
+ const signal = buildGuardianDecisionSignal({
195
+ requesterDisplayName: "Alice",
196
+ requesterExternalUserId: "U07CLDQ4TB3",
197
+ decidedByDisplayName: "Bob",
198
+ decidedByExternalUserId: "U099H19C0KA",
199
+ sourceChannel: "slack",
200
+ decision: "denied",
201
+ });
202
+ const result = composeFallbackCopy(signal, ["vellum"]);
203
+ const copy = result.vellum!;
204
+
205
+ expect(copy.body).toBe("Alice's access request has been denied by Bob.");
206
+ expect(copy.body).not.toContain("<@");
207
+ });
208
+
209
+ test("sanitizes control characters from display names", () => {
210
+ const signal = buildGuardianDecisionSignal({
211
+ requesterDisplayName: "Alice\x00\x07\nEvil",
212
+ decidedByDisplayName: "Bob\r\nMalicious",
213
+ decision: "approved",
214
+ });
215
+ const result = composeFallbackCopy(signal, ["vellum"]);
216
+ const copy = result.vellum!;
217
+
218
+ expect(copy.body).not.toMatch(/[\x00-\x1f\x7f-\x9f]/);
219
+ expect(copy.body).toContain("Alice");
220
+ expect(copy.body).toContain("Bob");
221
+ });
222
+
223
+ test("clamps excessively long display names", () => {
224
+ const longName = "A".repeat(200);
225
+ const signal = buildGuardianDecisionSignal({
226
+ requesterDisplayName: longName,
227
+ decidedByDisplayName: "Bob",
228
+ decision: "denied",
229
+ });
230
+ const result = composeFallbackCopy(signal, ["vellum"]);
231
+ const copy = result.vellum!;
232
+
233
+ // sanitizeIdentityField clamps to 120 chars + ellipsis
234
+ expect(copy.body.length).toBeLessThan(longName.length);
235
+ expect(copy.body).toContain("…");
236
+ });
237
+ });
238
+
239
+ // ── denied template ──────────────────────────────────────────────────────────
240
+
241
+ describe("trusted_contact.denied fallback copy", () => {
242
+ test("uses display name when present", () => {
243
+ const signal = buildDeniedSignal({
244
+ requesterDisplayName: "Alice",
245
+ });
246
+ const result = composeFallbackCopy(signal, ["vellum"]);
247
+ const copy = result.vellum!;
248
+
249
+ expect(copy.title).toBe("Trusted Contact Denied");
250
+ expect(copy.body).toBe(
251
+ "A trusted contact request from Alice has been denied.",
252
+ );
253
+ });
254
+
255
+ test("falls back to Slack <@ID> mention format on Slack when display name absent", () => {
256
+ const signal = buildDeniedSignal({
257
+ requesterDisplayName: null,
258
+ requesterExternalUserId: "U07CLDQ4TB3",
259
+ sourceChannel: "slack",
260
+ });
261
+ const result = composeFallbackCopy(signal, ["vellum"]);
262
+ const copy = result.vellum!;
263
+
264
+ expect(copy.body).toBe(
265
+ "A trusted contact request from <@U07CLDQ4TB3> has been denied.",
266
+ );
267
+ });
268
+
269
+ test("uses raw user ID without Slack formatting on non-Slack channels", () => {
270
+ const signal = buildDeniedSignal(
271
+ {
272
+ requesterDisplayName: null,
273
+ requesterExternalUserId: "U07CLDQ4TB3",
274
+ sourceChannel: "telegram",
275
+ },
276
+ "telegram",
277
+ );
278
+ const result = composeFallbackCopy(signal, ["vellum"]);
279
+ const copy = result.vellum!;
280
+
281
+ expect(copy.body).toBe(
282
+ "A trusted contact request from U07CLDQ4TB3 has been denied.",
283
+ );
284
+ expect(copy.body).not.toContain("<@");
285
+ });
286
+
287
+ test("falls back to 'Someone' when requester identity is absent", () => {
288
+ const signal = buildDeniedSignal({
289
+ requesterDisplayName: null,
290
+ requesterExternalUserId: null,
291
+ });
292
+ const result = composeFallbackCopy(signal, ["vellum"]);
293
+ const copy = result.vellum!;
294
+
295
+ expect(copy.body).toBe(
296
+ "A trusted contact request from Someone has been denied.",
297
+ );
298
+ });
299
+
300
+ test("does not expose raw conversation IDs (requesterChatId) in output", () => {
301
+ const signal = buildDeniedSignal({
302
+ requesterDisplayName: null,
303
+ requesterExternalUserId: "U07CLDQ4TB3",
304
+ requesterChatId: "D0AQ9C5PPPF",
305
+ });
306
+ const result = composeFallbackCopy(signal, ["vellum"]);
307
+ const copy = result.vellum!;
308
+
309
+ expect(copy.body).not.toContain("D0AQ9C5PPPF");
310
+ });
311
+
312
+ test("sanitizes control characters from display names", () => {
313
+ const signal = buildDeniedSignal({
314
+ requesterDisplayName: "Alice\x00\x07\nEvil",
315
+ });
316
+ const result = composeFallbackCopy(signal, ["vellum"]);
317
+ const copy = result.vellum!;
318
+
319
+ expect(copy.body).not.toMatch(/[\x00-\x1f\x7f-\x9f]/);
320
+ expect(copy.body).toContain("Alice");
321
+ });
322
+
323
+ test("clamps excessively long display names", () => {
324
+ const longName = "A".repeat(200);
325
+ const signal = buildDeniedSignal({
326
+ requesterDisplayName: longName,
327
+ });
328
+ const result = composeFallbackCopy(signal, ["vellum"]);
329
+ const copy = result.vellum!;
330
+
331
+ // sanitizeIdentityField clamps to 120 chars + ellipsis
332
+ expect(copy.body.length).toBeLessThan(longName.length);
333
+ expect(copy.body).toContain("…");
334
+ });
335
+ });
@@ -180,7 +180,7 @@ describe("CES flags do not affect unrelated flags", () => {
180
180
  expect(isAssistantFeatureFlagEnabled("browser", config)).toBe(true);
181
181
  });
182
182
 
183
- test("enabling all CES flags does not change contacts flag (defaultEnabled: true)", () => {
183
+ test("enabling all CES flags does not change sounds flag (defaultEnabled: true)", () => {
184
184
  const overrides: Record<string, boolean> = {};
185
185
  for (const key of ALL_CES_FLAG_KEYS) {
186
186
  overrides[key] = true;
@@ -188,7 +188,7 @@ describe("CES flags do not affect unrelated flags", () => {
188
188
  _setOverridesForTesting(overrides);
189
189
  const config = makeConfig();
190
190
 
191
- // contacts defaults to true in the registry and should stay true
192
- expect(isAssistantFeatureFlagEnabled("contacts", config)).toBe(true);
191
+ // sounds defaults to true in the registry and should stay true
192
+ expect(isAssistantFeatureFlagEnabled("sounds", config)).toBe(true);
193
193
  });
194
194
  });
@@ -33,8 +33,8 @@ describe("trust class categorization for CES lockdown", () => {
33
33
  expect(isUntrustedTrustClass("unknown")).toBe(true);
34
34
  });
35
35
 
36
- test("undefined is not untrusted", () => {
37
- expect(isUntrustedTrustClass(undefined)).toBe(false);
36
+ test("undefined is untrusted", () => {
37
+ expect(isUntrustedTrustClass(undefined)).toBe(true);
38
38
  });
39
39
  });
40
40
 
@@ -70,6 +70,8 @@ mock.module("../security/secure-keys.js", () => {
70
70
  accounts: [...storedKeys.keys()],
71
71
  unreachable: false,
72
72
  }),
73
+ getProviderKeyAsync: async () => undefined,
74
+ getMaskedProviderKey: async () => null,
73
75
  };
74
76
  });
75
77
 
@@ -1,219 +1,10 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
3
  import {
4
- buildTemporalContext,
5
4
  extractUserTimeZoneFromRecall,
5
+ formatTurnTimestamp,
6
6
  } from "../daemon/date-context.js";
7
7
 
8
- // Fixed timestamps for deterministic assertions (all UTC midday to avoid DST edge cases).
9
-
10
- /** Wednesday 2026-02-18 12:00 UTC */
11
- const WED_FEB_18 = Date.UTC(2026, 1, 18, 12, 0, 0);
12
-
13
- /** Tuesday 2026-12-29 12:00 UTC - year boundary */
14
- const TUE_DEC_29 = Date.UTC(2026, 11, 29, 12, 0, 0);
15
-
16
- // ---------------------------------------------------------------------------
17
- // Basic structure
18
- // ---------------------------------------------------------------------------
19
-
20
- describe("buildTemporalContext", () => {
21
- test("returns output wrapped in <temporal_context> tags", () => {
22
- const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: "UTC" });
23
- expect(result).toStartWith("<temporal_context>");
24
- expect(result).toEndWith("</temporal_context>");
25
- });
26
-
27
- test("includes today date, weekday, time and offset on one line", () => {
28
- const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: "UTC" });
29
- expect(result).toContain("Today: 2026-02-18 (Wed) 12:00 +00:00");
30
- });
31
-
32
- test("includes timezone", () => {
33
- const result = buildTemporalContext({
34
- nowMs: WED_FEB_18,
35
- timeZone: "America/New_York",
36
- });
37
- expect(result).toContain("TZ: America/New_York");
38
- });
39
-
40
- test("does not include UTC time, timezone source, or seconds", () => {
41
- const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: "UTC" });
42
- expect(result).not.toContain("Current UTC time");
43
- expect(result).not.toContain("Current local time");
44
- expect(result).not.toContain("Timezone source:");
45
- // No seconds in the time
46
- expect(result).not.toContain("12:00:00");
47
- });
48
-
49
- test("does not include week definitions, next weekend, next work week, or horizon dates", () => {
50
- const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: "UTC" });
51
- expect(result).not.toContain("Week definitions");
52
- expect(result).not.toContain("Next weekend");
53
- expect(result).not.toContain("Next work week");
54
- expect(result).not.toContain("Upcoming dates");
55
- });
56
-
57
- test("uses user timezone when provided", () => {
58
- const result = buildTemporalContext({
59
- nowMs: WED_FEB_18,
60
- hostTimeZone: "UTC",
61
- userTimeZone: "America/New_York",
62
- });
63
- expect(result).toContain("TZ: America/New_York");
64
- expect(result).toContain("Today: 2026-02-18 (Wed) 07:00 -05:00");
65
- expect(result).not.toContain("(host fallback)");
66
- });
67
-
68
- test("shows user TZ only when different from primary timezone", () => {
69
- // When user timezone equals the primary timezone, omit it
70
- const sameResult = buildTemporalContext({
71
- nowMs: WED_FEB_18,
72
- hostTimeZone: "UTC",
73
- configuredUserTimeZone: "UTC",
74
- });
75
- expect(sameResult).not.toContain("User TZ:");
76
-
77
- // When user timezone differs from host, it becomes the primary timezone
78
- // and the host timezone is shown as a secondary annotation
79
- const diffResult = buildTemporalContext({
80
- nowMs: WED_FEB_18,
81
- hostTimeZone: "UTC",
82
- userTimeZone: "America/New_York",
83
- });
84
- expect(diffResult).toContain("TZ: America/New_York");
85
- expect(diffResult).toContain("Host TZ: UTC");
86
- expect(diffResult).not.toContain("User TZ:");
87
- });
88
-
89
- test("shows host TZ only when different from primary timezone", () => {
90
- // When host timezone equals the primary timezone, omit it
91
- const sameResult = buildTemporalContext({
92
- nowMs: WED_FEB_18,
93
- hostTimeZone: "UTC",
94
- timeZone: "UTC",
95
- });
96
- expect(sameResult).not.toContain("Host TZ:");
97
-
98
- // When different, include it
99
- const diffResult = buildTemporalContext({
100
- nowMs: WED_FEB_18,
101
- hostTimeZone: "UTC",
102
- userTimeZone: "America/New_York",
103
- });
104
- expect(diffResult).toContain("Host TZ: UTC");
105
- });
106
-
107
- test("uses configured user timezone when profile timezone is unavailable", () => {
108
- const result = buildTemporalContext({
109
- nowMs: WED_FEB_18,
110
- hostTimeZone: "UTC",
111
- configuredUserTimeZone: "America/Chicago",
112
- userTimeZone: null,
113
- });
114
- expect(result).toContain("TZ: America/Chicago");
115
- expect(result).toContain("Today: 2026-02-18 (Wed) 06:00 -06:00");
116
- expect(result).not.toContain("(host fallback)");
117
- });
118
-
119
- test("configured user timezone takes precedence over profile timezone", () => {
120
- const result = buildTemporalContext({
121
- nowMs: WED_FEB_18,
122
- hostTimeZone: "UTC",
123
- configuredUserTimeZone: "America/Los_Angeles",
124
- userTimeZone: "America/New_York",
125
- });
126
- expect(result).toContain("TZ: America/Los_Angeles");
127
- expect(result).toContain("Today: 2026-02-18 (Wed) 04:00 -08:00");
128
- });
129
-
130
- test("falls back to host timezone with (host fallback) suffix", () => {
131
- const result = buildTemporalContext({
132
- nowMs: WED_FEB_18,
133
- hostTimeZone: "UTC",
134
- userTimeZone: null,
135
- });
136
- expect(result).toContain("TZ: UTC (host fallback)");
137
- });
138
-
139
- test("accepts UTC/GMT offset-style user timezone values", () => {
140
- const result = buildTemporalContext({
141
- nowMs: WED_FEB_18,
142
- hostTimeZone: "UTC",
143
- userTimeZone: "UTC+2",
144
- });
145
- expect(result).toContain("TZ: Etc/GMT-2");
146
- expect(result).toContain("Today: 2026-02-18 (Wed) 14:00 +02:00");
147
- expect(result).not.toContain("(host fallback)");
148
- });
149
-
150
- test("accepts fractional UTC/GMT offset-style user timezone values", () => {
151
- const result = buildTemporalContext({
152
- nowMs: WED_FEB_18,
153
- hostTimeZone: "UTC",
154
- userTimeZone: "UTC+5:30",
155
- });
156
- expect(result).toContain("TZ: +05:30");
157
- expect(result).toContain("Today: 2026-02-18 (Wed) 17:30 +05:30");
158
- expect(result).not.toContain("(host fallback)");
159
- });
160
-
161
- test("formats midnight hours as 00 (never 24)", () => {
162
- const justAfterMidnight = Date.UTC(2026, 1, 19, 0, 5, 0);
163
- const result = buildTemporalContext({
164
- nowMs: justAfterMidnight,
165
- timeZone: "UTC",
166
- });
167
- expect(result).toContain("00:05 +00:00");
168
- expect(result).not.toContain("24:05");
169
- });
170
-
171
- test("Today line includes full YYYY-MM-DD format with year", () => {
172
- const result = buildTemporalContext({ nowMs: WED_FEB_18, timeZone: "UTC" });
173
- expect(result).toMatch(/Today: \d{4}-\d{2}-\d{2} \(\w{3}\) \d{2}:\d{2}/);
174
- expect(result).toContain("2026-02-18");
175
- });
176
-
177
- test("handles year boundary correctly", () => {
178
- const result = buildTemporalContext({ nowMs: TUE_DEC_29, timeZone: "UTC" });
179
- expect(result).toContain("Today: 2026-12-29 (Tue)");
180
- });
181
- });
182
-
183
- // ---------------------------------------------------------------------------
184
- // DST-safe timezone behavior
185
- // ---------------------------------------------------------------------------
186
-
187
- describe("DST-safe timezone behavior", () => {
188
- test("date labels are correct in US Eastern timezone", () => {
189
- const result = buildTemporalContext({
190
- nowMs: WED_FEB_18,
191
- timeZone: "America/New_York",
192
- });
193
- expect(result).toContain("Today: 2026-02-18 (Wed) 07:00 -05:00");
194
- });
195
-
196
- test("date labels are correct in timezone ahead of UTC", () => {
197
- // Feb 18 23:00 UTC = Feb 19 08:00 JST
198
- const nearMidnight = Date.UTC(2026, 1, 18, 23, 0, 0);
199
- const result = buildTemporalContext({
200
- nowMs: nearMidnight,
201
- timeZone: "Asia/Tokyo",
202
- });
203
- expect(result).toContain("Today: 2026-02-19 (Thu) 08:00 +09:00");
204
- });
205
-
206
- test("local offset tracks daylight saving changes", () => {
207
- // Jul 1 12:00 UTC = Jul 1 08:00 EDT
208
- const summer = Date.UTC(2026, 6, 1, 12, 0, 0);
209
- const result = buildTemporalContext({
210
- nowMs: summer,
211
- timeZone: "America/New_York",
212
- });
213
- expect(result).toContain("Today: 2026-07-01 (Wed) 08:00 -04:00");
214
- });
215
- });
216
-
217
8
  // ---------------------------------------------------------------------------
218
9
  // extractUserTimeZoneFromRecall
219
10
  // ---------------------------------------------------------------------------
@@ -293,3 +84,78 @@ describe("extractUserTimeZoneFromRecall", () => {
293
84
  expect(extractUserTimeZoneFromRecall(text)).toBe("America/Denver");
294
85
  });
295
86
  });
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // formatTurnTimestamp
90
+ // ---------------------------------------------------------------------------
91
+
92
+ describe("formatTurnTimestamp", () => {
93
+ /** 2026-04-02 06:52:33 UTC (Thursday) */
94
+ const THU_APR_02_0652 = Date.UTC(2026, 3, 2, 6, 52, 33);
95
+
96
+ test("includes seconds in the timestamp", () => {
97
+ const result = formatTurnTimestamp({
98
+ nowMs: THU_APR_02_0652,
99
+ timeZone: "America/Chicago",
100
+ });
101
+ expect(result).toContain("01:52:33");
102
+ });
103
+
104
+ test("timezone name appears in parentheses", () => {
105
+ const result = formatTurnTimestamp({
106
+ nowMs: THU_APR_02_0652,
107
+ timeZone: "America/Chicago",
108
+ });
109
+ expect(result).toContain("(America/Chicago)");
110
+ });
111
+
112
+ test("produces expected full format", () => {
113
+ const result = formatTurnTimestamp({
114
+ nowMs: THU_APR_02_0652,
115
+ timeZone: "America/Chicago",
116
+ });
117
+ expect(result).toBe(
118
+ "2026-04-02 (Thu) 01:52:33 -05:00 (America/Chicago)",
119
+ );
120
+ });
121
+
122
+ test("handles UTC fallback when no timezone provided", () => {
123
+ const result = formatTurnTimestamp({
124
+ nowMs: THU_APR_02_0652,
125
+ hostTimeZone: "UTC",
126
+ });
127
+ expect(result).toBe("2026-04-02 (Thu) 06:52:33 +00:00 (UTC)");
128
+ });
129
+
130
+ test("handles user timezone override", () => {
131
+ const result = formatTurnTimestamp({
132
+ nowMs: THU_APR_02_0652,
133
+ hostTimeZone: "UTC",
134
+ userTimeZone: "Asia/Tokyo",
135
+ });
136
+ expect(result).toBe("2026-04-02 (Thu) 15:52:33 +09:00 (Asia/Tokyo)");
137
+ });
138
+
139
+ test("handles DST correctly", () => {
140
+ // Jul 1 12:00:30 UTC = Jul 1 08:00:30 EDT (Eastern Daylight Time, -04:00)
141
+ const summerWithSeconds = Date.UTC(2026, 6, 1, 12, 0, 30);
142
+ const result = formatTurnTimestamp({
143
+ nowMs: summerWithSeconds,
144
+ timeZone: "America/New_York",
145
+ });
146
+ expect(result).toBe(
147
+ "2026-07-01 (Wed) 08:00:30 -04:00 (America/New_York)",
148
+ );
149
+ });
150
+
151
+ test("formats midnight as 00", () => {
152
+ // 2026-02-19 00:00:15 UTC
153
+ const justAfterMidnight = Date.UTC(2026, 1, 19, 0, 0, 15);
154
+ const result = formatTurnTimestamp({
155
+ nowMs: justAfterMidnight,
156
+ timeZone: "UTC",
157
+ });
158
+ expect(result).toContain("00:00:15");
159
+ expect(result).not.toContain("24:");
160
+ });
161
+ });