@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
@@ -0,0 +1,450 @@
1
+ /**
2
+ * Tests for `getSkillFiles` catalog fallback.
3
+ *
4
+ * When a skill id isn't resolvable via `findSkillById` (i.e. not installed
5
+ * locally, not bundled, not a managed skill), `getSkillFiles` falls back to
6
+ * the Vellum catalog via `readCatalogSkillFiles`. Catalog fallback entries
7
+ * carry `content: null` because the catalog-files helper defers content
8
+ * fetching to a follow-up per-file endpoint. When a skill IS resolved by
9
+ * `findSkillById` but its on-disk directory is missing, `getSkillFiles`
10
+ * returns a 404 without falling through to the catalog so the listing and
11
+ * detail responses agree on `isInstalled`.
12
+ *
13
+ * Coverage:
14
+ * - Uninstalled catalog skill: returns `{ skill: catalog/vellum/available, files }` with `content: null` for every entry.
15
+ * - Neither installed nor in catalog: returns 404.
16
+ * - Installed skill: preserves the disk-read behavior with inline `content`.
17
+ * - Installed skill with missing directory: returns 404 without consulting the catalog.
18
+ * - `catalogSkillToSlim` mapping: `metadata.vellum["display-name"]` wins over `cs.name`.
19
+ */
20
+
21
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
22
+ import { tmpdir } from "node:os";
23
+ import { join } from "node:path";
24
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
25
+
26
+ import type { SkillSummary } from "../config/skills.js";
27
+ import type { SkillFileEntry } from "../skills/catalog-files.js";
28
+ import type { CatalogSkill } from "../skills/catalog-install.js";
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Mock state — mutated by individual tests via reset helpers below
32
+ // ---------------------------------------------------------------------------
33
+
34
+ type ResolvedSkillEntry = {
35
+ summary: SkillSummary;
36
+ state: "enabled" | "disabled";
37
+ };
38
+
39
+ let mockResolvedStates: ResolvedSkillEntry[] = [];
40
+ let mockCatalog: CatalogSkill[] = [];
41
+ let mockCatalogFiles: SkillFileEntry[] | null = null;
42
+ const catalogFilesCalls: string[] = [];
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // Module mocks — must be declared before importing the module under test
46
+ // ---------------------------------------------------------------------------
47
+
48
+ mock.module("../util/logger.js", () => ({
49
+ getLogger: () =>
50
+ new Proxy({} as Record<string, unknown>, {
51
+ get: () => () => {},
52
+ }),
53
+ }));
54
+
55
+ mock.module("../config/loader.js", () => ({
56
+ getConfig: () => ({}),
57
+ invalidateConfigCache: () => {},
58
+ loadRawConfig: () => ({}),
59
+ saveRawConfig: () => {},
60
+ }));
61
+
62
+ mock.module("../config/skills.js", () => ({
63
+ loadSkillCatalog: () => [],
64
+ }));
65
+
66
+ mock.module("../config/skill-state.js", () => ({
67
+ resolveSkillStates: () => mockResolvedStates,
68
+ skillFlagKey: () => null,
69
+ }));
70
+
71
+ mock.module("../config/assistant-feature-flags.js", () => ({
72
+ isAssistantFeatureFlagEnabled: () => true,
73
+ }));
74
+
75
+ mock.module("../skills/install-meta.js", () => ({
76
+ readInstallMeta: () => null,
77
+ }));
78
+
79
+ mock.module("../skills/catalog-cache.js", () => ({
80
+ getCatalog: async () => mockCatalog,
81
+ }));
82
+
83
+ mock.module("../skills/catalog-files.js", () => ({
84
+ readCatalogSkillFiles: async (skillId: string) => {
85
+ catalogFilesCalls.push(skillId);
86
+ return mockCatalogFiles;
87
+ },
88
+ }));
89
+
90
+ mock.module("../skills/catalog-install.js", () => ({
91
+ installSkillLocally: async () => {},
92
+ upsertSkillsIndex: () => {},
93
+ }));
94
+
95
+ mock.module("../skills/catalog-search.js", () => ({
96
+ filterByQuery: () => [],
97
+ }));
98
+
99
+ mock.module("../skills/clawhub.js", () => ({
100
+ clawhubCheckUpdates: async () => [],
101
+ clawhubInspect: async () => ({}),
102
+ clawhubInstall: async () => ({ success: true }),
103
+ clawhubSearch: async () => ({ skills: [] }),
104
+ clawhubUpdate: async () => ({ success: true }),
105
+ }));
106
+
107
+ mock.module("../skills/skillssh-registry.js", () => ({
108
+ installExternalSkill: async () => {},
109
+ resolveSkillSource: () => ({ owner: "", repo: "", skillSlug: "" }),
110
+ searchSkillsRegistry: async () => [],
111
+ }));
112
+
113
+ mock.module("../skills/managed-store.js", () => ({
114
+ createManagedSkill: () => ({ created: true }),
115
+ deleteManagedSkill: () => ({ deleted: true }),
116
+ removeSkillsIndexEntry: () => {},
117
+ validateManagedSkillId: () => null,
118
+ }));
119
+
120
+ mock.module("../memory/graph/capability-seed.js", () => ({
121
+ deleteSkillCapabilityNode: () => {},
122
+ seedSkillGraphNodes: () => {},
123
+ seedUninstalledCatalogSkillMemories: async () => {},
124
+ }));
125
+
126
+ mock.module("../providers/provider-send-message.js", () => ({
127
+ createTimeout: () => ({
128
+ signal: AbortSignal.timeout(1000),
129
+ cleanup: () => {},
130
+ }),
131
+ extractText: () => "",
132
+ getConfiguredProvider: async () => null,
133
+ userMessage: () => ({}),
134
+ }));
135
+
136
+ mock.module("../util/platform.js", () => ({
137
+ getWorkspaceSkillsDir: () => "/tmp/test-skills-fallback",
138
+ }));
139
+
140
+ mock.module("../daemon/handlers/shared.js", () => ({
141
+ CONFIG_RELOAD_DEBOUNCE_MS: 100,
142
+ ensureSkillEntry: (_raw: Record<string, unknown>, _id: string) => ({
143
+ enabled: false,
144
+ }),
145
+ log: {
146
+ info: () => {},
147
+ warn: () => {},
148
+ error: () => {},
149
+ debug: () => {},
150
+ },
151
+ }));
152
+
153
+ // ---------------------------------------------------------------------------
154
+ // Import after mocks
155
+ // ---------------------------------------------------------------------------
156
+
157
+ import { getSkillFiles } from "../daemon/handlers/skills.js";
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // Helpers
161
+ // ---------------------------------------------------------------------------
162
+
163
+ const dummyCtx = {
164
+ debounceTimers: { schedule: () => {} },
165
+ setSuppressConfigReload: () => {},
166
+ updateConfigFingerprint: () => {},
167
+ broadcast: () => {},
168
+ } as unknown as Parameters<typeof getSkillFiles>[1];
169
+
170
+ function makeSummary(overrides: Partial<SkillSummary>): SkillSummary {
171
+ return {
172
+ id: overrides.id ?? "summary-id",
173
+ name: overrides.name ?? "summary-id",
174
+ displayName: overrides.displayName ?? "Summary",
175
+ description: overrides.description ?? "",
176
+ directoryPath: overrides.directoryPath ?? "/tmp/nonexistent-skill-dir",
177
+ skillFilePath:
178
+ overrides.skillFilePath ??
179
+ join(overrides.directoryPath ?? "/tmp/nonexistent-skill-dir", "SKILL.md"),
180
+ source: overrides.source ?? "workspace",
181
+ bundled: overrides.bundled,
182
+ icon: overrides.icon,
183
+ emoji: overrides.emoji,
184
+ toolManifest: overrides.toolManifest,
185
+ includes: overrides.includes,
186
+ featureFlag: overrides.featureFlag,
187
+ activationHints: overrides.activationHints,
188
+ avoidWhen: overrides.avoidWhen,
189
+ inlineCommandExpansions: overrides.inlineCommandExpansions,
190
+ };
191
+ }
192
+
193
+ // ---------------------------------------------------------------------------
194
+ // Tests
195
+ // ---------------------------------------------------------------------------
196
+
197
+ describe("getSkillFiles — catalog fallback", () => {
198
+ beforeEach(() => {
199
+ mockResolvedStates = [];
200
+ mockCatalog = [];
201
+ mockCatalogFiles = null;
202
+ catalogFilesCalls.length = 0;
203
+ });
204
+
205
+ test("returns catalog skill with files (content: null) when skill is uninstalled but present in catalog", async () => {
206
+ mockCatalog = [
207
+ {
208
+ id: "acme-seo",
209
+ name: "acme-seo",
210
+ description: "SEO helper",
211
+ emoji: "\u{1F50D}",
212
+ metadata: { vellum: { "display-name": "Acme SEO" } },
213
+ },
214
+ ];
215
+ mockCatalogFiles = [
216
+ {
217
+ path: "SKILL.md",
218
+ name: "SKILL.md",
219
+ size: 42,
220
+ mimeType: "",
221
+ isBinary: false,
222
+ content: null,
223
+ },
224
+ {
225
+ path: "assets/logo.png",
226
+ name: "logo.png",
227
+ size: 1024,
228
+ mimeType: "",
229
+ isBinary: true,
230
+ content: null,
231
+ },
232
+ ];
233
+
234
+ const result = await getSkillFiles("acme-seo", dummyCtx);
235
+
236
+ expect("error" in result).toBe(false);
237
+ if ("error" in result) return;
238
+
239
+ expect(result.skill).toEqual({
240
+ id: "acme-seo",
241
+ name: "Acme SEO",
242
+ description: "SEO helper",
243
+ emoji: "\u{1F50D}",
244
+ kind: "catalog",
245
+ origin: "vellum",
246
+ status: "available",
247
+ });
248
+ expect(result.files).toHaveLength(2);
249
+ for (const entry of result.files) {
250
+ expect(entry.content).toBeNull();
251
+ }
252
+ // Files should be sorted by path via localeCompare (not codepoint) —
253
+ // so "assets/logo.png" sorts before "SKILL.md" under default collation.
254
+ expect(result.files.map((f) => f.path)).toEqual(
255
+ [...result.files.map((f) => f.path)].sort((a, b) => a.localeCompare(b)),
256
+ );
257
+ expect(new Set(result.files.map((f) => f.path))).toEqual(
258
+ new Set(["SKILL.md", "assets/logo.png"]),
259
+ );
260
+ expect(catalogFilesCalls).toEqual(["acme-seo"]);
261
+ });
262
+
263
+ test("returns 404 when skill is neither installed nor in the catalog", async () => {
264
+ mockResolvedStates = [];
265
+ mockCatalog = [];
266
+
267
+ const result = await getSkillFiles("ghost-skill", dummyCtx);
268
+
269
+ expect("error" in result).toBe(true);
270
+ if (!("error" in result)) return;
271
+ expect(result.status).toBe(404);
272
+ expect(result.error).toContain("ghost-skill");
273
+ expect(catalogFilesCalls).toEqual([]);
274
+ });
275
+
276
+ test("returns 404 when skill is in catalog but readCatalogSkillFiles returns null", async () => {
277
+ mockCatalog = [
278
+ {
279
+ id: "broken-skill",
280
+ name: "broken-skill",
281
+ description: "",
282
+ },
283
+ ];
284
+ mockCatalogFiles = null;
285
+
286
+ const result = await getSkillFiles("broken-skill", dummyCtx);
287
+
288
+ expect("error" in result).toBe(true);
289
+ if (!("error" in result)) return;
290
+ expect(result.status).toBe(404);
291
+ expect(result.error).toContain("broken-skill");
292
+ });
293
+
294
+ test("installed skill returns inline disk content (no catalog call)", async () => {
295
+ // Create a real temp directory for the installed-skill path to read.
296
+ const workspaceRoot = mkdtempSync(
297
+ join(tmpdir(), "vellum-skill-files-test-"),
298
+ );
299
+ const installedDir = join(workspaceRoot, "installed-skill");
300
+ mkdirSync(installedDir, { recursive: true });
301
+ writeFileSync(
302
+ join(installedDir, "SKILL.md"),
303
+ "# Installed\n\nBody of the installed skill.",
304
+ );
305
+ writeFileSync(
306
+ join(installedDir, "notes.txt"),
307
+ "Some notes about the skill.",
308
+ );
309
+
310
+ try {
311
+ mockResolvedStates = [
312
+ {
313
+ summary: makeSummary({
314
+ id: "installed-skill",
315
+ name: "installed-skill",
316
+ displayName: "Installed Skill",
317
+ description: "A pre-installed skill",
318
+ directoryPath: installedDir,
319
+ source: "workspace",
320
+ }),
321
+ state: "enabled",
322
+ },
323
+ ];
324
+
325
+ const result = await getSkillFiles("installed-skill", dummyCtx);
326
+
327
+ expect("error" in result).toBe(false);
328
+ if ("error" in result) return;
329
+
330
+ // Catalog fallback should NOT have been consulted for an installed skill.
331
+ expect(catalogFilesCalls).toEqual([]);
332
+
333
+ expect(result.files).toHaveLength(2);
334
+ const skillMd = result.files.find((f) => f.path === "SKILL.md");
335
+ const notes = result.files.find((f) => f.path === "notes.txt");
336
+ expect(skillMd).toBeDefined();
337
+ expect(notes).toBeDefined();
338
+ expect(skillMd!.content).toBe(
339
+ "# Installed\n\nBody of the installed skill.",
340
+ );
341
+ expect(notes!.content).toBe("Some notes about the skill.");
342
+
343
+ // Sort-by-path behavior (localeCompare order) is preserved.
344
+ expect(result.files.map((f) => f.path)).toEqual(
345
+ [...result.files.map((f) => f.path)].sort((a, b) => a.localeCompare(b)),
346
+ );
347
+ } finally {
348
+ rmSync(workspaceRoot, { recursive: true, force: true });
349
+ }
350
+ });
351
+
352
+ test("returns 404 without catalog fallback when installed skill directory is missing on disk", async () => {
353
+ // `findSkillById` resolves the skill (resolver lists it as installed)
354
+ // but the on-disk directoryPath does not exist — simulates a corrupted
355
+ // install, mid-delete race, or external unmount. The handler must
356
+ // return 404 so the listing response (`kind: "installed"`) and the
357
+ // detail response stay consistent; falling through to the catalog
358
+ // would flip the detail to `kind: "catalog"` and break the
359
+ // client-side `isInstalled` contract.
360
+ mockResolvedStates = [
361
+ {
362
+ summary: makeSummary({
363
+ id: "ghost-installed",
364
+ name: "ghost-installed",
365
+ displayName: "Ghost Installed",
366
+ description: "Installed in resolver but directory is gone",
367
+ directoryPath: "/tmp/definitely-does-not-exist-" + Date.now(),
368
+ source: "workspace",
369
+ }),
370
+ state: "enabled",
371
+ },
372
+ ];
373
+ // Even if the same id is present in the catalog, the handler must NOT
374
+ // fall through — return 404 instead to avoid masking the missing
375
+ // install directory with a catalog response.
376
+ mockCatalog = [
377
+ {
378
+ id: "ghost-installed",
379
+ name: "ghost-installed",
380
+ description: "Also present in catalog",
381
+ },
382
+ ];
383
+ mockCatalogFiles = [
384
+ {
385
+ path: "SKILL.md",
386
+ name: "SKILL.md",
387
+ size: 10,
388
+ mimeType: "",
389
+ isBinary: false,
390
+ content: null,
391
+ },
392
+ ];
393
+
394
+ const result = await getSkillFiles("ghost-installed", dummyCtx);
395
+
396
+ expect("error" in result).toBe(true);
397
+ if (!("error" in result)) return;
398
+ expect(result.status).toBe(404);
399
+ expect(result.error).toContain("ghost-installed");
400
+ expect(result.error).toContain("directory missing");
401
+ // Catalog fallback must not have been consulted.
402
+ expect(catalogFilesCalls).toEqual([]);
403
+ });
404
+
405
+ test("catalogSkillToSlim falls back to cs.name when metadata.vellum.display-name is absent", async () => {
406
+ mockCatalog = [
407
+ {
408
+ id: "plain-skill",
409
+ name: "plain-skill",
410
+ description: "Minimal",
411
+ },
412
+ ];
413
+ mockCatalogFiles = [];
414
+
415
+ const result = await getSkillFiles("plain-skill", dummyCtx);
416
+
417
+ expect("error" in result).toBe(false);
418
+ if ("error" in result) return;
419
+ expect(result.skill.name).toBe("plain-skill");
420
+ expect(result.skill.kind).toBe("catalog");
421
+ expect(result.skill.origin).toBe("vellum");
422
+ expect(result.skill.status).toBe("available");
423
+ });
424
+
425
+ test("catalogSkillToSlim prefers metadata.vellum.display-name over cs.name", async () => {
426
+ mockCatalog = [
427
+ {
428
+ id: "fancy-skill",
429
+ name: "raw-fancy-name",
430
+ description: "",
431
+ metadata: { vellum: { "display-name": "Pretty Fancy Name" } },
432
+ },
433
+ ];
434
+ mockCatalogFiles = [];
435
+
436
+ const result = await getSkillFiles("fancy-skill", dummyCtx);
437
+
438
+ expect("error" in result).toBe(false);
439
+ if ("error" in result) return;
440
+ expect(result.skill.name).toBe("Pretty Fancy Name");
441
+ });
442
+ });
443
+
444
+ // ---------------------------------------------------------------------------
445
+ // Cleanup: ensure we don't leak temp dirs if a test fails mid-way.
446
+ // ---------------------------------------------------------------------------
447
+
448
+ afterEach(() => {
449
+ // Nothing to clean up outside test scope — temp dirs are cleaned per-test.
450
+ });
@@ -82,8 +82,8 @@ let oauthConnectionStore: Record<
82
82
  > = {};
