@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
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Server-side Google token exchange for CLI / desktop PKCE.
3
+ *
4
+ * Desktop/"installed" OAuth clients must be type Desktop in Google Console (no secret).
5
+ * If the configured desktop client_id was created as a Web client, Google demands a secret.
6
+ * Secrets never leave the Worker: GOOGLE_DESKTOP_CLIENT_SECRET (optional) or GOOGLE_CLIENT_SECRET
7
+ * when client_id matches the web client.
8
+ */
9
+
10
+ const GOOGLE_TOKEN_URL = 'https://oauth2.googleapis.com/token';
11
+
12
+ function clean(value) {
13
+ return value == null ? '' : String(value).trim();
14
+ }
15
+
16
+ function json(data, status = 200) {
17
+ return new Response(JSON.stringify(data), {
18
+ status,
19
+ headers: {
20
+ 'content-type': 'application/json; charset=utf-8',
21
+ 'cache-control': 'no-store',
22
+ },
23
+ });
24
+ }
25
+
26
+ function isLoopbackRedirect(uri) {
27
+ try {
28
+ const u = new URL(String(uri || ''));
29
+ if (u.protocol !== 'http:') return false;
30
+ return u.hostname === '127.0.0.1' || u.hostname === 'localhost' || u.hostname === '[::1]';
31
+ } catch {
32
+ return false;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Resolve which secret (if any) the Worker may attach for this client_id.
38
+ */
39
+ export function resolveGoogleExchangeSecret(env, clientId) {
40
+ const id = clean(clientId);
41
+ const desktopId = clean(env?.GOOGLE_DESKTOP_CLIENT_ID);
42
+ const webId = clean(env?.GOOGLE_CLIENT_ID);
43
+ const desktopSecret = clean(env?.GOOGLE_DESKTOP_CLIENT_SECRET);
44
+ const webSecret = clean(env?.GOOGLE_CLIENT_SECRET);
45
+
46
+ if (id && desktopId && id === desktopId && desktopSecret) {
47
+ return { clientId: id, clientSecret: desktopSecret, mode: 'desktop_with_optional_secret' };
48
+ }
49
+ if (id && webId && id === webId && webSecret) {
50
+ return { clientId: id, clientSecret: webSecret, mode: 'web_confidential' };
51
+ }
52
+ if (id && desktopId && id === desktopId) {
53
+ return { clientId: id, clientSecret: null, mode: 'desktop_public_pkce' };
54
+ }
55
+ return { clientId: id || null, clientSecret: null, mode: 'unknown_client' };
56
+ }
57
+
58
+ export async function exchangeGoogleAuthorizationCode({
59
+ code,
60
+ codeVerifier,
61
+ clientId,
62
+ redirectUri,
63
+ clientSecret = null,
64
+ fetchImpl = fetch,
65
+ } = {}) {
66
+ const body = new URLSearchParams({
67
+ grant_type: 'authorization_code',
68
+ code: clean(code),
69
+ code_verifier: clean(codeVerifier),
70
+ client_id: clean(clientId),
71
+ redirect_uri: clean(redirectUri),
72
+ });
73
+ if (clean(clientSecret)) body.set('client_secret', clean(clientSecret));
74
+
75
+ const res = await fetchImpl(GOOGLE_TOKEN_URL, {
76
+ method: 'POST',
77
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
78
+ body,
79
+ });
80
+ const data = await res.json().catch(() => ({}));
81
+ return { ok: res.ok && Boolean(data.access_token), status: res.status, data };
82
+ }
83
+
84
+ /**
85
+ * POST /api/oauth/google/desktop-exchange
86
+ * Body JSON: { code, code_verifier, redirect_uri, client_id }
87
+ */
88
+ export async function handleGoogleDesktopExchangeRequest(request, env, opts = {}) {
89
+ if (request.method !== 'POST') {
90
+ return json({ ok: false, error: 'method_not_allowed' }, 405);
91
+ }
92
+
93
+ let payload = {};
94
+ try {
95
+ payload = await request.json();
96
+ } catch {
97
+ return json({ ok: false, error: 'invalid_json' }, 400);
98
+ }
99
+
100
+ const code = clean(payload.code);
101
+ const codeVerifier = clean(payload.code_verifier || payload.codeVerifier);
102
+ const redirectUri = clean(payload.redirect_uri || payload.redirectUri);
103
+ const clientId = clean(payload.client_id || payload.clientId);
104
+
105
+ if (!code || !codeVerifier || !redirectUri || !clientId) {
106
+ return json({
107
+ ok: false,
108
+ error: 'code_code_verifier_redirect_uri_client_id_required',
109
+ }, 400);
110
+ }
111
+ if (!isLoopbackRedirect(redirectUri)) {
112
+ return json({ ok: false, error: 'redirect_uri_must_be_loopback' }, 400);
113
+ }
114
+
115
+ const allowed = new Set(
116
+ [clean(env?.GOOGLE_DESKTOP_CLIENT_ID), clean(env?.GOOGLE_CLIENT_ID)].filter(Boolean),
117
+ );
118
+ if (!allowed.has(clientId)) {
119
+ return json({ ok: false, error: 'client_id_not_configured_on_host' }, 403);
120
+ }
121
+
122
+ const resolved = resolveGoogleExchangeSecret(env, clientId);
123
+ if (resolved.mode === 'unknown_client') {
124
+ return json({ ok: false, error: 'client_id_not_configured_on_host' }, 403);
125
+ }
126
+
127
+ const fetchImpl = opts.fetchImpl || fetch;
128
+ const result = await exchangeGoogleAuthorizationCode({
129
+ code,
130
+ codeVerifier,
131
+ clientId,
132
+ redirectUri,
133
+ clientSecret: resolved.clientSecret,
134
+ fetchImpl,
135
+ });
136
+
137
+ const detail = String(result.data?.error_description || result.data?.error || '');
138
+ if (
139
+ !result.ok
140
+ && /client_secret/i.test(detail)
141
+ && resolved.mode === 'desktop_public_pkce'
142
+ && !resolved.clientSecret
143
+ ) {
144
+ return json({
145
+ ok: false,
146
+ error: 'desktop_client_requires_secret',
147
+ detail,
148
+ remediation: [
149
+ 'In Google Cloud Console create OAuth client type Desktop app (no secret) and set GOOGLE_DESKTOP_CLIENT_ID to that client id.',
150
+ 'Or if this client id is a Web app, put its secret on the Worker: wrangler secret put GOOGLE_DESKTOP_CLIENT_SECRET',
151
+ 'Web browser login continues to use GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET.',
152
+ ],
153
+ }, 502);
154
+ }
155
+
156
+ if (!result.ok) {
157
+ return json({
158
+ ok: false,
159
+ error: 'google_token_exchange_failed',
160
+ detail: detail || `http_${result.status}`,
161
+ }, 502);
162
+ }
163
+
164
+ return json({
165
+ ok: true,
166
+ access_token: result.data.access_token,
167
+ refresh_token: result.data.refresh_token || null,
168
+ token_type: result.data.token_type || 'Bearer',
169
+ expires_in: result.data.expires_in || null,
170
+ scope: result.data.scope || null,
171
+ exchange_mode: resolved.mode,
172
+ });
173
+ }
@@ -1,4 +1,4 @@
1
- import { AUTH_LOGIN_PATH } from '../core/constants.js';
1
+ import { IDENTITY_ROUTE_IDS } from '../contracts/route-ids.js';
2
2
  import {
3
3
  IAM_PLATFORM_CALLBACK_PATH,
4
4
  IAM_PLATFORM_STATE_PROVIDER,
@@ -38,6 +38,7 @@ export async function iamPlatformOAuthStart(request, env, adapter, identity) {
38
38
  provider: IAM_PLATFORM_STATE_PROVIDER,
39
39
  codeVerifier,
40
40
  redirectTo,
41
+ appId: identity?.app?.id || null,
41
42
  });
42
43
 
43
44
  const redirectUri = `${url.origin}${IAM_PLATFORM_CALLBACK_PATH}`;
@@ -67,15 +68,15 @@ export async function iamPlatformOAuthCallback(request, env, adapter, identity)
67
68
  const err = url.searchParams.get('error');
68
69
 
69
70
  if (err || !code || !state) {
70
- return Response.redirect(`${url.origin}${AUTH_LOGIN_PATH}?error=oauth_failed`, 302);
71
+ return Response.redirect(`${url.origin}${identity.routeRegistry.resolve(identity.app.id, IDENTITY_ROUTE_IDS.LOGIN)}?error=oauth_failed`, 302);
71
72
  }
72
73
  if (!creds) {
73
- return Response.redirect(`${url.origin}${AUTH_LOGIN_PATH}?error=iam_oauth_not_configured`, 302);
74
+ return Response.redirect(`${url.origin}${identity.routeRegistry.resolve(identity.app.id, IDENTITY_ROUTE_IDS.LOGIN)}?error=iam_oauth_not_configured`, 302);
74
75
  }
75
76
 
76
77
  const saved = await adapter.consumeOAuthState(state);
77
78
  if (!saved || saved.provider !== IAM_PLATFORM_STATE_PROVIDER) {
78
- return Response.redirect(`${url.origin}${AUTH_LOGIN_PATH}?error=state_mismatch`, 302);
79
+ return Response.redirect(`${url.origin}${identity.routeRegistry.resolve(identity.app.id, IDENTITY_ROUTE_IDS.LOGIN)}?error=state_mismatch`, 302);
79
80
  }
80
81
 
81
82
  const redirectUri = `${url.origin}${IAM_PLATFORM_CALLBACK_PATH}`;
@@ -88,7 +89,7 @@ export async function iamPlatformOAuthCallback(request, env, adapter, identity)
88
89
  redirectUri,
89
90
  });
