@vellumai/assistant 0.4.48 → 0.4.50

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 (423) hide show
  1. package/ARCHITECTURE.md +26 -35
  2. package/README.md +5 -26
  3. package/docs/architecture/integrations.md +45 -41
  4. package/docs/architecture/keychain-broker.md +3 -3
  5. package/docs/architecture/memory.md +180 -119
  6. package/docs/runbook-trusted-contacts.md +3 -8
  7. package/hook-templates/debug-prompt-logger/hook.json +1 -1
  8. package/hook-templates/debug-prompt-logger/run.sh +1 -3
  9. package/package.json +2 -2
  10. package/src/__tests__/actor-token-service.test.ts +0 -1
  11. package/src/__tests__/agent-loop.test.ts +3 -1
  12. package/src/__tests__/anthropic-provider.test.ts +249 -2
  13. package/src/__tests__/approval-cascade.test.ts +796 -0
  14. package/src/__tests__/approval-primitive.test.ts +0 -1
  15. package/src/__tests__/approval-routes-http.test.ts +4 -0
  16. package/src/__tests__/assistant-attachments.test.ts +12 -34
  17. package/src/__tests__/assistant-feature-flag-guard.test.ts +0 -23
  18. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +76 -0
  19. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -1
  20. package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
  21. package/src/__tests__/canonical-guardian-store.test.ts +95 -0
  22. package/src/__tests__/channel-guardian.test.ts +0 -2
  23. package/src/__tests__/channel-readiness-routes.test.ts +15 -6
  24. package/src/__tests__/channel-readiness-service.test.ts +10 -9
  25. package/src/__tests__/checker.test.ts +13 -20
  26. package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
  27. package/src/__tests__/computer-use-tools.test.ts +2 -19
  28. package/src/__tests__/config-schema.test.ts +1 -68
  29. package/src/__tests__/config-watcher.test.ts +0 -1
  30. package/src/__tests__/confirmation-request-guardian-bridge.test.ts +0 -1
  31. package/src/__tests__/context-image-dimensions.test.ts +332 -0
  32. package/src/__tests__/context-memory-e2e.test.ts +11 -100
  33. package/src/__tests__/context-token-estimator.test.ts +196 -13
  34. package/src/__tests__/conversation-attention-store.test.ts +0 -1
  35. package/src/__tests__/conversation-attention-telegram.test.ts +0 -1
  36. package/src/__tests__/conversation-routes-guardian-reply.test.ts +152 -0
  37. package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
  38. package/src/__tests__/credential-metadata-store.test.ts +64 -73
  39. package/src/__tests__/credential-security-e2e.test.ts +1 -0
  40. package/src/__tests__/credential-security-invariants.test.ts +13 -7
  41. package/src/__tests__/credential-vault-unit.test.ts +284 -49
  42. package/src/__tests__/credential-vault.test.ts +150 -16
  43. package/src/__tests__/credentials-cli.test.ts +71 -0
  44. package/src/__tests__/cu-unified-flow.test.ts +532 -0
  45. package/src/__tests__/date-context.test.ts +93 -77
  46. package/src/__tests__/deterministic-verification-control-plane.test.ts +64 -0
  47. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +0 -1
  48. package/src/__tests__/ephemeral-permissions.test.ts +3 -3
  49. package/src/__tests__/gateway-only-guard.test.ts +0 -1
  50. package/src/__tests__/guardian-action-grant-mint-consume.test.ts +0 -1
  51. package/src/__tests__/guardian-decision-primitive-canonical.test.ts +0 -1
  52. package/src/__tests__/guardian-routing-invariants.test.ts +93 -1
  53. package/src/__tests__/guardian-verification-voice-binding.test.ts +0 -1
  54. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -39
  55. package/src/__tests__/heartbeat-service.test.ts +0 -1
  56. package/src/__tests__/history-repair.test.ts +245 -0
  57. package/src/__tests__/host-cu-proxy.test.ts +791 -0
  58. package/src/__tests__/host-shell-tool.test.ts +27 -15
  59. package/src/__tests__/http-user-message-parity.test.ts +2 -0
  60. package/src/__tests__/ingress-url-consistency.test.ts +14 -21
  61. package/src/__tests__/integration-status.test.ts +32 -51
  62. package/src/__tests__/intent-routing.test.ts +0 -1
  63. package/src/__tests__/invite-redemption-service.test.ts +65 -1
  64. package/src/__tests__/invite-routes-http.test.ts +10 -9
  65. package/src/__tests__/keychain-broker-client.test.ts +14 -46
  66. package/src/__tests__/memory-context-benchmark.benchmark.test.ts +56 -18
  67. package/src/__tests__/memory-lifecycle-e2e.test.ts +244 -387
  68. package/src/__tests__/memory-recall-quality.test.ts +244 -407
  69. package/src/__tests__/memory-regressions.experimental.test.ts +126 -101
  70. package/src/__tests__/memory-regressions.test.ts +477 -2841
  71. package/src/__tests__/memory-retrieval.benchmark.test.ts +33 -150
  72. package/src/__tests__/memory-upsert-concurrency.test.ts +5 -244
  73. package/src/__tests__/mime-builder.test.ts +28 -0
  74. package/src/__tests__/native-web-search.test.ts +1 -0
  75. package/src/__tests__/notification-routing-intent.test.ts +0 -1
  76. package/src/__tests__/oauth-cli.test.ts +941 -15
  77. package/src/__tests__/oauth-provider-profiles.test.ts +9 -9
  78. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  79. package/src/__tests__/oauth-store.test.ts +870 -0
  80. package/src/__tests__/onboarding-starter-tasks.test.ts +0 -1
  81. package/src/__tests__/provider-error-scenarios.test.ts +0 -1
  82. package/src/__tests__/provider-streaming.benchmark.test.ts +0 -1
  83. package/src/__tests__/public-ingress-urls.test.ts +15 -21
  84. package/src/__tests__/qdrant-collection-migration.test.ts +53 -8
  85. package/src/__tests__/recording-handler.test.ts +3 -4
  86. package/src/__tests__/registry.test.ts +2 -3
  87. package/src/__tests__/relay-server.test.ts +46 -1
  88. package/src/__tests__/runtime-events-sse.test.ts +55 -7
  89. package/src/__tests__/schedule-store.test.ts +0 -1
  90. package/src/__tests__/schedule-tools.test.ts +32 -0
  91. package/src/__tests__/scheduler-recurrence.test.ts +0 -1
  92. package/src/__tests__/scoped-approval-grants.test.ts +0 -1
  93. package/src/__tests__/scoped-grant-security-matrix.test.ts +0 -1
  94. package/src/__tests__/script-proxy-certs.test.ts +1 -1
  95. package/src/__tests__/secret-ingress-handler.test.ts +0 -1
  96. package/src/__tests__/secret-onetime-send.test.ts +1 -0
  97. package/src/__tests__/secure-keys.test.ts +7 -2
  98. package/src/__tests__/send-endpoint-busy.test.ts +24 -6
  99. package/src/__tests__/sequence-store.test.ts +0 -1
  100. package/src/__tests__/session-abort-tool-results.test.ts +1 -14
  101. package/src/__tests__/session-agent-loop-overflow.test.ts +1583 -0
  102. package/src/__tests__/session-agent-loop.test.ts +19 -15
  103. package/src/__tests__/session-confirmation-signals.test.ts +1 -15
  104. package/src/__tests__/session-error.test.ts +124 -2
  105. package/src/__tests__/session-history-web-search.test.ts +918 -0
  106. package/src/__tests__/session-init.benchmark.test.ts +4 -5
  107. package/src/__tests__/session-pre-run-repair.test.ts +1 -14
  108. package/src/__tests__/session-provider-retry-repair.test.ts +25 -28
  109. package/src/__tests__/session-queue.test.ts +37 -27
  110. package/src/__tests__/session-runtime-assembly.test.ts +54 -0
  111. package/src/__tests__/session-slash-known.test.ts +1 -15
  112. package/src/__tests__/session-slash-queue.test.ts +1 -15
  113. package/src/__tests__/session-slash-unknown.test.ts +1 -15
  114. package/src/__tests__/session-workspace-cache-state.test.ts +3 -33
  115. package/src/__tests__/session-workspace-injection.test.ts +3 -37
  116. package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -37
  117. package/src/__tests__/skill-include-graph.test.ts +66 -0
  118. package/src/__tests__/skill-load-feature-flag.test.ts +0 -1
  119. package/src/__tests__/skill-load-tool.test.ts +149 -1
  120. package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
  121. package/src/__tests__/skills-install-extract.test.ts +93 -0
  122. package/src/__tests__/skills-uninstall.test.ts +1 -1
  123. package/src/__tests__/skills.test.ts +3 -3
  124. package/src/__tests__/skillssh-registry.test.ts +451 -0
  125. package/src/__tests__/slack-channel-config.test.ts +67 -3
  126. package/src/__tests__/slack-share-routes.test.ts +17 -19
  127. package/src/__tests__/system-prompt.test.ts +0 -1
  128. package/src/__tests__/telegram-invite-adapter.test.ts +18 -22
  129. package/src/__tests__/terminal-tools.test.ts +4 -3
  130. package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
  131. package/src/__tests__/tool-approval-handler.test.ts +0 -1
  132. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +0 -1
  133. package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
  134. package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
  135. package/src/__tests__/tool-executor.test.ts +0 -1
  136. package/src/__tests__/tool-grant-request-escalation.test.ts +0 -1
  137. package/src/__tests__/trust-store-pattern-matches.test.ts +29 -0
  138. package/src/__tests__/trust-store.test.ts +7 -13
  139. package/src/__tests__/trusted-contact-approval-notifier.test.ts +0 -1
  140. package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +0 -1
  141. package/src/__tests__/twilio-routes.test.ts +0 -16
  142. package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
  143. package/src/__tests__/voice-invite-redemption.test.ts +32 -1
  144. package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
  145. package/src/agent/ax-tree-compaction.test.ts +286 -0
  146. package/src/agent/loop.ts +104 -131
  147. package/src/approvals/AGENTS.md +1 -1
  148. package/src/approvals/guardian-request-resolvers.ts +14 -2
  149. package/src/bundler/compiler-tools.ts +66 -2
  150. package/src/calls/call-domain.ts +133 -6
  151. package/src/calls/call-store.ts +6 -0
  152. package/src/calls/relay-server.ts +52 -18
  153. package/src/calls/relay-setup-router.ts +17 -1
  154. package/src/calls/twilio-config.ts +3 -8
  155. package/src/calls/twilio-routes.ts +1 -2
  156. package/src/calls/types.ts +3 -1
  157. package/src/calls/voice-ingress-preflight.ts +1 -1
  158. package/src/cli/commands/browser-relay.ts +18 -12
  159. package/src/cli/commands/completions.ts +0 -3
  160. package/src/cli/commands/credentials.ts +101 -15
  161. package/src/cli/commands/doctor.ts +4 -3
  162. package/src/cli/commands/mcp.ts +46 -59
  163. package/src/cli/commands/memory.ts +16 -165
  164. package/src/cli/commands/oauth/apps.ts +284 -0
  165. package/src/cli/commands/oauth/connections.ts +633 -0
  166. package/src/cli/commands/oauth/index.ts +52 -0
  167. package/src/cli/commands/oauth/providers.ts +256 -0
  168. package/src/cli/commands/sessions.ts +5 -2
  169. package/src/cli/commands/skills.ts +177 -339
  170. package/src/cli/http-client.ts +0 -20
  171. package/src/cli/main-screen.tsx +2 -2
  172. package/src/cli/program.ts +6 -11
  173. package/src/cli/reference.ts +1 -3
  174. package/src/cli.ts +4 -10
  175. package/src/config/assistant-feature-flags.ts +0 -3
  176. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  177. package/src/config/bundled-skills/computer-use/SKILL.md +3 -6
  178. package/src/config/bundled-skills/computer-use/TOOLS.json +23 -5
  179. package/src/config/bundled-skills/computer-use/tools/{computer-use-request-control.ts → computer-use-observe.ts} +1 -5
  180. package/src/config/bundled-skills/google-calendar/calendar-client.ts +21 -16
  181. package/src/config/bundled-skills/messaging/tools/shared.ts +1 -4
  182. package/src/config/bundled-skills/settings/SKILL.md +1 -1
  183. package/src/config/bundled-skills/settings/TOOLS.json +2 -8
  184. package/src/config/bundled-skills/settings/tools/voice-config-update.ts +5 -33
  185. package/src/config/bundled-tool-registry.ts +2 -5
  186. package/src/config/env-registry.ts +14 -83
  187. package/src/config/env.ts +11 -50
  188. package/src/config/feature-flag-registry.json +16 -16
  189. package/src/config/loader.ts +0 -6
  190. package/src/config/schema.ts +4 -13
  191. package/src/config/schemas/memory-lifecycle.ts +0 -9
  192. package/src/config/schemas/memory-processing.ts +0 -180
  193. package/src/config/schemas/memory-retrieval.ts +32 -104
  194. package/src/config/schemas/memory.ts +0 -10
  195. package/src/config/skills.ts +21 -2
  196. package/src/config/types.ts +0 -4
  197. package/src/context/image-dimensions.ts +229 -0
  198. package/src/context/token-estimator.ts +75 -12
  199. package/src/context/window-manager.ts +53 -11
  200. package/src/daemon/assistant-attachments.ts +1 -13
  201. package/src/daemon/config-watcher.ts +61 -3
  202. package/src/daemon/daemon-control.ts +1 -1
  203. package/src/daemon/date-context.ts +114 -31
  204. package/src/daemon/handlers/config-ingress.ts +8 -33
  205. package/src/daemon/handlers/config-slack-channel.ts +49 -46
  206. package/src/daemon/handlers/config-telegram.ts +32 -16
  207. package/src/daemon/handlers/sessions.ts +27 -36
  208. package/src/daemon/handlers/shared.ts +0 -130
  209. package/src/daemon/handlers/skills.ts +20 -1
  210. package/src/daemon/history-repair.ts +72 -8
  211. package/src/daemon/host-cu-proxy.ts +430 -0
  212. package/src/daemon/lifecycle.ts +67 -71
  213. package/src/daemon/mcp-reload-service.ts +2 -2
  214. package/src/daemon/message-protocol.ts +3 -0
  215. package/src/daemon/message-types/computer-use.ts +1 -129
  216. package/src/daemon/message-types/host-cu.ts +19 -0
  217. package/src/daemon/message-types/memory.ts +4 -16
  218. package/src/daemon/message-types/messages.ts +4 -0
  219. package/src/daemon/message-types/sessions.ts +4 -0
  220. package/src/daemon/server.ts +25 -21
  221. package/src/daemon/session-agent-loop-handlers.ts +40 -0
  222. package/src/daemon/session-agent-loop.ts +334 -48
  223. package/src/daemon/session-attachments.ts +1 -2
  224. package/src/daemon/session-error.ts +89 -6
  225. package/src/daemon/session-history.ts +17 -7
  226. package/src/daemon/session-media-retry.ts +6 -2
  227. package/src/daemon/session-memory.ts +69 -149
  228. package/src/daemon/session-process.ts +10 -1
  229. package/src/daemon/session-runtime-assembly.ts +49 -19
  230. package/src/daemon/session-slash.ts +1 -1
  231. package/src/daemon/session-surfaces.ts +43 -28
  232. package/src/daemon/session-tool-setup.ts +9 -10
  233. package/src/daemon/session.ts +150 -17
  234. package/src/daemon/tool-side-effects.ts +2 -8
  235. package/src/daemon/watch-handler.ts +2 -2
  236. package/src/events/tool-metrics-listener.ts +2 -2
  237. package/src/hooks/manager.ts +1 -4
  238. package/src/inbound/public-ingress-urls.ts +7 -7
  239. package/src/instrument.ts +61 -1
  240. package/src/logfire.ts +16 -5
  241. package/src/memory/admin.ts +2 -191
  242. package/src/memory/canonical-guardian-store.ts +38 -2
  243. package/src/memory/conversation-crud.ts +0 -33
  244. package/src/memory/conversation-key-store.ts +21 -0
  245. package/src/memory/conversation-queries.ts +22 -3
  246. package/src/memory/db-init.ts +32 -0
  247. package/src/memory/embedding-backend.ts +84 -8
  248. package/src/memory/embedding-types.ts +9 -1
  249. package/src/memory/indexer.ts +7 -46
  250. package/src/memory/items-extractor.ts +274 -76
  251. package/src/memory/job-handlers/backfill.ts +2 -127
  252. package/src/memory/job-handlers/cleanup.ts +2 -16
  253. package/src/memory/job-handlers/extraction.ts +2 -138
  254. package/src/memory/job-handlers/index-maintenance.ts +1 -6
  255. package/src/memory/job-handlers/summarization.ts +3 -148
  256. package/src/memory/job-utils.ts +21 -59
  257. package/src/memory/jobs-store.ts +1 -159
  258. package/src/memory/jobs-worker.ts +9 -52
  259. package/src/memory/migrations/104-core-indexes.ts +3 -3
  260. package/src/memory/migrations/149-oauth-tables.ts +62 -0
  261. package/src/memory/migrations/150-oauth-apps-client-secret-path.ts +98 -0
  262. package/src/memory/migrations/151-oauth-providers-ping-url.ts +11 -0
  263. package/src/memory/migrations/152-memory-item-supersession.ts +44 -0
  264. package/src/memory/migrations/153-drop-entity-tables.ts +15 -0
  265. package/src/memory/migrations/154-drop-fts.ts +20 -0
  266. package/src/memory/migrations/155-drop-conflicts.ts +7 -0
  267. package/src/memory/migrations/156-call-session-invite-metadata.ts +24 -0
  268. package/src/memory/migrations/index.ts +8 -0
  269. package/src/memory/qdrant-client.ts +148 -51
  270. package/src/memory/raw-query.ts +1 -1
  271. package/src/memory/retriever.test.ts +294 -273
  272. package/src/memory/retriever.ts +421 -645
  273. package/src/memory/schema/calls.ts +2 -0
  274. package/src/memory/schema/index.ts +1 -0
  275. package/src/memory/schema/memory-core.ts +3 -48
  276. package/src/memory/schema/oauth.ts +67 -0
  277. package/src/memory/search/formatting.ts +263 -176
  278. package/src/memory/search/lexical.ts +1 -254
  279. package/src/memory/search/ranking.ts +0 -455
  280. package/src/memory/search/semantic.ts +100 -14
  281. package/src/memory/search/staleness.ts +47 -0
  282. package/src/memory/search/tier-classifier.ts +21 -0
  283. package/src/memory/search/types.ts +15 -77
  284. package/src/memory/task-memory-cleanup.ts +4 -6
  285. package/src/messaging/provider.ts +4 -4
  286. package/src/messaging/providers/gmail/client.ts +82 -2
  287. package/src/messaging/providers/gmail/mime-builder.ts +17 -7
  288. package/src/messaging/providers/gmail/people-client.ts +10 -10
  289. package/src/messaging/providers/telegram-bot/adapter.ts +17 -17
  290. package/src/messaging/providers/whatsapp/adapter.ts +11 -8
  291. package/src/messaging/registry.ts +2 -32
  292. package/src/notifications/copy-composer.ts +0 -5
  293. package/src/notifications/signal.ts +4 -5
  294. package/src/oauth/byo-connection.test.ts +133 -25
  295. package/src/oauth/byo-connection.ts +22 -6
  296. package/src/oauth/connect-orchestrator.ts +113 -57
  297. package/src/oauth/connect-types.ts +17 -23
  298. package/src/oauth/connection-resolver.ts +35 -11
  299. package/src/oauth/connection.ts +1 -1
  300. package/src/oauth/manual-token-connection.ts +104 -0
  301. package/src/oauth/oauth-store.ts +582 -0
  302. package/src/oauth/platform-connection.test.ts +29 -0
  303. package/src/oauth/platform-connection.ts +6 -5
  304. package/src/oauth/provider-behaviors.ts +124 -0
  305. package/src/oauth/scope-policy.ts +9 -2
  306. package/src/oauth/seed-providers.ts +167 -0
  307. package/src/oauth/token-persistence.ts +81 -77
  308. package/src/permissions/checker.ts +3 -3
  309. package/src/permissions/defaults.ts +1 -1
  310. package/src/permissions/prompter.ts +10 -1
  311. package/src/permissions/trust-store.ts +36 -1
  312. package/src/playbooks/playbook-compiler.ts +1 -1
  313. package/src/prompts/__tests__/build-cli-reference-section.test.ts +3 -1
  314. package/src/prompts/system-prompt.ts +46 -42
  315. package/src/providers/anthropic/client.ts +59 -20
  316. package/src/providers/retry.ts +1 -27
  317. package/src/providers/types.ts +7 -1
  318. package/src/runtime/AGENTS.md +9 -0
  319. package/src/runtime/auth/route-policy.ts +6 -6
  320. package/src/runtime/channel-reply-delivery.ts +0 -40
  321. package/src/runtime/gateway-client.ts +0 -7
  322. package/src/runtime/guardian-reply-router.ts +24 -22
  323. package/src/runtime/http-server.ts +10 -8
  324. package/src/runtime/http-types.ts +2 -2
  325. package/src/runtime/invite-redemption-service.ts +19 -1
  326. package/src/runtime/invite-service.ts +25 -0
  327. package/src/runtime/middleware/twilio-validation.ts +1 -11
  328. package/src/runtime/pending-interactions.ts +14 -12
  329. package/src/runtime/routes/brain-graph-routes.ts +10 -90
  330. package/src/runtime/routes/channel-delivery-routes.ts +0 -1
  331. package/src/runtime/routes/conversation-routes.ts +81 -19
  332. package/src/runtime/routes/events-routes.ts +21 -11
  333. package/src/runtime/routes/host-cu-routes.ts +97 -0
  334. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -12
  335. package/src/runtime/routes/inbound-stages/background-dispatch.ts +12 -111
  336. package/src/runtime/routes/integrations/slack/share.ts +6 -7
  337. package/src/runtime/routes/log-export-routes.ts +126 -8
  338. package/src/runtime/routes/memory-item-routes.test.ts +754 -0
  339. package/src/runtime/routes/memory-item-routes.ts +503 -0
  340. package/src/runtime/routes/session-management-routes.ts +3 -3
  341. package/src/runtime/routes/settings-routes.ts +55 -48
  342. package/src/runtime/routes/surface-action-routes.ts +1 -1
  343. package/src/runtime/routes/trust-rules-routes.ts +14 -0
  344. package/src/runtime/routes/watch-routes.ts +128 -0
  345. package/src/runtime/routes/workspace-routes.ts +2 -1
  346. package/src/schedule/integration-status.ts +10 -9
  347. package/src/security/credential-key.ts +0 -156
  348. package/src/security/keychain-broker-client.ts +22 -10
  349. package/src/security/oauth2.ts +1 -1
  350. package/src/security/secure-keys.ts +25 -3
  351. package/src/security/token-manager.ts +137 -64
  352. package/src/skills/catalog-install.ts +414 -0
  353. package/src/skills/include-graph.ts +32 -0
  354. package/src/skills/skillssh-registry.ts +503 -0
  355. package/src/telegram/bot-username.ts +2 -3
  356. package/src/tools/assets/search.ts +5 -1
  357. package/src/tools/browser/network-recorder.ts +1 -1
  358. package/src/tools/browser/network-recording-types.ts +1 -1
  359. package/src/tools/computer-use/definitions.ts +36 -11
  360. package/src/tools/computer-use/registry.ts +5 -6
  361. package/src/tools/credentials/broker.ts +1 -2
  362. package/src/tools/credentials/metadata-store.ts +17 -121
  363. package/src/tools/credentials/vault.ts +92 -167
  364. package/src/tools/memory/definitions.ts +4 -13
  365. package/src/tools/memory/handlers.test.ts +83 -103
  366. package/src/tools/memory/handlers.ts +50 -85
  367. package/src/tools/registry.ts +2 -7
  368. package/src/tools/schedule/create.ts +8 -1
  369. package/src/tools/schedule/update.ts +8 -1
  370. package/src/tools/skills/load.ts +85 -3
  371. package/src/tools/watch/watch-state.ts +0 -12
  372. package/src/util/logger.ts +7 -41
  373. package/src/util/platform.ts +9 -28
  374. package/src/watcher/providers/google-calendar.ts +2 -1
  375. package/src/__tests__/clarification-resolver.test.ts +0 -193
  376. package/src/__tests__/computer-use-session-compaction.test.ts +0 -143
  377. package/src/__tests__/computer-use-session-lifecycle.test.ts +0 -322
  378. package/src/__tests__/computer-use-session-working-dir.test.ts +0 -166
  379. package/src/__tests__/computer-use-skill-baseline.test.ts +0 -78
  380. package/src/__tests__/computer-use-skill-endstate.test.ts +0 -105
  381. package/src/__tests__/computer-use-skill-lifecycle-cleanup.test.ts +0 -249
  382. package/src/__tests__/conflict-intent-tokenization.test.ts +0 -160
  383. package/src/__tests__/conflict-policy.test.ts +0 -269
  384. package/src/__tests__/conflict-store.test.ts +0 -372
  385. package/src/__tests__/contradiction-checker.test.ts +0 -361
  386. package/src/__tests__/entity-extractor.test.ts +0 -211
  387. package/src/__tests__/entity-search.test.ts +0 -1117
  388. package/src/__tests__/profile-compiler.test.ts +0 -392
  389. package/src/__tests__/ride-shotgun-handler.test.ts +0 -452
  390. package/src/__tests__/session-conflict-gate.test.ts +0 -1228
  391. package/src/__tests__/session-profile-injection.test.ts +0 -557
  392. package/src/cli/commands/dev.ts +0 -129
  393. package/src/cli/commands/map.ts +0 -391
  394. package/src/cli/commands/oauth.ts +0 -77
  395. package/src/config/bundled-skills/knowledge-graph/SKILL.md +0 -25
  396. package/src/config/bundled-skills/knowledge-graph/TOOLS.json +0 -66
  397. package/src/config/bundled-skills/knowledge-graph/tools/graph-query.ts +0 -211
  398. package/src/daemon/computer-use-session.ts +0 -1026
  399. package/src/daemon/ride-shotgun-handler.ts +0 -569
  400. package/src/daemon/session-conflict-gate.ts +0 -167
  401. package/src/daemon/session-dynamic-profile.ts +0 -77
  402. package/src/memory/clarification-resolver.ts +0 -417
  403. package/src/memory/conflict-intent.ts +0 -205
  404. package/src/memory/conflict-policy.ts +0 -127
  405. package/src/memory/conflict-store.ts +0 -410
  406. package/src/memory/contradiction-checker.ts +0 -508
  407. package/src/memory/entity-extractor.ts +0 -535
  408. package/src/memory/format-recall.ts +0 -47
  409. package/src/memory/fts-reconciler.ts +0 -165
  410. package/src/memory/job-handlers/conflict.ts +0 -200
  411. package/src/memory/profile-compiler.ts +0 -195
  412. package/src/memory/recall-cache.ts +0 -117
  413. package/src/memory/search/entity.ts +0 -535
  414. package/src/memory/search/query-expansion.test.ts +0 -70
  415. package/src/memory/search/query-expansion.ts +0 -118
  416. package/src/oauth/provider-base-urls.ts +0 -21
  417. package/src/oauth/provider-profiles.ts +0 -192
  418. package/src/prompts/computer-use-prompt.ts +0 -98
  419. package/src/runtime/routes/computer-use-routes.ts +0 -641
  420. package/src/runtime/routes/mcp-routes.ts +0 -20
  421. package/src/runtime/telegram-streaming-delivery.test.ts +0 -729
  422. package/src/runtime/telegram-streaming-delivery.ts +0 -393
  423. package/src/tools/computer-use/request-computer-control.ts +0 -56