83
83
 
84
84
  mock.module("../oauth/oauth-store.js", () => ({
85
- getConnectionByProvider: (providerKey: string) =>
86
- oauthConnectionStore[providerKey] ?? undefined,
85
+ getConnectionByProvider: (provider: string) =>
86
+ oauthConnectionStore[provider] ?? undefined,
87
87
  createConnection: () => ({ id: "test-conn-id" }),
88
88
  updateConnection: () => true,
89
89
  deleteConnection: (id: string) => {
@@ -101,24 +101,21 @@ mock.module("../oauth/oauth-store.js", () => ({
101
101
  // Mock manual-token-connection
102
102
  mock.module("../oauth/manual-token-connection.js", () => ({
103
103
  ensureManualTokenConnection: async (
104
- providerKey: string,
104
+ provider: string,
105
105
  accountInfo?: string,
106
106
  ) => {
107
- oauthConnectionStore[providerKey] = {
108
- id: `conn-${providerKey}`,
107
+ oauthConnectionStore[provider] = {
108
+ id: `conn-${provider}`,
109
109
  status: "active",
110
110
  accountInfo: accountInfo ?? null,
111
111
  };
112
112
  },
113
- removeManualTokenConnection: (providerKey: string) => {
114
- delete oauthConnectionStore[providerKey];
113
+ removeManualTokenConnection: (provider: string) => {
114
+ delete oauthConnectionStore[provider];
115
115
  },
116
- syncManualTokenConnection: async (
117
- providerKey: string,
118
- accountInfo?: string,
119
- ) => {
116
+ syncManualTokenConnection: async (provider: string, accountInfo?: string) => {
120
117
  const { getSecureKeyAsync } = await import("../security/secure-keys.js");
121
- if (providerKey !== "slack_channel") return;
118
+ if (provider !== "slack_channel") return;
122
119
  const hasBotToken = !!(await getSecureKeyAsync(
123
120
  credentialKey("slack_channel", "bot_token"),
124
121
  ));
@@ -126,14 +123,14 @@ mock.module("../oauth/manual-token-connection.js", () => ({
126
123
  credentialKey("slack_channel", "app_token"),
127
124
  ));
128
125
  if (hasBotToken && hasAppToken) {
129
- oauthConnectionStore[providerKey] = {
130
- id: `conn-${providerKey}`,
126
+ oauthConnectionStore[provider] = {
127
+ id: `conn-${provider}`,
131
128
  status: "active",
132
129
  accountInfo: accountInfo ?? null,
133
130
  };
134
131
  return;
135
132
  }
136
- delete oauthConnectionStore[providerKey];
133
+ delete oauthConnectionStore[provider];
137
134
  },
138
135
  }));
139
136
 
@@ -5,10 +5,20 @@
5
5
 
6
6
  import { describe, expect, test } from "bun:test";
7
7
 
8
+ import type { MessageRow } from "../memory/conversation-crud.js";
8
9
  import { parseSubagentMessages } from "../runtime/routes/subagents-routes.js";
9
10
 
10
- function msg(role: string, content: unknown[]) {
11
- return { role, content: JSON.stringify(content) };
11
+ let msgCounter = 0;
12
+ function msg(role: string, content: unknown[]): MessageRow {
13
+ msgCounter += 1;
14
+ return {
15
+ id: `msg-${msgCounter}`,
16
+ conversationId: "conv-1",
17
+ role,
18
+ content: JSON.stringify(content),
19
+ createdAt: Date.now(),
20
+ metadata: null,
21
+ };
12
22
  }
13
23
 
14
24
  describe("parseSubagentMessages", () => {
@@ -81,4 +91,36 @@ describe("parseSubagentMessages", () => {
81
91
  const result = parseSubagentMessages("sub-1", messages);
82
92
  expect(result.objective).toBe("Research vampire lore");
83
93
  });
94
+
95
+ test("strips fork directive framing from objective", () => {
96
+ const forkPrompt = [
97
+ "⎯⎯⎯ FORK TASK ⎯⎯⎯",
98
+ "You have been forked from the parent conversation to execute a specific task.",
99
+ "The conversation above is context — do NOT continue it. Do NOT spawn sub-agents.",
100
+ "Complete this task directly and return only your findings:",
101
+ "",
102
+ "Research vampire lore",
103
+ "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯",
104
+ ].join("\n");
105
+
106
+ const messages = [
107
+ msg("user", [{ type: "text", text: forkPrompt }]),
108
+ msg("assistant", [{ type: "text", text: "On it." }]),
109
+ ];
110
+
111
+ const result = parseSubagentMessages("sub-1", messages);
112
+ expect(result.objective).toBe("Research vampire lore");
113
+ });
114
+
115
+ test("includes messageId on text events from assistant messages", () => {
116
+ const messages = [
117
+ msg("user", [{ type: "text", text: "Do something" }]),
118
+ msg("assistant", [{ type: "text", text: "Done." }]),
119
+ ];
120
+
121
+ const result = parseSubagentMessages("sub-1", messages);
122
+ const textEvent = result.events.find((e) => e.type === "text");
123
+ expect(textEvent).toBeDefined();
124
+ expect(textEvent!.messageId).toBe(messages[1].id);
125
+ });
84
126
  });
@@ -89,6 +89,7 @@ function makeState(
89
89
  },
90
90
  status: "running",
91
91
  conversationId: "conv-sub-1",
92
+ isFork: false,
92
93
  createdAt: Date.now(),
93
94
  usage: { inputTokens: 0, outputTokens: 0, estimatedCost: 0 },
94
95
  ...overrides,