@vellumai/assistant 0.4.49 → 0.4.51

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 (353) hide show
  1. package/ARCHITECTURE.md +24 -33
  2. package/README.md +3 -3
  3. package/docs/architecture/integrations.md +2 -2
  4. package/docs/architecture/keychain-broker.md +6 -6
  5. package/docs/architecture/memory.md +180 -119
  6. package/knip.json +32 -0
  7. package/package.json +3 -2
  8. package/src/__tests__/agent-loop.test.ts +3 -1
  9. package/src/__tests__/anthropic-provider.test.ts +114 -23
  10. package/src/__tests__/approval-cascade.test.ts +1 -15
  11. package/src/__tests__/approval-routes-http.test.ts +2 -0
  12. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  13. package/src/__tests__/btw-routes.test.ts +61 -5
  14. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  15. package/src/__tests__/checker.test.ts +13 -0
  16. package/src/__tests__/config-schema.test.ts +1 -68
  17. package/src/__tests__/config-watcher.test.ts +8 -0
  18. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  19. package/src/__tests__/conversation-routes-guardian-reply.test.ts +8 -0
  20. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  21. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  22. package/src/__tests__/credential-security-invariants.test.ts +8 -7
  23. package/src/__tests__/credential-vault-unit.test.ts +23 -18
  24. package/src/__tests__/credential-vault.test.ts +30 -18
  25. package/src/__tests__/credentials-cli.test.ts +257 -82
  26. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  27. package/src/__tests__/date-context.test.ts +93 -77
  28. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  29. package/src/__tests__/guardian-routing-invariants.test.ts +93 -0
  30. package/src/__tests__/history-repair.test.ts +245 -0
  31. package/src/__tests__/host-cu-proxy.test.ts +165 -3
  32. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  33. package/src/__tests__/inbound-invite-redemption.test.ts +36 -7
  34. package/src/__tests__/integration-status.test.ts +31 -30
  35. package/src/__tests__/invite-redemption-service.test.ts +166 -13
  36. package/src/__tests__/invite-routes-http.test.ts +166 -5
  37. package/src/__tests__/keychain-broker-client.test.ts +4 -4
  38. package/src/__tests__/list-messages-attachments.test.ts +193 -0
  39. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  40. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  41. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  42. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  43. package/src/__tests__/memory-regressions.test.ts +477 -2841
  44. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  45. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  46. package/src/__tests__/mime-builder.test.ts +28 -0
  47. package/src/__tests__/native-web-search.test.ts +1 -0
  48. package/src/__tests__/oauth-cli.test.ts +824 -31
  49. package/src/__tests__/oauth-provider-profiles.test.ts +1 -1
  50. package/src/__tests__/oauth-store.test.ts +363 -17
  51. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  52. package/src/__tests__/registry.test.ts +0 -1
  53. package/src/__tests__/relay-server.test.ts +55 -1
  54. package/src/__tests__/schedule-tools.test.ts +32 -0
  55. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  56. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  57. package/src/__tests__/secret-routes-managed-proxy.test.ts +183 -0
  58. package/src/__tests__/secure-keys.test.ts +78 -18
  59. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  60. package/src/__tests__/server-history-render.test.ts +2 -2
  61. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  62. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  63. package/src/__tests__/session-agent-loop.test.ts +19 -15
  64. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  65. package/src/__tests__/session-error.test.ts +124 -2
  66. package/src/__tests__/session-history-web-search.test.ts +918 -0
  67. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  68. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  69. package/src/__tests__/session-queue.test.ts +37 -27
  70. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  71. package/src/__tests__/session-slash-known.test.ts +1 -15
  72. package/src/__tests__/session-slash-queue.test.ts +1 -15
  73. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  74. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  75. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  76. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  77. package/src/__tests__/skills-install-extract.test.ts +93 -0
  78. package/src/__tests__/skills.test.ts +2 -2
  79. package/src/__tests__/skillssh-registry.test.ts +451 -0
  80. package/src/__tests__/slack-channel-config.test.ts +10 -8
  81. package/src/__tests__/trust-store.test.ts +15 -0
  82. package/src/__tests__/twilio-config.test.ts +11 -10
  83. package/src/__tests__/twilio-provider.test.ts +9 -4
  84. package/src/__tests__/voice-invite-redemption.test.ts +85 -5
  85. package/src/agent/ax-tree-compaction.test.ts +51 -0
  86. package/src/agent/loop.ts +39 -12
  87. package/src/approvals/AGENTS.md +1 -1
  88. package/src/approvals/guardian-request-resolvers.ts +14 -2
  89. package/src/bundler/compiler-tools.ts +66 -2
  90. package/src/calls/call-domain.ts +134 -3
  91. package/src/calls/call-store.ts +6 -0
  92. package/src/calls/relay-server.ts +44 -6
  93. package/src/calls/relay-setup-router.ts +17 -1
  94. package/src/calls/twilio-config.ts +5 -4
  95. package/src/calls/twilio-provider.ts +14 -9
  96. package/src/calls/twilio-rest.ts +10 -7
  97. package/src/calls/types.ts +3 -1
  98. package/src/cli/commands/config.ts +14 -9
  99. package/src/cli/commands/contacts.ts +3 -0
  100. package/src/cli/commands/credentials.ts +170 -174
  101. package/src/cli/commands/doctor.ts +11 -8
  102. package/src/cli/commands/keys.ts +9 -9
  103. package/src/cli/commands/mcp.ts +46 -59
  104. package/src/cli/commands/memory.ts +16 -165
  105. package/src/cli/commands/oauth/apps.ts +68 -10
  106. package/src/cli/commands/oauth/connections.ts +475 -105
  107. package/src/cli/commands/oauth/index.ts +3 -3
  108. package/src/cli/commands/oauth/providers.ts +18 -4
  109. package/src/cli/commands/sessions.ts +5 -2
  110. package/src/cli/commands/skills.ts +173 -1
  111. package/src/cli/http-client.ts +0 -20
  112. package/src/cli/main-screen.tsx +2 -2
  113. package/src/cli/program.ts +5 -6
  114. package/src/cli.ts +20 -22
  115. package/src/config/__tests__/feature-flag-registry-bundled.test.ts +39 -0
  116. package/src/config/bundled-skills/computer-use/TOOLS.json +1 -1
  117. package/src/config/bundled-skills/computer-use/tools/computer-use-observe.ts +12 -0
  118. package/src/config/bundled-skills/contacts/SKILL.md +35 -11
  119. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
  120. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  121. package/src/config/bundled-skills/gmail/TOOLS.json +52 -0
  122. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +13 -3
  123. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +9 -2
  124. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +5 -1
  125. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +5 -1
  126. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +5 -1
  127. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +5 -1
  128. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +9 -2
  129. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +5 -1
  130. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +5 -1
  131. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +5 -1
  132. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +5 -1
  133. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +5 -1
  134. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +5 -1
  135. package/src/config/bundled-skills/google-calendar/TOOLS.json +20 -0
  136. package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +2 -1
  137. package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +2 -1
  138. package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +2 -1
  139. package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +2 -1
  140. package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +2 -1
  141. package/src/config/bundled-skills/google-calendar/tools/shared.ts +8 -2
  142. package/src/config/bundled-skills/messaging/SKILL.md +1 -1
  143. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -2
  144. package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
  145. package/src/config/bundled-skills/messaging/tools/messaging-auth-test.ts +2 -2
  146. package/src/config/bundled-skills/messaging/tools/messaging-list-conversations.ts +2 -2
  147. package/src/config/bundled-skills/messaging/tools/messaging-mark-read.ts +2 -2
  148. package/src/config/bundled-skills/messaging/tools/messaging-read.ts +2 -2
  149. package/src/config/bundled-skills/messaging/tools/messaging-search.ts +2 -2
  150. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +2 -2
  151. package/src/config/bundled-skills/messaging/tools/messaging-sender-digest.ts +2 -2
  152. package/src/config/bundled-skills/messaging/tools/shared.ts +7 -5
  153. package/src/config/bundled-skills/slack/tools/shared.ts +1 -1
  154. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +1 -1
  155. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +1 -1
  156. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +1 -1
  157. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +1 -1
  158. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +1 -1
  159. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +1 -1
  160. package/src/config/bundled-tool-registry.ts +2 -5
  161. package/src/config/loader.ts +6 -42
  162. package/src/config/schema.ts +1 -12
  163. package/src/config/schemas/memory-lifecycle.ts +0 -9
  164. package/src/config/schemas/memory-processing.ts +0 -180
  165. package/src/config/schemas/memory-retrieval.ts +32 -104
  166. package/src/config/schemas/memory.ts +0 -10
  167. package/src/config/types.ts +0 -4
  168. package/src/contacts/contact-store.ts +39 -2
  169. package/src/contacts/contacts-write.ts +9 -0
  170. package/src/context/window-manager.ts +4 -1
  171. package/src/daemon/config-watcher.ts +55 -2
  172. package/src/daemon/daemon-control.ts +1 -1
  173. package/src/daemon/date-context.ts +114 -31
  174. package/src/daemon/handlers/config-ingress.ts +2 -2
  175. package/src/daemon/handlers/config-slack-channel.ts +59 -39
  176. package/src/daemon/handlers/config-telegram.ts +23 -14
  177. package/src/daemon/handlers/session-history.ts +1 -358
  178. package/src/daemon/handlers/sessions.ts +18 -13
  179. package/src/daemon/handlers/shared.ts +3 -17
  180. package/src/daemon/handlers/skills.ts +20 -1
  181. package/src/daemon/history-repair.ts +72 -8
  182. package/src/daemon/host-cu-proxy.ts +55 -26
  183. package/src/daemon/lifecycle.ts +39 -4
  184. package/src/daemon/mcp-reload-service.ts +2 -2
  185. package/src/daemon/message-types/computer-use.ts +1 -12
  186. package/src/daemon/message-types/memory.ts +4 -16
  187. package/src/daemon/message-types/messages.ts +1 -0
  188. package/src/daemon/message-types/sessions.ts +4 -42
  189. package/src/daemon/server.ts +6 -1
  190. package/src/daemon/session-agent-loop-handlers.ts +38 -0
  191. package/src/daemon/session-agent-loop.ts +334 -48
  192. package/src/daemon/session-error.ts +89 -6
  193. package/src/daemon/session-history.ts +17 -7
  194. package/src/daemon/session-media-retry.ts +6 -2
  195. package/src/daemon/session-memory.ts +69 -149
  196. package/src/daemon/session-process.ts +10 -1
  197. package/src/daemon/session-runtime-assembly.ts +49 -19
  198. package/src/daemon/session-slash.ts +3 -5
  199. package/src/daemon/session-surfaces.ts +4 -1
  200. package/src/daemon/session-tool-setup.ts +7 -1
  201. package/src/daemon/session.ts +12 -2
  202. package/src/email/providers/index.ts +2 -2
  203. package/src/instrument.ts +61 -1
  204. package/src/media/avatar-router.ts +1 -1
  205. package/src/memory/admin.ts +2 -191
  206. package/src/memory/canonical-guardian-store.ts +38 -2
  207. package/src/memory/conversation-crud.ts +0 -33
  208. package/src/memory/conversation-queries.ts +25 -83
  209. package/src/memory/db-init.ts +32 -0
  210. package/src/memory/embedding-backend.ts +84 -8
  211. package/src/memory/embedding-types.ts +9 -1
  212. package/src/memory/indexer.ts +7 -46
  213. package/src/memory/invite-store.ts +19 -0
  214. package/src/memory/items-extractor.ts +274 -76
  215. package/src/memory/job-handlers/backfill.ts +2 -127
  216. package/src/memory/job-handlers/cleanup.ts +2 -16
  217. package/src/memory/job-handlers/extraction.ts +2 -138
  218. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  219. package/src/memory/job-handlers/summarization.ts +3 -148
  220. package/src/memory/job-utils.ts +21 -59
  221. package/src/memory/jobs-store.ts +1 -159
  222. package/src/memory/jobs-worker.ts +9 -52
  223. package/src/memory/migrations/104-core-indexes.ts +3 -3
  224. package/src/memory/migrations/149-oauth-tables.ts +2 -0
  225. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  226. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  227. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  228. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  229. package/src/memory/migrations/154-drop-fts.ts +20 -0
  230. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  231. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  232. package/src/memory/migrations/157-invite-contact-id.ts +104 -0
  233. package/src/memory/migrations/index.ts +8 -0
  234. package/src/memory/migrations/registry.ts +6 -0
  235. package/src/memory/qdrant-client.ts +148 -51
  236. package/src/memory/raw-query.ts +1 -1
  237. package/src/memory/retriever.test.ts +294 -273
  238. package/src/memory/retriever.ts +421 -645
  239. package/src/memory/schema/calls.ts +2 -0
  240. package/src/memory/schema/contacts.ts +1 -0
  241. package/src/memory/schema/memory-core.ts +3 -48
  242. package/src/memory/schema/oauth.ts +2 -0
  243. package/src/memory/search/formatting.ts +263 -176
  244. package/src/memory/search/lexical.ts +1 -254
  245. package/src/memory/search/ranking.ts +0 -455
  246. package/src/memory/search/semantic.ts +100 -14
  247. package/src/memory/search/staleness.ts +47 -0
  248. package/src/memory/search/tier-classifier.ts +21 -0
  249. package/src/memory/search/types.ts +15 -77
  250. package/src/memory/task-memory-cleanup.ts +4 -6
  251. package/src/messaging/provider.ts +1 -1
  252. package/src/messaging/providers/gmail/adapter.ts +1 -1
  253. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  254. package/src/messaging/providers/telegram-bot/adapter.ts +17 -8
  255. package/src/messaging/providers/whatsapp/adapter.ts +13 -9
  256. package/src/messaging/registry.ts +9 -5
  257. package/src/oauth/byo-connection.test.ts +40 -25
  258. package/src/oauth/connect-orchestrator.ts +4 -10
  259. package/src/oauth/connection-resolver.ts +20 -6
  260. package/src/oauth/manual-token-connection.ts +5 -5
  261. package/src/oauth/oauth-store.ts +183 -31
  262. package/src/oauth/platform-connection.test.ts +1 -1
  263. package/src/oauth/provider-behaviors.ts +503 -4
  264. package/src/oauth/seed-providers.ts +214 -8
  265. package/src/oauth/token-persistence.ts +31 -16
  266. package/src/permissions/defaults.ts +1 -0
  267. package/src/permissions/trust-store.ts +23 -1
  268. package/src/playbooks/playbook-compiler.ts +1 -1
  269. package/src/prompts/system-prompt.ts +18 -2
  270. package/src/providers/anthropic/client.ts +56 -126
  271. package/src/providers/types.ts +7 -1
  272. package/src/runtime/AGENTS.md +9 -0
  273. package/src/runtime/auth/route-policy.ts +6 -3
  274. package/src/runtime/channel-readiness-service.ts +48 -40
  275. package/src/runtime/guardian-reply-router.ts +24 -22
  276. package/src/runtime/http-server.ts +2 -2
  277. package/src/runtime/http-types.ts +2 -0
  278. package/src/runtime/invite-redemption-service.ts +72 -12
  279. package/src/runtime/invite-service.ts +43 -0
  280. package/src/runtime/middleware/twilio-validation.ts +1 -1
  281. package/src/runtime/pending-interactions.ts +2 -2
  282. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  283. package/src/runtime/routes/btw-routes.ts +10 -5
  284. package/src/runtime/routes/conversation-routes.ts +56 -11
  285. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  286. package/src/runtime/routes/integrations/slack/channel.ts +2 -2
  287. package/src/runtime/routes/integrations/telegram.ts +2 -2
  288. package/src/runtime/routes/integrations/twilio.ts +17 -17
  289. package/src/runtime/routes/invite-routes.ts +29 -4
  290. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  291. package/src/runtime/routes/memory-item-routes.ts +503 -0
  292. package/src/runtime/routes/secret-routes.ts +17 -0
  293. package/src/runtime/routes/session-management-routes.ts +3 -3
  294. package/src/runtime/routes/settings-routes.ts +3 -3
  295. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  296. package/src/runtime/routes/workspace-routes.ts +9 -4
  297. package/src/runtime/routes/workspace-utils.ts +8 -2
  298. package/src/schedule/integration-status.ts +26 -19
  299. package/src/security/keychain-broker-client.ts +17 -4
  300. package/src/security/oauth2.ts +6 -7
  301. package/src/security/secure-keys.ts +44 -19
  302. package/src/security/token-manager.ts +46 -39
  303. package/src/services/vercel-deploy.ts +0 -24
  304. package/src/signals/confirm.ts +78 -0
  305. package/src/signals/mcp-reload.ts +18 -0
  306. package/src/skills/catalog-install.ts +74 -18
  307. package/src/skills/skillssh-registry.ts +503 -0
  308. package/src/tools/assets/search.ts +5 -1
  309. package/src/tools/computer-use/definitions.ts +0 -10
  310. package/src/tools/computer-use/registry.ts +1 -1
  311. package/src/tools/credentials/vault.ts +22 -7
  312. package/src/tools/memory/definitions.ts +4 -13
  313. package/src/tools/memory/handlers.test.ts +83 -103
  314. package/src/tools/memory/handlers.ts +50 -85
  315. package/src/tools/network/script-proxy/session-manager.ts +8 -8
  316. package/src/tools/schedule/create.ts +10 -3
  317. package/src/tools/schedule/update.ts +8 -1
  318. package/src/tools/skills/load.ts +25 -2
  319. package/src/watcher/provider-types.ts +1 -1
  320. package/src/watcher/providers/github.ts +1 -1
  321. package/src/watcher/providers/gmail.ts +3 -3
  322. package/src/watcher/providers/google-calendar.ts +3 -3
  323. package/src/watcher/providers/linear.ts +1 -1
  324. package/src/__tests__/clarification-resolver.test.ts +0 -193
  325. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  326. package/src/__tests__/conflict-policy.test.ts +0 -269
  327. package/src/__tests__/conflict-store.test.ts +0 -372
  328. package/src/__tests__/contradiction-checker.test.ts +0 -361
  329. package/src/__tests__/entity-extractor.test.ts +0 -211
  330. package/src/__tests__/entity-search.test.ts +0 -1117
  331. package/src/__tests__/profile-compiler.test.ts +0 -392
  332. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  333. package/src/__tests__/session-profile-injection.test.ts +0 -557
  334. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  335. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  336. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  337. package/src/daemon/session-conflict-gate.ts +0 -167
  338. package/src/daemon/session-dynamic-profile.ts +0 -77
  339. package/src/memory/clarification-resolver.ts +0 -417
  340. package/src/memory/conflict-intent.ts +0 -205
  341. package/src/memory/conflict-policy.ts +0 -127
  342. package/src/memory/conflict-store.ts +0 -410
  343. package/src/memory/contradiction-checker.ts +0 -508
  344. package/src/memory/entity-extractor.ts +0 -535
  345. package/src/memory/format-recall.ts +0 -47
  346. package/src/memory/fts-reconciler.ts +0 -165
  347. package/src/memory/job-handlers/conflict.ts +0 -200
  348. package/src/memory/profile-compiler.ts +0 -195
  349. package/src/memory/recall-cache.ts +0 -117
  350. package/src/memory/search/entity.ts +0 -535
  351. package/src/memory/search/query-expansion.test.ts +0 -70
  352. package/src/memory/search/query-expansion.ts +0 -118
  353. package/src/runtime/routes/mcp-routes.ts +0 -20
