@vellumai/assistant 0.5.10 → 0.5.12

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 (395) hide show
  1. package/AGENTS.md +8 -0
  2. package/ARCHITECTURE.md +43 -43
  3. package/Dockerfile +3 -0
  4. package/docs/architecture/integrations.md +37 -42
  5. package/docs/architecture/memory.md +7 -12
  6. package/docs/credential-execution-service.md +9 -9
  7. package/docs/skills.md +1 -1
  8. package/node_modules/@vellumai/ces-contracts/src/__tests__/grants.test.ts +7 -7
  9. package/node_modules/@vellumai/ces-contracts/src/handles.ts +5 -4
  10. package/node_modules/@vellumai/credential-storage/src/index.ts +3 -3
  11. package/node_modules/@vellumai/credential-storage/src/static-credentials.ts +1 -1
  12. package/openapi.yaml +7208 -0
  13. package/package.json +2 -1
  14. package/scripts/generate-openapi.ts +562 -0
  15. package/src/__tests__/acp-session.test.ts +239 -44
  16. package/src/__tests__/assistant-feature-flag-guard.test.ts +8 -8
  17. package/src/__tests__/assistant-feature-flag-guardrails.test.ts +5 -86
  18. package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -14
  19. package/src/__tests__/browser-skill-endstate.test.ts +1 -1
  20. package/src/__tests__/btw-routes.test.ts +8 -0
  21. package/src/__tests__/bundled-skill-retrieval-guard.test.ts +10 -10
  22. package/src/__tests__/catalog-cache.test.ts +164 -0
  23. package/src/__tests__/catalog-search.test.ts +61 -0
  24. package/src/__tests__/channel-approvals.test.ts +7 -7
  25. package/src/__tests__/channel-readiness-service.test.ts +41 -0
  26. package/src/__tests__/cli-command-risk-guard.test.ts +181 -6
  27. package/src/__tests__/config-schema.test.ts +10 -2
  28. package/src/__tests__/context-memory-e2e.test.ts +2 -6
  29. package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +396 -0
  30. package/src/__tests__/conversation-error.test.ts +3 -2
  31. package/src/__tests__/conversation-skill-tools.test.ts +1 -3
  32. package/src/__tests__/conversation-title-service.test.ts +2 -15
  33. package/src/__tests__/credential-execution-feature-gates.test.ts +4 -8
  34. package/src/__tests__/credential-execution-managed-contract.test.ts +8 -8
  35. package/src/__tests__/credential-security-e2e.test.ts +4 -4
  36. package/src/__tests__/credential-security-invariants.test.ts +12 -18
  37. package/src/__tests__/credential-vault-unit.test.ts +32 -34
  38. package/src/__tests__/credential-vault.test.ts +25 -33
  39. package/src/__tests__/credentials-cli.test.ts +3 -3
  40. package/src/__tests__/daemon-credential-client.test.ts +2 -2
  41. package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -1
  42. package/src/__tests__/gateway-only-guard.test.ts +3 -0
  43. package/src/__tests__/heartbeat-service.test.ts +35 -0
  44. package/src/__tests__/host-bash-proxy.test.ts +79 -0
  45. package/src/__tests__/host-cu-proxy.test.ts +90 -0
  46. package/src/__tests__/host-file-proxy.test.ts +89 -0
  47. package/src/__tests__/host-shell-tool.test.ts +1 -1
  48. package/src/__tests__/inline-skill-load-permissions.test.ts +3 -3
  49. package/src/__tests__/integration-status.test.ts +5 -5
  50. package/src/__tests__/list-messages-attachments.test.ts +171 -0
  51. package/src/__tests__/llm-request-log-turn-query.test.ts +64 -0
  52. package/src/__tests__/log-export-workspace.test.ts +1 -1
  53. package/src/__tests__/mcp-abort-signal.test.ts +205 -0
  54. package/src/__tests__/mcp-client-auth.test.ts +1 -1
  55. package/src/__tests__/memory-lifecycle-e2e.test.ts +2 -2
  56. package/src/__tests__/memory-recall-log-store.test.ts +182 -0
  57. package/src/__tests__/memory-recall-quality.test.ts +6 -8
  58. package/src/__tests__/memory-regressions.test.ts +53 -42
  59. package/src/__tests__/memory-retrieval.benchmark.test.ts +5 -9
  60. package/src/__tests__/messaging-send-tool.test.ts +5 -5
  61. package/src/__tests__/messaging-skill-split.test.ts +2 -17
  62. package/src/__tests__/notification-telegram-adapter.test.ts +125 -0
  63. package/src/__tests__/oauth-cli.test.ts +203 -649
  64. package/src/__tests__/oauth-provider-profiles.test.ts +55 -20
  65. package/src/__tests__/oauth-scope-policy.test.ts +4 -6
  66. package/src/__tests__/onboarding-template-contract.test.ts +2 -2
  67. package/src/__tests__/platform-callback-registration.test.ts +119 -0
  68. package/src/__tests__/secret-ingress-channel.test.ts +261 -0
  69. package/src/__tests__/secret-ingress-cli.test.ts +201 -0
  70. package/src/__tests__/secret-ingress-http.test.ts +312 -0
  71. package/src/__tests__/secret-ingress.test.ts +283 -0
  72. package/src/__tests__/secret-onetime-send.test.ts +4 -4
  73. package/src/__tests__/secret-routes-managed-proxy.test.ts +78 -0
  74. package/src/__tests__/secure-keys-managed-failover.test.ts +73 -0
  75. package/src/__tests__/skill-feature-flags-integration.test.ts +4 -4
  76. package/src/__tests__/skill-feature-flags.test.ts +11 -19
  77. package/src/__tests__/skill-load-feature-flag.test.ts +1 -1
  78. package/src/__tests__/skill-load-inline-command.test.ts +3 -3
  79. package/src/__tests__/skill-load-inline-includes.test.ts +2 -2
  80. package/src/__tests__/skill-memory.test.ts +2 -4
  81. package/src/__tests__/skill-projection-feature-flag.test.ts +2 -4
  82. package/src/__tests__/skill-projection.benchmark.test.ts +1 -3
  83. package/src/__tests__/skills-uninstall.test.ts +2 -2
  84. package/src/__tests__/skills.test.ts +16 -2
  85. package/src/__tests__/slack-channel-config.test.ts +1 -1
  86. package/src/__tests__/slack-messaging-token-resolution.test.ts +22 -24
  87. package/src/__tests__/slack-share-routes.test.ts +5 -5
  88. package/src/__tests__/slack-skill.test.ts +5 -69
  89. package/src/__tests__/system-prompt.test.ts +39 -0
  90. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +1 -1
  91. package/src/__tests__/workspace-migration-018-rekey-compound-credential-keys.test.ts +181 -0
  92. package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +5 -4
  93. package/src/acp/client-handler.ts +113 -31
  94. package/src/acp/session-manager.ts +29 -27
  95. package/src/approvals/guardian-request-resolvers.ts +1 -1
  96. package/src/cli/AGENTS.md +113 -0
  97. package/src/cli/commands/autonomy.ts +3 -5
  98. package/src/cli/commands/browser-relay.ts +2 -17
  99. package/src/cli/commands/contacts.ts +6 -4
  100. package/src/cli/commands/conversations.ts +13 -1
  101. package/src/cli/commands/credential-execution.ts +17 -3
  102. package/src/cli/commands/credentials.ts +2 -8
  103. package/src/cli/commands/memory.ts +2 -3
  104. package/src/cli/commands/oauth/__tests__/connect.test.ts +706 -0
  105. package/src/cli/commands/oauth/__tests__/disconnect.test.ts +686 -0
  106. package/src/cli/commands/oauth/__tests__/mode.test.ts +625 -0
  107. package/src/cli/commands/oauth/__tests__/ping.test.ts +631 -0
  108. package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +574 -0
  109. package/src/cli/commands/oauth/__tests__/providers-update.test.ts +416 -0
  110. package/src/cli/commands/oauth/__tests__/status.test.ts +551 -0
  111. package/src/cli/commands/oauth/__tests__/token.test.ts +420 -0
  112. package/src/cli/commands/oauth/apps.ts +87 -50
  113. package/src/cli/commands/oauth/connect.ts +405 -0
  114. package/src/cli/commands/oauth/disconnect.ts +285 -0
  115. package/src/cli/commands/oauth/index.ts +62 -20
  116. package/src/cli/commands/oauth/mode.ts +251 -0
  117. package/src/cli/commands/oauth/ping.ts +196 -0
  118. package/src/cli/commands/oauth/providers.ts +589 -55
  119. package/src/cli/commands/oauth/request.ts +564 -0
  120. package/src/cli/commands/oauth/shared.ts +114 -0
  121. package/src/cli/commands/oauth/status.ts +191 -0
  122. package/src/cli/commands/oauth/token.ts +150 -0
  123. package/src/cli/commands/platform/connect.ts +104 -0
  124. package/src/cli/commands/platform/disconnect.ts +118 -0
  125. package/src/cli/commands/platform/index.ts +252 -0
  126. package/src/cli/commands/sequence.ts +5 -4
  127. package/src/cli/commands/shotgun.ts +16 -0
  128. package/src/cli/commands/skills.ts +173 -41
  129. package/src/cli/commands/usage.ts +5 -11
  130. package/src/cli/lib/daemon-credential-client.ts +22 -38
  131. package/src/cli/program.ts +1 -1
  132. package/src/cli.ts +82 -17
  133. package/src/config/assistant-feature-flags.ts +77 -18
  134. package/src/config/bundled-skills/_shared/CLI_RETRIEVAL_PATTERN.md +1 -1
  135. package/src/config/bundled-skills/app-builder/tools/app-create.ts +1 -1
  136. package/src/config/bundled-skills/contacts/tools/google-contacts.ts +1 -1
  137. package/src/config/bundled-skills/conversations/SKILL.md +20 -0
  138. package/src/config/bundled-skills/conversations/TOOLS.json +23 -0
  139. package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +66 -0
  140. package/src/config/bundled-skills/gmail/SKILL.md +13 -13
  141. package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +3 -3
  142. package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +2 -2
  143. package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +1 -1
  144. package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +1 -1
  145. package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +1 -1
  146. package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +1 -1
  147. package/src/config/bundled-skills/gmail/tools/gmail-label.ts +2 -2
  148. package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +1 -1
  149. package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +1 -1
  150. package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +1 -1
  151. package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +1 -1
  152. package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +1 -1
  153. package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +1 -1
  154. package/src/config/bundled-skills/google-calendar/SKILL.md +10 -4
  155. package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
  156. package/src/config/bundled-skills/messaging/SKILL.md +19 -42
  157. package/src/config/bundled-skills/messaging/TOOLS.json +9 -9
  158. package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
  159. package/src/config/bundled-skills/messaging/tools/messaging-send.ts +5 -2
  160. package/src/config/bundled-skills/messaging/tools/shared.ts +5 -6
  161. package/src/config/bundled-skills/notifications/SKILL.md +1 -1
  162. package/src/config/bundled-skills/schedule/SKILL.md +2 -2
  163. package/src/config/bundled-skills/settings/SKILL.md +5 -3
  164. package/src/config/bundled-skills/settings/TOOLS.json +17 -0
  165. package/src/config/bundled-skills/settings/tools/avatar-get.ts +50 -0
  166. package/src/config/bundled-skills/settings/tools/avatar-remove.ts +7 -0
  167. package/src/config/bundled-skills/settings/tools/avatar-update.ts +6 -1
  168. package/src/config/bundled-skills/settings/tools/identity-avatar.ts +55 -0
  169. package/src/config/bundled-skills/skills-catalog/SKILL.md +3 -3
  170. package/src/config/bundled-skills/slack/SKILL.md +58 -44
  171. package/src/config/bundled-tool-registry.ts +7 -19
  172. package/src/config/env.ts +5 -1
  173. package/src/config/feature-flag-registry.json +58 -42
  174. package/src/config/loader.ts +4 -0
  175. package/src/config/schemas/platform.ts +0 -8
  176. package/src/config/schemas/security.ts +9 -1
  177. package/src/config/schemas/services.ts +1 -1
  178. package/src/config/skill-state.ts +1 -3
  179. package/src/config/skills.ts +2 -4
  180. package/src/credential-execution/client.ts +1 -1
  181. package/src/credential-execution/feature-gates.ts +9 -16
  182. package/src/credential-execution/process-manager.ts +12 -0
  183. package/src/daemon/config-watcher.ts +4 -0
  184. package/src/daemon/conversation-agent-loop-handlers.ts +10 -0
  185. package/src/daemon/conversation-agent-loop.ts +51 -2
  186. package/src/daemon/conversation-error.ts +36 -6
  187. package/src/daemon/conversation-memory.ts +0 -1
  188. package/src/daemon/conversation-messaging.ts +9 -0
  189. package/src/daemon/conversation-runtime-assembly.ts +33 -0
  190. package/src/daemon/conversation-surfaces.ts +120 -14
  191. package/src/daemon/conversation.ts +5 -0
  192. package/src/daemon/handlers/config-slack-channel.ts +43 -1
  193. package/src/daemon/handlers/conversations.ts +41 -33
  194. package/src/daemon/handlers/skills.ts +148 -3
  195. package/src/daemon/host-bash-proxy.ts +16 -0
  196. package/src/daemon/host-cu-proxy.ts +16 -0
  197. package/src/daemon/host-file-proxy.ts +16 -0
  198. package/src/daemon/lifecycle.ts +73 -3
  199. package/src/daemon/message-types/acp.ts +0 -15
  200. package/src/daemon/message-types/conversations.ts +1 -0
  201. package/src/daemon/message-types/guardian-actions.ts +2 -0
  202. package/src/daemon/message-types/host-bash.ts +6 -1
  203. package/src/daemon/message-types/host-cu.ts +6 -1
  204. package/src/daemon/message-types/host-file.ts +6 -1
  205. package/src/daemon/message-types/integrations.ts +0 -1
  206. package/src/daemon/message-types/memory.ts +0 -1
  207. package/src/daemon/message-types/messages.ts +9 -1
  208. package/src/daemon/message-types/schedules.ts +9 -0
  209. package/src/daemon/server.ts +48 -9
  210. package/src/email/feature-gate.ts +3 -3
  211. package/src/heartbeat/heartbeat-service.ts +48 -0
  212. package/src/hooks/cli.ts +74 -0
  213. package/src/inbound/platform-callback-registration.ts +68 -19
  214. package/src/mcp/client.ts +6 -1
  215. package/src/mcp/manager.ts +2 -1
  216. package/src/mcp/mcp-oauth-provider.ts +3 -3
  217. package/src/memory/app-store.ts +3 -3
  218. package/src/memory/conversation-crud.ts +213 -0
  219. package/src/memory/conversation-key-store.ts +26 -0
  220. package/src/memory/conversation-title-service.ts +7 -17
  221. package/src/memory/db-init.ts +24 -0
  222. package/src/memory/embedding-local.ts +47 -2
  223. package/src/memory/indexer.ts +13 -10
  224. package/src/memory/items-extractor.ts +12 -4
  225. package/src/memory/job-utils.ts +5 -0
  226. package/src/memory/jobs-store.ts +10 -2
  227. package/src/memory/journal-memory.ts +6 -2
  228. package/src/memory/llm-request-log-store.ts +88 -21
  229. package/src/memory/memory-recall-log-store.ts +128 -0
  230. package/src/memory/migrations/194-memory-recall-logs.ts +50 -0
  231. package/src/memory/migrations/195-oauth-providers-ping-config.ts +23 -0
  232. package/src/memory/migrations/196-messages-conversation-created-at-index.ts +9 -0
  233. package/src/memory/migrations/196-strip-integration-prefix-from-provider-keys.ts +186 -0
  234. package/src/memory/migrations/197-oauth-providers-behavior-columns.ts +29 -0
  235. package/src/memory/migrations/198-drop-setup-skill-id-column.ts +11 -0
  236. package/src/memory/migrations/index.ts +6 -0
  237. package/src/memory/migrations/registry.ts +8 -0
  238. package/src/memory/retriever.test.ts +4 -5
  239. package/src/memory/schema/infrastructure.ts +31 -0
  240. package/src/memory/schema/oauth.ts +14 -0
  241. package/src/messaging/provider.ts +13 -12
  242. package/src/messaging/providers/gmail/adapter.ts +44 -35
  243. package/src/messaging/providers/slack/adapter.ts +63 -33
  244. package/src/messaging/providers/telegram-bot/adapter.ts +7 -9
  245. package/src/messaging/providers/whatsapp/adapter.ts +6 -8
  246. package/src/notifications/adapters/telegram.ts +78 -2
  247. package/src/oauth/__tests__/identity-verifier.test.ts +464 -0
  248. package/src/oauth/byo-connection.test.ts +22 -24
  249. package/src/oauth/connect-orchestrator.ts +79 -64
  250. package/src/oauth/connect-types.ts +7 -65
  251. package/src/oauth/connection-resolver.test.ts +13 -13
  252. package/src/oauth/connection-resolver.ts +3 -4
  253. package/src/oauth/identity-verifier.ts +177 -0
  254. package/src/oauth/manual-token-connection.ts +5 -5
  255. package/src/oauth/oauth-store.ts +251 -5
  256. package/src/oauth/platform-connection.test.ts +56 -6
  257. package/src/oauth/platform-connection.ts +8 -1
  258. package/src/oauth/seed-providers.ts +256 -34
  259. package/src/permissions/checker.ts +129 -3
  260. package/src/permissions/trust-client.ts +2 -2
  261. package/src/platform/client.ts +2 -2
  262. package/src/prompts/journal-context.ts +6 -1
  263. package/src/prompts/system-prompt.ts +43 -9
  264. package/src/prompts/templates/BOOTSTRAP.md +16 -5
  265. package/src/providers/anthropic/client.ts +139 -28
  266. package/src/runtime/auth/__tests__/middleware.test.ts +19 -0
  267. package/src/runtime/auth/route-policy.ts +0 -1
  268. package/src/runtime/btw-sidechain.ts +7 -1
  269. package/src/runtime/channel-approvals.ts +2 -2
  270. package/src/runtime/channel-readiness-service.ts +30 -7
  271. package/src/runtime/guardian-action-service.ts +7 -2
  272. package/src/runtime/http-router.ts +31 -0
  273. package/src/runtime/http-server.ts +26 -7
  274. package/src/runtime/http-types.ts +9 -0
  275. package/src/runtime/pending-interactions.ts +21 -3
  276. package/src/runtime/routes/acp-routes.ts +46 -28
  277. package/src/runtime/routes/app-management-routes.ts +123 -0
  278. package/src/runtime/routes/app-routes.ts +31 -0
  279. package/src/runtime/routes/approval-routes.ts +108 -3
  280. package/src/runtime/routes/attachment-routes.ts +45 -0
  281. package/src/runtime/routes/avatar-routes.ts +16 -0
  282. package/src/runtime/routes/brain-graph-routes.ts +18 -0
  283. package/src/runtime/routes/btw-routes.ts +20 -0
  284. package/src/runtime/routes/call-routes.ts +81 -0
  285. package/src/runtime/routes/channel-readiness-routes.ts +48 -7
  286. package/src/runtime/routes/channel-routes.ts +18 -0
  287. package/src/runtime/routes/channel-verification-routes.ts +49 -1
  288. package/src/runtime/routes/contact-routes.ts +77 -0
  289. package/src/runtime/routes/conversation-attention-routes.ts +37 -0
  290. package/src/runtime/routes/conversation-management-routes.ts +125 -0
  291. package/src/runtime/routes/conversation-query-routes.ts +78 -0
  292. package/src/runtime/routes/conversation-routes.ts +191 -39
  293. package/src/runtime/routes/conversation-starter-routes.ts +29 -0
  294. package/src/runtime/routes/debug-routes.ts +23 -0
  295. package/src/runtime/routes/diagnostics-routes.ts +30 -0
  296. package/src/runtime/routes/documents-routes.ts +42 -0
  297. package/src/runtime/routes/events-routes.ts +10 -0
  298. package/src/runtime/routes/global-search-routes.ts +35 -0
  299. package/src/runtime/routes/guardian-action-routes.ts +61 -3
  300. package/src/runtime/routes/guardian-approval-prompt.ts +77 -2
  301. package/src/runtime/routes/heartbeat-routes.ts +278 -0
  302. package/src/runtime/routes/host-bash-routes.ts +16 -1
  303. package/src/runtime/routes/host-cu-routes.ts +23 -1
  304. package/src/runtime/routes/host-file-routes.ts +18 -1
  305. package/src/runtime/routes/identity-routes.ts +35 -0
  306. package/src/runtime/routes/inbound-message-handler.ts +46 -25
  307. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +21 -8
  308. package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +30 -2
  309. package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -2
  310. package/src/runtime/routes/integrations/slack/share.ts +1 -1
  311. package/src/runtime/routes/integrations/twilio.ts +32 -22
  312. package/src/runtime/routes/invite-routes.ts +83 -0
  313. package/src/runtime/routes/log-export-routes.ts +14 -0
  314. package/src/runtime/routes/memory-item-routes.ts +99 -1
  315. package/src/runtime/routes/migration-rollback-routes.ts +25 -0
  316. package/src/runtime/routes/migration-routes.ts +40 -0
  317. package/src/runtime/routes/notification-routes.ts +20 -0
  318. package/src/runtime/routes/oauth-apps.ts +13 -4
  319. package/src/runtime/routes/pairing-routes.ts +15 -0
  320. package/src/runtime/routes/recording-routes.ts +72 -0
  321. package/src/runtime/routes/schedule-routes.ts +77 -5
  322. package/src/runtime/routes/secret-routes.ts +99 -14
  323. package/src/runtime/routes/settings-routes.ts +102 -19
  324. package/src/runtime/routes/skills-routes.ts +141 -18
  325. package/src/runtime/routes/subagents-routes.ts +38 -3
  326. package/src/runtime/routes/surface-action-routes.ts +66 -24
  327. package/src/runtime/routes/surface-content-routes.ts +20 -0
  328. package/src/runtime/routes/telemetry-routes.ts +12 -0
  329. package/src/runtime/routes/trace-event-routes.ts +25 -0
  330. package/src/runtime/routes/trust-rules-routes.ts +46 -0
  331. package/src/runtime/routes/tts-routes.ts +15 -4
  332. package/src/runtime/routes/upgrade-broadcast-routes.ts +38 -0
  333. package/src/runtime/routes/usage-routes.ts +59 -0
  334. package/src/runtime/routes/watch-routes.ts +28 -0
  335. package/src/runtime/routes/work-items-routes.ts +59 -0
  336. package/src/runtime/routes/workspace-commit-routes.ts +12 -0
  337. package/src/runtime/routes/workspace-routes.ts +102 -0
  338. package/src/schedule/integration-status.ts +2 -2
  339. package/src/schedule/scheduler.ts +7 -1
  340. package/src/security/AGENTS.md +7 -0
  341. package/src/security/ces-rpc-credential-backend.ts +19 -16
  342. package/src/security/credential-backend.ts +1 -1
  343. package/src/security/encrypted-store.ts +3 -3
  344. package/src/security/oauth-completion-page.ts +153 -0
  345. package/src/security/oauth2.ts +58 -17
  346. package/src/security/secret-ingress.ts +174 -0
  347. package/src/security/secret-patterns.ts +133 -0
  348. package/src/security/secret-scanner.ts +28 -117
  349. package/src/security/secure-keys.ts +207 -7
  350. package/src/security/token-manager.ts +3 -6
  351. package/src/signals/bash.ts +6 -1
  352. package/src/signals/confirm.ts +12 -8
  353. package/src/signals/user-message.ts +18 -3
  354. package/src/skills/catalog-cache.ts +44 -0
  355. package/src/skills/catalog-search.ts +18 -0
  356. package/src/skills/skill-memory.ts +1 -2
  357. package/src/tasks/task-runner.ts +7 -1
  358. package/src/tools/credentials/broker.ts +1 -1
  359. package/src/tools/credentials/metadata-store.ts +1 -1
  360. package/src/tools/credentials/post-connect-hooks.ts +1 -1
  361. package/src/tools/credentials/vault.ts +36 -48
  362. package/src/tools/host-terminal/host-shell.ts +16 -3
  363. package/src/tools/mcp/mcp-tool-factory.ts +2 -1
  364. package/src/tools/memory/definitions.ts +1 -1
  365. package/src/tools/memory/handlers.test.ts +2 -4
  366. package/src/tools/skills/load.ts +1 -1
  367. package/src/tools/skills/sandbox-runner.ts +16 -3
  368. package/src/tools/terminal/safe-env.ts +7 -0
  369. package/src/tools/terminal/shell.ts +16 -3
  370. package/src/tools/tool-manifest.ts +1 -1
  371. package/src/util/log-redact.ts +9 -34
  372. package/src/util/logger.ts +11 -1
  373. package/src/util/sentry-log-stream.ts +51 -0
  374. package/src/watcher/providers/github.ts +2 -2
  375. package/src/watcher/providers/gmail.ts +1 -1
  376. package/src/watcher/providers/google-calendar.ts +1 -1
  377. package/src/watcher/providers/linear.ts +2 -2
  378. package/src/workspace/migrations/011-backfill-installation-id.ts +5 -3
  379. package/src/workspace/migrations/020-rename-oauth-skill-dirs.ts +119 -0
  380. package/src/workspace/migrations/registry.ts +2 -0
  381. package/docs/architecture/keychain-broker.md +0 -68
  382. package/src/cli/commands/oauth/connections.ts +0 -734
  383. package/src/cli/commands/oauth/platform.ts +0 -525
  384. package/src/cli/commands/platform.ts +0 -176
  385. package/src/config/bundled-skills/slack/TOOLS.json +0 -272
  386. package/src/config/bundled-skills/slack/tools/shared.ts +0 -34
  387. package/src/config/bundled-skills/slack/tools/slack-add-reaction.ts +0 -27
  388. package/src/config/bundled-skills/slack/tools/slack-channel-details.ts +0 -38
  389. package/src/config/bundled-skills/slack/tools/slack-channel-permissions.ts +0 -146
  390. package/src/config/bundled-skills/slack/tools/slack-configure-channels.ts +0 -105
  391. package/src/config/bundled-skills/slack/tools/slack-delete-message.ts +0 -26
  392. package/src/config/bundled-skills/slack/tools/slack-edit-message.ts +0 -27
  393. package/src/config/bundled-skills/slack/tools/slack-leave-channel.ts +0 -25
  394. package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +0 -372
  395. package/src/oauth/provider-behaviors.ts +0 -634
