@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
@@ -1,28 +1,5 @@
1
1
  import { z } from "zod";
2
2
 
3
- const VALID_MEMORY_ITEM_KINDS = [
4
- "preference",
5
- "profile",
6
- "project",
7
- "decision",
8
- "todo",
9
- "fact",
10
- "constraint",
11
- "relationship",
12
- "event",
13
- "opinion",
14
- "instruction",
15
- "style",
16
- ] as const;
17
-
18
- const DEFAULT_CONFLICTABLE_KINDS = [
19
- "preference",
20
- "profile",
21
- "constraint",
22
- "instruction",
23
- "style",
24
- ] as const;
25
-
26
3
  export const MemoryExtractionConfigSchema = z.object({
27
4
  useLLM: z
28
5
  .boolean({ error: "memory.extraction.useLLM must be a boolean" })
@@ -50,166 +27,9 @@ export const MemorySummarizationConfigSchema = z.object({
50
27
  .default("latency-optimized"),
51
28
  });
52
29
 
53
- export const MemoryEntityConfigSchema = z.object({
54
- enabled: z
55
- .boolean({ error: "memory.entity.enabled must be a boolean" })
56
- .default(true),
57
- modelIntent: z
58
- .enum(["latency-optimized", "quality-optimized", "vision-optimized"], {
59
- error: "memory.entity.modelIntent must be a valid model intent",
60
- })
61
- .default("latency-optimized"),
62
- extractRelations: z
63
- .object({
64
- enabled: z
65
- .boolean({
66
- error: "memory.entity.extractRelations.enabled must be a boolean",
67
- })
68
- .default(true),
69
- backfillBatchSize: z
70
- .number({
71
- error:
72
- "memory.entity.extractRelations.backfillBatchSize must be a number",
73
- })
74
- .int(
75
- "memory.entity.extractRelations.backfillBatchSize must be an integer",
76
- )
77
- .positive(
78
- "memory.entity.extractRelations.backfillBatchSize must be a positive integer",
79
- )
80
- .default(200),
81
- })
82
- .default({ enabled: true, backfillBatchSize: 200 }),
83
- relationRetrieval: z
84
- .object({
85
- enabled: z
86
- .boolean({
87
- error: "memory.entity.relationRetrieval.enabled must be a boolean",
88
- })
89
- .default(true),
90
- maxSeedEntities: z
91
- .number({
92
- error:
93
- "memory.entity.relationRetrieval.maxSeedEntities must be a number",
94
- })
95
- .int(
96
- "memory.entity.relationRetrieval.maxSeedEntities must be an integer",
97
- )
98
- .positive(
99
- "memory.entity.relationRetrieval.maxSeedEntities must be a positive integer",
100
- )
101
- .default(8),
102
- maxNeighborEntities: z
103
- .number({
104
- error:
105
- "memory.entity.relationRetrieval.maxNeighborEntities must be a number",
106
- })
107
- .int(
108
- "memory.entity.relationRetrieval.maxNeighborEntities must be an integer",
109
- )
110
- .positive(
111
- "memory.entity.relationRetrieval.maxNeighborEntities must be a positive integer",
112
- )
113
- .default(20),
114
- maxEdges: z
115
- .number({
116
- error: "memory.entity.relationRetrieval.maxEdges must be a number",
117
- })
118
- .int("memory.entity.relationRetrieval.maxEdges must be an integer")
119
- .positive(
120
- "memory.entity.relationRetrieval.maxEdges must be a positive integer",
121
- )
122
- .default(40),
123
- neighborScoreMultiplier: z
124
- .number({
125
- error:
126
- "memory.entity.relationRetrieval.neighborScoreMultiplier must be a number",
127
- })
128
- .gt(
129
- 0,
130
- "memory.entity.relationRetrieval.neighborScoreMultiplier must be > 0",
131
- )
132
- .lte(
133
- 1,
134
- "memory.entity.relationRetrieval.neighborScoreMultiplier must be <= 1",
135
- )
136
- .default(0.7),
137
- maxDepth: z
138
- .number({
139
- error: "memory.entity.relationRetrieval.maxDepth must be a number",
140
- })
141
- .int("memory.entity.relationRetrieval.maxDepth must be an integer")
142
- .positive(
143
- "memory.entity.relationRetrieval.maxDepth must be a positive integer",
144
- )
145
- .default(3),
146
- depthDecay: z
147
- .boolean({
148
- error: "memory.entity.relationRetrieval.depthDecay must be a boolean",
149
- })
150
- .default(true),
151
- })
152
- .default({
153
- enabled: true,
154
- maxSeedEntities: 8,
155
- maxNeighborEntities: 20,
156
- maxEdges: 40,
157
- neighborScoreMultiplier: 0.7,
158
- maxDepth: 3,
159
- depthDecay: true,
160
- }),
161
- });
162
-
163
- export const MemoryConflictsConfigSchema = z.object({
164
- enabled: z
165
- .boolean({ error: "memory.conflicts.enabled must be a boolean" })
166
- .default(true),
167
- gateMode: z
168
- .enum(["soft"], { error: 'memory.conflicts.gateMode must be "soft"' })
169
- .default("soft"),
170
- resolverLlmTimeoutMs: z
171
- .number({ error: "memory.conflicts.resolverLlmTimeoutMs must be a number" })
172
- .int("memory.conflicts.resolverLlmTimeoutMs must be an integer")
173
- .positive(
174
- "memory.conflicts.resolverLlmTimeoutMs must be a positive integer",
175
- )
176
- .default(12000),
177
- relevanceThreshold: z
178
- .number({ error: "memory.conflicts.relevanceThreshold must be a number" })
179
- .min(0, "memory.conflicts.relevanceThreshold must be >= 0")
180
- .max(1, "memory.conflicts.relevanceThreshold must be <= 1")
181
- .default(0.3),
182
- conflictableKinds: z
183
- .array(
184
- z.enum(VALID_MEMORY_ITEM_KINDS, {
185
- error: `memory.conflicts.conflictableKinds entries must be one of: ${VALID_MEMORY_ITEM_KINDS.join(
186
- ", ",
187
- )}`,
188
- }),
189
- )
190
- .nonempty({
191
- message: "memory.conflicts.conflictableKinds must not be empty",
192
- })
193
- .default([...DEFAULT_CONFLICTABLE_KINDS]),
194
- });
195
-
196
- export const MemoryProfileConfigSchema = z.object({
197
- enabled: z
198
- .boolean({ error: "memory.profile.enabled must be a boolean" })
199
- .default(true),
200
- maxInjectTokens: z
201
- .number({ error: "memory.profile.maxInjectTokens must be a number" })
202
- .int("memory.profile.maxInjectTokens must be an integer")
203
- .positive("memory.profile.maxInjectTokens must be a positive integer")
204
- .default(800),
205
- });
206
-
207
30
  export type MemoryExtractionConfig = z.infer<
208
31
  typeof MemoryExtractionConfigSchema
209
32
  >;
210
33
  export type MemorySummarizationConfig = z.infer<
211
34
  typeof MemorySummarizationConfigSchema
212
35
  >;
213
- export type MemoryEntityConfig = z.infer<typeof MemoryEntityConfigSchema>;
214
- export type MemoryConflictsConfig = z.infer<typeof MemoryConflictsConfigSchema>;
215
- export type MemoryProfileConfig = z.infer<typeof MemoryProfileConfigSchema>;
@@ -1,22 +1,5 @@
1
1
  import { z } from "zod";
2
2
 
3
- export const MemoryRerankingConfigSchema = z.object({
4
- enabled: z
5
- .boolean({ error: "memory.retrieval.reranking.enabled must be a boolean" })
6
- .default(false),
7
- modelIntent: z
8
- .enum(["latency-optimized", "quality-optimized", "vision-optimized"], {
9
- error:
10
- "memory.retrieval.reranking.modelIntent must be a valid model intent",
11
- })
12
- .default("latency-optimized"),
13
- topK: z
14
- .number({ error: "memory.retrieval.reranking.topK must be a number" })
15
- .int("memory.retrieval.reranking.topK must be an integer")
16
- .positive("memory.retrieval.reranking.topK must be a positive integer")
17
- .default(20),
18
- });
19
-
20
3
  export const MemoryDynamicBudgetConfigSchema = z.object({
21
4
  enabled: z
22
5
  .boolean({
@@ -55,49 +38,6 @@ export const MemoryDynamicBudgetConfigSchema = z.object({
55
38
  .default(10000),
56
39
  });
57
40
 
58
- export const MemoryEarlyTerminationConfigSchema = z.object({
59
- enabled: z
60
- .boolean({
61
- error: "memory.retrieval.earlyTermination.enabled must be a boolean",
62
- })
63
- .default(true),
64
- minCandidates: z
65
- .number({
66
- error: "memory.retrieval.earlyTermination.minCandidates must be a number",
67
- })
68
- .int("memory.retrieval.earlyTermination.minCandidates must be an integer")
69
- .positive(
70
- "memory.retrieval.earlyTermination.minCandidates must be a positive integer",
71
- )
72
- .default(20),
73
- minHighConfidence: z
74
- .number({
75
- error:
76
- "memory.retrieval.earlyTermination.minHighConfidence must be a number",
77
- })
78
- .int(
79
- "memory.retrieval.earlyTermination.minHighConfidence must be an integer",
80
- )
81
- .positive(
82
- "memory.retrieval.earlyTermination.minHighConfidence must be a positive integer",
83
- )
84
- .default(10),
85
- confidenceThreshold: z
86
- .number({
87
- error:
88
- "memory.retrieval.earlyTermination.confidenceThreshold must be a number",
89
- })
90
- .min(
91
- 0,
92
- "memory.retrieval.earlyTermination.confidenceThreshold must be >= 0",
93
- )
94
- .max(
95
- 1,
96
- "memory.retrieval.earlyTermination.confidenceThreshold must be <= 1",
97
- )
98
- .default(0.7),
99
- });
100
-
101
41
  /**
102
42
  * Per-kind freshness windows (in days). Items older than their window
103
43
  * (based on lastSeenAt) are down-ranked unless recently reinforced.
@@ -109,12 +49,13 @@ const MemoryFreshnessConfigSchema = z.object({
109
49
  .default(true),
110
50
  maxAgeDays: z
111
51
  .object({
112
- fact: z
52
+ identity: z
113
53
  .number({
114
- error: "memory.retrieval.freshness.maxAgeDays.fact must be a number",
54
+ error:
55
+ "memory.retrieval.freshness.maxAgeDays.identity must be a number",
115
56
  })
116
57
  .nonnegative(
117
- "memory.retrieval.freshness.maxAgeDays.fact must be non-negative",
58
+ "memory.retrieval.freshness.maxAgeDays.identity must be non-negative",
118
59
  )
119
60
  .default(0),
120
61
  preference: z
@@ -126,34 +67,50 @@ const MemoryFreshnessConfigSchema = z.object({
126
67
  "memory.retrieval.freshness.maxAgeDays.preference must be non-negative",
127
68
  )
128
69
  .default(0),
129
- behavior: z
70
+ project: z
130
71
  .number({
131
72
  error:
132
- "memory.retrieval.freshness.maxAgeDays.behavior must be a number",
73
+ "memory.retrieval.freshness.maxAgeDays.project must be a number",
133
74
  })
134
75
  .nonnegative(
135
- "memory.retrieval.freshness.maxAgeDays.behavior must be non-negative",
76
+ "memory.retrieval.freshness.maxAgeDays.project must be non-negative",
136
77
  )
137
- .default(90),
138
- event: z
78
+ .default(30),
79
+ decision: z
139
80
  .number({
140
- error: "memory.retrieval.freshness.maxAgeDays.event must be a number",
81
+ error:
82
+ "memory.retrieval.freshness.maxAgeDays.decision must be a number",
141
83
  })
142
84
  .nonnegative(
143
- "memory.retrieval.freshness.maxAgeDays.event must be non-negative",
85
+ "memory.retrieval.freshness.maxAgeDays.decision must be non-negative",
144
86
  )
145
87
  .default(30),
146
- opinion: z
88
+ constraint: z
147
89
  .number({
148
90
  error:
149
- "memory.retrieval.freshness.maxAgeDays.opinion must be a number",
91
+ "memory.retrieval.freshness.maxAgeDays.constraint must be a number",
92
+ })
93
+ .nonnegative(
94
+ "memory.retrieval.freshness.maxAgeDays.constraint must be non-negative",
95
+ )
96
+ .default(90),
97
+ event: z
98
+ .number({
99
+ error: "memory.retrieval.freshness.maxAgeDays.event must be a number",
150
100
  })
151
101
  .nonnegative(
152
- "memory.retrieval.freshness.maxAgeDays.opinion must be non-negative",
102
+ "memory.retrieval.freshness.maxAgeDays.event must be non-negative",
153
103
  )
154
- .default(60),
104
+ .default(30),
155
105
  })
156
- .default({ fact: 0, preference: 0, behavior: 90, event: 30, opinion: 60 }),
106
+ .default({
107
+ identity: 0,
108
+ preference: 0,
109
+ project: 30,
110
+ decision: 30,
111
+ constraint: 90,
112
+ event: 30,
113
+ }),
157
114
  staleDecay: z
158
115
  .number({ error: "memory.retrieval.freshness.staleDecay must be a number" })
159
116
  .min(0, "memory.retrieval.freshness.staleDecay must be >= 0")
@@ -171,36 +128,11 @@ const MemoryFreshnessConfigSchema = z.object({
171
128
  });
172
129
 
173
130
  export const MemoryRetrievalConfigSchema = z.object({
174
- lexicalTopK: z
175
- .number({ error: "memory.retrieval.lexicalTopK must be a number" })
176
- .int("memory.retrieval.lexicalTopK must be an integer")
177
- .positive("memory.retrieval.lexicalTopK must be a positive integer")
178
- .default(80),
179
- semanticTopK: z
180
- .number({ error: "memory.retrieval.semanticTopK must be a number" })
181
- .int("memory.retrieval.semanticTopK must be an integer")
182
- .positive("memory.retrieval.semanticTopK must be a positive integer")
183
- .default(40),
184
131
  maxInjectTokens: z
185
132
  .number({ error: "memory.retrieval.maxInjectTokens must be a number" })
186
133
  .int("memory.retrieval.maxInjectTokens must be an integer")
187
134
  .positive("memory.retrieval.maxInjectTokens must be a positive integer")
188
135
  .default(10000),
189
- injectionFormat: z
190
- .enum(["markdown", "structured_v1"], {
191
- error:
192
- 'memory.retrieval.injectionFormat must be "markdown" or "structured_v1"',
193
- })
194
- .default("markdown"),
195
- injectionStrategy: z
196
- .enum(["prepend_user_block", "separate_context_message"], {
197
- error:
198
- 'memory.retrieval.injectionStrategy must be "prepend_user_block" or "separate_context_message"',
199
- })
200
- .default("prepend_user_block"),
201
- reranking: MemoryRerankingConfigSchema.default(
202
- MemoryRerankingConfigSchema.parse({}),
203
- ),
204
136
  freshness: MemoryFreshnessConfigSchema.default(
205
137
  MemoryFreshnessConfigSchema.parse({}),
206
138
  ),
@@ -213,10 +145,6 @@ export const MemoryRetrievalConfigSchema = z.object({
213
145
  dynamicBudget: MemoryDynamicBudgetConfigSchema.default(
214
146
  MemoryDynamicBudgetConfigSchema.parse({}),
215
147
  ),
216
- earlyTermination: MemoryEarlyTerminationConfigSchema.default(
217
- MemoryEarlyTerminationConfigSchema.parse({}),
218
- ),
219
148
  });
220
149
 
221
- export type MemoryRerankingConfig = z.infer<typeof MemoryRerankingConfigSchema>;
222
150
  export type MemoryRetrievalConfig = z.infer<typeof MemoryRetrievalConfigSchema>;
@@ -6,10 +6,7 @@ import {
6
6
  MemoryRetentionConfigSchema,
7
7
  } from "./memory-lifecycle.js";
8
8
  import {
9
- MemoryConflictsConfigSchema,
10
- MemoryEntityConfigSchema,
11
9
  MemoryExtractionConfigSchema,
12
- MemoryProfileConfigSchema,
13
10
  MemorySummarizationConfigSchema,
14
11
  } from "./memory-processing.js";
15
12
  import { MemoryRetrievalConfigSchema } from "./memory-retrieval.js";
@@ -46,13 +43,6 @@ export const MemoryConfigSchema = z.object({
46
43
  summarization: MemorySummarizationConfigSchema.default(
47
44
  MemorySummarizationConfigSchema.parse({}),
48
45
  ),
49
- entity: MemoryEntityConfigSchema.default(MemoryEntityConfigSchema.parse({})),
50
- conflicts: MemoryConflictsConfigSchema.default(
51
- MemoryConflictsConfigSchema.parse({}),
52
- ),
53
- profile: MemoryProfileConfigSchema.default(
54
- MemoryProfileConfigSchema.parse({}),
55
- ),
56
46
  });
57
47
 
58
48
  export type MemoryConfig = z.infer<typeof MemoryConfigSchema>;
@@ -12,13 +12,9 @@ export type {
12
12
  IngressConfig,
13
13
  LogFileConfig,
14
14
  MemoryConfig,
15
- MemoryConflictsConfig,
16
15
  MemoryEmbeddingsConfig,
17
- MemoryEntityConfig,
18
16
  MemoryExtractionConfig,
19
17
  MemoryJobsConfig,
20
- MemoryProfileConfig,
21
- MemoryRerankingConfig,
22
18
  MemoryRetentionConfig,
23
19
  MemoryRetrievalConfig,
24
20
  MemorySegmentationConfig,
@@ -140,6 +140,10 @@ export function upsertContact(params: {
140
140
  contactType?: ContactType;
141
141
  principalId?: string | null;
142
142
  channels?: SyncChannelData[];
143
+ /** When true, conflicting channels on other contacts are reassigned to this
144
+ * contact instead of being skipped. Used by invite redemption to bind a
145
+ * redeemer's existing channel identity to the invite's target contact. */
146
+ reassignConflictingChannels?: boolean;
143
147
  }): ContactWithChannels & { created: boolean } {
144
148
  const db = getDb();
145
149
  const now = Date.now();
@@ -171,7 +175,12 @@ export function upsertContact(params: {
171
175
  .run();
172
176
 
173
177
  if (params.channels) {
174
- syncChannels(contactId, params.channels, now);
178
+ syncChannels(
179
+ contactId,
180
+ params.channels,
181
+ now,
182
+ params.reassignConflictingChannels,
183
+ );
175
184
  }
176
185
 
177
186
  emitContactChange();
@@ -253,6 +262,7 @@ function syncChannels(
253
262
  contactId: string,
254
263
  channels: SyncChannelData[],
255
264
  now: number,
265
+ reassignConflicting?: boolean,
256
266
  ): void {
257
267
  const db = getDb();
258
268
 
@@ -312,7 +322,34 @@ function syncChannels(
312
322
  .get();
313
323
 
314
324
  if (conflicting) {
315
- // Channel belongs to another contact -- skip to avoid unique constraint violation.
325
+ if (reassignConflicting) {
326
+ // Reassign the channel to the target contact. Used by invite redemption
327
+ // to bind a redeemer's existing channel identity to the invite's target.
328
+ const reassignSet: Record<string, unknown> = {
329
+ contactId,
330
+ updatedAt: now,
331
+ };
332
+ if (ch.externalUserId !== undefined)
333
+ reassignSet.externalUserId = ch.externalUserId;
334
+ if (ch.externalChatId !== undefined)
335
+ reassignSet.externalChatId = ch.externalChatId;
336
+ if (ch.status !== undefined) reassignSet.status = ch.status;
337
+ if (ch.policy !== undefined) reassignSet.policy = ch.policy;
338
+ if (ch.verifiedAt !== undefined) reassignSet.verifiedAt = ch.verifiedAt;
339
+ if (ch.verifiedVia !== undefined)
340
+ reassignSet.verifiedVia = ch.verifiedVia;
341
+ if (ch.inviteId !== undefined) reassignSet.inviteId = ch.inviteId;
342
+ if (ch.revokedReason !== undefined)
343
+ reassignSet.revokedReason = ch.revokedReason;
344
+ if (ch.blockedReason !== undefined)
345
+ reassignSet.blockedReason = ch.blockedReason;
346
+
347
+ db.update(contactChannels)
348
+ .set(reassignSet)
349
+ .where(eq(contactChannels.id, conflicting.id))
350
+ .run();
351
+ }
352
+ // When not reassigning, skip to avoid unique constraint violation.
316
353
  // The caller should use contact_merge to combine the two contacts.
317
354
  continue;
318
355
  }
@@ -24,6 +24,7 @@ import {
24
24
  import type {
25
25
  ChannelPolicy,
26
26
  ChannelStatus,
27
+ ContactRole,
27
28
  ContactWriteResult,
28
29
  } from "./types.js";
29
30
 
@@ -146,6 +147,8 @@ export function upsertContactChannel(params: {
146
147
  createdBySessionId?: string;
147
148
  verifiedAt?: number;
148
149
  verifiedVia?: string;
150
+ role?: ContactRole;
151
+ contactId?: string;
149
152
  }): ContactWriteResult | null {
150
153
  let address: string;
151
154
 
@@ -173,7 +176,9 @@ export function upsertContactChannel(params: {
173
176
  : null;
174
177
 
175
178
  upsertContact({
179
+ id: params.contactId,
176
180
  displayName,
181
+ role: params.role,
177
182
  channels: [
178
183
  {
179
184
  type: params.sourceChannel,
@@ -189,6 +194,10 @@ export function upsertContactChannel(params: {
189
194
  verifiedVia: params.verifiedVia ?? undefined,
190
195
  },
191
196
  ],
197
+ // When a specific contactId is provided, reassign conflicting channels from
198
+ // other contacts. This enables invite redemption to bind a redeemer's
199
+ // existing channel identity to the invite's target contact.
200
+ reassignConflictingChannels: !!params.contactId,
192
201
  });
193
202
 
194
203
  const contactResult = findContactChannel({
@@ -671,7 +671,10 @@ function countPersistedMessages(messages: Message[]): number {
671
671
  function isToolResultOnly(message: Message): boolean {
672
672
  return (
673
673
  message.content.length > 0 &&
674
- message.content.every((block) => block.type === "tool_result")
674
+ message.content.every(
675
+ (block) =>
676
+ block.type === "tool_result" || block.type === "web_search_tool_result",
677
+ )
675
678
  );
676
679
  }
677
680
 
@@ -3,7 +3,13 @@
3
3
  * Watches workspace files (config, prompts), protected directory
4
4
  * (trust rules, secret allowlist), and skills directories for changes.
5
5
  */
6
- import { existsSync, type FSWatcher, readdirSync, watch } from "node:fs";
6
+ import {
7
+ existsSync,
8
+ type FSWatcher,
9
+ mkdirSync,
10
+ readdirSync,
11
+ watch,
12
+ } from "node:fs";
7
13
  import { join } from "node:path";
8
14
 
9
15
  import { getConfig, invalidateConfigCache } from "../config/loader.js";
@@ -14,6 +20,8 @@ import {
14
20
  resetAllowlist,
15
21
  validateAllowlistFile,
16
22
  } from "../security/secret-allowlist.js";
23
+ import { handleConfirmationSignal } from "../signals/confirm.js";
24
+ import { handleMcpReloadSignal } from "../signals/mcp-reload.js";
17
25
  import { DebouncerMap } from "../util/debounce.js";
18
26
  import { getLogger } from "../util/logger.js";
19
27
  import {
@@ -107,8 +115,17 @@ export class ConfigWatcher {
107
115
  "config.json": () => {
108
116
  if (this.suppressReload) return;
109
117
  try {
118
+ const prevConfig = getConfig();
119
+ const prevMcpFingerprint = JSON.stringify(prevConfig.mcp ?? {});
110
120
  const changed = this.refreshConfigFromSources();
111
- if (changed) onSessionEvict();
121
+ if (changed) {
122
+ onSessionEvict();
123
+ const newConfig = getConfig();
124
+ const newMcpFingerprint = JSON.stringify(newConfig.mcp ?? {});
125
+ if (newMcpFingerprint !== prevMcpFingerprint) {
126
+ handleMcpReloadSignal();
127
+ }
128
+ }
112
129
  } catch (err) {
113
130
  log.error(
114
131
  { err, configPath: join(workspaceDir, "config.json") },
@@ -185,6 +202,7 @@ export class ConfigWatcher {
185
202
  );
186
203
  }
187
204
 
205
+ this.startSignalsWatcher();
188
206
  this.startSkillsWatchers(onSessionEvict);
189
207
  }
190
208
 
@@ -196,6 +214,41 @@ export class ConfigWatcher {
196
214
  this.watchers = [];
197
215
  }
198
216
 
217
+ private startSignalsWatcher(): void {
218
+ const signalsDir = join(getWorkspaceDir(), "signals");
219
+ try {
220
+ if (!existsSync(signalsDir)) {
221
+ mkdirSync(signalsDir, { recursive: true });
222
+ }
223
+ } catch {
224
+ // If we can't create it, watching will also fail — handled below.
225
+ }
226
+
227
+ const signalHandlers: Record<string, () => void> = {
228
+ "mcp-reload": handleMcpReloadSignal,
229
+ confirm: handleConfirmationSignal,
230
+ };
231
+
232
+ try {
233
+ const watcher = watch(signalsDir, (_eventType, filename) => {
234
+ if (!filename) return;
235
+ const file = String(filename);
236
+ if (!signalHandlers[file]) return;
237
+ this.debounceTimers.schedule(`signal:${file}`, () => {
238
+ log.info({ file }, "Signal file detected");
239
+ signalHandlers[file]();
240
+ });
241
+ });
242
+ this.watchers.push(watcher);
243
+ log.info({ dir: signalsDir }, "Watching signals directory");
244
+ } catch (err) {
245
+ log.warn(
246
+ { err, dir: signalsDir },
247
+ "Failed to watch signals directory. Signal-based reload will be unavailable.",
248
+ );
249
+ }
250
+ }
251
+
199
252
  private startSkillsWatchers(onSessionEvict: () => void): void {
200
253
  const skillsDir = getWorkspaceSkillsDir();
201
254
  if (!existsSync(skillsDir)) return;
@@ -143,7 +143,7 @@ function healthCheckHost(host: string): string {
143
143
  /** Hit the daemon's HTTP /healthz endpoint. Returns true if it responds
144
144
  * with HTTP 200 within the timeout — false on connection refused, timeout,
145
145
  * or any other error. */
146
- async function isHttpHealthy(): Promise<boolean> {
146
+ export async function isHttpHealthy(): Promise<boolean> {
147
147
  const host = healthCheckHost(getRuntimeHttpHost());
148
148
  const port = getRuntimeHttpPort();
149
149
  try {