@@ -0,0 +1,451 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
+
3
+ import type {
4
+ AuditResponse,
5
+ SkillAuditData,
6
+ SkillsShSearchResult,
7
+ } from "../skills/skillssh-registry.js";
8
+ import {
9
+ fetchSkillAudits,
10
+ fetchSkillFromGitHub,
11
+ formatAuditBadges,
12
+ providerDisplayName,
13
+ resolveSkillSource,
14
+ riskToDisplay,
15
+ searchSkillsRegistry,
16
+ validateSkillSlug,
17
+ } from "../skills/skillssh-registry.js";
18
+
19
+ // ─── Fetch mock helpers ──────────────────────────────────────────────────────
20
+
21
+ const originalFetch = globalThis.fetch;
22
+
23
+ let mockFetchImpl: (url: string | URL | Request) => Promise<Response>;
24
+
25
+ beforeEach(() => {
26
+ mockFetchImpl = () =>
27
+ Promise.resolve(new Response("not mocked", { status: 500 }));
28
+ globalThis.fetch = mock((input: string | URL | Request) =>
29
+ mockFetchImpl(typeof input === "string" ? input : input.toString()),
30
+ ) as unknown as typeof fetch;
31
+ });
32
+
33
+ afterEach(() => {
34
+ globalThis.fetch = originalFetch;
35
+ });
36
+
37
+ // ─── searchSkillsRegistry ────────────────────────────────────────────────────
38
+
39
+ describe("searchSkillsRegistry", () => {
40
+ test("sends correct query parameters and returns results", async () => {
41
+ const mockResults: SkillsShSearchResult[] = [
42
+ {
43
+ id: "vercel-labs/agent-skills/vercel-react-best-practices",
44
+ skillId: "vercel-react-best-practices",
45
+ name: "Vercel React Best Practices",
46
+ installs: 1200,
47
+ source: "vercel-labs/agent-skills",
48
+ },
49
+ ];
50
+
51
+ mockFetchImpl = (url: string | URL | Request) => {
52
+ const urlStr = url.toString();
53
+ expect(urlStr).toContain("skills.sh/api/search");
54
+ expect(urlStr).toContain("q=react");
55
+ expect(urlStr).toContain("limit=5");
56
+ return Promise.resolve(
57
+ new Response(JSON.stringify({ skills: mockResults }), {
58
+ status: 200,
59
+ headers: { "Content-Type": "application/json" },
60
+ }),
61
+ );
62
+ };
63
+
64
+ const results = await searchSkillsRegistry("react", 5);
65
+ expect(results).toEqual(mockResults);
66
+ });
67
+
68
+ test("omits limit parameter when not provided", async () => {
69
+ mockFetchImpl = (url: string | URL | Request) => {
70
+ const urlStr = url.toString();
71
+ expect(urlStr).not.toContain("limit=");
72
+ return Promise.resolve(
73
+ new Response(JSON.stringify({ skills: [] }), {
74
+ status: 200,
75
+ headers: { "Content-Type": "application/json" },
76
+ }),
77
+ );
78
+ };
79
+
80
+ const results = await searchSkillsRegistry("test");
81
+ expect(results).toEqual([]);
82
+ });
83
+
84
+ test("throws on non-OK response", async () => {
85
+ mockFetchImpl = () =>
86
+ Promise.resolve(new Response("Not Found", { status: 404 }));
87
+
88
+ await expect(searchSkillsRegistry("bad-query")).rejects.toThrow(
89
+ "skills.sh search failed: HTTP 404",
90
+ );
91
+ });
92
+ });
93
+
94
+ // ─── fetchSkillAudits ────────────────────────────────────────────────────────
95
+
96
+ describe("fetchSkillAudits", () => {
97
+ test("sends correct parameters and returns audit data", async () => {
98
+ const mockAudits: AuditResponse = {
99
+ "vercel-react-best-practices": {
100
+ ath: {
101
+ risk: "safe",
102
+ alerts: 0,
103
+ score: 100,
104
+ analyzedAt: "2025-01-15T00:00:00Z",
105
+ },
106
+ socket: {
107
+ risk: "low",
108
+ alerts: 1,
109
+ score: 95,
110
+ analyzedAt: "2025-01-15T00:00:00Z",
111
+ },
112
+ },
113
+ };
114
+
115
+ mockFetchImpl = (url: string | URL | Request) => {
116
+ const urlStr = url.toString();
117
+ expect(urlStr).toContain("add-skill.vercel.sh/audit");
118
+ expect(urlStr).toContain("source=vercel-labs%2Fagent-skills");
119
+ expect(urlStr).toContain(
120
+ "skills=vercel-react-best-practices%2Canother-skill",
121
+ );
122
+ return Promise.resolve(
123
+ new Response(JSON.stringify(mockAudits), {
124
+ status: 200,
125
+ headers: { "Content-Type": "application/json" },
126
+ }),
127
+ );
128
+ };
129
+
130
+ const audits = await fetchSkillAudits("vercel-labs/agent-skills", [
131
+ "vercel-react-best-practices",
132
+ "another-skill",
133
+ ]);
134
+ expect(audits).toEqual(mockAudits);
135
+ });
136
+
137
+ test("returns empty object for empty slugs list", async () => {
138
+ const audits = await fetchSkillAudits("some/source", []);
139
+ expect(audits).toEqual({});
140
+ // fetch should not have been called
141
+ expect(globalThis.fetch).not.toHaveBeenCalled();
142
+ });
143
+
144
+ test("throws on non-OK response", async () => {
145
+ mockFetchImpl = () =>
146
+ Promise.resolve(new Response("Internal Server Error", { status: 500 }));
147
+
148
+ await expect(fetchSkillAudits("some/source", ["slug"])).rejects.toThrow(
149
+ "Audit fetch failed: HTTP 500",
150
+ );
151
+ });
152
+ });
153
+
154
+ // ─── Display helpers ─────────────────────────────────────────────────────────
155
+
156
+ describe("riskToDisplay", () => {
157
+ test("maps risk levels correctly", () => {
158
+ expect(riskToDisplay("safe")).toBe("PASS");
159
+ expect(riskToDisplay("low")).toBe("PASS");
160
+ expect(riskToDisplay("medium")).toBe("WARN");
161
+ expect(riskToDisplay("high")).toBe("FAIL");
162
+ expect(riskToDisplay("critical")).toBe("FAIL");
163
+ expect(riskToDisplay("unknown")).toBe("?");
164
+ });
165
+ });
166
+
167
+ describe("providerDisplayName", () => {
168
+ test("maps known providers", () => {
169
+ expect(providerDisplayName("ath")).toBe("ATH");
170
+ expect(providerDisplayName("socket")).toBe("Socket");
171
+ expect(providerDisplayName("snyk")).toBe("Snyk");
172
+ });
173
+
174
+ test("returns raw name for unknown providers", () => {
175
+ expect(providerDisplayName("custom-auditor")).toBe("custom-auditor");
176
+ });
177
+ });
178
+
179
+ describe("formatAuditBadges", () => {
180
+ test("formats multiple providers as badges", () => {
181
+ const auditData: SkillAuditData = {
182
+ ath: { risk: "safe", analyzedAt: "2025-01-15T00:00:00Z" },
183
+ socket: { risk: "safe", analyzedAt: "2025-01-15T00:00:00Z" },
184
+ snyk: { risk: "medium", analyzedAt: "2025-01-15T00:00:00Z" },
185
+ };
186
+ expect(formatAuditBadges(auditData)).toBe(
187
+ "Security: [ATH:PASS] [Socket:PASS] [Snyk:WARN]",
188
+ );
189
+ });
190
+
191
+ test("returns fallback message when no providers present", () => {
192
+ expect(formatAuditBadges({})).toBe("Security: no audit data");
193
+ });
194
+
195
+ test("handles single provider", () => {
196
+ const auditData: SkillAuditData = {
197
+ ath: { risk: "critical", analyzedAt: "2025-01-15T00:00:00Z" },
198
+ };
199
+ expect(formatAuditBadges(auditData)).toBe("Security: [ATH:FAIL]");
200
+ });
201
+ });
202
+
203
+ // ─── resolveSkillSource ─────────────────────────────────────────────────────
204
+
205
+ describe("resolveSkillSource", () => {
206
+ test("parses owner/repo@skill-name format", () => {
207
+ const result = resolveSkillSource("vercel-labs/skills@find-skills");
208
+ expect(result).toEqual({
209
+ owner: "vercel-labs",
210
+ repo: "skills",
211
+ skillSlug: "find-skills",
212
+ });
213
+ });
214
+
215
+ test("parses owner/repo/skill-name format", () => {
216
+ const result = resolveSkillSource("vercel-labs/skills/find-skills");
217
+ expect(result).toEqual({
218
+ owner: "vercel-labs",
219
+ repo: "skills",
220
+ skillSlug: "find-skills",
221
+ });
222
+ });
223
+
224
+ test("parses full GitHub URL with main branch", () => {
225
+ const result = resolveSkillSource(
226
+ "https://github.com/vercel-labs/skills/tree/main/skills/find-skills",
227
+ );
228
+ expect(result).toEqual({
229
+ owner: "vercel-labs",
230
+ repo: "skills",
231
+ skillSlug: "find-skills",
232
+ ref: "main",
233
+ });
234
+ });
235
+
236
+ test("parses full GitHub URL with non-main branch", () => {
237
+ const result = resolveSkillSource(
238
+ "https://github.com/some-org/repo/tree/develop/skills/my-skill",
239
+ );
240
+ expect(result).toEqual({
241
+ owner: "some-org",
242
+ repo: "repo",
243
+ skillSlug: "my-skill",
244
+ ref: "develop",
245
+ });
246
+ });
247
+
248
+ test("parses GitHub URL with trailing slash", () => {
249
+ const result = resolveSkillSource(
250
+ "https://github.com/owner/repo/tree/main/skills/skill-name/",
251
+ );
252
+ expect(result).toEqual({
253
+ owner: "owner",
254
+ repo: "repo",
255
+ skillSlug: "skill-name",
256
+ ref: "main",
257
+ });
258
+ });
259
+
260
+ test("throws on bare skill name (no owner/repo)", () => {
261
+ expect(() => resolveSkillSource("find-skills")).toThrow(
262
+ 'Invalid skill source "find-skills"',
263
+ );
264
+ });
265
+
266
+ test("throws on empty string", () => {
267
+ expect(() => resolveSkillSource("")).toThrow('Invalid skill source ""');
268
+ });
269
+
270
+ test("throws on owner-only format", () => {
271
+ expect(() => resolveSkillSource("vercel-labs")).toThrow(
272
+ 'Invalid skill source "vercel-labs"',
273
+ );
274
+ });
275
+
276
+ test("throws on owner/repo without skill", () => {
277
+ expect(() => resolveSkillSource("vercel-labs/skills")).toThrow(
278
+ 'Invalid skill source "vercel-labs/skills"',
279
+ );
280
+ });
281
+
282
+ test("rejects path traversal in @ format slug", () => {
283
+ expect(() => resolveSkillSource("owner/repo@../../malicious")).toThrow(
284
+ 'Invalid skill source "owner/repo@../../malicious"',
285
+ );
286
+ });
287
+
288
+ test("rejects uppercase slug in @ format", () => {
289
+ expect(() => resolveSkillSource("owner/repo@BadSlug")).toThrow(
290
+ 'Invalid skill source "owner/repo@BadSlug"',
291
+ );
292
+ });
293
+ });
294
+
295
+ // ─── validateSkillSlug ──────────────────────────────────────────────────────
296
+
297
+ describe("validateSkillSlug", () => {
298
+ test("accepts valid slugs", () => {
299
+ expect(() => validateSkillSlug("my-skill")).not.toThrow();
300
+ expect(() => validateSkillSlug("skill123")).not.toThrow();
301
+ expect(() => validateSkillSlug("my.skill")).not.toThrow();
302
+ expect(() => validateSkillSlug("my_skill")).not.toThrow();
303
+ });
304
+
305
+ test("rejects path traversal characters", () => {
306
+ expect(() => validateSkillSlug("../../malicious")).toThrow(
307
+ "path traversal",
308
+ );
309
+ expect(() => validateSkillSlug("foo/bar")).toThrow("path traversal");
310
+ expect(() => validateSkillSlug("foo\\bar")).toThrow("path traversal");
311
+ });
312
+
313
+ test("rejects slugs starting with special chars", () => {
314
+ expect(() => validateSkillSlug(".hidden")).toThrow();
315
+ expect(() => validateSkillSlug("-dash")).toThrow();
316
+ });
317
+
318
+ test("rejects empty input", () => {
319
+ expect(() => validateSkillSlug("")).toThrow("Skill slug is required");
320
+ });
321
+ });
322
+
323
+ // ─── fetchSkillFromGitHub ───────────────────────────────────────────────────
324
+
325
+ describe("fetchSkillFromGitHub", () => {
326
+ test("fetches from conventional skills/<slug>/ path", async () => {
327
+ mockFetchImpl = (url: string | URL | Request) => {
328
+ const urlStr = url.toString();
329
+ // Probe request for skills/my-skill
330
+ if (urlStr.includes("/contents/skills/my-skill")) {
331
+ return Promise.resolve(
332
+ new Response(
333
+ JSON.stringify([
334
+ {
335
+ name: "SKILL.md",
336
+ type: "file",
337
+ download_url: "https://raw.example.com/SKILL.md",
338
+ },
339
+ ]),
340
+ { status: 200, headers: { "Content-Type": "application/json" } },
341
+ ),
342
+ );
343
+ }
344
+ // File download
345
+ if (urlStr.includes("raw.example.com/SKILL.md")) {
346
+ return Promise.resolve(new Response("# My Skill", { status: 200 }));
347
+ }
348
+ return Promise.resolve(new Response("not found", { status: 404 }));
349
+ };
350
+
351
+ const files = await fetchSkillFromGitHub("owner", "repo", "my-skill");
352
+ expect(files["SKILL.md"]).toBe("# My Skill");
353
+ });
354
+
355
+ test("falls back to tree search when skills/<slug>/ returns 404", async () => {
356
+ mockFetchImpl = (url: string | URL | Request) => {
357
+ const urlStr = url.toString();
358
+ // Probe for conventional path returns 404
359
+ if (urlStr.includes("/contents/skills/csv")) {
360
+ return Promise.resolve(new Response("Not Found", { status: 404 }));
361
+ }
362
+ // Tree search returns the skill at a non-standard path
363
+ if (urlStr.includes("/git/trees/")) {
364
+ return Promise.resolve(
365
+ new Response(
366
+ JSON.stringify({
367
+ tree: [
368
+ { path: "examples/skills/csv/SKILL.md", type: "blob" },
369
+ { path: "examples/skills/csv/scripts/filter.sh", type: "blob" },
370
+ { path: "README.md", type: "blob" },
371
+ ],
372
+ }),
373
+ { status: 200, headers: { "Content-Type": "application/json" } },
374
+ ),
375
+ );
376
+ }
377
+ // Subdirectory listing (must precede parent path check — both use
378
+ // .includes() and the parent path is a prefix of this one)
379
+ if (urlStr.includes("/contents/examples/skills/csv/scripts")) {
380
+ return Promise.resolve(
381
+ new Response(
382
+ JSON.stringify([
383
+ {
384
+ name: "filter.sh",
385
+ type: "file",
386
+ download_url: "https://raw.example.com/filter.sh",
387
+ },
388
+ ]),
389
+ { status: 200, headers: { "Content-Type": "application/json" } },
390
+ ),
391
+ );
392
+ }
393
+ // Contents API for the discovered path
394
+ if (urlStr.includes("/contents/examples/skills/csv")) {
395
+ return Promise.resolve(
396
+ new Response(
397
+ JSON.stringify([
398
+ {
399
+ name: "SKILL.md",
400
+ type: "file",
401
+ download_url: "https://raw.example.com/SKILL.md",
402
+ },
403
+ {
404
+ name: "scripts",
405
+ type: "dir",
406
+ download_url: null,
407
+ },
408
+ ]),
409
+ { status: 200, headers: { "Content-Type": "application/json" } },
410
+ ),
411
+ );
412
+ }
413
+ // File downloads
414
+ if (urlStr.includes("raw.example.com/SKILL.md")) {
415
+ return Promise.resolve(new Response("# CSV Skill", { status: 200 }));
416
+ }
417
+ if (urlStr.includes("raw.example.com/filter.sh")) {
418
+ return Promise.resolve(
419
+ new Response("#!/bin/bash\necho filter", { status: 200 }),
420
+ );
421
+ }
422
+ return Promise.resolve(new Response("not found", { status: 404 }));
423
+ };
424
+
425
+ const files = await fetchSkillFromGitHub("vercel-labs", "bash-tool", "csv");
426
+ expect(files["SKILL.md"]).toBe("# CSV Skill");
427
+ expect(files["scripts/filter.sh"]).toBe("#!/bin/bash\necho filter");
428
+ });
429
+
430
+ test("throws when skill not found in tree either", async () => {
431
+ mockFetchImpl = (url: string | URL | Request) => {
432
+ const urlStr = url.toString();
433
+ if (urlStr.includes("/contents/skills/missing")) {
434
+ return Promise.resolve(new Response("Not Found", { status: 404 }));
435
+ }
436
+ if (urlStr.includes("/git/trees/")) {
437
+ return Promise.resolve(
438
+ new Response(
439
+ JSON.stringify({ tree: [{ path: "README.md", type: "blob" }] }),
440
+ { status: 200, headers: { "Content-Type": "application/json" } },
441
+ ),
442
+ );
443
+ }
444
+ return Promise.resolve(new Response("not found", { status: 404 }));
445
+ };
446
+
447
+ await expect(
448
+ fetchSkillFromGitHub("owner", "repo", "missing"),
449
+ ).rejects.toThrow("Searched skills/missing/ and the full repo tree");
450
+ });
451
+ });
@@ -75,13 +75,11 @@ mock.module("../util/logger.js", () => ({
75
75
  debug: () => {},
76
76
  trace: () => {},
77
77
  fatal: () => {},
78
- isDebug: () => false,
79
78
  child: () => ({
80
79
  info: () => {},
81
80
  warn: () => {},
82
81
  error: () => {},
83
82
  debug: () => {},
84
- isDebug: () => false,
85
83
  }),
86
84
  }),
87
85
  }));
