@vellumai/assistant 0.11.2 → 0.11.3-staging.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (720) hide show
  1. package/ARCHITECTURE.md +58 -3
  2. package/docs/trusted-contact-access.md +9 -5
  3. package/docs/vellum-doctor.md +195 -0
  4. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  5. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  6. package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  7. package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
  8. package/node_modules/@vellumai/ces-client/src/rpc-client.ts +19 -2
  9. package/node_modules/@vellumai/environments/package.json +2 -1
  10. package/node_modules/@vellumai/environments/src/__tests__/cloud-assistant-hub-url.test.ts +38 -0
  11. package/node_modules/@vellumai/environments/src/__tests__/install-layout.test.ts +98 -0
  12. package/node_modules/@vellumai/environments/src/__tests__/package-boundary.test.ts +5 -5
  13. package/node_modules/@vellumai/environments/src/index.ts +17 -6
  14. package/node_modules/@vellumai/environments/src/install-layout.ts +49 -0
  15. package/node_modules/@vellumai/environments/src/seeds.ts +29 -0
  16. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  17. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  18. package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  19. package/node_modules/@vellumai/gateway-client/src/index.ts +4 -0
  20. package/node_modules/@vellumai/gateway-client/src/verification-session-contract.ts +91 -6
  21. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.test.ts +36 -0
  22. package/node_modules/@vellumai/ipc-server-utils/src/endpoint.ts +142 -0
  23. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +2 -0
  24. package/node_modules/@vellumai/ipc-server-utils/src/listen-options.ts +3 -0
  25. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +15 -1
  26. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +17 -2
  27. package/node_modules/@vellumai/service-contracts/src/channels.ts +39 -0
  28. package/node_modules/@vellumai/service-contracts/src/ingress.ts +10 -0
  29. package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +6 -0
  30. package/node_modules/@vellumai/slack-text/src/index.ts +16 -2
  31. package/openapi.yaml +589 -30
  32. package/package.json +1 -1
  33. package/src/__tests__/agent-image-optimize.test.ts +4 -4
  34. package/src/__tests__/agent-loop-mutable-latest-user-message.test.ts +113 -66
  35. package/src/__tests__/agent-loop-override-profile.test.ts +38 -48
  36. package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +166 -0
  37. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
  38. package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
  39. package/src/__tests__/answered-question-persistence.test.ts +240 -0
  40. package/src/__tests__/anthropic-provider.test.ts +165 -30
  41. package/src/__tests__/app-control-flow.test.ts +9 -8
  42. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  43. package/src/__tests__/attachments-store-heic-normalize.test.ts +16 -12
  44. package/src/__tests__/attachments-store.test.ts +130 -68
  45. package/src/__tests__/attachments.test.ts +36 -36
  46. package/src/__tests__/background-workers-disk-pressure.test.ts +1 -1
  47. package/src/__tests__/call-start-guardian-guard.test.ts +18 -4
  48. package/src/__tests__/canned-reply-release.test.ts +86 -0
  49. package/src/__tests__/channel-guardian.test.ts +59 -29
  50. package/src/__tests__/channel-readiness-telegram-remote.test.ts +154 -0
  51. package/src/__tests__/channel-setup-panel-ack.test.ts +6 -5
  52. package/src/__tests__/client-os-metadata-persistence.test.ts +103 -4
  53. package/src/__tests__/compactor-image-manifest-trust.test.ts +1 -1
  54. package/src/__tests__/consult-deadline.test.ts +65 -0
  55. package/src/__tests__/conversation-agent-loop.test.ts +438 -2
  56. package/src/__tests__/conversation-disk-view-integration.test.ts +5 -1
  57. package/src/__tests__/conversation-disk-view.test.ts +4 -4
  58. package/src/__tests__/conversation-error.test.ts +22 -3
  59. package/src/__tests__/conversation-fork-crud.test.ts +5 -1
  60. package/src/__tests__/conversation-fork-referential.test.ts +352 -0
  61. package/src/__tests__/conversation-fork-retrospective.test.ts +5 -1
  62. package/src/__tests__/conversation-group-tools.test.ts +32 -2
  63. package/src/__tests__/conversation-inference-profile-route.test.ts +27 -1
  64. package/src/__tests__/conversation-initial-prompt.test.ts +51 -22
  65. package/src/__tests__/conversation-key-store-bootstrap-cleanup.test.ts +119 -0
  66. package/src/__tests__/conversation-key-store-origin.test.ts +105 -0
  67. package/src/__tests__/conversation-list-recency-ordering.test.ts +104 -0
  68. package/src/__tests__/conversation-origin-at-creation.test.ts +153 -0
  69. package/src/__tests__/conversation-origin-channel-filter.test.ts +95 -0
  70. package/src/__tests__/conversation-placement-promotion.test.ts +172 -0
  71. package/src/__tests__/conversation-process-app-control-preactivation.test.ts +123 -0
  72. package/src/__tests__/conversation-queue.test.ts +433 -1
  73. package/src/__tests__/conversation-retry-route.test.ts +3 -1
  74. package/src/__tests__/conversation-routes-hidden-queue.test.ts +64 -1
  75. package/src/__tests__/conversation-store-ephemeral.test.ts +4 -0
  76. package/src/__tests__/conversation-store.test.ts +26 -10
  77. package/src/__tests__/conversation-summarize-route.test.ts +1 -1
  78. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +15 -6
  79. package/src/__tests__/conversation-surfaces-activation-emit.test.ts +6 -5
  80. package/src/__tests__/conversation-surfaces-app-control.test.ts +12 -11
  81. package/src/__tests__/conversation-surfaces-app-open.test.ts +11 -5
  82. package/src/__tests__/conversation-surfaces-data-persist.test.ts +5 -4
  83. package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +967 -0
  84. package/src/__tests__/conversation-surfaces-persisted-info.test.ts +312 -0
  85. package/src/__tests__/conversation-surfaces-queued-emit.test.ts +6 -5
  86. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -8
  87. package/src/__tests__/conversation-surfaces-standalone.test.ts +21 -11
  88. package/src/__tests__/conversation-surfaces-state-update.test.ts +7 -11
  89. package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -6
  90. package/src/__tests__/conversation-surfaces-task-progress.test.ts +11 -6
  91. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +9 -6
  92. package/src/__tests__/conversation-tool-setup-attribution.test.ts +70 -7
  93. package/src/__tests__/conversation-wait-for-idle.test.ts +6 -11
  94. package/src/__tests__/credential-prompt-route.test.ts +57 -7
  95. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  96. package/src/__tests__/cu-unified-flow.test.ts +89 -28
  97. package/src/__tests__/custom-group-listing-visibility.test.ts +1 -1
  98. package/src/__tests__/db-schedule-syntax-migration.test.ts +4 -1
  99. package/src/__tests__/discord-access-request-privacy.test.ts +366 -0
  100. package/src/__tests__/discord-requester-notice-privacy.test.ts +87 -0
  101. package/src/__tests__/disk-pressure-guard.test.ts +55 -55
  102. package/src/__tests__/disk-pressure-routes.test.ts +10 -10
  103. package/src/__tests__/disk-pressure-tools.test.ts +2 -2
  104. package/src/__tests__/disk-usage.test.ts +35 -20
  105. package/src/__tests__/document-sync-tags.test.ts +379 -0
  106. package/src/__tests__/document-update-default-surface.test.ts +28 -0
  107. package/src/__tests__/dynamic-skill-background-guard.test.ts +2 -2
  108. package/src/__tests__/emit-signal-routing-intent.test.ts +3 -4
  109. package/src/__tests__/ensure-conversation-exists.test.ts +8 -5
  110. package/src/__tests__/events-dev-bypass-actor.test.ts +183 -1
  111. package/src/__tests__/evict-conversations-for-reload.test.ts +90 -0
  112. package/src/__tests__/file-list-tool.test.ts +12 -10
  113. package/src/__tests__/file-ops-service.test.ts +151 -44
  114. package/src/__tests__/file-read-container-boundary.test.ts +137 -0
  115. package/src/__tests__/file-read-tool.test.ts +21 -9
  116. package/src/__tests__/file-url-path-guard.test.ts +83 -0
  117. package/src/__tests__/filesystem-tools.test.ts +82 -66
  118. package/src/__tests__/guardian-outbound-http.test.ts +8 -8
  119. package/src/__tests__/helpers/gateway-verification-sessions-stub.ts +14 -1
  120. package/src/__tests__/helpers/hub-clients.ts +42 -0
  121. package/src/__tests__/helpers/mock-conversation.ts +46 -0
  122. package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +61 -57
  123. package/src/__tests__/history-repair.test.ts +112 -12
  124. package/src/__tests__/host-bash-proxy.test.ts +10 -5
  125. package/src/__tests__/host-bash-routes.test.ts +67 -0
  126. package/src/__tests__/host-browser-proxy.test.ts +2 -2
  127. package/src/__tests__/host-cu-proxy.test.ts +3 -7
  128. package/src/__tests__/host-file-proxy.test.ts +3 -7
  129. package/src/__tests__/host-file-read-tool.test.ts +5 -1
  130. package/src/__tests__/host-transfer-proxy-targeted.test.ts +6 -4
  131. package/src/__tests__/http-conversation-lineage.test.ts +2 -5
  132. package/src/__tests__/http-user-message-parity.test.ts +44 -0
  133. package/src/__tests__/identity-routes.test.ts +13 -13
  134. package/src/__tests__/image-conversion.test.ts +29 -26
  135. package/src/__tests__/image-source-path-reinject.test.ts +2 -2
  136. package/src/__tests__/inference-profile-session-handler.test.ts +95 -0
  137. package/src/__tests__/inference-profile-session-ipc.test.ts +25 -0
  138. package/src/__tests__/introduction-card-resolver.test.ts +6 -1
  139. package/src/__tests__/list-messages-attachments.test.ts +56 -32
  140. package/src/__tests__/list-messages-background-tool-completion.test.ts +4 -4
  141. package/src/__tests__/list-messages-client-message-id.test.ts +2 -2
  142. package/src/__tests__/list-messages-hidden-metadata.test.ts +16 -16
  143. package/src/__tests__/list-messages-page-latest.test.ts +61 -61
  144. package/src/__tests__/list-messages-provider-error.test.ts +6 -6
  145. package/src/__tests__/list-messages-queued.test.ts +49 -6
  146. package/src/__tests__/list-messages-system-card.test.ts +4 -4
  147. package/src/__tests__/list-messages-tool-merge.test.ts +9 -9
  148. package/src/__tests__/llm-resolver-override-or-default.test.ts +141 -0
  149. package/src/__tests__/llm-usage-store.test.ts +55 -0
  150. package/src/__tests__/manual-token-reconciliation.test.ts +45 -0
  151. package/src/__tests__/mcp-tool-annotations-risk.test.ts +133 -0
  152. package/src/__tests__/media-stream-server-integration.test.ts +36 -0
  153. package/src/__tests__/migration-export-http.test.ts +1 -1
  154. package/src/__tests__/migration-import-commit-http.test.ts +1 -1
  155. package/src/__tests__/migration-import-preflight-http.test.ts +1 -1
  156. package/src/__tests__/migration-validate-http.test.ts +1 -1
  157. package/src/__tests__/mtime-cache.test.ts +65 -3
  158. package/src/__tests__/notification-platform-adapter.test.ts +104 -0
  159. package/src/__tests__/notification-schedule-notify-dedup.test.ts +1 -1
  160. package/src/__tests__/oauth-provider-profiles.test.ts +4 -2
  161. package/src/__tests__/oauth-provider-seed-logos.test.ts +65 -0
  162. package/src/__tests__/oauth-provider-serializer.test.ts +17 -0
  163. package/src/__tests__/oauth-providers-routes.test.ts +1 -0
  164. package/src/__tests__/openai-responses-prompt-cache.test.ts +5 -2
  165. package/src/__tests__/path-policy.test.ts +164 -1
  166. package/src/__tests__/persist-media-references.test.ts +2 -2
  167. package/src/__tests__/persist-unsendable-image-downscale.test.ts +5 -5
  168. package/src/__tests__/persist-unsendable-image.test.ts +23 -23
  169. package/src/__tests__/persona-resolver.test.ts +154 -0
  170. package/src/__tests__/plugin-api-webhook-url.test.ts +170 -0
  171. package/src/__tests__/plugin-import-boundary-guard.test.ts +6 -0
  172. package/src/__tests__/processing-flag-persist-failure.test.ts +15 -10
  173. package/src/__tests__/profile-availability-incomplete.test.ts +113 -0
  174. package/src/__tests__/profiler-routes.test.ts +13 -13
  175. package/src/__tests__/prompt-cache-cross-turn-stability.test.ts +593 -0
  176. package/src/__tests__/proxy-approval-callback.test.ts +3 -3
  177. package/src/__tests__/queued-message-cancel-and-steer.test.ts +189 -0
  178. package/src/__tests__/queued-message-steer-actor-scoping.test.ts +164 -0
  179. package/src/__tests__/resolve-trust-class.test.ts +20 -0
  180. package/src/__tests__/run-conversation-turn-persistence.test.ts +1 -1
  181. package/src/__tests__/run-due-schedules.test.ts +174 -0
  182. package/src/__tests__/runtime-attachment-metadata.test.ts +15 -7
  183. package/src/__tests__/scaffold-managed-skill-tool.test.ts +166 -0
  184. package/src/__tests__/schedule-routes.test.ts +730 -8
  185. package/src/__tests__/schedule-store.test.ts +540 -0
  186. package/src/__tests__/schedule-tools.test.ts +103 -0
  187. package/src/__tests__/scheduler-wake.test.ts +5 -0
  188. package/src/__tests__/secret-routes-platform-proxy.test.ts +29 -1
  189. package/src/__tests__/secure-keys.test.ts +91 -0
  190. package/src/__tests__/server-history-render.test.ts +97 -7
  191. package/src/__tests__/size-guard.test.ts +8 -8
  192. package/src/__tests__/skill-docs-sync-guard.test.ts +3 -2
  193. package/src/__tests__/skill-tool-factory.test.ts +247 -0
  194. package/src/__tests__/skills-install-staging.test.ts +15 -6
  195. package/src/__tests__/slack-edit-ordering-characterization.test.ts +103 -0
  196. package/src/__tests__/slack-inbound-verification.test.ts +4 -1
  197. package/src/__tests__/slack-mention-binding-invariance.test.ts +103 -0
  198. package/src/__tests__/slack-mention-provider-leak-audit.test.ts +135 -0
  199. package/src/__tests__/starter-task-flow.test.ts +5 -4
  200. package/src/__tests__/steer-on-enqueue-question.test.ts +3 -2
  201. package/src/__tests__/stt-language-catalog-parity.test.ts +38 -0
  202. package/src/__tests__/subagent-allowlist-validation.test.ts +26 -4
  203. package/src/__tests__/subagent-cron-run-attribution.test.ts +301 -0
  204. package/src/__tests__/subagent-disposal.test.ts +10 -0
  205. package/src/__tests__/subagent-fork-notifications.test.ts +10 -0
  206. package/src/__tests__/subagent-fork-prompt-role.test.ts +85 -11
  207. package/src/__tests__/subagent-fork-spawn.test.ts +15 -7
  208. package/src/__tests__/subagent-manager-notify.test.ts +148 -0
  209. package/src/__tests__/subagent-notify-parent.test.ts +1 -1
  210. package/src/__tests__/subagent-persistence.test.ts +141 -0
  211. package/src/__tests__/subagent-role-registry.test.ts +172 -63
  212. package/src/__tests__/subagent-role-resolution.test.ts +88 -0
  213. package/src/__tests__/subagent-spawn-and-await.test.ts +48 -0
  214. package/src/__tests__/subagent-spawn-tool-fork.test.ts +111 -10
  215. package/src/__tests__/subagent-terminal-message.test.ts +88 -1
  216. package/src/__tests__/subagent-tool-gate-mode.test.ts +225 -10
  217. package/src/__tests__/subagent-tools.test.ts +1901 -108
  218. package/src/__tests__/surface-completion-compaction-boundary.test.ts +124 -0
  219. package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +298 -0
  220. package/src/__tests__/system-prompt.test.ts +82 -7
  221. package/src/__tests__/tool-grant-request-escalation.test.ts +40 -1
  222. package/src/__tests__/tool-result-spool.test.ts +74 -18
  223. package/src/__tests__/tool-side-effects-documents.test.ts +174 -0
  224. package/src/__tests__/tool-side-effects-hook-failures.test.ts +129 -0
  225. package/src/__tests__/tools-audio-read.test.ts +10 -10
  226. package/src/__tests__/tools-get-route.test.ts +85 -12
  227. package/src/__tests__/tools-list-cli.test.ts +171 -0
  228. package/src/__tests__/top-level-renderer.test.ts +54 -1
  229. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +68 -1
  230. package/src/__tests__/trusted-contact-multichannel.test.ts +4 -1
  231. package/src/__tests__/turn-tail-chain.test.ts +135 -0
  232. package/src/__tests__/ui-choice-copy-surfaces.test.ts +6 -4
  233. package/src/__tests__/ui-visual-surface.test.ts +5 -4
  234. package/src/__tests__/ui-work-result-surface.test.ts +5 -4
  235. package/src/__tests__/unified-turn-context-visible-app.test.ts +7 -8
  236. package/src/__tests__/usage-routes.test.ts +62 -0
  237. package/src/__tests__/verification-outbound-delivery.test.ts +176 -0
  238. package/src/__tests__/visible-app-context.test.ts +20 -4
  239. package/src/__tests__/web-search-history.test.ts +96 -0
  240. package/src/__tests__/workspace-migration-139-clear-renamed-cost-profile-label.test.ts +6 -6
  241. package/src/__tests__/workspace-migration-140-repair-seed-pinned-memory-v3-live.test.ts +330 -0
  242. package/src/acp/failure-error.ts +3 -5
  243. package/src/agent/attachments.ts +26 -24
  244. package/src/agent/history-repair/history-repair.ts +27 -26
  245. package/src/agent/image-optimize.ts +6 -6
  246. package/src/agent/loop.ts +40 -22
  247. package/src/agent/message-types.ts +3 -3
  248. package/src/api/constants/document-tools.ts +55 -0
  249. package/src/api/events/question-answered.ts +53 -0
  250. package/src/api/events/tool-result.ts +7 -0
  251. package/src/api/index.ts +13 -0
  252. package/src/api/responses/conversation-message.ts +11 -0
  253. package/src/api/surfaces-oauth-connect.test.ts +96 -0
  254. package/src/api/surfaces.ts +36 -8
  255. package/src/approvals/guardian-channel-delivery.ts +83 -0
  256. package/src/approvals/guardian-expiry-notifier.ts +13 -11
  257. package/src/approvals/guardian-request-resolvers.ts +222 -135
  258. package/src/calls/__tests__/voice-session-bridge.test.ts +50 -0
  259. package/src/calls/media-stream-server.ts +9 -1
  260. package/src/calls/voice-session-bridge.ts +38 -0
  261. package/src/channels/__tests__/gateway-verification-sessions.test.ts +3 -3
  262. package/src/channels/config.ts +7 -2
  263. package/src/channels/gateway-verification-sessions.ts +7 -2
  264. package/src/channels/types.ts +7 -1
  265. package/src/cli/commands/__tests__/cache.test.ts +7 -4
  266. package/src/cli/commands/__tests__/cli-test-harness.ts +82 -0
  267. package/src/cli/commands/__tests__/conversations-wake.test.ts +51 -6
  268. package/src/cli/commands/__tests__/inference-models.test.ts +14 -0
  269. package/src/cli/commands/__tests__/inference-profiles.test.ts +44 -1
  270. package/src/cli/commands/__tests__/inference-providers.test.ts +7 -0
  271. package/src/cli/commands/__tests__/plugins.test.ts +692 -0
  272. package/src/cli/commands/__tests__/schedules.test.ts +136 -37
  273. package/src/cli/commands/conversations.ts +7 -3
  274. package/src/cli/commands/credentials.help.ts +7 -0
  275. package/src/cli/commands/inference-profiles.ts +9 -3
  276. package/src/cli/commands/inference.help.ts +16 -2
  277. package/src/cli/commands/mcp.help.ts +4 -2
  278. package/src/cli/commands/memory/memory-retrospective.ts +18 -0
  279. package/src/cli/commands/notifications.ts +3 -6
  280. package/src/cli/commands/oauth/connect-surface-guidance.test.ts +16 -0
  281. package/src/cli/commands/oauth/connect-surface-guidance.ts +25 -7
  282. package/src/cli/commands/oauth/connect.test.ts +26 -1
  283. package/src/cli/commands/oauth/connect.ts +4 -1
  284. package/src/cli/commands/oauth/index.help.ts +3 -1
  285. package/src/cli/commands/oauth/providers.ts +3 -0
  286. package/src/cli/commands/plugins.help.ts +25 -2
  287. package/src/cli/commands/plugins.ts +289 -10
  288. package/src/cli/commands/schedules.help.ts +14 -12
  289. package/src/cli/commands/schedules.ts +19 -5
  290. package/src/cli/commands/tools.help.ts +4 -2
  291. package/src/cli/commands/tools.ts +38 -0
  292. package/src/cli/lib/__tests__/inspect-plugin.test.ts +1 -0
  293. package/src/cli/lib/__tests__/install-from-github.test.ts +79 -0
  294. package/src/cli/lib/__tests__/install-from-platform.test.ts +47 -1
  295. package/src/cli/lib/__tests__/plugin-surfaces.test.ts +86 -7
  296. package/src/cli/lib/__tests__/upgrade-plugin.test.ts +122 -0
  297. package/src/cli/lib/install-from-github.ts +61 -0
  298. package/src/cli/lib/install-from-platform.ts +13 -2
  299. package/src/cli/lib/plugin-surfaces.ts +124 -4
  300. package/src/cli/lib/upgrade-plugin.ts +18 -0
  301. package/src/cli.ts +1 -1
  302. package/src/config/__tests__/deployment-context-defaults.test.ts +73 -0
  303. package/src/config/__tests__/memory-retrospective-schema.test.ts +40 -0
  304. package/src/config/__tests__/plugin-updates-schema.test.ts +43 -0
  305. package/src/config/__tests__/profile-tool-support.test.ts +131 -0
  306. package/src/config/bundled-skills/AGENTS.md +1 -1
  307. package/src/config/bundled-skills/app-builder/SKILL.md +1 -1
  308. package/src/config/bundled-skills/app-builder/references/RESPONSIVE.md +3 -3
  309. package/src/config/bundled-skills/schedule/SKILL.md +5 -1
  310. package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
  311. package/src/config/bundled-skills/settings/TOOLS.json +2 -2
  312. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +1 -0
  313. package/src/config/bundled-skills/subagent/SKILL.md +53 -22
  314. package/src/config/bundled-skills/subagent/TOOLS.json +14 -13
  315. package/src/config/bundled-skills/visualize/SKILL.md +1 -1
  316. package/src/config/default-profile-catalog.ts +6 -2
  317. package/src/config/feature-flag-registry.json +25 -17
  318. package/src/config/llm-resolver.ts +45 -15
  319. package/src/config/loader.ts +70 -1
  320. package/src/config/profile-tool-support.ts +110 -0
  321. package/src/config/sanitize-for-transfer.ts +11 -0
  322. package/src/config/schema.ts +4 -0
  323. package/src/config/schemas/__tests__/stt.test.ts +20 -5
  324. package/src/config/schemas/channels.ts +13 -0
  325. package/src/config/schemas/memory-retrospective.ts +25 -0
  326. package/src/config/schemas/plugin-updates.ts +55 -0
  327. package/src/config/schemas/services.ts +8 -4
  328. package/src/config/schemas/stt.ts +17 -5
  329. package/src/config/schemas/timeouts.ts +1 -1
  330. package/src/contacts/__tests__/guardian-delivery-reader.test.ts +16 -0
  331. package/src/contacts/guardian-delivery-reader.ts +29 -2
  332. package/src/context/compactor.ts +4 -4
  333. package/src/context/post-turn-tool-result-truncation.ts +32 -16
  334. package/src/context/strip-injections.ts +42 -0
  335. package/src/context/tool-result-spool.ts +32 -18
  336. package/src/credential-execution/ces-runtime.ts +3 -0
  337. package/src/credential-execution/client.ts +22 -1
  338. package/src/credential-execution/process-manager.test.ts +99 -0
  339. package/src/credential-execution/process-manager.ts +62 -16
  340. package/src/daemon/__tests__/abort-null-controller.test.ts +141 -5
  341. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +8 -12
  342. package/src/daemon/__tests__/install-assistant-command.test.ts +234 -0
  343. package/src/daemon/__tests__/turn-tail-assistant-reply-notify.test.ts +2 -4
  344. package/src/daemon/__tests__/turn-tail-deleted-conversation.test.ts +20 -29
  345. package/src/daemon/channel-ui-capability.ts +4 -4
  346. package/src/daemon/conversation-agent-loop-handlers.ts +117 -61
  347. package/src/daemon/conversation-agent-loop.ts +318 -148
  348. package/src/daemon/conversation-attachments.ts +1 -1
  349. package/src/daemon/conversation-error.ts +8 -1
  350. package/src/daemon/conversation-initial-prompt.ts +16 -38
  351. package/src/daemon/conversation-lifecycle.ts +154 -32
  352. package/src/daemon/conversation-messaging.ts +106 -43
  353. package/src/daemon/conversation-process.ts +131 -33
  354. package/src/daemon/conversation-queue-manager.ts +10 -0
  355. package/src/daemon/conversation-runtime-assembly.ts +9 -4
  356. package/src/daemon/conversation-store.ts +52 -3
  357. package/src/daemon/conversation-surface-state.ts +9 -0
  358. package/src/daemon/conversation-surfaces.ts +403 -210
  359. package/src/daemon/conversation-tool-setup.ts +56 -7
  360. package/src/daemon/conversation-turn-finalize.ts +134 -89
  361. package/src/daemon/conversation.ts +111 -27
  362. package/src/daemon/disk-pressure-guard-lifecycle.ts +2 -2
  363. package/src/daemon/disk-pressure-guard.ts +3 -3
  364. package/src/daemon/doordash-steps.ts +5 -5
  365. package/src/daemon/external-plugins-bootstrap.ts +7 -1
  366. package/src/daemon/handlers/config-channels.ts +48 -5
  367. package/src/daemon/handlers/conversation-history.ts +1 -1
  368. package/src/daemon/handlers/conversations.ts +62 -17
  369. package/src/daemon/handlers/shared.ts +12 -0
  370. package/src/daemon/handlers/skills.ts +11 -1
  371. package/src/daemon/identity-helpers.ts +38 -10
  372. package/src/daemon/install-assistant-command.ts +322 -0
  373. package/src/daemon/lifecycle.ts +14 -3
  374. package/src/daemon/message-types/conversations.ts +6 -7
  375. package/src/daemon/message-types/sync.ts +1 -0
  376. package/src/daemon/persist-media-references.ts +23 -14
  377. package/src/daemon/process-message.ts +6 -6
  378. package/src/daemon/tool-setup-types.ts +19 -122
  379. package/src/daemon/tool-side-effects.ts +17 -94
  380. package/src/daemon/trust-context-types.ts +39 -0
  381. package/src/daemon/trust-context.ts +14 -6
  382. package/src/daemon/turn-tail-chain.ts +73 -0
  383. package/src/daemon/web-search-history.ts +47 -33
  384. package/src/heartbeat/heartbeat-service.ts +6 -6
  385. package/src/ipc/__tests__/socket-path.test.ts +21 -0
  386. package/src/ipc/assistant-server.ts +17 -10
  387. package/src/ipc/cli-client.ts +10 -1
  388. package/src/ipc/routes/__tests__/documents-sync-ipc-routes.test.ts +56 -0
  389. package/src/ipc/routes/documents-sync-ipc-routes.ts +42 -0
  390. package/src/ipc/socket-cleanup.ts +9 -7
  391. package/src/ipc/socket-path.ts +4 -96
  392. package/src/live-voice/__tests__/live-voice-archive.test.ts +16 -16
  393. package/src/live-voice/__tests__/live-voice-attach-image.test.ts +209 -0
  394. package/src/live-voice/__tests__/live-voice-connection.test.ts +20 -0
  395. package/src/live-voice/__tests__/live-voice-integration.test.ts +7 -5
  396. package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +265 -0
  397. package/src/live-voice/__tests__/live-voice-stt.test.ts +6 -1
  398. package/src/live-voice/__tests__/live-voice-vad.test.ts +35 -15
  399. package/src/live-voice/__tests__/protocol.test.ts +52 -0
  400. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +3 -0
  401. package/src/live-voice/live-voice-archive.ts +13 -13
  402. package/src/live-voice/live-voice-connection.ts +7 -1
  403. package/src/live-voice/live-voice-photo.ts +169 -0
  404. package/src/live-voice/live-voice-session.ts +108 -4
  405. package/src/live-voice/protocol.ts +89 -1
  406. package/src/mcp/client.ts +10 -0
  407. package/src/messaging/providers/__tests__/callback-routing.test.ts +5 -1
  408. package/src/messaging/providers/__tests__/transport-dispatch.test.ts +66 -4
  409. package/src/messaging/providers/callback-routing.ts +1 -0
  410. package/src/messaging/providers/discord/api.ts +312 -0
  411. package/src/messaging/providers/discord/dm-delivery.test.ts +204 -0
  412. package/src/messaging/providers/discord/render.test.ts +204 -0
  413. package/src/messaging/providers/discord/render.ts +195 -0
  414. package/src/messaging/providers/discord/send.test.ts +179 -0
  415. package/src/messaging/providers/discord/send.ts +212 -0
  416. package/src/messaging/providers/discord/transport.ts +81 -0
  417. package/src/messaging/providers/index.ts +3 -1
  418. package/src/messaging/providers/retry-policy.test.ts +247 -0
  419. package/src/messaging/providers/retry-policy.ts +206 -0
  420. package/src/messaging/providers/slack/mention-source.test.ts +285 -0
  421. package/src/messaging/providers/slack/mention-source.ts +287 -0
  422. package/src/messaging/providers/telegram-bot/api.ts +47 -130
  423. package/src/messaging/providers/whatsapp/api.ts +47 -136
  424. package/src/monitoring/__tests__/db-integrity-sample.test.ts +4 -1
  425. package/src/monitoring/__tests__/plugin-auto-update.test.ts +332 -0
  426. package/src/monitoring/db-integrity-sample.ts +6 -1
  427. package/src/monitoring/plugin-auto-update.ts +359 -0
  428. package/src/monitoring/resource-sampler.ts +21 -5
  429. package/src/monitoring/worker.ts +13 -0
  430. package/src/notifications/__tests__/assistant-reply-producer.test.ts +375 -2
  431. package/src/notifications/__tests__/broadcaster.test.ts +37 -1
  432. package/src/notifications/__tests__/copy-composer.test.ts +165 -0
  433. package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +205 -0
  434. package/src/notifications/__tests__/notification-utils.test.ts +249 -0
  435. package/src/notifications/adapters/platform.ts +31 -2
  436. package/src/notifications/assistant-reply-producer.ts +79 -32
  437. package/src/notifications/broadcaster.ts +30 -10
  438. package/src/notifications/copy-composer.ts +69 -0
  439. package/src/notifications/emit-signal.ts +82 -15
  440. package/src/notifications/events-store.ts +10 -2
  441. package/src/notifications/home-feed-side-effect.ts +6 -3
  442. package/src/notifications/notification-utils.ts +173 -5
  443. package/src/notifications/signal.ts +12 -0
  444. package/src/oauth/AGENTS.md +4 -2
  445. package/src/oauth/byo-connection.ts +6 -1
  446. package/src/oauth/credential-token-resolver.ts +3 -10
  447. package/src/oauth/manual-token-connection.ts +29 -64
  448. package/src/oauth/manual-token-providers.ts +62 -0
  449. package/src/oauth/provider-serializer.ts +13 -0
  450. package/src/oauth/seed-providers.ts +62 -11
  451. package/src/onboarding/onboarding-events-store.ts +91 -1
  452. package/src/permissions/question-prompter.test.ts +4 -0
  453. package/src/permissions/question-prompter.ts +21 -4
  454. package/src/persistence/__tests__/conversation-lineage.test.ts +248 -0
  455. package/src/persistence/attachments-store.ts +15 -15
  456. package/src/persistence/conversation-attention-store.ts +30 -10
  457. package/src/persistence/conversation-bootstrap.ts +17 -0
  458. package/src/persistence/conversation-crud.ts +363 -97
  459. package/src/persistence/conversation-group-migration.ts +1 -1
  460. package/src/persistence/conversation-key-store.ts +38 -10
  461. package/src/persistence/conversation-lineage.ts +226 -0
  462. package/src/persistence/conversation-plugin-facade.ts +28 -3
  463. package/src/persistence/conversation-queries.ts +207 -33
  464. package/src/persistence/conversation-types.ts +107 -1
  465. package/src/persistence/jobs-store.ts +32 -8
  466. package/src/persistence/llm-usage-store.ts +22 -0
  467. package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +168 -0
  468. package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +76 -0
  469. package/src/persistence/migrations/362-add-conversation-subagent-kind.test.ts +187 -0
  470. package/src/persistence/migrations/362-add-conversation-subagent-kind.ts +93 -0
  471. package/src/persistence/migrations/363-backfill-schedule-inference-profile.test.ts +229 -0
  472. package/src/persistence/migrations/363-backfill-schedule-inference-profile.ts +127 -0
  473. package/src/persistence/migrations/364-add-schedule-source-key.test.ts +120 -0
  474. package/src/persistence/migrations/364-add-schedule-source-key.ts +43 -0
  475. package/src/persistence/migrations/365-add-conversation-fork-strategy.ts +27 -0
  476. package/src/persistence/schema/conversations.ts +32 -0
  477. package/src/persistence/schema/infrastructure.ts +3 -0
  478. package/src/persistence/steps.ts +30 -0
  479. package/src/persistence/subagent-store.ts +148 -6
  480. package/src/platform/managed-speech.test.ts +20 -0
  481. package/src/platform/managed-speech.ts +9 -0
  482. package/src/plugin-api/index.ts +8 -0
  483. package/src/plugin-api/webhook-url.ts +151 -0
  484. package/src/plugins/__tests__/installed-plugin-dirs.test.ts +82 -0
  485. package/src/plugins/__tests__/source-fingerprint.test.ts +5 -1
  486. package/src/plugins/collect-source-versions.ts +4 -27
  487. package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +5 -5
  488. package/src/plugins/defaults/exploration-drift/package.json +1 -1
  489. package/src/plugins/defaults/image-recovery/hooks/post-model-call.ts +4 -2
  490. package/src/plugins/defaults/image-recovery/recover.ts +67 -57
  491. package/src/plugins/defaults/index.ts +1 -1
  492. package/src/plugins/defaults/memory/__tests__/conversation-queries.test.ts +64 -18
  493. package/src/plugins/defaults/memory/__tests__/jobs-worker-outcome-contract.test.ts +242 -0
  494. package/src/plugins/defaults/memory/__tests__/jobs-worker-retrospective-sweep.test.ts +24 -1
  495. package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +48 -3
  496. package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +511 -19
  497. package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +39 -3
  498. package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +493 -0
  499. package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +5 -4
  500. package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +5 -1
  501. package/src/plugins/defaults/memory/job-handlers.ts +87 -5
  502. package/src/plugins/defaults/memory/jobs-worker.ts +60 -6
  503. package/src/plugins/defaults/memory/memory-retrospective-constants.ts +11 -0
  504. package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +29 -0
  505. package/src/plugins/defaults/memory/memory-retrospective-job.ts +342 -60
  506. package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +23 -3
  507. package/src/plugins/defaults/memory/substrate/consolidation-lock.ts +9 -4
  508. package/src/plugins/defaults/memory/substrate/sweep-job.ts +7 -0
  509. package/src/plugins/defaults/memory/v1/graph/injection.ts +1 -1
  510. package/src/plugins/defaults/memory/v2/router.ts +8 -0
  511. package/src/plugins/defaults/turn-context/unified-turn-context.ts +1 -2
  512. package/src/plugins/external-plugin-loader.ts +17 -8
  513. package/src/plugins/installed-plugin-dirs.ts +101 -0
  514. package/src/plugins/mtime-cache.ts +34 -22
  515. package/src/plugins/types.ts +69 -8
  516. package/src/prompts/persona-resolver.ts +6 -2
  517. package/src/prompts/system-prompt.ts +72 -17
  518. package/src/providers/__tests__/inference.test.ts +40 -0
  519. package/src/providers/__tests__/preflight-resolved-config.test.ts +58 -1
  520. package/src/providers/__tests__/retry-callsite.test.ts +67 -0
  521. package/src/providers/__tests__/vellum-connection-routing.test.ts +42 -11
  522. package/src/providers/anthropic/client.ts +112 -95
  523. package/src/providers/connection-resolution.ts +35 -1
  524. package/src/providers/gemini/client.ts +5 -5
  525. package/src/providers/inference/adapter-factory.ts +16 -10
  526. package/src/providers/inference/auth.ts +16 -0
  527. package/src/providers/inference/connection-availability.ts +164 -12
  528. package/src/providers/inference/connections.ts +25 -1
  529. package/src/providers/media-resolve.ts +21 -11
  530. package/src/providers/model-catalog.ts +15 -0
  531. package/src/providers/openai/chat-completions-provider.ts +4 -4
  532. package/src/providers/openai/responses-provider.ts +12 -25
  533. package/src/providers/registry.ts +9 -4
  534. package/src/providers/retry.ts +25 -0
  535. package/src/providers/server-tool-pairing.ts +134 -0
  536. package/src/providers/speech-to-text/__tests__/resolve.test.ts +187 -6
  537. package/src/providers/speech-to-text/deepgram.ts +1 -0
  538. package/src/providers/speech-to-text/resolve.ts +80 -10
  539. package/src/providers/speech-to-text/vellum-managed.ts +7 -1
  540. package/src/providers/speech-to-text/vellum-speech-relay-connection.ts +3 -1
  541. package/src/providers/types.ts +26 -6
  542. package/src/providers/vellum-model-routing.ts +6 -7
  543. package/src/runtime/AGENTS.md +2 -0
  544. package/src/runtime/__tests__/agent-wake.test.ts +16 -16
  545. package/src/runtime/__tests__/background-job-runner.test.ts +0 -144
  546. package/src/runtime/__tests__/desktop-presence.test.ts +162 -0
  547. package/src/runtime/agent-wake.ts +63 -24
  548. package/src/runtime/assistant-event-hub.ts +87 -12
  549. package/src/runtime/auth/__tests__/guard-tests.test.ts +2 -0
  550. package/src/runtime/auth/__tests__/scopes.test.ts +20 -0
  551. package/src/runtime/auth/same-actor.ts +56 -8
  552. package/src/runtime/auth/scopes.ts +12 -0
  553. package/src/runtime/auth/types.ts +2 -0
  554. package/src/runtime/background-job-runner.ts +5 -27
  555. package/src/runtime/channel-readiness-service.ts +59 -3
  556. package/src/runtime/channel-readiness-types.ts +18 -0
  557. package/src/runtime/desktop-presence.ts +69 -0
  558. package/src/runtime/http-server.ts +12 -0
  559. package/src/runtime/local-actor-identity.ts +11 -9
  560. package/src/runtime/migrations/__tests__/vbundle-symlink-streaming-importer.test.ts +0 -0
  561. package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
  562. package/src/runtime/routes/__tests__/client-routes.test.ts +186 -14
  563. package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +547 -1
  564. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +162 -7
  565. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +14 -10
  566. package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +340 -3
  567. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +28 -1
  568. package/src/runtime/routes/__tests__/plugins-routes.test.ts +173 -1
  569. package/src/runtime/routes/__tests__/question-routes.test.ts +64 -0
  570. package/src/runtime/routes/__tests__/retrospective-routes.test.ts +20 -0
  571. package/src/runtime/routes/__tests__/skill-history-route.test.ts +116 -0
  572. package/src/runtime/routes/__tests__/wake-conversation-routes.test.ts +60 -2
  573. package/src/runtime/routes/archive-utils.ts +16 -12
  574. package/src/runtime/routes/attachment-routes.ts +13 -6
  575. package/src/runtime/routes/canned-reply-release.ts +54 -0
  576. package/src/runtime/routes/channel-verification-routes.ts +2 -27
  577. package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +5 -0
  578. package/src/runtime/routes/client-routes.ts +114 -2
  579. package/src/runtime/routes/conversation-cli-routes.ts +10 -12
  580. package/src/runtime/routes/conversation-list-routes.ts +59 -10
  581. package/src/runtime/routes/conversation-management-routes.ts +47 -18
  582. package/src/runtime/routes/conversation-query-routes.ts +32 -53
  583. package/src/runtime/routes/conversation-routes.ts +360 -293
  584. package/src/runtime/routes/credential-prompt-routes.ts +23 -2
  585. package/src/runtime/routes/debug-routes.ts +4 -1
  586. package/src/runtime/routes/default-provider-routes.ts +7 -11
  587. package/src/runtime/routes/documents-routes.ts +44 -1
  588. package/src/runtime/routes/events-routes.ts +28 -1
  589. package/src/runtime/routes/host-app-control-routes.ts +2 -0
  590. package/src/runtime/routes/host-bash-routes.ts +2 -0
  591. package/src/runtime/routes/host-browser-routes.ts +2 -0
  592. package/src/runtime/routes/host-cu-routes.ts +2 -0
  593. package/src/runtime/routes/host-file-routes.ts +2 -0
  594. package/src/runtime/routes/host-transfer-routes.ts +4 -0
  595. package/src/runtime/routes/identity-routes.ts +6 -6
  596. package/src/runtime/routes/inbound-message-handler.ts +5 -5
  597. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +7 -3
  598. package/src/runtime/routes/inbound-stages/background-dispatch.ts +14 -0
  599. package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -2
  600. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +14 -11
  601. package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +14 -17
  602. package/src/runtime/routes/inference-profile-availability-guard.ts +171 -0
  603. package/src/runtime/routes/inference-profile-session-handler.ts +26 -2
  604. package/src/runtime/routes/inference-profile-session-routes.ts +2 -1
  605. package/src/runtime/routes/inference-profiles-routes.ts +152 -73
  606. package/src/runtime/routes/inference-provider-connection-routes.ts +0 -16
  607. package/src/runtime/routes/llm-call-sites-routes.ts +13 -0
  608. package/src/runtime/routes/log-export-routes.ts +1 -1
  609. package/src/runtime/routes/notification-routes.ts +0 -14
  610. package/src/runtime/routes/oauth-providers.ts +1 -0
  611. package/src/runtime/routes/plugins-routes.ts +106 -1
  612. package/src/runtime/routes/profiler-routes.ts +4 -2
  613. package/src/runtime/routes/question-routes.ts +28 -10
  614. package/src/runtime/routes/retrospective-routes.ts +9 -3
  615. package/src/runtime/routes/schedule-routes.ts +210 -12
  616. package/src/runtime/routes/secret-routes.ts +40 -23
  617. package/src/runtime/routes/settings-routes.ts +41 -13
  618. package/src/runtime/routes/skills-routes.ts +79 -0
  619. package/src/runtime/routes/ui-snapshot-routes.ts +1 -0
  620. package/src/runtime/routes/wake-conversation-routes.ts +23 -0
  621. package/src/runtime/services/__tests__/conversation-serializer.test.ts +1 -0
  622. package/src/runtime/services/conversation-serializer.ts +24 -9
  623. package/src/runtime/sync/documents-sidecar-publish.test.ts +101 -0
  624. package/src/runtime/sync/resource-sync-events.ts +79 -1
  625. package/src/runtime/sync/worker-daemon-notify.test.ts +34 -0
  626. package/src/runtime/sync/worker-daemon-notify.ts +44 -6
  627. package/src/runtime/verification-outbound-actions.ts +250 -222
  628. package/src/runtime/verification-templates.ts +41 -30
  629. package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +614 -0
  630. package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +1066 -0
  631. package/src/schedule/inference-profile.ts +64 -3
  632. package/src/schedule/plugin-schedule-declarations.ts +518 -0
  633. package/src/schedule/plugin-schedule-reconciler.ts +605 -0
  634. package/src/schedule/plugin-schedules-gate.ts +23 -0
  635. package/src/schedule/schedule-store.ts +520 -26
  636. package/src/schedule/scheduler.ts +38 -0
  637. package/src/security/credential-backend.ts +51 -4
  638. package/src/security/encrypted-store.ts +64 -42
  639. package/src/security/secure-keys.ts +55 -7
  640. package/src/skills/catalog-install.ts +20 -7
  641. package/src/skills/inline-command-runner.ts +2 -7
  642. package/src/skills/skill-history.test.ts +269 -0
  643. package/src/skills/skill-history.ts +250 -0
  644. package/src/skills/skillssh-registry.ts +1 -1
  645. package/src/stt/daemon-batch-transcriber.ts +8 -6
  646. package/src/subagent/__tests__/consult-prompt.test.ts +25 -0
  647. package/src/subagent/consult-prompt.ts +3 -1
  648. package/src/subagent/consult-transcript.ts +1 -1
  649. package/src/subagent/manager.ts +304 -44
  650. package/src/subagent/progress-events.ts +35 -0
  651. package/src/subagent/role-resolution.ts +88 -0
  652. package/src/subagent/types.ts +302 -67
  653. package/src/subagent/validate-allowlists.ts +3 -4
  654. package/src/telegram/__tests__/webhook-health.test.ts +73 -1
  655. package/src/telegram/webhook-health.ts +101 -3
  656. package/src/telemetry/__tests__/telemetry-event-fixtures.ts +2 -0
  657. package/src/telemetry/activation-funnel.ts +6 -1
  658. package/src/telemetry/live-voice-funnel.ts +75 -0
  659. package/src/telemetry/telemetry-event-sources.ts +6 -0
  660. package/src/telemetry/telemetry-wire-source.json +1 -1
  661. package/src/telemetry/telemetry-wire.generated.ts +2 -0
  662. package/src/telemetry/types.ts +23 -4
  663. package/src/telemetry/usage-telemetry-reporter.test.ts +4 -0
  664. package/src/tools/__tests__/subagent-workflow-tool-input-schemas.test.ts +1 -1
  665. package/src/tools/ask-question/ask-question-tool.test.ts +118 -3
  666. package/src/tools/ask-question/ask-question-tool.ts +56 -2
  667. package/src/tools/browser/browser-execution.ts +1 -1
  668. package/src/tools/calls/call-start.ts +8 -5
  669. package/src/tools/conversation-groups/move_to_group.ts +10 -12
  670. package/src/tools/document/document-tool.ts +11 -4
  671. package/src/tools/execution-timeout.ts +9 -2
  672. package/src/tools/executor.ts +7 -6
  673. package/src/tools/filesystem/edit.ts +1 -1
  674. package/src/tools/filesystem/list.ts +1 -1
  675. package/src/tools/filesystem/read.ts +7 -13
  676. package/src/tools/filesystem/search.ts +23 -21
  677. package/src/tools/filesystem/write.ts +4 -1
  678. package/src/tools/host-filesystem/edit.ts +7 -2
  679. package/src/tools/host-filesystem/read.ts +21 -8
  680. package/src/tools/host-filesystem/write.ts +10 -2
  681. package/src/tools/host-terminal/host-shell.ts +2 -2
  682. package/src/tools/mcp/mcp-tool-factory.ts +25 -1
  683. package/src/tools/permission-checker.ts +8 -3
  684. package/src/tools/schedule/delete.ts +13 -1
  685. package/src/tools/schedule/list.ts +14 -3
  686. package/src/tools/schedule/update.ts +56 -33
  687. package/src/tools/shared/filesystem/audio-read.ts +6 -4
  688. package/src/tools/shared/filesystem/file-ops-service.ts +176 -116
  689. package/src/tools/shared/filesystem/image-read.ts +12 -12
  690. package/src/tools/shared/filesystem/path-policy.ts +70 -5
  691. package/src/tools/shared/filesystem/size-guard.ts +6 -6
  692. package/src/tools/shared/input-misuse.ts +72 -0
  693. package/src/tools/skills/scaffold-managed.ts +82 -0
  694. package/src/tools/skills/skill-tool-factory.ts +14 -1
  695. package/src/tools/subagent/consult-deadline.ts +12 -7
  696. package/src/tools/subagent/message.ts +3 -1
  697. package/src/tools/subagent/read.ts +66 -4
  698. package/src/tools/subagent/spawn.ts +500 -78
  699. package/src/tools/tool-approval-handler.ts +45 -4
  700. package/src/tools/types.ts +22 -3
  701. package/src/tools/ui-surface/surface-shape-docs.ts +1 -1
  702. package/src/tools/ui-surface/visual-validation.ts +2 -0
  703. package/src/usage/__tests__/subagent-attribution.test.ts +109 -0
  704. package/src/usage/subagent-attribution.ts +108 -0
  705. package/src/util/__tests__/sqlite-retry.test.ts +112 -0
  706. package/src/util/abort-reasons.ts +16 -0
  707. package/src/util/ansi.ts +44 -0
  708. package/src/util/disk-usage.ts +17 -13
  709. package/src/util/image-conversion.ts +20 -13
  710. package/src/util/schedule-source-key.ts +23 -0
  711. package/src/util/worker-process.ts +7 -1
  712. package/src/workspace/git-service.ts +33 -9
  713. package/src/workspace/migrations/140-repair-seed-pinned-memory-v3-live.ts +260 -0
  714. package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +111 -0
  715. package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +126 -0
  716. package/src/workspace/migrations/registry.ts +4 -0
  717. package/src/workspace/top-level-renderer.ts +15 -2
  718. package/src/__tests__/tool-side-effects-slack-dm.test.ts +0 -357
  719. package/src/daemon/install-symlink.ts +0 -207
  720. package/src/notifications/deferred-emit.ts +0 -147
