@vellumai/assistant 0.5.13 → 0.5.15

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 (425) hide show
  1. package/.env.example +1 -6
  2. package/AGENTS.md +4 -0
  3. package/ARCHITECTURE.md +0 -1
  4. package/bunfig.toml +1 -0
  5. package/docs/architecture/memory.md +3 -3
  6. package/openapi.yaml +127 -22
  7. package/package.json +1 -1
  8. package/src/__tests__/access-request-decision.test.ts +2 -32
  9. package/src/__tests__/actor-token-service.test.ts +1 -31
  10. package/src/__tests__/anthropic-provider.test.ts +53 -40
  11. package/src/__tests__/app-git-history.test.ts +9 -17
  12. package/src/__tests__/app-git-service.test.ts +14 -20
  13. package/src/__tests__/app-store-dir-names.test.ts +10 -20
  14. package/src/__tests__/approval-cascade.test.ts +2 -19
  15. package/src/__tests__/approval-primitive.test.ts +2 -27
  16. package/src/__tests__/approval-routes-http.test.ts +2 -30
  17. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -28
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +2 -45
  19. package/src/__tests__/attachments-store.test.ts +5 -32
  20. package/src/__tests__/audit-log-rotation.test.ts +5 -36
  21. package/src/__tests__/avatar-e2e.test.ts +1 -9
  22. package/src/__tests__/avatar-generator.test.ts +1 -7
  23. package/src/__tests__/browser-fill-credential.test.ts +0 -4
  24. package/src/__tests__/browser-manager.test.ts +0 -6
  25. package/src/__tests__/call-controller.test.ts +1 -22
  26. package/src/__tests__/call-conversation-messages.test.ts +0 -21
  27. package/src/__tests__/call-domain.test.ts +0 -25
  28. package/src/__tests__/call-pointer-messages.test.ts +0 -21
  29. package/src/__tests__/call-recovery.test.ts +0 -22
  30. package/src/__tests__/call-routes-http.test.ts +0 -24
  31. package/src/__tests__/call-store.test.ts +0 -21
  32. package/src/__tests__/cancel-resolves-conversation-key.test.ts +0 -24
  33. package/src/__tests__/canonical-guardian-store.test.ts +48 -21
  34. package/src/__tests__/channel-approval-routes.test.ts +6 -26
  35. package/src/__tests__/channel-approvals.test.ts +1 -38
  36. package/src/__tests__/channel-delivery-store.test.ts +0 -21
  37. package/src/__tests__/channel-guardian.test.ts +0 -26
  38. package/src/__tests__/channel-reply-delivery.test.ts +5 -0
  39. package/src/__tests__/channel-retry-sweep.test.ts +0 -21
  40. package/src/__tests__/checker.test.ts +26 -61
  41. package/src/__tests__/clawhub.test.ts +9 -25
  42. package/src/__tests__/cli-command-risk-guard.test.ts +0 -18
  43. package/src/__tests__/config-loader-backfill.test.ts +9 -28
  44. package/src/__tests__/config-schema-cmd.test.ts +5 -25
  45. package/src/__tests__/config-schema.test.ts +21 -40
  46. package/src/__tests__/config-watcher.test.ts +4 -91
  47. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -21
  48. package/src/__tests__/contacts-tools.test.ts +0 -21
  49. package/src/__tests__/context-memory-e2e.test.ts +0 -21
  50. package/src/__tests__/context-window-manager.test.ts +130 -3
  51. package/src/__tests__/conversation-abort-tool-results.test.ts +0 -4
  52. package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -4
  53. package/src/__tests__/conversation-agent-loop.test.ts +0 -4
  54. package/src/__tests__/conversation-attachments.test.ts +1 -24
  55. package/src/__tests__/conversation-attention-store.test.ts +0 -21
  56. package/src/__tests__/conversation-attention-telegram.test.ts +0 -22
  57. package/src/__tests__/conversation-clear-safety.test.ts +0 -22
  58. package/src/__tests__/conversation-confirmation-signals.test.ts +2 -21
  59. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -24
  60. package/src/__tests__/conversation-disk-view-integration.test.ts +1 -23
  61. package/src/__tests__/conversation-disk-view.test.ts +5 -27
  62. package/src/__tests__/conversation-error.test.ts +1 -1
  63. package/src/__tests__/conversation-fork-crud.test.ts +1 -33
  64. package/src/__tests__/conversation-fork-route.test.ts +0 -27
  65. package/src/__tests__/conversation-history-web-search.test.ts +23 -16
  66. package/src/__tests__/conversation-init.benchmark.test.ts +22 -43
  67. package/src/__tests__/conversation-key-store-disk-view.test.ts +8 -34
  68. package/src/__tests__/conversation-load-history-repair.test.ts +0 -4
  69. package/src/__tests__/conversation-pre-run-repair.test.ts +0 -4
  70. package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -4
  71. package/src/__tests__/conversation-queue.test.ts +8 -8
  72. package/src/__tests__/conversation-routes-disk-view.test.ts +13 -51
  73. package/src/__tests__/conversation-runtime-assembly.test.ts +64 -38
  74. package/src/__tests__/conversation-slash-commands.test.ts +5 -0
  75. package/src/__tests__/conversation-slash-queue.test.ts +0 -4
  76. package/src/__tests__/conversation-slash-unknown.test.ts +0 -4
  77. package/src/__tests__/conversation-speed-override.test.ts +326 -0
  78. package/src/__tests__/conversation-starter-routes.test.ts +0 -23
  79. package/src/__tests__/conversation-store.test.ts +0 -21
  80. package/src/__tests__/conversation-unread-route.test.ts +0 -24
  81. package/src/__tests__/conversation-usage.test.ts +56 -21
  82. package/src/__tests__/conversation-wipe.test.ts +0 -21
  83. package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -4
  84. package/src/__tests__/conversation-workspace-injection.test.ts +0 -4
  85. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -4
  86. package/src/__tests__/credential-execution-shell-lockdown.test.ts +8 -5
  87. package/src/__tests__/credential-vault-unit.test.ts +9 -428
  88. package/src/__tests__/credentials-cli.test.ts +10 -10
  89. package/src/__tests__/daemon-assistant-events.test.ts +0 -19
  90. package/src/__tests__/date-context.test.ts +77 -97
  91. package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +7 -24
  92. package/src/__tests__/db-llm-request-log-provider-migration.test.ts +29 -42
  93. package/src/__tests__/delete-managed-skill-tool.test.ts +2 -10
  94. package/src/__tests__/deterministic-verification-control-plane.test.ts +1 -26
  95. package/src/__tests__/docker-signing-key-bootstrap.test.ts +61 -15
  96. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +2 -36
  97. package/src/__tests__/email-cli.test.ts +6 -6
  98. package/src/__tests__/ephemeral-permissions.test.ts +5 -17
  99. package/src/__tests__/first-greeting.test.ts +4 -32
  100. package/src/__tests__/followup-tools.test.ts +0 -21
  101. package/src/__tests__/gateway-only-enforcement.test.ts +0 -20
  102. package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -23
  103. package/src/__tests__/guardian-action-followup-executor.test.ts +0 -23
  104. package/src/__tests__/guardian-action-followup-store.test.ts +0 -21
  105. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -21
  106. package/src/__tests__/guardian-action-late-reply.test.ts +0 -21
  107. package/src/__tests__/guardian-action-store.test.ts +0 -21
  108. package/src/__tests__/guardian-action-sweep.test.ts +0 -21
  109. package/src/__tests__/guardian-binding-drift-heal.test.ts +0 -23
  110. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +172 -22
  111. package/src/__tests__/guardian-dispatch.test.ts +0 -21
  112. package/src/__tests__/guardian-grant-minting.test.ts +0 -22
  113. package/src/__tests__/guardian-outbound-http.test.ts +0 -22
  114. package/src/__tests__/guardian-principal-id-roundtrip.test.ts +0 -23
  115. package/src/__tests__/guardian-routing-invariants.test.ts +0 -22
  116. package/src/__tests__/guardian-routing-state.test.ts +0 -22
  117. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -24
  118. package/src/__tests__/headless-browser-interactions.test.ts +0 -4
  119. package/src/__tests__/headless-browser-navigate.test.ts +0 -4
  120. package/src/__tests__/headless-browser-read-tools.test.ts +0 -4
  121. package/src/__tests__/headless-browser-snapshot.test.ts +0 -4
  122. package/src/__tests__/heartbeat-service.test.ts +99 -26
  123. package/src/__tests__/hooks-blocking.test.ts +3 -3
  124. package/src/__tests__/hooks-config.test.ts +7 -7
  125. package/src/__tests__/hooks-discovery.test.ts +3 -3
  126. package/src/__tests__/hooks-integration.test.ts +5 -5
  127. package/src/__tests__/hooks-manager.test.ts +3 -3
  128. package/src/__tests__/hooks-runner.test.ts +5 -23
  129. package/src/__tests__/hooks-settings.test.ts +3 -3
  130. package/src/__tests__/hooks-templates.test.ts +3 -3
  131. package/src/__tests__/http-conversation-lineage.test.ts +0 -27
  132. package/src/__tests__/identity-intro-cache.test.ts +0 -4
  133. package/src/__tests__/inbound-invite-redemption.test.ts +0 -22
  134. package/src/__tests__/inline-skill-load-permissions.test.ts +5 -16
  135. package/src/__tests__/intent-routing.test.ts +2 -55
  136. package/src/__tests__/invite-redemption-service.test.ts +0 -21
  137. package/src/__tests__/invite-routes-http.test.ts +0 -21
  138. package/src/__tests__/jobs-store-qdrant-breaker.test.ts +0 -17
  139. package/src/__tests__/journal-context.test.ts +8 -75
  140. package/src/__tests__/list-messages-attachments.test.ts +0 -22
  141. package/src/__tests__/llm-context-route-provider.test.ts +0 -21
  142. package/src/__tests__/llm-request-log-turn-query.test.ts +46 -28
  143. package/src/__tests__/llm-usage-store.test.ts +0 -21
  144. package/src/__tests__/log-export-workspace.test.ts +1 -1
  145. package/src/__tests__/managed-skill-lifecycle.test.ts +1 -1
  146. package/src/__tests__/managed-store.test.ts +1 -1
  147. package/src/__tests__/mcp-cli.test.ts +7 -10
  148. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +0 -21
  149. package/src/__tests__/memory-jobs-worker-backoff.test.ts +0 -11
  150. package/src/__tests__/memory-lifecycle-e2e.test.ts +0 -21
  151. package/src/__tests__/memory-recall-log-store.test.ts +0 -27
  152. package/src/__tests__/memory-recall-quality.test.ts +0 -21
  153. package/src/__tests__/memory-regressions.experimental.test.ts +31 -30
  154. package/src/__tests__/memory-regressions.test.ts +282 -70
  155. package/src/__tests__/memory-retrieval.benchmark.test.ts +0 -21
  156. package/src/__tests__/memory-upsert-concurrency.test.ts +0 -21
  157. package/src/__tests__/messaging-send-tool.test.ts +201 -0
  158. package/src/__tests__/migration-cross-version-compatibility.test.ts +18 -13
  159. package/src/__tests__/migration-export-http.test.ts +7 -1
  160. package/src/__tests__/migration-import-commit-http.test.ts +16 -14
  161. package/src/__tests__/migration-import-preflight-http.test.ts +27 -44
  162. package/src/__tests__/migration-validate-http.test.ts +1 -28
  163. package/src/__tests__/native-web-search.test.ts +25 -22
  164. package/src/__tests__/non-member-access-request.test.ts +0 -22
  165. package/src/__tests__/notification-guardian-path.test.ts +0 -21
  166. package/src/__tests__/notification-schedule-dedup.test.ts +1 -25
  167. package/src/__tests__/oauth-apps-routes.test.ts +103 -2
  168. package/src/__tests__/oauth-cli.test.ts +52 -0
  169. package/src/__tests__/oauth-provider-profiles.test.ts +0 -16
  170. package/src/__tests__/oauth-provider-serializer.test.ts +232 -0
  171. package/src/__tests__/oauth-providers-routes.test.ts +257 -0
  172. package/src/__tests__/oauth-store.test.ts +0 -21
  173. package/src/__tests__/onboarding-template-contract.test.ts +2 -2
  174. package/src/__tests__/openai-provider.test.ts +261 -0
  175. package/src/__tests__/pairing-concurrent.test.ts +6 -6
  176. package/src/__tests__/pairing-routes.test.ts +7 -1
  177. package/src/__tests__/path-policy.test.ts +1 -1
  178. package/src/__tests__/platform.test.ts +64 -88
  179. package/src/__tests__/playbook-execution.test.ts +0 -21
  180. package/src/__tests__/playbook-tools.test.ts +0 -21
  181. package/src/__tests__/pricing.test.ts +100 -0
  182. package/src/__tests__/relay-server.test.ts +1 -25
  183. package/src/__tests__/runtime-attachment-metadata.test.ts +0 -24
  184. package/src/__tests__/runtime-events-sse-parity.test.ts +2 -24
  185. package/src/__tests__/runtime-events-sse.test.ts +0 -24
  186. package/src/__tests__/sandbox-diagnostics.test.ts +2 -1
  187. package/src/__tests__/scaffold-managed-skill-tool.test.ts +1 -1
  188. package/src/__tests__/schedule-store.test.ts +0 -21
  189. package/src/__tests__/schedule-tools.test.ts +0 -21
  190. package/src/__tests__/scheduler-recurrence.test.ts +0 -21
  191. package/src/__tests__/scoped-approval-grants.test.ts +0 -21
  192. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -21
  193. package/src/__tests__/secret-allowlist.test.ts +1 -1
  194. package/src/__tests__/secret-ingress-channel.test.ts +0 -5
  195. package/src/__tests__/secret-ingress-cli.test.ts +0 -6
  196. package/src/__tests__/secret-ingress-http.test.ts +0 -5
  197. package/src/__tests__/secret-ingress.test.ts +0 -5
  198. package/src/__tests__/send-endpoint-busy.test.ts +0 -24
  199. package/src/__tests__/sequence-store.test.ts +0 -21
  200. package/src/__tests__/server-history-render.test.ts +0 -24
  201. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -4
  202. package/src/__tests__/skill-load-inline-command.test.ts +9 -0
  203. package/src/__tests__/skill-load-inline-includes.test.ts +9 -0
  204. package/src/__tests__/skill-load-tool.test.ts +11 -0
  205. package/src/__tests__/skills-uninstall.test.ts +10 -8
  206. package/src/__tests__/skills.test.ts +1 -1
  207. package/src/__tests__/slack-channel-config.test.ts +1 -1
  208. package/src/__tests__/slack-inbound-verification.test.ts +0 -22
  209. package/src/__tests__/starter-bundle.test.ts +4 -1
  210. package/src/__tests__/suggestion-routes.test.ts +2 -0
  211. package/src/__tests__/system-prompt.test.ts +1 -1
  212. package/src/__tests__/terminal-tools.test.ts +1 -1
  213. package/src/__tests__/test-preload.ts +31 -0
  214. package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -1
  215. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
  216. package/src/__tests__/tool-executor.test.ts +0 -20
  217. package/src/__tests__/tool-input-summary.test.ts +124 -0
  218. package/src/__tests__/tool-preview-lifecycle.test.ts +2 -1
  219. package/src/__tests__/trust-store.test.ts +7 -1
  220. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +1 -1
  221. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +1 -1
  222. package/src/__tests__/trusted-contact-multichannel.test.ts +1 -1
  223. package/src/__tests__/trusted-contact-verification.test.ts +1 -1
  224. package/src/__tests__/turn-boundary-resolution.test.ts +1 -1
  225. package/src/__tests__/twilio-routes.test.ts +1 -1
  226. package/src/__tests__/update-bulletin.test.ts +1 -1
  227. package/src/__tests__/vbundle-pax-and-symlink.test.ts +1 -1
  228. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -0
  229. package/src/__tests__/voice-scoped-grant-consumer.test.ts +1 -1
  230. package/src/__tests__/voice-session-bridge.test.ts +1 -1
  231. package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +4 -4
  232. package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +1 -1
  233. package/src/__tests__/workspace-migration-down-functions.test.ts +15 -3
  234. package/src/__tests__/workspace-migration-seed-device-id.test.ts +40 -4
  235. package/src/agent/loop.ts +6 -9
  236. package/src/approvals/guardian-decision-primitive.ts +46 -18
  237. package/src/approvals/guardian-request-resolvers.ts +19 -2
  238. package/src/calls/active-call-lease.ts +2 -2
  239. package/src/cli/AGENTS.md +1 -1
  240. package/src/cli/commands/doctor.ts +9 -9
  241. package/src/cli/commands/memory.ts +142 -0
  242. package/src/cli/commands/oauth/__tests__/connect.test.ts +13 -11
  243. package/src/cli/commands/oauth/__tests__/ping.test.ts +1 -1
  244. package/src/cli/commands/oauth/connect.ts +13 -12
  245. package/src/cli/commands/oauth/index.ts +1 -1
  246. package/src/cli/commands/oauth/providers.ts +47 -62
  247. package/src/cli/commands/platform/__tests__/connect.test.ts +72 -46
  248. package/src/cli/commands/platform/__tests__/disconnect.test.ts +54 -1
  249. package/src/cli/commands/platform/__tests__/status.test.ts +36 -0
  250. package/src/cli/commands/platform/connect.ts +17 -7
  251. package/src/cli/commands/platform/disconnect.ts +28 -3
  252. package/src/cli/commands/platform/index.ts +3 -3
  253. package/src/cli.ts +1 -299
  254. package/src/config/assistant-feature-flags.ts +23 -15
  255. package/src/config/bundled-skills/app-builder/TOOLS.json +16 -0
  256. package/src/config/bundled-skills/app-builder/tools/app-create.ts +4 -0
  257. package/src/config/bundled-skills/app-builder/tools/app-delete.ts +5 -1
  258. package/src/config/bundled-skills/app-builder/tools/app-generate-icon.ts +9 -1
  259. package/src/config/bundled-skills/app-builder/tools/app-refresh.ts +5 -1
  260. package/src/config/bundled-skills/contacts/TOOLS.json +8 -0
  261. package/src/config/bundled-skills/contacts/tools/contact-search.ts +10 -1
  262. package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +16 -2
  263. package/src/config/bundled-skills/media-processing/tools/ingest-media.ts +1 -0
  264. package/src/config/bundled-skills/messaging/SKILL.md +7 -7
  265. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +37 -0
  266. package/src/config/bundled-skills/slack/SKILL.md +18 -0
  267. package/src/config/env-registry.ts +15 -11
  268. package/src/config/env.ts +1 -11
  269. package/src/config/feature-flag-registry.json +16 -0
  270. package/src/config/schema.ts +4 -0
  271. package/src/config/schemas/heartbeat.ts +6 -1
  272. package/src/config/schemas/inference.ts +14 -3
  273. package/src/config/schemas/memory-processing.ts +16 -8
  274. package/src/config/schemas/memory-retrieval.ts +3 -3
  275. package/src/config/skills.ts +1 -1
  276. package/src/context/window-manager.ts +174 -51
  277. package/src/credential-execution/executable-discovery.ts +2 -2
  278. package/src/daemon/approved-devices-store.ts +2 -2
  279. package/src/daemon/assistant-attachments.ts +2 -0
  280. package/src/daemon/config-watcher.ts +4 -50
  281. package/src/daemon/conversation-agent-loop-handlers.ts +9 -1
  282. package/src/daemon/conversation-agent-loop.ts +12 -0
  283. package/src/daemon/conversation-error.ts +3 -5
  284. package/src/daemon/conversation-history.ts +7 -3
  285. package/src/daemon/conversation-lifecycle.ts +16 -0
  286. package/src/daemon/conversation-messaging.ts +1 -0
  287. package/src/daemon/conversation-notifiers.ts +67 -30
  288. package/src/daemon/conversation-process.ts +161 -2
  289. package/src/daemon/conversation-queue-manager.ts +2 -0
  290. package/src/daemon/conversation-runtime-assembly.ts +33 -11
  291. package/src/daemon/conversation-slash.ts +14 -3
  292. package/src/daemon/conversation-tool-setup.ts +2 -0
  293. package/src/daemon/conversation-usage.ts +32 -4
  294. package/src/daemon/conversation.ts +33 -1
  295. package/src/daemon/daemon-control.ts +32 -16
  296. package/src/daemon/date-context.ts +47 -45
  297. package/src/daemon/dictation-profile-store.ts +2 -2
  298. package/src/daemon/handlers/conversations.ts +19 -0
  299. package/src/daemon/handlers/shared.ts +14 -21
  300. package/src/daemon/lifecycle.ts +5 -7
  301. package/src/daemon/message-types/conversations.ts +2 -0
  302. package/src/daemon/message-types/guardian-actions.ts +3 -17
  303. package/src/daemon/message-types/integrations.ts +11 -1
  304. package/src/daemon/message-types/messages.ts +1 -0
  305. package/src/daemon/pairing-store.ts +2 -79
  306. package/src/daemon/server.ts +154 -8
  307. package/src/daemon/watch-handler.ts +65 -21
  308. package/src/email/guardrails.ts +3 -3
  309. package/src/heartbeat/heartbeat-service.ts +14 -7
  310. package/src/hooks/cli.ts +2 -2
  311. package/src/hooks/config.ts +2 -2
  312. package/src/hooks/discovery.ts +2 -2
  313. package/src/hooks/manager.ts +2 -2
  314. package/src/hooks/runner.ts +5 -2
  315. package/src/hooks/templates.ts +2 -2
  316. package/src/memory/admin.ts +181 -2
  317. package/src/memory/app-git-service.ts +61 -4
  318. package/src/memory/attachments-store.ts +2 -0
  319. package/src/memory/canonical-guardian-store.ts +16 -0
  320. package/src/memory/db-init.ts +8 -0
  321. package/src/memory/embedding-local.ts +5 -2
  322. package/src/memory/indexer.ts +44 -26
  323. package/src/memory/items-extractor.ts +34 -82
  324. package/src/memory/job-handlers/batch-extraction.ts +741 -0
  325. package/src/memory/job-handlers/journal-carry-forward.test.ts +383 -0
  326. package/src/memory/job-handlers/journal-carry-forward.ts +255 -0
  327. package/src/memory/jobs-store.ts +28 -0
  328. package/src/memory/jobs-worker.ts +56 -9
  329. package/src/memory/lifecycle-events-store.ts +4 -2
  330. package/src/memory/llm-request-log-store.ts +40 -2
  331. package/src/memory/llm-usage-store.ts +4 -3
  332. package/src/memory/migrations/199-guardian-request-enrichment-columns.ts +71 -0
  333. package/src/memory/migrations/200-usage-llm-call-count.ts +20 -0
  334. package/src/memory/migrations/index.ts +2 -0
  335. package/src/memory/query-expansion.ts +83 -0
  336. package/src/memory/retriever.test.ts +119 -0
  337. package/src/memory/retriever.ts +513 -105
  338. package/src/memory/schema/guardian.ts +4 -0
  339. package/src/memory/schema/infrastructure.ts +1 -0
  340. package/src/memory/search/formatting.test.ts +140 -0
  341. package/src/memory/search/formatting.ts +143 -198
  342. package/src/memory/search/mmr.ts +136 -0
  343. package/src/memory/search/staleness.ts +0 -15
  344. package/src/memory/search/tier-classifier.ts +10 -21
  345. package/src/memory/search/types.ts +17 -0
  346. package/src/messaging/providers/slack/adapter.ts +51 -5
  347. package/src/notifications/broadcaster.ts +13 -0
  348. package/src/notifications/copy-composer.ts +8 -0
  349. package/src/oauth/connect-orchestrator.ts +1 -1
  350. package/src/oauth/connection-resolver.ts +2 -2
  351. package/src/oauth/provider-serializer.ts +116 -0
  352. package/src/permissions/trust-store.ts +24 -7
  353. package/src/prompts/__tests__/build-cli-reference-section.test.ts +5 -0
  354. package/src/prompts/journal-context.ts +50 -35
  355. package/src/prompts/persona-resolver.ts +1 -1
  356. package/src/prompts/system-prompt.ts +27 -28
  357. package/src/prompts/templates/BOOTSTRAP.md +14 -1
  358. package/src/prompts/templates/HEARTBEAT.md +10 -0
  359. package/src/prompts/templates/NOW.md +19 -25
  360. package/src/prompts/templates/SOUL.md +13 -1
  361. package/src/prompts/templates/UPDATES.md +12 -0
  362. package/src/prompts/update-bulletin.ts +1 -1
  363. package/src/providers/anthropic/client.ts +89 -18
  364. package/src/providers/model-catalog.ts +22 -2
  365. package/src/providers/model-intents.ts +2 -2
  366. package/src/providers/openai/client.ts +40 -1
  367. package/src/providers/retry.ts +23 -4
  368. package/src/providers/types.ts +2 -0
  369. package/src/runtime/assistant-scope.ts +1 -1
  370. package/src/runtime/auth/__tests__/credential-service.test.ts +1 -0
  371. package/src/runtime/auth/route-policy.ts +1 -0
  372. package/src/runtime/auth/token-service.ts +51 -29
  373. package/src/runtime/confirmation-request-guardian-bridge.ts +3 -1
  374. package/src/runtime/guardian-decision-types.ts +16 -10
  375. package/src/runtime/http-server.ts +3 -14
  376. package/src/runtime/http-types.ts +1 -0
  377. package/src/runtime/migrations/vbundle-builder.ts +7 -4
  378. package/src/runtime/migrations/vbundle-import-analyzer.ts +0 -4
  379. package/src/runtime/migrations/vbundle-importer.ts +1 -1
  380. package/src/runtime/routes/conversation-query-routes.ts +40 -8
  381. package/src/runtime/routes/conversation-routes.ts +125 -3
  382. package/src/runtime/routes/guardian-action-routes.ts +9 -3
  383. package/src/runtime/routes/identity-routes.ts +25 -4
  384. package/src/runtime/routes/llm-context-normalization.ts +1 -0
  385. package/src/runtime/routes/log-export-routes.ts +34 -12
  386. package/src/runtime/routes/migration-routes.ts +6 -10
  387. package/src/runtime/routes/oauth-apps.ts +2 -9
  388. package/src/runtime/routes/oauth-providers.ts +60 -0
  389. package/src/runtime/routes/pairing-routes.ts +0 -8
  390. package/src/runtime/routes/settings-routes.ts +0 -1
  391. package/src/runtime/routes/telemetry-routes.ts +16 -4
  392. package/src/security/encrypted-store.ts +2 -2
  393. package/src/security/secret-allowlist.ts +3 -3
  394. package/src/signals/emit-event.ts +42 -0
  395. package/src/signals/user-message.ts +37 -0
  396. package/src/telemetry/usage-telemetry-reporter.test.ts +83 -19
  397. package/src/telemetry/usage-telemetry-reporter.ts +23 -17
  398. package/src/tools/browser/runtime-check.ts +2 -2
  399. package/src/tools/credentials/vault.ts +2 -249
  400. package/src/tools/memory/definitions.ts +1 -1
  401. package/src/tools/memory/handlers.test.ts +50 -8
  402. package/src/tools/memory/handlers.ts +3 -1
  403. package/src/tools/side-effects.ts +1 -6
  404. package/src/tools/terminal/safe-env.ts +3 -2
  405. package/src/tools/terminal/shell.ts +11 -14
  406. package/src/tools/tool-approval-handler.ts +20 -1
  407. package/src/tools/tool-input-summary.ts +66 -0
  408. package/src/tools/types.ts +4 -0
  409. package/src/usage/types.ts +4 -0
  410. package/src/util/device-id.ts +10 -10
  411. package/src/util/platform.ts +71 -33
  412. package/src/util/pricing.ts +19 -6
  413. package/src/util/strip-comment-lines.ts +28 -0
  414. package/src/workspace/git-service.ts +8 -18
  415. package/src/workspace/migrations/003-seed-device-id.ts +6 -4
  416. package/src/workspace/migrations/016-extract-feature-flags-to-protected.ts +7 -1
  417. package/src/workspace/migrations/017-seed-persona-dirs.ts +2 -4
  418. package/src/workspace/migrations/021-move-signals-to-workspace.ts +84 -0
  419. package/src/workspace/migrations/022-move-hooks-to-workspace.ts +94 -0
  420. package/src/workspace/migrations/023-move-config-files-to-workspace.ts +86 -0
  421. package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +126 -0
  422. package/src/workspace/migrations/migrate-to-workspace-volume.ts +3 -6
  423. package/src/workspace/migrations/registry.ts +8 -0
  424. package/src/signals/confirm.ts +0 -82
  425. package/src/signals/trust-rule.ts +0 -174
