@vellumai/assistant 0.7.2 → 0.8.0

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 (424) hide show
  1. package/ARCHITECTURE.md +45 -29
  2. package/Dockerfile +1 -0
  3. package/__tests__/permissions/gateway-threshold-reader.test.ts +236 -9
  4. package/bun.lock +3 -0
  5. package/docs/architecture/memory.md +5 -2
  6. package/knip.json +1 -0
  7. package/node_modules/@vellumai/gateway-client/src/ipc-client.ts +13 -4
  8. package/node_modules/@vellumai/ipc-server-utils/bun.lock +24 -0
  9. package/node_modules/@vellumai/ipc-server-utils/package.json +18 -0
  10. package/node_modules/@vellumai/ipc-server-utils/src/index.ts +6 -0
  11. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.test.ts +430 -0
  12. package/node_modules/@vellumai/ipc-server-utils/src/socket-watchdog.ts +221 -0
  13. package/node_modules/@vellumai/ipc-server-utils/tsconfig.json +20 -0
  14. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -9
  15. package/node_modules/@vellumai/slack-text/src/index.test.ts +18 -35
  16. package/node_modules/@vellumai/slack-text/src/index.ts +2 -48
  17. package/openapi.yaml +470 -25
  18. package/package.json +3 -1
  19. package/src/__tests__/annotate-risk-options.test.ts +291 -0
  20. package/src/__tests__/app-control-flow.test.ts +21 -11
  21. package/src/__tests__/approval-cascade.test.ts +8 -16
  22. package/src/__tests__/approval-routes-http.test.ts +6 -0
  23. package/src/__tests__/assistant-event-hub.test.ts +48 -0
  24. package/src/__tests__/assistant-event.test.ts +0 -10
  25. package/src/__tests__/assistant-events-sse-hardening.test.ts +2 -7
  26. package/src/__tests__/assistant-feature-flags-integration.test.ts +18 -0
  27. package/src/__tests__/auto-analysis-end-to-end.test.ts +48 -0
  28. package/src/__tests__/background-workers-disk-pressure.test.ts +268 -0
  29. package/src/__tests__/call-constants.test.ts +10 -1
  30. package/src/__tests__/call-controller.test.ts +127 -0
  31. package/src/__tests__/call-conversation-messages.test.ts +8 -2
  32. package/src/__tests__/channel-inbound-disk-pressure.test.ts +537 -0
  33. package/src/__tests__/channel-readiness-service.test.ts +4 -2
  34. package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +58 -28
  35. package/src/__tests__/config-loader-backfill.test.ts +379 -0
  36. package/src/__tests__/config-loader-platform-defaults.test.ts +284 -1
  37. package/src/__tests__/config-schema.test.ts +1 -0
  38. package/src/__tests__/config-watcher-cleanup-throttle.test.ts +18 -9
  39. package/src/__tests__/config-watcher.test.ts +140 -69
  40. package/src/__tests__/context-search-agent-runner.test.ts +61 -3
  41. package/src/__tests__/context-search-conversations-source.test.ts +0 -24
  42. package/src/__tests__/context-search-fanout.test.ts +0 -1
  43. package/src/__tests__/context-search-memory-source.test.ts +6 -33
  44. package/src/__tests__/context-search-memory-v2-source.test.ts +0 -2
  45. package/src/__tests__/context-search-pkb-source.test.ts +12 -7
  46. package/src/__tests__/context-search-workspace-source.test.ts +0 -1
  47. package/src/__tests__/conversation-abort-tool-results.test.ts +1 -0
  48. package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +223 -0
  49. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -1
  50. package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -1
  51. package/src/__tests__/conversation-agent-loop.test.ts +457 -8
  52. package/src/__tests__/conversation-confirmation-signals.test.ts +5 -13
  53. package/src/__tests__/conversation-error.test.ts +150 -3
  54. package/src/__tests__/conversation-init.benchmark.test.ts +1 -1
  55. package/src/__tests__/conversation-process-callsite.test.ts +38 -0
  56. package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -0
  57. package/src/__tests__/conversation-runtime-assembly.test.ts +74 -0
  58. package/src/__tests__/conversation-slash-unknown.test.ts +1 -0
  59. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  60. package/src/__tests__/conversation-store.test.ts +0 -18
  61. package/src/__tests__/conversation-surfaces-action-delivery.test.ts +170 -9
  62. package/src/__tests__/conversation-surfaces-app-control.test.ts +15 -4
  63. package/src/__tests__/conversation-surfaces-data-persist.test.ts +476 -0
  64. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +61 -5
  65. package/src/__tests__/conversation-workspace-injection.test.ts +1 -1
  66. package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
  67. package/src/__tests__/credentials-cli.test.ts +7 -0
  68. package/src/__tests__/cu-unified-flow.test.ts +176 -10
  69. package/src/__tests__/date-context.test.ts +164 -2
  70. package/src/__tests__/disk-pressure-guard.test.ts +262 -0
  71. package/src/__tests__/disk-pressure-lifecycle.test.ts +168 -0
  72. package/src/__tests__/disk-pressure-policy.test.ts +241 -0
  73. package/src/__tests__/disk-pressure-routes.test.ts +379 -0
  74. package/src/__tests__/disk-pressure-tools.test.ts +277 -0
  75. package/src/__tests__/disk-usage.test.ts +150 -0
  76. package/src/__tests__/events-client-registration.test.ts +52 -0
  77. package/src/__tests__/events-dev-bypass-actor.test.ts +162 -0
  78. package/src/__tests__/file-write-tool.test.ts +4 -10
  79. package/src/__tests__/filing-service.test.ts +2 -20
  80. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +10 -26
  81. package/src/__tests__/heartbeat-disk-pressure.test.ts +183 -0
  82. package/src/__tests__/heartbeat-service.test.ts +260 -11
  83. package/src/__tests__/host-app-control-proxy.test.ts +195 -25
  84. package/src/__tests__/host-bash-proxy.test.ts +227 -34
  85. package/src/__tests__/host-bash-routes.test.ts +178 -13
  86. package/src/__tests__/host-cu-proxy.test.ts +210 -3
  87. package/src/__tests__/host-cu-routes-targeted.test.ts +141 -12
  88. package/src/__tests__/host-file-proxy-targeted.test.ts +48 -9
  89. package/src/__tests__/host-file-proxy.test.ts +268 -6
  90. package/src/__tests__/host-file-routes-targeted.test.ts +175 -17
  91. package/src/__tests__/host-transfer-proxy-targeted.test.ts +408 -59
  92. package/src/__tests__/host-transfer-routes-targeted.test.ts +232 -17
  93. package/src/__tests__/http-user-message-parity.test.ts +107 -1
  94. package/src/__tests__/injector-chain.test.ts +36 -16
  95. package/src/__tests__/injector-disk-pressure.test.ts +224 -0
  96. package/src/__tests__/injector-pkb-v2-silenced.test.ts +10 -7
  97. package/src/__tests__/lifecycle-memory-v2-seed.test.ts +154 -67
  98. package/src/__tests__/managed-profile-guard.test.ts +18 -0
  99. package/src/__tests__/mcp-abort-signal.test.ts +130 -0
  100. package/src/__tests__/memory-admin-recall.test.ts +3 -11
  101. package/src/__tests__/memory-retrieval-pipeline.test.ts +22 -1
  102. package/src/__tests__/normalize-onboarding.test.ts +180 -0
  103. package/src/__tests__/notification-decision-fallback.test.ts +91 -0
  104. package/src/__tests__/notification-decision-strategy.test.ts +22 -0
  105. package/src/__tests__/oauth-cli.test.ts +121 -0
  106. package/src/__tests__/oauth-connect-routes.test.ts +316 -0
  107. package/src/__tests__/oauth-provider-seed-logos.test.ts +24 -2
  108. package/src/__tests__/onboarding-persona-write.test.ts +308 -0
  109. package/src/__tests__/openai-provider.test.ts +45 -8
  110. package/src/__tests__/persist-onboarding-artifacts.test.ts +44 -64
  111. package/src/__tests__/platform-callback-registration.test.ts +21 -4
  112. package/src/__tests__/platform.test.ts +2 -1
  113. package/src/__tests__/playbook-execution.test.ts +0 -43
  114. package/src/__tests__/plugin-tool-contribution.test.ts +47 -0
  115. package/src/__tests__/prechat-onboarding-contract.test.ts +214 -27
  116. package/src/__tests__/provider-tool-name.test.ts +23 -0
  117. package/src/__tests__/relay-server.test.ts +60 -5
  118. package/src/__tests__/runtime-events-sse.test.ts +4 -8
  119. package/src/__tests__/scheduler-disk-pressure.test.ts +148 -0
  120. package/src/__tests__/secret-ingress-http.test.ts +0 -1
  121. package/src/__tests__/secret-prompt-log-hygiene.test.ts +7 -5
  122. package/src/__tests__/secret-prompter-channel-fallback.test.ts +7 -5
  123. package/src/__tests__/secret-response-routing.test.ts +7 -5
  124. package/src/__tests__/server-history-render.test.ts +82 -0
  125. package/src/__tests__/skill-include-graph.test.ts +31 -0
  126. package/src/__tests__/skill-load-tool.test.ts +44 -16
  127. package/src/__tests__/skills.test.ts +39 -0
  128. package/src/__tests__/suggestion-routes.test.ts +46 -0
  129. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -42
  130. package/src/__tests__/tool-executor.test.ts +155 -0
  131. package/src/__tests__/twilio-validation.test.ts +2 -2
  132. package/src/__tests__/voice-session-bridge.test.ts +3 -0
  133. package/src/__tests__/workspace-migration-065-bump-stale-heartbeat-interval.test.ts +122 -0
  134. package/src/__tests__/workspace-migration-066-seed-heartbeat-callsite-cost-default.test.ts +285 -0
  135. package/src/__tests__/workspace-migration-068-release-notes-local-timezone.test.ts +90 -0
  136. package/src/__tests__/workspace-migration-069-seed-onboarding-threads.test.ts +120 -0
  137. package/src/__tests__/workspace-migration-071-remove-safe-storage-release-note.test.ts +206 -0
  138. package/src/__tests__/workspace-migration-safe-storage-limits-release.test.ts +78 -0
  139. package/src/agent/loop.ts +11 -0
  140. package/src/approvals/guardian-request-resolvers.ts +3 -32
  141. package/src/backup/snapshot-lock.ts +2 -27
  142. package/src/bundler/compiler-tools.ts +3 -2
  143. package/src/calls/call-constants.ts +5 -8
  144. package/src/calls/call-controller.ts +130 -67
  145. package/src/calls/call-conversation-messages.ts +46 -10
  146. package/src/calls/relay-server.ts +7 -1
  147. package/src/calls/voice-session-bridge.ts +1 -1
  148. package/src/cli/commands/__tests__/webhooks.test.ts +0 -4
  149. package/src/cli/commands/bash.ts +35 -108
  150. package/src/cli/commands/contacts.ts +64 -25
  151. package/src/cli/commands/credentials.ts +56 -0
  152. package/src/cli/commands/memory-v2.ts +11 -10
  153. package/src/cli/commands/oauth/__tests__/connect.test.ts +401 -219
  154. package/src/cli/commands/oauth/connect.ts +124 -40
  155. package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -3
  156. package/src/cli/commands/platform/__tests__/connect.test.ts +7 -1
  157. package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
  158. package/src/cli/commands/platform/__tests__/status.test.ts +103 -6
  159. package/src/cli/commands/platform/index.ts +16 -7
  160. package/src/cli/commands/status.ts +57 -0
  161. package/src/cli/program.ts +4 -2
  162. package/src/config/assistant-feature-flags.ts +13 -3
  163. package/src/config/bundled-skills/app-builder/SKILL.md +1 -3
  164. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +4 -3
  165. package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +13 -7
  166. package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +2 -2
  167. package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +2 -2
  168. package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +2 -2
  169. package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -2
  170. package/src/config/env.ts +0 -8
  171. package/src/config/feature-flag-registry.json +13 -5
  172. package/src/config/loader.ts +199 -27
  173. package/src/config/schemas/__tests__/memory-v2.test.ts +10 -5
  174. package/src/config/schemas/call-site-catalog.ts +14 -0
  175. package/src/config/schemas/channels.ts +0 -5
  176. package/src/config/schemas/heartbeat.ts +1 -1
  177. package/src/config/schemas/llm.ts +2 -0
  178. package/src/config/schemas/memory-lifecycle.ts +13 -0
  179. package/src/config/schemas/memory-v2.ts +76 -12
  180. package/src/config/schemas/platform.ts +43 -3
  181. package/src/config/schemas/services.ts +28 -0
  182. package/src/config/seed-inference-profiles.ts +230 -33
  183. package/src/contacts/contact-store.ts +0 -25
  184. package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +32 -0
  185. package/src/daemon/__tests__/conversation-tool-setup.test.ts +86 -25
  186. package/src/daemon/assistant-attachments.ts +4 -4
  187. package/src/daemon/config-watcher.ts +85 -57
  188. package/src/daemon/conversation-agent-loop-handlers.ts +38 -0
  189. package/src/daemon/conversation-agent-loop.ts +183 -43
  190. package/src/daemon/conversation-error.ts +87 -15
  191. package/src/daemon/conversation-lifecycle.ts +22 -10
  192. package/src/daemon/conversation-process.ts +8 -0
  193. package/src/daemon/conversation-runtime-assembly.ts +26 -0
  194. package/src/daemon/conversation-store.ts +2 -2
  195. package/src/daemon/conversation-surfaces.ts +211 -29
  196. package/src/daemon/conversation-tool-setup.ts +66 -19
  197. package/src/daemon/conversation.ts +18 -23
  198. package/src/daemon/date-context.ts +71 -22
  199. package/src/daemon/disk-pressure-background-gate.ts +73 -0
  200. package/src/daemon/disk-pressure-guard.ts +343 -0
  201. package/src/daemon/disk-pressure-policy.ts +163 -0
  202. package/src/daemon/handlers/shared.ts +26 -1
  203. package/src/daemon/handlers/skills.ts +3 -4
  204. package/src/daemon/host-app-control-proxy.ts +137 -41
  205. package/src/daemon/host-bash-proxy.ts +47 -22
  206. package/src/daemon/host-browser-proxy.ts +1 -1
  207. package/src/daemon/host-cu-proxy.ts +50 -4
  208. package/src/daemon/host-file-proxy.ts +44 -8
  209. package/src/daemon/host-transfer-proxy.ts +97 -6
  210. package/src/daemon/lifecycle.ts +167 -101
  211. package/src/daemon/meet-host-supervisor.ts +4 -4
  212. package/src/daemon/meet-manifest-loader.ts +0 -1
  213. package/src/daemon/memory-v2-startup.ts +66 -15
  214. package/src/daemon/message-protocol.ts +3 -0
  215. package/src/daemon/message-types/conversations.ts +4 -0
  216. package/src/daemon/message-types/disk-pressure.ts +9 -0
  217. package/src/daemon/message-types/messages.ts +22 -1
  218. package/src/daemon/profiler-run-store.ts +5 -5
  219. package/src/daemon/tool-setup-types.ts +2 -2
  220. package/src/documents/document-store.ts +119 -0
  221. package/src/filing/filing-service.ts +29 -5
  222. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +9 -16
  223. package/src/heartbeat/__tests__/heartbeat-run-store.test.ts +36 -0
  224. package/src/heartbeat/heartbeat-run-store.ts +13 -0
  225. package/src/heartbeat/heartbeat-service.ts +205 -31
  226. package/src/home/feed-scheduler.ts +18 -0
  227. package/src/inbound/platform-callback-registration.ts +8 -15
  228. package/src/ipc/__tests__/clients-list-ipc.test.ts +169 -0
  229. package/src/ipc/assistant-server.ts +149 -38
  230. package/src/ipc/gateway-client.ts +37 -3
  231. package/src/ipc/skill-server.ts +99 -42
  232. package/src/live-voice/live-voice-archive.ts +4 -4
  233. package/src/live-voice/protocol.ts +5 -7
  234. package/src/media/image-service.ts +1 -7
  235. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +21 -13
  236. package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +34 -51
  237. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +0 -6
  238. package/src/memory/__tests__/memory-v2-concept-frequency.test.ts +272 -0
  239. package/src/memory/admin.ts +5 -9
  240. package/src/memory/context-search/agent-runner.ts +19 -2
  241. package/src/memory/context-search/sources/conversations.ts +2 -11
  242. package/src/memory/context-search/sources/memory-v2.ts +1 -16
  243. package/src/memory/context-search/sources/memory.ts +2 -3
  244. package/src/memory/context-search/sources/pkb.ts +2 -3
  245. package/src/memory/context-search/types.ts +0 -1
  246. package/src/memory/conversation-crud.ts +4 -12
  247. package/src/memory/db-init.ts +2 -0
  248. package/src/memory/embedding-runtime-manager.ts +119 -5
  249. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +136 -82
  250. package/src/memory/graph/__tests__/handle-remember-v2.test.ts +11 -26
  251. package/src/memory/graph/conversation-graph-memory.ts +72 -61
  252. package/src/memory/graph/extraction.ts +1 -3
  253. package/src/memory/graph/graph-search.test.ts +11 -67
  254. package/src/memory/graph/graph-search.ts +4 -24
  255. package/src/memory/graph/retriever.test.ts +12 -1
  256. package/src/memory/graph/retriever.ts +10 -15
  257. package/src/memory/graph/tool-handlers.ts +3 -4
  258. package/src/memory/graph/tools.ts +4 -4
  259. package/src/memory/indexer.ts +53 -45
  260. package/src/memory/job-handlers/backfill.ts +2 -11
  261. package/src/memory/job-handlers/cleanup.ts +43 -0
  262. package/src/memory/job-handlers/embedding.ts +6 -8
  263. package/src/memory/job-handlers/summarization.ts +2 -7
  264. package/src/memory/jobs/__tests__/embed-concept-page.test.ts +116 -0
  265. package/src/memory/jobs/embed-concept-page.ts +223 -87
  266. package/src/memory/jobs-store.ts +48 -0
  267. package/src/memory/jobs-worker.ts +85 -43
  268. package/src/memory/memory-v2-activation-log-store.ts +32 -14
  269. package/src/memory/memory-v2-concept-frequency.ts +169 -0
  270. package/src/memory/migrations/239-trace-events-created-at-index.ts +18 -0
  271. package/src/memory/migrations/index.ts +1 -0
  272. package/src/memory/pkb/pkb-search.test.ts +7 -0
  273. package/src/memory/pkb/pkb-search.ts +4 -5
  274. package/src/memory/qdrant-client.ts +3 -13
  275. package/src/memory/rerank-local.ts +374 -0
  276. package/src/memory/search/semantic.ts +10 -72
  277. package/src/memory/trace-event-store.ts +1 -17
  278. package/src/memory/v2/__tests__/activation.test.ts +346 -255
  279. package/src/memory/v2/__tests__/consolidation-job.test.ts +61 -40
  280. package/src/memory/v2/__tests__/injection.test.ts +297 -190
  281. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +61 -2
  282. package/src/memory/v2/__tests__/qdrant.test.ts +326 -9
  283. package/src/memory/v2/__tests__/reranker.test.ts +338 -0
  284. package/src/memory/v2/__tests__/sim.test.ts +113 -196
  285. package/src/memory/v2/__tests__/skill-store.test.ts +71 -65
  286. package/src/memory/v2/__tests__/static-context.test.ts +77 -14
  287. package/src/memory/v2/__tests__/sweep-job.test.ts +19 -33
  288. package/src/memory/v2/activation.ts +149 -156
  289. package/src/memory/v2/consolidation-job.ts +69 -20
  290. package/src/memory/v2/injection.ts +75 -68
  291. package/src/memory/v2/page-store.ts +39 -0
  292. package/src/memory/v2/prompts/consolidation.ts +41 -1
  293. package/src/memory/v2/qdrant.ts +306 -46
  294. package/src/memory/v2/reranker.ts +177 -0
  295. package/src/memory/v2/sim.ts +77 -110
  296. package/src/memory/v2/skill-content.ts +4 -3
  297. package/src/memory/v2/skill-store.ts +82 -59
  298. package/src/memory/v2/static-context.ts +26 -8
  299. package/src/memory/v2/sweep-job.ts +5 -6
  300. package/src/memory/v2/types.ts +17 -10
  301. package/src/notifications/copy-composer.ts +47 -0
  302. package/src/notifications/decision-engine.ts +46 -0
  303. package/src/notifications/signal.ts +4 -0
  304. package/src/oauth/AGENTS.md +3 -1
  305. package/src/oauth/__tests__/oauth-connect-state.test.ts +137 -0
  306. package/src/oauth/connect-orchestrator.ts +2 -0
  307. package/src/oauth/connection-resolver.test.ts +66 -1
  308. package/src/oauth/connection-resolver.ts +55 -1
  309. package/src/oauth/oauth-connect-state.ts +77 -0
  310. package/src/oauth/seed-providers.ts +58 -1
  311. package/src/permissions/gateway-threshold-reader.ts +116 -8
  312. package/src/permissions/prompter.ts +86 -96
  313. package/src/permissions/secret-prompter.ts +31 -31
  314. package/src/plugins/defaults/injectors.ts +36 -4
  315. package/src/plugins/defaults/memory-retrieval.ts +5 -6
  316. package/src/plugins/types.ts +7 -0
  317. package/src/proactive-artifact/aux-message-injector.ts +74 -0
  318. package/src/proactive-artifact/decision.test.ts +226 -0
  319. package/src/proactive-artifact/decision.ts +165 -0
  320. package/src/proactive-artifact/index.ts +7 -0
  321. package/src/proactive-artifact/job.test.ts +914 -0
  322. package/src/proactive-artifact/job.ts +366 -0
  323. package/src/proactive-artifact/message-copy.ts +58 -0
  324. package/src/proactive-artifact/trigger-state.test.ts +277 -0
  325. package/src/proactive-artifact/trigger-state.ts +119 -0
  326. package/src/prompts/normalize-onboarding.ts +80 -0
  327. package/src/prompts/persona-resolver.ts +101 -9
  328. package/src/prompts/system-prompt.ts +21 -7
  329. package/src/prompts/templates/BOOTSTRAP.md +13 -5
  330. package/src/prompts/templates/SOUL.md +13 -28
  331. package/src/providers/__tests__/retry-callsite.test.ts +222 -1
  332. package/src/providers/model-intents.ts +7 -0
  333. package/src/providers/openrouter/client.ts +8 -0
  334. package/src/providers/retry.ts +50 -0
  335. package/src/providers/types.ts +1 -0
  336. package/src/runtime/__tests__/agent-wake.test.ts +456 -3
  337. package/src/runtime/agent-wake.ts +238 -100
  338. package/src/runtime/assistant-event-hub.ts +36 -6
  339. package/src/runtime/assistant-event.ts +0 -1
  340. package/src/runtime/auth/__tests__/route-policy.test.ts +64 -0
  341. package/src/runtime/auth/route-policy.ts +15 -1
  342. package/src/runtime/auth/same-actor.ts +216 -0
  343. package/src/runtime/channel-approvals.ts +3 -2
  344. package/src/runtime/channel-retry-sweep.ts +65 -1
  345. package/src/runtime/local-actor-identity.ts +52 -11
  346. package/src/runtime/pending-interactions.ts +27 -15
  347. package/src/runtime/routes/__tests__/client-routes.test.ts +155 -0
  348. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +0 -5
  349. package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
  350. package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +147 -0
  351. package/src/runtime/routes/approval-routes.ts +7 -3
  352. package/src/runtime/routes/client-routes.ts +20 -2
  353. package/src/runtime/routes/consolidation-routes.ts +8 -9
  354. package/src/runtime/routes/contact-routes.ts +0 -25
  355. package/src/runtime/routes/conversation-query-routes.ts +44 -1
  356. package/src/runtime/routes/conversation-routes.ts +35 -26
  357. package/src/runtime/routes/debug-bash-routes.ts +165 -0
  358. package/src/runtime/routes/disk-pressure-routes.ts +121 -0
  359. package/src/runtime/routes/document-pdf-renderer.ts +6 -2
  360. package/src/runtime/routes/documents-routes.ts +2 -75
  361. package/src/runtime/routes/events-routes.ts +41 -9
  362. package/src/runtime/routes/filing-routes.ts +2 -3
  363. package/src/runtime/routes/host-bash-routes.ts +23 -3
  364. package/src/runtime/routes/host-cu-routes.ts +33 -6
  365. package/src/runtime/routes/host-file-routes.ts +32 -6
  366. package/src/runtime/routes/host-transfer-routes.ts +79 -16
  367. package/src/runtime/routes/identity-routes.ts +7 -138
  368. package/src/runtime/routes/inbound-message-handler.ts +77 -12
  369. package/src/runtime/routes/index.ts +6 -0
  370. package/src/runtime/routes/memory-item-routes.test.ts +37 -17
  371. package/src/runtime/routes/memory-item-routes.ts +5 -6
  372. package/src/runtime/routes/memory-v2-routes.ts +136 -17
  373. package/src/runtime/routes/oauth-connect-routes.ts +153 -0
  374. package/src/runtime/verification-outbound-actions.ts +4 -4
  375. package/src/schedule/run-script.ts +37 -5
  376. package/src/schedule/scheduler.ts +20 -1
  377. package/src/security/encrypted-store.ts +2 -0
  378. package/src/security/secure-keys.ts +55 -0
  379. package/src/skills/include-graph.ts +35 -13
  380. package/src/skills/remote-skill-policy.ts +4 -10
  381. package/src/subagent/index.ts +1 -7
  382. package/src/subagent/manager.ts +1 -15
  383. package/src/tasks/task-runner.ts +0 -1
  384. package/src/tasks/task-store.ts +0 -3
  385. package/src/tools/background-tool-registry.ts +17 -3
  386. package/src/tools/document/document-tool.ts +20 -0
  387. package/src/tools/executor.ts +18 -2
  388. package/src/tools/host-filesystem/edit.test.ts +151 -0
  389. package/src/tools/host-filesystem/edit.ts +43 -1
  390. package/src/tools/host-filesystem/read.test.ts +129 -0
  391. package/src/tools/host-filesystem/read.ts +43 -1
  392. package/src/tools/host-filesystem/transfer.test.ts +127 -2
  393. package/src/tools/host-filesystem/transfer.ts +56 -11
  394. package/src/tools/host-filesystem/write.test.ts +134 -0
  395. package/src/tools/host-filesystem/write.ts +43 -1
  396. package/src/tools/host-terminal/host-shell.ts +13 -6
  397. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  398. package/src/tools/memory/register.test.ts +14 -9
  399. package/src/tools/memory/register.ts +1 -2
  400. package/src/tools/permission-checker.ts +15 -0
  401. package/src/tools/provider-tool-name.ts +28 -0
  402. package/src/tools/registry.ts +30 -9
  403. package/src/tools/skills/load.ts +24 -20
  404. package/src/tools/terminal/shell.ts +9 -1
  405. package/src/tools/tool-approval-handler.ts +31 -6
  406. package/src/tools/tool-name-aliases.ts +19 -0
  407. package/src/tools/types.ts +43 -3
  408. package/src/tts/provider-catalog.ts +3 -5
  409. package/src/util/disk-usage.ts +138 -0
  410. package/src/util/platform.ts +21 -11
  411. package/src/util/process-liveness.ts +26 -0
  412. package/src/workspace/heartbeat-service.ts +19 -0
  413. package/src/workspace/migrations/065-bump-stale-heartbeat-interval.ts +60 -0
  414. package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +146 -0
  415. package/src/workspace/migrations/067-release-notes-safe-storage-limits.ts +14 -0
  416. package/src/workspace/migrations/068-release-notes-local-timezone.ts +65 -0
  417. package/src/workspace/migrations/069-seed-onboarding-threads.ts +28 -0
  418. package/src/workspace/migrations/070-memory-v2-summary-schema-rebuild.ts +31 -0
  419. package/src/workspace/migrations/071-remove-safe-storage-release-note.ts +111 -0
  420. package/src/workspace/migrations/registry.ts +14 -0
  421. package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +0 -167
  422. package/src/memory/v2/__tests__/skill-qdrant.test.ts +0 -657
  423. package/src/memory/v2/skill-qdrant.ts +0 -404
  424. package/src/signals/bash.ts +0 -198