@@ -4,14 +4,18 @@
4
4
  *
5
5
  * This module owns only the durable row shape and raw SQL. The mapping to and
6
6
  * from the manager's `SubagentState` lives in `SubagentManager`, keeping this
7
- * layer decoupled from the subagent domain types. The one domain function it
8
- * does reach for is `normalizeSubagentLabel`: label lookups have to fold
9
- * exactly the way the manager's in-memory index folds, and SQLite has no
10
- * equivalent (`lower()` is ASCII-only), so the comparison runs in JS against
11
- * the single shared normalizer rather than a SQL predicate that can drift.
7
+ * layer decoupled from the subagent domain types. It reaches into that domain
8
+ * for exactly two things, both to keep a SQL predicate from drifting away from
9
+ * the definition the manager uses: `normalizeSubagentLabel`, because label
10
+ * lookups have to fold the way the in-memory index folds and SQLite has no
11
+ * equivalent (`lower()` is ASCII-only), and `TERMINAL_STATUSES`, which decides
12
+ * which statuses a similarity scan reads as finished.
12
13
  */
13
14
 
14
- import { normalizeSubagentLabel } from "../subagent/types.js";
15
+ import {
16
+ normalizeSubagentLabel,
17
+ TERMINAL_STATUSES,
18
+ } from "../subagent/types.js";
15
19
  import { rawAll, rawGet, rawRun } from "./raw-query.js";