@@ -116,6 +114,46 @@ mock.module("../security/secure-keys.js", () => {
116
114
  };
117
115
  });
118
116
 
117
+ // Mock oauth-store (getConnectionByProvider)
118
+ let oauthConnectionStore: Record<
119
+ string,
120
+ { id: string; status: string; accountInfo?: string | null }
121
+ > = {};
122
+
123
+ mock.module("../oauth/oauth-store.js", () => ({
124
+ getConnectionByProvider: (providerKey: string) =>
125
+ oauthConnectionStore[providerKey] ?? undefined,
126
+ createConnection: () => ({ id: "test-conn-id" }),
127
+ updateConnection: () => true,
128
+ deleteConnection: (id: string) => {
129
+ for (const [key, conn] of Object.entries(oauthConnectionStore)) {
130
+ if (conn.id === id) {
131
+ delete oauthConnectionStore[key];
132
+ return true;
133
+ }
134
+ }
135
+ return false;
136
+ },
137
+ upsertApp: async () => ({ id: "test-app-id" }),
138
+ }));
139
+
140
+ // Mock manual-token-connection
141
+ mock.module("../oauth/manual-token-connection.js", () => ({
142
+ ensureManualTokenConnection: async (
143
+ providerKey: string,
144
+ accountInfo?: string,
145
+ ) => {
146
+ oauthConnectionStore[providerKey] = {
147
+ id: `conn-${providerKey}`,
148
+ status: "active",
149
+ accountInfo: accountInfo ?? null,
150
+ };
151
+ },
152
+ removeManualTokenConnection: (providerKey: string) => {
153
+ delete oauthConnectionStore[providerKey];
154
+ },
155
+ }));
156
+
119
157
  // Mock credential metadata store