90
91
  if (!token?.access_token) {
91
- return Response.redirect(`${url.origin}${AUTH_LOGIN_PATH}?error=token_exchange_failed`, 302);
92
+ return Response.redirect(`${url.origin}${identity.routeRegistry.resolve(identity.app.id, IDENTITY_ROUTE_IDS.LOGIN)}?error=token_exchange_failed`, 302);
92
93
  }
93
94
 
94
95
  const profile = await fetchIamProfile({
@@ -97,7 +98,7 @@ export async function iamPlatformOAuthCallback(request, env, adapter, identity)
97
98
  });
98
99
  const normalized = profile ? normalizeIamIdentity(profile) : null;
99
100
  if (!normalized?.subject || !normalized?.email) {
100
- return Response.redirect(`${url.origin}${AUTH_LOGIN_PATH}?error=userinfo_failed`, 302);
101
+ return Response.redirect(`${url.origin}${identity.routeRegistry.resolve(identity.app.id, IDENTITY_ROUTE_IDS.LOGIN)}?error=userinfo_failed`, 302);
101
102
  }
102
103
 
103
104
  const result = await identity.provisionOAuthUser({
@@ -109,7 +110,7 @@ export async function iamPlatformOAuthCallback(request, env, adapter, identity)
109
110
 
110
111
  const redirectTo = identity.resolvePostLoginPath(saved.redirect_to);
111
112
  const res = identity.buildLoginSuccessResponse(request, result.sessionId, redirectTo);
112
- const globeUrl = `${url.origin}${AUTH_LOGIN_PATH}?globe_exit=1&next=${encodeURIComponent(redirectTo)}`;
113
+ const globeUrl = `${url.origin}${identity.routeRegistry.resolve(identity.app.id, IDENTITY_ROUTE_IDS.LOGIN)}?globe_exit=1&next=${encodeURIComponent(redirectTo)}`;
113
114
  return new Response(null, {
114
115
  status: 302,
115
116
  headers: {
@@ -1,29 +1,35 @@
1
1
  /**
2
2
  * Post-OAuth redirect helpers — portable path policy with host injectables.
3
+ * Hosts MUST pass loginPath from their route projection (never hardcode).
3
4
  */
4
5
 
5
- const DEFAULT_DASHBOARD_FALLBACK = '/dashboard/agent';
6
+ import { sanitizeBrowserNextPath } from '../core/browser-paths.js';
7
+ import { IdentityRoutingError } from '../contracts/identity-store.js';
6
8
 
7
9
  /**
8
10
  * @typedef {object} OAuthRedirectPathOptions
9
11
  * @property {string} [authCookieName]
10
- * @property {string} [dashboardFallback]
12
+ * @property {string} loginPath concrete projection of identity.login
13
+ * @property {string} [fallback] only used when isAllowedLoginResumePath rejects; prefer omit
11
14
  * @property {(path: string) => boolean} [isAllowedLoginResumePath]
12
15
  * @property {(env: unknown, rawCookie: string) => Promise<string|null>} [resolveSessionIdFromCookie]
13
16
  * @property {(env: unknown, sessionId: string, reason: string, userId: string|null) => Promise<void>} [revokeAuthSession]
14
17
  */
15
18
 
16
- /**
17
- * @param {OAuthRedirectPathOptions} options
18
- */
19
19
  export function createOAuthRedirectHelpers(options = {}) {
20
20
  const authCookieName = options.authCookieName ?? 'iam_session';
21
- const dashboardFallback = options.dashboardFallback ?? DEFAULT_DASHBOARD_FALLBACK;
22
- const isAllowedLoginResumePath = options.isAllowedLoginResumePath ?? (() => false);
21
+ const loginPath = options.loginPath;
22
+ if (!loginPath || !String(loginPath).startsWith('/')) {
23
+ throw new IdentityRoutingError(
24
+ 'AUTH_ROUTE_MISSING',
25
+ 'createOAuthRedirectHelpers requires loginPath from route projection',
26
+ );
27
+ }
28
+ const isAllowedLoginResumePath = options.isAllowedLoginResumePath
29
+ ?? ((path) => Boolean(sanitizeBrowserNextPath(path)));
23
30
  const resolveSessionIdFromCookie = options.resolveSessionIdFromCookie;
24
31
  const revokeAuthSession = options.revokeAuthSession;
25
32
 
26
- /** Revoke browser cookie session before issuing a new login session. */
27
33
  async function revokeIncomingCookieSession(request, env, reason = 'oauth_login_replaced') {
28
34
  const cookie = request.headers.get('Cookie') || '';
29
35
  const match = cookie.match(new RegExp(`(?:^|;\\s*)${authCookieName}=([^;]+)`));
@@ -45,48 +51,33 @@ export function createOAuthRedirectHelpers(options = {}) {
45
51
  }
46
52
  }
47
53
 
48
- function safeDashboardLoginRedirectPath(originBase, returnTo) {
49
- if (!returnTo || typeof returnTo !== 'string') return dashboardFallback;
50
- const t = returnTo.trim();
51
- if (!t) return dashboardFallback;
52
- if (t.startsWith('/') && !t.startsWith('//') && !t.includes('://')) {
53
- if (isAllowedLoginResumePath(t)) return t;
54
- if (t.startsWith('/dashboard/settings/integrations')) return dashboardFallback;
55
- if (!t.startsWith('/dashboard')) return dashboardFallback;
56
- return t;
54
+ function safeLoginRedirectPath(_originBase, returnTo) {
55
+ const cleaned = sanitizeBrowserNextPath(returnTo);
56
+ if (!cleaned) {
57
+ throw new IdentityRoutingError('AUTH_DESTINATION_UNRESOLVED', 'return_to missing/invalid');
57
58
  }
58
- try {
59
- const u = new URL(t);
60
- const ob = new URL(originBase);
61
- if (u.origin !== ob.origin) return dashboardFallback;
62
- const p = u.pathname + (u.search || '');
63
- if (p.startsWith('/dashboard/settings/integrations')) return dashboardFallback;
64
- if (!p.startsWith('/dashboard')) return dashboardFallback;
65
- return p;
66
- } catch {
67
- return dashboardFallback;
59
+ if (!isAllowedLoginResumePath(cleaned)) {
60
+ throw new IdentityRoutingError('AUTH_DESTINATION_UNRESOLVED', 'return_to not allowed', { returnTo: cleaned });
68
61
  }
62
+ return cleaned;
69
63
  }
70
64
 
71
65
  function oauthPostLoginGlobeRedirectUrl(originBase, returnToFullUrl) {
72
- let path = dashboardFallback;
66
+ let path;
73
67
  try {
74
68
  const u = new URL(returnToFullUrl);
75
69
  path = u.pathname + (u.search || '');
76
70
  } catch {
77
- /* keep default */
78
- }
79
- if (!path.startsWith('/') || path.startsWith('//')) path = dashboardFallback;
80
- if (path.startsWith('/dashboard/settings/integrations')) path = dashboardFallback;
81
- if (!isAllowedLoginResumePath(path) && !path.startsWith('/dashboard')) {
82
- path = dashboardFallback;
71
+ throw new IdentityRoutingError('AUTH_DESTINATION_UNRESOLVED', 'invalid returnToFullUrl');
83
72
  }
84
- return `${originBase}/auth/login?globe_exit=1&next=${encodeURIComponent(path)}`;
73
+ path = safeLoginRedirectPath(originBase, path);
74
+ return `${originBase}${loginPath}?globe_exit=1&next=${encodeURIComponent(path)}`;
85
75
  }
86
76
 
87
77
  return {
88
78
  revokeIncomingCookieSession,
89
- safeDashboardLoginRedirectPath,
79
+ safeDashboardLoginRedirectPath: safeLoginRedirectPath,
80
+ safeLoginRedirectPath,
90
81
  oauthPostLoginGlobeRedirectUrl,
91
82
  };
92
83
  }
@@ -1,10 +1,9 @@
1
1
  const CLOUDFLARE_AUTH_URL = 'https://dash.cloudflare.com/oauth2/auth';
2
2
  const CLOUDFLARE_TOKEN_URL = 'https://dash.cloudflare.com/oauth2/token';
3
3
 
4
- // Identity-only login should not request the full resource-access scope set
5
- // used elsewhere for the Local Studio Cloudflare *connection* (deploy/d1/r2/etc).
6
- // account-settings.read is enough to resolve the account for userinfo.
7
- const CLOUDFLARE_LOGIN_SCOPE = 'account-settings.read offline_access';
4
+ // Identity-only login: account identity + user details (email/name via /v4/user).
5
+ // Do NOT request the resource-access scope set used by the Cloudflare *connection* flow.
6
+ const CLOUDFLARE_LOGIN_SCOPE = 'account-settings.read user-details.read offline_access';
8
7
 
9
8
  /** @param {import('../../provider-contract.js').OAuthAuthorizeInput} input */
10
9
  export function getCloudflareAuthUrl({ clientId, redirectUri, state, codeChallenge, scope } = {}) {
@@ -1,26 +1,61 @@
1
1
  const CLOUDFLARE_USERINFO_URL = 'https://dash.cloudflare.com/oauth2/userinfo';
2
+ const CLOUDFLARE_API_USER_URL = 'https://api.cloudflare.com/client/v4/user';
2
3
 
3
4
  /**
4
5
  * Fetch the authenticated Cloudflare user's identity.
5
- * Response shape not fully verified against a live token yet — this reads
6
- * defensively (sub/id/user_id, email, name) rather than assuming Cloudflare's
7
- * exact OIDC field names. Confirm the real shape on first live login and
8
- * tighten this if fields differ.
6
+ *
7
+ * OIDC userinfo (`/oauth2/userinfo`) structurally returns `sub` only.
8
+ * Email/name require `user-details.read` and a second call to `/client/v4/user`
9
+ * (`data.result.*`, not top-level fields).
10
+ *
9
11
  * @param {string} accessToken
12
+ * @param {{ fetchImpl?: typeof fetch }} [opts]
10
13
  */
11
- export async function fetchCloudflareProfile(accessToken) {
14
+ export async function fetchCloudflareProfile(accessToken, opts = {}) {
15
+ const token = String(accessToken || '').trim();
16
+ if (!token) return null;
17
+ const fetchImpl = opts.fetchImpl || fetch;
12
18
  try {
13
- const res = await fetch(CLOUDFLARE_USERINFO_URL, {
14
- headers: { Authorization: `Bearer ${accessToken}` },
19
+ const userinfoRes = await fetchImpl(CLOUDFLARE_USERINFO_URL, {
20
+ headers: { Authorization: `Bearer ${token}` },
15
21
  });
16
- if (!res.ok) return null;
17
- const data = await res.json();
18
- if (!data) return null;
22
+ if (!userinfoRes.ok) return null;
23
+ const userinfo = await userinfoRes.json().catch(() => null);
24
+ if (!userinfo) return null;
25
+
26
+ const sub = userinfo.sub || userinfo.id || userinfo.user_id || null;
27
+ let email = typeof userinfo.email === 'string' ? userinfo.email : null;
28
+ let name = typeof userinfo.name === 'string'
29
+ ? userinfo.name
30
+ : (typeof userinfo.display_name === 'string' ? userinfo.display_name : null);
31
+
32
+ // Cloudflare userinfo does not return email/name — enrich from /v4/user.
33
+ if (!email || !name) {
34
+ try {
35
+ const apiRes = await fetchImpl(CLOUDFLARE_API_USER_URL, {
36
+ headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' },
37
+ });
38
+ if (apiRes.ok) {
39
+ const body = await apiRes.json().catch(() => null);
40
+ const result = body?.result && typeof body.result === 'object' ? body.result : null;
41
+ if (result) {
42
+ if (!email && typeof result.email === 'string') email = result.email;
43
+ if (!name && typeof result.first_name === 'string' && typeof result.last_name === 'string') {
44
+ name = `${result.first_name} ${result.last_name}`.trim() || null;
45
+ }
46
+ if (!name && typeof result.name === 'string') name = result.name;
47
+ }
48
+ }
49
+ } catch {
50
+ // Non-fatal: identity can proceed with sub alone.
51
+ }
52
+ }
53
+
19
54
  return {
20
- sub: data.sub || data.id || data.user_id || data.account_id || null,
21
- email: data.email || null,
22
- name: data.name || data.display_name || null,
23
- raw: data,
55
+ sub: sub || null,
56
+ email,
57
+ name,
58
+ raw: userinfo,
24
59
  };
25
60
  } catch {
26
61
  return null;
@@ -9,13 +9,16 @@ import {
9
9
  } from './oauth.js';
10
10
  import { fetchIamProfile } from './profile.js';
11
11
  import { normalizeIamIdentity } from './mapper.js';
12
- import { DEFAULT_IAM_ORIGIN } from '../../contracts/auth-config.js';
12
+ import { resolveIamIssuer } from '../../contracts/auth-config.js';
13
13
 
14
14
  /**
15
- * @param {string} [origin]
15
+ * @param {string} origin Absolute IAM issuer origin (required — no DEFAULT_*).
16
16
  */
17
- export function createIamIdentityProvider(origin = DEFAULT_IAM_ORIGIN) {
18
- const resolvedOrigin = String(origin || DEFAULT_IAM_ORIGIN).replace(/\/+$/, '');
17
+ export function createIamIdentityProvider(origin) {
18
+ const resolvedOrigin = String(origin || '').replace(/\/+$/, '');
19
+ if (!resolvedOrigin) {
20
+ throw new Error('IAM_OAUTH_ISSUER is required to create the IAM identity provider');
21
+ }
19
22
  return createIdentityProvider({
20
23
  id: 'iam',
21
24
  authorizeUrl: (input) => getIamAuthUrl({ ...input, origin: resolvedOrigin }),
@@ -25,7 +28,26 @@ export function createIamIdentityProvider(origin = DEFAULT_IAM_ORIGIN) {
25
28
  });
26
29
  }
27
30
 
28
- export const IamProvider = createIamIdentityProvider();
31
+ /**
32
+ * Registry singleton — resolves IAM_OAUTH_ISSUER from env/input at call time.
33
+ * No baked-in product host.
34
+ */
35
+ export const IamProvider = createIdentityProvider({
36
+ id: 'iam',
37
+ authorizeUrl: (input) => {
38
+ const origin = resolveIamIssuer(input?.env || process.env, input?.origin || input?.issuer || '');
39
+ return getIamAuthUrl({ ...input, origin });
40
+ },
41
+ exchangeCode: (input) => {
42
+ const origin = resolveIamIssuer(input?.env || process.env, input?.origin || input?.issuer || '');
43
+ return exchangeIamCode({ ...input, origin });
44
+ },
45
+ getProfile: (accessToken, input = {}) => {
46
+ const origin = resolveIamIssuer(input?.env || process.env, input?.origin || input?.issuer || '');
47
+ return fetchIamProfile({ origin, accessToken });
48
+ },
49
+ normalizeIdentity: normalizeIamIdentity,
50
+ });
29
51
 
30
52
  export {
31
53
  getIamAuthUrl,
@@ -1,33 +1,48 @@
1
- import { AUTH_COOKIE_NAME, AUTH_SESSION_TTL_SECONDS } from '../core/constants.js';
1
+ import { AUTH_COOKIE_NAME, SESSION_POLICY } from '../core/constants.js';
2
2
  import { hashPassword, verifyPassword } from '../core/password-crypto.js';
3
3
  import { jsonResponse } from '../core/http-json.js';
4
- import { sanitizeBrowserNextPath } from '../core/browser-paths.js';
4
+ import { resolvePostAuthDestination } from './post-auth.js';
5
+ import { IdentityRoutingError } from '../contracts/identity-store.js';
5
6
  import { newAuthUserId } from '../adapters/cloudflare-d1/ids.js';
6
7
 
7
8
  /**
8
- * @typedef {ReturnType<import('../adapters/cloudflare-d1/index.js').createCloudflareD1Adapter>} IdentityStorageAdapter
9
+ * @typedef {import('../contracts/identity-store.js').IdentityStore} IdentityStore
9
10
  */
10
11
 
11
12
  /**
12
13
  * Server-side identity orchestration (Worker / Node). No secrets in client bundle.
13
- * @param {{ adapter: IdentityStorageAdapter, cookieName?: string, defaultRedirect?: string }} config
14
+ * Hosts must supply `app` + `routeRegistry` — no platform "/" fallback.
15
+ *
16
+ * @param {{
17
+ * adapter: IdentityStore,
18
+ * cookieName?: string,
19
+ * app: { id: string },
20
+ * routeRegistry: ReturnType<import('../contracts/route-projection.js').createRouteRegistry>,
21
+ * }} config
14
22
  */
15
23
  export function createIdentityService(config) {
16
24
  const adapter = config.adapter;
17
25
  if (!adapter) throw new Error('identity_service_requires_adapter');
18
26
  const cookieName = config.cookieName || AUTH_COOKIE_NAME;
19
- // Direct sign-in has no navigation intent to restore. Land at the site root;
20
- // protected routes always provide their own validated `next` path.
21
- const defaultRedirect = sanitizeBrowserNextPath(config.defaultRedirect) || '/';
27
+ const app = config.app || null;
28
+ const routeRegistry = config.routeRegistry || null;
29
+ if (!app?.id || !routeRegistry) {
30
+ throw new IdentityRoutingError(
31
+ 'AUTH_APP_UNRESOLVED',
32
+ 'createIdentityService requires app + routeRegistry (no platform fallback)',
33
+ );
34
+ }
35
+ routeRegistry.assertAuthCapable(app.id);
22
36
 
23
- // OAuth state is durable and may outlive a deployment. Normalize both the
24
- // incoming request value and a value read back from prior state so an old or
25
- // malformed value can never become an open redirect.
26
37
  function resolvePostLoginPath(nextPath) {
27
- return sanitizeBrowserNextPath(nextPath) || defaultRedirect;
38
+ return resolvePostAuthDestination({
39
+ transaction: { return_to: nextPath, app_id: app.id },
40
+ app,
41
+ routeRegistry,
42
+ });
28
43
  }
29
44
 
30
- function sessionCookieHeader(sessionId, requestUrl, maxAge = AUTH_SESSION_TTL_SECONDS) {
45
+ function sessionCookieHeader(sessionId, requestUrl, maxAge = SESSION_POLICY.browser.ttlSeconds) {
31
46
  const secure = new URL(requestUrl).protocol === 'https:';
32
47
  const parts = [
33
48
  `${cookieName}=${sessionId}`,
@@ -46,9 +61,16 @@ export function createIdentityService(config) {
46
61
  return match ? decodeURIComponent(match[1]) : null;
47
62
  }
48
63
 
64
+ function parseBearerSessionId(authorization) {
65
+ const match = String(authorization || '').match(/^Bearer\s+([^\s]+)$/i);
66
+ return match ? match[1] : null;
67
+ }
68
+
49
69
  return Object.freeze({
50
70
  cookieName,
51
- defaultRedirect,
71
+ app,
72
+ routeRegistry,
73
+ defaultRedirect: resolvePostLoginPath(null),
52
74
 
53
75
  async signup({ email, password, displayName }) {
54
76
  const normalized = String(email || '').trim().toLowerCase();
@@ -91,7 +113,8 @@ export function createIdentityService(config) {
91
113
  },
92
114
 
93
115
  async sessionFromRequest(request) {
94
- const sessionId = parseSessionId(request.headers.get('Cookie'));
116
+ const sessionId = parseBearerSessionId(request.headers.get('Authorization'))
117
+ || parseSessionId(request.headers.get('Cookie'));
95
118
  if (!sessionId) return null;
96
119
  const session = await adapter.getSession(sessionId);
97
120
  if (!session) return null;
@@ -101,7 +124,8 @@ export function createIdentityService(config) {
101
124
  },
102
125
 
103
126
  async logout(request) {
104
- const sessionId = parseSessionId(request.headers.get('Cookie'));
127
+ const sessionId = parseBearerSessionId(request.headers.get('Authorization'))
128
+ || parseSessionId(request.headers.get('Cookie'));
105
129
  if (sessionId) await adapter.revokeSession(sessionId);
106
130
  return { ok: true };
107
131
  },
@@ -135,6 +159,28 @@ export function createIdentityService(config) {
135
159
  return { ok: true, authUserId: user.id, sessionId: session.id, session };
136
160
  },
137
161
 
162
+ isNativeSessionRequest(request) {
163
+ const marker = String(request?.headers?.get?.('X-AgentSam-Native-Client') || '').toLowerCase();
164
+ return marker === '1' || marker === 'true' || marker === 'native';
165
+ },
166
+
167
+ buildNativeLoginSuccessResponse(result, nextPath) {
168
+ const redirect = resolvePostLoginPath(nextPath);
169
+ return jsonResponse({
170
+ ok: true,
171
+ redirect,
172
+ session_id: result.sessionId,
173
+ expires_at: result.session?.expires_at ?? null,
174
+ user: result.user
175
+ ? {
176
+ id: result.user.id,
177
+ email: result.user.email,
178
+ displayName: result.user.display_name ?? null,
179
+ }
180
+ : null,
181
+ });
182
+ },
183
+
138
184
  buildLoginSuccessResponse(request, sessionId, nextPath) {
139
185
  const redirect = resolvePostLoginPath(nextPath);
140
186
  return jsonResponse(
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Mount-level auth policy helpers (host applies; identity does not invent mounts).
3
+ */
4
+
5
+ /**
6
+ * @typedef {{ path: string, auth?: 'required'|'optional'|'public', spa?: boolean, shell?: string }} AppMount
7
+ */
8
+
9
+ export function pathMatchesMount(pathname, mount) {
10
+ const base = String(mount?.path || '');
11
+ if (!base) return false;
12
+ const path = String(pathname || '');
13
+ return path === base || path.startsWith(`${base}/`);
14
+ }
15
+
16
+ export function resolveMountForPath(pathname, app) {
17
+ const mounts = Array.isArray(app?.routes?.mounts) ? app.routes.mounts : [];
18
+ let best = null;
19
+ for (const mount of mounts) {
20
+ if (!pathMatchesMount(pathname, mount)) continue;
21
+ if (!best || String(mount.path).length > String(best.path).length) best = mount;
22
+ }
23
+ return best;
24
+ }
25
+
26
+ export function mountRequiresAuth(pathname, app) {
27
+ const mount = resolveMountForPath(pathname, app);
28
+ if (!mount) return false;
29
+ return (mount.auth || 'public') === 'required';
30
+ }