@@ -14,6 +14,7 @@ mock.module("../util/logger.js", () => ({
14
14
 
15
15
  mock.module("../security/secure-keys.js", () => ({
16
16
  getSecureKey: (key: string) => mockSecureKeys[key] ?? null,
17
+ getSecureKeyAsync: async (key: string) => mockSecureKeys[key] ?? null,
17
18
  }));
18
19
 
19
20
  mock.module("../config/loader.js", () => ({
@@ -41,8 +42,8 @@ describe("twilio-config", () => {
41
42
  };
42
43
  });
43
44
 
44
- test("returns config when credentials and phone number are set", () => {
45
- const config = getTwilioConfig();
45
+ test("returns config when credentials and phone number are set", async () => {
46
+ const config = await getTwilioConfig();
46
47
  expect(config.accountSid).toBe("AC_test_sid");
47
48
  expect(config.authToken).toBe("test_auth_token");
48
49
  expect(config.phoneNumber).toBe("+15551234567");
@@ -50,16 +51,16 @@ describe("twilio-config", () => {
50
51
  expect(config.wssBaseUrl).toBe("wss://test.example.com/twilio/relay");
51
52
  });
52
53
 
53
- test("throws ConfigError when account SID is missing", () => {
54
+ test("throws ConfigError when account SID is missing", async () => {
54
55
  mockLoadConfigResult = {
55
56
  twilio: { accountSid: "", phoneNumber: "+15551234567" },
56
57
  };
57
- expect(() => getTwilioConfig()).toThrow(
58
+ expect(getTwilioConfig()).rejects.toThrow(
58
59
  /Twilio credentials not configured/,
59
60
  );
60
61
  });
61
62
 
62
- test("throws ConfigError when auth token is missing", () => {
63
+ test("throws ConfigError when auth token is missing", async () => {
63
64
  mockSecureKeys = {};
64
65
  mockLoadConfigResult = {
65
66
  twilio: {
@@ -67,26 +68,26 @@ describe("twilio-config", () => {
67
68
  phoneNumber: "+15551234567",
68
69
  },
69
70
  };
70
- expect(() => getTwilioConfig()).toThrow(
71
+ expect(getTwilioConfig()).rejects.toThrow(
71
72
  /Twilio credentials not configured/,
72
73
  );
73
74
  });
74
75
 
75
- test("throws ConfigError when phone number is missing", () => {
76
+ test("throws ConfigError when phone number is missing", async () => {
76
77
  mockLoadConfigResult = {
77
78
  twilio: {
78
79
  accountSid: "AC_test_sid",
79
80
  phoneNumber: "",
80
81
  },
81
82
  };
82
- expect(() => getTwilioConfig()).toThrow(
83
+ expect(getTwilioConfig()).rejects.toThrow(
83
84
  /Twilio phone number not configured/,
84
85
  );
85
86
  });
86
87
 
87
- test("throws ConfigError when twilio config section is absent", () => {
88
+ test("throws ConfigError when twilio config section is absent", async () => {
88
89
  mockLoadConfigResult = {};
89
- expect(() => getTwilioConfig()).toThrow(
90
+ expect(getTwilioConfig()).rejects.toThrow(
90
91
  /Twilio credentials not configured/,
91
92
  );
92
93
  });
@@ -46,6 +46,11 @@ mock.module("../security/secure-keys.js", () => ({
46
46
  if (key === credentialKey("twilio", "account_sid")) return mockAccountSid;
47
47
  return undefined;
48
48
  },
49
+ getSecureKeyAsync: async (key: string) => {
50
+ if (key === credentialKey("twilio", "auth_token")) return mockAuthToken;
51
+ if (key === credentialKey("twilio", "account_sid")) return mockAccountSid;
52
+ return undefined;
53
+ },
49
54
  }));
50
55
 
51
56
  import { TwilioConversationRelayProvider } from "../calls/twilio-provider.js";
@@ -143,15 +148,15 @@ describe("TwilioConversationRelayProvider", () => {
143
148
  });
144
149
 
145
150
  describe("getAuthToken", () => {
146
- test("returns the auth token when configured", () => {
151
+ test("returns the auth token when configured", async () => {
147
152
  mockAuthToken = "my-secret-token";
148
- const token = TwilioConversationRelayProvider.getAuthToken();
153
+ const token = await TwilioConversationRelayProvider.getAuthToken();
149
154
  expect(token).toBe("my-secret-token");
150
155
  });
151
156
 
152
- test("returns null when auth token is not configured", () => {
157
+ test("returns null when auth token is not configured", async () => {
153
158
  mockAuthToken = undefined;
154
- const token = TwilioConversationRelayProvider.getAuthToken();
159
+ const token = await TwilioConversationRelayProvider.getAuthToken();
155
160
  expect(token).toBeNull();
156
161
  });
157
162
  });
@@ -23,7 +23,11 @@ mock.module("../util/logger.js", () => ({
23
23
  }),
24
24
  }));
25
25
 
26
- import { findContactChannel } from "../contacts/contact-store.js";
26
+ import {
27
+ findContactChannel,
28
+ getContact,
29
+ upsertContact,
30
+ } from "../contacts/contact-store.js";
27
31
  import { upsertContactChannel } from "../contacts/contacts-write.js";
28
32
  import { getSqlite, initializeDb, resetDb } from "../memory/db.js";
29
33
  import { createInvite, revokeInvite } from "../memory/invite-store.js";
@@ -47,6 +51,11 @@ function resetTables() {
47
51
  getSqlite().run("DELETE FROM contacts");
48
52
  }
49
53
 
54
+ /** Create a throwaway contact and return its ID, for use as the invite's contactId. */
55
+ function createTargetContact(displayName = "Target Contact"): string {
56
+ return upsertContact({ displayName, role: "contact" }).id;
57
+ }
58
+
50
59
  // ---------------------------------------------------------------------------
51
60
  // generateVoiceCode
52
61
  // ---------------------------------------------------------------------------
@@ -140,14 +149,18 @@ describe("redeemVoiceInviteCode", () => {
140
149
  expiresInMs?: number;
141
150
  voiceCodeDigits?: number;
142
151
  assistantId?: string;
152
+ contactId?: string;
143
153
  } = {},
144
154
  ) {
145
155
  const digits = opts.voiceCodeDigits ?? 6;
146
156
  const code = generateVoiceCode(digits);
147
157
  const codeHash = hashVoiceCode(code);
148
158
 
159
+ const contactId = opts.contactId ?? createTargetContact();
160
+
149
161
  const { invite } = createInvite({
150
162
  sourceChannel: "phone",
163
+ contactId,
151
164
  maxUses: opts.maxUses ?? 1,
152
165
  expiresInMs: opts.expiresInMs,
153
166
  expectedExternalUserId: opts.callerPhone ?? "+15551234567",
@@ -277,11 +290,13 @@ describe("redeemVoiceInviteCode", () => {
277
290
  // Create a non-voice invite with voice code metadata to simulate a
278
291
  // hypothetical misconfiguration. The redemption service filters by
279
292
  // sourceChannel='phone', so non-phone invites are invisible.
293
+ const targetContactId = createTargetContact();
280
294
  const code = generateVoiceCode(6);
281
295
  const codeHash = hashVoiceCode(code);
282
296
 
283
297
  createInvite({
284
298
  sourceChannel: "telegram",
299
+ contactId: targetContactId,
285
300
  maxUses: 1,
286
301
  expectedExternalUserId: "+15551234567",
287
302
  voiceCodeHash: codeHash,
@@ -301,16 +316,21 @@ describe("redeemVoiceInviteCode", () => {
301
316
 
302
317
  test("already-member caller gets already_member outcome", () => {
303
318
  const phone = "+15551234567";
304
- const { code } = createVoiceInvite({ callerPhone: phone });
305
319
 
306
320
  // Pre-create an active member for this phone on voice channel
307
- upsertContactChannel({
321
+ const member = upsertContactChannel({
308
322
  sourceChannel: "phone",
309
323
  externalUserId: phone,
310
324
  status: "active",
311
325
  policy: "allow",
312
326
  });
313
327
 
328
+ // Create a voice invite targeting the same contact that owns the channel
329
+ const { code } = createVoiceInvite({
330
+ callerPhone: phone,
331
+ contactId: member!.contact.id,
332
+ });
333
+
314
334
  const result = redeemVoiceInviteCode({
315
335
  callerExternalUserId: phone,
316
336
  sourceChannel: "phone",
@@ -327,15 +347,21 @@ describe("redeemVoiceInviteCode", () => {
327
347
 
328
348
  test("blocked member gets generic failure to avoid leaking membership status", () => {
329
349
  const phone = "+15551234567";
330
- const { code } = createVoiceInvite({ callerPhone: phone });
331
350
 
332
- upsertContactChannel({
351
+ // Pre-create a blocked member and find their contact
352
+ const member = upsertContactChannel({
333
353
  sourceChannel: "phone",
334
354
  externalUserId: phone,
335
355
  status: "blocked",
336
356
  policy: "deny",
337
357
  });
338
358
 
359
+ // Create a voice invite targeting the same contact that owns the channel
360
+ const { code } = createVoiceInvite({
361
+ callerPhone: phone,
362
+ contactId: member!.contact.id,
363
+ });
364
+
339
365
  const result = redeemVoiceInviteCode({
340
366
  callerExternalUserId: phone,
341
367
  sourceChannel: "phone",
@@ -354,4 +380,58 @@ describe("redeemVoiceInviteCode", () => {
354
380
 
355
381
  expect(result).toEqual({ ok: false, reason: "invalid_or_expired" });
356
382
  });
383
+
384
+ test("binds redeemer to the invite's target contact, not the guardian, on voice redemption", () => {
385
+ const phone = "+15559998888";
386
+
387
+ // Pre-create a guardian contact with a revoked phone channel
388
+ const guardianContact = upsertContact({
389
+ displayName: "Guardian",
390
+ role: "guardian",
391
+ channels: [
392
+ {
393
+ type: "phone",
394
+ address: phone,
395
+ externalUserId: phone,
396
+ status: "revoked",
397
+ },
398
+ ],
399
+ });
400
+
401
+ // Create a separate target contact "Mom"
402
+ const momContact = upsertContact({
403
+ displayName: "Mom",
404
+ role: "contact",
405
+ });
406
+
407
+ // Create a voice invite targeting Mom's contact
408
+ const { code } = createVoiceInvite({
409
+ callerPhone: phone,
410
+ contactId: momContact.id,
411
+ });
412
+
413
+ const result = redeemVoiceInviteCode({
414
+ callerExternalUserId: phone,
415
+ sourceChannel: "phone",
416
+ code,
417
+ });
418
+
419
+ // Should succeed — redeemer's channel is bound to Mom
420
+ expect(result.ok).toBe(true);
421
+ expect((result as { type: string }).type).toBe("redeemed");
422
+
423
+ // Verify the redeemer's phone is now bound to Mom's contact
424
+ const contactResult = findContactChannel({
425
+ channelType: "phone",
426
+ externalUserId: phone,
427
+ });
428
+ expect(contactResult).not.toBeNull();
429
+ expect(contactResult!.contact.id).toBe(momContact.id);
430
+ expect(contactResult!.channel.status).toBe("active");
431
+
432
+ // Verify the original guardian contact was NOT modified
433
+ const guardian = getContact(guardianContact.id);
434
+ expect(guardian).not.toBeNull();
435
+ expect(guardian!.role).toBe("guardian");
436
+ });
357
437
  });
@@ -182,6 +182,57 @@ describe("compactAxTreeHistory", () => {
182
182
  expect(result).toEqual([]);
183
183
  });
184
184
 
185
+ test("counts AX trees per block, not per message", () => {
186
+ // One message has two AX tree blocks — they should count as 2 trees
187
+ const messages: Message[] = [
188
+ {
189
+ role: "user",
190
+ content: [
191
+ {
192
+ type: "tool_result",
193
+ tool_use_id: "t1a",
194
+ content: "<ax-tree>\ntree-1a\n</ax-tree>",
195
+ is_error: false,
196
+ },
197
+ {
198
+ type: "tool_result",
199
+ tool_use_id: "t1b",
200
+ content: "<ax-tree>\ntree-1b\n</ax-tree>",
201
+ is_error: false,
202
+ },
203
+ ],
204
+ },
205
+ assistantText("ok"),
206
+ axTreeToolResult("t2", "tree-2"),
207
+ ];
208
+
209
+ const result = compactAxTreeHistory(messages);
210
+
211
+ // 3 total AX tree blocks, keep last 2 → strip only first block (t1a)
212
+ const msg0 = result[0];
213
+ const block0 = msg0.content[0];
214
+ expect(block0.type).toBe("tool_result");
215
+ if (block0.type === "tool_result") {
216
+ expect(block0.content).toContain("<ax_tree_omitted />");
217
+ expect(block0.content).not.toContain("<ax-tree>");
218
+ }
219
+
220
+ // Second block in same message (t1b) should be kept
221
+ const block1 = msg0.content[1];
222
+ expect(block1.type).toBe("tool_result");
223
+ if (block1.type === "tool_result") {
224
+ expect(block1.content).toContain("<ax-tree>");
225
+ expect(block1.content).not.toContain("<ax_tree_omitted />");
226
+ }
227
+
228
+ // Last message (t2) should also be kept
229
+ const lastBlock = result[2].content[0];
230
+ expect(lastBlock.type).toBe("tool_result");
231
+ if (lastBlock.type === "tool_result") {
232
+ expect(lastBlock.content).toContain("<ax-tree>");
233
+ }
234
+ });
235
+
185
236
  test("is pure — does not mutate input messages", () => {
186
237
  const messages: Message[] = [
187
238
  axTreeToolResult("t1", "tree-1"),
package/src/agent/loop.ts CHANGED
@@ -35,6 +35,7 @@ export interface CheckpointInfo {
35
35
  turnIndex: number;
36
36
  toolCount: number;
37
37
  hasToolUse: boolean;
38
+ history: Message[]; // current history snapshot for token estimation
38
39
  }
39
40
 
40
41
  export type CheckpointDecision = "continue" | "yield";
@@ -71,7 +72,13 @@ export type AgentEvent =
71
72
  toolUseId: string;
72
73
  accumulatedJson: string;
73
74
  }
74
- | { type: "server_tool_start"; name: string; toolUseId: string }
75
+ | {
76
+ type: "server_tool_start";
77
+ name: string;
78
+ toolUseId: string;
79
+ input: Record<string, unknown>;
80
+ }
81
+ | { type: "server_tool_complete"; toolUseId: string }
75
82
  | { type: "error"; error: Error }
76
83
  | {
77
84
  type: "usage";
@@ -305,6 +312,12 @@ export class AgentLoop {
305
312
  type: "server_tool_start",
306
313
  name: event.name,
307
314
  toolUseId: event.toolUseId,
315
+ input: event.input,
316
+ });
317
+ } else if (event.type === "server_tool_complete") {
318
+ onEvent({
319
+ type: "server_tool_complete",
320
+ toolUseId: event.toolUseId,
308
321
  });
309
322
  }
310
323
  },
@@ -561,6 +574,7 @@ export class AgentLoop {
561
574
  turnIndex: toolUseTurns - 1, // 0-based (toolUseTurns was already incremented)
562
575
  toolCount: toolUseBlocks.length,
563
576
  hasToolUse: true,
577
+ history,
564
578
  });
565
579
  if (decision === "yield") {
566
580
  break;
@@ -622,40 +636,53 @@ export function escapeAxTreeContent(content: string): string {
622
636
  * `MAX_AX_TREES_IN_HISTORY` `<ax-tree>` blocks have been replaced with a
623
637
  * short placeholder. This keeps the conversation context small so that
624
638
  * TTFT does not grow linearly with step count in computer-use sessions.
639
+ *
640
+ * Counting is per-block, not per-message — a single user message can
641
+ * contain multiple tool_result blocks each with their own AX tree snapshot.
625
642
  */
626
643
  export function compactAxTreeHistory(messages: Message[]): Message[] {
627
- // Collect indices of user messages that contain an <ax-tree> block
628
- const indicesWithAxTree: number[] = [];
644
+ // Collect (messageIndex, blockIndex) for every tool_result block with <ax-tree>
645
+ const axBlocks: Array<{ msgIdx: number; blockIdx: number }> = [];
629
646
  for (let i = 0; i < messages.length; i++) {
630
647
  const msg = messages[i];
631
648
  if (msg.role !== "user") continue;
632
- for (const block of msg.content) {
649
+ for (let j = 0; j < msg.content.length; j++) {
650
+ const block = msg.content[j];
633
651
  if (
634
652
  block.type === "tool_result" &&
635
653
  typeof block.content === "string" &&
636
654
  block.content.includes("<ax-tree>")
637
655
  ) {
638
- indicesWithAxTree.push(i);
639
- break;
656
+ axBlocks.push({ msgIdx: i, blockIdx: j });
640
657
  }
641
658
  }
642
659
  }
643
660
 
644
- if (indicesWithAxTree.length <= MAX_AX_TREES_IN_HISTORY) {
661
+ if (axBlocks.length <= MAX_AX_TREES_IN_HISTORY) {
645
662
  return messages;
646
663
  }
647
664
 
648
- const toStrip = new Set(indicesWithAxTree.slice(0, -MAX_AX_TREES_IN_HISTORY));
665
+ // Build a set of "msgIdx:blockIdx" keys for blocks that should be stripped
666
+ const toStrip = new Set(
667
+ axBlocks
668
+ .slice(0, -MAX_AX_TREES_IN_HISTORY)
669
+ .map((b) => `${b.msgIdx}:${b.blockIdx}`),
670
+ );
649
671
 
650
672
  return messages.map((msg, idx) => {
651
- if (!toStrip.has(idx)) return msg;
673
+ // Quick check: does this message have any blocks to strip?
674
+ const hasStripTarget = msg.content.some((_, j) =>
675
+ toStrip.has(`${idx}:${j}`),
676
+ );
677
+ if (!hasStripTarget) return msg;
678
+
652
679
  return {
653
680
  ...msg,
654
- content: msg.content.map((block) => {
681
+ content: msg.content.map((block, j) => {
655
682
  if (
683
+ toStrip.has(`${idx}:${j}`) &&
656
684
  block.type === "tool_result" &&
657
- typeof block.content === "string" &&
658
- block.content.includes("<ax-tree>")
685
+ typeof block.content === "string"
659
686
  ) {
660
687
  return {
661
688
  ...block,
@@ -16,7 +16,7 @@ Conversational guardian verification control-plane invocation is guardian-only.
16
16
 
17
17
  ## Memory Provenance Invariant
18
18
 
19
- All memory extraction and retrieval decisions must consider actor-role provenance. Untrusted actors (non-guardian, unverified_channel) must not trigger profile extraction or receive memory recall/conflict disclosures. This invariant is enforced in `indexer.ts` (write gate) and `session-memory.ts` (read gate).
19
+ All memory retrieval decisions must consider actor-role provenance. Untrusted actors (non-guardian, unverified_channel) must not receive memory recall results. This invariant is enforced in `indexer.ts` (write gate) and `session-memory.ts` (read gate).
20
20
 
21
21
  ## Guardian Privilege Isolation Invariant
22
22
 
@@ -424,11 +424,17 @@ const accessRequestResolver: GuardianRequestResolver = {
424
424
  dedupeKey: `trusted-contact:denied:${request.id}`,
425
425
  });
426
426
  } else if (desktopDeliverUrl && requesterChatId) {
427
+ // For Slack, route to DM via requesterExternalUserId (user ID) instead
428
+ // of requesterChatId (channel ID) to avoid posting in public channels.
429
+ const targetChatId =
430
+ channel === "slack" && requesterExternalUserId
431
+ ? requesterExternalUserId
432
+ : requesterChatId;
427
433
  try {
428
434
  await deliverChannelReply(
429
435
  desktopDeliverUrl,
430
436
  {
431
- chatId: requesterChatId,
437
+ chatId: targetChatId,
432
438
  text: "Your access request has been denied by the guardian.",
433
439
  assistantId,
434
440
  },
@@ -601,11 +607,17 @@ const accessRequestResolver: GuardianRequestResolver = {
601
607
  });
602
608
  }
603
609
  } else if (desktopDeliverUrl && requesterChatId) {
610
+ // For Slack, route to DM via requesterExternalUserId (user ID) instead
611
+ // of requesterChatId (channel ID) to avoid posting in public channels.
612
+ const targetChatId =
613
+ channel === "slack" && requesterExternalUserId
614
+ ? requesterExternalUserId
615
+ : requesterChatId;
604
616
  try {
605
617
  await deliverChannelReply(
606
618
  desktopDeliverUrl,
607
619
  {
608
- chatId: requesterChatId,
620
+ chatId: targetChatId,
609
621
  text:
610
622
  "Your access request has been approved! " +
611
623
  "Please enter the 6-digit verification code you receive from the guardian.",
@@ -6,6 +6,7 @@
6
6
  * same pattern as EmbeddingRuntimeManager.
7
7
  */
8
8
 
9
+ import { createHash } from "node:crypto";
9
10
  import {
10
11
  chmodSync,
11
12
  existsSync,
@@ -54,11 +55,69 @@ function npmTarballUrl(pkg: string, version: string): string {
54
55
  return `https://registry.npmjs.org/${encoded}/-/${basename}-${version}.tgz`;
55
56
  }
56
57
 
58
+ async function fetchNpmIntegrity(
59
+ pkg: string,
60
+ version: string,
61
+ ): Promise<string> {
62
+ const encoded = pkg.replace("/", "%2f");
63
+ const metadataUrl = `https://registry.npmjs.org/${encoded}/${version}`;
64
+ const response = await fetch(metadataUrl);
65
+ if (!response.ok) {
66
+ throw new Error(
67
+ `Failed to fetch npm metadata for ${pkg}@${version}: ${response.status} ${response.statusText}`,
68
+ );
69
+ }
70
+
71
+ const data = (await response.json()) as {
72
+ dist?: { integrity?: string; shasum?: string };
73
+ };
74
+
75
+ if (
76
+ typeof data.dist?.integrity === "string" &&
77
+ data.dist.integrity.length > 0
78
+ ) {
79
+ return data.dist.integrity;
80
+ }
81
+
82
+ if (typeof data.dist?.shasum === "string" && data.dist.shasum.length > 0) {
83
+ return `sha1-${Buffer.from(data.dist.shasum, "hex").toString("base64")}`;
84
+ }
85
+
86
+ throw new Error(`Missing npm integrity metadata for ${pkg}@${version}`);
87
+ }
88
+
89
+ function verifyIntegrity(
90
+ tarball: Uint8Array,
91
+ integrity: string,
92
+ pkg: string,
93
+ version: string,
94
+ ): void {
95
+ const [algorithm, expectedDigest] = integrity.split("-", 2);
96
+ if (!algorithm || !expectedDigest) {
97
+ throw new Error(`Invalid integrity metadata for ${pkg}@${version}`);
98
+ }
99
+
100
+ if (algorithm !== "sha512" && algorithm !== "sha1") {
101
+ throw new Error(
102
+ `Unsupported integrity algorithm ${algorithm} for ${pkg}@${version}`,
103
+ );
104
+ }
105
+
106
+ const actualDigest = createHash(algorithm).update(tarball).digest("base64");
107
+ if (actualDigest !== expectedDigest) {
108
+ throw new Error(`Integrity verification failed for ${pkg}@${version}`);
109
+ }
110
+ }
111
+
57
112
  async function downloadAndExtract(
113
+ pkg: string,
114
+ version: string,
58
115
  url: string,
59
116
  targetDir: string,
60
117
  ): Promise<void> {
61
- log.info({ url, targetDir }, "Downloading npm package");
118
+ log.info({ pkg, version, url, targetDir }, "Downloading npm package");
119
+
120
+ const integrity = await fetchNpmIntegrity(pkg, version);
62
121
 
63
122
  const response = await fetch(url);
64
123
  if (!response.ok) {
@@ -67,7 +126,8 @@ async function downloadAndExtract(
67
126
  );
68
127
  }
69
128
 
70
- const tarball = await response.arrayBuffer();
129
+ const tarball = new Uint8Array(await response.arrayBuffer());
130
+ verifyIntegrity(tarball, integrity, pkg, version);
71
131
  mkdirSync(targetDir, { recursive: true });
72
132
 
73
133
  const tmpTar = join(targetDir, `download-${Date.now()}.tgz`);
@@ -191,10 +251,14 @@ async function install(baseDir: string): Promise<void> {
191
251
  // Download esbuild binary + preact in parallel
192
252
  await Promise.all([
193
253
  downloadAndExtract(
254
+ `@esbuild/${esbuildPlatform}`,
255
+ ESBUILD_VERSION,
194
256
  npmTarballUrl(`@esbuild/${esbuildPlatform}`, ESBUILD_VERSION),
195
257
  join(tmpDir, "esbuild-pkg"),
196
258
  ),
197
259
  downloadAndExtract(
260
+ "preact",
261
+ PREACT_VERSION,
198
262
  npmTarballUrl("preact", PREACT_VERSION),
199
263
  join(tmpDir, "node_modules", "preact"),
200
264
  ),