@vellumai/assistant 0.8.1 → 0.8.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 (506) hide show
  1. package/ARCHITECTURE.md +2 -7
  2. package/Dockerfile +75 -1
  3. package/bun.lock +11 -1
  4. package/docker-entrypoint.sh +5 -0
  5. package/docker-init-apt-root.sh +94 -0
  6. package/docker-kata-apt-env.sh +39 -0
  7. package/docs/plugins.md +88 -47
  8. package/docs/skills.md +9 -7
  9. package/examples/plugins/echo/README.md +27 -27
  10. package/examples/plugins/echo/package.json +3 -0
  11. package/examples/plugins/echo/register.ts +31 -31
  12. package/node_modules/@vellumai/slack-text/src/index.test.ts +114 -14
  13. package/node_modules/@vellumai/slack-text/src/index.ts +82 -18
  14. package/openapi.yaml +325 -3
  15. package/package.json +3 -1
  16. package/scripts/generate-openapi.ts +83 -10
  17. package/scripts/sync-llm-catalog.ts +2 -2
  18. package/scripts/sync-web-search-catalog.ts +47 -25
  19. package/src/__tests__/agent-image-optimize.test.ts +11 -3
  20. package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +131 -0
  21. package/src/__tests__/anthropic-provider.test.ts +45 -0
  22. package/src/__tests__/app-builder-tool-scripts.test.ts +9 -3
  23. package/src/__tests__/app-executors.test.ts +220 -4
  24. package/src/__tests__/auto-analysis-end-to-end.test.ts +35 -0
  25. package/src/__tests__/bundled-asset.test.ts +6 -6
  26. package/src/__tests__/channel-availability-routes.test.ts +206 -0
  27. package/src/__tests__/channel-delivery-store.test.ts +289 -1
  28. package/src/__tests__/circuit-breaker-pipeline.test.ts +0 -1
  29. package/src/__tests__/clawhub.test.ts +75 -16
  30. package/src/__tests__/compactor-tail-resolution.test.ts +41 -0
  31. package/src/__tests__/config-schema.test.ts +21 -0
  32. package/src/__tests__/config-set-route.test.ts +80 -0
  33. package/src/__tests__/config-sounds-sync.test.ts +97 -0
  34. package/src/__tests__/config-watcher-skill-reseed.test.ts +453 -0
  35. package/src/__tests__/context-search-conversations-source.test.ts +117 -2
  36. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
  37. package/src/__tests__/context-search-workspace-source.test.ts +7 -0
  38. package/src/__tests__/context-token-estimator.test.ts +1 -0
  39. package/src/__tests__/conversation-abort-tool-results.test.ts +4 -1
  40. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
  41. package/src/__tests__/conversation-agent-loop-overflow.test.ts +92 -92
  42. package/src/__tests__/conversation-agent-loop.test.ts +2 -0
  43. package/src/__tests__/conversation-error.test.ts +42 -3
  44. package/src/__tests__/conversation-fork-crud.test.ts +82 -0
  45. package/src/__tests__/conversation-inference-profile-route.test.ts +40 -4
  46. package/src/__tests__/conversation-lifecycle.test.ts +173 -0
  47. package/src/__tests__/conversation-message-sync-tags.test.ts +97 -0
  48. package/src/__tests__/conversation-pairing.test.ts +54 -0
  49. package/src/__tests__/conversation-process-callsite.test.ts +4 -1
  50. package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -1
  51. package/src/__tests__/conversation-queue.test.ts +4 -1
  52. package/src/__tests__/conversation-runtime-assembly.test.ts +76 -9
  53. package/src/__tests__/conversation-slash-queue.test.ts +59 -1
  54. package/src/__tests__/conversation-slash-unknown.test.ts +4 -1
  55. package/src/__tests__/conversation-surfaces-table-action.test.ts +360 -0
  56. package/src/__tests__/conversation-sync-tags.test.ts +235 -0
  57. package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
  58. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
  59. package/src/__tests__/credential-security-invariants.test.ts +3 -2
  60. package/src/__tests__/db-slack-external-content-normalization.test.ts +301 -0
  61. package/src/__tests__/delete-managed-skill-tool.test.ts +55 -13
  62. package/src/__tests__/disk-pressure-tools.test.ts +1 -0
  63. package/src/__tests__/dm-backfill.test.ts +121 -10
  64. package/src/__tests__/document-tool-security.test.ts +258 -0
  65. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  66. package/src/__tests__/edit-propagation.test.ts +33 -0
  67. package/src/__tests__/empty-response-pipeline.test.ts +0 -4
  68. package/src/__tests__/external-plugin-loader.test.ts +60 -36
  69. package/src/__tests__/filing-service.test.ts +140 -0
  70. package/src/__tests__/get-skill-detail-audit.test.ts +0 -4
  71. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +43 -62
  72. package/src/__tests__/helpers/tar-fixtures.ts +39 -0
  73. package/src/__tests__/helpers/wait-for.ts +21 -0
  74. package/src/__tests__/history-repair-pipeline.test.ts +0 -3
  75. package/src/__tests__/history-repair.test.ts +73 -0
  76. package/src/__tests__/host-app-control-proxy.test.ts +266 -10
  77. package/src/__tests__/image-credentials.test.ts +1 -1
  78. package/src/__tests__/inbound-slack-persistence.test.ts +2 -0
  79. package/src/__tests__/inference-no-mode-boot-e2e.test.ts +1 -1
  80. package/src/__tests__/inference-profile-reaper.test.ts +4 -2
  81. package/src/__tests__/inference-profile-session-handler.test.ts +18 -6
  82. package/src/__tests__/inference-profile-session-ipc.test.ts +17 -5
  83. package/src/__tests__/injector-chain.test.ts +10 -8
  84. package/src/__tests__/install-skill-routing.test.ts +155 -37
  85. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +92 -3
  86. package/src/__tests__/list-messages-page-latest.test.ts +55 -0
  87. package/src/__tests__/llm-call-pipeline.test.ts +0 -3
  88. package/src/__tests__/llm-catalog-parity.test.ts +55 -13
  89. package/src/__tests__/llm-request-log-source-clickhouse.test.ts +34 -0
  90. package/src/__tests__/llm-request-log-source-factory.test.ts +29 -53
  91. package/src/__tests__/llm-usage-store.test.ts +114 -0
  92. package/src/__tests__/managed-profile-guard.test.ts +31 -29
  93. package/src/__tests__/managed-skill-lifecycle.test.ts +109 -18
  94. package/src/__tests__/managed-store.test.ts +84 -192
  95. package/src/__tests__/media-generate-image.test.ts +1 -1
  96. package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -2
  97. package/src/__tests__/messages-after-tiebreaker.test.ts +122 -0
  98. package/src/__tests__/oauth-commands-routes.test.ts +168 -16
  99. package/src/__tests__/oauth-provider-profiles.test.ts +9 -0
  100. package/src/__tests__/openai-provider.test.ts +24 -0
  101. package/src/__tests__/openai-responses-cutover-guard.test.ts +17 -9
  102. package/src/__tests__/overflow-reduce-pipeline.test.ts +0 -2
  103. package/src/__tests__/persistence-pipeline.test.ts +0 -2
  104. package/src/__tests__/{managed-proxy-context.test.ts → platform-proxy-context.test.ts} +1 -1
  105. package/src/__tests__/platform.test.ts +2 -0
  106. package/src/__tests__/plugin-api-shim.test.ts +125 -0
  107. package/src/__tests__/plugin-bootstrap.test.ts +10 -36
  108. package/src/__tests__/plugin-external-api.test.ts +68 -0
  109. package/src/__tests__/plugin-registry.test.ts +0 -77
  110. package/src/__tests__/plugin-route-contribution.test.ts +0 -1
  111. package/src/__tests__/plugin-skill-contribution.test.ts +0 -2
  112. package/src/__tests__/plugin-tool-contribution.test.ts +16 -15
  113. package/src/__tests__/plugin-types.test.ts +3 -13
  114. package/src/__tests__/process-message-background-slack.test.ts +8 -1
  115. package/src/__tests__/process-message-display-content.test.ts +421 -0
  116. package/src/__tests__/provider-catalog-visibility.test.ts +142 -0
  117. package/src/__tests__/provider-error-scenarios.test.ts +111 -0
  118. package/src/__tests__/{provider-managed-proxy-integration.test.ts → provider-platform-proxy-integration.test.ts} +8 -8
  119. package/src/__tests__/scaffold-managed-skill-tool.test.ts +65 -13
  120. package/src/__tests__/schedule-routes.test.ts +50 -3
  121. package/src/__tests__/schedule-store.test.ts +94 -0
  122. package/src/__tests__/scheduler-reuse-conversation.test.ts +54 -7
  123. package/src/__tests__/schema-transforms.test.ts +20 -0
  124. package/src/__tests__/search-skills-unified.test.ts +0 -5
  125. package/src/__tests__/server-history-render.test.ts +43 -0
  126. package/src/__tests__/skill-load-feature-flag.test.ts +0 -12
  127. package/src/__tests__/skill-load-tool.test.ts +27 -89
  128. package/src/__tests__/skill-memory.test.ts +23 -3
  129. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -38
  130. package/src/__tests__/skills-files-catalog-fallback.test.ts +0 -3
  131. package/src/__tests__/skills-install-extract.test.ts +49 -38
  132. package/src/__tests__/skills-install-staging.test.ts +159 -0
  133. package/src/__tests__/skills-uninstall.test.ts +9 -41
  134. package/src/__tests__/skills.test.ts +51 -58
  135. package/src/__tests__/slack-channel-config.test.ts +9 -0
  136. package/src/__tests__/subagent-tool-filtering.test.ts +50 -0
  137. package/src/__tests__/system-prompt.test.ts +737 -63
  138. package/src/__tests__/terminal-tools.test.ts +28 -1
  139. package/src/__tests__/thread-backfill.test.ts +557 -27
  140. package/src/__tests__/title-generate-pipeline.test.ts +0 -13
  141. package/src/__tests__/token-estimate-pipeline.test.ts +0 -3
  142. package/src/__tests__/tool-error-pipeline.test.ts +0 -3
  143. package/src/__tests__/tool-execute-pipeline.test.ts +0 -5
  144. package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
  145. package/src/__tests__/tool-executor.test.ts +16 -4
  146. package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -12
  147. package/src/__tests__/turn-events-store.test.ts +256 -0
  148. package/src/__tests__/twilio-routes.test.ts +4 -0
  149. package/src/__tests__/user-plugin-loader.test.ts +0 -7
  150. package/src/__tests__/voice-session-bridge.test.ts +198 -0
  151. package/src/__tests__/web-search-catalog-parity.test.ts +32 -10
  152. package/src/__tests__/workspace-migration-057-repair-stale-gemini-model-ids.test.ts +115 -3
  153. package/src/__tests__/workspace-migration-072-seed-reply-suggestion-callsite.test.ts +50 -0
  154. package/src/__tests__/workspace-migration-073-repair-recall-callsite-empty-profile.test.ts +153 -0
  155. package/src/__tests__/workspace-migration-085-memory-v2-bm25-b-reembed-disabled-v2-pages.test.ts +220 -0
  156. package/src/__tests__/workspace-migration-086-revert-stale-gemini-mis-rewrites.test.ts +269 -0
  157. package/src/__tests__/workspace-migration-remove-legacy-skills-index.test.ts +309 -0
  158. package/src/__tests__/workspace-migrations-runner.test.ts +111 -3
  159. package/src/acp/resolve-agent.ts +1 -1
  160. package/src/agent/image-optimize.ts +13 -5
  161. package/src/calls/voice-session-bridge.ts +61 -42
  162. package/src/channels/types.ts +108 -0
  163. package/src/cli/__tests__/unknown-command.test.ts +24 -0
  164. package/src/cli/commands/__tests__/changelog.test.ts +304 -319
  165. package/src/cli/commands/__tests__/schedules.test.ts +491 -0
  166. package/src/cli/commands/changelog.ts +106 -42
  167. package/src/cli/commands/conversations.ts +102 -17
  168. package/src/cli/commands/default-action.ts +10 -53
  169. package/src/cli/commands/notifications.ts +329 -317
  170. package/src/cli/commands/plugins.ts +185 -0
  171. package/src/cli/commands/schedules.ts +391 -0
  172. package/src/cli/commands/telemetry.ts +40 -0
  173. package/src/cli/lib/__tests__/cli-colors.test.ts +48 -0
  174. package/src/cli/lib/__tests__/confirm-prompt.test.ts +159 -0
  175. package/src/cli/lib/__tests__/install-from-github.test.ts +355 -0
  176. package/src/cli/lib/__tests__/list-installed-plugins.test.ts +154 -0
  177. package/src/cli/lib/__tests__/uninstall-plugin.test.ts +124 -0
  178. package/src/cli/lib/__tests__/unknown-command.test.ts +106 -0
  179. package/src/cli/lib/cli-colors.ts +12 -0
  180. package/src/cli/lib/confirm-prompt.ts +79 -0
  181. package/src/cli/lib/install-from-github.ts +304 -0
  182. package/src/cli/lib/list-installed-plugins.ts +137 -0
  183. package/src/cli/lib/uninstall-plugin.ts +82 -0
  184. package/src/cli/lib/unknown-command.ts +111 -0
  185. package/src/cli/program.ts +38 -2
  186. package/src/config/bundled-skills/app-builder/SKILL.md +23 -21
  187. package/src/config/bundled-skills/app-builder/TOOLS.json +7 -0
  188. package/src/config/bundled-skills/computer-use/TOOLS.json +15 -52
  189. package/src/config/bundled-skills/document/SKILL.md +23 -3
  190. package/src/config/bundled-skills/document/TOOLS.json +53 -0
  191. package/src/config/bundled-skills/document/tools/document-delete.ts +12 -0
  192. package/src/config/bundled-skills/document/tools/document-list.ts +12 -0
  193. package/src/config/bundled-skills/document/tools/document-read.ts +12 -0
  194. package/src/config/bundled-skills/skill-management/SKILL.md +2 -2
  195. package/src/config/bundled-skills/skill-management/TOOLS.json +7 -7
  196. package/src/config/bundled-tool-registry.ts +6 -0
  197. package/src/config/feature-flag-registry.json +41 -1
  198. package/src/config/loader.ts +64 -38
  199. package/src/config/schema.ts +7 -10
  200. package/src/config/schemas/__tests__/llm-request-logs.test.ts +36 -0
  201. package/src/config/schemas/channels.ts +8 -0
  202. package/src/config/schemas/compaction.ts +28 -0
  203. package/src/config/schemas/heartbeat.ts +9 -0
  204. package/src/config/schemas/llm-request-logs.ts +31 -7
  205. package/src/config/schemas/llm.ts +3 -0
  206. package/src/config/schemas/memory-retrieval.ts +18 -0
  207. package/src/config/schemas/tools.ts +14 -0
  208. package/src/config/skills.ts +3 -96
  209. package/src/context/compactor.ts +1047 -0
  210. package/src/context/token-estimator.ts +2 -2
  211. package/src/context/window-manager.ts +197 -1520
  212. package/src/credential-execution/managed-catalog.ts +37 -0
  213. package/src/credential-health/credential-health-service.ts +280 -19
  214. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +34 -0
  215. package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +138 -0
  216. package/src/daemon/__tests__/conversation-tool-setup.test.ts +74 -0
  217. package/src/daemon/approval-generators.ts +8 -6
  218. package/src/daemon/config-watcher.ts +94 -31
  219. package/src/daemon/conversation-agent-loop.ts +169 -9
  220. package/src/daemon/conversation-error.ts +171 -37
  221. package/src/daemon/conversation-lifecycle.ts +53 -40
  222. package/src/daemon/conversation-messaging.ts +25 -6
  223. package/src/daemon/conversation-process.ts +49 -12
  224. package/src/daemon/conversation-runtime-assembly.ts +16 -1
  225. package/src/daemon/conversation-slash.ts +12 -5
  226. package/src/daemon/conversation-store.ts +11 -4
  227. package/src/daemon/conversation-tool-setup.ts +39 -7
  228. package/src/daemon/conversation.ts +33 -1
  229. package/src/daemon/external-plugins-bootstrap.ts +217 -181
  230. package/src/daemon/first-greeting.ts +22 -2
  231. package/src/daemon/handlers/config-model.ts +6 -5
  232. package/src/daemon/handlers/config-slack-channel.ts +15 -3
  233. package/src/daemon/handlers/shared.ts +14 -5
  234. package/src/daemon/handlers/skills.ts +111 -108
  235. package/src/daemon/history-repair.ts +28 -1
  236. package/src/daemon/host-app-control-proxy.ts +98 -23
  237. package/src/daemon/lifecycle.ts +45 -35
  238. package/src/daemon/meet-host-supervisor.ts +5 -4
  239. package/src/daemon/memory-v2-startup.ts +49 -0
  240. package/src/daemon/message-protocol.ts +1 -0
  241. package/src/daemon/message-types/conversations.ts +25 -0
  242. package/src/daemon/message-types/messages.ts +61 -0
  243. package/src/daemon/message-types/subagents.ts +1 -0
  244. package/src/daemon/message-types/sync.ts +1 -0
  245. package/src/daemon/pkb-reminder-builder.test.ts +1 -1
  246. package/src/daemon/pkb-reminder-builder.ts +1 -1
  247. package/src/daemon/plugin-source-watcher.ts +146 -0
  248. package/src/daemon/process-message.ts +21 -3
  249. package/src/daemon/server.ts +11 -2
  250. package/src/daemon/skill-memory-refresh.ts +29 -0
  251. package/src/documents/document-store.ts +221 -3
  252. package/src/embedded/plugin-api.ts +40 -0
  253. package/src/filing/filing-service.ts +39 -0
  254. package/src/heartbeat/__tests__/heartbeat-service.test.ts +91 -6
  255. package/src/heartbeat/heartbeat-run-store.ts +2 -1
  256. package/src/heartbeat/heartbeat-service.ts +41 -0
  257. package/src/home/__tests__/feed-types.test.ts +40 -0
  258. package/src/home/feed-types.ts +22 -0
  259. package/src/home/post-connect-feed.ts +1 -0
  260. package/src/index.ts +18 -1
  261. package/src/live-voice/__tests__/live-voice-stt.test.ts +57 -0
  262. package/src/mcp/client.ts +20 -4
  263. package/src/media/image-credentials.ts +3 -3
  264. package/src/memory/__tests__/bookmark-crud.test.ts +33 -27
  265. package/src/memory/__tests__/conversation-queries.test.ts +263 -0
  266. package/src/memory/__tests__/jobs-worker-v2-graph-trigger-embed.test.ts +113 -0
  267. package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +119 -14
  268. package/src/memory/__tests__/message-content.test.ts +35 -0
  269. package/src/memory/bookmark-crud.ts +42 -10
  270. package/src/memory/context-search/sources/conversations.ts +62 -2
  271. package/src/memory/context-search/sources/workspace.ts +4 -0
  272. package/src/memory/conversation-crud.ts +63 -19
  273. package/src/memory/conversation-queries.ts +110 -10
  274. package/src/memory/db-init.ts +6 -0
  275. package/src/memory/delivery-crud.ts +152 -5
  276. package/src/memory/embedding-backend.ts +4 -4
  277. package/src/memory/external-conversation-store.ts +66 -5
  278. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +66 -9
  279. package/src/memory/graph/conversation-graph-memory.ts +31 -15
  280. package/src/memory/graph/tools.ts +3 -3
  281. package/src/memory/indexer.ts +34 -29
  282. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +73 -0
  283. package/src/memory/jobs/embed-concept-page.ts +20 -11
  284. package/src/memory/jobs-worker.ts +6 -1
  285. package/src/memory/llm-request-log-source-clickhouse.ts +17 -10
  286. package/src/memory/llm-request-log-source.ts +19 -52
  287. package/src/memory/llm-usage-store.ts +125 -5
  288. package/src/memory/memory-retrospective-startup-cleanup.ts +72 -5
  289. package/src/memory/message-content.ts +1 -1
  290. package/src/memory/migrations/109-external-conversation-bindings.ts +15 -4
  291. package/src/memory/migrations/229-delete-private-conversations.test.ts +38 -1
  292. package/src/memory/migrations/229-delete-private-conversations.ts +7 -0
  293. package/src/memory/migrations/247-external-conversation-binding-thread-id.ts +78 -0
  294. package/src/memory/migrations/248-create-onboarding-events.ts +21 -0
  295. package/src/memory/migrations/249-normalize-slack-external-content.ts +240 -0
  296. package/src/memory/migrations/index.ts +6 -0
  297. package/src/memory/migrations/registry.ts +8 -0
  298. package/src/memory/onboarding-events-store.ts +106 -0
  299. package/src/memory/schema/bookmarks.ts +0 -2
  300. package/src/memory/schema/calls.ts +1 -0
  301. package/src/memory/schema/inference.ts +1 -3
  302. package/src/memory/schema/infrastructure.ts +12 -0
  303. package/src/memory/turn-events-store.ts +127 -2
  304. package/src/memory/v2/__tests__/activation.test.ts +0 -8
  305. package/src/memory/v2/__tests__/injection.test.ts +98 -8
  306. package/src/memory/v2/__tests__/migration.test.ts +87 -0
  307. package/src/memory/v2/__tests__/page-index.test.ts +83 -0
  308. package/src/memory/v2/__tests__/prompts-router.test.ts +58 -6
  309. package/src/memory/v2/__tests__/qdrant.test.ts +66 -3
  310. package/src/memory/v2/__tests__/router.test.ts +15 -0
  311. package/src/memory/v2/__tests__/skill-store.test.ts +387 -8
  312. package/src/memory/v2/injection.ts +32 -6
  313. package/src/memory/v2/migration.ts +49 -19
  314. package/src/memory/v2/page-index.ts +35 -5
  315. package/src/memory/v2/prompts/router.ts +11 -8
  316. package/src/memory/v2/prompts/sweep.ts +2 -2
  317. package/src/memory/v2/qdrant.ts +135 -7
  318. package/src/memory/v2/router.ts +9 -8
  319. package/src/memory/v2/skill-store.ts +120 -35
  320. package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +45 -5
  321. package/src/messaging/providers/slack/__tests__/download.test.ts +231 -0
  322. package/src/messaging/providers/slack/adapter.ts +43 -5
  323. package/src/messaging/providers/slack/client.ts +27 -0
  324. package/src/messaging/providers/slack/deep-link.ts +65 -0
  325. package/src/messaging/providers/slack/download.ts +104 -0
  326. package/src/messaging/providers/slack/message-metadata.test.ts +32 -0
  327. package/src/messaging/providers/slack/message-metadata.ts +27 -0
  328. package/src/messaging/providers/slack/render-transcript.test.ts +134 -0
  329. package/src/messaging/providers/slack/render-transcript.ts +69 -5
  330. package/src/messaging/providers/slack/types.ts +20 -1
  331. package/src/notifications/conversation-pairing.ts +2 -1
  332. package/src/notifications/decision-engine.ts +2 -1
  333. package/src/notifications/emit-signal.ts +20 -1
  334. package/src/notifications/home-feed-side-effect.ts +54 -0
  335. package/src/notifications/signal.ts +3 -1
  336. package/src/oauth/connection-resolver.ts +8 -4
  337. package/src/oauth/platform-connection.ts +6 -2
  338. package/src/oauth/seed-providers.ts +10 -1
  339. package/src/permissions/checker.ts +2 -0
  340. package/src/permissions/ipc-risk-types.ts +1 -0
  341. package/src/permissions/question-prompter.test.ts +416 -0
  342. package/src/permissions/question-prompter.ts +294 -0
  343. package/src/platform/client.test.ts +1 -1
  344. package/src/platform/client.ts +1 -1
  345. package/src/plugin-api/constants.ts +26 -0
  346. package/src/plugin-api/index.ts +34 -1
  347. package/src/plugin-api/types.ts +104 -22
  348. package/src/plugins/defaults/circuit-breaker.ts +0 -5
  349. package/src/plugins/defaults/compaction.ts +0 -4
  350. package/src/plugins/defaults/empty-response.ts +0 -2
  351. package/src/plugins/defaults/history-repair.ts +0 -2
  352. package/src/plugins/defaults/injectors.ts +36 -3
  353. package/src/plugins/defaults/llm-call.ts +0 -2
  354. package/src/plugins/defaults/memory-retrieval.ts +0 -1
  355. package/src/plugins/defaults/overflow-reduce.ts +0 -1
  356. package/src/plugins/defaults/persistence.ts +0 -2
  357. package/src/plugins/defaults/title-generate.ts +0 -5
  358. package/src/plugins/defaults/token-estimate.ts +0 -2
  359. package/src/plugins/defaults/tool-error.ts +0 -7
  360. package/src/plugins/defaults/tool-execute.ts +0 -2
  361. package/src/plugins/defaults/tool-result-truncate.ts +0 -4
  362. package/src/plugins/ensure-plugin-api-shim.ts +96 -0
  363. package/src/plugins/external-api.ts +104 -0
  364. package/src/plugins/external-plugin-loader.ts +105 -32
  365. package/src/plugins/feature-gate.ts +22 -0
  366. package/src/plugins/pipeline.ts +37 -0
  367. package/src/plugins/registry.ts +48 -80
  368. package/src/plugins/types.ts +31 -26
  369. package/src/plugins/user-loader.ts +21 -2
  370. package/src/proactive-artifact/aux-message-injector.ts +11 -0
  371. package/src/proactive-artifact/job.test.ts +37 -5
  372. package/src/prompts/__tests__/system-prompt.test.ts +12 -0
  373. package/src/prompts/__tests__/task-progress-hint-section.test.ts +99 -0
  374. package/src/prompts/normalize-onboarding.ts +27 -0
  375. package/src/prompts/sections.ts +302 -0
  376. package/src/prompts/system-prompt.ts +63 -166
  377. package/src/prompts/templates/BOOTSTRAP.md +17 -1
  378. package/src/prompts/templates/system-sections.ts +173 -0
  379. package/src/providers/__tests__/inference.test.ts +22 -7
  380. package/src/providers/anthropic/client.ts +28 -28
  381. package/src/providers/connection-resolution.ts +7 -0
  382. package/src/providers/inference/adapter-factory.ts +41 -4
  383. package/src/providers/inference/connections.ts +74 -29
  384. package/src/providers/inference/resolve-auth.ts +12 -4
  385. package/src/providers/model-catalog.ts +294 -12
  386. package/src/providers/openai/chat-completions-provider.ts +10 -2
  387. package/src/providers/openrouter/client.ts +7 -0
  388. package/src/providers/{managed-proxy → platform-proxy}/constants.ts +4 -1
  389. package/src/providers/{managed-proxy → platform-proxy}/context.ts +3 -3
  390. package/src/providers/provider-availability.ts +17 -2
  391. package/src/providers/provider-catalog-visibility.ts +36 -0
  392. package/src/providers/registry.ts +22 -14
  393. package/src/providers/retry.ts +47 -1
  394. package/src/runtime/__tests__/agent-wake.test.ts +152 -0
  395. package/src/runtime/agent-wake.ts +42 -14
  396. package/src/runtime/auth/route-policy.ts +8 -1
  397. package/src/runtime/btw-sidechain.ts +2 -0
  398. package/src/runtime/http-types.ts +19 -0
  399. package/src/runtime/migrations/origin-mode.ts +1 -1
  400. package/src/runtime/pending-interactions.ts +1 -0
  401. package/src/runtime/routes/__tests__/bookmark-routes.test.ts +17 -0
  402. package/src/runtime/routes/__tests__/conversation-management-routes.test.ts +5 -1
  403. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +107 -20
  404. package/src/runtime/routes/__tests__/question-routes.test.ts +395 -0
  405. package/src/runtime/routes/__tests__/tts-routes.test.ts +64 -1
  406. package/src/runtime/routes/acp-routes-list.test.ts +143 -0
  407. package/src/runtime/routes/acp-routes.ts +5 -3
  408. package/src/runtime/routes/auth-routes.ts +1 -1
  409. package/src/runtime/routes/bookmark-routes.ts +5 -3
  410. package/src/runtime/routes/btw-routes.ts +5 -1
  411. package/src/runtime/routes/channel-availability-routes.ts +121 -0
  412. package/src/runtime/routes/conversation-cli-routes.ts +44 -3
  413. package/src/runtime/routes/conversation-list-routes.ts +3 -20
  414. package/src/runtime/routes/conversation-management-routes.ts +17 -42
  415. package/src/runtime/routes/conversation-query-routes.ts +40 -35
  416. package/src/runtime/routes/conversation-routes.ts +90 -11
  417. package/src/runtime/routes/documents-routes.ts +25 -86
  418. package/src/runtime/routes/group-routes.ts +5 -0
  419. package/src/runtime/routes/inbound-conversation.ts +28 -8
  420. package/src/runtime/routes/inbound-message-handler.ts +236 -41
  421. package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +111 -0
  422. package/src/runtime/routes/inbound-stages/background-dispatch.ts +32 -1
  423. package/src/runtime/routes/inbound-stages/edit-intercept.ts +17 -4
  424. package/src/runtime/routes/index.ts +6 -0
  425. package/src/runtime/routes/inference-profile-session-handler.ts +17 -44
  426. package/src/runtime/routes/inference-profile-session-reaper.ts +7 -21
  427. package/src/runtime/routes/inference-provider-connection-routes.ts +65 -21
  428. package/src/runtime/routes/integrations/slack/share.ts +4 -52
  429. package/src/runtime/routes/integrations/slack/token.ts +43 -0
  430. package/src/runtime/routes/integrations/twilio.ts +6 -13
  431. package/src/runtime/routes/notification-routes.ts +1 -1
  432. package/src/runtime/routes/oauth-commands-routes.ts +105 -15
  433. package/src/runtime/routes/oauth-lifecycle-routes.ts +43 -0
  434. package/src/runtime/routes/question-routes.ts +259 -0
  435. package/src/runtime/routes/rename-conversation-routes.ts +2 -33
  436. package/src/runtime/routes/schedule-routes.ts +4 -7
  437. package/src/runtime/routes/subagents-routes.ts +57 -18
  438. package/src/runtime/routes/telemetry-routes.ts +27 -0
  439. package/src/runtime/routes/tts-routes.ts +27 -2
  440. package/src/runtime/routes/workspace-routes.test.ts +43 -0
  441. package/src/runtime/routes/workspace-routes.ts +28 -0
  442. package/src/runtime/services/conversation-serializer.ts +39 -7
  443. package/src/runtime/sync/resource-sync-events.ts +93 -1
  444. package/src/schedule/schedule-store.ts +27 -2
  445. package/src/schedule/scheduler.ts +9 -1
  446. package/src/security/__tests__/untrusted-content.test.ts +86 -0
  447. package/src/security/untrusted-content.ts +93 -8
  448. package/src/skills/catalog-files.ts +1 -1
  449. package/src/skills/catalog-install.ts +233 -116
  450. package/src/skills/clawhub.ts +70 -13
  451. package/src/skills/managed-store.ts +4 -119
  452. package/src/skills/skillssh-registry.ts +27 -48
  453. package/src/subagent/manager.ts +15 -7
  454. package/src/telemetry/types.ts +113 -1
  455. package/src/telemetry/usage-telemetry-reporter.test.ts +312 -5
  456. package/src/telemetry/usage-telemetry-reporter.ts +113 -7
  457. package/src/tools/apps/executors.ts +58 -7
  458. package/src/tools/ask-question/ask-question-tool.test.ts +509 -0
  459. package/src/tools/ask-question/ask-question-tool.ts +304 -0
  460. package/src/tools/browser/browser-execution.ts +15 -11
  461. package/src/tools/computer-use/definitions.ts +3 -3
  462. package/src/tools/credentials/vault.ts +1 -1
  463. package/src/tools/document/document-tool.ts +124 -1
  464. package/src/tools/filesystem/edit.ts +1 -1
  465. package/src/tools/filesystem/list.ts +1 -1
  466. package/src/tools/filesystem/read.ts +1 -1
  467. package/src/tools/filesystem/write.ts +5 -2
  468. package/src/tools/host-filesystem/transfer.ts +1 -1
  469. package/src/tools/host-terminal/host-shell.ts +1 -1
  470. package/src/tools/permission-checker.ts +1 -1
  471. package/src/tools/registry.ts +17 -7
  472. package/src/tools/schedule/create.ts +2 -2
  473. package/src/tools/schema-transforms.ts +7 -2
  474. package/src/tools/side-effects.ts +1 -0
  475. package/src/tools/skills/delete-managed.ts +4 -4
  476. package/src/tools/skills/execute.ts +1 -1
  477. package/src/tools/skills/scaffold-managed.ts +3 -2
  478. package/src/tools/subagent/notify-parent.ts +1 -1
  479. package/src/tools/system/request-permission.ts +2 -2
  480. package/src/tools/terminal/safe-env.ts +60 -1
  481. package/src/tools/tool-manifest.ts +2 -0
  482. package/src/tools/types.ts +72 -21
  483. package/src/tools/ui-surface/definitions.ts +6 -5
  484. package/src/tts/__tests__/provider-adapters.test.ts +76 -2
  485. package/src/tts/providers/elevenlabs-provider.ts +75 -1
  486. package/src/types/onboarding-context.ts +2 -0
  487. package/src/util/errors.ts +17 -0
  488. package/src/util/platform.ts +10 -0
  489. package/src/watcher/__tests__/engine.test.ts +22 -0
  490. package/src/watcher/engine.ts +6 -2
  491. package/src/workspace/migrations/057-repair-stale-gemini-model-ids.ts +80 -15
  492. package/src/workspace/migrations/072-seed-reply-suggestion-callsite.ts +35 -22
  493. package/src/workspace/migrations/073-repair-recall-callsite-empty-profile.ts +3 -1
  494. package/src/workspace/migrations/083-system-prompt-prefix-to-file.ts +191 -0
  495. package/src/workspace/migrations/084-remove-legacy-skills-index.ts +276 -0
  496. package/src/workspace/migrations/085-memory-v2-bm25-b-reembed-disabled-v2-pages.ts +137 -0
  497. package/src/workspace/migrations/086-revert-stale-gemini-mis-rewrites.ts +198 -0
  498. package/src/workspace/migrations/registry.ts +8 -0
  499. package/src/workspace/migrations/runner.ts +39 -9
  500. package/src/workspace/migrations/types.ts +4 -0
  501. package/examples/plugins/echo/bun.lock +0 -25
  502. package/src/__tests__/context-window-manager.test.ts +0 -2481
  503. package/src/context/__tests__/compact-prompt.test.ts +0 -63
  504. package/src/context/prompts/compact.md +0 -26
  505. package/src/prompts/__tests__/build-cli-reference-section.test.ts +0 -37
  506. /package/src/__tests__/{secret-routes-managed-proxy.test.ts → secret-routes-platform-proxy.test.ts} +0 -0