@@ -1,8 +1,16 @@
1
- import { createUserMessage } from "../agent/message-types.js";
2
1
  import type { ContextWindowConfig } from "../config/types.js";
3
- import type { ContentBlock, Message, Provider } from "../providers/types.js";
2
+ import type {
3
+ ContentBlock,
4
+ ImageContent,
5
+ Message,
6
+ Provider,
7
+ } from "../providers/types.js";
4
8
  import { getLogger } from "../util/logger.js";
5
- import { estimatePromptTokens, estimateTextTokens } from "./token-estimator.js";
9
+ import {
10
+ estimateContentBlockTokens,
11
+ estimatePromptTokens,
12
+ estimateTextTokens,
13
+ } from "./token-estimator.js";
6
14
  import { truncateToolResultsAcrossHistory } from "./tool-result-truncation.js";
7
15
 
8
16
  const log = getLogger("context-window");
@@ -399,13 +407,13 @@ export class ContextWindowManager {
399
407
  };
400
408
  }
401
409
 
402
- const transcript = this.capTranscriptToTokenBudget(
403
- serializeMessages(compactableMessages),
410
+ const transcriptBlocks = this.capTranscriptBlocksToTokenBudget(
411
+ serializeMessagesToContentBlocks(compactableMessages),
404
412
  existingSummary ?? "No previous summary.",
405
413
  );
406
414
  const summaryUpdate = await this.updateSummary(
407
415
  existingSummary ?? "No previous summary.",
408
- transcript,
416
+ transcriptBlocks,
409
417
  signal,
410
418
  );
411
419
  const summary = summaryUpdate.summary;
@@ -426,7 +434,8 @@ export class ContextWindowManager {
426
434
  // Media (images, files) in kept turns is preserved naturally — those
427
435
  // turns are carried forward as-is and their token cost is already
428
436
  // accounted for by pickKeepBoundary's estimatePromptTokens call.
429
- // Media in compacted turns is described textually in the summary transcript.
437
+ // Images in compacted turns are passed to the summarizer so it can
438
+ // describe their visual content in the summary text.
430
439
  const summaryMessage = createContextSummaryMessage(summary);
431
440
 
432
441
  const { messages: truncatedKeptMessages } =
@@ -555,18 +564,20 @@ export class ContextWindowManager {
555
564
  }
556
565
 
557
566
  /**
558
- * Trim the serialized transcript so that the summary prompt (system prompt +
559
- * existing summary + transcript + scaffolding) fits within the provider's
560
- * input token limit, minus the output budget reserved for the summary itself.
561
- * This prevents the summarizer LLM call from exceeding its context window
562
- * during forced compaction of very large histories.
567
+ * Trim the serialized transcript content blocks so that the summary prompt
568
+ * (system prompt + existing summary + transcript + scaffolding) fits within
569
+ * the provider's input token limit, minus the output budget reserved for the
570
+ * summary itself.
571
+ *
572
+ * When the transcript exceeds the budget, blocks are dropped from the
573
+ * beginning (oldest messages first) to preserve recent context. Image blocks
574
+ * are dropped before text blocks within each pass since they are expensive
575
+ * and their surrounding text context already captures the conversation flow.
563
576
  */
564
- private capTranscriptToTokenBudget(
565
- transcript: string,
577
+ private capTranscriptBlocksToTokenBudget(
578
+ blocks: ContentBlock[],
566
579
  currentSummary: string,
567
- ): string {
568
- // Reserve tokens for: system prompt, summary prompt scaffolding, existing
569
- // summary, message overhead, and the output (summaryMaxTokens).
580
+ ): ContentBlock[] {
570
581
  const overheadTokens =
571
582
  estimateTextTokens(SUMMARY_SYSTEM_PROMPT) +
572
583
  estimateTextTokens(currentSummary) +
@@ -580,26 +591,59 @@ export class ContextWindowManager {
580
591
  this.config.maxInputTokens - overheadTokens,
581
592
  );
582
593
 
583
- const transcriptTokens = estimateTextTokens(transcript);
584
- if (transcriptTokens <= maxTranscriptTokens) return transcript;
594
+ const estimateBlockTokens = (b: ContentBlock): number =>
595
+ estimateContentBlockTokens(b, { providerName: this.provider.name });
596
+
597
+ let totalTokens = 0;
598
+ for (const block of blocks) {
599
+ totalTokens += estimateBlockTokens(block);
600
+ }
601
+ if (totalTokens <= maxTranscriptTokens) return blocks;
602
+
603
+ // First pass: drop images from the beginning until we fit or run out of
604
+ // images to drop. Images are high-cost and their text context (message
605
+ // headers, surrounding tool_use/tool_result serializations) is preserved.
606
+ const result = [...blocks];
607
+ for (let i = 0; i < result.length && totalTokens > maxTranscriptTokens; i++) {
608
+ if (result[i].type === "image") {
609
+ totalTokens -= estimateBlockTokens(result[i]);
610
+ const stub: ContentBlock = {
611
+ type: "text",
612
+ text: `[image omitted from summary context]`,
613
+ };
614
+ totalTokens += estimateBlockTokens(stub);
615
+ result[i] = stub;
616
+ }
617
+ }
618
+ if (totalTokens <= maxTranscriptTokens) return result;
619
+
620
+ // Second pass: drop text blocks from the beginning (oldest) until we fit.
621
+ let dropUntil = 0;
622
+ let droppedTokens = 0;
623
+ for (let i = 0; i < result.length && totalTokens > maxTranscriptTokens; i++) {
624
+ droppedTokens += estimateBlockTokens(result[i]);
625
+ totalTokens -= estimateBlockTokens(result[i]);
626
+ dropUntil = i + 1;
627
+ }
585
628
 
586
- // Truncate from the beginning (older messages) to preserve recent context.
587
- const maxChars = maxTranscriptTokens * 4; // inverse of estimateTextTokens
588
- const truncated = transcript.slice(transcript.length - maxChars);
589
629
  log.info(
590
630
  {
591
- originalTokens: transcriptTokens,
631
+ originalTokens: totalTokens + droppedTokens,
592
632
  cappedTokens: maxTranscriptTokens,
593
- droppedTokens: transcriptTokens - maxTranscriptTokens,
633
+ droppedTokens,
594
634
  },
595
- "Capped summary transcript to fit provider input limit",
635
+ "Capped summary transcript blocks to fit provider input limit",
596
636
  );
597
- return `[earlier messages truncated]\n${truncated}`;
637
+
638
+ return [
639
+ { type: "text", text: "[earlier messages truncated]" } as ContentBlock,
640
+ ...result.slice(dropUntil),
641
+ ];
598
642
  }
599
643
 
600
644
  private async updateSummary(
601
645
  currentSummary: string,
602
- transcript: string,
646
+ transcriptBlocks: ContentBlock[],
603
647
  signal?: AbortSignal,
604
648
  ): Promise<{
605
649
  summary: string;
@@ -610,10 +654,14 @@ export class ContextWindowManager {
610
654
  cacheReadInputTokens: number;
611
655
  rawResponse?: unknown;
612
656
  }> {
613
- const prompt = buildSummaryPrompt(currentSummary, transcript);
657
+ const contentBlocks = buildSummaryContentBlocks(
658
+ currentSummary,
659
+ transcriptBlocks,
660
+ );
661
+ const summaryMessage: Message = { role: "user", content: contentBlocks };
614
662
  try {
615
663
  const response = await this.provider.sendMessage(
616
- [createUserMessage(prompt)],
664
+ [summaryMessage],
617
665
  undefined,
618
666
  SUMMARY_SYSTEM_PROMPT,
619
667
  {
@@ -639,8 +687,14 @@ export class ContextWindowManager {
639
687
  log.warn({ err }, "Summary generation failed, using local fallback");
640
688
  }
641
689
 
690
+ // Fallback: extract text-only transcript for local summary generation.
691
+ const textTranscript = transcriptBlocks
692
+ .filter((b): b is Extract<ContentBlock, { type: "text" }> => b.type === "text")
693
+ .map((b) => b.text)
694
+ .join("\n\n");
695
+
642
696
  return {
643
- summary: fallbackSummary(currentSummary, transcript),
697
+ summary: fallbackSummary(currentSummary, textTranscript),
644
698
  inputTokens: 0,
645
699
  outputTokens: 0,
646
700
  model: "",
@@ -789,35 +843,102 @@ export function createContextSummaryMessage(summary: string): Message {
789
843
  return message;
790
844
  }
791
845
 
792
- function buildSummaryPrompt(
846
+ /**
847
+ * Build content blocks for the summary prompt. Returns a mix of text blocks
848
+ * (for the scaffolding, existing summary, and serialized non-image content)
849
+ * and image blocks (preserved from the original messages so the summarizer
850
+ * can describe what was in them).
851
+ */
852
+ function buildSummaryContentBlocks(
793
853
  currentSummary: string,
794
- transcript: string,
795
- ): string {
854
+ transcriptBlocks: ContentBlock[],
855
+ ): ContentBlock[] {
796
856
  return [
797
- "Update the summary with new transcript data.",
798
- "If new information conflicts with older notes, keep the most recent and explicit detail.",
799
- "Keep all unresolved asks and next steps.",
800
- "",
801
- "### Existing Summary",
802
- currentSummary.trim().length > 0 ? currentSummary.trim() : "None.",
803
- "",
804
- "### Transcript",
805
- transcript,
806
- ].join("\n");
857
+ {
858
+ type: "text",
859
+ text: [
860
+ "Update the summary with new transcript data.",
861
+ "If new information conflicts with older notes, keep the most recent and explicit detail.",
862
+ "Keep all unresolved asks and next steps.",
863
+ "For any images included below, describe their visual content in the summary so the information is preserved after compaction.",
864
+ "",
865
+ "### Existing Summary",
866
+ currentSummary.trim().length > 0 ? currentSummary.trim() : "None.",
867
+ "",
868
+ "### Transcript",
869
+ ].join("\n"),
870
+ } as ContentBlock,
871
+ ...transcriptBlocks,
872
+ ];
807
873
  }
808
874
 
809
- function serializeMessages(messages: Message[]): string {
810
- return messages.map((m, i) => serializeForSummary(m, i)).join("\n\n");
811
- }
875
+ /**
876
+ * Serialize messages into a sequence of content blocks. Text-based content
877
+ * (tool calls, tool results, thinking, etc.) is serialized into text blocks.
878
+ * Image blocks — both top-level and nested inside tool_result contentBlocks —
879
+ * are preserved as-is so the summarizer LLM can see them.
880
+ */
881
+ function serializeMessagesToContentBlocks(messages: Message[]): ContentBlock[] {
882
+ const blocks: ContentBlock[] = [];
883
+ for (let i = 0; i < messages.length; i++) {
884
+ const msg = messages[i];
885
+ const textLines: string[] = [`Message #${i + 1} (${msg.role})`];
812
886
 
813
- function serializeForSummary(message: Message, index: number): string {
814
- const lines = [`Message #${index + 1} (${message.role})`];
887
+ for (const block of msg.content) {
888
+ if (block.type === "image") {
889
+ // Flush accumulated text lines before the image.
890
+ if (textLines.length > 0) {
891
+ blocks.push({ type: "text", text: textLines.join("\n") });
892
+ textLines.length = 0;
893
+ }
894
+ blocks.push(block);
895
+ } else if (block.type === "web_search_tool_result") {
896
+ textLines.push(
897
+ `web_search_tool_result ${block.tool_use_id}: [opaque]`,
898
+ );
899
+ } else if (block.type === "tool_result") {
900
+ // Extract images from tool_result contentBlocks before serializing.
901
+ const collectedImages: ImageContent[] = [];
902
+ textLines.push(serializeToolResultBlock(block, collectedImages));
903
+ if (collectedImages.length > 0) {
904
+ // Flush text, emit collected images, then continue.
905
+ if (textLines.length > 0) {
906
+ blocks.push({ type: "text", text: textLines.join("\n") });
907
+ textLines.length = 0;
908
+ }
909
+ blocks.push(...collectedImages);
910
+ }
911
+ } else {
912
+ textLines.push(serializeBlock(block));
913
+ }
914
+ }
815
915
 
816
- for (const block of message.content) {
817
- lines.push(serializeBlock(block));
916
+ // Flush remaining text lines for this message.
917
+ if (textLines.length > 0) {
918
+ blocks.push({ type: "text", text: textLines.join("\n") });
919
+ }
818
920
  }
921
+ return blocks;
922
+ }
819
923
 
820
- return lines.join("\n");
924
+ /**
925
+ * Serialize images nested inside tool_result contentBlocks, returning them
926
+ * as separate content blocks to preserve for the summarizer.
927
+ */
928
+ function serializeToolResultBlock(
929
+ block: Extract<ContentBlock, { type: "tool_result" }>,
930
+ collectedImages: ImageContent[],
931
+ ): string {
932
+ if (block.contentBlocks) {
933
+ for (const cb of block.contentBlocks) {
934
+ if (cb.type === "image") {
935
+ collectedImages.push(cb);
936
+ }
937
+ }
938
+ }
939
+ return `tool_result ${block.tool_use_id}${
940
+ block.is_error ? " (error)" : ""
941
+ }: ${clampText(block.content)}`;
821
942
  }
822
943
 
823
944
  function serializeBlock(block: ContentBlock): string {
@@ -831,6 +952,8 @@ function serializeBlock(block: ContentBlock): string {
831
952
  block.is_error ? " (error)" : ""
832
953
  }: ${clampText(block.content)}`;
833
954
  case "image":
955
+ // Top-level images are handled by serializeMessagesToContentBlocks.
956
+ // This path is only hit for images in unexpected positions.
834
957
  return `image: ${block.source.media_type}, ${
835
958
  Math.ceil(block.source.data.length / 4) * 3
836
959
  } bytes(base64)`;
@@ -21,7 +21,7 @@ import { join } from "node:path";
21
21
 
22
22
  import { getIsContainerized } from "../config/env-registry.js";
23
23
  import { getLogger } from "../util/logger.js";
24
- import { getRootDir } from "../util/platform.js";
24
+ import { getBinDir } from "../util/platform.js";
25
25
 
26
26
  const log = getLogger("ces-discovery");
27
27
 
@@ -67,7 +67,7 @@ function getManagedBootstrapSocketPath(): string {
67
67
  * a malicious binary there. Removed to close the sandbox-escape vector.
68
68
  */
69
69
  function getLocalBinarySearchPaths(): string[] {
70
- return [join(getRootDir(), "bin", "credential-executor")];
70
+ return [join(getBinDir(), "credential-executor")];
71
71
  }
72
72
 
73
73
  // ---------------------------------------------------------------------------
@@ -17,7 +17,7 @@ import {
17
17
  import { dirname, join } from "node:path";
18
18
 
19
19
  import { getLogger } from "../util/logger.js";
20
- import { getRootDir } from "../util/platform.js";
20
+ import { getProtectedDir } from "../util/platform.js";
21
21
 
22
22
  const log = getLogger("approved-devices-store");
23
23
 
@@ -33,7 +33,7 @@ interface ApprovedDevicesFile {
33
33
  }
34
34
 
35
35
  function getStorePath(): string {
36
- return join(getRootDir(), "protected", "approved-devices.json");
36
+ return join(getProtectedDir(), "approved-devices.json");
37
37
  }
38
38
 
39
39
  /** Hash a raw deviceId for storage. */
@@ -63,6 +63,8 @@ const EXTENSION_MIME_MAP: Record<string, string> = {
63
63
  svg: "image/svg+xml",
64
64
  ico: "image/x-icon",
65
65
  bmp: "image/bmp",
66
+ heic: "image/heic",
67
+ heif: "image/heif",
66
68
 
67
69
  // Documents
68
70
  pdf: "application/pdf",
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * File watchers and config reload logic extracted from DaemonServer.
3
- * Watches workspace files (config, prompts), protected directory
4
- * (trust rules, secret allowlist), and skills directories for changes.
3
+ * Watches workspace files (config, prompts) and skills directories
4
+ * for changes.
5
5
  */
6
6
  import {
7
7
  existsSync,
@@ -12,27 +12,20 @@ import {
12
12
  } from "node:fs";
13
13
  import { join } from "node:path";
14
14
 
15
- import { clearFeatureFlagOverridesCache } from "../config/assistant-feature-flags.js";
16
15
  import { getConfig, invalidateConfigCache } from "../config/loader.js";
17
16
  import { clearEmbeddingBackendCache } from "../memory/embedding-backend.js";
18
17
  import { clearCache as clearTrustCache } from "../permissions/trust-store.js";
19
18
  import { initializeProviders } from "../providers/registry.js";
20
- import {
21
- resetAllowlist,
22
- validateAllowlistFile,
23
- } from "../security/secret-allowlist.js";
24
19
  import { handleBashSignal } from "../signals/bash.js";
25
20
  import { handleCancelSignal } from "../signals/cancel.js";
26
- import { handleConfirmationSignal } from "../signals/confirm.js";
27
21
  import { handleConversationUndoSignal } from "../signals/conversation-undo.js";
22
+ import { handleEmitEventSignal } from "../signals/emit-event.js";
28
23
  import { handleMcpReloadSignal } from "../signals/mcp-reload.js";
29
24
  import { handleShotgunSignal } from "../signals/shotgun.js";
30
- import { handleTrustRuleSignal } from "../signals/trust-rule.js";
31
25
  import { handleUserMessageSignal } from "../signals/user-message.js";
32
26
  import { DebouncerMap } from "../util/debounce.js";
33
27
  import { getLogger } from "../util/logger.js";
34
28
  import {
35
- getRootDir,
36
29
  getSignalsDir,
37
30
  getWorkspaceDir,
38
31
  getWorkspaceSkillsDir,
@@ -117,7 +110,6 @@ export class ConfigWatcher {
117
110
  */
118
111
  start(onConversationEvict: () => void, onIdentityChanged?: () => void): void {
119
112
  const workspaceDir = getWorkspaceDir();
120
- const protectedDir = join(getRootDir(), "protected");
121
113
 
122
114
  const workspaceHandlers: Record<string, () => void> = {
123
115
  "config.json": async () => {
@@ -150,36 +142,6 @@ export class ConfigWatcher {
150
142
  "UPDATES.md": () => onConversationEvict(),
151
143
  };
152
144
 
153
- const protectedHandlers: Record<string, () => void> = {
154
- "trust.json": () => {
155
- clearTrustCache();
156
- },
157
- "feature-flags.json": () => {
158
- clearFeatureFlagOverridesCache();
159
- onConversationEvict();
160
- },
161
- "feature-flags-remote.json": () => {
162
- clearFeatureFlagOverridesCache();
163
- onConversationEvict();
164
- },
165
- "secret-allowlist.json": () => {
166
- resetAllowlist();
167
- try {
168
- const errors = validateAllowlistFile();
169
- if (errors && errors.length > 0) {
170
- for (const e of errors) {
171
- log.warn(
172
- { index: e.index, pattern: e.pattern },
173
- `Invalid regex in secret-allowlist.json: ${e.message}`,
174
- );
175
- }
176
- }
177
- } catch (err) {
178
- log.warn({ err }, "Failed to validate secret-allowlist.json");
179
- }
180
- },
181
- };
182
-
183
145
  const watchDir = (
184
146
  dir: string,
185
147
  handlers: Record<string, () => void>,
@@ -210,13 +172,6 @@ export class ConfigWatcher {
210
172
  workspaceHandlers,
211
173
  "workspace directory for config/prompt changes",
212
174
  );
213
- if (existsSync(protectedDir)) {
214
- watchDir(
215
- protectedDir,
216
- protectedHandlers,
217
- "protected directory for trust/allowlist changes",
218
- );
219
- }
220
175
 
221
176
  this.startSignalsWatcher();
222
177
  this.startSkillsWatchers(onConversationEvict);
@@ -242,10 +197,9 @@ export class ConfigWatcher {
242
197
 
243
198
  const exactSignalHandlers: Record<string, () => void | Promise<void>> = {
244
199
  cancel: handleCancelSignal,
245
- confirm: handleConfirmationSignal,
246
200
  "mcp-reload": handleMcpReloadSignal,
247
- "trust-rule": handleTrustRuleSignal,
248
201
  "conversation-undo": handleConversationUndoSignal,
202
+ "emit-event": handleEmitEventSignal,
249
203
  };
250
204
 
251
205
  const prefixSignalHandlers: Record<
@@ -60,6 +60,8 @@ export interface EventHandlerState {
60
60
  exchangeCacheCreationInputTokens: number;
61
61
  exchangeCacheReadInputTokens: number;
62
62
  exchangeOutputTokens: number;
63
+ /** Number of actual LLM API calls within this exchange. */
64
+ exchangeLlmCallCount: number;
63
65
  readonly exchangeRawResponses: unknown[];
64
66
  model: string;
65
67
  orderingErrorDetected: boolean;
@@ -100,6 +102,8 @@ export interface EventHandlerState {
100
102
  >;
101
103
  /** tool_use_ids emitted in the current turn (populated in handleToolUse, cleared after annotation). */
102
104
  currentTurnToolUseIds: string[];
105
+ /** Wall-clock time (ms since epoch) when the agent loop turn started, used as the display timestamp for assistant messages. */
106
+ turnStartedAt: number;
103
107
  }
104
108
 
105
109
  /** Immutable context shared across event handlers within a single agent loop run. */
@@ -127,6 +131,7 @@ export function createEventHandlerState(): EventHandlerState {
127
131
  exchangeCacheCreationInputTokens: 0,
128
132
  exchangeCacheReadInputTokens: 0,
129
133
  exchangeOutputTokens: 0,
134
+ exchangeLlmCallCount: 0,
130
135
  exchangeRawResponses: [],
131
136
  model: "",
132
137
  orderingErrorDetected: false,
@@ -151,6 +156,7 @@ export function createEventHandlerState(): EventHandlerState {
151
156
  requestIdToToolUseId: new Map(),
152
157
  toolConfirmationOutcomes: new Map(),
153
158
  currentTurnToolUseIds: [],
159
+ turnStartedAt: Date.now(),
154
160
  };
155
161
  }
156
162
 
@@ -267,7 +273,7 @@ export function handleThinkingDelta(
267
273
  }
268
274
  if (!deps.ctx.streamThinking) return;
269
275
  emitLlmCallStartedIfNeeded(state, deps);
270
- deps.onEvent({ type: "assistant_thinking_delta", thinking: event.thinking });
276
+ deps.onEvent({ type: "assistant_thinking_delta", thinking: event.thinking, conversationId: deps.ctx.conversationId });
271
277
  }
272
278
 
273
279
  export function handleToolUse(
@@ -719,6 +725,7 @@ export async function handleMessageComplete(
719
725
  userMessageInterface: deps.turnInterfaceContext.userMessageInterface,
720
726
  assistantMessageInterface:
721
727
  deps.turnInterfaceContext.assistantMessageInterface,
728
+ sentAt: state.turnStartedAt,
722
729
  };
723
730
  const assistantMsg = await addMessage(
724
731
  deps.ctx.conversationId,
@@ -778,6 +785,7 @@ export function handleUsage(
778
785
  ): void {
779
786
  const providerName = event.actualProvider ?? deps.ctx.provider.name;
780
787
  state.exchangeProviderName = providerName;
788
+ state.exchangeLlmCallCount += 1;
781
789
  state.exchangeInputTokens += event.inputTokens;
782
790
  state.exchangeCacheCreationInputTokens += event.cacheCreationInputTokens ?? 0;
783
791
  state.exchangeCacheReadInputTokens += event.cacheReadInputTokens ?? 0;
@@ -1589,6 +1589,12 @@ export async function runAgentLoopImpl(
1589
1589
  const restoredHistory = [...preRepairMessages, ...newMessages];
1590
1590
  ctx.messages = stripInjectedContext(restoredHistory);
1591
1591
 
1592
+ const postLoopContextEstimate = estimatePromptTokens(
1593
+ ctx.messages,
1594
+ ctx.systemPrompt,
1595
+ { providerName: ctx.provider.name, toolTokenBudget },
1596
+ );
1597
+
1592
1598
  emitUsage(
1593
1599
  ctx,
1594
1600
  state.exchangeInputTokens,
@@ -1601,6 +1607,8 @@ export async function runAgentLoopImpl(
1601
1607
  state.exchangeCacheReadInputTokens,
1602
1608
  collapseRawResponses(state.exchangeRawResponses),
1603
1609
  state.exchangeProviderName,
1610
+ state.exchangeLlmCallCount,
1611
+ { tokens: postLoopContextEstimate, maxTokens: config.contextWindow.maxInputTokens },
1604
1612
  );
1605
1613
 
1606
1614
  void getHookManager().trigger("post-message", {
@@ -1868,6 +1876,8 @@ function emitUsage(
1868
1876
  cacheReadInputTokens = 0,
1869
1877
  rawResponse?: unknown,
1870
1878
  providerName?: string,
1879
+ llmCallCount = 1,
1880
+ contextWindow?: { tokens: number; maxTokens: number },
1871
1881
  ): void {
1872
1882
  recordUsage(
1873
1883
  {
@@ -1884,6 +1894,8 @@ function emitUsage(
1884
1894
  cacheCreationInputTokens,
1885
1895
  cacheReadInputTokens,
1886
1896
  rawResponse,
1897
+ llmCallCount,
1898
+ contextWindow,
1887
1899
  );
1888
1900
  }
1889
1901
 
@@ -458,15 +458,13 @@ function classifyByMessage(
458
458
  .split("\n")
459
459
  .map((l) => l.trim())
460
460
  .find((l) => l.length > 0) ?? "";
461
- const summary =
462
- firstLine.length > 150 ? firstLine.slice(0, 150) + "..." : firstLine;
463
- const userMessage = summary
464
- ? `Processing failed: ${summary}`
461
+ const userMessage = firstLine
462
+ ? `Processing failed: ${firstLine}`
465
463
  : "Something went wrong processing your message. Please try again.";
466
464
  return {
467
465
  code: "CONVERSATION_PROCESSING_FAILED",
468
466
  userMessage,
469
- retryable: false,
467
+ retryable: true,
470
468
  errorCategory: "processing_failed",
471
469
  };
472
470
  }
@@ -10,6 +10,7 @@ import {
10
10
  } from "../memory/conversation-crud.js";
11
11
  import { isLastUserMessageToolResult } from "../memory/conversation-queries.js";
12
12
  import { enqueueMemoryJob } from "../memory/jobs-store.js";
13
+ import { relinkLlmRequestLogs } from "../memory/llm-request-log-store.js";
13
14
  import { withQdrantBreaker } from "../memory/qdrant-circuit-breaker.js";
14
15
  import { getQdrantClient } from "../memory/qdrant-client.js";
15
16
  import type { ContentBlock, Message } from "../providers/types.js";
@@ -295,9 +296,10 @@ export function consolidateAssistantMessages(
295
296
  JSON.stringify(consolidatedContent),
296
297
  );
297
298
 
298
- // Re-link attachments from messages about to be deleted to the consolidated
299
- // message. Without this, ON DELETE CASCADE on message_attachments destroys
300
- // the attachment links, and deleteOrphanAttachments removes the data.
299
+ // Re-link attachments and LLM request logs from messages about to be
300
+ // deleted to the consolidated message. Without this, ON DELETE CASCADE on
301
+ // message_attachments destroys the attachment links, and LLM call logs
302
+ // become orphaned (invisible in the context inspector).
301
303
  const messageIdsToDelete = [
302
304
  ...messagesToConsolidate.slice(1).map((m) => m.id),
303
305
  ...messagesToDelete,
@@ -313,6 +315,8 @@ export function consolidateAssistantMessages(
313
315
  "Re-linked attachments to consolidated message",
314
316
  );
315
317
  }
318
+
319
+ relinkLlmRequestLogs(messageIdsToDelete, firstAssistantMsg.id);
316
320
  }
317
321
 
318
322
  // Delete the other assistant messages and internal tool_result messages,
@@ -9,11 +9,13 @@ import type { EventBus } from "../events/bus.js";
9
9
  import type { AssistantDomainEvents } from "../events/domain-events.js";
10
10
  import type { ToolProfiler } from "../events/tool-profiling-listener.js";
11
11
  import { getHookManager } from "../hooks/manager.js";
12
+ import { getMemoryCheckpoint } from "../memory/checkpoints.js";
12
13
  import {
13
14
  getConversation,
14
15
  getMessages,
15
16
  type MessageRow,
16
17
  } from "../memory/conversation-crud.js";
18
+ import { enqueueMemoryJob } from "../memory/jobs-store.js";
17
19
  import type { PermissionPrompter } from "../permissions/prompter.js";
18
20
  import type { SecretPrompter } from "../permissions/secret-prompter.js";
19
21
  import type { ContentBlock, Message } from "../providers/types.js";
@@ -266,6 +268,20 @@ export function disposeConversation(ctx: DisposeContext): void {
266
268
  void getHookManager().trigger("conversation-end", {
267
269
  conversationId: ctx.conversationId,
268
270
  });
271
+
272
+ // Trigger batch extraction for any remaining unextracted messages
273
+ try {
274
+ const pendingKey = `batch_extract:${ctx.conversationId}:pending_count`;
275
+ const pending = getMemoryCheckpoint(pendingKey);
276
+ if (pending && parseInt(pending, 10) > 0) {
277
+ enqueueMemoryJob("batch_extract", {
278
+ conversationId: ctx.conversationId,
279
+ });
280
+ }
281
+ } catch {
282
+ // Best-effort — don't block conversation disposal
283
+ }
284
+
269
285
  ctx.abort();
270
286
  unregisterCallNotifiers(ctx.conversationId);
271
287
  unregisterConversationSender(ctx.conversationId);
@@ -247,6 +247,7 @@ export function enqueueMessage(
247
247
  turnInterfaceContext,
248
248
  isInteractive: options?.isInteractive,
249
249
  displayContent,
250
+ sentAt: Date.now(),
250
251
  });
251
252
  if (!accepted) {
252
253
  onEvent({