120
158
  let credentialMetadataStore: Array<{
121
159
  service: string;
@@ -187,6 +225,7 @@ describe("Slack channel config handler", () => {
187
225
  beforeEach(() => {
188
226
  secureKeyStore = {};
189
227
  credentialMetadataStore = [];
228
+ oauthConnectionStore = {};
190
229
  configStore = {};
191
230
  globalThis.fetch = originalFetch;
192
231
  });
@@ -199,7 +238,11 @@ describe("Slack channel config handler", () => {
199
238
  expect(result.connected).toBe(false);
200
239
  });
201
240
 
202
- test("GET returns connected: true when both tokens are set", () => {
241
+ test("GET returns connected: true when oauth_connection is active and both keys exist", () => {
242
+ oauthConnectionStore["slack_channel"] = {
243
+ id: "conn-slack",
244
+ status: "active",
245
+ };
203
246
  secureKeyStore[credentialKey("slack_channel", "bot_token")] = "xoxb-test";
204
247
  secureKeyStore[credentialKey("slack_channel", "app_token")] = "xapp-test";
205
248
 
@@ -210,8 +253,29 @@ describe("Slack channel config handler", () => {
210
253
  expect(result.connected).toBe(true);
211
254
  });
212
255
 
256
+ test("GET reports per-field token presence independently of connection row", () => {
257
+ // Only bot_token in keychain, no app_token, but connection row exists
258
+ oauthConnectionStore["slack_channel"] = {
259
+ id: "conn-slack",
260
+ status: "active",
261
+ };
262
+ secureKeyStore[credentialKey("slack_channel", "bot_token")] = "xoxb-test";
263
+
264
+ const result = getSlackChannelConfig();
265
+ expect(result.success).toBe(true);
266
+ expect(result.hasBotToken).toBe(true);
267
+ expect(result.hasAppToken).toBe(false);
268
+ // connected requires both keys AND connection row
269
+ expect(result.connected).toBe(false);
270
+ });
271
+
213
272
  test("GET returns metadata from config when available", () => {
273
+ oauthConnectionStore["slack_channel"] = {
274
+ id: "conn-slack",
275
+ status: "active",
276
+ };
214
277
  secureKeyStore[credentialKey("slack_channel", "bot_token")] = "xoxb-test";
278
+ secureKeyStore[credentialKey("slack_channel", "app_token")] = "xapp-test";
215
279
  configStore = {
216
280
  slack: {
217
281
  teamId: "T123",
@@ -1,7 +1,5 @@
1
1
  import { beforeEach, describe, expect, mock, test } from "bun:test";
2
2
 
3
- import { credentialKey } from "../security/credential-key.js";
4
-
5
3
  // ---------------------------------------------------------------------------
6
4
  // Mocks — must be declared before any imports that pull in mocked modules
7
5
  // ---------------------------------------------------------------------------
@@ -12,6 +10,12 @@ mock.module("../security/secure-keys.js", () => ({
12
10
  setSecureKeyAsync: async () => {},
13
11
  }));
14
12
 
13
+ let connectionByProvider: Record<string, unknown> = {};
14
+ mock.module("../oauth/oauth-store.js", () => ({
15
+ getConnectionByProvider: (key: string) =>
16
+ connectionByProvider[key] ?? undefined,
17
+ }));
18
+
15
19
  let listConversationsResult: unknown = { ok: true, channels: [] };
16
20
  let postMessageResult: unknown = {
17
21
  ok: true,
@@ -86,6 +90,7 @@ function makeRequest(body: unknown): Request {
86
90
 
87
91
  beforeEach(() => {
88
92
  secureKeyValues.clear();
93
+ connectionByProvider = {};
89
94
  listConversationsResult = { ok: true, channels: [] };
90
95
  userInfoResults = new Map();
91
96
  appStoreResult = null;
@@ -106,8 +111,9 @@ describe("handleListSlackChannels", () => {
106
111
  });
107
112
 
108
113
  test("returns channels sorted by type then name", async () => {
114
+ connectionByProvider["integration:slack"] = { id: "conn-slack-1" };
109
115
  secureKeyValues.set(
110
- credentialKey("integration:slack", "access_token"),
116
+ "oauth_connection/conn-slack-1/access_token",
111
117
  "xoxb-test",
112
118
  );
113
119
 
@@ -176,18 +182,6 @@ describe("handleListSlackChannels", () => {
176
182
  isPrivate: true,
177
183
  });
178
184
  });
179
-
180
- test("falls back to legacy bot token", async () => {
181
- secureKeyValues.set(
182
- credentialKey("slack_channel", "bot_token"),
183
- "xoxb-legacy",
184
- );
185
-
186
- listConversationsResult = { ok: true, channels: [] };
187
-
188
- const res = await handleListSlackChannels();
189
- expect(res.status).toBe(200);
190
- });
191
185
  });
192
186
 
193
187
  describe("handleShareToSlackChannel", () => {
@@ -198,8 +192,9 @@ describe("handleShareToSlackChannel", () => {
198
192
  });
199
193
 
200
194
  test("returns 400 for malformed JSON", async () => {
195
+ connectionByProvider["integration:slack"] = { id: "conn-slack-1" };
201
196
  secureKeyValues.set(
202
- credentialKey("integration:slack", "access_token"),
197
+ "oauth_connection/conn-slack-1/access_token",
203
198
  "xoxb-test",
204
199
  );
205
200
  const req = new Request("http://localhost/v1/slack/share", {
@@ -212,8 +207,9 @@ describe("handleShareToSlackChannel", () => {
212
207
  });
213
208
 
214
209
  test("returns 400 when missing required fields", async () => {
210
+ connectionByProvider["integration:slack"] = { id: "conn-slack-1" };
215
211
  secureKeyValues.set(
216
- credentialKey("integration:slack", "access_token"),
212
+ "oauth_connection/conn-slack-1/access_token",
217
213
  "xoxb-test",
218
214
  );
219
215
  const req = makeRequest({ appId: "app1" });
@@ -224,8 +220,9 @@ describe("handleShareToSlackChannel", () => {
224
220
  });
225
221
 
226
222
  test("returns 404 when app not found", async () => {
223
+ connectionByProvider["integration:slack"] = { id: "conn-slack-1" };
227
224
  secureKeyValues.set(
228
- credentialKey("integration:slack", "access_token"),
225
+ "oauth_connection/conn-slack-1/access_token",
229
226
  "xoxb-test",
230
227
  );
231
228
  appStoreResult = null;
@@ -235,8 +232,9 @@ describe("handleShareToSlackChannel", () => {
235
232
  });
236
233
 
237
234
  test("posts message and returns success", async () => {
235
+ connectionByProvider["integration:slack"] = { id: "conn-slack-1" };
238
236
  secureKeyValues.set(
239
- credentialKey("integration:slack", "access_token"),
237
+ "oauth_connection/conn-slack-1/access_token",
240
238
  "xoxb-test",
241
239
  );
242
240
  appStoreResult = {
@@ -53,7 +53,6 @@ mock.module("../util/logger.js", () => ({
53
53
  ...realLogger,
54
54
  getLogger: () => noopLogger,
55
55
  getCliLogger: () => noopLogger,
56
- isDebug: () => false,
57
56
  truncateForLog: (v: string) => v,
58
57
  initLogger: () => {},
59
58
  pruneOldLogFiles: () => 0,