16
20
 
17
21
  /** A durable subagent lifecycle record (camelCase mirror of the row). */
@@ -299,6 +303,144 @@ export function getSubagentRecordsByParent(
299
303
  ).map(rowToRecord);
300
304
  }
301
305
 
306
+ /**
307
+ * Rows the similarity scan reads before it stops. The scan is bounded so a
308
+ * long-lived assistant cannot turn every spawn into a full-table read; a
309
+ * truncated window can only under-count, which is the safe direction for a
310
+ * caller that rate-limits on the count.
311
+ */
312
+ const RECENT_SPAWN_SCAN_LIMIT = 500;
313
+
314
+ /**
315
+ * The status a run has to hold to count as a repeat of already-done work. A
316
+ * failed, aborted, or interrupted run left no answer behind, so spawning its
317
+ * objective again is recovery rather than repetition, and counting those runs
318
+ * would hold the retry that is exactly the right move.
319
+ */
320
+ const REUSABLE_SPAWN_STATUS = "completed";
321
+
322
+ /**
323
+ * Statuses the scan skips: the run ended without an answer, so it is neither
324
+ * work already done nor work still under way. Derived from the terminal set so
325
+ * a new terminal status cannot silently read as in flight.
326
+ */
327
+ const DEAD_END_SPAWN_STATUSES: readonly string[] = [
328
+ ...TERMINAL_STATUSES,
329
+ ].filter((status) => status !== REUSABLE_SPAWN_STATUS);
330
+
331
+ /**
332
+ * Fold an objective to the form two spawns are compared in: lowercased and
333
+ * whitespace-collapsed. Callers normalize the incoming objective with this same
334
+ * function, so both sides of the comparison are folded by one implementation.
335
+ *
336
+ * The whole objective is compared, never a prefix. Batch work routinely shares
337
+ * a long instruction preamble and diverges only in its tail (the file,
338
+ * component, or ticket each run targets), and comparing prefixes reads that
339
+ * batch as one objective repeated. Comparing in full costs only the re-run
340
+ * whose wording changed, which is a missed catch by an advisory guard, while a
341
+ * batch folded together holds work the caller genuinely meant to run. The
342
+ * runaway shape this guard exists for is a burst of the SAME objective, which
343
+ * folds identically either way, so full comparison gives up nothing on the
344
+ * case that matters and keeps a legitimate fan-out running.
345
+ *
346
+ * The separate `context` spawn argument is not part of the key: a subagent row
347
+ * does not carry it (context, prompts, and trust are deliberately left off the
348
+ * durable record), so two spawns with one objective and different contexts
349
+ * still fold together. That residue is bounded by the guard being advisory:
350
+ * the caller is told what already ran and spawns anyway if it meant to.
351
+ */
352
+ export function normalizeSpawnObjective(objective: string): string {
353
+ return objective.toLowerCase().replace(/\s+/gu, " ").trim();
354
+ }
355
+
356
+ /** How runs matching one normalized objective are distributed in a scope. */
357
+ export interface SimilarSpawnTally {
358
+ /** Runs that completed, so their answer exists and can be read. */
359
+ count: number;
360
+ /** Summed `estimated_cost` of the completed rows. */
361
+ estimatedCost: number;
362
+ /**
363
+ * Runs still under way (pending, running, or awaiting input). Kept apart
364
+ * from `count` because they have produced nothing to read yet: they are
365
+ * evidence of duplicated work in progress, not of an available answer.
366
+ */
367
+ inFlight: number;
368
+ }
369
+
370
+ /** The two scopes a repeat spawn is judged against. */
371
+ export interface RecentSimilarSpawns {
372
+ /** Matches spawned under the same parent conversation. */
373
+ conversation: SimilarSpawnTally;
374
+ /** Matches anywhere in this assistant; the table holds one assistant's rows. */
375
+ assistant: SimilarSpawnTally;
376
+ }
377
+
378
+ /**
379
+ * Tally the runs since `sinceMs` (an epoch cutoff) whose objective folds to
380
+ * `normalizedObjective`, both under `parentConversationId` and assistant-wide,
381
+ * split by whether each run finished with an answer or is still under way.
382
+ *
383
+ * Runs that ended without an answer are left out entirely: spawning their
384
+ * objective again is recovery rather than repetition. Advisor rows are left out
385
+ * too: an advisor consult blocks its caller and is not rate-limited, so its
386
+ * history must not count against a background spawn.
387
+ *
388
+ * Both scopes come from one bounded scan because the assistant-wide set
389
+ * contains the conversation's, so a second query would read the same rows
390
+ * twice.
391
+ *
392
+ * Rows hold the raw objective, so the fold has to happen on the column too.
393
+ * It runs in JS rather than SQL: SQLite's `lower()` folds ASCII only and has no
394
+ * whitespace-collapsing function, so a SQL predicate would disagree with
395
+ * {@link normalizeSpawnObjective} on exactly the objectives (accented, oddly
396
+ * spaced) a re-run is most likely to differ by.
397
+ */
398
+ export function countRecentSimilarSpawns(args: {
399
+ parentConversationId: string;
400
+ normalizedObjective: string;
401
+ sinceMs: number;
402
+ }): RecentSimilarSpawns {
403
+ const rows = rawAll<{
404
+ parent_conversation_id: string;
405
+ objective: string;
406
+ status: string;
407
+ estimated_cost: number;
408
+ }>(
409
+ "subagent:countRecentSimilar",
410
+ `SELECT parent_conversation_id, objective, status, estimated_cost FROM subagents
411
+ WHERE created_at >= ? AND role <> 'advisor'
412
+ AND status NOT IN (${DEAD_END_SPAWN_STATUSES.map(() => "?").join(", ")})
413
+ ORDER BY created_at DESC
414
+ LIMIT ?`,
415
+ args.sinceMs,
416
+ ...DEAD_END_SPAWN_STATUSES,
417
+ RECENT_SPAWN_SCAN_LIMIT,
418
+ );
419
+
420
+ const tally: RecentSimilarSpawns = {
421
+ conversation: { count: 0, estimatedCost: 0, inFlight: 0 },
422
+ assistant: { count: 0, estimatedCost: 0, inFlight: 0 },
423
+ };
424
+ const add = (scope: SimilarSpawnTally, row: (typeof rows)[number]): void => {
425
+ if (row.status === REUSABLE_SPAWN_STATUS) {
426
+ scope.count += 1;
427
+ scope.estimatedCost += row.estimated_cost;
428
+ } else {
429
+ scope.inFlight += 1;
430
+ }
431
+ };
432
+ for (const row of rows) {
433
+ if (normalizeSpawnObjective(row.objective) !== args.normalizedObjective) {
434
+ continue;
435
+ }
436
+ add(tally.assistant, row);
437
+ if (row.parent_conversation_id === args.parentConversationId) {
438
+ add(tally.conversation, row);
439
+ }
440
+ }
441
+ return tally;
442
+ }
443
+
302
444
  /**
303
445
  * Delete every subagent record spawned under `parentConversationId`. A row
304
446
  * lives as long as its parent conversation, and the TTL sweep drops a child's
@@ -75,6 +75,26 @@ describe("managedSpeechTranscribe", () => {
75
75
  expect(JSON.parse(init.body as string)).not.toHaveProperty("source");
76
76
  });
77
77
 
78
+ test("sends the spoken language when one is given", async () => {
79
+ // The platform forwards this to Deepgram, which otherwise decodes the
80
+ // audio as English rather than detecting its language.
81
+ await managedSpeechTranscribe({
82
+ audio: AUDIO,
83
+ mimeType: "audio/wav",
84
+ language: "multi",
85
+ });
86
+ const [, init] = mockClient!.fetch.mock.calls[0] as [string, RequestInit];
87
+ expect(JSON.parse(init.body as string).language).toBe("multi");
88
+ });
89
+
90
+ test("omits language when not provided", async () => {
91
+ // An absent field and an empty one are different requests upstream, and
92
+ // the empty one fails the platform serializer.
93
+ await managedSpeechTranscribe({ audio: AUDIO, mimeType: "audio/wav" });
94
+ const [, init] = mockClient!.fetch.mock.calls[0] as [string, RequestInit];
95
+ expect(JSON.parse(init.body as string)).not.toHaveProperty("language");
96
+ });
97
+
78
98
  test("returns unavailable when no platform client resolves", async () => {
79
99
  mockClient = null;
80
100
  const result = await managedSpeechTranscribe({
@@ -90,6 +90,14 @@ export async function managedSpeechTranscribe(input: {
90
90
  audio: Buffer;
91
91
  mimeType: string;
92
92
  source?: string;
93
+ /**
94
+ * Spoken language to decode: a BCP-47 code, or `"multi"` for nova-3
95
+ * code-switching. Omitted means the platform sends none, which Deepgram
96
+ * decodes as English rather than detecting the language. Older platform
97
+ * builds that predate the field ignore it, so sending one is safe before
98
+ * the platform side deploys.
99
+ */
100
+ language?: string;
93
101
  signal?: AbortSignal;