@@ -7,6 +7,8 @@
7
7
  * the same pattern as other gateway-forwarded control-plane endpoints.
8
8
  */
9
9
 
10
+ import { z } from "zod";
11
+
10
12
  import { getWorkspaceDir } from "../../util/platform.js";
11
13
  import { getWorkspaceGitService } from "../../workspace/git-service.js";
12
14
  import { httpError } from "../http-errors.js";
@@ -17,6 +19,16 @@ export function workspaceCommitRouteDefinitions(): RouteDefinition[] {
17
19
  {
18
20
  endpoint: "admin/workspace-commit",
19
21
  method: "POST",
22
+ summary: "Commit workspace changes",
23
+ description:
24
+ "Create a git commit in the workspace directory with all pending changes.",
25
+ tags: ["admin"],
26
+ requestBody: z.object({
27
+ message: z.string().describe("Commit message"),
28
+ }),
29
+ responseBody: z.object({
30
+ ok: z.boolean(),
31
+ }),
20
32
  handler: async ({ req }) => {
21
33
  let body: unknown;
22
34
  try {
@@ -16,6 +16,8 @@ import {
16
16
  } from "node:fs";
17
17
  import { basename, dirname, join } from "node:path";
18
18
 
19
+ import { z } from "zod";
20
+
19
21
  import { getWorkspaceDir } from "../../util/platform.js";
20
22
  import { httpError } from "../http-errors.js";
21
23
  import type { RouteContext, RouteDefinition } from "../http-router.js";
@@ -382,36 +384,136 @@ export function workspaceRouteDefinitions(): RouteDefinition[] {
382
384
  {
383
385
  endpoint: "workspace/tree",
384
386
  method: "GET",
387
+ summary: "List workspace directory",
388
+ description: "Return directory entries for a workspace path.",
389
+ tags: ["workspace"],
390
+ queryParams: [
391
+ {
392
+ name: "path",
393
+ schema: { type: "string" },
394
+ description: "Relative path (default root)",
395
+ },
396
+ {
397
+ name: "showHidden",
398
+ schema: { type: "string" },
399
+ description: "Include dotfiles (true/false)",
400
+ },
401
+ ],
402
+ responseBody: z.object({
403
+ path: z.string(),
404
+ entries: z.array(z.unknown()).describe("Directory entry objects"),
405
+ }),
385
406
  handler: (ctx) => handleWorkspaceTree(ctx),
386
407
  },
387
408
  {
388
409
  endpoint: "workspace/file/content",
389
410
  method: "GET",
411
+ summary: "Get workspace file content",
412
+ description: "Return raw file bytes with HTTP range support.",
413
+ tags: ["workspace"],
414
+ queryParams: [
415
+ {
416
+ name: "path",
417
+ schema: { type: "string" },
418
+ description: "Relative file path (required)",
419
+ },
420
+ {
421
+ name: "showHidden",
422
+ schema: { type: "string" },
423
+ description: "Allow hidden files (true/false)",
424
+ },
425
+ ],
390
426
  handler: (ctx) => handleWorkspaceFileContent(ctx),
391
427
  },
392
428
  {
393
429
  endpoint: "workspace/file",
394
430
  method: "GET",
431
+ summary: "Get workspace file metadata",
432
+ description:
433
+ "Return file metadata and inline text content (if small enough).",
434
+ tags: ["workspace"],
435
+ queryParams: [
436
+ {
437
+ name: "path",
438
+ schema: { type: "string" },
439
+ description: "Relative file path (required)",
440
+ },
441
+ {
442
+ name: "showHidden",
443
+ schema: { type: "string" },
444
+ description: "Allow hidden files (true/false)",
445
+ },
446
+ ],
447
+ responseBody: z.object({
448
+ path: z.string(),
449
+ name: z.string(),
450
+ size: z.number(),
451
+ mimeType: z.string(),
452
+ modifiedAt: z.string(),
453
+ content: z.string().describe("Inline text content or null"),
454
+ isBinary: z.boolean(),
455
+ }),
395
456
  handler: (ctx) => handleWorkspaceFile(ctx),
396
457
  },
397
458
  {
398
459
  endpoint: "workspace/write",
399
460
  method: "POST",
461
+ summary: "Write workspace file",
462
+ description: "Create or overwrite a file in the workspace.",
463
+ tags: ["workspace"],
464
+ requestBody: z.object({
465
+ path: z.string().describe("Relative file path"),
466
+ content: z.string().describe("File content").optional(),
467
+ encoding: z
468
+ .string()
469
+ .describe("Content encoding (base64 or utf-8)")
470
+ .optional(),
471
+ }),
472
+ responseBody: z.object({
473
+ path: z.string(),
474
+ size: z.number(),
475
+ }),
400
476
  handler: (ctx) => handleWorkspaceWrite(ctx),
401
477
  },
402
478
  {
403
479
  endpoint: "workspace/mkdir",
404
480
  method: "POST",
481
+ summary: "Create workspace directory",
482
+ description: "Create directories recursively in the workspace.",
483
+ tags: ["workspace"],
484
+ requestBody: z.object({
485
+ path: z.string().describe("Relative directory path"),
486
+ }),
487
+ responseBody: z.object({
488
+ path: z.string(),
489
+ }),
405
490
  handler: (ctx) => handleWorkspaceMkdir(ctx),
406
491
  },
407
492
  {
408
493
  endpoint: "workspace/rename",
409
494
  method: "POST",
495
+ summary: "Rename workspace entry",
496
+ description: "Rename or move a file or directory in the workspace.",
497
+ tags: ["workspace"],
498
+ requestBody: z.object({
499
+ oldPath: z.string().describe("Current relative path"),
500
+ newPath: z.string().describe("New relative path"),
501
+ }),
502
+ responseBody: z.object({
503
+ oldPath: z.string(),
504
+ newPath: z.string(),
505
+ }),
410
506
  handler: (ctx) => handleWorkspaceRename(ctx),
411
507
  },
412
508
  {
413
509
  endpoint: "workspace/delete",
414
510
  method: "POST",
511
+ summary: "Delete workspace entry",
512
+ description: "Delete a file or directory from the workspace.",
513
+ tags: ["workspace"],
514
+ requestBody: z.object({
515
+ path: z.string().describe("Relative path to delete"),
516
+ }),
415
517
  handler: (ctx) => handleWorkspaceDelete(ctx),
416
518
  },
417
519
  ];
@@ -15,12 +15,12 @@ const INTEGRATION_PROBES: IntegrationProbe[] = [
15
15
  {
16
16
  name: "Gmail",
17
17
  category: "email",
18
- isConnected: () => isProviderConnected("integration:google"),
18
+ isConnected: () => isProviderConnected("google"),
19
19
  },
20
20
  {
21
21
  name: "Slack",
22
22
  category: "messaging",
23
- isConnected: () => isProviderConnected("integration:slack"),
23
+ isConnected: () => isProviderConnected("slack"),
24
24
  },
25
25
  {
26
26
  name: "Twilio",
@@ -188,7 +188,7 @@ async function runScheduleOnce(
188
188
  );
189
189
  const { runTask } = await import("../tasks/task-runner.js");
190
190
  const result = await runTask(
191
- { taskId, workingDir: process.cwd(), source: "schedule" },
191
+ { taskId, workingDir: process.cwd(), source: "schedule", scheduleJobId: job.id },
192
192
  processMessage as (
193
193
  conversationId: string,
194
194
  message: string,
@@ -196,6 +196,12 @@ async function runScheduleOnce(
196
196
  ) => Promise<void>,
197
197
  );
198
198
 
199
+ onScheduleConversationCreated?.({
200
+ conversationId: result.conversationId,
201
+ scheduleJobId: job.id,
202
+ title: result.status === "failed" ? `${job.name}: Error` : job.name,
203
+ });
204
+
199
205
  // Track the schedule run using the task's conversation
200
206
  const runId = createScheduleRun(job.id, result.conversationId);
201
207
  if (result.status === "failed") {
@@ -0,0 +1,7 @@
1
+ # Security — Agent Instructions
2
+
3
+ ## Integration API Key Patterns
4
+
5
+ When adding a new third-party integration, check whether the service uses a recognizable API key prefix (e.g., `lin_api_`, `sk-ant-`, `ghp_`). If it does, add a corresponding entry to `PREFIX_PATTERNS` in `secret-patterns.ts`. This is the single source of truth for prefix-based secret detection — ingress blocking, tool output scanning, and log redaction all consume this list.
6
+
7
+ OAuth-only services with opaque access tokens (no fixed prefix) do not need a pattern.
@@ -30,11 +30,13 @@ export class CesRpcCredentialBackend implements CredentialBackend {
30
30
  }
31
31
 
32
32
  async get(account: string): Promise<CredentialGetResult> {
33
+ if (!this.isAvailable()) {
34
+ return { value: undefined, unreachable: true };
35
+ }
33
36
  try {
34
- const result = await this.client.call(
35
- CesRpcMethod.GetCredential,
36
- { account },
37
- );
37
+ const result = await this.client.call(CesRpcMethod.GetCredential, {
38
+ account,
39
+ });
38
40
  return {
39
41
  value: result.found ? result.value : undefined,
40
42
  unreachable: false,
@@ -46,11 +48,12 @@ export class CesRpcCredentialBackend implements CredentialBackend {
46
48
  }
47
49
 
48
50
  async set(account: string, value: string): Promise<boolean> {
51
+ if (!this.isAvailable()) return false;
49
52
  try {
50
- const result = await this.client.call(
51
- CesRpcMethod.SetCredential,
52
- { account, value },
53
- );
53
+ const result = await this.client.call(CesRpcMethod.SetCredential, {
54
+ account,
55
+ value,
56
+ });
54
57
  return result.ok;
55
58
  } catch (err) {
56
59
  log.warn({ err, account }, "CES RPC credential set failed");
@@ -59,11 +62,11 @@ export class CesRpcCredentialBackend implements CredentialBackend {
59
62
  }
60
63
 
61
64
  async delete(account: string): Promise<DeleteResult> {
65
+ if (!this.isAvailable()) return "error";
62
66
  try {
63
- const result = await this.client.call(
64
- CesRpcMethod.DeleteCredential,
65
- { account },
66
- );
67
+ const result = await this.client.call(CesRpcMethod.DeleteCredential, {
68
+ account,
69
+ });
67
70
  return result.result;
68
71
  } catch (err) {
69
72
  log.warn({ err, account }, "CES RPC credential delete failed");
@@ -72,11 +75,11 @@ export class CesRpcCredentialBackend implements CredentialBackend {
72
75
  }
73
76
 
74
77
  async list(): Promise<CredentialListResult> {
78
+ if (!this.isAvailable()) {
79
+ return { accounts: [], unreachable: true };
80
+ }
75
81
  try {
76
- const result = await this.client.call(
77
- CesRpcMethod.ListCredentials,
78
- {},
79
- );
82
+ const result = await this.client.call(CesRpcMethod.ListCredentials, {});
80
83
  return { accounts: result.accounts, unreachable: false };
81
84
  } catch (err) {
82
85
  log.warn({ err }, "CES RPC credential list failed");
@@ -30,7 +30,7 @@ export interface CredentialListResult {
30
30
  // ---------------------------------------------------------------------------
31
31
 
32
32
  export interface CredentialBackend {
33
- /** Human-readable name for logging (e.g. "keychain", "encrypted-store"). */
33
+ /** Human-readable name for logging (e.g. "encrypted-store"). */
34
34
  readonly name: string;
35
35
 
36
36
  /** Whether this backend is currently reachable. Sync and cheap. */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Encrypted-at-rest key storage -- fallback for systems without OS keychain.
2
+ * Encrypted-at-rest key storage.
3
3
  *
4
4
  * v2 stores use a cryptographically random 32-byte `store.key` file as the
5
5
  * AES-256-GCM key directly (no key derivation). The key file lives alongside
@@ -8,7 +8,7 @@
8
8
  * v1 stores (legacy) derived the AES key from machine-specific entropy via
9
9
  * PBKDF2. Existing v1 stores are automatically migrated to v2 on first access.
10
10
  *
11
- * Provides the same get/set/delete interface as `keychain.ts`.
11
+ * Provides the standard get/set/delete credential storage interface.
12
12
  */
13
13
 
14
14
  import {
@@ -418,7 +418,7 @@ function decrypt(entry: EncryptedEntry, key: Buffer): string {
418
418
  }
419
419
 
420
420
  // ---------------------------------------------------------------------------
421
- // Public API -- matches keychain.ts interface
421
+ // Public API
422
422
  // ---------------------------------------------------------------------------
423
423
 
424
424
  /**
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Shared HTML rendering for OAuth completion pages shown in the browser
3
+ * after a loopback/redirect OAuth flow completes.
4
+ */
5
+
6
+ export function escapeHtml(s: string): string {
7
+ return s
8
+ .replace(/&/g, "&amp;")
9
+ .replace(/</g, "&lt;")
10
+ .replace(/>/g, "&gt;")
11
+ .replace(/"/g, "&quot;")
12
+ .replace(/'/g, "&#39;");
13
+ }
14
+
15
+ function formatProviderName(provider: string): string {
16
+ // Capitalize first letter of each word, handle common acronyms
17
+ return provider
18
+ .split(/[-_]/)
19
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
20
+ .join(" ");
21
+ }
22
+
23
+ export function renderOAuthCompletionPage(
24
+ message: string,
25
+ success: boolean,
26
+ provider?: string,
27
+ ): string {
28
+ const displayProvider = provider ? formatProviderName(provider) : "";
29
+ const title = success
30
+ ? displayProvider
31
+ ? `Connected to ${escapeHtml(displayProvider)}`
32
+ : "Authorization Successful"
33
+ : "Authorization Failed";
34
+ const subtitle = success
35
+ ? "You can close this tab and return to your assistant."
36
+ : escapeHtml(message);
37
+
38
+ const checkmarkSvg = `<svg class="icon" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
39
+ <circle cx="28" cy="28" r="28" fill="var(--positive-bg)"/>
40
+ <path class="check" d="M17 28.5L24.5 36L39 21" stroke="var(--positive-fg)" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
41
+ </svg>`;
42
+
43
+ const errorSvg = `<svg class="icon" viewBox="0 0 56 56" fill="none" xmlns="http://www.w3.org/2000/svg">
44
+ <circle cx="28" cy="28" r="28" fill="var(--negative-bg)"/>
45
+ <path class="cross cross-1" d="M20 20L36 36" stroke="var(--negative-fg)" stroke-width="3.5" stroke-linecap="round" fill="none"/>
46
+ <path class="cross cross-2" d="M36 20L20 36" stroke="var(--negative-fg)" stroke-width="3.5" stroke-linecap="round" fill="none"/>
47
+ </svg>`;
48
+
49
+ return `<!DOCTYPE html>
50
+ <html lang="en">
51
+ <head>
52
+ <meta charset="utf-8">
53
+ <meta name="viewport" content="width=device-width, initial-scale=1">
54
+ <title>${escapeHtml(title)}</title>
55
+ <style>
56
+ :root {
57
+ --surface: #F5F3EB;
58
+ --surface-card: #FFFFFF;
59
+ --card-border: #E8E6DA;
60
+ --text-primary: #2A2A28;
61
+ --text-secondary: #4A4A46;
62
+ --text-tertiary: #A1A096;
63
+ --positive-bg: #D4DFD0;
64
+ --positive-fg: #516748;
65
+ --negative-bg: #F7DAC9;
66
+ --negative-fg: #DA491A;
67
+ --shadow: 0 1px 3px rgba(0,0,0,0.04), 0 4px 12px rgba(0,0,0,0.06);
68
+ --font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
69
+ }
70
+ @media (prefers-color-scheme: dark) {
71
+ :root {
72
+ --surface: #1A1A18;
73
+ --surface-card: #2A2A28;
74
+ --card-border: #3A3A37;
75
+ --text-primary: #F5F3EB;
76
+ --text-secondary: #BDB9A9;
77
+ --text-tertiary: #6B6B65;
78
+ --positive-bg: #1A2316;
79
+ --positive-fg: #7A8B6F;
80
+ --negative-bg: #4E281D;
81
+ --negative-fg: #E86B40;
82
+ --shadow: 0 1px 3px rgba(0,0,0,0.2), 0 4px 12px rgba(0,0,0,0.3);
83
+ }
84
+ }
85
+ * { margin: 0; padding: 0; box-sizing: border-box; }
86
+ body {
87
+ font-family: var(--font);
88
+ background: var(--surface);
89
+ color: var(--text-primary);
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ min-height: 100vh;
94
+ -webkit-font-smoothing: antialiased;
95
+ }
96
+ .card {
97
+ text-align: center;
98
+ padding: 48px 40px 40px;
99
+ background: var(--surface-card);
100
+ border: 1px solid var(--card-border);
101
+ border-radius: 16px;
102
+ box-shadow: var(--shadow);
103
+ max-width: 380px;
104
+ width: 100%;
105
+ opacity: 0;
106
+ transform: translateY(8px) scale(0.98);
107
+ animation: cardIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.1s forwards;
108
+ }
109
+ @keyframes cardIn {
110
+ to { opacity: 1; transform: translateY(0) scale(1); }
111
+ }
112
+ .icon {
113
+ width: 56px;
114
+ height: 56px;
115
+ margin-bottom: 20px;
116
+ }
117
+ .check {
118
+ stroke-dasharray: 32;
119
+ stroke-dashoffset: 32;
120
+ animation: draw 0.4s ease-out 0.45s forwards;
121
+ }
122
+ .cross {
123
+ stroke-dasharray: 22;
124
+ stroke-dashoffset: 22;
125
+ }
126
+ .cross-1 { animation: draw 0.3s ease-out 0.45s forwards; }
127
+ .cross-2 { animation: draw 0.3s ease-out 0.55s forwards; }
128
+ @keyframes draw {
129
+ to { stroke-dashoffset: 0; }
130
+ }
131
+ h1 {
132
+ font-size: 18px;
133
+ font-weight: 600;
134
+ letter-spacing: -0.2px;
135
+ color: var(--text-primary);
136
+ margin-bottom: 6px;
137
+ }
138
+ p {
139
+ font-size: 13px;
140
+ line-height: 1.5;
141
+ color: var(--text-secondary);
142
+ }
143
+ </style>
144
+ </head>
145
+ <body>
146
+ <div class="card">
147
+ ${success ? checkmarkSvg : errorSvg}
148
+ <h1>${escapeHtml(title)}</h1>
149
+ <p>${subtitle}</p>
150
+ </div>
151
+ </body>
152
+ </html>`;
153
+ }
@@ -20,6 +20,7 @@ import { createHash, randomBytes } from "node:crypto";
20
20
  import { createServer, type Server } from "node:http";
21
21
 
22
22
  import { getLogger } from "../util/logger.js";
23
+ import { renderOAuthCompletionPage as renderLoopbackPage } from "./oauth-completion-page.js";
23
24
 
24
25
  const log = getLogger("oauth2");
25
26
 
@@ -289,6 +290,15 @@ function startLoopbackServerAndWaitForCode(
289
290
  let boundRedirectUri = "";
290
291
 
291
292
  const server: Server = createServer((req, res) => {
293
+ log.info(
294
+ {
295
+ method: req.method,
296
+ path: new URL(req.url ?? "/", "http://127.0.0.1").pathname,
297
+ settled,
298
+ },
299
+ "oauth2 loopback: received request",
300
+ );
301
+
292
302
  if (settled) {
293
303
  res.writeHead(400, { "Content-Type": "text/html" });
294
304
  res.end(renderLoopbackPage("Authorization already completed", false));
@@ -298,6 +308,10 @@ function startLoopbackServerAndWaitForCode(
298
308
  const url = new URL(req.url ?? "/", `http://127.0.0.1`);
299
309
 
300
310
  if (url.pathname !== LOOPBACK_CALLBACK_PATH) {
311
+ log.info(
312
+ { pathname: url.pathname },
313
+ "oauth2 loopback: non-callback path, returning 404",
314
+ );
301
315
  res.writeHead(404, { "Content-Type": "text/plain" });
302
316
  res.end("Not found");
303
317
  return;
@@ -308,6 +322,7 @@ function startLoopbackServerAndWaitForCode(
308
322
  const error = url.searchParams.get("error");
309
323
 
310
324
  if (callbackState !== state) {
325
+ log.warn("oauth2 loopback: state mismatch in callback");
311
326
  res.writeHead(400, { "Content-Type": "text/html" });
312
327
  res.end(renderLoopbackPage("Invalid state parameter", false));
313
328
  return;
@@ -317,6 +332,10 @@ function startLoopbackServerAndWaitForCode(
317
332
 
318
333
  if (error) {
319
334
  const errorDesc = url.searchParams.get("error_description") ?? error;
335
+ log.error(
336
+ { error, errorDesc },
337
+ "oauth2 loopback: authorization denied by user/provider",
338
+ );
320
339
  res.writeHead(200, { "Content-Type": "text/html" });
321
340
  res.end(
322
341
  renderLoopbackPage(`Authorization failed: ${errorDesc}`, false),
@@ -327,6 +346,7 @@ function startLoopbackServerAndWaitForCode(
327
346
  }
328
347
 
329
348
  if (!code) {
349
+ log.error("oauth2 loopback: callback missing authorization code");
330
350
  res.writeHead(400, { "Content-Type": "text/html" });
331
351
  res.end(renderLoopbackPage("Missing authorization code", false));
332
352
  cleanup();
@@ -334,10 +354,13 @@ function startLoopbackServerAndWaitForCode(
334
354
  return;
335
355
  }
336
356
 
357
+ log.info(
358
+ "oauth2 loopback: authorization code received, exchanging for tokens",
359
+ );
337
360
  res.writeHead(200, { "Content-Type": "text/html" });
338
361
  res.end(
339
362
  renderLoopbackPage(
340
- "Authorization successful! You can close this tab.",
363
+ "You can close this tab and return to your assistant.",
341
364
  true,
342
365
  ),
343
366
  );
@@ -347,6 +370,10 @@ function startLoopbackServerAndWaitForCode(
347
370
 
348
371
  const timeout = setTimeout(() => {
349
372
  if (!settled) {
373
+ log.warn(
374
+ { timeoutMs: LOOPBACK_TIMEOUT_MS, state },
375
+ "oauth2 loopback: callback timed out — no authorization code received",
376
+ );
350
377
  settled = true;
351
378
  cleanup();
352
379
  reject(new Error("OAuth2 loopback callback timed out"));
@@ -359,10 +386,20 @@ function startLoopbackServerAndWaitForCode(
359
386
  server.close();
360
387
  }
361
388
 
389
+ log.info(
390
+ { requestedPort: loopbackPort ?? "random" },
391
+ "oauth2 loopback: binding server",
392
+ );
393
+
362
394
  server.listen(loopbackPort ?? 0, "localhost", () => {
363
395
  const addr = server.address() as { port: number };
364
396
  boundRedirectUri = `http://localhost:${addr.port}${LOOPBACK_CALLBACK_PATH}`;
365
397
 
398
+ log.info(
399
+ { port: addr.port, redirectUri: boundRedirectUri },
400
+ "oauth2 loopback: server listening",
401
+ );
402
+
366
403
  const authParams = new URLSearchParams({
367
404
  ...config.extraParams,
368
405
  client_id: config.clientId,
@@ -375,10 +412,19 @@ function startLoopbackServerAndWaitForCode(
375
412
  });
376
413
 
377
414
  const authUrl = `${config.authUrl}?${authParams}`;
415
+ log.info(
416
+ { authUrlLength: authUrl.length, state },
417
+ "oauth2 loopback: built auth URL, calling openUrl callback",
418
+ );
378
419
  callbacks.openUrl(authUrl);
420
+ log.info("oauth2 loopback: openUrl callback returned");
379
421
  });
380
422
 
381
423
  server.on("error", (err) => {
424
+ log.error(
425
+ { err: err.message, loopbackPort },
426
+ "oauth2 loopback: server error",
427
+ );
382
428
  if (!settled) {
383
429
  settled = true;
384
430
  cleanup();
@@ -388,21 +434,6 @@ function startLoopbackServerAndWaitForCode(
388
434
  });
389
435
  }
390
436
 
391
- function escapeHtml(s: string): string {
392
- return s
393
- .replace(/&/g, "&amp;")
394
- .replace(/</g, "&lt;")
395
- .replace(/>/g, "&gt;")
396
- .replace(/"/g, "&quot;")
397
- .replace(/'/g, "&#39;");
398
- }
399
-
400
- function renderLoopbackPage(message: string, success: boolean): string {
401
- const title = success ? "Authorization Successful" : "Authorization Failed";
402
- const color = success ? "#4CAF50" : "#f44336";
403
- return `<!DOCTYPE html><html><head><title>${escapeHtml(title)}</title><style>body{font-family:system-ui,sans-serif;display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#f5f5f5}div{text-align:center;padding:2rem;background:white;border-radius:8px;box-shadow:0 2px 4px rgba(0,0,0,0.1)}h1{color:${color}}</style></head><body><div><h1>${escapeHtml(title)}</h1><p>${escapeHtml(message)}</p></div></body></html>`;
404
- }
405
-
406
437
  // ---------------------------------------------------------------------------
407
438
  // Public API
408
439
  // ---------------------------------------------------------------------------
@@ -595,7 +626,7 @@ function startLoopbackServerForPreparedFlow(
595
626
  res.writeHead(200, { "Content-Type": "text/html" });
596
627
  res.end(
597
628
  renderLoopbackPage(
598
- "Authorization successful! You can close this tab.",
629
+ "You can close this tab and return to your assistant.",
599
630
  true,
600
631
  ),
601
632
  );
@@ -687,6 +718,16 @@ export async function startOAuth2Flow(
687
718
  const transport =
688
719
  options?.callbackTransport ?? (hasPublicUrl ? "gateway" : "loopback");
689
720
 
721
+ log.info(
722
+ {
723
+ transport,
724
+ hasPublicUrl,
725
+ explicitTransport: options?.callbackTransport,
726
+ loopbackPort: options?.loopbackPort,
727
+ },
728
+ "startOAuth2Flow: resolved transport",
729
+ );
730
+
690
731
  if (transport === "gateway") {
691
732
  if (!hasPublicUrl) {
692
733
  throw new Error(