@vellumai/assistant 0.6.2 → 0.6.3

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 (396) hide show
  1. package/bun.lock +40 -40
  2. package/bunfig.toml +3 -0
  3. package/docs/architecture/memory.md +1 -1
  4. package/node_modules/@vellumai/ces-contracts/src/rpc.ts +42 -0
  5. package/openapi.yaml +184 -69
  6. package/package.json +41 -41
  7. package/scripts/generate-openapi.ts +1 -2
  8. package/src/__tests__/acp-session.test.ts +43 -0
  9. package/src/__tests__/app-builder-tool-scripts.test.ts +1 -0
  10. package/src/__tests__/app-executors.test.ts +1 -0
  11. package/src/__tests__/app-source-watcher.test.ts +37 -11
  12. package/src/__tests__/approval-routes-http.test.ts +178 -1
  13. package/src/__tests__/browser-fill-credential.test.ts +229 -94
  14. package/src/__tests__/browser-manager.test.ts +40 -27
  15. package/src/__tests__/catalog-files.test.ts +862 -0
  16. package/src/__tests__/channel-approvals.test.ts +53 -0
  17. package/src/__tests__/config-managed-gemini-defaults.test.ts +326 -0
  18. package/src/__tests__/config-schema-cmd.test.ts +2 -2
  19. package/src/__tests__/config-schema.test.ts +125 -48
  20. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +23 -0
  21. package/src/__tests__/context-overflow-approval.test.ts +16 -1
  22. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  23. package/src/__tests__/conversation-agent-loop.test.ts +1 -1
  24. package/src/__tests__/conversation-analysis-routes.test.ts +2 -2
  25. package/src/__tests__/conversation-attachments.test.ts +80 -4
  26. package/src/__tests__/conversation-confirmation-signals.test.ts +155 -0
  27. package/src/__tests__/conversation-fork-crud.test.ts +17 -0
  28. package/src/__tests__/conversation-history-web-search.test.ts +1 -0
  29. package/src/__tests__/conversation-host-access-routes.test.ts +229 -0
  30. package/src/__tests__/conversation-inject-context.test.ts +103 -0
  31. package/src/__tests__/conversation-queue.test.ts +45 -2
  32. package/src/__tests__/conversation-routes-disk-view.test.ts +5 -0
  33. package/src/__tests__/conversation-routes-guardian-reply.test.ts +16 -0
  34. package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -0
  35. package/src/__tests__/conversation-runtime-assembly.test.ts +269 -46
  36. package/src/__tests__/conversation-starter-routes.test.ts +126 -0
  37. package/src/__tests__/conversation-starters-cadence.test.ts +161 -0
  38. package/src/__tests__/conversation-store.test.ts +195 -0
  39. package/src/__tests__/conversation-workspace-cache-state.test.ts +193 -0
  40. package/src/__tests__/credential-execution-approval-bridge.test.ts +32 -1
  41. package/src/__tests__/credential-security-invariants.test.ts +1 -0
  42. package/src/__tests__/credential-vault-unit.test.ts +4 -4
  43. package/src/__tests__/credential-vault.test.ts +152 -13
  44. package/src/__tests__/credentials-cli.test.ts +2 -2
  45. package/src/__tests__/date-context.test.ts +4 -4
  46. package/src/__tests__/embedding-managed-proxy-selection.test.ts +256 -0
  47. package/src/__tests__/extension-id-sync-guard.test.ts +155 -0
  48. package/src/__tests__/fixtures/mock-chrome-extension.ts +375 -0
  49. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  50. package/src/__tests__/gemini-provider.test.ts +2 -2
  51. package/src/__tests__/guardian-routing-invariants.test.ts +70 -2
  52. package/src/__tests__/headless-browser-interactions.test.ts +707 -371
  53. package/src/__tests__/headless-browser-navigate.test.ts +389 -47
  54. package/src/__tests__/headless-browser-read-tools.test.ts +266 -103
  55. package/src/__tests__/headless-browser-snapshot.test.ts +240 -77
  56. package/src/__tests__/host-bash-proxy.test.ts +150 -1
  57. package/src/__tests__/host-browser-e2e-cloud.test.ts +462 -0
  58. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +286 -0
  59. package/src/__tests__/host-browser-e2e-self-hosted.test.ts +374 -0
  60. package/src/__tests__/host-browser-event-routes.test.ts +350 -0
  61. package/src/__tests__/host-browser-proxy.test.ts +444 -0
  62. package/src/__tests__/host-browser-routes.test.ts +198 -0
  63. package/src/__tests__/host-browser-ws-events-e2e.test.ts +320 -0
  64. package/src/__tests__/host-cu-proxy.test.ts +171 -1
  65. package/src/__tests__/host-file-proxy.test.ts +185 -1
  66. package/src/__tests__/host-file-read-tool.test.ts +52 -0
  67. package/src/__tests__/host-proxy-interface.test.ts +165 -0
  68. package/src/__tests__/host-shell-tool.test.ts +1 -11
  69. package/src/__tests__/http-user-message-parity.test.ts +1 -0
  70. package/src/__tests__/integration-status.test.ts +6 -7
  71. package/src/__tests__/list-messages-tool-merge.test.ts +37 -12
  72. package/src/__tests__/mcp-client-auth.test.ts +40 -4
  73. package/src/__tests__/mcp-health-check.test.ts +10 -3
  74. package/src/__tests__/migration-cross-version-compatibility.test.ts +3 -1
  75. package/src/__tests__/migration-export-http.test.ts +61 -2
  76. package/src/__tests__/migration-export-streaming.test.ts +66 -0
  77. package/src/__tests__/migration-import-commit-http.test.ts +101 -1
  78. package/src/__tests__/native-host-marker-sync-guard.test.ts +157 -0
  79. package/src/__tests__/oauth-apps-routes.test.ts +17 -12
  80. package/src/__tests__/oauth-cli.test.ts +707 -60
  81. package/src/__tests__/oauth-connect-orchestrator.test.ts +116 -24
  82. package/src/__tests__/oauth-provider-seed-logos.test.ts +23 -0
  83. package/src/__tests__/oauth-provider-serializer.test.ts +146 -10
  84. package/src/__tests__/oauth-provider-visibility.test.ts +19 -21
  85. package/src/__tests__/oauth-providers-routes.test.ts +50 -14
  86. package/src/__tests__/oauth-store.test.ts +1386 -182
  87. package/src/__tests__/oauth2-gateway-transport.test.ts +211 -20
  88. package/src/__tests__/onboarding-template-contract.test.ts +75 -57
  89. package/src/__tests__/openai-provider.test.ts +2 -2
  90. package/src/__tests__/outlook-categories.test.ts +1 -1
  91. package/src/__tests__/outlook-client-automation.test.ts +1 -1
  92. package/src/__tests__/outlook-compose-tools.test.ts +1 -1
  93. package/src/__tests__/outlook-email-watcher.test.ts +1 -1
  94. package/src/__tests__/outlook-follow-up.test.ts +1 -1
  95. package/src/__tests__/outlook-messaging-provider.test.ts +2 -2
  96. package/src/__tests__/outlook-trash.test.ts +1 -1
  97. package/src/__tests__/outlook-unsubscribe.test.ts +1 -1
  98. package/src/__tests__/permission-checker-host-gate.test.ts +74 -14
  99. package/src/__tests__/permission-mode.test.ts +28 -56
  100. package/src/__tests__/platform-callback-registration.test.ts +19 -0
  101. package/src/__tests__/post-turn-tool-result-truncation.test.ts +296 -0
  102. package/src/__tests__/proxy-approval-callback.test.ts +18 -0
  103. package/src/__tests__/require-fresh-approval.test.ts +40 -1
  104. package/src/__tests__/sanitize-config-for-transfer.test.ts +132 -0
  105. package/src/__tests__/schedule-routes.test.ts +162 -0
  106. package/src/__tests__/secret-detection-handler.test.ts +84 -0
  107. package/src/__tests__/secret-ingress-http.test.ts +1 -0
  108. package/src/__tests__/send-endpoint-busy.test.ts +3 -0
  109. package/src/__tests__/set-permission-mode.test.ts +13 -250
  110. package/src/__tests__/skills-file-content-endpoint.test.ts +670 -0
  111. package/src/__tests__/skills-files-catalog-fallback.test.ts +450 -0
  112. package/src/__tests__/slack-channel-config.test.ts +12 -15
  113. package/src/__tests__/subagent-detail.test.ts +44 -2
  114. package/src/__tests__/subagent-disposal.test.ts +1 -0
  115. package/src/__tests__/subagent-fork-notifications.test.ts +291 -0
  116. package/src/__tests__/subagent-fork-spawn.test.ts +384 -0
  117. package/src/__tests__/subagent-manager-notify.test.ts +1 -0
  118. package/src/__tests__/subagent-notify-parent.test.ts +1 -0
  119. package/src/__tests__/subagent-spawn-tool-fork.test.ts +411 -0
  120. package/src/__tests__/subagent-tools.test.ts +1 -0
  121. package/src/__tests__/subagent-types.test.ts +1 -0
  122. package/src/__tests__/system-prompt-ask-mode.test.ts +27 -71
  123. package/src/__tests__/system-prompt.test.ts +72 -1
  124. package/src/__tests__/task-scheduler.test.ts +32 -6
  125. package/src/__tests__/telegram-config.test.ts +10 -13
  126. package/src/__tests__/terminal-tools.test.ts +9 -0
  127. package/src/__tests__/tool-approval-handler.test.ts +73 -0
  128. package/src/__tests__/tool-side-effects-slack-dm.test.ts +22 -0
  129. package/src/__tests__/top-level-renderer.test.ts +73 -1
  130. package/src/__tests__/transport-hints-queue.test.ts +14 -29
  131. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +109 -0
  132. package/src/__tests__/v2-consent-policy.test.ts +103 -0
  133. package/src/acp/client-handler.ts +30 -4
  134. package/src/agent/loop.ts +12 -6
  135. package/src/approvals/guardian-request-resolvers.ts +21 -15
  136. package/src/browser-session/__tests__/manager.test.ts +297 -0
  137. package/src/browser-session/backends/cdp-inspect.ts +30 -0
  138. package/src/browser-session/backends/extension.ts +26 -0
  139. package/src/browser-session/backends/local.ts +24 -0
  140. package/src/browser-session/events.ts +164 -0
  141. package/src/browser-session/index.ts +27 -0
  142. package/src/browser-session/manager.ts +159 -0
  143. package/src/browser-session/types.ts +28 -0
  144. package/src/channels/__tests__/types.test.ts +134 -0
  145. package/src/channels/types.ts +53 -3
  146. package/src/cli/commands/browser-relay.ts +339 -409
  147. package/src/cli/commands/credentials.ts +3 -3
  148. package/src/cli/commands/email.ts +18 -13
  149. package/src/cli/commands/mcp.ts +16 -4
  150. package/src/cli/commands/oauth/__tests__/connect.test.ts +44 -44
  151. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +21 -21
  152. package/src/cli/commands/oauth/__tests__/mode.test.ts +17 -17
  153. package/src/cli/commands/oauth/__tests__/ping.test.ts +16 -16
  154. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +31 -33
  155. package/src/cli/commands/oauth/__tests__/providers-register.test.ts +329 -0
  156. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +116 -12
  157. package/src/cli/commands/oauth/__tests__/status.test.ts +10 -10
  158. package/src/cli/commands/oauth/__tests__/token.test.ts +7 -7
  159. package/src/cli/commands/oauth/apps.ts +7 -4
  160. package/src/cli/commands/oauth/connect.ts +6 -3
  161. package/src/cli/commands/oauth/disconnect.ts +1 -1
  162. package/src/cli/commands/oauth/providers.ts +200 -36
  163. package/src/cli/commands/oauth/shared.ts +5 -5
  164. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +259 -0
  165. package/src/cli/commands/platform/index.ts +107 -10
  166. package/src/cli/commands/usage.ts +10 -9
  167. package/src/cli/lib/daemon-credential-client.ts +4 -0
  168. package/src/cli/program.ts +1 -1
  169. package/src/config/bundled-skills/app-builder/SKILL.md +26 -249
  170. package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +105 -0
  171. package/src/config/bundled-skills/app-builder/references/INTERACTION_HOOKS.md +56 -0
  172. package/src/config/bundled-skills/app-builder/references/WIDGETS.md +125 -0
  173. package/src/config/bundled-skills/contacts/SKILL.md +3 -0
  174. package/src/config/bundled-skills/document/SKILL.md +4 -0
  175. package/src/config/bundled-skills/gmail/SKILL.md +1 -1
  176. package/src/config/bundled-skills/outlook/SKILL.md +7 -0
  177. package/src/config/bundled-skills/subagent/SKILL.md +21 -0
  178. package/src/config/bundled-skills/subagent/TOOLS.json +8 -4
  179. package/src/config/bundled-skills/tasks/SKILL.md +5 -0
  180. package/src/config/env-registry.ts +14 -0
  181. package/src/config/env.ts +21 -0
  182. package/src/config/feature-flag-registry.json +44 -5
  183. package/src/config/loader.ts +56 -1
  184. package/src/config/sanitize-for-transfer.ts +47 -0
  185. package/src/config/schema.ts +46 -5
  186. package/src/config/schemas/host-browser.ts +66 -0
  187. package/src/config/schemas/memory-lifecycle.ts +1 -1
  188. package/src/config/schemas/memory-retrieval.ts +103 -0
  189. package/src/config/schemas/security.ts +0 -6
  190. package/src/config/schemas/services.ts +8 -0
  191. package/src/config/types.ts +0 -1
  192. package/src/context/post-turn-tool-result-truncation.ts +176 -0
  193. package/src/context/window-manager.ts +19 -1
  194. package/src/credential-execution/approval-bridge.ts +49 -15
  195. package/src/daemon/__tests__/conversation-tool-setup.test.ts +186 -0
  196. package/src/daemon/app-source-watcher.ts +35 -0
  197. package/src/daemon/context-overflow-approval.ts +5 -0
  198. package/src/daemon/conversation-agent-loop-handlers.ts +17 -2
  199. package/src/daemon/conversation-agent-loop.ts +58 -24
  200. package/src/daemon/conversation-attachments.ts +40 -0
  201. package/src/daemon/conversation-process.ts +48 -1
  202. package/src/daemon/conversation-runtime-assembly.ts +118 -36
  203. package/src/daemon/conversation-surfaces.ts +37 -36
  204. package/src/daemon/conversation-tool-setup.ts +74 -8
  205. package/src/daemon/conversation-workspace.ts +12 -0
  206. package/src/daemon/conversation.ts +226 -8
  207. package/src/daemon/date-context.ts +10 -10
  208. package/src/daemon/first-greeting.ts +3 -2
  209. package/src/daemon/handlers/conversations.ts +9 -140
  210. package/src/daemon/handlers/shared.ts +58 -0
  211. package/src/daemon/handlers/skills.ts +232 -37
  212. package/src/daemon/host-bash-proxy.ts +48 -13
  213. package/src/daemon/host-browser-proxy.ts +191 -0
  214. package/src/daemon/host-cu-proxy.ts +36 -11
  215. package/src/daemon/host-file-proxy.ts +57 -9
  216. package/src/daemon/lifecycle.ts +65 -11
  217. package/src/daemon/message-protocol.ts +7 -0
  218. package/src/daemon/message-types/conversations.ts +55 -13
  219. package/src/daemon/message-types/host-browser.ts +100 -0
  220. package/src/daemon/message-types/messages.ts +5 -5
  221. package/src/daemon/message-types/skills.ts +10 -0
  222. package/src/daemon/message-types/subagents.ts +2 -0
  223. package/src/daemon/server.ts +92 -12
  224. package/src/daemon/tool-side-effects.ts +6 -0
  225. package/src/daemon/transport-hints.ts +5 -24
  226. package/src/inbound/platform-callback-registration.ts +18 -17
  227. package/src/mcp/client.ts +59 -24
  228. package/src/memory/app-store.ts +31 -1
  229. package/src/memory/conversation-crud.ts +23 -0
  230. package/src/memory/conversation-starters-cadence.ts +76 -0
  231. package/src/memory/conversation-title-service.ts +5 -2
  232. package/src/memory/db-init.ts +12 -0
  233. package/src/memory/embedding-backend.test.ts +75 -0
  234. package/src/memory/embedding-backend.ts +131 -5
  235. package/src/memory/embedding-gemini.test.ts +54 -0
  236. package/src/memory/embedding-gemini.ts +20 -9
  237. package/src/memory/embedding-local.ts +176 -17
  238. package/src/memory/graph/consolidation.ts +10 -23
  239. package/src/memory/graph/extraction-job.ts +15 -0
  240. package/src/memory/graph/retriever.ts +40 -22
  241. package/src/memory/graph/store.test.ts +7 -3
  242. package/src/memory/graph/store.ts +47 -12
  243. package/src/memory/llm-usage-store.ts +45 -4
  244. package/src/memory/migrations/213-oauth-providers-scope-separator.ts +13 -0
  245. package/src/memory/migrations/214-oauth-providers-refresh-url.ts +11 -0
  246. package/src/memory/migrations/215-oauth-providers-revoke.ts +14 -0
  247. package/src/memory/migrations/216-oauth-providers-token-auth-method.ts +30 -0
  248. package/src/memory/migrations/217-conversation-host-access.ts +40 -0
  249. package/src/memory/migrations/218-oauth-providers-logo-url.ts +11 -0
  250. package/src/memory/migrations/index.ts +6 -0
  251. package/src/memory/migrations/registry.ts +8 -0
  252. package/src/memory/schema/conversations.ts +1 -0
  253. package/src/memory/schema/oauth.ts +18 -13
  254. package/src/oauth/AGENTS.md +76 -0
  255. package/src/oauth/__tests__/identity-verifier.test.ts +24 -19
  256. package/src/oauth/__tests__/seed-providers-managed.test.ts +32 -0
  257. package/src/oauth/byo-connection.test.ts +8 -8
  258. package/src/oauth/byo-connection.ts +7 -7
  259. package/src/oauth/connect-orchestrator.ts +23 -21
  260. package/src/oauth/connect-types.ts +3 -3
  261. package/src/oauth/connection-resolver.test.ts +17 -4
  262. package/src/oauth/connection-resolver.ts +16 -16
  263. package/src/oauth/connection.ts +1 -1
  264. package/src/oauth/manual-token-connection.ts +13 -13
  265. package/src/oauth/oauth-store.ts +214 -100
  266. package/src/oauth/platform-connection.test.ts +3 -3
  267. package/src/oauth/platform-connection.ts +4 -4
  268. package/src/oauth/provider-serializer.ts +31 -5
  269. package/src/oauth/revoke.ts +76 -0
  270. package/src/oauth/seed-providers.ts +126 -87
  271. package/src/oauth/token-persistence.ts +1 -1
  272. package/src/permissions/permission-mode.ts +4 -11
  273. package/src/permissions/prompter.ts +13 -1
  274. package/src/permissions/v2-consent-policy.ts +87 -0
  275. package/src/prompts/system-prompt.ts +18 -21
  276. package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +3 -65
  277. package/src/prompts/templates/BOOTSTRAP.md +59 -105
  278. package/src/providers/anthropic/client.ts +1 -0
  279. package/src/providers/types.ts +1 -1
  280. package/src/runtime/AGENTS.md +23 -0
  281. package/src/runtime/__tests__/browser-extension-pair-routes.test.ts +715 -0
  282. package/src/runtime/__tests__/capability-tokens.test.ts +258 -0
  283. package/src/runtime/__tests__/chrome-extension-registry.test.ts +518 -0
  284. package/src/runtime/assistant-event-hub.ts +2 -2
  285. package/src/runtime/auth/__tests__/guard-tests.test.ts +1 -0
  286. package/src/runtime/auth/__tests__/middleware.test.ts +116 -1
  287. package/src/runtime/auth/__tests__/route-policy.test.ts +8 -0
  288. package/src/runtime/auth/middleware.ts +98 -0
  289. package/src/runtime/auth/route-policy.ts +6 -7
  290. package/src/runtime/capability-tokens.ts +414 -0
  291. package/src/runtime/channel-approvals.ts +18 -5
  292. package/src/runtime/chrome-extension-registry.ts +332 -0
  293. package/src/runtime/confirmation-request-guardian-bridge.ts +6 -0
  294. package/src/runtime/guardian-decision-types.ts +7 -0
  295. package/src/runtime/http-server.ts +425 -70
  296. package/src/runtime/migrations/__tests__/rebind-secrets-credentials.test.ts +172 -0
  297. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +276 -0
  298. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +162 -0
  299. package/src/runtime/migrations/migration-transport.ts +6 -0
  300. package/src/runtime/migrations/migration-wizard.ts +22 -2
  301. package/src/runtime/migrations/rebind-secrets-screen.ts +76 -15
  302. package/src/runtime/migrations/vbundle-builder.ts +145 -38
  303. package/src/runtime/migrations/vbundle-import-analyzer.ts +19 -0
  304. package/src/runtime/migrations/vbundle-importer.ts +55 -5
  305. package/src/runtime/pending-interactions.ts +29 -13
  306. package/src/runtime/routes/approval-routes.ts +90 -16
  307. package/src/runtime/routes/browser-cdp-routes.ts +229 -0
  308. package/src/runtime/routes/browser-extension-pair-routes.ts +497 -0
  309. package/src/runtime/routes/conversation-analysis-routes.ts +2 -1
  310. package/src/runtime/routes/conversation-management-routes.ts +108 -0
  311. package/src/runtime/routes/conversation-routes.ts +301 -27
  312. package/src/runtime/routes/conversation-starter-routes.ts +78 -16
  313. package/src/runtime/routes/guardian-action-routes.ts +24 -13
  314. package/src/runtime/routes/host-browser-routes.ts +279 -0
  315. package/src/runtime/routes/host-file-routes.ts +9 -1
  316. package/src/runtime/routes/identity-routes.ts +259 -16
  317. package/src/runtime/routes/log-export-routes.ts +42 -22
  318. package/src/runtime/routes/memory-item-routes.ts +1 -7
  319. package/src/runtime/routes/migration-routes.ts +87 -2
  320. package/src/runtime/routes/oauth-apps.ts +15 -17
  321. package/src/runtime/routes/oauth-providers.ts +4 -0
  322. package/src/runtime/routes/schedule-routes.ts +24 -11
  323. package/src/runtime/routes/settings-routes.ts +9 -97
  324. package/src/runtime/routes/skills-routes.ts +52 -2
  325. package/src/runtime/routes/subagents-routes.ts +14 -10
  326. package/src/runtime/routes/usage-routes.ts +8 -7
  327. package/src/runtime/routes/workspace-routes.test.ts +22 -0
  328. package/src/runtime/routes/workspace-routes.ts +8 -1
  329. package/src/runtime/routes/workspace-utils.ts +2 -0
  330. package/src/schedule/scheduler.ts +7 -5
  331. package/src/security/ces-credential-client.ts +20 -0
  332. package/src/security/ces-rpc-credential-backend.ts +17 -0
  333. package/src/security/credential-backend.ts +5 -0
  334. package/src/security/oauth2.ts +42 -25
  335. package/src/security/secure-keys.ts +118 -25
  336. package/src/security/token-manager.ts +23 -10
  337. package/src/skills/catalog-files.ts +492 -0
  338. package/src/subagent/manager.ts +131 -26
  339. package/src/subagent/types.ts +19 -0
  340. package/src/tools/apps/executors.ts +11 -2
  341. package/src/tools/browser/__tests__/auth-detector.test.ts +202 -108
  342. package/src/tools/browser/auth-detector.ts +43 -12
  343. package/src/tools/browser/browser-execution.ts +645 -340
  344. package/src/tools/browser/browser-manager.ts +36 -12
  345. package/src/tools/browser/cdp-client/__tests__/accessibility-snapshot.test.ts +318 -0
  346. package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +1175 -0
  347. package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +870 -0
  348. package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +330 -0
  349. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +377 -0
  350. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-nested-frames.json +64 -0
  351. package/src/tools/browser/cdp-client/__tests__/fixtures/ax-tree-simple.json +69 -0
  352. package/src/tools/browser/cdp-client/__tests__/local-cdp-client.test.ts +310 -0
  353. package/src/tools/browser/cdp-client/__tests__/types.test.ts +96 -0
  354. package/src/tools/browser/cdp-client/accessibility-snapshot.ts +387 -0
  355. package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +695 -0
  356. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +743 -0
  357. package/src/tools/browser/cdp-client/cdp-inspect/__tests__/ws-transport.test.ts +580 -0
  358. package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +578 -0
  359. package/src/tools/browser/cdp-client/cdp-inspect/ws-transport.ts +579 -0
  360. package/src/tools/browser/cdp-client/cdp-inspect-client.ts +635 -0
  361. package/src/tools/browser/cdp-client/errors.ts +34 -0
  362. package/src/tools/browser/cdp-client/extension-cdp-client.ts +125 -0
  363. package/src/tools/browser/cdp-client/factory.ts +204 -0
  364. package/src/tools/browser/cdp-client/index.ts +14 -0
  365. package/src/tools/browser/cdp-client/local-cdp-client.ts +187 -0
  366. package/src/tools/browser/cdp-client/types.ts +52 -0
  367. package/src/tools/filesystem/edit.ts +1 -1
  368. package/src/tools/filesystem/list.ts +1 -1
  369. package/src/tools/filesystem/read.ts +1 -1
  370. package/src/tools/filesystem/write.ts +2 -1
  371. package/src/tools/host-filesystem/edit.ts +1 -1
  372. package/src/tools/host-filesystem/read.ts +12 -15
  373. package/src/tools/host-filesystem/write.ts +1 -1
  374. package/src/tools/host-terminal/host-shell.ts +21 -16
  375. package/src/tools/permission-checker.ts +77 -82
  376. package/src/tools/registry.ts +0 -2
  377. package/src/tools/secret-detection-handler.ts +34 -0
  378. package/src/tools/shared/filesystem/image-read.ts +61 -40
  379. package/src/tools/subagent/spawn.ts +47 -3
  380. package/src/tools/subagent/status.ts +2 -0
  381. package/src/tools/system/register.ts +2 -16
  382. package/src/tools/terminal/safe-env.ts +7 -0
  383. package/src/tools/terminal/shell.ts +21 -16
  384. package/src/tools/tool-approval-handler.ts +48 -2
  385. package/src/tools/types.ts +2 -0
  386. package/src/util/platform.ts +14 -19
  387. package/src/workspace/top-level-renderer.ts +19 -1
  388. package/src/__tests__/chrome-cdp.test.ts +0 -419
  389. package/src/__tests__/permission-mode-sse.test.ts +0 -418
  390. package/src/__tests__/permission-mode-store.test.ts +0 -277
  391. package/src/browser-extension-relay/protocol.ts +0 -63
  392. package/src/browser-extension-relay/server.ts +0 -203
  393. package/src/config/schemas/sandbox.ts +0 -14
  394. package/src/permissions/permission-mode-store.ts +0 -180
  395. package/src/tools/browser/chrome-cdp.ts +0 -239
  396. package/src/tools/system/set-permission-mode.ts +0 -103
