@inneranimalmedia/agentsam-sdk 2.6.4 → 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.
- package/AGENTSAM.md +11 -0
- package/README.md +15 -9
- package/apps/client-cms-editor/AGENTS.md +12 -0
- package/apps/client-cms-editor/IMPORT_PROVENANCE.json +8 -0
- package/apps/client-cms-editor/README.md +72 -0
- package/apps/client-cms-editor/agentsam.app.json +57 -0
- package/apps/client-cms-editor/backend/README.md +5 -0
- package/apps/client-cms-editor/backend/package.json +22 -0
- package/apps/client-cms-editor/backend/src/api/client.ts +200 -0
- package/apps/client-cms-editor/backend/src/demo-bootstrap.ts +145 -0
- package/apps/client-cms-editor/backend/src/index.ts +6 -0
- package/apps/client-cms-editor/backend/src/model.ts +115 -0
- package/apps/client-cms-editor/backend/src/preview/bridge.ts +79 -0
- package/apps/client-cms-editor/backend/src/preview/urls.ts +55 -0
- package/apps/client-cms-editor/backend/src/routing/cms-route.js +293 -0
- package/apps/client-cms-editor/backend/src/routing/index.js +10 -0
- package/apps/client-cms-editor/backend/src/storefront.ts +52 -0
- package/apps/client-cms-editor/backend/tsconfig.json +15 -0
- package/apps/client-cms-editor/backend/worker/index.js +34 -0
- package/apps/client-cms-editor/backend/wrangler.jsonc +11 -0
- package/apps/client-cms-editor/bin/agentsam-cms.mjs +229 -0
- package/apps/client-cms-editor/frontend/index.html +12 -0
- package/apps/client-cms-editor/frontend/package.json +33 -0
- package/apps/client-cms-editor/frontend/src/AgentSamDrawer.tsx +188 -0
- package/apps/client-cms-editor/frontend/src/CmsAgentSurface.tsx +191 -0
- package/apps/client-cms-editor/frontend/src/CmsEditor.tsx +1345 -0
- package/apps/client-cms-editor/frontend/src/env.d.ts +14 -0
- package/apps/client-cms-editor/frontend/src/index.ts +8 -0
- package/apps/client-cms-editor/frontend/src/lib/content-safety.ts +63 -0
- package/apps/client-cms-editor/frontend/src/main.tsx +17 -0
- package/apps/client-cms-editor/frontend/src/mount.tsx +51 -0
- package/apps/client-cms-editor/frontend/src/styles/agentsam-drawer.css +214 -0
- package/apps/client-cms-editor/frontend/src/styles/cmsShell.css +1739 -0
- package/apps/client-cms-editor/frontend/src/styles/studio.css +106 -0
- package/apps/client-cms-editor/frontend/src/surfaces/CmsDashboard.tsx +313 -0
- package/apps/client-cms-editor/frontend/src/surfaces/CmsGuidedChatHero.tsx +121 -0
- package/apps/client-cms-editor/frontend/src/surfaces/CmsHubPage.tsx +132 -0
- package/apps/client-cms-editor/frontend/src/surfaces/CmsSiteSwitcher.tsx +176 -0
- package/apps/client-cms-editor/frontend/src/surfaces/index.ts +5 -0
- package/apps/client-cms-editor/frontend/src/surfaces/types.ts +48 -0
- package/apps/client-cms-editor/frontend/tsconfig.json +25 -0
- package/apps/client-cms-editor/frontend/vite.config.ts +13 -0
- package/apps/client-cms-editor/package-lock.json +3832 -0
- package/apps/client-cms-editor/package.json +23 -0
- package/apps/client-cms-editor/reference/DONOR_README.md +52 -0
- package/apps/client-cms-editor/reference/DONOR_package-lock.json +1253 -0
- package/apps/client-cms-editor/reference/DONOR_tsconfig.json +15 -0
- package/apps/client-cms-editor/shared/cms/package.json +21 -0
- package/apps/client-cms-editor/shared/cms/src/agent-context.ts +35 -0
- package/apps/client-cms-editor/shared/cms/src/cloudflare-bindings.ts +51 -0
- package/apps/client-cms-editor/shared/cms/src/editor-types.ts +58 -0
- package/apps/client-cms-editor/shared/cms/src/index.ts +5 -0
- package/apps/client-cms-editor/shared/cms/src/publication.ts +24 -0
- package/apps/client-cms-editor/shared/cms/src/routes.ts +11 -0
- package/apps/client-cms-editor/shared/cms/tsconfig.json +12 -0
- package/bin/agentsam-rapid-rust +36 -0
- package/docs/AUTH_IDENTITY_CONTRACT.md +2 -0
- package/docs/MACHINE_INTELLIGENCE.md +120 -0
- package/docs/WINDOWS_RUNTIME_INSTALL.md +30 -0
- package/docs/architecture/AGENTSAM_APPLICATION_CONTRACT.md +188 -0
- package/docs/architecture/AGENTSAM_RUNTIME_PROTOCOL.md +151 -0
- package/docs/architecture/LOCAL_STUDIO_CLOUDFLARE_OAUTH.md +12 -4
- package/docs/architecture/R2_SLUG_NAMESPACE.md +20 -0
- package/docs/architecture/WORK_PRODUCT_EXTRACTION.md +97 -0
- package/docs/brand/AGENT_SAM_ICON_GRAMMAR.md +70 -0
- package/docs/content-studio/HOST_MOUNT_CHECKLIST.md +71 -0
- package/docs/content-studio/REVISION_GATE_2026-09-27.md +501 -0
- package/docs/contracts/capability-install-planner.md +67 -0
- package/docs/contracts/environment-vocabulary.md +195 -0
- package/docs/contracts/google-cloud-oauth-setup.md +199 -0
- package/docs/local-studio/WORKMODE_DONOR_NOTES.md +1 -1
- package/docs/plans/LOCAL-STUDIO-GRADUATION-2026-09-26.md +290 -0
- package/docs/plans/LOCAL-STUDIO-REAL-APP-CONTRACT-ADDENDUM-2026-09-27.md +394 -0
- package/docs/plans/MACHINE-FIRST-APPS-TUI-2026-09-24.md +227 -0
- package/docs/plans/TOMORROW-EXTENSIVE-TODO-2026-09-26.md +221 -0
- package/docs/plans/TOMORROW-MACHINE-FIRST-BRIEFING.md +145 -0
- package/docs/plans/UNIVERSAL-INGEST-MACHINE-FIRST-2026-09-24.md +215 -0
- package/docs/plugins.md +2 -2
- package/docs/product/AGENTSAM_PRODUCT_EXPERIENCE_STANDARD.md +280 -0
- package/docs/product/LOCAL_STUDIO_DESKTOP_EXPERIENCE.md +139 -0
- package/docs/product/README.md +8 -0
- package/docs/rapid-rust-wasm.md +35 -0
- package/migrations/d1/0016_cloudflare_oauth_user_oauth_tokens_ssot.sql +14 -0
- package/package.json +73 -14
- package/packages/agentsam-brand/README.md +106 -11
- package/packages/agentsam-brand/bin/agentsam-brand.mjs +9 -0
- package/packages/agentsam-brand/package.json +39 -7
- package/packages/agentsam-brand/src/adapters/cloudflare-images.js +453 -0
- package/packages/agentsam-brand/src/adapters/cloudflare-r2.js +74 -0
- package/packages/agentsam-brand/src/adapters/d1-registry.js +26 -0
- package/packages/agentsam-brand/src/adapters/filesystem.js +59 -0
- package/packages/agentsam-brand/src/adapters/memory.js +31 -0
- package/packages/agentsam-brand/src/cli/controller.js +503 -0
- package/packages/agentsam-brand/src/cli/wizard.js +526 -0
- package/packages/agentsam-brand/src/core/capabilities.js +201 -0
- package/packages/agentsam-brand/src/core/derivatives.js +211 -0
- package/packages/agentsam-brand/src/core/errors.js +12 -0
- package/packages/agentsam-brand/src/core/ingest.js +195 -0
- package/packages/agentsam-brand/src/core/inspect.js +109 -0
- package/packages/agentsam-brand/src/core/keys.js +83 -0
- package/packages/agentsam-brand/src/core/naming.js +142 -0
- package/packages/agentsam-brand/src/core/optimize.js +316 -0
- package/packages/agentsam-brand/src/core/pack.js +305 -0
- package/packages/agentsam-brand/src/core/plan.js +276 -0
- package/packages/agentsam-brand/src/core/preview.js +159 -0
- package/packages/agentsam-brand/src/core/processors/cloudflare.js +68 -0
- package/packages/agentsam-brand/src/core/processors/contract.js +203 -0
- package/packages/agentsam-brand/src/core/processors/index.js +148 -0
- package/packages/agentsam-brand/src/core/processors/native.js +82 -0
- package/packages/agentsam-brand/src/core/processors/sharp.js +113 -0
- package/packages/agentsam-brand/src/core/processors/squoosh-binary.js +195 -0
- package/packages/agentsam-brand/src/core/promote.js +288 -0
- package/packages/agentsam-brand/src/core/receipts.js +155 -0
- package/packages/agentsam-brand/src/core/v2/compile.js +280 -0
- package/packages/agentsam-brand/src/core/v2/derivatives-semantic.js +207 -0
- package/packages/agentsam-brand/src/core/v2/ingest-graph.js +387 -0
- package/packages/agentsam-brand/src/core/v2/roles.js +180 -0
- package/packages/agentsam-brand/src/core/v2/schema.js +209 -0
- package/packages/agentsam-brand/src/core/v2/templates.js +114 -0
- package/packages/agentsam-brand/src/index.js +193 -36
- package/packages/agentsam-brand/src/presets/app-icon.json +16 -0
- package/packages/agentsam-brand/src/presets/favicon.json +17 -0
- package/packages/agentsam-brand/src/presets/hero.json +12 -0
- package/packages/agentsam-brand/src/presets/index.js +40 -0
- package/packages/agentsam-brand/src/presets/logo.json +12 -0
- package/packages/agentsam-brand/src/presets/model.json +8 -0
- package/packages/agentsam-brand/src/presets/og-image.json +10 -0
- package/packages/agentsam-brand/src/presets/product-shot.json +11 -0
- package/packages/agentsam-brand/src/presets/social-image.json +10 -0
- package/packages/agentsam-brand/src/presets/web-image.json +10 -0
- package/packages/agentsam-contracts/src/artifacts.ts +41 -0
- package/packages/agentsam-knowledge/src/autorag/index.js +1 -1
- package/packages/agentsam-knowledge/src/providers/index.js +6 -4
- package/packages/agentsam-knowledge/src/providers/ollama-endpoint.js +31 -0
- package/packages/agentsam-knowledge/test/autorag.test.mjs +22 -1
- package/packages/agentsam-nav/package.json +2 -2
- package/packages/agentsam-rapid-rust/Cargo.lock +344 -0
- package/packages/agentsam-rapid-rust/Cargo.toml +18 -0
- package/packages/agentsam-rapid-rust/README.md +5 -0
- package/packages/agentsam-rapid-rust/src/main.rs +718 -0
- package/packages/agentsam-rapid-rust/templates/api/Cargo.toml.tmpl +18 -0
- package/packages/agentsam-rapid-rust/templates/api/src/lib.rs.tmpl +17 -0
- package/packages/agentsam-rapid-rust/templates/common/README.md.tmpl +22 -0
- package/packages/agentsam-rapid-rust/templates/common/package.json.tmpl +12 -0
- package/packages/agentsam-rapid-rust/templates/common/wrangler.toml.tmpl +6 -0
- package/packages/agentsam-rapid-rust/templates/crypto-auth/Cargo.toml.tmpl +21 -0
- package/packages/agentsam-rapid-rust/templates/crypto-auth/src/lib.rs.tmpl +32 -0
- package/packages/agentsam-rapid-rust/templates/data-processor/Cargo.toml.tmpl +18 -0
- package/packages/agentsam-rapid-rust/templates/data-processor/src/lib.rs.tmpl +41 -0
- package/packages/agentsam-rapid-rust/templates/shared-core/Cargo.toml.tmpl +9 -0
- package/packages/agentsam-rapid-rust/templates/shared-core/core/Cargo.toml.tmpl +7 -0
- package/packages/agentsam-rapid-rust/templates/shared-core/core/src/lib.rs.tmpl +23 -0
- package/packages/agentsam-rapid-rust/templates/shared-core/worker/Cargo.toml.tmpl +12 -0
- package/packages/agentsam-rapid-rust/templates/shared-core/worker/src/lib.rs.tmpl +16 -0
- package/packages/agentsam-rapid-rust/templates/shared-core/wrangler.toml.tmpl +6 -0
- package/packages/agentsam-repository/src/index.js +1 -0
- package/packages/agentsam-repository/src/merkle/semantic.js +3 -10
- package/packages/agentsam-repository/src/source-types.js +85 -0
- package/packages/agentsam-vault/README.md +96 -0
- package/packages/agentsam-vault/package.json +32 -0
- package/packages/agentsam-vault/src/contracts/credential.d.ts +78 -0
- package/packages/agentsam-vault/src/contracts/credential.js +132 -0
- package/packages/agentsam-vault/src/crypto/aes-gcm.d.ts +30 -0
- package/packages/agentsam-vault/src/crypto/aes-gcm.js +107 -0
- package/packages/agentsam-vault/src/crypto/oauth-envelope.js +150 -0
- package/packages/agentsam-vault/src/index.d.ts +9 -0
- package/packages/agentsam-vault/src/index.js +27 -0
- package/packages/agentsam-vault/src/providers/registry.js +90 -0
- package/packages/agentsam-vault/src/runtime/resolver.js +77 -0
- package/packages/agentsam-workbench/README.md +24 -0
- package/packages/agentsam-workbench/package.json +38 -0
- package/packages/agentsam-workbench/src/agent/AgentPanel.tsx +41 -0
- package/packages/agentsam-workbench/src/agent/ArtifactCard.tsx +9 -0
- package/packages/agentsam-workbench/src/agent/Composer.tsx +102 -0
- package/packages/agentsam-workbench/src/agent/ConnectedAgentPanel.tsx +194 -0
- package/packages/agentsam-workbench/src/agent/ContextualComposer.tsx +788 -0
- package/packages/agentsam-workbench/src/agent/GoalStatusStrip.tsx +38 -0
- package/packages/agentsam-workbench/src/agent/MiniAgentSam.tsx +86 -0
- package/packages/agentsam-workbench/src/agent/ModelSelect.tsx +19 -0
- package/packages/agentsam-workbench/src/agent/Thread.tsx +56 -0
- package/packages/agentsam-workbench/src/agent/ToolReceipt.tsx +11 -0
- package/packages/agentsam-workbench/src/agent/contextual-composer.css +611 -0
- package/packages/agentsam-workbench/src/agent/index.ts +10 -0
- package/packages/agentsam-workbench/src/agent/mini-agentsam.css +12 -0
- package/packages/agentsam-workbench/src/browser/BrowserFrame.tsx +37 -0
- package/packages/agentsam-workbench/src/browser/index.ts +1 -0
- package/packages/agentsam-workbench/src/index.ts +6 -0
- package/packages/agentsam-workbench/src/project-control.css +160 -0
- package/packages/agentsam-workbench/src/projects/ProjectsSurface.tsx +131 -0
- package/packages/agentsam-workbench/src/projects/index.ts +1 -0
- package/packages/agentsam-workbench/src/shell/Breadcrumb.tsx +35 -0
- package/packages/agentsam-workbench/src/shell/ResizableStage.tsx +99 -0
- package/packages/agentsam-workbench/src/shell/SidePanel.tsx +5 -0
- package/packages/agentsam-workbench/src/shell/index.ts +4 -0
- package/packages/agentsam-workbench/src/shell/surfaceRegistry.ts +29 -0
- package/packages/agentsam-workbench/src/terminal/TerminalSurface.tsx +5 -0
- package/packages/agentsam-workbench/src/terminal/index.ts +1 -0
- package/packages/agentsam-workbench/src/timeline/GanttBoard.tsx +258 -0
- package/packages/agentsam-workbench/src/timeline/index.ts +1 -0
- package/packages/agentsam-workbench/tsconfig.json +13 -0
- package/packages/connectors/cloudflare/OAUTH_STORAGE_AUDIT.md +53 -0
- package/packages/connectors/cloudflare/package.json +3 -1
- package/packages/connectors/cloudflare/src/api-client.js +279 -0
- package/packages/connectors/cloudflare/src/capabilities.js +585 -0
- package/packages/connectors/cloudflare/src/credential.js +318 -0
- package/packages/connectors/cloudflare/src/families/brand-protection.js +123 -0
- package/packages/connectors/cloudflare/src/families/keyless.js +114 -0
- package/packages/connectors/cloudflare/src/families/pages.js +151 -0
- package/packages/connectors/cloudflare/src/families/scanner.js +123 -0
- package/packages/connectors/cloudflare/src/families/snippets.js +207 -0
- package/packages/connectors/cloudflare/src/families/tag-gateway.js +46 -0
- package/packages/connectors/cloudflare/src/families/tags.js +124 -0
- package/packages/connectors/cloudflare/src/families/token-validation.js +95 -0
- package/packages/connectors/cloudflare/src/families/workflows.js +156 -0
- package/packages/connectors/cloudflare/src/index.js +102 -265
- package/packages/connectors/cloudflare/src/oauth-persist.js +336 -0
- package/packages/connectors/cloudflare/src/owner.js +42 -12
- package/packages/connectors/cloudflare/src/permission-options.js +1064 -0
- package/packages/connectors/cloudflare/src/routes.js +309 -144
- package/packages/connectors/cloudflare/src/scope-catalog.js +345 -0
- package/packages/connectors/cloudflare/src/vault.js +2 -2
- package/packages/connectors/cloudflare/tests/capabilities.test.mjs +276 -0
- package/packages/connectors/cloudflare/tests/connector.test.mjs +26 -30
- package/packages/connectors/cloudflare/tests/oauth-persist.test.mjs +125 -0
- package/packages/identity/.agentsam/features/oauth-login-portal/providers.json +22 -2
- package/packages/identity/.agentsam/features/oauth-login-portal/resources.json +10 -4
- package/packages/identity/.agentsam/features/oauth-login-portal/routes.json +20 -0
- package/packages/identity/README.md +1 -1
- package/packages/identity/backend/README.md +1 -1
- package/packages/identity/migrations/D1_SCHEMA_MAPPING.md +3 -2
- package/packages/identity/migrations/sqlite/004_identity_oauth_consent_catalog.sql +48 -0
- package/packages/identity/package.json +4 -3
- package/packages/identity/scripts/local-identity-bridge.mjs +184 -0
- package/packages/identity/scripts/preview-auth-portal.mjs +7 -7
- package/packages/identity/src/adapters/cloudflare-d1/index.js +31 -2
- package/packages/identity/src/adapters/sqlite/index.js +4 -1
- package/packages/identity/src/contracts/auth-config.js +28 -8
- package/packages/identity/src/contracts/external-identity.js +1 -1
- package/packages/identity/src/contracts/provider.js +1 -0
- package/packages/identity/src/frontend/auth-portal/README.md +2 -1
- package/packages/identity/src/frontend/auth-portal/pages/login.html +6 -5
- package/packages/identity/src/frontend/auth-portal/pages/reset.html +6 -5
- package/packages/identity/src/frontend/auth-portal/pages/signup.html +6 -5
- package/packages/identity/src/frontend/auth-portal/shared/company-branding.js +1 -1
- package/packages/identity/src/frontend/auth-portal/shared/desktop-identity-bridge.js +207 -0
- package/packages/identity/src/index.js +16 -0
- package/packages/identity/src/migrations/0001_identity_core.sql +19 -2
- package/packages/identity/src/migrations/0003_accounts_ssot.sql +20 -0
- package/packages/identity/src/migrations/README.md +5 -2
- package/packages/identity/src/oauth/README.md +10 -5
- package/packages/identity/src/oauth/consent-view.js +388 -0
- package/packages/identity/src/oauth/credentials.js +31 -11
- package/packages/identity/src/oauth/google-desktop-exchange.js +173 -0
- package/packages/identity/src/providers/cloudflare/oauth.js +3 -4
- package/packages/identity/src/providers/cloudflare/profile.js +49 -14
- package/packages/identity/src/providers/iam/index.js +27 -5
- package/packages/identity/src/server/identity-service.js +31 -2
- package/packages/identity/src/server/worker-router.js +111 -81
- package/packages/identity/tests/auth-config.test.mjs +3 -2
- package/packages/identity/tests/identity-init-scaffold.test.mjs +3 -0
- package/packages/identity/tests/identity-service.test.mjs +16 -0
- package/packages/identity/tests/native-session-worker.test.mjs +64 -0
- package/packages/identity/tests/oauth-consent-view.test.mjs +106 -0
- package/packages/identity/tests/oauth-credentials.test.mjs +17 -4
- package/packages/identity/tests/oauth-presets-accounts-ssot.test.mjs +58 -0
- package/packages/identity/tests/preview-auth-portal.test.mjs +3 -3
- package/packages/runtime-protocol/src/index.js +154 -0
- package/packages/theme-scenes/README.md +164 -0
- package/packages/theme-scenes/package.json +53 -0
- package/packages/theme-scenes/src/blocks/index.js +30 -0
- package/packages/theme-scenes/src/contracts/index.js +286 -0
- package/packages/theme-scenes/src/demos/index.js +65 -0
- package/packages/theme-scenes/src/index.js +75 -0
- package/packages/theme-scenes/src/presets/index.js +263 -0
- package/packages/theme-scenes/src/runtime/compile.js +119 -0
- package/packages/theme-scenes/src/scenes/index.js +146 -0
- package/packages/theme-scenes/src/shell/index.js +47 -0
- package/packages/theme-scenes/src/themes/index.js +129 -0
- package/packages/theme-scenes/src/visuals/index.js +41 -0
- package/packages/theme-scenes/test/theme-scenes.test.mjs +179 -0
- package/packages/work-graph/src/index.js +34 -1
- package/packages/work-graph/src/renderers/gantt-model.js +24 -0
- package/packages/work-graph/test/work-graph.test.mjs +27 -7
- package/protocol/README.md +15 -7
- package/protocol/apps/agentsam.app.v1.schema.json +89 -7
- package/protocol/apps/agentsam.host-install.v1.schema.json +36 -0
- package/protocol/assets/README.md +9 -0
- package/protocol/assets/asset-record.v1.schema.json +64 -0
- package/protocol/runtime/agentsam.runtime.v1.schema.json +75 -0
- package/protocol/scoring/README.md +10 -0
- package/protocol/scoring/scorecard.v1.schema.json +65 -0
- package/protocol/ui/icon-registry.mjs +1 -0
- package/registry/cms-rust-machine-20260928/installable-products.json +31 -0
- package/registry/cms-rust-machine-20260928/manifest.json +22 -0
- package/registry/cms-rust-machine-20260928/migration.sql +237 -0
- package/registry/cms-rust-machine-20260928/receipt.json +21 -0
- package/registry/cms-rust-machine-20260928/receipt.sql +27 -0
- package/scripts/build_rapid_rust_cli.py +83 -0
- package/src/attachments/index.js +6 -0
- package/src/attachments/intake.js +235 -0
- package/src/capabilities/repository-snapshot.js +19 -2
- package/src/cli/command-catalog.js +8 -0
- package/src/cli.js +182 -26
- package/src/commands/account-auth.js +94 -8
- package/src/commands/app.js +307 -3
- package/src/commands/billing.js +127 -0
- package/src/commands/brand.js +74 -60
- package/src/commands/cheat-sheet.js +154 -0
- package/src/commands/cloudflare-login.js +222 -0
- package/src/commands/cloudflare.js +453 -23
- package/src/commands/codebaseindex.js +6 -1
- package/src/commands/compute.js +4 -0
- package/src/commands/connections.js +11 -5
- package/src/commands/credentials.js +116 -0
- package/src/commands/gcloud-auth.js +427 -0
- package/src/commands/google-cloud.js +375 -0
- package/src/commands/identity-init.js +2 -2
- package/src/commands/mcp.js +226 -33
- package/src/commands/ollama.js +3 -2
- package/src/commands/plugins.js +7 -2
- package/src/commands/product.js +36 -0
- package/src/commands/runtime-status.js +28 -9
- package/src/commands/runtime.js +313 -0
- package/src/commands/rust.js +24 -0
- package/src/commands/setup.js +279 -0
- package/src/commands/shell.js +16 -3
- package/src/commands/start-local.js +2 -2
- package/src/commands/terminal.js +171 -0
- package/src/commands/update.js +140 -0
- package/src/commands/whoami.js +329 -65
- package/src/features/resolve.js +1 -0
- package/src/index.js +4 -0
- package/src/indexing/ingest/inventory.js +254 -19
- package/src/indexing/ingest/materials.js +3 -9
- package/src/indexing/ingest/style-evidence.js +181 -0
- package/src/knowledge/source.js +3 -3
- package/src/lib/account-session.js +11 -4
- package/src/lib/app-authority.js +172 -0
- package/src/lib/auth.js +94 -21
- package/src/lib/core-client.js +5 -6
- package/src/lib/credentials-audit.js +451 -0
- package/src/lib/deploy/health.js +24 -6
- package/src/lib/detect-context.js +7 -1
- package/src/lib/google-cloud-connection.js +270 -0
- package/src/lib/google-cloud-inventory.js +385 -0
- package/src/lib/google-desktop-oauth.js +372 -0
- package/src/lib/google-studio-brokered-oauth.js +158 -0
- package/src/lib/identity-scaffold.js +24 -6
- package/src/lib/init-options.js +192 -0
- package/src/lib/local-scaffold.js +48 -16
- package/src/lib/provider-command-remediation.js +308 -0
- package/src/lib/provider-credentials.js +8 -0
- package/src/lib/scaffold/index.js +8 -2
- package/src/lib/scaffold/mcp-server.js +43 -0
- package/src/lib/scaffold/templates/mcp-server/index.js +397 -0
- package/src/lib/scaffold/templates/mcp-server/presets.js +39 -0
- package/src/lib/scaffold/wizards/cms.js +93 -0
- package/src/lib/scaffold/wizards/mcp-server.js +61 -0
- package/src/lib/setup/contract.js +26 -0
- package/src/lib/setup/discover.js +116 -0
- package/src/lib/setup/execute.js +205 -0
- package/src/lib/setup/planner.js +134 -0
- package/src/lib/setup/recipes.js +235 -0
- package/src/lib/setup/runtime-persist.js +197 -0
- package/src/lib/setup/runtime.js +394 -0
- package/src/lib/slash-commands.js +2 -0
- package/src/lib/terminal/machine-identity.js +95 -0
- package/src/lib/terminal-local.js +164 -0
- package/src/lib/terminal-scopes.js +52 -0
- package/src/lib/whoami-capabilities.js +184 -0
- package/src/local-pty/server.js +34 -5
- package/src/mcp/authority.js +58 -108
- package/src/mcp/client-adapters.js +68 -144
- package/src/mcp/client.js +142 -122
- package/src/mcp/cloudflare-bundles.js +225 -0
- package/src/mcp/index.js +12 -0
- package/src/plugins/cloudflare.js +1 -1
- package/src/providers/ollama-chat.js +2 -1
- package/src/sam/types.js +27 -2
- package/src/security/inventory.js +12 -2
- package/src/security/local-vault.js +2 -0
- package/src/security/scan.js +1 -1
- package/test/account-session.test.mjs +3 -3
- package/test/app-authority-context.test.mjs +32 -0
- package/test/cli/codebaseindex-plan-ux.test.mjs +1 -1
- package/test/cli/mcp-client.test.mjs +21 -10
- package/test/cli/runtime-persist.test.mjs +56 -0
- package/test/cli/rust-command.test.mjs +21 -0
- package/test/credentials-audit.test.mjs +113 -0
- package/test/deploy-health-scan.test.mjs +29 -1
- package/test/gcloud-auth.test.mjs +111 -0
- package/test/google-cloud-connection.test.mjs +83 -0
- package/test/google-cloud-inventory.test.mjs +64 -0
- package/test/init-options.test.mjs +35 -0
- package/test/integration/app-command.test.mjs +30 -0
- package/test/integration/cad-project.test.mjs +4 -1
- package/test/integration/cms-app-scaffold.test.mjs +71 -0
- package/test/integration/cms-site-tenancy-contract.test.mjs +3 -2
- package/test/integration/codebaseindex-ingest.test.mjs +48 -0
- package/test/integration/features-resolve.test.mjs +30 -11
- package/test/integration/inspect-path-cli.test.mjs +46 -0
- package/test/integration/inventory-evidence.test.mjs +125 -0
- package/test/integration/runtime-status.integration.test.mjs +2 -1
- package/test/knowledge.test.mjs +35 -1
- package/test/loopback-oauth.test.mjs +53 -0
- package/test/ollama.test.mjs +22 -0
- package/test/security.test.mjs +23 -0
- package/test/setup-planner.test.mjs +74 -0
- package/test/setup-runtime.test.mjs +51 -0
- package/test/terminal/local-pty.mock.test.mjs +3 -1
- package/test/terminal-machine-identity.test.mjs +88 -0
- package/test/whoami-resume.test.mjs +27 -6
- package/packages/agentsam-brand/test/brand-scan.test.mjs +0 -106
- package/packages/work-graph/prototype/README.md +0 -32
- package/packages/work-graph/prototype/data/demo-workgraph.js +0 -47
- package/packages/work-graph/prototype/renderers/timeline-model.js +0 -21
- package/packages/work-graph/prototype/renderers/workgraph-runtime.js +0 -38
- 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
|
|
48
|
-
).bind(
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
expires_at INTEGER
|
|
74
|
-
|
|
75
|
-
|
|
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);
|
|
179
|
+
}
|
|
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
|
+
}
|
|
96
242
|
}
|
|
97
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
|
|
101
|
-
try {
|
|
102
|
-
const resources = [
|
|
103
|
-
|
|
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
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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,23 +362,41 @@ 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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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,
|
|
222
400
|
});
|
|
223
401
|
// Browser navigation → 302 to Cloudflare. XHR/fetch (Integrations UI) → JSON.
|
|
224
402
|
const accept = String(request.headers.get('accept') || '');
|
|
@@ -229,7 +407,13 @@ export async function handleCloudflareConnectionRequest(request, env) {
|
|
|
229
407
|
if (!wantsJson) {
|
|
230
408
|
return Response.redirect(authorize, 302);
|
|
231
409
|
}
|
|
232
|
-
return json({
|
|
410
|
+
return json({
|
|
411
|
+
ok: true,
|
|
412
|
+
authorize_url: authorize,
|
|
413
|
+
callback: CLOUDFLARE_CALLBACK_PATH,
|
|
414
|
+
capabilities: capabilitySet,
|
|
415
|
+
scopes,
|
|
416
|
+
});
|
|
233
417
|
}
|
|
234
418
|
|
|
235
419
|
if (url.pathname === CLOUDFLARE_CALLBACK_PATH && request.method === 'GET') {
|
|
@@ -237,14 +421,11 @@ export async function handleCloudflareConnectionRequest(request, env) {
|
|
|
237
421
|
const state = url.searchParams.get('state') || '';
|
|
238
422
|
const providerError = url.searchParams.get('error') || '';
|
|
239
423
|
let stored = null;
|
|
240
|
-
|
|
241
|
-
stored = await env
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
} else if (env.oauthState instanceof Map) {
|
|
246
|
-
stored = env.oauthState.get(state);
|
|
247
|
-
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', '');
|
|
248
429
|
}
|
|
249
430
|
if (!stored) {
|
|
250
431
|
// A callback without a one-time PKCE state is forged or expired. Keep
|
|
@@ -257,11 +438,9 @@ export async function handleCloudflareConnectionRequest(request, env) {
|
|
|
257
438
|
}
|
|
258
439
|
const createdAt = Number(stored.created_at || 0);
|
|
259
440
|
const now = Math.floor(Date.now() / 1000);
|
|
260
|
-
if (createdAt && now - createdAt >
|
|
261
|
-
await deleteOAuthState(env, state);
|
|
441
|
+
if (createdAt && now - createdAt > OAUTH_STATE_TTL_SECONDS) {
|
|
262
442
|
return settingsRedirect(url, 'error', 'oauth_state_expired', stored.return_to || '');
|
|
263
443
|
}
|
|
264
|
-
await deleteOAuthState(env, state);
|
|
265
444
|
if (providerError) {
|
|
266
445
|
return settingsRedirect(url, 'error', providerError, stored.return_to || '');
|
|
267
446
|
}
|
|
@@ -288,47 +467,43 @@ export async function handleCloudflareConnectionRequest(request, env) {
|
|
|
288
467
|
return settingsRedirect(url, 'error', 'token_exchange_failed', stored.return_to || '');
|
|
289
468
|
}
|
|
290
469
|
const tokens = await tokenRes.json();
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
let accessEnc = null;
|
|
297
|
-
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;
|
|
298
475
|
try {
|
|
299
|
-
|
|
300
|
-
|
|
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
|
+
}
|
|
301
492
|
} catch (err) {
|
|
302
|
-
|
|
493
|
+
console.error('cloudflare_user_oauth_tokens_upsert_threw', String(err?.message || err));
|
|
494
|
+
return settingsRedirect(url, 'error', 'connection_persist_failed', stored.return_to || '');
|
|
303
495
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
WHERE owner_id = ? AND status = 'connected'`,
|
|
310
|
-
).bind(ownerId);
|
|
311
|
-
const insertConnection = env.DB.prepare(
|
|
312
|
-
`INSERT INTO agentsam_cloudflare_connections (
|
|
313
|
-
connection_id, owner_id, cloudflare_account_id, scopes, status,
|
|
314
|
-
access_token_encrypted, refresh_token_encrypted, created_at, updated_at, expires_at
|
|
315
|
-
) VALUES (?, ?, ?, ?, 'connected', ?, ?, unixepoch(), unixepoch(), ?)`,
|
|
316
|
-
).bind(
|
|
317
|
-
connectionId,
|
|
496
|
+
|
|
497
|
+
const connectionId = spine.connectionId || spine.accountId || `uot_${ownerId}`;
|
|
498
|
+
try {
|
|
499
|
+
await seedKnownCloudflareResources(
|
|
500
|
+
env,
|
|
318
501
|
ownerId,
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
accessEnc,
|
|
322
|
-
refreshEnc,
|
|
323
|
-
tokens.expires_in ? Math.floor(Date.now() / 1000) + Number(tokens.expires_in) : null,
|
|
502
|
+
connectionId,
|
|
503
|
+
spine.accountId || tokens.account_id || null,
|
|
324
504
|
);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
} else {
|
|
328
|
-
await removePrevious.run();
|
|
329
|
-
await insertConnection.run();
|
|
330
|
-
}
|
|
331
|
-
await seedKnownCloudflareResources(env, ownerId, connectionId, tokens.account_id || null);
|
|
505
|
+
} catch (err) {
|
|
506
|
+
console.error('cloudflare_resource_seed_failed', String(err?.message || err));
|
|
332
507
|
}
|
|
333
508
|
return settingsRedirect(url, 'connected', '', stored.return_to || '');
|
|
334
509
|
}
|
|
@@ -336,35 +511,25 @@ export async function handleCloudflareConnectionRequest(request, env) {
|
|
|
336
511
|
if (url.pathname === '/api/connections/cloudflare/disconnect' && request.method === 'POST') {
|
|
337
512
|
let providerRevoked = false;
|
|
338
513
|
if (env.DB) {
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
});
|
|
356
|
-
providerRevoked = revokeResponse.ok;
|
|
357
|
-
} catch (error) {
|
|
358
|
-
console.error('cloudflare_token_revoke_failed', String(error?.message || error));
|
|
359
|
-
}
|
|
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));
|
|
360
530
|
}
|
|
361
|
-
await env.DB.prepare(`
|
|
362
|
-
UPDATE agentsam_cloudflare_connections
|
|
363
|
-
SET status = 'revoked', access_token_encrypted = NULL,
|
|
364
|
-
refresh_token_encrypted = NULL, expires_at = unixepoch(), updated_at = unixepoch()
|
|
365
|
-
WHERE connection_id = ? AND owner_id = ?
|
|
366
|
-
`).bind(row.connection_id, ownerId).run();
|
|
367
531
|
}
|
|
532
|
+
await revokeCloudflareUserOauthTokens(env, ownerId);
|
|
368
533
|
}
|
|
369
534
|
return json({ ok: true, status: 'not_configured', provider_revoked: providerRevoked });
|
|
370
535
|
}
|