94
102
  }): Promise<ManagedSpeechResult<ManagedSpeechTranscription>> {
95
103
  const resolved = await resolveClient();
@@ -107,6 +115,7 @@ export async function managedSpeechTranscribe(input: {
107
115
  audioBase64: input.audio.toString("base64"),
108
116
  mimeType: input.mimeType,
109
117
  ...(input.source !== undefined ? { source: input.source } : {}),
118
+ ...(input.language ? { language: input.language } : {}),
110
119
  }),
111
120
  signal: input.signal,
112
121
  },
@@ -173,6 +173,13 @@ export {
173
173
  CredentialResolutionError,
174
174
  resolveCredential,
175
175
  } from "./resolve-credential.js";
176
+ // Resolve the public URL a third party should deliver to for one of the
177
+ // plugin's own ingress routes. Which URL is correct depends on how the
178
+ // assistant is reachable (a managed platform callback route, or a configured
179
+ // public ingress), and `ingress.publicBaseUrl` alone does not decide it. Uses
180
+ // the same resolution as `webhooks register`, and registers the callback route
181
+ // on the managed branches. The plugin defaults to the one in context.
182
+ export { resolveWebhookUrl, type WebhookUrlOptions } from "./webhook-url.js";
176
183
  // Resolve a provider for a call site (optionally overriding the profile) so a