@@ -42,6 +42,20 @@ let mockPlatformFetchCallIndex = 0;
42
42
 
43
43
  let mockIsManagedMode: (key: string) => boolean = () => false;
44
44
 
45
+ // Configurable logger mock: by default no-ops; individual tests can override
46
+ // mockLogInfo to write to process.stdout so the JSON-mode suppression guard is
47
+ // exercised (the real CLI logger writes log lines to stdout).
48
+ let mockLogInfo: (msg: string) => void = () => {};
49
+
50
+ let mockCliIpcCallFn: (
51
+ method: string,
52
+ params?: Record<string, unknown>,
53
+ opts?: { timeoutMs?: number },
54
+ ) => Promise<{ ok: boolean; result?: unknown; error?: string; statusCode?: number }> = async () => ({
55
+ ok: false,
56
+ error: "IPC unavailable (default mock — forces fallback)",
57
+ });
58
+
45
59
  // ---------------------------------------------------------------------------
46
60
  // Mocks
47
61
  // ---------------------------------------------------------------------------
@@ -101,7 +115,7 @@ mock.module("../../../../util/logger.js", () => ({
101
115
  debug: () => {},
102
116
  }),
103
117
  getCliLogger: () => ({
104
- info: () => {},
118
+ info: (msg: string) => mockLogInfo(msg),
105
119
  warn: () => {},
106
120
  error: () => {},
107
121
  debug: () => {},
@@ -127,6 +141,14 @@ mock.module("../../../../security/secure-keys.js", () => ({
127
141
  _resetBackend: () => {},
128
142
  }));
129
143
 
144
+ mock.module("../../../../ipc/cli-client.js", () => ({
145
+ cliIpcCall: (
146
+ method: string,
147
+ params?: Record<string, unknown>,
148
+ opts?: { timeoutMs?: number },
149
+ ) => mockCliIpcCallFn(method, params, opts),
150
+ }));
151
+
130
152
  mock.module("../../../lib/daemon-credential-client.js", () => ({
131
153
  deleteSecureKeyViaDaemon: async () => "not-found" as const,
132
154
  setSecureKeyViaDaemon: async () => false,
@@ -254,6 +276,8 @@ describe("assistant oauth connect", () => {
254
276
  mockPlatformFetchResults = [];
255
277
  mockPlatformFetchCallIndex = 0;
256
278
  mockIsManagedMode = () => false;
279
+ mockCliIpcCallFn = async () => ({ ok: false, error: "IPC unavailable" });
280
+ mockLogInfo = () => {};
257
281
  process.exitCode = 0;
258
282
  });
259
283
 
@@ -371,106 +395,6 @@ describe("assistant oauth connect", () => {
371
395
  );
372
396
  });
373
397
 
374
- // -------------------------------------------------------------------------
375
- // BYO mode with --no-browser: prints auth URL (deferred)
376
- // -------------------------------------------------------------------------
377
-
378
- test("BYO mode with --no-browser: prints auth URL", async () => {
379
- mockGetProvider = () => ({
380
- provider: "google",
381
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
382
- tokenExchangeUrl: "https://oauth2.googleapis.com/token",
383
- tokenExchangeBodyFormat: "form",
384
- managedServiceConfigKey: null,
385
- });
386
- mockIsManagedMode = () => false;
387
-
388
- mockGetMostRecentAppByProvider = () => ({
389
- id: "app-1",
390
- clientId: "byo-client-id",
391
- clientSecretCredentialPath: "oauth_app/app-1/client_secret",
392
- provider: "google",
393
- createdAt: 0,
394
- updatedAt: 0,
395
- });
396
-
397
- mockOrchestrateOAuthConnect = async () => ({
398
- success: true,
399
- deferred: true,
400
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth?state=abc",
401
- state: "abc",
402
- service: "google",
403
- });
404
-
405
- const { exitCode, stdout } = await runCommand([
406
- "connect",
407
- "google",
408
- "--no-browser",
409
- "--json",
410
- ]);
411
- expect(exitCode).toBe(0);
412
- const parsed = JSON.parse(stdout);
413
- expect(parsed.ok).toBe(true);
414
- expect(parsed.deferred).toBe(true);
415
- expect(parsed.authUrl).toBe(
416
- "https://accounts.google.com/o/oauth2/v2/auth?state=abc",
417
- );
418
- expect(parsed.service).toBe("google");
419
- });
420
-
421
- // -------------------------------------------------------------------------
422
- // BYO mode default: orchestrator called with isInteractive true
423
- // -------------------------------------------------------------------------
424
-
425
- test("BYO mode default calls orchestrator with isInteractive: true", async () => {
426
- mockGetProvider = () => ({
427
- provider: "google",
428
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
429
- tokenExchangeUrl: "https://oauth2.googleapis.com/token",
430
- tokenExchangeBodyFormat: "form",
431
- managedServiceConfigKey: null,
432
- });
433
- mockIsManagedMode = () => false;
434
-
435
- mockGetAppByProviderAndClientId = () => ({
436
- id: "app-1",
437
- clientId: "test-id",
438
- clientSecretCredentialPath: "oauth_app/app-1/client_secret",
439
- provider: "google",
440
- createdAt: 0,
441
- updatedAt: 0,
442
- });
443
-
444
- let capturedOpts: Record<string, unknown> | undefined;
445
- mockOrchestrateOAuthConnect = async (opts) => {
446
- capturedOpts = opts;
447
- return {
448
- success: true,
449
- deferred: false,
450
- grantedScopes: ["email"],
451
- accountInfo: "user@example.com",
452
- };
453
- };
454
-
455
- const { exitCode, stdout } = await runCommand([
456
- "connect",
457
- "google",
458
- "--client-id",
459
- "test-id",
460
- "--json",
461
- ]);
462
- expect(exitCode).toBe(0);
463
- expect(capturedOpts).toBeDefined();
464
- expect(capturedOpts!.isInteractive).toBe(true);
465
- // openUrl should be provided by default (browser opens automatically)
466
- expect(typeof capturedOpts!.openUrl).toBe("function");
467
-
468
- const parsed = JSON.parse(stdout);
469
- expect(parsed.ok).toBe(true);
470
- expect(parsed.grantedScopes).toEqual(["email"]);
471
- expect(parsed.accountInfo).toBe("user@example.com");
472
- });
473
-
474
398
  // -------------------------------------------------------------------------
475
399
  // BYO missing app: error with hint
476
400
  // -------------------------------------------------------------------------
@@ -566,96 +490,6 @@ describe("assistant oauth connect", () => {
566
490
  );
567
491
  });
568
492
 
569
- // -------------------------------------------------------------------------
570
- // JSON output format for deferred case (BYO)
571
- // -------------------------------------------------------------------------
572
-
573
- test("JSON output for deferred case includes ok, deferred, authUrl, service", async () => {
574
- mockGetProvider = () => ({
575
- provider: "slack",
576
- authorizeUrl: "https://slack.com/oauth/v2/authorize",
577
- tokenExchangeUrl: "https://slack.com/api/oauth.v2.access",
578
- tokenExchangeBodyFormat: "form",
579
- managedServiceConfigKey: null,
580
- });
581
- mockIsManagedMode = () => false;
582
-
583
- mockGetMostRecentAppByProvider = () => ({
584
- id: "app-slack",
585
- clientId: "slack-client-id",
586
- clientSecretCredentialPath: "oauth_app/app-slack/client_secret",
587
- provider: "slack",
588
- createdAt: 0,
589
- updatedAt: 0,
590
- });
591
-
592
- mockOrchestrateOAuthConnect = async () => ({
593
- success: true,
594
- deferred: true,
595
- authorizeUrl: "https://slack.com/oauth/v2/authorize?state=xyz",
596
- state: "xyz",
597
- service: "slack",
598
- });
599
-
600
- const { exitCode, stdout } = await runCommand([
601
- "connect",
602
- "slack",
603
- "--no-browser",
604
- "--json",
605
- ]);
606
- expect(exitCode).toBe(0);
607
- const parsed = JSON.parse(stdout);
608
- expect(parsed).toHaveProperty("ok", true);
609
- expect(parsed).toHaveProperty("deferred", true);
610
- expect(parsed).toHaveProperty("authUrl");
611
- expect(parsed).toHaveProperty("service", "slack");
612
- });
613
-
614
- // -------------------------------------------------------------------------
615
- // JSON output format for completed case (BYO)
616
- // -------------------------------------------------------------------------
617
-
618
- test("JSON output for completed case includes ok, grantedScopes, accountInfo", async () => {
619
- mockGetProvider = () => ({
620
- provider: "google",
621
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
622
- tokenExchangeUrl: "https://oauth2.googleapis.com/token",
623
- tokenExchangeBodyFormat: "form",
624
- managedServiceConfigKey: null,
625
- });
626
- mockIsManagedMode = () => false;
627
-
628
- mockGetMostRecentAppByProvider = () => ({
629
- id: "app-1",
630
- clientId: "completed-client-id",
631
- clientSecretCredentialPath: "oauth_app/app-1/client_secret",
632
- provider: "google",
633
- createdAt: 0,
634
- updatedAt: 0,
635
- });
636
-
637
- mockOrchestrateOAuthConnect = async () => ({
638
- success: true,
639
- deferred: false,
640
- grantedScopes: ["email", "profile"],
641
- accountInfo: "test@gmail.com",
642
- });
643
-
644
- const { exitCode, stdout } = await runCommand([
645
- "connect",
646
- "google",
647
- "--json",
648
- ]);
649
- expect(exitCode).toBe(0);
650
- const parsed = JSON.parse(stdout);
651
- expect(parsed).toHaveProperty("ok", true);
652
- expect(parsed).toHaveProperty("grantedScopes");
653
- expect(parsed.grantedScopes).toEqual(["email", "profile"]);
654
- expect(parsed).toHaveProperty("accountInfo", "test@gmail.com");
655
- // Should NOT have deferred
656
- expect(parsed.deferred).toBeUndefined();
657
- });
658
-
659
493
  // -------------------------------------------------------------------------
660
494
  // BYO mode: client_secret required but missing
661
495
  // -------------------------------------------------------------------------
@@ -725,41 +559,389 @@ describe("assistant oauth connect", () => {
725
559
  });
726
560
 
727
561
  // -------------------------------------------------------------------------
728
- // Orchestrator error propagation
562
+ // IPC-first path (daemon-orchestrated)
729
563
  // -------------------------------------------------------------------------
730
564
 
731
- test("BYO mode: orchestrator error propagates correctly", async () => {
732
- mockGetProvider = () => ({
733
- provider: "google",
734
- authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
735
- tokenExchangeUrl: "https://oauth2.googleapis.com/token",
736
- tokenExchangeBodyFormat: "form",
737
- managedServiceConfigKey: null,
565
+ describe("IPC-first path (BYO mode via daemon)", () => {
566
+ beforeEach(() => {
567
+ // Set up a valid BYO provider and app for all IPC tests
568
+ mockGetProvider = () => ({
569
+ provider: "google",
570
+ authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
571
+ tokenExchangeUrl: "https://oauth2.googleapis.com/token",
572
+ tokenExchangeBodyFormat: "form",
573
+ managedServiceConfigKey: null,
574
+ });
575
+ mockIsManagedMode = () => false;
576
+ mockGetMostRecentAppByProvider = () => ({
577
+ id: "app-1",
578
+ clientId: "ipc-client-id",
579
+ clientSecretCredentialPath: "oauth_app/app-1/client_secret",
580
+ provider: "google",
581
+ createdAt: 0,
582
+ updatedAt: 0,
583
+ });
738
584
  });
739
- mockIsManagedMode = () => false;
740
585
 
741
- mockGetMostRecentAppByProvider = () => ({
742
- id: "app-1",
743
- clientId: "client-id",
744
- clientSecretCredentialPath: "oauth_app/app-1/client_secret",
745
- provider: "google",
746
- createdAt: 0,
747
- updatedAt: 0,
586
+ test("IPC start succeeds + polling returns complete → exits 0 with success output", async () => {
587
+ let pollCallCount = 0;
588
+ mockCliIpcCallFn = async (method) => {
589
+ if (method === "internal_oauth_connect_start") {
590
+ return {
591
+ ok: true,
592
+ result: {
593
+ auth_url: "https://accounts.google.com/o/oauth2/auth?state=ipc-state",
594
+ state: "ipc-state",
595
+ },
596
+ };
597
+ }
598
+ if (method === "internal_oauth_connect_status") {
599
+ pollCallCount++;
600
+ return {
601
+ ok: true,
602
+ result: {
603
+ status: "complete",
604
+ service: "google",
605
+ account_info: "user@example.com",
606
+ },
607
+ };
608
+ }
609
+ return { ok: false, error: "unexpected method" };
610
+ };
611
+
612
+ const { exitCode, stdout } = await runCommand([
613
+ "connect",
614
+ "google",
615
+ "--json",
616
+ ]);
617
+ expect(exitCode).toBe(0);
618
+ const parsed = JSON.parse(stdout);
619
+ expect(parsed.ok).toBe(true);
620
+ expect(parsed.accountInfo).toBe("user@example.com");
621
+ expect(mockOpenInBrowserCalls.length).toBe(1);
622
+ expect(mockOpenInBrowserCalls[0]).toBe(
623
+ "https://accounts.google.com/o/oauth2/auth?state=ipc-state",
624
+ );
625
+ expect(pollCallCount).toBeGreaterThanOrEqual(1);
748
626
  });
749
627
 
750
- mockOrchestrateOAuthConnect = async () => ({
751
- success: false,
752
- error: "Token exchange failed: invalid_grant",
628
+ test("IPC start succeeds + polling returns error → exits 1 with error message", async () => {
629
+ mockCliIpcCallFn = async (method) => {
630
+ if (method === "internal_oauth_connect_start") {
631
+ return {
632
+ ok: true,
633
+ result: {
634
+ auth_url: "https://accounts.google.com/o/oauth2/auth?state=ipc-state",
635
+ state: "ipc-state",
636
+ },
637
+ };
638
+ }
639
+ if (method === "internal_oauth_connect_status") {
640
+ return {
641
+ ok: true,
642
+ result: {
643
+ status: "error",
644
+ service: "google",
645
+ error: "exchange failed",
646
+ },
647
+ };
648
+ }
649
+ return { ok: false, error: "unexpected method" };
650
+ };
651
+
652
+ const { exitCode, stdout } = await runCommand([
653
+ "connect",
654
+ "google",
655
+ "--json",
656
+ ]);
657
+ expect(exitCode).toBe(1);
658
+ const parsed = JSON.parse(stdout);
659
+ expect(parsed.ok).toBe(false);
660
+ expect(parsed.error).toBe("exchange failed");
753
661
  });
754
662
 
755
- const { exitCode, stdout } = await runCommand([
756
- "connect",
757
- "google",
758
- "--json",
759
- ]);
760
- expect(exitCode).toBe(1);
761
- const parsed = JSON.parse(stdout);
762
- expect(parsed.ok).toBe(false);
763
- expect(parsed.error).toBe("Token exchange failed: invalid_grant");
663
+ test("IPC start + --no-browser + json returns deferred JSON without polling status", async () => {
664
+ let statusCallCount = 0;
665
+ mockCliIpcCallFn = async (method) => {
666
+ if (method === "internal_oauth_connect_start") {
667
+ return {
668
+ ok: true,
669
+ result: {
670
+ auth_url: "https://accounts.google.com/o/oauth2/auth?state=ipc-state",
671
+ state: "ipc-state",
672
+ },
673
+ };
674
+ }
675
+ if (method === "internal_oauth_connect_status") {
676
+ statusCallCount++;
677
+ }
678
+ return { ok: false, error: "unexpected method" };
679
+ };
680
+
681
+ const { exitCode, stdout } = await runCommand([
682
+ "connect",
683
+ "google",
684
+ "--no-browser",
685
+ "--json",
686
+ ]);
687
+ expect(exitCode).toBe(0);
688
+ const parsed = JSON.parse(stdout);
689
+ expect(parsed.ok).toBe(true);
690
+ expect(parsed.deferred).toBe(true);
691
+ expect(parsed.authUrl).toBe("https://accounts.google.com/o/oauth2/auth?state=ipc-state");
692
+ expect(parsed.state).toBe("ipc-state");
693
+ expect(parsed.service).toBe("google");
694
+ // Should NOT poll status when --no-browser is set
695
+ expect(statusCallCount).toBe(0);
696
+ // Should NOT open browser
697
+ expect(mockOpenInBrowserCalls.length).toBe(0);
698
+ });
699
+
700
+ test("IPC start + --no-browser without json → prints URL to stdout", async () => {
701
+ mockCliIpcCallFn = async (method) => {
702
+ if (method === "internal_oauth_connect_start") {
703
+ return {
704
+ ok: true,
705
+ result: {
706
+ auth_url: "https://accounts.google.com/o/oauth2/auth?state=ipc-state",
707
+ state: "ipc-state",
708
+ },
709
+ };
710
+ }
711
+ return { ok: false, error: "unexpected method" };
712
+ };
713
+
714
+ const { exitCode, stdout } = await runCommand([
715
+ "connect",
716
+ "google",
717
+ "--no-browser",
718
+ ]);
719
+ expect(exitCode).toBe(0);
720
+ expect(stdout).toContain("https://accounts.google.com/o/oauth2/auth?state=ipc-state");
721
+ expect(mockOpenInBrowserCalls.length).toBe(0);
722
+ });
723
+
724
+ test("IPC returns ok:false with statusCode → surfaces daemon error, does NOT fall back", async () => {
725
+ // Daemon was reachable but returned an error (e.g. 500)
726
+ mockCliIpcCallFn = async (method) => {
727
+ if (method === "internal_oauth_connect_start") {
728
+ return { ok: false, statusCode: 500, error: "internal server error" };
729
+ }
730
+ return { ok: false, error: "unexpected method" };
731
+ };
732
+ let orchestratorCalled = false;
733
+ mockOrchestrateOAuthConnect = async () => {
734
+ orchestratorCalled = true;
735
+ return { success: true, deferred: false, grantedScopes: [] };
736
+ };
737
+
738
+ const { exitCode, stdout } = await runCommand([
739
+ "connect",
740
+ "google",
741
+ "--json",
742
+ ]);
743
+ expect(exitCode).toBe(1);
744
+ // Must NOT fall back to the in-process orchestrator
745
+ expect(orchestratorCalled).toBe(false);
746
+ const parsed = JSON.parse(stdout);
747
+ expect(parsed.ok).toBe(false);
748
+ expect(parsed.error).toBe("internal server error");
749
+ });
750
+
751
+ test("IPC poll returns ok:false with statusCode → breaks early with error, does NOT wait for timeout", async () => {
752
+ // Fix 1: daemon was reachable during status poll but errored — should surface the
753
+ // error immediately instead of waiting out the full 5-minute timeout.
754
+ mockCliIpcCallFn = async (method) => {
755
+ if (method === "internal_oauth_connect_start") {
756
+ return {
757
+ ok: true,
758
+ result: {
759
+ auth_url: "https://accounts.google.com/o/oauth2/auth?state=poll-err-state",
760
+ state: "poll-err-state",
761
+ },
762
+ };
763
+ }
764
+ if (method === "internal_oauth_connect_status") {
765
+ return { ok: false, statusCode: 500, error: "poll error" };
766
+ }
767
+ return { ok: false, error: "unexpected method" };
768
+ };
769
+
770
+ const { exitCode, stdout } = await runCommand([
771
+ "connect",
772
+ "google",
773
+ "--json",
774
+ ]);
775
+ expect(exitCode).toBe(1);
776
+ const parsed = JSON.parse(stdout);
777
+ expect(parsed.ok).toBe(false);
778
+ // The daemon error should be surfaced, not a timeout sentinel
779
+ expect(parsed.error).toBe("poll error");
780
+ });
781
+
782
+ test("IPC start returns ok:true with no auth_url → surfaces error, does NOT call in-process orchestrator", async () => {
783
+ // Fix 2: daemon returns { ok: true } but without an auth_url — malformed response
784
+ // should be an error, not a silent fallback to in-process (which has heap-split bug).
785
+ mockCliIpcCallFn = async (method) => {
786
+ if (method === "internal_oauth_connect_start") {
787
+ return { ok: true, result: {} };
788
+ }
789
+ return { ok: false, error: "unexpected method" };
790
+ };
791
+ let orchestratorCalled = false;
792
+ mockOrchestrateOAuthConnect = async () => {
793
+ orchestratorCalled = true;
794
+ return { success: true, deferred: false, grantedScopes: [] };
795
+ };
796
+
797
+ const { exitCode, stdout } = await runCommand([
798
+ "connect",
799
+ "google",
800
+ "--json",
801
+ ]);
802
+ expect(exitCode).toBe(1);
803
+ // Must NOT fall back to the in-process orchestrator
804
+ expect(orchestratorCalled).toBe(false);
805
+ const parsed = JSON.parse(stdout);
806
+ expect(parsed.ok).toBe(false);
807
+ expect(parsed.error).toContain("assistant returned unexpected response");
808
+ });
809
+
810
+ test("IPC poll: transient ok:false with no statusCode does not abort the flow (continues to next poll)", async () => {
811
+ // Verifies intentional behavior: a single IPC status call returning { ok: false }
812
+ // with NO statusCode (socket error / timeout) is treated as a transient failure and
813
+ // silently retried. Only ok:false WITH a statusCode (i.e., the daemon was reachable
814
+ // and returned an HTTP error) causes an early abort.
815
+ let statusCallCount = 0;
816
+ mockCliIpcCallFn = async (method) => {
817
+ if (method === "internal_oauth_connect_start") {
818
+ return {
819
+ ok: true,
820
+ result: {
821
+ auth_url: "https://accounts.google.com/o/oauth2/auth?state=transient-state",
822
+ state: "transient-state",
823
+ },
824
+ };
825
+ }
826
+ if (method === "internal_oauth_connect_status") {
827
+ statusCallCount++;
828
+ if (statusCallCount === 1) {
829
+ // First poll: transient IPC failure (no statusCode — socket error/timeout)
830
+ return { ok: false };
831
+ }
832
+ // Second poll: succeeds
833
+ return {
834
+ ok: true,
835
+ result: {
836
+ status: "complete",
837
+ service: "google",
838
+ account_info: "user@example.com",
839
+ },
840
+ };
841
+ }
842
+ return { ok: false, error: "unexpected method" };
843
+ };
844
+
845
+ const { exitCode, stdout } = await runCommand([
846
+ "connect",
847
+ "google",
848
+ "--json",
849
+ ]);
850
+ expect(exitCode).toBe(0);
851
+ const parsed = JSON.parse(stdout);
852
+ expect(parsed.ok).toBe(true);
853
+ expect(parsed.accountInfo).toBe("user@example.com");
854
+ // Both poll calls were made — the transient failure did not abort the loop
855
+ expect(statusCallCount).toBeGreaterThanOrEqual(2);
856
+ });
857
+
858
+ test("IPC success path with --json: stdout does NOT contain 'Waiting for authorization' text", async () => {
859
+ // Regression guard for P1: the browser-wait log.info must be suppressed in JSON mode
860
+ // so that machine consumers parsing stdout as JSON don't see corrupted non-JSON output.
861
+ //
862
+ // We configure the logger mock to write to process.stdout (matching the real CLI logger's
863
+ // behavior) so this test would FAIL if the `if (!jsonMode)` guard were removed from connect.ts.
864
+ mockLogInfo = (msg: string) => {
865
+ process.stdout.write(msg + "\n");
866
+ };
867
+
868
+ mockCliIpcCallFn = async (method) => {
869
+ if (method === "internal_oauth_connect_start") {
870
+ return {
871
+ ok: true,
872
+ result: {
873
+ auth_url: "https://accounts.google.com/o/oauth2/auth?state=json-mode-state",
874
+ state: "json-mode-state",
875
+ },
876
+ };
877
+ }
878
+ if (method === "internal_oauth_connect_status") {
879
+ return {
880
+ ok: true,
881
+ result: {
882
+ status: "complete",
883
+ service: "google",
884
+ account_info: "user@example.com",
885
+ },
886
+ };
887
+ }
888
+ return { ok: false, error: "unexpected method" };
889
+ };
890
+
891
+ const { exitCode, stdout } = await runCommand([
892
+ "connect",
893
+ "google",
894
+ "--json",
895
+ ]);
896
+ expect(exitCode).toBe(0);
897
+ // The suppressed log line must not appear anywhere in stdout
898
+ expect(stdout).not.toContain("Waiting for authorization");
899
+ // stdout must be valid JSON — no plain-text lines mixed in
900
+ expect(() => JSON.parse(stdout)).not.toThrow();
901
+ const parsed = JSON.parse(stdout);
902
+ expect(parsed.ok).toBe(true);
903
+ });
904
+
905
+ test("IPC start with --callback-transport=gateway passes callbackTransport in body", async () => {
906
+ let capturedParams: Record<string, unknown> | undefined;
907
+ mockCliIpcCallFn = async (method, params) => {
908
+ if (method === "internal_oauth_connect_start") {
909
+ capturedParams = params;
910
+ return {
911
+ ok: true,
912
+ result: {
913
+ auth_url: "https://accounts.google.com/o/oauth2/auth?state=gw-state",
914
+ state: "gw-state",
915
+ },
916
+ };
917
+ }
918
+ if (method === "internal_oauth_connect_status") {
919
+ return {
920
+ ok: true,
921
+ result: {
922
+ status: "complete",
923
+ service: "google",
924
+ account_info: "gw-user@example.com",
925
+ },
926
+ };
927
+ }
928
+ return { ok: false, error: "unexpected method" };
929
+ };
930
+
931
+ const { exitCode, stdout } = await runCommand([
932
+ "connect",
933
+ "google",
934
+ "--callback-transport",
935
+ "gateway",
936
+ "--json",
937
+ ]);
938
+ expect(exitCode).toBe(0);
939
+ // Verify callbackTransport was forwarded in the IPC body
940
+ expect(capturedParams).toBeDefined();
941
+ expect((capturedParams!.body as Record<string, unknown>).callbackTransport).toBe("gateway");
942
+ const parsed = JSON.parse(stdout);
943
+ expect(parsed.ok).toBe(true);
944
+ expect(parsed.accountInfo).toBe("gw-user@example.com");
945
+ });
764
946
  });
765
947
  });