@inneranimalmedia/agentsam-sdk 2.6.3 → 2.6.5

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 (608) hide show
  1. package/AGENTSAM.md +17 -0
  2. package/README.md +31 -10
  3. package/apps/client-cms-editor/AGENTS.md +12 -0
  4. package/apps/client-cms-editor/IMPORT_PROVENANCE.json +8 -0
  5. package/apps/client-cms-editor/README.md +72 -0
  6. package/apps/client-cms-editor/agentsam.app.json +57 -0
  7. package/apps/client-cms-editor/backend/README.md +5 -0
  8. package/apps/client-cms-editor/backend/package.json +22 -0
  9. package/apps/client-cms-editor/backend/src/api/client.ts +200 -0
  10. package/apps/client-cms-editor/backend/src/demo-bootstrap.ts +145 -0
  11. package/apps/client-cms-editor/backend/src/index.ts +6 -0
  12. package/apps/client-cms-editor/backend/src/model.ts +115 -0
  13. package/apps/client-cms-editor/backend/src/preview/bridge.ts +79 -0
  14. package/apps/client-cms-editor/backend/src/preview/urls.ts +55 -0
  15. package/apps/client-cms-editor/backend/src/routing/cms-route.js +293 -0
  16. package/apps/client-cms-editor/backend/src/routing/index.js +10 -0
  17. package/apps/client-cms-editor/backend/src/storefront.ts +52 -0
  18. package/apps/client-cms-editor/backend/tsconfig.json +15 -0
  19. package/apps/client-cms-editor/backend/worker/index.js +34 -0
  20. package/apps/client-cms-editor/backend/wrangler.jsonc +11 -0
  21. package/apps/client-cms-editor/bin/agentsam-cms.mjs +229 -0
  22. package/apps/client-cms-editor/frontend/index.html +12 -0
  23. package/apps/client-cms-editor/frontend/package.json +33 -0
  24. package/apps/client-cms-editor/frontend/src/AgentSamDrawer.tsx +188 -0
  25. package/apps/client-cms-editor/frontend/src/CmsAgentSurface.tsx +191 -0
  26. package/apps/client-cms-editor/frontend/src/CmsEditor.tsx +1345 -0
  27. package/apps/client-cms-editor/frontend/src/env.d.ts +14 -0
  28. package/apps/client-cms-editor/frontend/src/index.ts +8 -0
  29. package/apps/client-cms-editor/frontend/src/lib/content-safety.ts +63 -0
  30. package/apps/client-cms-editor/frontend/src/main.tsx +17 -0
  31. package/apps/client-cms-editor/frontend/src/mount.tsx +51 -0
  32. package/apps/client-cms-editor/frontend/src/styles/agentsam-drawer.css +214 -0
  33. package/apps/client-cms-editor/frontend/src/styles/cmsShell.css +1739 -0
  34. package/apps/client-cms-editor/frontend/src/styles/studio.css +106 -0
  35. package/apps/client-cms-editor/frontend/src/surfaces/CmsDashboard.tsx +313 -0
  36. package/apps/client-cms-editor/frontend/src/surfaces/CmsGuidedChatHero.tsx +121 -0
  37. package/apps/client-cms-editor/frontend/src/surfaces/CmsHubPage.tsx +132 -0
  38. package/apps/client-cms-editor/frontend/src/surfaces/CmsSiteSwitcher.tsx +176 -0
  39. package/apps/client-cms-editor/frontend/src/surfaces/index.ts +5 -0
  40. package/apps/client-cms-editor/frontend/src/surfaces/types.ts +48 -0
  41. package/apps/client-cms-editor/frontend/tsconfig.json +25 -0
  42. package/apps/client-cms-editor/frontend/vite.config.ts +13 -0
  43. package/apps/client-cms-editor/package-lock.json +3832 -0
  44. package/apps/client-cms-editor/package.json +23 -0
  45. package/apps/client-cms-editor/reference/DONOR_README.md +52 -0
  46. package/apps/client-cms-editor/reference/DONOR_package-lock.json +1253 -0
  47. package/apps/client-cms-editor/reference/DONOR_tsconfig.json +15 -0
  48. package/apps/client-cms-editor/shared/cms/package.json +21 -0
  49. package/apps/client-cms-editor/shared/cms/src/agent-context.ts +35 -0
  50. package/apps/client-cms-editor/shared/cms/src/cloudflare-bindings.ts +51 -0
  51. package/apps/client-cms-editor/shared/cms/src/editor-types.ts +58 -0
  52. package/apps/client-cms-editor/shared/cms/src/index.ts +5 -0
  53. package/apps/client-cms-editor/shared/cms/src/publication.ts +24 -0
  54. package/apps/client-cms-editor/shared/cms/src/routes.ts +11 -0
  55. package/apps/client-cms-editor/shared/cms/tsconfig.json +12 -0
  56. package/bin/agentsam +16 -1
  57. package/bin/agentsam-rapid-rust +36 -0
  58. package/docs/AUTH_IDENTITY_CONTRACT.md +2 -0
  59. package/docs/BRAND_INTELLIGENCE.md +1 -1
  60. package/docs/MACHINE_INTELLIGENCE.md +120 -0
  61. package/docs/WINDOWS_RUNTIME_INSTALL.md +30 -0
  62. package/docs/architecture/AGENTSAM_APPLICATION_CONTRACT.md +188 -0
  63. package/docs/architecture/AGENTSAM_DISTRIBUTION_OWNERSHIP.md +176 -0
  64. package/docs/architecture/AGENTSAM_GO_RUNTIME.md +282 -0
  65. package/docs/architecture/AGENTSAM_RUNTIME_PROTOCOL.md +151 -0
  66. package/docs/architecture/CODEBASEINDEX_GUIDED_PATH.md +202 -0
  67. package/docs/architecture/FS_E2E_CLOSURE_RECEIPT.md +59 -0
  68. package/docs/architecture/LOCAL_FS_AUTHORITY.md +38 -0
  69. package/docs/architecture/LOCAL_STUDIO_CLOUDFLARE_OAUTH.md +36 -0
  70. package/docs/architecture/PLAN_CLI_AND_LOCAL_STUDIO_DESKTOP.md +96 -0
  71. package/docs/architecture/R2_SLUG_NAMESPACE.md +20 -0
  72. package/docs/architecture/SAM_ACTIVITY_RECOVERY_RECEIPT.md +39 -0
  73. package/docs/architecture/SAM_DECISION_WORK_RECEIPT.md +54 -0
  74. package/docs/architecture/SAM_KERNEL.md +181 -0
  75. package/docs/architecture/SAM_MACHINE_NORMALIZATION_PRECOMMIT_REPORT.md +208 -0
  76. package/docs/architecture/SLASH_SKILLS_PORTABLE.md +116 -0
  77. package/docs/architecture/WORK_PRODUCT_EXTRACTION.md +97 -0
  78. package/docs/architecture/fs-e2e-receipt.latest.json +42 -0
  79. package/docs/architecture/previews/codebaseindex-guided-path/CODEBASEINDEX_GUIDED_PATH.md +202 -0
  80. package/docs/architecture/previews/codebaseindex-guided-path/index.html +321 -0
  81. package/docs/brand/AGENT_SAM_ICON_GRAMMAR.md +70 -0
  82. package/docs/content-studio/HOST_MOUNT_CHECKLIST.md +71 -0
  83. package/docs/content-studio/REVISION_GATE_2026-09-27.md +501 -0
  84. package/docs/contracts/capability-install-planner.md +67 -0
  85. package/docs/contracts/environment-vocabulary.md +195 -0
  86. package/docs/contracts/google-cloud-oauth-setup.md +199 -0
  87. package/docs/local-studio/WORKMODE_DONOR_NOTES.md +1 -1
  88. package/docs/plans/LOCAL-STUDIO-GRADUATION-2026-09-26.md +290 -0
  89. package/docs/plans/LOCAL-STUDIO-REAL-APP-CONTRACT-ADDENDUM-2026-09-27.md +394 -0
  90. package/docs/plans/MACHINE-FIRST-APPS-TUI-2026-09-24.md +227 -0
  91. package/docs/plans/TOMORROW-EXTENSIVE-TODO-2026-09-26.md +221 -0
  92. package/docs/plans/TOMORROW-MACHINE-FIRST-BRIEFING.md +145 -0
  93. package/docs/plans/UNIVERSAL-INGEST-MACHINE-FIRST-2026-09-24.md +215 -0
  94. package/docs/plugins.md +2 -2
  95. package/docs/product/AGENTSAM_PRODUCT_EXPERIENCE_STANDARD.md +280 -0
  96. package/docs/product/LOCAL_STUDIO_DESKTOP_EXPERIENCE.md +139 -0
  97. package/docs/product/README.md +8 -0
  98. package/docs/rapid-rust-wasm.md +35 -0
  99. package/migrations/d1/0010_portable_tickets_memory.sql +140 -0
  100. package/migrations/d1/0011_agentsam_skill_v2.sql +185 -0
  101. package/migrations/d1/0011b_agentsam_skill_v2_cutover.sql +22 -0
  102. package/migrations/d1/0011c_agentsam_skill_v2_backfill.sql +76 -0
  103. package/migrations/d1/0011d_agentsam_skill_v2_retrieval_revisions.sql +50 -0
  104. package/migrations/d1/0012_agentsam_tools_required_seed.sql +67 -0
  105. package/migrations/d1/0013_identity_oauth_states.sql +15 -0
  106. package/migrations/d1/0014_auth_event_log.sql +20 -0
  107. package/migrations/d1/0015_identity_oauth_state_app_id.sql +3 -0
  108. package/migrations/d1/0016_cloudflare_oauth_user_oauth_tokens_ssot.sql +14 -0
  109. package/migrations/d1/README_PORTABLE_CONTROL_PLANE.md +16 -0
  110. package/migrations/sqlite/agentsam_skill_retrieval.portable.sql +42 -0
  111. package/package.json +88 -14
  112. package/packages/agentsam-brand/README.md +106 -11
  113. package/packages/agentsam-brand/bin/agentsam-brand.mjs +9 -0
  114. package/packages/agentsam-brand/package.json +39 -7
  115. package/packages/agentsam-brand/src/adapters/cloudflare-images.js +453 -0
  116. package/packages/agentsam-brand/src/adapters/cloudflare-r2.js +74 -0
  117. package/packages/agentsam-brand/src/adapters/d1-registry.js +26 -0
  118. package/packages/agentsam-brand/src/adapters/filesystem.js +59 -0
  119. package/packages/agentsam-brand/src/adapters/memory.js +31 -0
  120. package/packages/agentsam-brand/src/cli/controller.js +503 -0
  121. package/packages/agentsam-brand/src/cli/wizard.js +526 -0
  122. package/packages/agentsam-brand/src/core/capabilities.js +201 -0
  123. package/packages/agentsam-brand/src/core/derivatives.js +211 -0
  124. package/packages/agentsam-brand/src/core/errors.js +12 -0
  125. package/packages/agentsam-brand/src/core/ingest.js +195 -0
  126. package/packages/agentsam-brand/src/core/inspect.js +109 -0
  127. package/packages/agentsam-brand/src/core/keys.js +83 -0
  128. package/packages/agentsam-brand/src/core/naming.js +142 -0
  129. package/packages/agentsam-brand/src/core/optimize.js +316 -0
  130. package/packages/agentsam-brand/src/core/pack.js +305 -0
  131. package/packages/agentsam-brand/src/core/plan.js +276 -0
  132. package/packages/agentsam-brand/src/core/preview.js +159 -0
  133. package/packages/agentsam-brand/src/core/processors/cloudflare.js +68 -0
  134. package/packages/agentsam-brand/src/core/processors/contract.js +203 -0
  135. package/packages/agentsam-brand/src/core/processors/index.js +148 -0
  136. package/packages/agentsam-brand/src/core/processors/native.js +82 -0
  137. package/packages/agentsam-brand/src/core/processors/sharp.js +113 -0
  138. package/packages/agentsam-brand/src/core/processors/squoosh-binary.js +195 -0
  139. package/packages/agentsam-brand/src/core/promote.js +288 -0
  140. package/packages/agentsam-brand/src/core/receipts.js +155 -0
  141. package/packages/agentsam-brand/src/core/v2/compile.js +280 -0
  142. package/packages/agentsam-brand/src/core/v2/derivatives-semantic.js +207 -0
  143. package/packages/agentsam-brand/src/core/v2/ingest-graph.js +387 -0
  144. package/packages/agentsam-brand/src/core/v2/roles.js +180 -0
  145. package/packages/agentsam-brand/src/core/v2/schema.js +209 -0
  146. package/packages/agentsam-brand/src/core/v2/templates.js +114 -0
  147. package/packages/agentsam-brand/src/index.js +193 -36
  148. package/packages/agentsam-brand/src/presets/app-icon.json +16 -0
  149. package/packages/agentsam-brand/src/presets/favicon.json +17 -0
  150. package/packages/agentsam-brand/src/presets/hero.json +12 -0
  151. package/packages/agentsam-brand/src/presets/index.js +40 -0
  152. package/packages/agentsam-brand/src/presets/logo.json +12 -0
  153. package/packages/agentsam-brand/src/presets/model.json +8 -0
  154. package/packages/agentsam-brand/src/presets/og-image.json +10 -0
  155. package/packages/agentsam-brand/src/presets/product-shot.json +11 -0
  156. package/packages/agentsam-brand/src/presets/social-image.json +10 -0
  157. package/packages/agentsam-brand/src/presets/web-image.json +10 -0
  158. package/packages/agentsam-contracts/src/artifacts.ts +41 -0
  159. package/packages/agentsam-contracts/src/errors.ts +16 -0
  160. package/packages/agentsam-errors/src/envelope.js +53 -0
  161. package/packages/agentsam-errors/src/index.js +1 -0
  162. package/packages/agentsam-errors/src/recovery.js +301 -0
  163. package/packages/agentsam-knowledge/src/autorag/index.js +1 -1
  164. package/packages/agentsam-knowledge/src/providers/index.js +20 -6
  165. package/packages/agentsam-knowledge/src/providers/ollama-endpoint.js +31 -0
  166. package/packages/agentsam-knowledge/test/autorag.test.mjs +22 -1
  167. package/packages/agentsam-nav/package.json +2 -2
  168. package/packages/agentsam-rapid-rust/Cargo.lock +344 -0
  169. package/packages/agentsam-rapid-rust/Cargo.toml +18 -0
  170. package/packages/agentsam-rapid-rust/README.md +5 -0
  171. package/packages/agentsam-rapid-rust/src/main.rs +718 -0
  172. package/packages/agentsam-rapid-rust/templates/api/Cargo.toml.tmpl +18 -0
  173. package/packages/agentsam-rapid-rust/templates/api/src/lib.rs.tmpl +17 -0
  174. package/packages/agentsam-rapid-rust/templates/common/README.md.tmpl +22 -0
  175. package/packages/agentsam-rapid-rust/templates/common/package.json.tmpl +12 -0
  176. package/packages/agentsam-rapid-rust/templates/common/wrangler.toml.tmpl +6 -0
  177. package/packages/agentsam-rapid-rust/templates/crypto-auth/Cargo.toml.tmpl +21 -0
  178. package/packages/agentsam-rapid-rust/templates/crypto-auth/src/lib.rs.tmpl +32 -0
  179. package/packages/agentsam-rapid-rust/templates/data-processor/Cargo.toml.tmpl +18 -0
  180. package/packages/agentsam-rapid-rust/templates/data-processor/src/lib.rs.tmpl +41 -0
  181. package/packages/agentsam-rapid-rust/templates/shared-core/Cargo.toml.tmpl +9 -0
  182. package/packages/agentsam-rapid-rust/templates/shared-core/core/Cargo.toml.tmpl +7 -0
  183. package/packages/agentsam-rapid-rust/templates/shared-core/core/src/lib.rs.tmpl +23 -0
  184. package/packages/agentsam-rapid-rust/templates/shared-core/worker/Cargo.toml.tmpl +12 -0
  185. package/packages/agentsam-rapid-rust/templates/shared-core/worker/src/lib.rs.tmpl +16 -0
  186. package/packages/agentsam-rapid-rust/templates/shared-core/wrangler.toml.tmpl +6 -0
  187. package/packages/agentsam-repository/src/index.js +1 -0
  188. package/packages/agentsam-repository/src/merkle/semantic.js +3 -10
  189. package/packages/agentsam-repository/src/source-types.js +85 -0
  190. package/packages/agentsam-vault/README.md +96 -0
  191. package/packages/agentsam-vault/package.json +32 -0
  192. package/packages/agentsam-vault/src/contracts/credential.d.ts +78 -0
  193. package/packages/agentsam-vault/src/contracts/credential.js +132 -0
  194. package/packages/agentsam-vault/src/crypto/aes-gcm.d.ts +30 -0
  195. package/packages/agentsam-vault/src/crypto/aes-gcm.js +107 -0
  196. package/packages/agentsam-vault/src/crypto/oauth-envelope.js +150 -0
  197. package/packages/agentsam-vault/src/index.d.ts +9 -0
  198. package/packages/agentsam-vault/src/index.js +27 -0
  199. package/packages/agentsam-vault/src/providers/registry.js +90 -0
  200. package/packages/agentsam-vault/src/runtime/resolver.js +77 -0
  201. package/packages/agentsam-workbench/README.md +24 -0
  202. package/packages/agentsam-workbench/package.json +38 -0
  203. package/packages/agentsam-workbench/src/agent/AgentPanel.tsx +41 -0
  204. package/packages/agentsam-workbench/src/agent/ArtifactCard.tsx +9 -0
  205. package/packages/agentsam-workbench/src/agent/Composer.tsx +102 -0
  206. package/packages/agentsam-workbench/src/agent/ConnectedAgentPanel.tsx +194 -0
  207. package/packages/agentsam-workbench/src/agent/ContextualComposer.tsx +788 -0
  208. package/packages/agentsam-workbench/src/agent/GoalStatusStrip.tsx +38 -0
  209. package/packages/agentsam-workbench/src/agent/MiniAgentSam.tsx +86 -0
  210. package/packages/agentsam-workbench/src/agent/ModelSelect.tsx +19 -0
  211. package/packages/agentsam-workbench/src/agent/Thread.tsx +56 -0
  212. package/packages/agentsam-workbench/src/agent/ToolReceipt.tsx +11 -0
  213. package/packages/agentsam-workbench/src/agent/contextual-composer.css +611 -0
  214. package/packages/agentsam-workbench/src/agent/index.ts +10 -0
  215. package/packages/agentsam-workbench/src/agent/mini-agentsam.css +12 -0
  216. package/packages/agentsam-workbench/src/browser/BrowserFrame.tsx +37 -0
  217. package/packages/agentsam-workbench/src/browser/index.ts +1 -0
  218. package/packages/agentsam-workbench/src/index.ts +6 -0
  219. package/packages/agentsam-workbench/src/project-control.css +160 -0
  220. package/packages/agentsam-workbench/src/projects/ProjectsSurface.tsx +131 -0
  221. package/packages/agentsam-workbench/src/projects/index.ts +1 -0
  222. package/packages/agentsam-workbench/src/shell/Breadcrumb.tsx +35 -0
  223. package/packages/agentsam-workbench/src/shell/ResizableStage.tsx +99 -0
  224. package/packages/agentsam-workbench/src/shell/SidePanel.tsx +5 -0
  225. package/packages/agentsam-workbench/src/shell/index.ts +4 -0
  226. package/packages/agentsam-workbench/src/shell/surfaceRegistry.ts +29 -0
  227. package/packages/agentsam-workbench/src/terminal/TerminalSurface.tsx +5 -0
  228. package/packages/agentsam-workbench/src/terminal/index.ts +1 -0
  229. package/packages/agentsam-workbench/src/timeline/GanttBoard.tsx +258 -0
  230. package/packages/agentsam-workbench/src/timeline/index.ts +1 -0
  231. package/packages/agentsam-workbench/tsconfig.json +13 -0
  232. package/packages/connectors/cloudflare/OAUTH_STORAGE_AUDIT.md +53 -0
  233. package/packages/connectors/cloudflare/package.json +3 -1
  234. package/packages/connectors/cloudflare/src/api-client.js +279 -0
  235. package/packages/connectors/cloudflare/src/capabilities.js +585 -0
  236. package/packages/connectors/cloudflare/src/credential.js +318 -0
  237. package/packages/connectors/cloudflare/src/families/brand-protection.js +123 -0
  238. package/packages/connectors/cloudflare/src/families/keyless.js +114 -0
  239. package/packages/connectors/cloudflare/src/families/pages.js +151 -0
  240. package/packages/connectors/cloudflare/src/families/scanner.js +123 -0
  241. package/packages/connectors/cloudflare/src/families/snippets.js +207 -0
  242. package/packages/connectors/cloudflare/src/families/tag-gateway.js +46 -0
  243. package/packages/connectors/cloudflare/src/families/tags.js +124 -0
  244. package/packages/connectors/cloudflare/src/families/token-validation.js +95 -0
  245. package/packages/connectors/cloudflare/src/families/workflows.js +156 -0
  246. package/packages/connectors/cloudflare/src/index.js +102 -265
  247. package/packages/connectors/cloudflare/src/oauth-persist.js +336 -0
  248. package/packages/connectors/cloudflare/src/owner.js +42 -12
  249. package/packages/connectors/cloudflare/src/permission-options.js +1064 -0
  250. package/packages/connectors/cloudflare/src/routes.js +318 -144
  251. package/packages/connectors/cloudflare/src/scope-catalog.js +345 -0
  252. package/packages/connectors/cloudflare/src/vault.js +2 -2
  253. package/packages/connectors/cloudflare/tests/capabilities.test.mjs +276 -0
  254. package/packages/connectors/cloudflare/tests/connector.test.mjs +51 -30
  255. package/packages/connectors/cloudflare/tests/oauth-persist.test.mjs +125 -0
  256. package/packages/identity/.agentsam/features/oauth-login-portal/agentsam.feature.json +1 -1
  257. package/packages/identity/.agentsam/features/oauth-login-portal/providers.json +22 -2
  258. package/packages/identity/.agentsam/features/oauth-login-portal/resources.json +10 -4
  259. package/packages/identity/.agentsam/features/oauth-login-portal/routes.json +31 -2
  260. package/packages/identity/README.md +1 -1
  261. package/packages/identity/backend/README.md +1 -1
  262. package/packages/identity/docs/PORTABLE_IDENTITY_ARCHITECTURE.md +50 -0
  263. package/packages/identity/migrations/D1_SCHEMA_MAPPING.md +32 -0
  264. package/packages/identity/migrations/sqlite/001_identity_core.sql +99 -0
  265. package/packages/identity/migrations/sqlite/002_identity_oauth_client.sql +38 -0
  266. package/packages/identity/migrations/sqlite/003_identity_oauth_server.sql +64 -0
  267. package/packages/identity/migrations/sqlite/004_identity_oauth_consent_catalog.sql +48 -0
  268. package/packages/identity/package.json +3 -2
  269. package/packages/identity/scripts/local-identity-bridge.mjs +184 -0
  270. package/packages/identity/scripts/preview-auth-portal.mjs +7 -7
  271. package/packages/identity/src/adapters/cloudflare-d1/index.js +153 -24
  272. package/packages/identity/src/adapters/sqlite/index.js +322 -0
  273. package/packages/identity/src/app/verify-app.js +95 -0
  274. package/packages/identity/src/contracts/auth-config.js +28 -8
  275. package/packages/identity/src/contracts/external-identity.js +1 -1
  276. package/packages/identity/src/contracts/identity-store.js +115 -0
  277. package/packages/identity/src/contracts/provider.js +1 -0
  278. package/packages/identity/src/contracts/route-ids.js +30 -0
  279. package/packages/identity/src/contracts/route-projection.js +218 -0
  280. package/packages/identity/src/contracts/routes.js +11 -0
  281. package/packages/identity/src/core/browser-paths.js +4 -5
  282. package/packages/identity/src/core/constants.js +13 -8
  283. package/packages/identity/src/core/session-policy.js +32 -0
  284. package/packages/identity/src/frontend/auth-portal/README.md +2 -1
  285. package/packages/identity/src/frontend/auth-portal/pages/login.html +16 -15
  286. package/packages/identity/src/frontend/auth-portal/pages/reset.html +9 -8
  287. package/packages/identity/src/frontend/auth-portal/pages/signup.html +9 -8
  288. package/packages/identity/src/frontend/auth-portal/preview/dashboard-stub.html +1 -1
  289. package/packages/identity/src/frontend/auth-portal/shared/company-branding.js +1 -1
  290. package/packages/identity/src/frontend/auth-portal/shared/desktop-identity-bridge.js +207 -0
  291. package/packages/identity/src/index.js +41 -0
  292. package/packages/identity/src/migrations/0001_identity_core.sql +19 -2
  293. package/packages/identity/src/migrations/0003_accounts_ssot.sql +20 -0
  294. package/packages/identity/src/migrations/README.md +5 -2
  295. package/packages/identity/src/oauth/README.md +19 -8
  296. package/packages/identity/src/oauth/consent-view.js +388 -0
  297. package/packages/identity/src/oauth/credentials.js +46 -19
  298. package/packages/identity/src/oauth/finalize-inbound.js +1 -1
  299. package/packages/identity/src/oauth/google-desktop-exchange.js +173 -0
  300. package/packages/identity/src/oauth/iam-platform.js +8 -7
  301. package/packages/identity/src/oauth/redirect-paths.js +27 -36
  302. package/packages/identity/src/providers/cloudflare/oauth.js +3 -4
  303. package/packages/identity/src/providers/cloudflare/profile.js +49 -14
  304. package/packages/identity/src/providers/iam/index.js +27 -5
  305. package/packages/identity/src/server/identity-service.js +61 -15
  306. package/packages/identity/src/server/mount-policy.js +30 -0
  307. package/packages/identity/src/server/post-auth.js +79 -0
  308. package/packages/identity/src/server/worker-router.js +208 -146
  309. package/packages/identity/tests/auth-config.test.mjs +3 -2
  310. package/packages/identity/tests/finalize-inbound-oauth.test.mjs +6 -6
  311. package/packages/identity/tests/iam-provider.test.mjs +1 -1
  312. package/packages/identity/tests/identity-init-scaffold.test.mjs +3 -0
  313. package/packages/identity/tests/identity-service.test.mjs +52 -5
  314. package/packages/identity/tests/native-session-worker.test.mjs +64 -0
  315. package/packages/identity/tests/oauth-consent-view.test.mjs +106 -0
  316. package/packages/identity/tests/oauth-credentials.test.mjs +20 -5
  317. package/packages/identity/tests/oauth-presets-accounts-ssot.test.mjs +58 -0
  318. package/packages/identity/tests/portable-identity-architecture.test.mjs +157 -0
  319. package/packages/identity/tests/preview-auth-portal.test.mjs +3 -3
  320. package/packages/identity/tests/session-routes-policy.test.mjs +21 -0
  321. package/packages/runtime-protocol/src/index.js +154 -0
  322. package/packages/theme-church-site/package.json +2 -1
  323. package/packages/theme-church-site/src/index.js +1 -0
  324. package/packages/theme-companions-site/package.json +2 -1
  325. package/packages/theme-companions-site/src/index.js +1 -0
  326. package/packages/theme-floors-site/package.json +2 -1
  327. package/packages/theme-floors-site/src/index.js +1 -0
  328. package/packages/theme-fuelnfree-site/package.json +2 -1
  329. package/packages/theme-fuelnfree-site/src/index.js +1 -0
  330. package/packages/theme-handyman-site/package.json +2 -1
  331. package/packages/theme-handyman-site/src/index.js +1 -0
  332. package/packages/theme-insurance-site/package.json +2 -1
  333. package/packages/theme-insurance-site/src/index.js +1 -0
  334. package/packages/theme-scenes/README.md +164 -0
  335. package/packages/theme-scenes/package.json +53 -0
  336. package/packages/theme-scenes/src/blocks/index.js +30 -0
  337. package/packages/theme-scenes/src/contracts/index.js +286 -0
  338. package/packages/theme-scenes/src/demos/index.js +65 -0
  339. package/packages/theme-scenes/src/index.js +75 -0
  340. package/packages/theme-scenes/src/presets/index.js +263 -0
  341. package/packages/theme-scenes/src/runtime/compile.js +119 -0
  342. package/packages/theme-scenes/src/scenes/index.js +146 -0
  343. package/packages/theme-scenes/src/shell/index.js +47 -0
  344. package/packages/theme-scenes/src/themes/index.js +129 -0
  345. package/packages/theme-scenes/src/visuals/index.js +41 -0
  346. package/packages/theme-scenes/test/theme-scenes.test.mjs +179 -0
  347. package/packages/theme-shinshu-site/package.json +2 -1
  348. package/packages/theme-shinshu-site/src/index.js +1 -0
  349. package/packages/work-graph/src/index.js +34 -1
  350. package/packages/work-graph/src/renderers/gantt-model.js +24 -0
  351. package/packages/work-graph/test/work-graph.test.mjs +27 -7
  352. package/protocol/README.md +15 -7
  353. package/protocol/apps/agentsam.app.v1.schema.json +133 -0
  354. package/protocol/apps/agentsam.host-install.v1.schema.json +36 -0
  355. package/protocol/assets/README.md +9 -0
  356. package/protocol/assets/asset-record.v1.schema.json +64 -0
  357. package/protocol/brand/brandpack.v1.schema.json +43 -0
  358. package/protocol/credentials/issue.v1.schema.json +38 -0
  359. package/protocol/database/connection.v1.schema.json +41 -0
  360. package/protocol/embeddings/embedding-profile.v1.schema.json +20 -0
  361. package/protocol/errors/error-envelope.schema.json +135 -1
  362. package/protocol/errors/recovery.v1.schema.json +50 -0
  363. package/protocol/runtime/agentsam.runtime.v1.schema.json +75 -0
  364. package/protocol/runtime/workspace-fs.v1.schema.json +71 -0
  365. package/protocol/sam/activity.v1.schema.json +48 -0
  366. package/protocol/sam/answer.v1.schema.json +35 -0
  367. package/protocol/sam/calibration.v1.schema.json +21 -0
  368. package/protocol/sam/decision-receipt.v1.schema.json +29 -0
  369. package/protocol/sam/evaluation.v1.schema.json +19 -0
  370. package/protocol/sam/operation.schema.json +66 -0
  371. package/protocol/sam/outcome.v1.schema.json +36 -0
  372. package/protocol/sam/question.v1.schema.json +26 -0
  373. package/protocol/sam/registry.seed.json +153 -0
  374. package/protocol/sam/result.schema.json +53 -0
  375. package/protocol/sam/state.v1.schema.json +23 -0
  376. package/protocol/scoring/README.md +10 -0
  377. package/protocol/scoring/scorecard.v1.schema.json +65 -0
  378. package/protocol/skills/agentsam.interaction.v1.schema.json +50 -0
  379. package/protocol/skills/agentsam.skill.v1.schema.json +57 -0
  380. package/protocol/ui/icon-registry.mjs +227 -0
  381. package/protocol/ui/icon.v1.schema.json +51 -0
  382. package/registry/cms-rust-machine-20260928/installable-products.json +31 -0
  383. package/registry/cms-rust-machine-20260928/manifest.json +22 -0
  384. package/registry/cms-rust-machine-20260928/migration.sql +237 -0
  385. package/registry/cms-rust-machine-20260928/receipt.json +21 -0
  386. package/registry/cms-rust-machine-20260928/receipt.sql +27 -0
  387. package/scripts/build_rapid_rust_cli.py +83 -0
  388. package/skills/README.md +22 -9
  389. package/skills/agentsam-codebaseindex/SKILL.md +225 -0
  390. package/skills/catalog.json +14 -0
  391. package/src/attachments/index.js +6 -0
  392. package/src/attachments/intake.js +235 -0
  393. package/src/capabilities/repository-snapshot.js +19 -2
  394. package/src/cli/command-catalog.js +138 -0
  395. package/src/cli/dispatch.js +48 -0
  396. package/src/cli.js +225 -27
  397. package/src/commands/account-auth.js +94 -8
  398. package/src/commands/api-key.js +244 -0
  399. package/src/commands/app.js +365 -29
  400. package/src/commands/billing.js +127 -0
  401. package/src/commands/brand.js +86 -74
  402. package/src/commands/cheat-sheet.js +154 -0
  403. package/src/commands/cloudflare-login.js +222 -0
  404. package/src/commands/cloudflare.js +453 -23
  405. package/src/commands/codebaseindex.js +693 -0
  406. package/src/commands/compute.js +4 -0
  407. package/src/commands/connections.js +11 -5
  408. package/src/commands/credentials.js +116 -0
  409. package/src/commands/env.js +152 -25
  410. package/src/commands/gcloud-auth.js +427 -0
  411. package/src/commands/go.js +366 -53
  412. package/src/commands/google-cloud.js +375 -0
  413. package/src/commands/identity-init.js +2 -2
  414. package/src/commands/interaction-clack.js +115 -0
  415. package/src/commands/mcp.js +226 -33
  416. package/src/commands/models.js +1 -1
  417. package/src/commands/ollama.js +3 -2
  418. package/src/commands/plugins.js +7 -2
  419. package/src/commands/product.js +36 -0
  420. package/src/commands/providers.js +62 -14
  421. package/src/commands/runtime-status.js +28 -9
  422. package/src/commands/runtime.js +313 -0
  423. package/src/commands/rust.js +24 -0
  424. package/src/commands/setup.js +279 -0
  425. package/src/commands/shell.js +59 -5
  426. package/src/commands/skill.js +248 -0
  427. package/src/commands/skills.js +1 -1
  428. package/src/commands/start-local.js +6 -2
  429. package/src/commands/terminal.js +171 -0
  430. package/src/commands/update.js +140 -0
  431. package/src/commands/whoami.js +392 -56
  432. package/src/features/resolve.js +1 -0
  433. package/src/go/build.js +229 -39
  434. package/src/go/cloudflare.js +506 -105
  435. package/src/go/container.js +120 -0
  436. package/src/go/contract.js +9 -4
  437. package/src/go/discover.js +149 -33
  438. package/src/go/index.js +15 -3
  439. package/src/go/native-probe-runner.mjs +119 -0
  440. package/src/go/{registry.js → official-registry.js} +64 -7
  441. package/src/go/official-release.js +10 -0
  442. package/src/go/receipts.js +77 -10
  443. package/src/go/verify.js +9 -2
  444. package/src/index.js +31 -0
  445. package/src/indexing/ingest/discover-models.js +298 -0
  446. package/src/indexing/ingest/inventory.js +478 -0
  447. package/src/indexing/ingest/job-graph.js +181 -0
  448. package/src/indexing/ingest/materials.js +204 -0
  449. package/src/indexing/ingest/style-evidence.js +181 -0
  450. package/src/knowledge/source.js +3 -3
  451. package/src/lib/account-session.js +11 -4
  452. package/src/lib/app-authority.js +172 -0
  453. package/src/lib/auth.js +94 -21
  454. package/src/lib/core-client.js +5 -6
  455. package/src/lib/credentials-audit.js +451 -0
  456. package/src/lib/deploy/health.js +24 -6
  457. package/src/lib/detect-context.js +7 -1
  458. package/src/lib/google-cloud-connection.js +270 -0
  459. package/src/lib/google-cloud-inventory.js +385 -0
  460. package/src/lib/google-desktop-oauth.js +372 -0
  461. package/src/lib/google-studio-brokered-oauth.js +158 -0
  462. package/src/lib/identity-scaffold.js +24 -6
  463. package/src/lib/init-options.js +192 -0
  464. package/src/lib/local-scaffold.js +48 -16
  465. package/src/lib/provider-command-remediation.js +308 -0
  466. package/src/lib/provider-credentials.js +71 -21
  467. package/src/lib/scaffold/index.js +8 -2
  468. package/src/lib/scaffold/mcp-server.js +43 -0
  469. package/src/lib/scaffold/templates/mcp-server/index.js +397 -0
  470. package/src/lib/scaffold/templates/mcp-server/presets.js +39 -0
  471. package/src/lib/scaffold/wizards/cms.js +93 -0
  472. package/src/lib/scaffold/wizards/mcp-server.js +61 -0
  473. package/src/lib/setup/contract.js +26 -0
  474. package/src/lib/setup/discover.js +116 -0
  475. package/src/lib/setup/execute.js +205 -0
  476. package/src/lib/setup/planner.js +134 -0
  477. package/src/lib/setup/recipes.js +235 -0
  478. package/src/lib/setup/runtime-persist.js +197 -0
  479. package/src/lib/setup/runtime.js +394 -0
  480. package/src/lib/slash-commands.js +3 -0
  481. package/src/lib/terminal/machine-identity.js +95 -0
  482. package/src/lib/terminal-local.js +164 -0
  483. package/src/lib/terminal-scopes.js +52 -0
  484. package/src/lib/whoami-capabilities.js +184 -0
  485. package/src/local-fs/capability.js +121 -0
  486. package/src/local-fs/freshness.js +75 -0
  487. package/src/local-fs/index.js +385 -0
  488. package/src/local-fs/paths.js +100 -0
  489. package/src/local-pty/server.js +326 -21
  490. package/src/mcp/authority.js +58 -108
  491. package/src/mcp/client-adapters.js +68 -144
  492. package/src/mcp/client.js +142 -122
  493. package/src/mcp/cloudflare-bundles.js +225 -0
  494. package/src/mcp/index.js +12 -0
  495. package/src/models/ai-access-onboarding.js +112 -0
  496. package/src/models/discovery.js +10 -2
  497. package/src/models/inventory-core.js +9 -1
  498. package/src/plugins/cloudflare.js +1 -1
  499. package/src/providers/ollama-chat.js +2 -1
  500. package/src/sam/activity/index.js +183 -0
  501. package/src/sam/client.js +252 -0
  502. package/src/sam/decision/calibration.js +109 -0
  503. package/src/sam/decision/confidence.js +126 -0
  504. package/src/sam/decision/evaluate.js +157 -0
  505. package/src/sam/decision/evaluators/deterministic.js +341 -0
  506. package/src/sam/decision/evaluators/heuristic.js +61 -0
  507. package/src/sam/decision/evaluators/select.js +50 -0
  508. package/src/sam/decision/evaluators/semantic.js +149 -0
  509. package/src/sam/decision/hierarchical.js +61 -0
  510. package/src/sam/decision/index.js +53 -0
  511. package/src/sam/decision/policy.js +86 -0
  512. package/src/sam/decision/questions.js +120 -0
  513. package/src/sam/decision/receipt.js +148 -0
  514. package/src/sam/decision/state.js +117 -0
  515. package/src/sam/decision/types.js +22 -0
  516. package/src/sam/decision/validate.js +200 -0
  517. package/src/sam/define.js +51 -0
  518. package/src/sam/index.js +65 -0
  519. package/src/sam/operations/brand-scan.js +62 -0
  520. package/src/sam/operations/cad-blender-inspect.js +36 -0
  521. package/src/sam/operations/codebaseindex-ingest.js +49 -0
  522. package/src/sam/operations/decision-evaluate.js +59 -0
  523. package/src/sam/operations/planning-astar.js +77 -0
  524. package/src/sam/operations/planning-goap.js +60 -0
  525. package/src/sam/operations/repository-inspect.js +72 -0
  526. package/src/sam/operations/security-scan.js +33 -0
  527. package/src/sam/operations/terminal-exec.js +29 -0
  528. package/src/sam/planning/astar.js +311 -0
  529. package/src/sam/planning/goap.js +177 -0
  530. package/src/sam/planning/index.js +21 -0
  531. package/src/sam/planning/state.js +84 -0
  532. package/src/sam/registry.js +48 -0
  533. package/src/sam/result.js +77 -0
  534. package/src/sam/seed.js +44 -0
  535. package/src/sam/types.js +116 -0
  536. package/src/security/inventory.js +12 -2
  537. package/src/security/local-vault.js +2 -0
  538. package/src/security/scan.js +1 -1
  539. package/src/skills/catalog.js +64 -0
  540. package/src/skills/content-resolver.js +124 -0
  541. package/src/skills/hosted-store.js +37 -0
  542. package/src/skills/index.js +29 -64
  543. package/src/skills/interaction.js +102 -0
  544. package/src/skills/local-store.js +228 -0
  545. package/src/skills/manifest.js +104 -0
  546. package/src/skills/metrics.js +31 -0
  547. package/src/skills/registry.js +184 -0
  548. package/src/skills/runtime.js +287 -0
  549. package/src/skills/slash.js +44 -0
  550. package/src/ui/cli/help.js +94 -101
  551. package/test/account-session.test.mjs +3 -3
  552. package/test/app-authority-context.test.mjs +32 -0
  553. package/test/cli/api-key-env-whoami.test.mjs +129 -0
  554. package/test/cli/codebaseindex-plan-ux.test.mjs +37 -0
  555. package/test/cli/go.test.mjs +62 -5
  556. package/test/cli/mcp-client.test.mjs +21 -10
  557. package/test/cli/runtime-persist.test.mjs +56 -0
  558. package/test/cli/rust-command.test.mjs +21 -0
  559. package/test/cli/skill-npm-and-env.test.mjs +52 -0
  560. package/test/cli/wireframes-go-registry.test.mjs +87 -2
  561. package/test/credentials-audit.test.mjs +113 -0
  562. package/test/deploy-health-scan.test.mjs +29 -1
  563. package/test/gcloud-auth.test.mjs +111 -0
  564. package/test/go/build-source-identity.test.mjs +31 -0
  565. package/test/go/cloudflare-probe.test.mjs +274 -10
  566. package/test/go/distribution.test.mjs +28 -0
  567. package/test/google-cloud-connection.test.mjs +83 -0
  568. package/test/google-cloud-inventory.test.mjs +64 -0
  569. package/test/init-options.test.mjs +35 -0
  570. package/test/integration/ai-access-onboarding.test.mjs +47 -0
  571. package/test/integration/app-command.test.mjs +30 -0
  572. package/test/integration/cad-project.test.mjs +4 -1
  573. package/test/integration/cli-help.test.mjs +1 -1
  574. package/test/integration/cms-app-scaffold.test.mjs +71 -0
  575. package/test/integration/cms-site-tenancy-contract.test.mjs +9 -8
  576. package/test/integration/codebaseindex-ingest.test.mjs +214 -0
  577. package/test/integration/features-resolve.test.mjs +30 -11
  578. package/test/integration/icon-registry.test.mjs +67 -0
  579. package/test/integration/ingest-discover-models.test.mjs +30 -0
  580. package/test/integration/inspect-path-cli.test.mjs +46 -0
  581. package/test/integration/install-script.test.mjs +12 -9
  582. package/test/integration/inventory-evidence.test.mjs +125 -0
  583. package/test/integration/local-fs.test.mjs +113 -0
  584. package/test/integration/provider-env-cli.test.mjs +2 -1
  585. package/test/integration/runtime-status.integration.test.mjs +2 -1
  586. package/test/integration/sam-activity-recovery.test.mjs +147 -0
  587. package/test/integration/sam-decision.test.mjs +584 -0
  588. package/test/integration/sam-kernel.test.mjs +99 -0
  589. package/test/integration/sam-planning-astar.test.mjs +279 -0
  590. package/test/integration/skill-runtime.test.mjs +240 -0
  591. package/test/integration/studio-fs-pty-e2e.test.mjs +294 -0
  592. package/test/knowledge.test.mjs +35 -1
  593. package/test/loopback-oauth.test.mjs +53 -0
  594. package/test/models.test.mjs +14 -7
  595. package/test/ollama.test.mjs +22 -0
  596. package/test/security.test.mjs +23 -0
  597. package/test/setup-planner.test.mjs +74 -0
  598. package/test/setup-runtime.test.mjs +51 -0
  599. package/test/shell.test.mjs +4 -4
  600. package/test/terminal/local-pty.mock.test.mjs +3 -1
  601. package/test/terminal-machine-identity.test.mjs +88 -0
  602. package/test/whoami-resume.test.mjs +27 -6
  603. package/packages/agentsam-brand/test/brand-scan.test.mjs +0 -106
  604. package/packages/work-graph/prototype/README.md +0 -32
  605. package/packages/work-graph/prototype/data/demo-workgraph.js +0 -47
  606. package/packages/work-graph/prototype/renderers/timeline-model.js +0 -21
  607. package/packages/work-graph/prototype/renderers/workgraph-runtime.js +0 -38
  608. package/packages/work-graph/prototype-gnantt.html +0 -655