177
184
  // plugin can run inference through the workspace's configured profiles and
178
185
  // credentials — managed-proxy or BYOK — without supplying its own API key.
@@ -317,6 +324,7 @@ export {
317
324
  deleteConversation,
318
325
  getConversation,
319
326
  getConversationDirPath,
327
+ getConversationProcessingStartedAt,
320
328
  getMessages,
321
329
  hasLexicalTokens,
322
330
  isConversationProcessing,
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Public URL resolution for a plugin's own ingress route.
3
+ *
4
+ * A plugin that receives third-party webhooks has to tell the vendor where to
5
+ * deliver. Which URL is correct depends on how the assistant is reachable:
6
+ * a platform pod and a platform-connected assistant are served through a
7
+ * managed callback route, while a self-hosted deployment is served through its
8
+ * configured public ingress. `ingress.publicBaseUrl` alone does not decide it,
9
+ * because on a platform-connected assistant that value holds the Velay tunnel
10
+ * address, which is not where plugin routes are served.
11
+ *
12
+ * `resolveCallbackUrl` owns that decision for every caller in the codebase
13
+ * (`webhooks register`, the Telegram webhook manager). This exposes it to
14
+ * plugins so there is one implementation rather than one per plugin.
15
+ */
16
+
17
+ import { createHash } from "node:crypto";
18
+
19
+ import { getConfig } from "../config/loader.js";
20
+ import { resolveCallbackUrl } from "../inbound/platform-callback-registration.js";
21
+ import { getPublicBaseUrl } from "../inbound/public-ingress-urls.js";
22
+ import { getCurrentPluginName } from "../plugins/plugin-execution-context.js";
23
+
24
+ /**
25
+ * The namespace the gateway serves plugin ingress under. Fixed here rather
26
+ * than supplied by the caller, so a plugin cannot claim another's routes.
27
+ */
28
+ const PLUGIN_WEBHOOK_PREFIX = "webhooks/plugins";
29
+
30
+ /** Plugin directory names usable as a URL path segment. */
31
+ const SAFE_PLUGIN_NAME = /^[a-z0-9][a-z0-9._-]*$/i;
32
+
33
+ /**
34
+ * Route paths the gateway serves, matching `IngressRouteSchema` in
35
+ * `gateway/src/channels/plugin-ingress.ts`: relative, free of query, fragment
36
+ * and whitespace, canonical under percent-decoding, and carrying no `.` or
37
+ * `..` segment.
38
+ *
39
+ * The two must agree. A path this rejects but the gateway serves is a route a
40
+ * plugin can install and then never resolve a URL for.
41
+ */
42
+ function isServableRoutePath(value: string): boolean {
43
+ if (value.length === 0 || value.endsWith("/")) {
44
+ return false;
45
+ }
46
+ if (!/^[^/?#\s][^?#\s]*$/.test(value)) {
47
+ return false;
48
+ }
49
+ let decoded: string;
50
+ try {
51
+ decoded = decodeURIComponent(value);
52
+ } catch {
53
+ return false;
54
+ }
55
+ if (decoded !== value) {
56
+ return false;
57
+ }
58
+ return !value.split("/").some((seg) => seg === "." || seg === "..");
59
+ }
60
+
61
+ export interface WebhookUrlOptions {
62
+ /**
63
+ * Route path within the plugin's namespace, such as `"events-photon"`.
64
+ * Relative, with no leading slash.
65
+ */
66
+ path: string;
67
+ /**
68
+ * Plugin whose namespace the route belongs to.
69
+ *
70
+ * Defaults to the plugin in context, which covers hooks and tools. Supply it
71
+ * explicitly from a plugin route handler, where no context is established.
72
+ */
73
+ plugin?: string;
74
+ /** Human-readable label for the platform's admin display. */
75
+ sourceIdentifier?: string;
76
+ }
77
+
78
+ /**
79
+ * Registration type for one route.
80
+ *
81
+ * The platform keys a callback route by type, so two routes of one plugin need
82
+ * two types or the second registration replaces the first. The slug keeps the
83
+ * type legible in the platform's admin display and the digest keeps it unique:
84
+ * `events-v1` and `events_v1` share a slug and differ here.
85
+ */
86
+ function registrationType(plugin: string, route: string): string {
87
+ const slug = route.replace(/[^a-z0-9]+/gi, "_");
88
+ const digest = createHash("sha256").update(route).digest("hex").slice(0, 8);
89
+ return `plugin_${plugin}_${slug}_${digest}`;
90
+ }
91
+
92
+ /**
93
+ * Drop a trailing slash from a resolved callback URL.
94
+ *
95
+ * The platform appends one to the callback URLs it hands back, and a provider
96
+ * given `.../events-comms/` delivers to that spelling verbatim. The gateway
97
+ * matches a plugin route against its declared path, which may never end in a
98
+ * slash (see `IngressRouteSchema`), so the slash comes off once here rather
99
+ * than at each plugin that registers a URL.
100
+ *
101
+ * Only the path is ever trimmed: this resolver passes no query parameters, and
102
+ * a URL carrying one is left alone rather than truncated.
103
+ */
104
+ function withoutTrailingSlash(url: string): string {
105
+ if (url.includes("?") || url.includes("#")) {
106
+ return url;
107
+ }
108
+ return url.replace(/\/+$/, "");
109
+ }
110
+
111
+ /**
112
+ * Resolve the public URL a third party should deliver to for `path`.
113
+ *
114
+ * On the managed branches this registers a callback route with the platform,
115
+ * which is idempotent and matches what `assistant webhooks register` does.
116
+ *
117
+ * @throws when the plugin cannot be determined, when `path` is not one the
118
+ * gateway would serve, or when no ingress is configured and the assistant is
119
+ * not connected to the platform. That last case has no URL that would work,
120
+ * and a plausible one would produce a vendor registration that silently
121
+ * receives nothing.
122
+ */
123
+ export async function resolveWebhookUrl(
124
+ options: WebhookUrlOptions,
125
+ ): Promise<string> {
126
+ const { path, sourceIdentifier } = options;
127
+ const plugin = options.plugin ?? getCurrentPluginName();
128
+
129
+ if (plugin === undefined) {
130
+ throw new Error(
131
+ "No plugin is in context; pass `plugin` to resolve a webhook URL.",
132
+ );
133
+ }
134
+ if (!SAFE_PLUGIN_NAME.test(plugin)) {
135
+ throw new Error(`Invalid plugin name for a webhook URL: "${plugin}"`);
136
+ }
137
+ if (!isServableRoutePath(path)) {
138
+ throw new Error(`Invalid plugin webhook path: "${path}"`);
139
+ }
140
+
141
+ const callbackPath = `${PLUGIN_WEBHOOK_PREFIX}/${plugin}/${path}`;
142
+
143
+ const resolved = await resolveCallbackUrl(
144
+ () => `${getPublicBaseUrl(getConfig())}/${callbackPath}`,
145
+ callbackPath,
146
+ registrationType(plugin, path),
147
+ undefined,
148
+ sourceIdentifier,
149
+ );
150
+ return withoutTrailingSlash(resolved);
151
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Enumeration of installed plugin directories, including the realpath
3
+ * containment boundary that keeps a symlinked root pointing outside the
4
+ * plugins directory out of the list.
5
+ */
6
+
7
+ import {
8
+ mkdirSync,
9
+ mkdtempSync,
10
+ rmSync,
11
+ symlinkSync,
12
+ writeFileSync,
13
+ } from "node:fs";
14
+ import { tmpdir } from "node:os";
15
+ import { join } from "node:path";
16
+ import { afterAll, beforeEach, describe, expect, test } from "bun:test";
17
+
18
+ import { getWorkspacePluginsDir } from "../../util/platform.js";
19
+ import { listInstalledPluginDirs } from "../installed-plugin-dirs.js";
20
+
21
+ const pluginsDir = getWorkspacePluginsDir();
22
+ const outsideRoot = mkdtempSync(join(tmpdir(), "installed-plugin-dirs-"));
23
+
24
+ afterAll(() => {
25
+ rmSync(outsideRoot, { recursive: true, force: true });
26
+ rmSync(pluginsDir, { recursive: true, force: true });
27
+ });
28
+
29
+ /** A directory carrying the `package.json` that marks it as an install. */
30
+ function makePluginDir(parent: string, name: string): string {
31
+ const dir = join(parent, name);
32
+ mkdirSync(dir, { recursive: true });
33
+ writeFileSync(
34
+ join(dir, "package.json"),
35
+ JSON.stringify({ name, version: "1.0.0" }),
36
+ );
37
+ return dir;
38
+ }
39
+
40
+ beforeEach(() => {
41
+ rmSync(pluginsDir, { recursive: true, force: true });
42
+ mkdirSync(pluginsDir, { recursive: true });
43
+ });
44
+
45
+ describe("listInstalledPluginDirs", () => {
46
+ test("lists real plugin directories and skips dot entries", () => {
47
+ makePluginDir(pluginsDir, "alpha");
48
+ makePluginDir(pluginsDir, ".hidden");
49
+ mkdirSync(join(pluginsDir, "no-manifest"), { recursive: true });
50
+
51
+ expect(listInstalledPluginDirs().map((p) => p.name)).toEqual(["alpha"]);
52
+ });
53
+
54
+ test("skips a symlinked plugin root that points outside the plugins dir", () => {
55
+ const escapee = makePluginDir(outsideRoot, "escapee");
56
+ symlinkSync(escapee, join(pluginsDir, "escapee"));
57
+ makePluginDir(pluginsDir, "contained");
58
+
59
+ // The plugin loader refuses to import from a root that resolves outside
60
+ // the plugins directory, so enumeration must not report it as installed:
61
+ // a caller acting on this list would arm and run code the loader would
62
+ // never activate.
63
+ expect(listInstalledPluginDirs().map((p) => p.name)).toEqual(["contained"]);
64
+ });
65
+
66
+ test("skips a symlink that aliases the plugins directory itself", () => {
67
+ symlinkSync(pluginsDir, join(pluginsDir, "self"));
68
+
69
+ expect(listInstalledPluginDirs()).toEqual([]);
70
+ });
71
+
72
+ test("keeps a symlinked plugin root that stays inside the plugins dir", () => {
73
+ const real = makePluginDir(pluginsDir, "real");
74
+ symlinkSync(real, join(pluginsDir, "alias"));
75
+
76
+ expect(
77
+ listInstalledPluginDirs()
78
+ .map((p) => p.name)
79
+ .sort(),
80
+ ).toEqual(["alias", "real"]);
81
+ });
82
+ });
@@ -7,6 +7,7 @@
7
7
  import {
8
8
  mkdirSync,
9
9
  mkdtempSync,
10
+ realpathSync,
10
11
  renameSync,
11
12
  rmSync,
12
13
  symlinkSync,
@@ -20,7 +21,10 @@ import { afterAll, describe, expect, test } from "bun:test";
20
21
 
21
22
  import { snapshotPluginSource } from "../source-fingerprint.js";
22
23
 
23
- const root = mkdtempSync(join(tmpdir(), "source-fingerprint-"));
24
+ // Resolved: snapshots report realpaths, and the platform temp directory can
25
+ // itself sit behind a link, so an unresolved fixture root would make every
26
+ // fixture path an alias of itself.
27
+ const root = realpathSync(mkdtempSync(join(tmpdir(), "source-fingerprint-")));
24
28
 
25
29
  afterAll(() => {
26
30
  rmSync(root, { recursive: true, force: true });
@@ -12,13 +12,11 @@
12
12
  * causes a spurious reload.
13
13
  */
14
14
 
15
- import { existsSync, readdirSync, statSync } from "node:fs";
15
+ import { existsSync } from "node:fs";
16
16
  import { join } from "node:path";
17
17
 
18
- import {
19
- getWorkspaceHooksDir,
20
- getWorkspacePluginsDir,
21
- } from "../util/platform.js";
18
+ import { getWorkspaceHooksDir } from "../util/platform.js";
19
+ import { listInstalledPluginDirs } from "./installed-plugin-dirs.js";
22
20
  import { snapshotPluginSource } from "./source-fingerprint.js";
23
21
  import type { PluginSourceVersion } from "./source-versions.js";
24
22
 
@@ -33,28 +31,7 @@ import type { PluginSourceVersion } from "./source-versions.js";
33
31
  export function collectSourceVersions(): Record<string, PluginSourceVersion> {
34
32
  const out: Record<string, PluginSourceVersion> = {};
35
33
 
36
- const pluginsDir = getWorkspacePluginsDir();
37
- let entries: string[] = [];
38
- try {
39
- entries = readdirSync(pluginsDir);
40
- } catch {
41
- // No plugins directory yet — nothing to watch there.
42
- }
43
- for (const entry of entries) {
44
- if (entry.startsWith(".")) {
45
- continue;
46
- }
47
- const dir = join(pluginsDir, entry);
48
- try {
49
- if (!statSync(dir).isDirectory()) {
50
- continue;
51
- }
52
- } catch {
53
- continue;
54
- }
55
- if (!existsSync(join(dir, "package.json"))) {
56
- continue;
57
- }
34
+ for (const { dir } of listInstalledPluginDirs()) {
58
35
  const snapshot = snapshotPluginSource(dir);
59
36
  out[dir] = {
60
37
  fingerprint: snapshot.fingerprint,
@@ -4,7 +4,7 @@
4
4
  * the user, or the model re-issuing the exact same call — surface a notice
5
5
  * via `additionalContext` that coaches it to (a) give the user a brief
6
6
  * progress summary and (b) delegate the rest of the investigation to an
7
- * `investigator` subagent instead of continuing inline.
7
+ * `researcher` subagent instead of continuing inline.
8
8
  *
9
9
  * Motivation: a root-cause request investigated inline ran 167 sequential
10
10
  * bash calls in a single turn with no user-facing text, overflowed the
@@ -48,7 +48,7 @@
48
48
  * all observe identical history and compute the same streak). Entries are
49
49
  * dropped when the streak restarts.
50
50
  *
51
- * Subagent conversations are exempt: an investigator is *supposed* to dig at
51
+ * Subagent conversations are exempt: a researcher is *supposed* to dig at
52
52
  * length, and subagents cannot nest (`SUBAGENT_LIMITS.maxDepth`), so the
53
53
  * delegation advice would be wrong there. Subagents run under the
54
54
  * `subagentSpawn` call site, so the exemption is a cheap `ctx.callSite` read.
@@ -66,7 +66,7 @@ import type {
66
66
  * provider-only context, never to the user.
67
67
  */
68
68
  export const EXPLORATION_DRIFT_NUDGE_TEXT =
69
- "<system_notice>You have made a long unbroken run of exploration tool calls (shell/file reads) without sending the user any text. Do two things now: (1) send the user a brief summary of what you have found so far do not keep working silently; (2) if you are tracing a root cause or exploring code/logs at length, stop exploring inline and delegate the remainder to a subagent: call subagent_spawn with role 'investigator' and a precise objective. It will investigate in its own context window and return a compact root-cause report. Continuing inline floods this conversation's context and risks losing your findings before you can report them.</system_notice>";
69
+ "<system_notice>You have made a long unbroken run of exploration tool calls (shell/file reads) without sending the user any text. Do two things now: (1) send the user a brief summary of what you have found so far, do not keep working silently; (2) if you are tracing a root cause or exploring code/logs at length, stop exploring inline and delegate the remainder to a subagent: call subagent_spawn with role 'researcher' and a precise objective. It will investigate in its own context window and return a compact root-cause report. Continuing inline floods this conversation's context and risks losing your findings before you can report them.</system_notice>";
70
70
 
71
71
  /**
72
72
  * Canonical loop notice, parameterized on the repeated call. Firmer than the
@@ -77,7 +77,7 @@ export function explorationLoopNudgeText(
77
77
  toolName: string,
78
78
  repeatCount: number,
79
79
  ): string {
80
- return `<system_notice>You have issued this exact ${toolName} call ${repeatCount} times in the current run of exploration tool calls. Repeating an identical read-only call yields no new information you are likely stuck. Do two things now: (1) send the user a brief summary of what you have found so far and what you are still missing do not keep working silently; (2) stop exploring inline and delegate the remaining investigation to a subagent: call subagent_spawn with role 'investigator' and a precise objective that includes what you have already checked and ruled out. It will investigate in its own context window and return a compact root-cause report. Do not re-issue this call again.</system_notice>`;
80
+ return `<system_notice>You have issued this exact ${toolName} call ${repeatCount} times in the current run of exploration tool calls. Repeating an identical read-only call yields no new information, you are likely stuck. Do two things now: (1) send the user a brief summary of what you have found so far and what you are still missing, do not keep working silently; (2) stop exploring inline and delegate the remaining investigation to a subagent: call subagent_spawn with role 'researcher' and a precise objective that includes what you have already checked and ruled out. It will investigate in its own context window and return a compact root-cause report. Do not re-issue this call again.</system_notice>`;
81
81
  }
82
82
 
83
83
  /**
@@ -308,7 +308,7 @@ const postToolUse: HookFunction<PostToolUseContext> = async (ctx) => {
308
308
  trigger: loopNudge ? "loop" : "long-dig",
309
309
  ...(loopNudge ? { repeatCount: loopRepeatCount } : {}),
310
310
  },
311
- "Exploration drift detected nudging summary + investigator delegation",
311
+ "Exploration drift detected: nudging summary + researcher delegation",
312
312
  );
313
313
  ctx.additionalContext = ctx.additionalContext
314
314
  ? `${ctx.additionalContext}\n${nudgeText}`
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "default-exploration-drift",
3
3
  "version": "1.0.0",
4
- "description": "First-party default plugin contributing a post-tool-use hook that nudges the model to summarize and delegate to an investigator subagent when a turn accumulates a long run of exploration tool calls, or (on loop-prone models) re-issues an identical exploration call.",
4
+ "description": "First-party default plugin contributing a post-tool-use hook that nudges the model to summarize and delegate to a researcher subagent when a turn accumulates a long run of exploration tool calls, or (on loop-prone models) re-issues an identical exploration call.",
5
5
  "private": true,
6
6
  "license": "MIT",
7
7
  "type": "module",
@@ -44,7 +44,7 @@ const postModelCall: HookFunction<PostModelCallContext> = async (ctx) => {
44
44
  isRecoverableImageError(ctx.error.message) &&
45
45
  !isImageRecoveryAttempted(ctx.conversationId)
46
46
  ) {
47
- const { messages, changed } = recoverUnsendableImages(ctx.messages);
47
+ const { messages, changed } = await recoverUnsendableImages(ctx.messages);
48
48
  // A rejection can be classified as recoverable yet leave nothing to fix —
49
49
  // e.g. a media-type mismatch on a format the sniffer cannot identify. Retrying
50
50
  // an unchanged history would resend the identical rejected image and surface a
@@ -59,7 +59,9 @@ const postModelCall: HookFunction<PostModelCallContext> = async (ctx) => {
59
59
  // turns, so a persistence failure must never abort the retry that is about
60
60
  // to run — log it and continue with the in-memory recovery.
61
61
  try {
62
- const rewritten = persistUnsendableImageDowngrades(ctx.conversationId);
62
+ const rewritten = await persistUnsendableImageDowngrades(
63
+ ctx.conversationId,
64
+ );
63
65
  if (rewritten > 0) {
64
66
  ctx.logger.info(
65
67
  { plugin: "image-recovery", rewritten },