@@ -0,0 +1,276 @@
1
+ import * as fs from "node:fs";
2
+ import {
3
+ basename,
4
+ dirname,
5
+ isAbsolute,
6
+ join,
7
+ normalize,
8
+ relative,
9
+ resolve,
10
+ } from "node:path";
11
+
12
+ import { getLogger } from "../../util/logger.js";
13
+ import type { WorkspaceMigration } from "./types.js";
14
+
15
+ const log = getLogger("workspace-migration-084-remove-legacy-skills-index");
16
+
17
+ function isNotFoundError(err: unknown): boolean {
18
+ return (
19
+ typeof err === "object" &&
20
+ err !== null &&
21
+ "code" in err &&
22
+ err.code === "ENOENT"
23
+ );
24
+ }
25
+
26
+ function isInsideDirectory(rootDir: string, candidatePath: string): boolean {
27
+ const rootRealPath = fs.realpathSync(rootDir);
28
+ const candidateRealPath = fs.realpathSync(candidatePath);
29
+ const relativePath = relative(rootRealPath, candidateRealPath);
30
+ return (
31
+ relativePath === "" ||
32
+ (!relativePath.startsWith("..") && !isAbsolute(relativePath))
33
+ );
34
+ }
35
+
36
+ function parseLegacySkillIndexEntry(line: string): string | null {
37
+ const match = line.match(/^\s*[-*]\s+(.+?)\s*$/);
38
+ if (!match) return null;
39
+
40
+ let entry = match[1].trim();
41
+ const markdownLink = entry.match(/^\[.+?\]\((.+?)\)$/);
42
+ if (markdownLink) {
43
+ entry = markdownLink[1].trim();
44
+ } else {
45
+ entry = entry.split(/\s+/)[0]?.trim() ?? "";
46
+ }
47
+
48
+ entry = entry.replace(/^`|`$/g, "");
49
+ if (!entry || entry.includes("\0") || isAbsolute(entry)) return null;
50
+
51
+ const normalized = normalize(entry);
52
+ if (
53
+ normalized === "." ||
54
+ normalized.startsWith("..") ||
55
+ isAbsolute(normalized)
56
+ ) {
57
+ return null;
58
+ }
59
+
60
+ if (basename(normalized).toLowerCase() === "skill.md") {
61
+ const skillDir = dirname(normalized);
62
+ return skillDir === "." ? null : skillDir;
63
+ }
64
+
65
+ return normalized;
66
+ }
67
+
68
+ function parseLegacySkillIndexEntries(contents: string): string[] {
69
+ const entries = new Set<string>();
70
+ for (const line of contents.split(/\r?\n/)) {
71
+ const entry = parseLegacySkillIndexEntry(line);
72
+ if (entry) entries.add(entry);
73
+ }
74
+ return [...entries];
75
+ }
76
+
77
+ function skillFileContentsMatch(
78
+ sourceDir: string,
79
+ destinationDir: string,
80
+ ): boolean {
81
+ try {
82
+ return (
83
+ fs.readFileSync(join(sourceDir, "SKILL.md"), "utf-8") ===
84
+ fs.readFileSync(join(destinationDir, "SKILL.md"), "utf-8")
85
+ );
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+
91
+ function topLevelPreservationName(relativeSkillDir: string): string {
92
+ return relativeSkillDir
93
+ .split(/[\\/]+/)
94
+ .filter(Boolean)
95
+ .join("__");
96
+ }
97
+
98
+ function getPreservationDestinationDir(
99
+ skillsDir: string,
100
+ sourceDir: string,
101
+ relativeSkillDir: string,
102
+ ): string | null {
103
+ const skillName = basename(relativeSkillDir);
104
+ if (!skillName || skillName === relativeSkillDir) return null;
105
+
106
+ const alternateName = topLevelPreservationName(relativeSkillDir);
107
+ const candidateNames = [
108
+ skillName,
109
+ ...(alternateName && alternateName !== skillName ? [alternateName] : []),
110
+ ];
111
+
112
+ for (const candidateName of candidateNames) {
113
+ const destinationDir = join(skillsDir, candidateName);
114
+ if (!fs.existsSync(destinationDir)) return destinationDir;
115
+ if (skillFileContentsMatch(sourceDir, destinationDir)) {
116
+ log.info(
117
+ { destinationDir, sourceDir },
118
+ "Nested indexed skill already preserved at top-level skills directory",
119
+ );
120
+ return null;
121
+ }
122
+ }
123
+
124
+ const baseName =
125
+ alternateName && alternateName !== skillName
126
+ ? alternateName
127
+ : `legacy__${skillName}`;
128
+ for (let suffix = 2; ; suffix += 1) {
129
+ const destinationDir = join(skillsDir, `${baseName}-${suffix}`);
130
+ if (!fs.existsSync(destinationDir)) return destinationDir;
131
+ if (skillFileContentsMatch(sourceDir, destinationDir)) {
132
+ log.info(
133
+ { destinationDir, sourceDir },
134
+ "Nested indexed skill already preserved at top-level skills directory",
135
+ );
136
+ return null;
137
+ }
138
+ }
139
+ }
140
+
141
+ function preserveNestedIndexedSkill(
142
+ tempRootDir: string,
143
+ skillsDir: string,
144
+ relativeSkillDir: string,
145
+ ): void {
146
+ const sourceDir = resolve(skillsDir, relativeSkillDir);
147
+ let destinationDir: string | null = null;
148
+
149
+ try {
150
+ if (!fs.existsSync(sourceDir)) return;
151
+ if (!isInsideDirectory(skillsDir, sourceDir)) {
152
+ log.warn(
153
+ { relativeSkillDir, sourceDir },
154
+ "Skipping nested indexed skill that resolves outside skills root",
155
+ );
156
+ return;
157
+ }
158
+
159
+ const sourceStat = fs.lstatSync(sourceDir);
160
+ if (!sourceStat.isDirectory()) return;
161
+
162
+ const skillFilePath = join(sourceDir, "SKILL.md");
163
+ if (!fs.existsSync(skillFilePath)) return;
164
+ if (!isInsideDirectory(sourceDir, skillFilePath)) {
165
+ log.warn(
166
+ { skillFilePath, sourceDir },
167
+ "Skipping nested indexed skill with SKILL.md outside skill directory",
168
+ );
169
+ return;
170
+ }
171
+
172
+ const skillFileStat = fs.lstatSync(skillFilePath);
173
+ if (!skillFileStat.isFile()) return;
174
+
175
+ destinationDir = getPreservationDestinationDir(
176
+ skillsDir,
177
+ sourceDir,
178
+ relativeSkillDir,
179
+ );
180
+ if (!destinationDir) return;
181
+
182
+ fs.mkdirSync(tempRootDir, { recursive: true });
183
+ const tempDir = join(tempRootDir, basename(destinationDir));
184
+ if (fs.existsSync(tempDir)) {
185
+ fs.rmSync(tempDir, { recursive: true, force: true });
186
+ }
187
+
188
+ fs.cpSync(sourceDir, tempDir, {
189
+ dereference: false,
190
+ errorOnExist: true,
191
+ force: false,
192
+ recursive: true,
193
+ });
194
+
195
+ if (fs.existsSync(destinationDir)) {
196
+ fs.rmSync(tempDir, { recursive: true, force: true });
197
+ if (skillFileContentsMatch(sourceDir, destinationDir)) return;
198
+ log.warn(
199
+ { destinationDir, sourceDir },
200
+ "Skipping nested indexed skill preservation because destination appeared during copy",
201
+ );
202
+ return;
203
+ }
204
+
205
+ fs.renameSync(tempDir, destinationDir);
206
+ log.info(
207
+ { destinationDir, sourceDir },
208
+ "Preserved nested indexed skill at top-level skills directory",
209
+ );
210
+ } catch (err) {
211
+ if (isNotFoundError(err)) return;
212
+ log.warn(
213
+ { err, relativeSkillDir, sourceDir, destinationDir },
214
+ "Failed to preserve nested indexed skill",
215
+ );
216
+ throw err;
217
+ }
218
+ }
219
+
220
+ function preserveNestedIndexedSkills(
221
+ workspaceDir: string,
222
+ skillsDir: string,
223
+ indexPath: string,
224
+ ): void {
225
+ const contents = fs.readFileSync(indexPath, "utf-8");
226
+ const tempRootDir = join(
227
+ workspaceDir,
228
+ ".workspace-migration-084-remove-legacy-skills-index",
229
+ );
230
+ for (const relativeSkillDir of parseLegacySkillIndexEntries(contents)) {
231
+ preserveNestedIndexedSkill(tempRootDir, skillsDir, relativeSkillDir);
232
+ }
233
+ if (fs.existsSync(tempRootDir)) {
234
+ fs.rmSync(tempRootDir, { recursive: true, force: true });
235
+ }
236
+ }
237
+
238
+ export const removeLegacySkillsIndexMigration: WorkspaceMigration = {
239
+ id: "084-remove-legacy-skills-index",
240
+ description: "Remove legacy workspace skills/SKILLS.md index file",
241
+ retryFailedCheckpoint: true,
242
+
243
+ run(workspaceDir: string): void {
244
+ const skillsDir = join(workspaceDir, "skills");
245
+ const indexPath = join(skillsDir, "SKILLS.md");
246
+
247
+ try {
248
+ const stat = fs.lstatSync(indexPath);
249
+ if (!stat.isFile() && !stat.isSymbolicLink()) {
250
+ log.warn(
251
+ { path: indexPath },
252
+ "Legacy SKILLS.md path is not a file; leaving it in place",
253
+ );
254
+ return;
255
+ }
256
+
257
+ if (stat.isFile()) {
258
+ preserveNestedIndexedSkills(workspaceDir, skillsDir, indexPath);
259
+ }
260
+
261
+ fs.unlinkSync(indexPath);
262
+ log.info({ path: indexPath }, "Removed legacy skills index file");
263
+ } catch (err) {
264
+ if (isNotFoundError(err)) return;
265
+ log.warn(
266
+ { err, path: indexPath },
267
+ "Failed to remove legacy skills index file",
268
+ );
269
+ throw err;
270
+ }
271
+ },
272
+
273
+ down(_workspaceDir: string): void {
274
+ // Forward-only: SKILLS.md is no longer a supported skill catalog format.
275
+ },
276
+ };
@@ -0,0 +1,137 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { Database } from "bun:sqlite";
5
+
6
+ import type { WorkspaceMigration } from "./types.js";
7
+
8
+ /**
9
+ * Follow-up to `075-memory-v2-bm25-b-default-reembed`. Migration 075 shipped
10
+ * in v0.8.1 with no gating beyond "db exists", so workspaces with no v2
11
+ * pages still recorded a checkpoint entry and a queued reembed job. We
12
+ * cannot edit 075 to add gating retroactively — the runner skips any
13
+ * already-checkpointed id — so this migration re-runs the enqueue with the
14
+ * gating we want now.
15
+ *
16
+ * Two gates:
17
+ *
18
+ * 1. `hasConceptPages` — only enqueue if `memory/concepts/` actually has a
19
+ * `.md` page. Workspaces that never wrote a v2 page have nothing to
20
+ * reembed.
21
+ * 2. `isMemoryV2Disabled` — skip when `memory.v2.enabled` is explicitly
22
+ * `false`. The worker does not currently gate `memory_v2_reembed`
23
+ * dispatch on the config flag, so enqueueing for a workspace that
24
+ * intentionally disabled v2 would immediately re-embed pages and hit
25
+ * the embedding backend against the user's intent.
26
+ *
27
+ * When either gate fails, we also DELETE any pending `memory_v2_reembed`
28
+ * job that 075 may have enqueued in the same startup sweep. For workspaces
29
+ * that never checkpointed 075 (upgrades from pre-v0.8.1, first-boot sweeps),
30
+ * 075 runs first and unconditionally enqueues a job; without the explicit
31
+ * cancellation here, 075's job would survive 085's gate and execute against
32
+ * the user's intent.
33
+ */
34
+ export const memoryV2Bm25BReembedDisabledV2PagesMigration: WorkspaceMigration =
35
+ {
36
+ id: "085-memory-v2-bm25-b-reembed-disabled-v2-pages",
37
+ description:
38
+ "Re-enqueue memory_v2_reembed for workspaces with v2 pages, gated on v2 not being explicitly disabled",
39
+
40
+ run(workspaceDir: string): void {
41
+ const dbPath = join(workspaceDir, "data", "db", "assistant.db");
42
+ if (!existsSync(dbPath)) return;
43
+
44
+ let db: Database;
45
+ try {
46
+ db = new Database(dbPath);
47
+ } catch {
48
+ return;
49
+ }
50
+
51
+ try {
52
+ const tableRow = db
53
+ .query(
54
+ `SELECT name FROM sqlite_master WHERE type='table' AND name='memory_jobs'`,
55
+ )
56
+ .get();
57
+ if (!tableRow) return;
58
+
59
+ // If either gate fails, cancel any pending reembed job that 075 may
60
+ // have enqueued in this same startup sweep. Leave 'running' jobs
61
+ // alone — the worker is already mid-flight and canceling would orphan
62
+ // its state. Only 'pending' jobs are safe to delete here.
63
+ if (
64
+ isMemoryV2Disabled(workspaceDir) ||
65
+ !hasConceptPages(workspaceDir)
66
+ ) {
67
+ db.query(
68
+ `DELETE FROM memory_jobs WHERE type='memory_v2_reembed' AND status='pending'`,
69
+ ).run();
70
+ return;
71
+ }
72
+
73
+ const existing = db
74
+ .query(
75
+ `SELECT id FROM memory_jobs WHERE type='memory_v2_reembed' AND status IN ('pending','running') LIMIT 1`,
76
+ )
77
+ .get();
78
+ if (existing) return;
79
+
80
+ const now = Date.now();
81
+ db.query(
82
+ `INSERT INTO memory_jobs
83
+ (id, type, payload, status, attempts, deferrals, run_after, last_error, created_at, updated_at)
84
+ VALUES (?, 'memory_v2_reembed', '{}', 'pending', 0, 0, ?, NULL, ?, ?)`,
85
+ ).run(randomUUID(), now, now, now);
86
+ } finally {
87
+ db.close();
88
+ }
89
+ },
90
+
91
+ down(_workspaceDir: string): void {
92
+ // Forward-only: the reembed is a one-shot data refresh.
93
+ },
94
+ };
95
+
96
+ /**
97
+ * Returns true only when `memory.v2.enabled` is explicitly set to `false`
98
+ * in the workspace `config.json`. Missing/unparseable config falls through
99
+ * to the schema default (enabled), matching `MemoryV2ConfigSchema`.
100
+ */
101
+ function isMemoryV2Disabled(workspaceDir: string): boolean {
102
+ const configPath = join(workspaceDir, "config.json");
103
+ if (!existsSync(configPath)) return false;
104
+ try {
105
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
106
+ const memory = (raw as { memory?: { v2?: { enabled?: unknown } } })?.memory;
107
+ return memory?.v2?.enabled === false;
108
+ } catch {
109
+ return false;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Returns true when `memory/concepts/` contains any `.md` file. Walks the
115
+ * tree iteratively so we bail on the first hit — pages can be nested in
116
+ * subdirectories (e.g. `memory/concepts/people/alice.md`).
117
+ */
118
+ function hasConceptPages(workspaceDir: string): boolean {
119
+ const stack = [join(workspaceDir, "memory", "concepts")];
120
+ while (stack.length > 0) {
121
+ const dir = stack.pop()!;
122
+ let entries;
123
+ try {
124
+ entries = readdirSync(dir, { withFileTypes: true });
125
+ } catch {
126
+ continue;
127
+ }
128
+ for (const entry of entries) {
129
+ if (entry.isDirectory()) {
130
+ stack.push(join(dir, entry.name));
131
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
132
+ return true;
133
+ }
134
+ }
135
+ }
136
+ return false;
137
+ }
@@ -0,0 +1,198 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import type { WorkspaceMigration } from "./types.js";
5
+
6
+ /**
7
+ * Revert mis-rewrites by migration 057 where a non-Gemini fragment was
8
+ * incorrectly treated as Gemini.
9
+ *
10
+ * Migration 057's `inferProvider` helper hardcodes `model === "gemini-3-flash"`
11
+ * as Gemini even when no `provider` is set, but `resolveCallSiteConfig` only
12
+ * infers providers via the catalog and `gemini-3-flash` is not a catalog
13
+ * entry. The runtime resolver therefore leaves such fragments inheriting from
14
+ * lower layers, so a workspace like `llm.default = {provider: "ollama"}` with
15
+ * `llm.callSites.recall = {model: "gemini-3-flash"}` (a user-named local
16
+ * model) resolves as Ollama at runtime — but 057 rewrites that recall model
17
+ * to `gemini-3-flash-preview`, flipping the catalog-based provider inference
18
+ * to Gemini and corrupting the user's intent.
19
+ *
20
+ * For each call-site or profile fragment that currently looks like a 057
21
+ * rewrite output (model in the replacement set, no explicit `provider`), this
22
+ * migration recomputes the effective provider via proper catalog-based
23
+ * inference using only the other layers. If that effective provider is
24
+ * explicitly non-Gemini, the fragment's model is reverted to
25
+ * `gemini-3-flash`, restoring the user's pre-057 state.
26
+ */
27
+ export const revertStaleGeminiMisRewritesMigration: WorkspaceMigration = {
28
+ id: "086-revert-stale-gemini-mis-rewrites",
29
+ description:
30
+ "Revert 057 mis-rewrites of gemini-3-flash in non-Gemini fragment contexts",
31
+ run(workspaceDir: string): void {
32
+ const configPath = join(workspaceDir, "config.json");
33
+ if (!existsSync(configPath)) return;
34
+
35
+ let config: Record<string, unknown>;
36
+ try {
37
+ const raw = JSON.parse(readFileSync(configPath, "utf-8"));
38
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return;
39
+ config = raw as Record<string, unknown>;
40
+ } catch {
41
+ return;
42
+ }
43
+
44
+ const llm = readObject(config.llm);
45
+ if (llm === null) return;
46
+
47
+ const defaultBlock = readObject(llm.default);
48
+ const defaultProvider = inferLayerProvider(defaultBlock);
49
+ const profiles = readObject(llm.profiles);
50
+ const activeProfileName =
51
+ typeof llm.activeProfile === "string" ? llm.activeProfile : undefined;
52
+
53
+ let changed = false;
54
+
55
+ // Pass 1: revert profile candidates first so that pass 2's call-site
56
+ // evaluation sees the reverted profile state. A call-site that references
57
+ // a profile candidate would otherwise infer `siteProfileProvider = gemini`
58
+ // from the profile's pre-revert rewritten model, masking the call-site's
59
+ // own need to revert.
60
+ if (profiles !== null) {
61
+ for (const rawProfile of Object.values(profiles)) {
62
+ const profile = readObject(rawProfile);
63
+ if (profile === null) continue;
64
+ if (!isRevertCandidate(profile)) continue;
65
+ if (isExplicitlyNonGemini(defaultProvider)) {
66
+ profile.model = STALE_MODEL;
67
+ changed = true;
68
+ }
69
+ }
70
+ }
71
+
72
+ // Compute the active-profile provider after pass 1 so it reflects any
73
+ // reversion of the active profile itself.
74
+ const activeProfileBlock =
75
+ profiles !== null && activeProfileName !== undefined
76
+ ? readObject(profiles[activeProfileName])
77
+ : null;
78
+ const activeProfileProvider = inferLayerProvider(activeProfileBlock);
79
+
80
+ // Pass 2: call-site candidates. `inferLayerProvider` is re-read from
81
+ // `profiles` per call site, so it observes pass 1's reversions.
82
+ const callSites = readObject(llm.callSites);
83
+ if (callSites !== null) {
84
+ for (const [site, rawConfig] of Object.entries(callSites)) {
85
+ const callSiteConfig = readObject(rawConfig);
86
+ if (callSiteConfig === null) continue;
87
+ if (!isRevertCandidate(callSiteConfig)) continue;
88
+ const siteProfileName =
89
+ typeof callSiteConfig.profile === "string"
90
+ ? callSiteConfig.profile
91
+ : undefined;
92
+ const siteProfileBlock =
93
+ profiles !== null && siteProfileName !== undefined
94
+ ? readObject(profiles[siteProfileName])
95
+ : null;
96
+ const siteProfileProvider = inferLayerProvider(siteProfileBlock);
97
+
98
+ const effective = effectiveProviderExcludingSite({
99
+ callSite: site,
100
+ siteProfileProvider,
101
+ activeProfileProvider,
102
+ defaultProvider,
103
+ });
104
+ if (isExplicitlyNonGemini(effective)) {
105
+ callSiteConfig.model = STALE_MODEL;
106
+ changed = true;
107
+ }
108
+ }
109
+ }
110
+
111
+ if (!changed) return;
112
+
113
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
114
+ },
115
+ down(_workspaceDir: string): void {
116
+ // Forward-only: re-applying the broken rewrite would reintroduce the bug.
117
+ },
118
+ };
119
+
120
+ // ---------------------------------------------------------------------------
121
+ // Helpers — self-contained per workspace migrations AGENTS.md
122
+ // ---------------------------------------------------------------------------
123
+
124
+ const STALE_MODEL = "gemini-3-flash";
125
+
126
+ // Models 057 writes when rewriting. Any fragment now holding one of these
127
+ // values without an explicit provider is a potential mis-rewrite candidate.
128
+ const REPLACEMENT_MODELS = new Set<string>([
129
+ "gemini-3-flash-preview",
130
+ "gemini-3.1-flash-lite-preview",
131
+ ]);
132
+
133
+ // Subset of the Gemini provider catalog used for per-layer catalog inference.
134
+ // Mirrors `getCatalogProviderForModel` for Gemini entries only — for other
135
+ // providers we treat a bare model as "provider unknown" and fall through to
136
+ // lower layers, which is safe because the revert only fires when at least one
137
+ // layer below carries an explicit non-Gemini provider.
138
+ const GEMINI_CATALOG_MODELS = new Set<string>([
139
+ "gemini-3.1-pro-preview",
140
+ "gemini-3.1-pro-preview-customtools",
141
+ "gemini-3-flash-preview",
142
+ "gemini-3.1-flash-lite-preview",
143
+ "gemini-2.5-flash",
144
+ "gemini-2.5-flash-lite",
145
+ "gemini-2.5-pro",
146
+ ]);
147
+
148
+ function isRevertCandidate(block: Record<string, unknown>): boolean {
149
+ if (typeof block.provider === "string") return false;
150
+ return typeof block.model === "string" && REPLACEMENT_MODELS.has(block.model);
151
+ }
152
+
153
+ function inferLayerProvider(
154
+ block: Record<string, unknown> | null,
155
+ ): string | undefined {
156
+ if (block === null) return undefined;
157
+ if (typeof block.provider === "string") return block.provider;
158
+ if (
159
+ typeof block.model === "string" &&
160
+ GEMINI_CATALOG_MODELS.has(block.model)
161
+ ) {
162
+ return "gemini";
163
+ }
164
+ return undefined;
165
+ }
166
+
167
+ function isExplicitlyNonGemini(provider: string | undefined): boolean {
168
+ return provider !== undefined && provider !== "gemini";
169
+ }
170
+
171
+ // Mirrors `resolveCallSiteConfig`'s layered provider resolution, omitting the
172
+ // candidate call-site fragment itself (its provider is undefined and its
173
+ // model is the replacement value, so including it would always say "gemini"
174
+ // via catalog inference — defeating the revert check).
175
+ function effectiveProviderExcludingSite(args: {
176
+ callSite: string;
177
+ siteProfileProvider: string | undefined;
178
+ activeProfileProvider: string | undefined;
179
+ defaultProvider: string | undefined;
180
+ }): string | undefined {
181
+ const {
182
+ callSite,
183
+ siteProfileProvider,
184
+ activeProfileProvider,
185
+ defaultProvider,
186
+ } = args;
187
+ if (callSite === "mainAgent") {
188
+ return activeProfileProvider ?? siteProfileProvider ?? defaultProvider;
189
+ }
190
+ return siteProfileProvider ?? activeProfileProvider ?? defaultProvider;
191
+ }
192
+
193
+ function readObject(value: unknown): Record<string, unknown> | null {
194
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
195
+ return null;
196
+ }
197
+ return value as Record<string, unknown>;
198
+ }
@@ -80,6 +80,10 @@ import { homeFeedNotificationOnlyMigration } from "./079-home-feed-notification-
80
80
  import { restrictVercelApiTokenMetadataMigration } from "./080-restrict-vercel-api-token-metadata.js";
81
81
  import { backfillBashAllowedToolsForInjectionCredentialsMigration } from "./081-backfill-bash-allowed-tools-for-injection-credentials.js";
82
82
  import { backfillManagedProfileLabelsMigration } from "./082-backfill-managed-profile-labels.js";
83
+ import { systemPromptPrefixToFileMigration } from "./083-system-prompt-prefix-to-file.js";
84
+ import { removeLegacySkillsIndexMigration } from "./084-remove-legacy-skills-index.js";
85
+ import { memoryV2Bm25BReembedDisabledV2PagesMigration } from "./085-memory-v2-bm25-b-reembed-disabled-v2-pages.js";
86
+ import { revertStaleGeminiMisRewritesMigration } from "./086-revert-stale-gemini-mis-rewrites.js";
83
87
  import { migrateToWorkspaceVolumeMigration } from "./migrate-to-workspace-volume.js";
84
88
  import type { WorkspaceMigration } from "./types.js";
85
89
 
@@ -171,4 +175,8 @@ export const WORKSPACE_MIGRATIONS: WorkspaceMigration[] = [
171
175
  restrictVercelApiTokenMetadataMigration,
172
176
  backfillBashAllowedToolsForInjectionCredentialsMigration,
173
177
  backfillManagedProfileLabelsMigration,
178
+ systemPromptPrefixToFileMigration,
179
+ removeLegacySkillsIndexMigration,
180
+ memoryV2Bm25BReembedDisabledV2PagesMigration,
181
+ revertStaleGeminiMisRewritesMigration,
174
182
  ];