@@ -7,10 +7,17 @@ import {
7
7
  resolveCloudflareOAuthClient,
8
8
  requestedCloudflareScopes,
9
9
  assertConnectionOwner,
10
+ upsertCloudflareUserOauthToken,
11
+ revokeCloudflareUserOauthTokens,
12
+ loadCloudflareConnectionRecord,
13
+ loadCloudflareFromUserOauthTokens,
10
14
  } from './index.js';
11
15
  import { resolveAuthenticatedOwner } from './owner.js';
12
16
  import { decryptSecret, sealToken, vaultConfigured } from './vault.js';
13
17
 
18
+ const OAUTH_STATE_PROVIDER = 'cloudflare';
19
+ const OAUTH_STATE_TTL_SECONDS = 600;
20
+
14
21
  function json(data, status = 200) {
15
22
  return new Response(JSON.stringify(data), {
16
23
  status,
@@ -41,16 +48,95 @@ function resolveReturnTo(url) {
41
48
  }
42
49
  }
43
50
 
51
+ function bytesToHex(bytes) {
52
+ return [...bytes].map((b) => b.toString(16).padStart(2, '0')).join('');
53
+ }
54
+
55
+ async function hashOAuthState(state) {
56
+ const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(String(state || '')));
57
+ return bytesToHex(new Uint8Array(digest));
58
+ }
59
+
60
+ function oauthStateAad(ownerId) {
61
+ return `oauth_state_nonces:${OAUTH_STATE_PROVIDER}:${ownerId}`;
62
+ }
63
+
44
64
  async function deleteOAuthState(env, state) {
45
65
  if (env.DB) {
66
+ const stateHash = await hashOAuthState(state);
46
67
  await env.DB.prepare(
47
- `DELETE FROM agentsam_cloudflare_oauth_state WHERE state = ?`,
48
- ).bind(state).run();
68
+ `DELETE FROM oauth_state_nonces WHERE state_hash = ? AND provider = ?`,
69
+ ).bind(stateHash, OAUTH_STATE_PROVIDER).run();
49
70
  } else if (env.oauthState instanceof Map) {
50
71
  env.oauthState.delete(state);
51
72
  }
52
73
  }