@@ -1,16 +1,19 @@
1
1
  import { describe, expect, test } from "bun:test";
2
2
 
3
+ import { _setOverridesForTesting } from "../config/assistant-feature-flags.js";
3
4
  import type {
4
5
  ChannelCapabilities,
5
6
  UnifiedTurnContextOptions,
6
7
  } from "../daemon/conversation-runtime-assembly.js";
7
8
  import {
8
9
  applyRuntimeInjections,
10
+ buildSubagentStatusBlock,
9
11
  buildUnifiedTurnContextBlock,
10
12
  findLastInjectedNowContent,
11
13
  injectChannelCapabilityContext,
12
14
  injectChannelCommandContext,
13
15
  injectNowScratchpad,
16
+ injectSubagentStatus,
14
17
  isGroupChatType,
15
18
  resolveChannelCapabilities,
16
19
  stripChannelCapabilityContext,
@@ -18,6 +21,7 @@ import {
18
21
  stripNowScratchpad,
19
22
  } from "../daemon/conversation-runtime-assembly.js";
20
23
  import type { Message } from "../providers/types.js";
24
+ import type { SubagentState } from "../subagent/types.js";
21
25
 
22
26
  // ---------------------------------------------------------------------------
23
27
  // resolveChannelCapabilities
@@ -601,8 +605,10 @@ describe("applyRuntimeInjections — injection mode", () => {
601
605
  supportsVoiceInput: false,
602
606
  } as ChannelCapabilities,
603
607
  unifiedTurnContext:
604
- "<turn_context>\ntimestamp: 2026-03-04 (Tue) 12:00:00 +00:00 (UTC)\ninterface: telegram\n</turn_context>",
608
+ "<turn_context>\ncurrent_time: 2026-03-04 (Tuesday) 12:00:00 +00:00 (UTC)\ninterface: telegram\n</turn_context>",
605
609
  nowScratchpad: "Current focus: shipping PR 3",
610
+ pkbContext: "essentials content here",
611
+ pkbActive: true,
606
612
  isNonInteractive: true,
607
613
  };
608
614
 
@@ -620,6 +626,8 @@ describe("applyRuntimeInjections — injection mode", () => {
620
626
  expect(allText).toContain("<turn_context>");
621
627
  expect(allText).toContain("<non_interactive_context>");
622
628
  expect(allText).toContain("<NOW.md");
629
+ expect(allText).toContain("<system_reminder>");
630
+ expect(allText).toContain("<pkb>");
623
631
  });
624
632
 
625
633
  test("explicit mode: 'full' behaves the same as default", () => {
@@ -653,6 +661,8 @@ describe("applyRuntimeInjections — injection mode", () => {
653
661
  expect(allText).not.toContain("<channel_command_context>");
654
662
  expect(allText).not.toContain("<active_workspace>");
655
663
  expect(allText).not.toContain("<NOW.md");
664
+ expect(allText).not.toContain("<system_reminder>");
665
+ expect(allText).not.toContain("<pkb>");
656
666
  });
657
667
 
658
668
  test("minimal mode preserves safety-critical blocks", () => {
@@ -889,7 +899,7 @@ describe("stripInjectionsForCompaction preserves persistent blocks", () => {
889
899
  content: [
890
900
  {
891
901
  type: "text",
892
- text: "<turn_context>\ntimestamp: 2026-04-02 (Thu) 01:52:33 -05:00 (America/Chicago)\ninterface: macos\n</turn_context>",
902
+ text: "<turn_context>\ncurrent_time: 2026-04-02 (Thursday) 01:52:33 -05:00 (America/Chicago)\ninterface: macos\n</turn_context>",
893
903
  },
894
904
  { type: "text", text: "Hello" },
895
905
  ],
@@ -1078,7 +1088,7 @@ describe("buildUnifiedTurnContextBlock", () => {
1078
1088
  const text = buildUnifiedTurnContextBlock(options);
1079
1089
  const lines = text.split("\n");
1080
1090
  expect(lines[0]).toBe("<turn_context>");
1081
- expect(lines[1]).toBe("timestamp: 2026-04-02T12:00:00Z");
1091
+ expect(lines[1]).toBe("current_time: 2026-04-02T12:00:00Z");
1082
1092
  expect(lines[2]).toBe("interface: macos");
1083
1093
  expect(lines[3]).toBe("</turn_context>");
1084
1094
  expect(lines).toHaveLength(4);
@@ -1089,46 +1099,89 @@ describe("buildUnifiedTurnContextBlock", () => {
1089
1099
  });
1090
1100
 
1091
1101
  test("non-guardian trusted_contact: all actor fields + behavioral guidance", () => {
1092
- const options: UnifiedTurnContextOptions = {
1093
- timestamp: "2026-04-02T12:00:00Z",
1094
- interfaceName: "telegram",
1095
- channelName: "telegram",
1096
- actorContext: {
1097
- sourceChannel: "telegram",
1098
- canonicalActorIdentity: "trusted-user-1",
1099
- actorIdentifier: "@jeff_handle",
1100
- actorDisplayName: "Jeff",
1101
- actorSenderDisplayName: "Jeffrey",
1102
- actorMemberDisplayName: "Jeff",
1103
- trustClass: "trusted_contact",
1104
- guardianIdentity: "guardian-user-1",
1105
- memberStatus: "active",
1106
- memberPolicy: "allow",
1107
- },
1108
- };
1109
-
1110
- const text = buildUnifiedTurnContextBlock(options);
1111
- expect(text).toContain("<turn_context>");
1112
- expect(text).toContain("timestamp: 2026-04-02T12:00:00Z");
1113
- expect(text).toContain("interface: telegram");
1114
- expect(text).toContain("source_channel: telegram");
1115
- expect(text).toContain("canonical_actor_identity: trusted-user-1");
1116
- expect(text).toContain("actor_identifier: @jeff_handle");
1117
- expect(text).toContain("actor_display_name: Jeff");
1118
- expect(text).toContain("actor_sender_display_name: Jeffrey");
1119
- expect(text).toContain("actor_member_display_name: Jeff");
1120
- expect(text).toContain("trust_class: trusted_contact");
1121
- expect(text).toContain("guardian_identity: guardian-user-1");
1122
- expect(text).toContain("member_status: active");
1123
- expect(text).toContain("member_policy: allow");
1124
- // Behavioral guidance
1125
- expect(text).toContain("trusted contact (non-guardian)");
1126
- expect(text).toContain("attempt to fulfill it normally");
1127
- expect(text).toContain(
1128
- "tool execution layer will automatically deny it and escalate",
1129
- );
1130
- expect(text).toContain('their name is "Jeff"');
1131
- expect(text).toContain("</turn_context>");
1102
+ _setOverridesForTesting({ "permission-controls-v2": false });
1103
+ try {
1104
+ const options: UnifiedTurnContextOptions = {
1105
+ timestamp: "2026-04-02T12:00:00Z",
1106
+ interfaceName: "telegram",
1107
+ channelName: "telegram",
1108
+ actorContext: {
1109
+ sourceChannel: "telegram",
1110
+ canonicalActorIdentity: "trusted-user-1",
1111
+ actorIdentifier: "@jeff_handle",
1112
+ actorDisplayName: "Jeff",
1113
+ actorSenderDisplayName: "Jeffrey",
1114
+ actorMemberDisplayName: "Jeff",
1115
+ trustClass: "trusted_contact",
1116
+ guardianIdentity: "guardian-user-1",
1117
+ memberStatus: "active",
1118
+ memberPolicy: "allow",
1119
+ },
1120
+ };
1121
+
1122
+ const text = buildUnifiedTurnContextBlock(options);
1123
+ expect(text).toContain("<turn_context>");
1124
+ expect(text).toContain("current_time: 2026-04-02T12:00:00Z");
1125
+ expect(text).toContain("interface: telegram");
1126
+ expect(text).toContain("source_channel: telegram");
1127
+ expect(text).toContain("canonical_actor_identity: trusted-user-1");
1128
+ expect(text).toContain("actor_identifier: @jeff_handle");
1129
+ expect(text).toContain("actor_display_name: Jeff");
1130
+ expect(text).toContain("actor_sender_display_name: Jeffrey");
1131
+ expect(text).toContain("actor_member_display_name: Jeff");
1132
+ expect(text).toContain("trust_class: trusted_contact");
1133
+ expect(text).toContain("guardian_identity: guardian-user-1");
1134
+ expect(text).toContain("member_status: active");
1135
+ expect(text).toContain("member_policy: allow");
1136
+ // Behavioral guidance
1137
+ expect(text).toContain("trusted contact (non-guardian)");
1138
+ expect(text).toContain("attempt to fulfill it normally");
1139
+ expect(text).toContain(
1140
+ "tool execution layer will automatically deny it and escalate",
1141
+ );
1142
+ expect(text).toContain('their name is "Jeff"');
1143
+ expect(text).toContain("</turn_context>");
1144
+ } finally {
1145
+ _setOverridesForTesting({});
1146
+ }
1147
+ });
1148
+
1149
+ test("non-guardian trusted_contact under v2: guidance shifts to conversational guardian confirmation", () => {
1150
+ _setOverridesForTesting({ "permission-controls-v2": true });
1151
+
1152
+ try {
1153
+ const options: UnifiedTurnContextOptions = {
1154
+ timestamp: "2026-04-02T12:00:00Z",
1155
+ interfaceName: "telegram",
1156
+ channelName: "telegram",
1157
+ actorContext: {
1158
+ sourceChannel: "telegram",
1159
+ canonicalActorIdentity: "trusted-user-1",
1160
+ actorIdentifier: "@jeff_handle",
1161
+ actorDisplayName: "Jeff",
1162
+ actorSenderDisplayName: "Jeffrey",
1163
+ actorMemberDisplayName: "Jeff",
1164
+ trustClass: "trusted_contact",
1165
+ guardianIdentity: "guardian-user-1",
1166
+ memberStatus: "active",
1167
+ memberPolicy: "allow",
1168
+ },
1169
+ };
1170
+
1171
+ const text = buildUnifiedTurnContextBlock(options);
1172
+ expect(text).toContain("trusted contact (non-guardian)");
1173
+ expect(text).toContain(
1174
+ "confirming the guardian's intent conversationally",
1175
+ );
1176
+ expect(text).toContain(
1177
+ "ask the guardian to enable computer access for this conversation",
1178
+ );
1179
+ expect(text).not.toContain(
1180
+ "tool execution layer will automatically deny it and escalate",
1181
+ );
1182
+ } finally {
1183
+ _setOverridesForTesting({});
1184
+ }
1132
1185
  });
1133
1186
 
1134
1187
  test("non-guardian unknown: all actor fields + unknown guidance", () => {
@@ -1145,7 +1198,7 @@ describe("buildUnifiedTurnContextBlock", () => {
1145
1198
 
1146
1199
  const text = buildUnifiedTurnContextBlock(options);
1147
1200
  expect(text).toContain("<turn_context>");
1148
- expect(text).toContain("timestamp: 2026-04-02T12:00:00Z");
1201
+ expect(text).toContain("current_time: 2026-04-02T12:00:00Z");
1149
1202
  expect(text).toContain("canonical_actor_identity: unknown");
1150
1203
  expect(text).toContain("trust_class: unknown");
1151
1204
  expect(text).toContain("non-guardian account");
@@ -1306,7 +1359,7 @@ describe("buildUnifiedTurnContextBlock", () => {
1306
1359
  expect(text).not.toContain("interface:");
1307
1360
  const lines = text.split("\n");
1308
1361
  expect(lines[0]).toBe("<turn_context>");
1309
- expect(lines[1]).toBe("timestamp: 2026-04-02T12:00:00Z");
1362
+ expect(lines[1]).toBe("current_time: 2026-04-02T12:00:00Z");
1310
1363
  expect(lines[2]).toBe("</turn_context>");
1311
1364
  });
1312
1365
 
@@ -1353,7 +1406,7 @@ describe("applyRuntimeInjections with unifiedTurnContext", () => {
1353
1406
  ];
1354
1407
 
1355
1408
  const sampleBlock =
1356
- "<turn_context>\ntimestamp: 2026-04-02T12:00:00Z\ninterface: macos\n</turn_context>";
1409
+ "<turn_context>\ncurrent_time: 2026-04-02T12:00:00Z\ninterface: macos\n</turn_context>";
1357
1410
 
1358
1411
  test("injects unifiedTurnContext when provided", () => {
1359
1412
  const result = applyRuntimeInjections(baseMessages, {
@@ -1494,3 +1547,173 @@ describe("findLastInjectedNowContent", () => {
1494
1547
  expect(findLastInjectedNowContent(messages)).toBe("User focus");
1495
1548
  });
1496
1549
  });
1550
+
1551
+ // ---------------------------------------------------------------------------
1552
+ // Subagent status injection
1553
+ // ---------------------------------------------------------------------------
1554
+
1555
+ function makeSubagentState(
1556
+ overrides: Partial<SubagentState> & { label: string; id: string },
1557
+ ): SubagentState {
1558
+ return {
1559
+ config: {
1560
+ id: overrides.id,
1561
+ parentConversationId: "parent-conv",
1562
+ label: overrides.label,
1563
+ objective: "test objective",
1564
+ ...overrides.config,
1565
+ },
1566
+ status: overrides.status ?? "running",
1567
+ conversationId: `conv-${overrides.id}`,
1568
+ isFork: overrides.isFork ?? false,
1569
+ createdAt: overrides.createdAt ?? Date.now() - 60_000,
1570
+ startedAt: overrides.startedAt ?? Date.now() - 55_000,
1571
+ completedAt: overrides.completedAt,
1572
+ error: overrides.error,
1573
+ usage: overrides.usage ?? {
1574
+ inputTokens: 0,
1575
+ outputTokens: 0,
1576
+ estimatedCost: 0,
1577
+ },
1578
+ };
1579
+ }
1580
+
1581
+ describe("buildSubagentStatusBlock", () => {
1582
+ test("returns null for empty children array", () => {
1583
+ expect(buildSubagentStatusBlock([])).toBeNull();
1584
+ });
1585
+
1586
+ test("formats running subagent with elapsed time", () => {
1587
+ const children = [
1588
+ makeSubagentState({
1589
+ id: "abc-123",
1590
+ label: "research-auth",
1591
+ status: "running",
1592
+ }),
1593
+ ];
1594
+ const block = buildSubagentStatusBlock(children)!;
1595
+ expect(block).toContain("<active_subagents>");
1596
+ expect(block).toContain("</active_subagents>");
1597
+ expect(block).toContain('[running] "research-auth" (abc-123)');
1598
+ expect(block).toContain("elapsed:");
1599
+ expect(block).toContain("subagent_read");
1600
+ });
1601
+
1602
+ test("formats pending subagent without elapsed time for terminal", () => {
1603
+ const children = [
1604
+ makeSubagentState({
1605
+ id: "def-456",
1606
+ label: "plan-feature",
1607
+ status: "completed",
1608
+ completedAt: Date.now(),
1609
+ }),
1610
+ ];
1611
+ const block = buildSubagentStatusBlock(children)!;
1612
+ expect(block).toContain('[completed] "plan-feature" (def-456)');
1613
+ expect(block).not.toContain("elapsed:");
1614
+ });
1615
+
1616
+ test("includes error for failed subagent", () => {
1617
+ const children = [
1618
+ makeSubagentState({
1619
+ id: "ghi-789",
1620
+ label: "run-tests",
1621
+ status: "failed",
1622
+ error: "Process exited with code 1",
1623
+ }),
1624
+ ];
1625
+ const block = buildSubagentStatusBlock(children)!;
1626
+ expect(block).toContain('[failed] "run-tests" (ghi-789)');
1627
+ expect(block).toContain("error: Process exited with code 1");
1628
+ });
1629
+
1630
+ test("includes both active and terminal subagents", () => {
1631
+ const children = [
1632
+ makeSubagentState({ id: "a", label: "researcher", status: "running" }),
1633
+ makeSubagentState({ id: "b", label: "coder", status: "completed" }),
1634
+ makeSubagentState({
1635
+ id: "c",
1636
+ label: "planner",
1637
+ status: "failed",
1638
+ error: "timeout",
1639
+ }),
1640
+ ];
1641
+ const block = buildSubagentStatusBlock(children)!;
1642
+ expect(block).toContain('"researcher"');
1643
+ expect(block).toContain('"coder"');
1644
+ expect(block).toContain('"planner"');
1645
+ });
1646
+ });
1647
+
1648
+ describe("injectSubagentStatus", () => {
1649
+ test("appends status block to user message", () => {
1650
+ const msg: Message = {
1651
+ role: "user",
1652
+ content: [{ type: "text", text: "hello" }],
1653
+ };
1654
+ const result = injectSubagentStatus(
1655
+ msg,
1656
+ "<active_subagents>\ntest\n</active_subagents>",
1657
+ );
1658
+ expect(result.content).toHaveLength(2);
1659
+ expect(
1660
+ (result.content[1] as { type: string; text: string }).text,
1661
+ ).toContain("<active_subagents>");
1662
+ });
1663
+ });
1664
+
1665
+ describe("applyRuntimeInjections — subagent status", () => {
1666
+ const userMsg: Message = {
1667
+ role: "user",
1668
+ content: [{ type: "text", text: "user message" }],
1669
+ };
1670
+
1671
+ test("includes subagent status in full mode", () => {
1672
+ const result = applyRuntimeInjections([userMsg], {
1673
+ subagentStatusBlock:
1674
+ "<active_subagents>\n- [running] test\n</active_subagents>",
1675
+ mode: "full",
1676
+ });
1677
+ const tail = result[result.length - 1];
1678
+ const texts = tail.content
1679
+ .filter((b): b is { type: "text"; text: string } => b.type === "text")
1680
+ .map((b) => b.text);
1681
+ expect(texts.some((t) => t.includes("<active_subagents>"))).toBe(true);
1682
+ });
1683
+
1684
+ test("skips subagent status in minimal mode", () => {
1685
+ const result = applyRuntimeInjections([userMsg], {
1686
+ subagentStatusBlock:
1687
+ "<active_subagents>\n- [running] test\n</active_subagents>",
1688
+ mode: "minimal",
1689
+ });
1690
+ const tail = result[result.length - 1];
1691
+ const texts = tail.content
1692
+ .filter((b): b is { type: "text"; text: string } => b.type === "text")
1693
+ .map((b) => b.text);
1694
+ expect(texts.some((t) => t.includes("<active_subagents>"))).toBe(false);
1695
+ });
1696
+ });
1697
+
1698
+ describe("stripInjectionsForCompaction — subagent status", () => {
1699
+ test("strips <active_subagents> blocks", () => {
1700
+ const messages: Message[] = [
1701
+ {
1702
+ role: "user",
1703
+ content: [
1704
+ { type: "text", text: "hello" },
1705
+ {
1706
+ type: "text",
1707
+ text: '<active_subagents>\n- [running] "test" (id)\n</active_subagents>',
1708
+ },
1709
+ ],
1710
+ },
1711
+ ];
1712
+ const result = stripInjectionsForCompaction(messages);
1713
+ const texts = result[0].content
1714
+ .filter((b): b is { type: "text"; text: string } => b.type === "text")
1715
+ .map((b) => b.text);
1716
+ expect(texts.some((t) => t.includes("<active_subagents>"))).toBe(false);
1717
+ expect(texts).toContain("hello");
1718
+ });
1719
+ });
@@ -11,6 +11,7 @@ mock.module("../util/logger.js", () => ({
11
11
 
12
12
  import { getSqlite, initializeDb } from "../memory/db.js";
13
13
  import {
14
+ CONVERSATION_STARTERS_STALE_TTL_MS,
14
15
  conversationStarterRouteDefinitions,
15
16
  orderStrongestFirst,
16
17
  } from "../runtime/routes/conversation-starter-routes.js";
@@ -89,22 +90,57 @@ function insertMemoryItem(scopeId = "default") {
89
90
  );
90
91
  }
91
92
 
93
+ function setCheckpoint(key: string, value: string, updatedAt = Date.now()) {
94
+ getSqlite().run(
95
+ `INSERT OR REPLACE INTO memory_checkpoints (key, value, updated_at) VALUES (?, ?, ?)`,
96
+ [key, value, updatedAt],
97
+ );
98
+ }
99
+
100
+ function insertStarterJob(scopeId = "default", status = "pending") {
101
+ const now = Date.now();
102
+ getSqlite().run(
103
+ `INSERT INTO memory_jobs (
104
+ id, type, payload, status, attempts, deferrals, run_after, last_error,
105
+ started_at, created_at, updated_at
106
+ ) VALUES (?, 'generate_conversation_starters', ?, ?, 0, 0, ?, NULL, NULL, ?, ?)`,
107
+ [uuid(), JSON.stringify({ scopeId }), status, now, now, now],
108
+ );
109
+ }
110
+
111
+ function countStarterJobs() {
112
+ return (
113
+ getSqlite()
114
+ .prepare(
115
+ `SELECT COUNT(*) AS c FROM memory_jobs WHERE type = 'generate_conversation_starters'`,
116
+ )
117
+ .get() as { c: number }
118
+ ).c;
119
+ }
120
+
92
121
  beforeEach(() => {
93
122
  clearTables();
94
123
  });
95
124
 
96
125
  describe("GET /v1/conversation-starters", () => {
97
126
  test("returns ready status with starters when they exist", async () => {
127
+ const now = Date.now();
98
128
  insertStarter({
99
129
  label: "Draft a PR summary",
100
130
  prompt: "Draft a summary for my latest PR",
101
131
  category: "development",
132
+ createdAt: now,
102
133
  });
103
134
  insertStarter({
104
135
  label: "Check Slack threads",
105
136
  prompt: "Check my unread Slack threads",
106
137
  category: "communication",
138
+ createdAt: now,
107
139
  });
140
+ setCheckpoint("conversation_starters:last_gen_at:default", String(now));
141
+ setCheckpoint("conversation_starters:item_count_at_last_gen:default", "2");
142
+ insertMemoryItem();
143
+ insertMemoryItem();
108
144
 
109
145
  const res = await dispatch("conversation-starters");
110
146
  const body = (await res.json()) as {
@@ -119,6 +155,96 @@ describe("GET /v1/conversation-starters", () => {
119
155
  expect(body.total).toBe(2);
120
156
  });
121
157
 
158
+ test("returns refreshing with existing starters when the batch is stale and enqueues one refresh job", async () => {
159
+ const now = Date.now();
160
+ insertStarter({
161
+ label: "Draft a PR summary",
162
+ prompt: "Draft a summary for my latest PR",
163
+ category: "development",
164
+ createdAt: now - 1_000,
165
+ });
166
+ insertStarter({
167
+ label: "Check Slack threads",
168
+ prompt: "Check my unread Slack threads",
169
+ category: "communication",
170
+ createdAt: now - 2_000,
171
+ });
172
+ setCheckpoint(
173
+ "conversation_starters:last_gen_at:default",
174
+ String(now - CONVERSATION_STARTERS_STALE_TTL_MS - 1_000),
175
+ );
176
+ setCheckpoint("conversation_starters:item_count_at_last_gen:default", "2");
177
+ insertMemoryItem();
178
+ insertMemoryItem();
179
+
180
+ const res = await dispatch("conversation-starters");
181
+ const body = (await res.json()) as {
182
+ starters: unknown[];
183
+ total: number;
184
+ status: string;
185
+ };
186
+
187
+ expect(res.status).toBe(200);
188
+ expect(body.status).toBe("refreshing");
189
+ expect(body.starters).toHaveLength(2);
190
+ expect(countStarterJobs()).toBe(1);
191
+ });
192
+
193
+ test("returns refreshing with existing starters when the checkpoint count is ahead of active memory and enqueues one refresh job", async () => {
194
+ const now = Date.now();
195
+ insertStarter({
196
+ label: "Draft a PR summary",
197
+ prompt: "Draft a summary for my latest PR",
198
+ category: "development",
199
+ createdAt: now,
200
+ });
201
+ setCheckpoint("conversation_starters:last_gen_at:default", String(now));
202
+ setCheckpoint("conversation_starters:item_count_at_last_gen:default", "5");
203
+ insertMemoryItem();
204
+ insertMemoryItem();
205
+
206
+ const res = await dispatch("conversation-starters");
207
+ const body = (await res.json()) as {
208
+ starters: unknown[];
209
+ total: number;
210
+ status: string;
211
+ };
212
+
213
+ expect(res.status).toBe(200);
214
+ expect(body.status).toBe("refreshing");
215
+ expect(body.starters).toHaveLength(1);
216
+ expect(countStarterJobs()).toBe(1);
217
+ });
218
+
219
+ test("does not enqueue duplicate refresh jobs when a starter job is already active", async () => {
220
+ const now = Date.now();
221
+ insertStarter({
222
+ label: "Draft a PR summary",
223
+ prompt: "Draft a summary for my latest PR",
224
+ category: "development",
225
+ createdAt: now,
226
+ });
227
+ setCheckpoint(
228
+ "conversation_starters:last_gen_at:default",
229
+ String(now - CONVERSATION_STARTERS_STALE_TTL_MS - 1_000),
230
+ );
231
+ setCheckpoint("conversation_starters:item_count_at_last_gen:default", "1");
232
+ insertMemoryItem();
233
+ insertStarterJob("default");
234
+
235
+ const res = await dispatch("conversation-starters");
236
+ const body = (await res.json()) as {
237
+ starters: unknown[];
238
+ total: number;
239
+ status: string;
240
+ };
241
+
242
+ expect(res.status).toBe(200);
243
+ expect(body.status).toBe("refreshing");
244
+ expect(body.starters).toHaveLength(1);
245
+ expect(countStarterJobs()).toBe(1);
246
+ });
247
+
122
248
  test("returns empty status when no memory items exist", async () => {
123
249
  const res = await dispatch("conversation-starters");
124
250
  const body = (await res.json()) as {