53
74
 
75
+ async function saveOAuthState(env, { state, ownerId, verifier, returnTo }) {
76
+ // In-memory fixture path (tests / local without vault).
77
+ if (env.oauthState instanceof Map && (!env.DB || !vaultConfigured(env))) {
78
+ env.oauthState.set(state, { ownerId, verifier, returnTo, createdAt: Math.floor(Date.now() / 1000) });
79
+ return { ok: true };
80
+ }
81
+ if (!env.DB) return { ok: false, error: 'db_unavailable' };
82
+ if (!vaultConfigured(env)) return { ok: false, error: 'vault_unavailable' };
83
+ const stateHash = await hashOAuthState(state);
84
+ const now = Math.floor(Date.now() / 1000);
85
+ const expiresAt = now + OAUTH_STATE_TTL_SECONDS;
86
+ const encrypted = await sealToken(env, verifier, oauthStateAad(ownerId));
87
+ const id = `osn_${crypto.randomUUID().replace(/-/g, '').slice(0, 20)}`;
88
+ await env.DB.prepare(
89
+ `DELETE FROM oauth_state_nonces
90
+ WHERE provider = ? AND (expires_at < ? OR consumed_at IS NOT NULL)`,
91
+ ).bind(OAUTH_STATE_PROVIDER, now).run();
92
+ await env.DB.prepare(
93
+ `INSERT INTO oauth_state_nonces (
94
+ id, tenant_id, user_id, provider, state_hash, code_verifier_encrypted,
95
+ redirect_after, metadata_json, expires_at, consumed_at, created_at
96
+ ) VALUES (?, NULL, ?, ?, ?, ?, ?, '{}', ?, NULL, ?)`,
97
+ ).bind(id, ownerId, OAUTH_STATE_PROVIDER, stateHash, encrypted, returnTo || null, expiresAt, now).run();
98
+ return { ok: true };
99
+ }
100
+
101
+ async function consumeOAuthState(env, state) {
102
+ if (env.oauthState instanceof Map && (!env.DB || env.oauthState.has(state))) {
103
+ const stored = env.oauthState.get(state);
104
+ if (!stored) {
105
+ // Fall through to D1 when Map miss and DB present.
106
+ } else {
107
+ env.oauthState.delete(state);
108
+ return {
109
+ owner_id: stored.ownerId,
110
+ code_verifier: stored.verifier,
111
+ return_to: stored.returnTo || '',
112
+ created_at: stored.createdAt || 0,
113
+ };
114
+ }
115
+ }
116
+ if (!env.DB) return null;
117
+ const stateHash = await hashOAuthState(state);
118
+ const row = await env.DB.prepare(
119
+ `SELECT id, user_id, code_verifier_encrypted, redirect_after, expires_at, created_at, consumed_at
120
+ FROM oauth_state_nonces
121
+ WHERE state_hash = ? AND provider = ?
122
+ LIMIT 1`,
123
+ ).bind(stateHash, OAUTH_STATE_PROVIDER).first();
124
+ if (!row || row.consumed_at) return null;
125
+ const now = Math.floor(Date.now() / 1000);
126
+ await env.DB.prepare(
127
+ `UPDATE oauth_state_nonces SET consumed_at = ? WHERE id = ?`,
128
+ ).bind(now, row.id).run();
129
+ if (Number(row.expires_at) <= now) return null;
130
+ if (!vaultConfigured(env) || !row.code_verifier_encrypted) return null;
131
+ const verifier = await decryptSecret(env, row.code_verifier_encrypted, oauthStateAad(row.user_id));
132
+ return {
133
+ owner_id: row.user_id,
134
+ code_verifier: verifier,
135
+ return_to: row.redirect_after || '',
136
+ created_at: row.created_at,
137
+ };
138
+ }
139
+
54
140
  export function isCloudflareConnectionPath(pathname) {
55
141
  return pathname === '/api/connections/cloudflare'
56
142
  || pathname === '/api/connections/cloudflare/start'
@@ -60,24 +146,19 @@ export function isCloudflareConnectionPath(pathname) {
60
146
 
61
147
  async function ensureTables(env) {
62
148
  if (!env?.DB?.prepare) return;
63
- await env.DB.prepare(`CREATE TABLE IF NOT EXISTS agentsam_cloudflare_connections (
64
- connection_id TEXT PRIMARY KEY,
65
- owner_id TEXT NOT NULL,
66
- cloudflare_account_id TEXT,
67
- scopes TEXT,
68
- status TEXT,
69
- access_token_encrypted TEXT,
70
- refresh_token_encrypted TEXT,
71
- created_at INTEGER,
72
- updated_at INTEGER,
73
- expires_at INTEGER
74
- )`).run();
75
- await env.DB.prepare(`CREATE TABLE IF NOT EXISTS agentsam_cloudflare_oauth_state (
76
- state TEXT PRIMARY KEY,
77
- owner_id TEXT NOT NULL,
78
- code_verifier TEXT NOT NULL,
79
- created_at INTEGER,
80
- return_to TEXT
149
+ // PKCE state + resource inventory only. Connection credentials live in user_oauth_tokens (SSOT).
150
+ await env.DB.prepare(`CREATE TABLE IF NOT EXISTS oauth_state_nonces (
151
+ id TEXT PRIMARY KEY,
152
+ tenant_id TEXT,
153
+ user_id TEXT,
154
+ provider TEXT NOT NULL,
155
+ state_hash TEXT NOT NULL,
156
+ code_verifier_encrypted TEXT,
157
+ redirect_after TEXT,
158
+ metadata_json TEXT DEFAULT '{}',
159
+ expires_at INTEGER NOT NULL,
160
+ consumed_at INTEGER,
161
+ created_at INTEGER DEFAULT (unixepoch())
81
162
  )`).run();
82
163
  await env.DB.prepare(`CREATE TABLE IF NOT EXISTS integration_resources (
83
164
  id TEXT PRIMARY KEY,
@@ -93,23 +174,83 @@ async function ensureTables(env) {
93
174
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
94
175
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
95
176
  )`).run();
177
+ // Best-effort one-shot: copy legacy agentsam_cloudflare_connections → user_oauth_tokens.
178
+ await migrateLegacyCloudflareConnections(env);
96
179
  }
97
180
 
181
+ let legacyMigrateAttempted = false;
182
+
183
+ /**
184
+ * One-shot migrate of connected legacy rows into user_oauth_tokens, then supersede legacy.
185
+ * Safe no-op when the legacy table is absent or vault cannot decrypt.
186
+ */
187
+ async function migrateLegacyCloudflareConnections(env) {
188
+ if (legacyMigrateAttempted || !env?.DB?.prepare) return;
189
+ legacyMigrateAttempted = true;
190
+ if (!vaultConfigured(env)) return;
191
+ let results = [];
192
+ try {
193
+ const out = await env.DB.prepare(`
194
+ SELECT connection_id, owner_id, cloudflare_account_id, scopes,
195
+ access_token_encrypted, refresh_token_encrypted, expires_at
196
+ FROM agentsam_cloudflare_connections
197
+ WHERE status = 'connected' AND access_token_encrypted IS NOT NULL
198
+ `).all();
199
+ results = out?.results || [];
200
+ } catch {
201
+ return;
202
+ }
203
+ for (const row of results) {
204
+ const ownerId = String(row.owner_id || '').trim();
205
+ if (!ownerId) continue;
206
+ try {
207
+ const existing = await loadCloudflareConnectionRecord(env, ownerId);
208
+ if (existing) {
209
+ await env.DB.prepare(`
210
+ UPDATE agentsam_cloudflare_connections
211
+ SET status = 'superseded', access_token_encrypted = NULL,
212
+ refresh_token_encrypted = NULL, updated_at = unixepoch()
213
+ WHERE connection_id = ?
214
+ `).bind(row.connection_id).run();
215
+ continue;
216
+ }
217
+ const aad = `cloudflare-connection:${ownerId}`;
218
+ const accessToken = await decryptSecret(env, row.access_token_encrypted, aad);
219
+ const refreshToken = row.refresh_token_encrypted
220
+ ? await decryptSecret(env, row.refresh_token_encrypted, aad)
221
+ : null;
222
+ const spine = await upsertCloudflareUserOauthToken(env, {
223
+ userId: ownerId,
224
+ accessToken,
225
+ refreshToken,
226
+ scopes: row.scopes || '',
227
+ accountId: row.cloudflare_account_id || null,
228
+ expiresAt: row.expires_at ? Number(row.expires_at) : null,
229
+ });
230
+ if (spine?.ok) {
231
+ await env.DB.prepare(`
232
+ UPDATE agentsam_cloudflare_connections
233
+ SET status = 'superseded', access_token_encrypted = NULL,
234
+ refresh_token_encrypted = NULL, updated_at = unixepoch()
235
+ WHERE connection_id = ?
236
+ `).bind(row.connection_id).run();
237
+ }
238
+ } catch (err) {
239
+ console.error('cloudflare_legacy_migrate_failed', String(err?.message || err));
240
+ }
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Optional host-declared resources only — never bake product account IDs here.
246
+ * Shape: env.AGENTSAM_RESOURCE_SCOPE_JSON = { "resources": [ { key, type, id, name, metadata? } ] }
247
+ */
98
248
  async function seedKnownCloudflareResources(env, ownerId, connectionId, cloudflareAccountId) {
99
249
  if (!env?.DB?.prepare || !cloudflareAccountId) return;
100
- let configuredScopes = {};
101
- try { configuredScopes = JSON.parse(env.AGENTSAM_RESOURCE_SCOPE_JSON || '{}'); } catch { configuredScopes = {}; }
102
- const resources = [
103
- { key: 'agentsam-sdk', type: 'worker', id: 'agentsam-sdk', name: 'AgentSam SDK / Local Studio' },
104
- { key: 'agentsam-cad-creator', type: 'worker', id: 'agentsam-cad-creator', name: 'AgentSam CAD Creator' },
105
- { key: 'agentsam-client-cms-editor', type: 'worker', id: 'agentsam-client-cms-editor', name: 'AgentSam Client CMS Editor' },
106
- { key: 'fuelnfreetime', type: 'worker', id: 'fuelnfreetime', name: 'Fuel & Free Time', metadata: {
107
- zone_id: '816a5d2284103e4481987ceeb16c2ca9',
108
- d1_database_id: '9fd6ff92-e407-4b51-8b01-3c93f3845bb2',
109
- r2_bucket: 'fuelnfreetime',
110
- ...(configuredScopes.fuelnfreetime || {}),
111
- } },
112
- ];
250
+ let configured = {};
251
+ try { configured = JSON.parse(env.AGENTSAM_RESOURCE_SCOPE_JSON || '{}'); } catch { configured = {}; }
252
+ const resources = Array.isArray(configured.resources) ? configured.resources : [];
253
+ if (!resources.length) return;
113
254
  const statements = resources.map((resource) => env.DB.prepare(`
114
255
  INSERT OR REPLACE INTO integration_resources (
115
256
  id, tenant_id, connection_id, provider, resource_type, provider_resource_id,
@@ -119,18 +260,34 @@ async function seedKnownCloudflareResources(env, ownerId, connectionId, cloudfla
119
260
  `ires_cf_${cloudflareAccountId}_${resource.key}`,
120
261
  ownerId,
121
262
  connectionId,
122
- resource.type,
123
- resource.id,
124
- resource.name,
263
+ resource.type || 'worker',
264
+ resource.id || resource.key,
265
+ resource.name || resource.key,
125
266
  JSON.stringify({ cloudflare_account_id: cloudflareAccountId, ...(resource.metadata || {}) }),
126
267
  ));
127
268
  if (typeof env.DB.batch === 'function') await env.DB.batch(statements);
128
269
  else for (const statement of statements) await statement.run();
129
270
  }
130
271
 
131
- export async function handleCloudflareConnectionRequest(request, env) {
272
+ /**
273
+ * @param {Request} request
274
+ * @param {object} env
275
+ * @param {{
276
+ * defaultCapabilities?: string[],
277
+ * app?: { id: string },
278
+ * routeRegistry?: object,
279
+ * loginPath?: string,
280
+ * }} [options]
281
+ * Host app supplies default capability ids when the request omits ?capabilities=.
282
+ * Host MUST supply app + routeRegistry for session owner resolution (APP contract).
283
+ * The connector package itself has no product-specific default set.
284
+ */
285
+ export async function handleCloudflareConnectionRequest(request, env, options = {}) {
132
286
  const url = new URL(request.url);
133
287
  const client = resolveCloudflareOAuthClient(env);
288
+ const hostDefaultCapabilities = Array.isArray(options.defaultCapabilities)
289
+ ? options.defaultCapabilities.filter(Boolean)
290
+ : [];
134
291
  if (request.method === 'OPTIONS') {
135
292
  return new Response(null, { status: 204 });
136
293
  }
@@ -141,12 +298,33 @@ export async function handleCloudflareConnectionRequest(request, env) {
141
298
  }
142
299
 
143
300
  const isCallback = url.pathname === CLOUDFLARE_CALLBACK_PATH && request.method === 'GET';
301
+ const isStart = url.pathname === '/api/connections/cloudflare/start' && request.method === 'GET';
144
302
  let ownerId = '';
145
303
  try {
146
- ownerId = await resolveAuthenticatedOwner(request, env, url, body);
304
+ ownerId = await resolveAuthenticatedOwner(request, env, url, body, {
305
+ app: options.app,
306
+ routeRegistry: options.routeRegistry,
307
+ });
147
308
  } catch (err) {
148
309
  if (!isCallback || err.code === 'untrusted_owner_hint') {
149
310
  const code = err.code || 'unauthenticated';
311
+ // Browser GET /start without a session → Local Studio login, then return here.
312
+ if (isStart && (code === 'unauthenticated' || code === 'AUTH_APP_UNRESOLVED')) {
313
+ if (code === 'AUTH_APP_UNRESOLVED' && (!options.app?.id || !options.routeRegistry)) {
314
+ return json({
315
+ ok: false,
316
+ error: 'AUTH_APP_UNRESOLVED',
317
+ message: 'Host must pass app + routeRegistry into Cloudflare connection handler',
318
+ }, 500);
319
+ }
320
+ const loginPath = String(options.loginPath || '/auth/login').startsWith('/')
321
+ ? String(options.loginPath || '/auth/login')
322
+ : `/${options.loginPath}`;
323
+ const next = `${url.pathname}${url.search}`;
324
+ const login = new URL(loginPath, url.origin);
325
+ login.searchParams.set('next', next);
326
+ return redirect(login.toString());
327
+ }
150
328
  return json({ ok: false, error: code }, code === 'untrusted_owner_hint' ? 400 : 401);
151
329
  }
152
330
  }
@@ -171,27 +349,9 @@ export async function handleCloudflareConnectionRequest(request, env) {
171
349
  await ensureTables(env);
172
350
 
173
351
  if (url.pathname === '/api/connections/cloudflare' && request.method === 'GET') {
174
- let connection = null;
175
- if (env.DB) {
176
- const row = await env.DB.prepare(
177
- `SELECT connection_id, owner_id, cloudflare_account_id, scopes, status, created_at, updated_at, expires_at
178
- FROM agentsam_cloudflare_connections
179
- WHERE owner_id = ? AND status = 'connected'
180
- ORDER BY updated_at DESC LIMIT 1`,
181
- ).bind(ownerId).first();
182
- if (row) {
183
- connection = {
184
- connectionId: row.connection_id,
185
- ownerId: row.owner_id,
186
- cloudflareAccountId: row.cloudflare_account_id,
187
- scopes: row.scopes ? String(row.scopes).split(' ') : [],
188
- status: row.status,
189
- createdAt: row.created_at,
190
- updatedAt: row.updated_at,
191
- expiresAt: row.expires_at,
192
- };
193
- }
194
- }
352
+ const connection = env.DB
353
+ ? await loadCloudflareConnectionRecord(env, ownerId)
354
+ : null;
195
355
  return json({ ok: true, ...cloudflareConnectionSafeStatus(env, connection, ownerId) });
196
356
  }
197
357
 
@@ -202,25 +362,58 @@ export async function handleCloudflareConnectionRequest(request, env) {
202
362
  const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(verifier));
203
363
  const challenge = btoa(String.fromCharCode(...new Uint8Array(digest))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
204
364
  const returnTo = resolveReturnTo(url);
205
- if (env.DB) {
206
- await env.DB.prepare(
207
- `DELETE FROM agentsam_cloudflare_oauth_state WHERE created_at < unixepoch() - 600`,
208
- ).run();
209
- await env.DB.prepare(
210
- `INSERT INTO agentsam_cloudflare_oauth_state (state, owner_id, code_verifier, created_at, return_to)
211
- VALUES (?, ?, ?, unixepoch(), ?)`,
212
- ).bind(state, ownerId, verifier, returnTo || null).run();
213
- } else if (env.oauthState instanceof Map) {
214
- env.oauthState.set(state, { ownerId, verifier, returnTo });
365
+ const saved = await saveOAuthState(env, { state, ownerId, verifier, returnTo });
366
+ if (!saved.ok) {
367
+ return json({
368
+ ok: false,
369
+ error: saved.error || 'oauth_state_unavailable',
370
+ message: saved.error === 'vault_unavailable'
371
+ ? 'VAULT_MASTER_KEY required to encrypt OAuth PKCE verifier'
372
+ : 'oauth state store unavailable',
373
+ }, 503);
215
374
  }
216
375
  const redirectUri = `${url.origin}${CLOUDFLARE_CALLBACK_PATH}`;
376
+ const capabilityParam = url.searchParams.get('capabilities') || url.searchParams.get('capability') || '';
377
+ const packsParam = url.searchParams.get('packs') || url.searchParams.get('pack') || '';
378
+ const requested = capabilityParam
379
+ .split(',')
380
+ .map((s) => s.trim())
381
+ .filter(Boolean);
382
+ const requestedPacks = packsParam
383
+ .split(',')
384
+ .map((s) => s.trim().toLowerCase())
385
+ .filter(Boolean);
386
+ // Explicit packs win alone. Host defaults only when neither packs nor capabilities were requested.
387
+ const capabilitySet = requested.length
388
+ ? requested
389
+ : (requestedPacks.length ? [] : hostDefaultCapabilities);
390
+ const scopes = requestedCloudflareScopes({
391
+ capabilities: capabilitySet,
392
+ packs: requestedPacks,
393
+ });
217
394
  const authorize = buildAuthorizeUrl({
218
395
  clientId: String(env.CLOUDFLARE_OAUTH_CLIENT_ID),
219
396
  redirectUri,
220
397
  state,
221
398
  codeChallenge: challenge,
399
+ scopes,
400
+ });
401
+ // Browser navigation → 302 to Cloudflare. XHR/fetch (Integrations UI) → JSON.
402
+ const accept = String(request.headers.get('accept') || '');
403
+ const mode = String(request.headers.get('sec-fetch-mode') || '');
404
+ const wantsJson = accept.includes('application/json')
405
+ || mode === 'cors'
406
+ || request.headers.get('x-agentsam-oauth') === 'json';
407
+ if (!wantsJson) {
408
+ return Response.redirect(authorize, 302);
409
+ }
410
+ return json({
411
+ ok: true,
412
+ authorize_url: authorize,
413
+ callback: CLOUDFLARE_CALLBACK_PATH,
414
+ capabilities: capabilitySet,
415
+ scopes,
222
416
  });
223
- return json({ ok: true, authorize_url: authorize, callback: CLOUDFLARE_CALLBACK_PATH });
224
417
  }
225
418
 
226
419
  if (url.pathname === CLOUDFLARE_CALLBACK_PATH && request.method === 'GET') {
@@ -228,14 +421,11 @@ export async function handleCloudflareConnectionRequest(request, env) {
228
421
  const state = url.searchParams.get('state') || '';
229
422
  const providerError = url.searchParams.get('error') || '';
230
423
  let stored = null;
231
- if (env.DB) {
232
- stored = await env.DB.prepare(
233
- `SELECT owner_id, code_verifier, created_at, return_to
234
- FROM agentsam_cloudflare_oauth_state WHERE state = ?`,
235
- ).bind(state).first();
236
- } else if (env.oauthState instanceof Map) {
237
- stored = env.oauthState.get(state);
238
- if (stored) stored = { owner_id: stored.ownerId, code_verifier: stored.verifier, return_to: stored.returnTo || '' };
424
+ try {
425
+ stored = await consumeOAuthState(env, state);
426
+ } catch (err) {
427
+ console.error('cloudflare_oauth_state_consume_failed', String(err?.message || err));
428
+ return settingsRedirect(url, 'error', 'oauth_state_unavailable', '');
239
429
  }
240
430
  if (!stored) {
241
431
  // A callback without a one-time PKCE state is forged or expired. Keep
@@ -248,11 +438,9 @@ export async function handleCloudflareConnectionRequest(request, env) {
248
438
  }
249
439
  const createdAt = Number(stored.created_at || 0);
250
440
  const now = Math.floor(Date.now() / 1000);
251
- if (createdAt && now - createdAt > 10 * 60) {
252
- await deleteOAuthState(env, state);
441
+ if (createdAt && now - createdAt > OAUTH_STATE_TTL_SECONDS) {
253
442
  return settingsRedirect(url, 'error', 'oauth_state_expired', stored.return_to || '');
254
443
  }
255
- await deleteOAuthState(env, state);
256
444
  if (providerError) {
257
445
  return settingsRedirect(url, 'error', providerError, stored.return_to || '');
258
446
  }
@@ -279,47 +467,43 @@ export async function handleCloudflareConnectionRequest(request, env) {
279
467
  return settingsRedirect(url, 'error', 'token_exchange_failed', stored.return_to || '');
280
468
  }
281
469
  const tokens = await tokenRes.json();
282
- const connectionId = `cfconn_${crypto.randomUUID().replace(/-/g, '').slice(0, 16)}`;
283
- if (tokens.access_token && !vaultConfigured(env)) {
284
- return settingsRedirect(url, 'error', 'vault_unavailable', stored.return_to || '');
285
- }
286
- const aad = `cloudflare-connection:${ownerId}`;
287
- let accessEnc = null;
288
- let refreshEnc = null;
470
+ const grantedScopeStr = tokens.scope || requestedCloudflareScopes({ capabilities: hostDefaultCapabilities }).join(' ');
471
+ const capabilitySet = hostDefaultCapabilities.slice();
472
+
473
+ // SSOT: user_oauth_tokens only (scope union + client_id provenance + vault seal).
474
+ let spine = null;
289
475
  try {
290
- accessEnc = await sealToken(env, tokens.access_token, aad);
291
- refreshEnc = await sealToken(env, tokens.refresh_token, aad);
476
+ spine = await upsertCloudflareUserOauthToken(env, {
477
+ userId: ownerId,
478
+ accessToken: tokens.access_token,
479
+ refreshToken: tokens.refresh_token || null,
480
+ scopes: grantedScopeStr,
481
+ accountId: tokens.account_id || null,
482
+ expiresAt: tokens.expires_in
483
+ ? Math.floor(Date.now() / 1000) + Number(tokens.expires_in)
484
+ : null,
485
+ clientId: String(env.CLOUDFLARE_OAUTH_CLIENT_ID || ''),
486
+ capabilitySet,
487
+ });
488
+ if (!spine?.ok) {
489
+ console.error('cloudflare_user_oauth_tokens_upsert_failed', spine?.error || 'unknown');
490
+ return settingsRedirect(url, 'error', spine?.error || 'connection_persist_failed', stored.return_to || '');
491
+ }
292
492
  } catch (err) {
293
- return settingsRedirect(url, 'error', err.code || 'vault_unavailable', stored.return_to || '');
493
+ console.error('cloudflare_user_oauth_tokens_upsert_threw', String(err?.message || err));
494
+ return settingsRedirect(url, 'error', 'connection_persist_failed', stored.return_to || '');
294
495
  }
295
- if (env.DB) {
296
- const removePrevious = env.DB.prepare(
297
- `UPDATE agentsam_cloudflare_connections
298
- SET status = 'superseded', access_token_encrypted = NULL,
299
- refresh_token_encrypted = NULL, updated_at = unixepoch()
300
- WHERE owner_id = ? AND status = 'connected'`,
301
- ).bind(ownerId);
302
- const insertConnection = env.DB.prepare(
303
- `INSERT INTO agentsam_cloudflare_connections (
304
- connection_id, owner_id, cloudflare_account_id, scopes, status,
305
- access_token_encrypted, refresh_token_encrypted, created_at, updated_at, expires_at
306
- ) VALUES (?, ?, ?, ?, 'connected', ?, ?, unixepoch(), unixepoch(), ?)`,
307
- ).bind(
308
- connectionId,
496
+
497
+ const connectionId = spine.connectionId || spine.accountId || `uot_${ownerId}`;
498
+ try {
499
+ await seedKnownCloudflareResources(
500
+ env,
309
501
  ownerId,
310
- tokens.account_id || null,
311
- (tokens.scope || requestedCloudflareScopes().join(' ')),
312
- accessEnc,
313
- refreshEnc,
314
- tokens.expires_in ? Math.floor(Date.now() / 1000) + Number(tokens.expires_in) : null,
502
+ connectionId,
503
+ spine.accountId || tokens.account_id || null,
315
504
  );
316
- if (typeof env.DB.batch === 'function') {
317
- await env.DB.batch([removePrevious, insertConnection]);
318
- } else {
319
- await removePrevious.run();
320
- await insertConnection.run();
321
- }
322
- await seedKnownCloudflareResources(env, ownerId, connectionId, tokens.account_id || null);
505
+ } catch (err) {
506
+ console.error('cloudflare_resource_seed_failed', String(err?.message || err));
323
507
  }
324
508
  return settingsRedirect(url, 'connected', '', stored.return_to || '');
325
509
  }
@@ -327,35 +511,25 @@ export async function handleCloudflareConnectionRequest(request, env) {
327
511
  if (url.pathname === '/api/connections/cloudflare/disconnect' && request.method === 'POST') {
328
512
  let providerRevoked = false;
329
513
  if (env.DB) {
330
- const row = await env.DB.prepare(
331
- `SELECT connection_id, owner_id, access_token_encrypted
332
- FROM agentsam_cloudflare_connections
333
- WHERE owner_id = ? AND status = 'connected'
334
- ORDER BY updated_at DESC LIMIT 1`,
335
- ).bind(ownerId).first();
336
- if (row) {
337
- assertConnectionOwner({ ownerId: row.owner_id, connectionId: row.connection_id }, ownerId);
338
- if (row.access_token_encrypted) {
339
- try {
340
- const token = await decryptSecret(env, row.access_token_encrypted, `cloudflare-connection:${ownerId}`);
341
- const revokeParams = new URLSearchParams({ token, client_id: String(env.CLOUDFLARE_OAUTH_CLIENT_ID) });
342
- const revokeResponse = await fetch(CLOUDFLARE_OAUTH_REVOKE_URL, {
343
- method: 'POST',
344
- headers: { 'content-type': 'application/x-www-form-urlencoded' },
345
- body: revokeParams,
346
- });
347
- providerRevoked = revokeResponse.ok;
348
- } catch (error) {
349
- console.error('cloudflare_token_revoke_failed', String(error?.message || error));
350
- }
514
+ const cred = await loadCloudflareFromUserOauthTokens(env, ownerId);
515
+ if (cred?.bearerToken) {
516
+ assertConnectionOwner({ ownerId, connectionId: cred.connectionId }, ownerId);
517
+ try {
518
+ const revokeParams = new URLSearchParams({
519
+ token: cred.bearerToken,
520
+ client_id: String(env.CLOUDFLARE_OAUTH_CLIENT_ID),
521
+ });
522
+ const revokeResponse = await fetch(CLOUDFLARE_OAUTH_REVOKE_URL, {
523
+ method: 'POST',
524
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
525
+ body: revokeParams,
526
+ });
527
+ providerRevoked = revokeResponse.ok;
528
+ } catch (error) {
529
+ console.error('cloudflare_token_revoke_failed', String(error?.message || error));
351
530
  }
352
- await env.DB.prepare(`
353
- UPDATE agentsam_cloudflare_connections
354
- SET status = 'revoked', access_token_encrypted = NULL,
355
- refresh_token_encrypted = NULL, expires_at = unixepoch(), updated_at = unixepoch()
356
- WHERE connection_id = ? AND owner_id = ?
357
- `).bind(row.connection_id, ownerId).run();
358
531
  }
532
+ await revokeCloudflareUserOauthTokens(env, ownerId);
359
533
  }
360
534
  return json({ ok: true, status: 'not_configured', provider_revoked: providerRevoked });
361
535
  }