studiograph 1.3.48-next.21 → 1.3.48-next.211
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/README.md +26 -16
- package/dist/agent/agent-pool.d.ts +87 -3
- package/dist/agent/agent-pool.js +188 -15
- package/dist/agent/agent-pool.js.map +1 -1
- package/dist/agent/followups.d.ts +34 -0
- package/dist/agent/followups.js +52 -0
- package/dist/agent/followups.js.map +1 -0
- package/dist/agent/orchestrator.d.ts +101 -4
- package/dist/agent/orchestrator.js +464 -66
- package/dist/agent/orchestrator.js.map +1 -1
- package/dist/agent/prompts/entity-types-section.d.ts +34 -0
- package/dist/agent/prompts/entity-types-section.js +76 -0
- package/dist/agent/prompts/entity-types-section.js.map +1 -0
- package/dist/agent/prompts/system.md +112 -54
- package/dist/agent/skill-loader.d.ts +30 -17
- package/dist/agent/skill-loader.js +63 -30
- package/dist/agent/skill-loader.js.map +1 -1
- package/dist/agent/skills/artifact-canvas/skill.md +152 -0
- package/dist/agent/skills/entity-schema.md +75 -431
- package/dist/agent/skills/interview/entity-templates.md +38 -37
- package/dist/agent/skills/interview/question-domains.md +54 -49
- package/dist/agent/skills/interview/skill.md +84 -16
- package/dist/agent/skills/schema-rules.md +28 -28
- package/dist/agent/tools/artifact-tools.d.ts +124 -0
- package/dist/agent/tools/artifact-tools.js +207 -0
- package/dist/agent/tools/artifact-tools.js.map +1 -0
- package/dist/agent/tools/capture-tools.d.ts +31 -0
- package/dist/agent/tools/capture-tools.js +40 -0
- package/dist/agent/tools/capture-tools.js.map +1 -0
- package/dist/agent/tools/folder-tools.d.ts +47 -0
- package/dist/agent/tools/folder-tools.js +249 -0
- package/dist/agent/tools/folder-tools.js.map +1 -0
- package/dist/agent/tools/fs-tools.d.ts +6 -5
- package/dist/agent/tools/fs-tools.js +5 -5
- package/dist/agent/tools/fs-tools.js.map +1 -1
- package/dist/agent/tools/graph-tools.d.ts +48 -72
- package/dist/agent/tools/graph-tools.js +852 -329
- package/dist/agent/tools/graph-tools.js.map +1 -1
- package/dist/agent/tools/history-tools.d.ts +95 -0
- package/dist/agent/tools/history-tools.js +461 -0
- package/dist/agent/tools/history-tools.js.map +1 -0
- package/dist/agent/tools/load-skill.d.ts +6 -5
- package/dist/agent/tools/load-skill.js +3 -3
- package/dist/agent/tools/load-skill.js.map +1 -1
- package/dist/agent/tools/ops-tools.d.ts +5 -4
- package/dist/agent/tools/ops-tools.js +130 -236
- package/dist/agent/tools/ops-tools.js.map +1 -1
- package/dist/agent/tools/permission-tools.d.ts +87 -17
- package/dist/agent/tools/permission-tools.js +233 -43
- package/dist/agent/tools/permission-tools.js.map +1 -1
- package/dist/agent/tools/tool-loader.js +5 -4
- package/dist/agent/tools/tool-loader.js.map +1 -1
- package/dist/agent/tools/web-tools.js +58 -9
- package/dist/agent/tools/web-tools.js.map +1 -1
- package/dist/cli/commands/about.d.ts +1 -0
- package/dist/cli/commands/about.js +55 -3
- package/dist/cli/commands/about.js.map +1 -1
- package/dist/cli/commands/admin/audit-workspace.d.ts +23 -0
- package/dist/cli/commands/admin/audit-workspace.js +133 -0
- package/dist/cli/commands/admin/audit-workspace.js.map +1 -0
- package/dist/cli/commands/admin/audit.d.ts +21 -0
- package/dist/cli/commands/admin/audit.js +174 -0
- package/dist/cli/commands/admin/audit.js.map +1 -0
- package/dist/cli/commands/admin/backup.d.ts +54 -0
- package/dist/cli/commands/admin/backup.js +207 -0
- package/dist/cli/commands/admin/backup.js.map +1 -0
- package/dist/cli/commands/admin/folder.d.ts +11 -0
- package/dist/cli/commands/{collection.js → admin/folder.js} +33 -32
- package/dist/cli/commands/admin/folder.js.map +1 -0
- package/dist/cli/commands/admin/index.d.ts +16 -0
- package/dist/cli/commands/admin/index.js +46 -0
- package/dist/cli/commands/admin/index.js.map +1 -0
- package/dist/cli/commands/admin/migrate-assets.d.ts +53 -0
- package/dist/cli/commands/admin/migrate-assets.js +184 -0
- package/dist/cli/commands/admin/migrate-assets.js.map +1 -0
- package/dist/cli/commands/admin/migrate.d.ts +56 -0
- package/dist/cli/commands/admin/migrate.js +263 -0
- package/dist/cli/commands/admin/migrate.js.map +1 -0
- package/dist/cli/commands/admin/restore.d.ts +24 -0
- package/dist/cli/commands/admin/restore.js +228 -0
- package/dist/cli/commands/admin/restore.js.map +1 -0
- package/dist/cli/commands/admin/secrets.d.ts +23 -0
- package/dist/cli/commands/admin/secrets.js +256 -0
- package/dist/cli/commands/admin/secrets.js.map +1 -0
- package/dist/cli/commands/admin/settings.d.ts +28 -0
- package/dist/cli/commands/admin/settings.js +284 -0
- package/dist/cli/commands/admin/settings.js.map +1 -0
- package/dist/cli/commands/admin/token.d.ts +42 -0
- package/dist/cli/commands/admin/token.js +126 -0
- package/dist/cli/commands/admin/token.js.map +1 -0
- package/dist/cli/commands/admin/transfer-ownership.d.ts +15 -0
- package/dist/cli/commands/admin/transfer-ownership.js +106 -0
- package/dist/cli/commands/admin/transfer-ownership.js.map +1 -0
- package/dist/cli/commands/admin/user.d.ts +11 -0
- package/dist/cli/commands/admin/user.js +187 -0
- package/dist/cli/commands/admin/user.js.map +1 -0
- package/dist/cli/commands/clone.d.ts +25 -3
- package/dist/cli/commands/clone.js +142 -36
- package/dist/cli/commands/clone.js.map +1 -1
- package/dist/cli/commands/config.d.ts +23 -107
- package/dist/cli/commands/config.js +52 -260
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/connector.d.ts +10 -13
- package/dist/cli/commands/connector.js +16 -58
- package/dist/cli/commands/connector.js.map +1 -1
- package/dist/cli/commands/deploy.js +215 -68
- package/dist/cli/commands/deploy.js.map +1 -1
- package/dist/cli/commands/index.js +5 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.js +10 -30
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/mcp.js +54 -6
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/r2.d.ts +3 -0
- package/dist/cli/commands/r2.js +223 -204
- package/dist/cli/commands/r2.js.map +1 -1
- package/dist/cli/commands/redeploy.js +65 -12
- package/dist/cli/commands/redeploy.js.map +1 -1
- package/dist/cli/commands/reset.d.ts +22 -6
- package/dist/cli/commands/reset.js +132 -34
- package/dist/cli/commands/reset.js.map +1 -1
- package/dist/cli/commands/serve.js +231 -26
- package/dist/cli/commands/serve.js.map +1 -1
- package/dist/cli/commands/sync.d.ts +1 -1
- package/dist/cli/commands/sync.js +237 -227
- package/dist/cli/commands/sync.js.map +1 -1
- package/dist/cli/crypto-bundle.d.ts +39 -0
- package/dist/cli/crypto-bundle.js +94 -0
- package/dist/cli/crypto-bundle.js.map +1 -0
- package/dist/cli/index.js +24 -26
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/railway-pin.d.ts +68 -0
- package/dist/cli/railway-pin.js +96 -0
- package/dist/cli/railway-pin.js.map +1 -0
- package/dist/cli/railway-provisioning.d.ts +53 -0
- package/dist/cli/railway-provisioning.js +85 -0
- package/dist/cli/railway-provisioning.js.map +1 -0
- package/dist/cli/setup-wizard.d.ts +30 -49
- package/dist/cli/setup-wizard.js +39 -40
- package/dist/cli/setup-wizard.js.map +1 -1
- package/dist/core/accent-palette.d.ts +22 -0
- package/dist/core/accent-palette.js +47 -0
- package/dist/core/accent-palette.js.map +1 -0
- package/dist/core/artifact-asset-urls.d.ts +51 -0
- package/dist/core/artifact-asset-urls.js +79 -0
- package/dist/core/artifact-asset-urls.js.map +1 -0
- package/dist/core/artifact-bundle.d.ts +69 -0
- package/dist/core/artifact-bundle.js +305 -0
- package/dist/core/artifact-bundle.js.map +1 -0
- package/dist/core/artifact-escape.d.ts +5 -0
- package/dist/core/artifact-escape.js +26 -0
- package/dist/core/artifact-escape.js.map +1 -0
- package/dist/core/artifact-export.d.ts +17 -0
- package/dist/core/artifact-export.js +529 -0
- package/dist/core/artifact-export.js.map +1 -0
- package/dist/core/cell-anchor.d.ts +29 -0
- package/dist/core/cell-anchor.js +53 -0
- package/dist/core/cell-anchor.js.map +1 -0
- package/dist/core/comment-anchor.d.ts +41 -0
- package/dist/core/comment-anchor.js +147 -0
- package/dist/core/comment-anchor.js.map +1 -0
- package/dist/core/commit-messages.d.ts +57 -0
- package/dist/core/commit-messages.js +85 -0
- package/dist/core/commit-messages.js.map +1 -0
- package/dist/core/embeds.d.ts +96 -0
- package/dist/core/embeds.js +196 -0
- package/dist/core/embeds.js.map +1 -0
- package/dist/core/entity-body-templates.d.ts +21 -0
- package/dist/core/entity-body-templates.js +67 -0
- package/dist/core/entity-body-templates.js.map +1 -0
- package/dist/core/entity-types-catalog.d.ts +65 -0
- package/dist/core/entity-types-catalog.js +142 -0
- package/dist/core/entity-types-catalog.js.map +1 -0
- package/dist/core/field-library.d.ts +62 -0
- package/dist/core/field-library.js +129 -0
- package/dist/core/field-library.js.map +1 -0
- package/dist/core/folder-paths.d.ts +41 -0
- package/dist/core/folder-paths.js +95 -0
- package/dist/core/folder-paths.js.map +1 -0
- package/dist/core/folder-sidecar.d.ts +36 -0
- package/dist/core/folder-sidecar.js +91 -0
- package/dist/core/folder-sidecar.js.map +1 -0
- package/dist/core/format-registry.d.ts +170 -0
- package/dist/core/format-registry.js +412 -0
- package/dist/core/format-registry.js.map +1 -0
- package/dist/core/graph.d.ts +773 -14
- package/dist/core/graph.js +2269 -308
- package/dist/core/graph.js.map +1 -1
- package/dist/core/history-diff.d.ts +35 -0
- package/dist/core/history-diff.js +114 -0
- package/dist/core/history-diff.js.map +1 -0
- package/dist/core/refs.d.ts +41 -0
- package/dist/core/refs.js +80 -0
- package/dist/core/refs.js.map +1 -0
- package/dist/core/schema-registry.d.ts +66 -0
- package/dist/core/schema-registry.js +198 -37
- package/dist/core/schema-registry.js.map +1 -1
- package/dist/core/schemas/connector.d.ts +67 -0
- package/dist/core/schemas/connector.js +100 -0
- package/dist/core/schemas/connector.js.map +1 -0
- package/dist/core/schemas/workspace.d.ts +122 -0
- package/dist/core/schemas/workspace.js +211 -0
- package/dist/core/schemas/workspace.js.map +1 -0
- package/dist/core/secrets/SecretStore.d.ts +77 -0
- package/dist/core/secrets/SecretStore.js +13 -0
- package/dist/core/secrets/SecretStore.js.map +1 -0
- package/dist/core/secrets/SettingsResolver.d.ts +54 -0
- package/dist/core/secrets/SettingsResolver.js +80 -0
- package/dist/core/secrets/SettingsResolver.js.map +1 -0
- package/dist/core/secrets/SettingsStore.d.ts +30 -0
- package/dist/core/secrets/SettingsStore.js +9 -0
- package/dist/core/secrets/SettingsStore.js.map +1 -0
- package/dist/core/secrets/SqliteEncryptedStore.d.ts +90 -0
- package/dist/core/secrets/SqliteEncryptedStore.js +598 -0
- package/dist/core/secrets/SqliteEncryptedStore.js.map +1 -0
- package/dist/core/secrets/audit.d.ts +36 -0
- package/dist/core/secrets/audit.js +60 -0
- package/dist/core/secrets/audit.js.map +1 -0
- package/dist/core/secrets/auth-db-migration.d.ts +129 -0
- package/dist/core/secrets/auth-db-migration.js +653 -0
- package/dist/core/secrets/auth-db-migration.js.map +1 -0
- package/dist/core/secrets/bundle.d.ts +141 -0
- package/dist/core/secrets/bundle.js +435 -0
- package/dist/core/secrets/bundle.js.map +1 -0
- package/dist/core/secrets/dual-write.d.ts +81 -0
- package/dist/core/secrets/dual-write.js +247 -0
- package/dist/core/secrets/dual-write.js.map +1 -0
- package/dist/core/secrets/encryption.d.ts +44 -0
- package/dist/core/secrets/encryption.js +97 -0
- package/dist/core/secrets/encryption.js.map +1 -0
- package/dist/core/secrets/index.d.ts +49 -0
- package/dist/core/secrets/index.js +74 -0
- package/dist/core/secrets/index.js.map +1 -0
- package/dist/core/secrets/master-key.d.ts +38 -0
- package/dist/core/secrets/master-key.js +122 -0
- package/dist/core/secrets/master-key.js.map +1 -0
- package/dist/core/secrets/probes/anthropic.d.ts +98 -0
- package/dist/core/secrets/probes/anthropic.js +300 -0
- package/dist/core/secrets/probes/anthropic.js.map +1 -0
- package/dist/core/secrets/probes/brave.d.ts +9 -0
- package/dist/core/secrets/probes/brave.js +15 -0
- package/dist/core/secrets/probes/brave.js.map +1 -0
- package/dist/core/secrets/probes/r2.d.ts +15 -0
- package/dist/core/secrets/probes/r2.js +14 -0
- package/dist/core/secrets/probes/r2.js.map +1 -0
- package/dist/core/secrets/probes/tavily.d.ts +18 -0
- package/dist/core/secrets/probes/tavily.js +58 -0
- package/dist/core/secrets/probes/tavily.js.map +1 -0
- package/dist/core/secrets/probes/voyage.d.ts +13 -0
- package/dist/core/secrets/probes/voyage.js +52 -0
- package/dist/core/secrets/probes/voyage.js.map +1 -0
- package/dist/core/secrets/r2-structural-migration.d.ts +39 -0
- package/dist/core/secrets/r2-structural-migration.js +109 -0
- package/dist/core/secrets/r2-structural-migration.js.map +1 -0
- package/dist/core/secrets/read-flip.d.ts +59 -0
- package/dist/core/secrets/read-flip.js +87 -0
- package/dist/core/secrets/read-flip.js.map +1 -0
- package/dist/core/secrets/registry.d.ts +188 -0
- package/dist/core/secrets/registry.js +616 -0
- package/dist/core/secrets/registry.js.map +1 -0
- package/dist/core/types.d.ts +77 -483
- package/dist/core/types.js +25 -3
- package/dist/core/types.js.map +1 -1
- package/dist/core/user-config.d.ts +75 -12
- package/dist/core/user-config.js +155 -14
- package/dist/core/user-config.js.map +1 -1
- package/dist/core/validation.d.ts +786 -1373
- package/dist/core/validation.js +458 -147
- package/dist/core/validation.js.map +1 -1
- package/dist/core/workspace-manager.d.ts +87 -14
- package/dist/core/workspace-manager.js +222 -94
- package/dist/core/workspace-manager.js.map +1 -1
- package/dist/core/workspace.d.ts +19 -5
- package/dist/core/workspace.js +67 -39
- package/dist/core/workspace.js.map +1 -1
- package/dist/mcp/comment-virtual-entity.d.ts +36 -0
- package/dist/mcp/comment-virtual-entity.js +71 -0
- package/dist/mcp/comment-virtual-entity.js.map +1 -0
- package/dist/mcp/connector-manager.d.ts +72 -2
- package/dist/mcp/connector-manager.js +211 -32
- package/dist/mcp/connector-manager.js.map +1 -1
- package/dist/mcp/oauth-provider.d.ts +0 -5
- package/dist/mcp/oauth-provider.js +10 -22
- package/dist/mcp/oauth-provider.js.map +1 -1
- package/dist/mcp/oauth-registry.d.ts +46 -8
- package/dist/mcp/oauth-registry.js +52 -13
- package/dist/mcp/oauth-registry.js.map +1 -1
- package/dist/mcp/server-discovery.d.ts +73 -0
- package/dist/mcp/server-discovery.js +164 -0
- package/dist/mcp/server-discovery.js.map +1 -0
- package/dist/mcp/server.d.ts +24 -3
- package/dist/mcp/server.js +53 -10
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/state-signer.d.ts +62 -0
- package/dist/mcp/state-signer.js +205 -0
- package/dist/mcp/state-signer.js.map +1 -0
- package/dist/mcp/stdio-proxy.d.ts +67 -0
- package/dist/mcp/stdio-proxy.js +149 -0
- package/dist/mcp/stdio-proxy.js.map +1 -0
- package/dist/mcp/tools.d.ts +65 -2
- package/dist/mcp/tools.js +1772 -124
- package/dist/mcp/tools.js.map +1 -1
- package/dist/server/__tests__/helpers/graph-api.d.ts +16 -0
- package/dist/server/__tests__/helpers/graph-api.js +16 -0
- package/dist/server/__tests__/helpers/graph-api.js.map +1 -0
- package/dist/server/artifact-asset-mint.d.ts +41 -0
- package/dist/server/artifact-asset-mint.js +59 -0
- package/dist/server/artifact-asset-mint.js.map +1 -0
- package/dist/server/asset-index-queue.d.ts +98 -0
- package/dist/server/asset-index-queue.js +193 -0
- package/dist/server/asset-index-queue.js.map +1 -0
- package/dist/server/body-write-coordinator.d.ts +161 -0
- package/dist/server/body-write-coordinator.js +182 -0
- package/dist/server/body-write-coordinator.js.map +1 -0
- package/dist/server/cloud-billing-flow.d.ts +32 -0
- package/dist/server/cloud-billing-flow.js +75 -0
- package/dist/server/cloud-billing-flow.js.map +1 -0
- package/dist/server/commit-audit.d.ts +26 -0
- package/dist/server/commit-audit.js +30 -0
- package/dist/server/commit-audit.js.map +1 -0
- package/dist/server/index.d.ts +29 -3
- package/dist/server/index.js +657 -269
- package/dist/server/index.js.map +1 -1
- package/dist/server/middleware/sanitize.d.ts +46 -0
- package/dist/server/middleware/sanitize.js +182 -0
- package/dist/server/middleware/sanitize.js.map +1 -0
- package/dist/server/routes/artifact-export-api.d.ts +11 -0
- package/dist/server/routes/artifact-export-api.js +159 -0
- package/dist/server/routes/artifact-export-api.js.map +1 -0
- package/dist/server/routes/asset-api.d.ts +76 -1
- package/dist/server/routes/asset-api.js +761 -61
- package/dist/server/routes/asset-api.js.map +1 -1
- package/dist/server/routes/audit-api.d.ts +24 -0
- package/dist/server/routes/audit-api.js +117 -0
- package/dist/server/routes/audit-api.js.map +1 -0
- package/dist/server/routes/auth-api.js +532 -63
- package/dist/server/routes/auth-api.js.map +1 -1
- package/dist/server/routes/capture-api.d.ts +10 -3
- package/dist/server/routes/capture-api.js +213 -25
- package/dist/server/routes/capture-api.js.map +1 -1
- package/dist/server/routes/chat-sessions-api.d.ts +13 -0
- package/dist/server/routes/chat-sessions-api.js +455 -0
- package/dist/server/routes/chat-sessions-api.js.map +1 -0
- package/dist/server/routes/chat.d.ts +35 -1
- package/dist/server/routes/chat.js +699 -54
- package/dist/server/routes/chat.js.map +1 -1
- package/dist/server/routes/comments-api.d.ts +15 -0
- package/dist/server/routes/comments-api.js +444 -0
- package/dist/server/routes/comments-api.js.map +1 -0
- package/dist/server/routes/config-api.d.ts +3 -2
- package/dist/server/routes/config-api.js +179 -49
- package/dist/server/routes/config-api.js.map +1 -1
- package/dist/server/routes/connectors-api.js +177 -42
- package/dist/server/routes/connectors-api.js.map +1 -1
- package/dist/server/routes/event-ingest-api.d.ts +15 -0
- package/dist/server/routes/{meeting-ingest-api.js → event-ingest-api.js} +35 -20
- package/dist/server/routes/event-ingest-api.js.map +1 -0
- package/dist/server/routes/field-library-api.d.ts +28 -0
- package/dist/server/routes/field-library-api.js +126 -0
- package/dist/server/routes/field-library-api.js.map +1 -0
- package/dist/server/routes/git-http.d.ts +2 -2
- package/dist/server/routes/git-http.js +86 -39
- package/dist/server/routes/git-http.js.map +1 -1
- package/dist/server/routes/graph-api-access.d.ts +57 -0
- package/dist/server/routes/graph-api-access.js +112 -0
- package/dist/server/routes/graph-api-access.js.map +1 -0
- package/dist/server/routes/graph-api.d.ts +1 -1
- package/dist/server/routes/graph-api.js +1455 -325
- package/dist/server/routes/graph-api.js.map +1 -1
- package/dist/server/routes/health.d.ts +18 -0
- package/dist/server/routes/health.js +74 -0
- package/dist/server/routes/health.js.map +1 -0
- package/dist/server/routes/import-batch.d.ts +24 -0
- package/dist/server/routes/import-batch.js +33 -0
- package/dist/server/routes/import-batch.js.map +1 -0
- package/dist/server/routes/insights-api.d.ts +9 -2
- package/dist/server/routes/insights-api.js +355 -61
- package/dist/server/routes/insights-api.js.map +1 -1
- package/dist/server/routes/mcp.d.ts +7 -3
- package/dist/server/routes/mcp.js +24 -8
- package/dist/server/routes/mcp.js.map +1 -1
- package/dist/server/routes/oauth-api.d.ts +67 -0
- package/dist/server/routes/oauth-api.js +421 -0
- package/dist/server/routes/oauth-api.js.map +1 -0
- package/dist/server/routes/permissions-api.d.ts +9 -2
- package/dist/server/routes/permissions-api.js +87 -31
- package/dist/server/routes/permissions-api.js.map +1 -1
- package/dist/server/routes/r2-api.d.ts +25 -0
- package/dist/server/routes/r2-api.js +276 -0
- package/dist/server/routes/r2-api.js.map +1 -0
- package/dist/server/routes/secrets-api.d.ts +36 -0
- package/dist/server/routes/secrets-api.js +308 -0
- package/dist/server/routes/secrets-api.js.map +1 -0
- package/dist/server/routes/settings-api.d.ts +29 -0
- package/dist/server/routes/settings-api.js +180 -0
- package/dist/server/routes/settings-api.js.map +1 -0
- package/dist/server/routes/share-api.d.ts +32 -0
- package/dist/server/routes/share-api.js +234 -0
- package/dist/server/routes/share-api.js.map +1 -0
- package/dist/server/routes/views-api.js +51 -0
- package/dist/server/routes/views-api.js.map +1 -1
- package/dist/server/routes/workspace-api.d.ts +2 -1
- package/dist/server/routes/workspace-api.js +142 -23
- package/dist/server/routes/workspace-api.js.map +1 -1
- package/dist/server/routes/ws.d.ts +3 -2
- package/dist/server/routes/ws.js +68 -17
- package/dist/server/routes/ws.js.map +1 -1
- package/dist/server/session-manager.d.ts +48 -5
- package/dist/server/session-manager.js +182 -14
- package/dist/server/session-manager.js.map +1 -1
- package/dist/server/ws-hub.d.ts +124 -37
- package/dist/server/ws-hub.js +0 -0
- package/dist/server/ws-hub.js.map +1 -1
- package/dist/server/yjs-manager.d.ts +278 -7
- package/dist/server/yjs-manager.js +830 -38
- package/dist/server/yjs-manager.js.map +1 -1
- package/dist/server/yjs-persistence.d.ts +165 -0
- package/dist/server/yjs-persistence.js +557 -0
- package/dist/server/yjs-persistence.js.map +1 -0
- package/dist/server/yjs-text-diff.d.ts +32 -0
- package/dist/server/yjs-text-diff.js +61 -0
- package/dist/server/yjs-text-diff.js.map +1 -0
- package/dist/services/access-control.d.ts +73 -0
- package/dist/services/access-control.js +165 -0
- package/dist/services/access-control.js.map +1 -0
- package/dist/services/artifact-asset-token.d.ts +69 -0
- package/dist/services/artifact-asset-token.js +94 -0
- package/dist/services/artifact-asset-token.js.map +1 -0
- package/dist/services/artifact-library-sources.d.ts +3 -0
- package/dist/services/artifact-library-sources.js +44 -0
- package/dist/services/artifact-library-sources.js.map +1 -0
- package/dist/services/artifact-render-export.d.ts +97 -0
- package/dist/services/artifact-render-export.js +467 -0
- package/dist/services/artifact-render-export.js.map +1 -0
- package/dist/services/asset-caption-service.d.ts +103 -0
- package/dist/services/asset-caption-service.js +186 -0
- package/dist/services/asset-caption-service.js.map +1 -0
- package/dist/services/asset-delete-authz.d.ts +31 -0
- package/dist/services/asset-delete-authz.js +61 -0
- package/dist/services/asset-delete-authz.js.map +1 -0
- package/dist/services/asset-finalize-service.d.ts +58 -0
- package/dist/services/asset-finalize-service.js +216 -0
- package/dist/services/asset-finalize-service.js.map +1 -0
- package/dist/services/asset-import-service.d.ts +111 -0
- package/dist/services/asset-import-service.js +394 -0
- package/dist/services/asset-import-service.js.map +1 -0
- package/dist/services/asset-index-sweep.d.ts +95 -0
- package/dist/services/asset-index-sweep.js +249 -0
- package/dist/services/asset-index-sweep.js.map +1 -0
- package/dist/services/asset-lifecycle-check.d.ts +22 -0
- package/dist/services/asset-lifecycle-check.js +80 -0
- package/dist/services/asset-lifecycle-check.js.map +1 -0
- package/dist/services/asset-listing.d.ts +72 -0
- package/dist/services/asset-listing.js +321 -0
- package/dist/services/asset-listing.js.map +1 -0
- package/dist/services/asset-presign-service.d.ts +68 -0
- package/dist/services/asset-presign-service.js +124 -0
- package/dist/services/asset-presign-service.js.map +1 -0
- package/dist/services/asset-sidecar-service.d.ts +28 -0
- package/dist/services/asset-sidecar-service.js +79 -0
- package/dist/services/asset-sidecar-service.js.map +1 -0
- package/dist/services/asset-upload-errors.d.ts +41 -0
- package/dist/services/asset-upload-errors.js +45 -0
- package/dist/services/asset-upload-errors.js.map +1 -0
- package/dist/services/asset-upload-service.d.ts +56 -0
- package/dist/services/asset-upload-service.js +200 -0
- package/dist/services/asset-upload-service.js.map +1 -0
- package/dist/services/asset-upload-token.d.ts +58 -0
- package/dist/services/asset-upload-token.js +75 -0
- package/dist/services/asset-upload-token.js.map +1 -0
- package/dist/services/assets/asset-index-service.d.ts +127 -0
- package/dist/services/assets/asset-index-service.js +227 -0
- package/dist/services/assets/asset-index-service.js.map +1 -0
- package/dist/services/assets/base.d.ts +128 -2
- package/dist/services/assets/base.js +98 -1
- package/dist/services/assets/base.js.map +1 -1
- package/dist/services/assets/index.d.ts +114 -1
- package/dist/services/assets/index.js +221 -0
- package/dist/services/assets/index.js.map +1 -1
- package/dist/services/assets/local.d.ts +16 -1
- package/dist/services/assets/local.js +110 -1
- package/dist/services/assets/local.js.map +1 -1
- package/dist/services/assets/r2-sync.d.ts +88 -0
- package/dist/services/assets/r2-sync.js +412 -0
- package/dist/services/assets/r2-sync.js.map +1 -0
- package/dist/services/assets/r2.d.ts +87 -1
- package/dist/services/assets/r2.js +203 -1
- package/dist/services/assets/r2.js.map +1 -1
- package/dist/services/auth-service.d.ts +312 -45
- package/dist/services/auth-service.js +1011 -195
- package/dist/services/auth-service.js.map +1 -1
- package/dist/services/chat-session-service.d.ts +188 -0
- package/dist/services/chat-session-service.js +512 -0
- package/dist/services/chat-session-service.js.map +1 -0
- package/dist/services/cloud-inference-billing.d.ts +64 -0
- package/dist/services/cloud-inference-billing.js +313 -0
- package/dist/services/cloud-inference-billing.js.map +1 -0
- package/dist/services/comment-service.d.ts +97 -0
- package/dist/services/comment-service.js +341 -0
- package/dist/services/comment-service.js.map +1 -0
- package/dist/services/comment-virtual-entity.d.ts +36 -0
- package/dist/services/comment-virtual-entity.js +71 -0
- package/dist/services/comment-virtual-entity.js.map +1 -0
- package/dist/services/convert-service.d.ts +64 -0
- package/dist/services/convert-service.js +68 -0
- package/dist/services/convert-service.js.map +1 -0
- package/dist/services/csv-service.d.ts +22 -17
- package/dist/services/csv-service.js +55 -92
- package/dist/services/csv-service.js.map +1 -1
- package/dist/services/entity-mutations.d.ts +213 -0
- package/dist/services/entity-mutations.js +380 -0
- package/dist/services/entity-mutations.js.map +1 -0
- package/dist/services/{meeting-processor.d.ts → event-processor.d.ts} +15 -8
- package/dist/services/{meeting-processor.js → event-processor.js} +124 -65
- package/dist/services/event-processor.js.map +1 -0
- package/dist/services/extract/docx.d.ts +1 -0
- package/dist/services/extract/docx.js +233 -0
- package/dist/services/extract/docx.js.map +1 -0
- package/dist/services/extract/index.d.ts +9 -0
- package/dist/services/extract/index.js +10 -0
- package/dist/services/extract/index.js.map +1 -0
- package/dist/services/extract/pdf.d.ts +13 -0
- package/dist/services/extract/pdf.js +69 -0
- package/dist/services/extract/pdf.js.map +1 -0
- package/dist/services/folder-creation.d.ts +33 -0
- package/dist/services/folder-creation.js +103 -0
- package/dist/services/folder-creation.js.map +1 -0
- package/dist/services/git.d.ts +58 -0
- package/dist/services/git.js +194 -55
- package/dist/services/git.js.map +1 -1
- package/dist/services/graph-maintenance.js +4 -4
- package/dist/services/graph-maintenance.js.map +1 -1
- package/dist/services/import-service.d.ts +51 -2
- package/dist/services/import-service.js +254 -107
- package/dist/services/import-service.js.map +1 -1
- package/dist/services/lint-service.js +10 -17
- package/dist/services/lint-service.js.map +1 -1
- package/dist/services/markdown.d.ts +12 -1
- package/dist/services/markdown.js +77 -9
- package/dist/services/markdown.js.map +1 -1
- package/dist/services/mention-detector.d.ts +23 -0
- package/dist/services/mention-detector.js +47 -0
- package/dist/services/mention-detector.js.map +1 -0
- package/dist/services/model-capabilities.d.ts +41 -0
- package/dist/services/model-capabilities.js +107 -0
- package/dist/services/model-capabilities.js.map +1 -0
- package/dist/services/notification-broadcast.d.ts +32 -0
- package/dist/services/notification-broadcast.js +38 -0
- package/dist/services/notification-broadcast.js.map +1 -0
- package/dist/services/notification-service.d.ts +41 -0
- package/dist/services/notification-service.js +100 -0
- package/dist/services/notification-service.js.map +1 -0
- package/dist/services/oauth-server-store.d.ts +147 -0
- package/dist/services/oauth-server-store.js +319 -0
- package/dist/services/oauth-server-store.js.map +1 -0
- package/dist/services/orphan-service.js +2 -2
- package/dist/services/orphan-service.js.map +1 -1
- package/dist/services/personal-folder.d.ts +40 -0
- package/dist/services/personal-folder.js +101 -0
- package/dist/services/personal-folder.js.map +1 -0
- package/dist/services/scratch-asset-promote.d.ts +57 -0
- package/dist/services/scratch-asset-promote.js +95 -0
- package/dist/services/scratch-asset-promote.js.map +1 -0
- package/dist/services/scratch-attachment-service.d.ts +196 -0
- package/dist/services/scratch-attachment-service.js +347 -0
- package/dist/services/scratch-attachment-service.js.map +1 -0
- package/dist/services/share-link-service.d.ts +57 -0
- package/dist/services/share-link-service.js +142 -0
- package/dist/services/share-link-service.js.map +1 -0
- package/dist/services/user-person-bridge.d.ts +136 -0
- package/dist/services/user-person-bridge.js +340 -0
- package/dist/services/user-person-bridge.js.map +1 -0
- package/dist/services/vector-service.d.ts +282 -5
- package/dist/services/vector-service.js +585 -29
- package/dist/services/vector-service.js.map +1 -1
- package/dist/services/workspace-access.d.ts +108 -0
- package/dist/services/workspace-access.js +149 -0
- package/dist/services/workspace-access.js.map +1 -0
- package/dist/services/workspace-assets-folder.d.ts +46 -0
- package/dist/services/workspace-assets-folder.js +75 -0
- package/dist/services/workspace-assets-folder.js.map +1 -0
- package/dist/utils/git.d.ts +17 -2
- package/dist/utils/git.js +23 -7
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/log.d.ts +42 -0
- package/dist/utils/log.js +137 -0
- package/dist/utils/log.js.map +1 -0
- package/dist/utils/preflight.js +2 -2
- package/dist/utils/preflight.js.map +1 -1
- package/dist/utils/version-checker.d.ts +12 -19
- package/dist/utils/version-checker.js +14 -104
- package/dist/utils/version-checker.js.map +1 -1
- package/dist/web/_app/immutable/assets/0.Bs_z3Z86.css +2 -0
- package/dist/web/_app/immutable/assets/12.DlIf1mKh.css +1 -0
- package/dist/web/_app/immutable/assets/14.7d-Q37wc.css +1 -0
- package/dist/web/_app/immutable/assets/15.BwBFdc1D.css +1 -0
- package/dist/web/_app/immutable/assets/16.CmLrbzZp.css +1 -0
- package/dist/web/_app/immutable/assets/17.DcuK5ZVm.css +1 -0
- package/dist/web/_app/immutable/assets/2.DATnR31c.css +1 -0
- package/dist/web/_app/immutable/assets/3.Cz4H8n_O.css +1 -0
- package/dist/web/_app/immutable/assets/4.aDHteTDy.css +1 -0
- package/dist/web/_app/immutable/assets/6.CF1i5Bon.css +1 -0
- package/dist/web/_app/immutable/assets/ArtifactEntityView.C4ooklFD.css +1 -0
- package/dist/web/_app/immutable/assets/AssetLibraryPicker.BOFW0MvF.css +1 -0
- package/dist/web/_app/immutable/assets/BulkActionsBar.cmKvHzRK.css +1 -0
- package/dist/web/_app/immutable/assets/CalendarView.lBCvS3X5.css +1 -0
- package/dist/web/_app/immutable/assets/CodeMirrorEditor.D6hf2pNJ.css +1 -0
- package/dist/web/_app/immutable/assets/CopyField.DVGZtw4U.css +1 -0
- package/dist/web/_app/immutable/assets/FolderMembersPanel.kiYnDFHu.css +1 -0
- package/dist/web/_app/immutable/assets/FolderMutationDialogs.BMRF6Z3z.css +1 -0
- package/dist/web/_app/immutable/assets/FolderPicker.Cp46GHe2.css +1 -0
- package/dist/web/_app/immutable/assets/GalleryView.WBGxxUQc.css +1 -0
- package/dist/web/_app/immutable/assets/GraphView.BJtGL9py.css +1 -0
- package/dist/web/_app/immutable/assets/KanbanView.BOaI5KFL.css +1 -0
- package/dist/web/_app/immutable/assets/Link.vV0ne105.css +1 -0
- package/dist/web/_app/immutable/assets/Markdown.BZnsSOSf.css +1 -0
- package/dist/web/_app/immutable/assets/Rail.ilusFgD4.css +1 -0
- package/dist/web/_app/immutable/assets/SettingsDialog.D7-BO6S_.css +1 -0
- package/dist/web/_app/immutable/assets/Spinner.UBfl0pab.css +1 -0
- package/dist/web/_app/immutable/assets/StopFilledAlt.fNlDN65D.css +1 -0
- package/dist/web/_app/immutable/assets/TimelineView.xAZBJhHw.css +1 -0
- package/dist/web/_app/immutable/assets/WorkspaceView.C8ErUJAY.css +1 -0
- package/dist/web/_app/immutable/assets/button.BlLm4Dg1.css +1 -0
- package/dist/web/_app/immutable/assets/history-summary.LpmHXasl.css +1 -0
- package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-400-normal.CvHOgSBP.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-400-normal.DMJ8VG8y.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-500-normal.CB9ihrfo.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-500-normal.DSY6xOcd.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-italic.CZTNEAuW.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-italic.CsGl1sm0.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-normal.CDDApCn2.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-normal.CYLoc0-x.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-italic.BNK2_mGO.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-italic.DpEwFAQM.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-normal.6ng42L7E.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-normal.BgVn5rGT.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-600-normal.Cu4Hd6ag.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-600-normal.CuJfVYMP.woff2 +0 -0
- package/dist/web/_app/immutable/assets/inline-list.cqga56xT.css +1 -0
- package/dist/web/_app/immutable/assets/list-columns.BgnafZ4K.css +1 -0
- package/dist/web/_app/immutable/assets/realtime.DDCWxn3B.css +1 -0
- package/dist/web/_app/immutable/assets/select.B90KUqR4.css +1 -0
- package/dist/web/_app/immutable/assets/sheet.RF9RBmml.css +1 -0
- package/dist/web/_app/immutable/chunks/22qYtvr82.js +1 -0
- package/dist/web/_app/immutable/chunks/2f1VclTm.js +1 -0
- package/dist/web/_app/immutable/chunks/4xgUPoGj2.js +2 -0
- package/dist/web/_app/immutable/chunks/5CYQk4xR.js +1 -0
- package/dist/web/_app/immutable/chunks/6S9WOky52.js +1 -0
- package/dist/web/_app/immutable/chunks/9K8VREGP.js +1 -0
- package/dist/web/_app/immutable/chunks/B-n5q6ku.js +1 -0
- package/dist/web/_app/immutable/chunks/B0p2fSEV2.js +1 -0
- package/dist/web/_app/immutable/chunks/B2oi87jG.js +2 -0
- package/dist/web/_app/immutable/chunks/B5Rb52QU.js +1 -0
- package/dist/web/_app/immutable/chunks/B6Lt-qaJ.js +1 -0
- package/dist/web/_app/immutable/chunks/B6O-q2CN.js +1 -0
- package/dist/web/_app/immutable/chunks/B6qeIQZz.js +2 -0
- package/dist/web/_app/immutable/chunks/B9KfEsQw2.js +1 -0
- package/dist/web/_app/immutable/chunks/BB-RSeXQ.js +1 -0
- package/dist/web/_app/immutable/chunks/BCQQzpbj.js +1 -0
- package/dist/web/_app/immutable/chunks/BGdzd-Tc.js +1 -0
- package/dist/web/_app/immutable/chunks/BLs1h0Rh2.js +1 -0
- package/dist/web/_app/immutable/chunks/BPzV_xOS2.js +1 -0
- package/dist/web/_app/immutable/chunks/BT2C6q8n.js +1 -0
- package/dist/web/_app/immutable/chunks/BUesbLq2.js +1 -0
- package/dist/web/_app/immutable/chunks/BUrnwOw4.js +2 -0
- package/dist/web/_app/immutable/chunks/BV-iCKqa.js +1 -0
- package/dist/web/_app/immutable/chunks/BVCJtXyc.js +1 -0
- package/dist/web/_app/immutable/chunks/BY9a3GSt.js +6 -0
- package/dist/web/_app/immutable/chunks/BZi3uKF4.js +1 -0
- package/dist/web/_app/immutable/chunks/BbfHGU9n2.js +1 -0
- package/dist/web/_app/immutable/chunks/Bbqu2-5G2.js +1 -0
- package/dist/web/_app/immutable/chunks/BbsX1Slr.js +3 -0
- package/dist/web/_app/immutable/chunks/BcwjqHaZ2.js +1 -0
- package/dist/web/_app/immutable/chunks/BizeEpTx.js +2 -0
- package/dist/web/_app/immutable/chunks/BlgjVmFM2.js +14 -0
- package/dist/web/_app/immutable/chunks/BocSRgv12.js +1 -0
- package/dist/web/_app/immutable/chunks/BojBYqc3.js +1 -0
- package/dist/web/_app/immutable/chunks/BpCG9m962.js +1 -0
- package/dist/web/_app/immutable/chunks/BqvnBg_y.js +1 -0
- package/dist/web/_app/immutable/chunks/BsT0J1QD.js +1 -0
- package/dist/web/_app/immutable/chunks/BsW72fyR2.js +1 -0
- package/dist/web/_app/immutable/chunks/Bti_XKwx.js +2692 -0
- package/dist/web/_app/immutable/chunks/Bua97Had2.js +2 -0
- package/dist/web/_app/immutable/chunks/BuhW5Fr0.js +689 -0
- package/dist/web/_app/immutable/chunks/Bx74EEyn2.js +1 -0
- package/dist/web/_app/immutable/chunks/BxPN0T61.js +1 -0
- package/dist/web/_app/immutable/chunks/Bz9lrgGs.js +1 -0
- package/dist/web/_app/immutable/chunks/BzaxZ6j12.js +1 -0
- package/dist/web/_app/immutable/chunks/C1B4xDs0.js +1 -0
- package/dist/web/_app/immutable/chunks/C1trcC8M.js +7 -0
- package/dist/web/_app/immutable/chunks/C9tvkk7Y2.js +22 -0
- package/dist/web/_app/immutable/chunks/CBQjdDZf.js +1 -0
- package/dist/web/_app/immutable/chunks/CDeazMFW2.js +2 -0
- package/dist/web/_app/immutable/chunks/CEahbtnP.js +1 -0
- package/dist/web/_app/immutable/chunks/CGjVCd0e.js +8 -0
- package/dist/web/_app/immutable/chunks/CICK9maP.js +1 -0
- package/dist/web/_app/immutable/chunks/CIoNEHQ82.js +184 -0
- package/dist/web/_app/immutable/chunks/CP8_U450.js +1 -0
- package/dist/web/_app/immutable/chunks/CQ1eQ93t2.js +6 -0
- package/dist/web/_app/immutable/chunks/CUFQMEBn2.js +1 -0
- package/dist/web/_app/immutable/chunks/CYEYDNwO2.js +1 -0
- package/dist/web/_app/immutable/chunks/CYyIP7kP2.js +66 -0
- package/dist/web/_app/immutable/chunks/CZgFaimi2.js +83 -0
- package/dist/web/_app/immutable/chunks/CZqvCTCv.js +1 -0
- package/dist/web/_app/immutable/chunks/CcI9jYBV.js +1 -0
- package/dist/web/_app/immutable/chunks/CcWaWbrI2.js +1 -0
- package/dist/web/_app/immutable/chunks/CdtzRVZK.js +1 -0
- package/dist/web/_app/immutable/chunks/CfYG72Hc.js +1 -0
- package/dist/web/_app/immutable/chunks/Ch1kmm3J2.js +1 -0
- package/dist/web/_app/immutable/chunks/CmSSN61R2.js +1 -0
- package/dist/web/_app/immutable/chunks/Cqm5wAin.js +1 -0
- package/dist/web/_app/immutable/chunks/CuYPXpWU.js +1 -0
- package/dist/web/_app/immutable/chunks/CvSEodTA.js +1 -0
- package/dist/web/_app/immutable/chunks/CvqqrTrj.js +1 -0
- package/dist/web/_app/immutable/chunks/CwAmWt6h.js +1 -0
- package/dist/web/_app/immutable/chunks/CwP3Oa7F.js +1 -0
- package/dist/web/_app/immutable/chunks/CyTluew1.js +2 -0
- package/dist/web/_app/immutable/chunks/CzXdOgVl2.js +7 -0
- package/dist/web/_app/immutable/chunks/D0IAituJ.js +1 -0
- package/dist/web/_app/immutable/chunks/D0W7c6Sg.js +185 -0
- package/dist/web/_app/immutable/chunks/D0rBhnqB2.js +1 -0
- package/dist/web/_app/immutable/chunks/D1-GZSN1.js +1 -0
- package/dist/web/_app/immutable/chunks/D1yvtriI.js +1 -0
- package/dist/web/_app/immutable/chunks/D25NMRpl.js +1 -0
- package/dist/web/_app/immutable/chunks/D8wZ2xul.js +4 -0
- package/dist/web/_app/immutable/chunks/DA3HEX3X.js +1 -0
- package/dist/web/_app/immutable/chunks/DD9wj4ca2.js +1 -0
- package/dist/web/_app/immutable/chunks/DJ1YeiJL2.js +1 -0
- package/dist/web/_app/immutable/chunks/DKFZnpVC.js +5 -0
- package/dist/web/_app/immutable/chunks/DKJl40hl.js +1 -0
- package/dist/web/_app/immutable/chunks/DPUVu0T0.js +1 -0
- package/dist/web/_app/immutable/chunks/DQMWa2t7.js +1 -0
- package/dist/web/_app/immutable/chunks/DXsFrDer2.js +1 -0
- package/dist/web/_app/immutable/chunks/DYc5KW3F.js +1 -0
- package/dist/web/_app/immutable/chunks/DZcTwXBW.js +1 -0
- package/dist/web/_app/immutable/chunks/DcPrzUMJ.js +1 -0
- package/dist/web/_app/immutable/chunks/DdPO8kRu2.js +2 -0
- package/dist/web/_app/immutable/chunks/DeOXyJZw2.js +1 -0
- package/dist/web/_app/immutable/chunks/DfufUFR1.js +2 -0
- package/dist/web/_app/immutable/chunks/DiQg3ing.js +1 -0
- package/dist/web/_app/immutable/chunks/DjIqa1_y.js +2 -0
- package/dist/web/_app/immutable/chunks/DkJ1dkxV2.js +1 -0
- package/dist/web/_app/immutable/chunks/Dl63IUnH2.js +224 -0
- package/dist/web/_app/immutable/chunks/DmFdD6zT.js +1 -0
- package/dist/web/_app/immutable/chunks/Dn3R8S-U.js +1 -0
- package/dist/web/_app/immutable/chunks/DpNr1GQ2.js +1 -0
- package/dist/web/_app/immutable/chunks/DpVNDHXz.js +2 -0
- package/dist/web/_app/immutable/chunks/DuxziNnw.js +1 -0
- package/dist/web/_app/immutable/chunks/Dv3A3Wcj.js +1 -0
- package/dist/web/_app/immutable/chunks/FuiJnZG0.js +1 -0
- package/dist/web/_app/immutable/chunks/Izkulod7.js +1 -0
- package/dist/web/_app/immutable/chunks/KZY6ongq2.js +2 -0
- package/dist/web/_app/immutable/chunks/LYL8znYR2.js +23 -0
- package/dist/web/_app/immutable/chunks/PhN6tXuJ2.js +1 -0
- package/dist/web/_app/immutable/chunks/PyLyCpuL.js +1 -0
- package/dist/web/_app/immutable/chunks/RK4gCWN0.js +2 -0
- package/dist/web/_app/immutable/chunks/VsBngTf4.js +2 -0
- package/dist/web/_app/immutable/chunks/XcWejFm_.js +5 -0
- package/dist/web/_app/immutable/chunks/al_nidE9.js +1 -0
- package/dist/web/_app/immutable/chunks/e50-izZO2.js +1 -0
- package/dist/web/_app/immutable/chunks/hG-QCarB.js +1 -0
- package/dist/web/_app/immutable/chunks/hStvCHkX.js +1 -0
- package/dist/web/_app/immutable/chunks/iJVY9p0y.js +3 -0
- package/dist/web/_app/immutable/chunks/kNaey6uv.js +1 -0
- package/dist/web/_app/immutable/chunks/pIXNe0C1.js +1 -0
- package/dist/web/_app/immutable/chunks/pNSRq_1B.js +2 -0
- package/dist/web/_app/immutable/chunks/qwr5boV8.js +1 -0
- package/dist/web/_app/immutable/chunks/rjjyuGhb.js +1 -0
- package/dist/web/_app/immutable/chunks/sg-T-FoN.js +1 -0
- package/dist/web/_app/immutable/chunks/uBgoVlsx.js +3 -0
- package/dist/web/_app/immutable/chunks/ulshNz6x.js +2 -0
- package/dist/web/_app/immutable/chunks/xY0c-UfG.js +3 -0
- package/dist/web/_app/immutable/chunks/xeNvx4Bl2.js +1 -0
- package/dist/web/_app/immutable/chunks/xihTtKlq.js +1 -0
- package/dist/web/_app/immutable/entry/app.pNUW-q4U.js +2 -0
- package/dist/web/_app/immutable/entry/start.DxGtIGG_.js +1 -0
- package/dist/web/_app/immutable/nodes/0.D6ZCY_R6.js +2 -0
- package/dist/web/_app/immutable/nodes/1.BryH3TNr.js +1 -0
- package/dist/web/_app/immutable/nodes/10.CMZMvR17.js +1 -0
- package/dist/web/_app/immutable/nodes/11.QHx_JShx.js +1 -0
- package/dist/web/_app/immutable/nodes/12.CiRWbb4X.js +1 -0
- package/dist/web/_app/immutable/nodes/13.CYrHXCA8.js +1 -0
- package/dist/web/_app/immutable/nodes/14.K18eIE5Z.js +1 -0
- package/dist/web/_app/immutable/nodes/15.DxgnZ61h.js +1 -0
- package/dist/web/_app/immutable/nodes/16.Dn6iuWIo.js +1 -0
- package/dist/web/_app/immutable/nodes/17.viwS7vTR.js +77 -0
- package/dist/web/_app/immutable/nodes/2.BQkQ_uUM.js +122 -0
- package/dist/web/_app/immutable/nodes/3.B4pqXsqq.js +6 -0
- package/dist/web/_app/immutable/nodes/4.CKXEgIAP.js +11 -0
- package/dist/web/_app/immutable/nodes/5.A2TASl4Q.js +1 -0
- package/dist/web/_app/immutable/nodes/6.DaIRO3SI.js +6 -0
- package/dist/web/_app/immutable/nodes/7.ga2UvFm-.js +1 -0
- package/dist/web/_app/immutable/nodes/8.BTEWT81u.js +1 -0
- package/dist/web/_app/immutable/nodes/9.BVxo0E0c.js +1 -0
- package/dist/web/_app/version.json +1 -1
- package/dist/web/favicon-16.png +0 -0
- package/dist/web/favicon-180.png +0 -0
- package/dist/web/favicon-32.png +0 -0
- package/dist/web/favicon-48.png +0 -0
- package/dist/web/favicon-512.png +0 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/favicon.svg +8 -0
- package/dist/web/index.html +56 -21
- package/dist/web/studiograph-logomark.svg +29 -0
- package/package.json +35 -14
- package/dist/agent/skills/enrich-entities.md +0 -136
- package/dist/agent/skills/sync-configuration.md +0 -119
- package/dist/agent/skills/sync-setup.md +0 -82
- package/dist/agent/tools/message-tools.d.ts +0 -42
- package/dist/agent/tools/message-tools.js +0 -106
- package/dist/agent/tools/message-tools.js.map +0 -1
- package/dist/cli/commands/app.d.ts +0 -7
- package/dist/cli/commands/app.js +0 -268
- package/dist/cli/commands/app.js.map +0 -1
- package/dist/cli/commands/clear.d.ts +0 -5
- package/dist/cli/commands/clear.js +0 -36
- package/dist/cli/commands/clear.js.map +0 -1
- package/dist/cli/commands/collection.d.ts +0 -10
- package/dist/cli/commands/collection.js.map +0 -1
- package/dist/cli/commands/join.d.ts +0 -9
- package/dist/cli/commands/join.js +0 -255
- package/dist/cli/commands/join.js.map +0 -1
- package/dist/cli/commands/start.d.ts +0 -7
- package/dist/cli/commands/start.js +0 -584
- package/dist/cli/commands/start.js.map +0 -1
- package/dist/cli/commands/update.d.ts +0 -8
- package/dist/cli/commands/update.js +0 -155
- package/dist/cli/commands/update.js.map +0 -1
- package/dist/cli/commands/user.d.ts +0 -7
- package/dist/cli/commands/user.js +0 -175
- package/dist/cli/commands/user.js.map +0 -1
- package/dist/cli/scaffolding.d.ts +0 -12
- package/dist/cli/scaffolding.js +0 -397
- package/dist/cli/scaffolding.js.map +0 -1
- package/dist/integrations/registry.d.ts +0 -8
- package/dist/integrations/registry.js +0 -7
- package/dist/integrations/registry.js.map +0 -1
- package/dist/integrations/types.d.ts +0 -43
- package/dist/integrations/types.js +0 -5
- package/dist/integrations/types.js.map +0 -1
- package/dist/lib/lib/utils.d.ts +0 -2
- package/dist/lib/lib/utils.js +0 -6
- package/dist/lib/lib/utils.js.map +0 -1
- package/dist/server/chrome/chrome.css +0 -691
- package/dist/server/chrome/chrome.js +0 -374
- package/dist/server/commit-scheduler.d.ts +0 -39
- package/dist/server/commit-scheduler.js +0 -113
- package/dist/server/commit-scheduler.js.map +0 -1
- package/dist/server/plugin-loader.d.ts +0 -53
- package/dist/server/plugin-loader.js +0 -155
- package/dist/server/plugin-loader.js.map +0 -1
- package/dist/server/routes/meeting-ingest-api.d.ts +0 -14
- package/dist/server/routes/meeting-ingest-api.js.map +0 -1
- package/dist/server/routes/messages-api.d.ts +0 -15
- package/dist/server/routes/messages-api.js +0 -385
- package/dist/server/routes/messages-api.js.map +0 -1
- package/dist/services/batch-processor.d.ts +0 -49
- package/dist/services/batch-processor.js +0 -166
- package/dist/services/batch-processor.js.map +0 -1
- package/dist/services/briefing.d.ts +0 -26
- package/dist/services/briefing.js +0 -230
- package/dist/services/briefing.js.map +0 -1
- package/dist/services/heartbeat.d.ts +0 -28
- package/dist/services/heartbeat.js +0 -183
- package/dist/services/heartbeat.js.map +0 -1
- package/dist/services/image-import-service.d.ts +0 -24
- package/dist/services/image-import-service.js +0 -108
- package/dist/services/image-import-service.js.map +0 -1
- package/dist/services/insights.d.ts +0 -38
- package/dist/services/insights.js +0 -269
- package/dist/services/insights.js.map +0 -1
- package/dist/services/meeting-processor.js.map +0 -1
- package/dist/services/message-service.d.ts +0 -68
- package/dist/services/message-service.js +0 -220
- package/dist/services/message-service.js.map +0 -1
- package/dist/utils/workspace-config.d.ts +0 -8
- package/dist/utils/workspace-config.js +0 -22
- package/dist/utils/workspace-config.js.map +0 -1
- package/dist/web/_app/immutable/assets/0.uPSqtsC5.css +0 -1
- package/dist/web/_app/immutable/assets/11.2jzI3cZY.css +0 -1
- package/dist/web/_app/immutable/assets/12.CT4xL4K3.css +0 -1
- package/dist/web/_app/immutable/assets/13.BUrHkYry.css +0 -1
- package/dist/web/_app/immutable/assets/2.DpQWr6q6.css +0 -1
- package/dist/web/_app/immutable/assets/3.BxdqT7zi.css +0 -1
- package/dist/web/_app/immutable/assets/4.DdNqjd0T.css +0 -1
- package/dist/web/_app/immutable/assets/5.DFeGxLYf.css +0 -1
- package/dist/web/_app/immutable/assets/6.DXZr_rI_.css +0 -1
- package/dist/web/_app/immutable/assets/GraphView.D9ePpZez.css +0 -1
- package/dist/web/_app/immutable/assets/Toaster.rN8r_Hzv.css +0 -1
- package/dist/web/_app/immutable/assets/ViewToolbar.BWE03S64.css +0 -1
- package/dist/web/_app/immutable/assets/WorkspaceView.CgGDi_Zb.css +0 -1
- package/dist/web/_app/immutable/assets/wikilinks.CzMBkUMB.css +0 -1
- package/dist/web/_app/immutable/chunks/-jG4Obyh.js +0 -4
- package/dist/web/_app/immutable/chunks/2nrc8f_O.js +0 -1
- package/dist/web/_app/immutable/chunks/7S2LGqoP.js +0 -2
- package/dist/web/_app/immutable/chunks/8Q3ZJIYu.js +0 -1
- package/dist/web/_app/immutable/chunks/B8ydT5xX.js +0 -2
- package/dist/web/_app/immutable/chunks/BB_5th5W.js +0 -3383
- package/dist/web/_app/immutable/chunks/BEn6N5c2.js +0 -1
- package/dist/web/_app/immutable/chunks/BFNNbCAM.js +0 -1
- package/dist/web/_app/immutable/chunks/BFZZEUd5.js +0 -1
- package/dist/web/_app/immutable/chunks/BGk8kfOE.js +0 -2
- package/dist/web/_app/immutable/chunks/BIBCr278.js +0 -1
- package/dist/web/_app/immutable/chunks/BPCkkwqe.js +0 -1
- package/dist/web/_app/immutable/chunks/BhlvzGQx.js +0 -1
- package/dist/web/_app/immutable/chunks/BlMknQeF.js +0 -1
- package/dist/web/_app/immutable/chunks/BpjqrlCg.js +0 -1
- package/dist/web/_app/immutable/chunks/BrebtJNG.js +0 -18
- package/dist/web/_app/immutable/chunks/ByBZ7JIT.js +0 -1
- package/dist/web/_app/immutable/chunks/Bz0ZjVQE.js +0 -2
- package/dist/web/_app/immutable/chunks/BzJTZOK2.js +0 -1
- package/dist/web/_app/immutable/chunks/C0LVZhvn.js +0 -1
- package/dist/web/_app/immutable/chunks/C3uBrOS5.js +0 -1
- package/dist/web/_app/immutable/chunks/C8R1sDpW.js +0 -1
- package/dist/web/_app/immutable/chunks/CDRHrs0g.js +0 -1
- package/dist/web/_app/immutable/chunks/CN5_py1I.js +0 -1
- package/dist/web/_app/immutable/chunks/CPne482a.js +0 -1
- package/dist/web/_app/immutable/chunks/CSVbGg_b.js +0 -5
- package/dist/web/_app/immutable/chunks/CWyU-seV.js +0 -1
- package/dist/web/_app/immutable/chunks/CX_61fY8.js +0 -5
- package/dist/web/_app/immutable/chunks/CYrVHOHA.js +0 -1
- package/dist/web/_app/immutable/chunks/ChsiIm-E.js +0 -1
- package/dist/web/_app/immutable/chunks/CojKppbh.js +0 -1
- package/dist/web/_app/immutable/chunks/CqkleIqs.js +0 -1
- package/dist/web/_app/immutable/chunks/CtYLtD7K.js +0 -1
- package/dist/web/_app/immutable/chunks/D-HDy5qS.js +0 -1
- package/dist/web/_app/immutable/chunks/D5aIYSkd.js +0 -1
- package/dist/web/_app/immutable/chunks/DBBXARVf.js +0 -1
- package/dist/web/_app/immutable/chunks/DFYOFE3o.js +0 -1
- package/dist/web/_app/immutable/chunks/DKaafS50.js +0 -1
- package/dist/web/_app/immutable/chunks/DUMOo1Pn.js +0 -1
- package/dist/web/_app/immutable/chunks/DWX9f6AF.js +0 -1
- package/dist/web/_app/immutable/chunks/DY8-EONP.js +0 -1
- package/dist/web/_app/immutable/chunks/DkoHPElM.js +0 -1
- package/dist/web/_app/immutable/chunks/DnL9f0lc.js +0 -7
- package/dist/web/_app/immutable/chunks/DsnmJJEf.js +0 -1
- package/dist/web/_app/immutable/chunks/DujahU3W.js +0 -1
- package/dist/web/_app/immutable/chunks/FBn-ES5k.js +0 -1
- package/dist/web/_app/immutable/chunks/HQpwSOb3.js +0 -1
- package/dist/web/_app/immutable/chunks/KyjzlWRN.js +0 -2
- package/dist/web/_app/immutable/chunks/PPVm8Dsz.js +0 -1
- package/dist/web/_app/immutable/chunks/UwYfmrPx.js +0 -2
- package/dist/web/_app/immutable/chunks/W25ZVI8L.js +0 -2
- package/dist/web/_app/immutable/chunks/WSUKABI_.js +0 -1
- package/dist/web/_app/immutable/chunks/XjsuEnNE.js +0 -5
- package/dist/web/_app/immutable/chunks/ZD6ARAtb.js +0 -1
- package/dist/web/_app/immutable/chunks/pBTf4stg.js +0 -2
- package/dist/web/_app/immutable/chunks/rGIarcnp.js +0 -1
- package/dist/web/_app/immutable/chunks/vxEtkL8z.js +0 -1
- package/dist/web/_app/immutable/chunks/wDIGUaoU.js +0 -3
- package/dist/web/_app/immutable/entry/app.ZHiTM8WS.js +0 -2
- package/dist/web/_app/immutable/entry/start.C9-FfAVc.js +0 -1
- package/dist/web/_app/immutable/nodes/0.D92Orz8k.js +0 -2
- package/dist/web/_app/immutable/nodes/1.BMdyglM_.js +0 -1
- package/dist/web/_app/immutable/nodes/10.BilVDHSL.js +0 -1
- package/dist/web/_app/immutable/nodes/11.CS9E0Hic.js +0 -1
- package/dist/web/_app/immutable/nodes/12.D3BOxYzG.js +0 -1
- package/dist/web/_app/immutable/nodes/13.CClMKYtN.js +0 -1
- package/dist/web/_app/immutable/nodes/2.C8KKElPL.js +0 -267
- package/dist/web/_app/immutable/nodes/3.C5n5uiWU.js +0 -5
- package/dist/web/_app/immutable/nodes/4.DcGVvgiL.js +0 -11
- package/dist/web/_app/immutable/nodes/5.DxIjnFQO.js +0 -4
- package/dist/web/_app/immutable/nodes/6.lkh6kKLj.js +0 -1
- package/dist/web/_app/immutable/nodes/7.Bg7SbfhA.js +0 -1
- package/dist/web/_app/immutable/nodes/8.DcXWCNBE.js +0 -1
- package/dist/web/_app/immutable/nodes/9.Cefi8Jfm.js +0 -1
|
@@ -4,37 +4,270 @@
|
|
|
4
4
|
* POST /api/repos/:repo/entities/:type/:id/assets — multipart image upload
|
|
5
5
|
* GET /api/assets/* — serve local assets
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import { reqGitUser } from './graph-api-access.js';
|
|
8
|
+
import * as access from '../../services/access-control.js';
|
|
9
|
+
import { listRepoAssets, listWorkspaceAssets, clearLegacyBucketCache, isFreeStandingOwner } from '../../services/asset-listing.js';
|
|
10
|
+
import { createEntityFromAsset } from '../../services/asset-import-service.js';
|
|
11
|
+
import { assetDeleteDenial, assetDeleteDenialByAssetId } from '../../services/asset-delete-authz.js';
|
|
12
|
+
import { resolveWorkspaceAssetsRepo } from '../../services/workspace-assets-folder.js';
|
|
13
|
+
import { Workspace } from '../../core/workspace.js';
|
|
8
14
|
import { existsSync, createReadStream, statSync } from 'fs';
|
|
9
15
|
import { join, extname } from 'path';
|
|
16
|
+
import { artifactAssetKey } from '../../services/artifact-asset-token.js';
|
|
17
|
+
import { mintArtifactAssetTokenForText } from '../artifact-asset-mint.js';
|
|
18
|
+
/** Max bytes proxied same-origin through the artifact-asset token path.
|
|
19
|
+
* `downloadAssetById` buffers, so this guard (checked BEFORE download against
|
|
20
|
+
* the index size) bounds memory. Images well under this; refuse unknown size. */
|
|
21
|
+
const ARTIFACT_PROXY_MAX_BYTES = 25 * 1024 * 1024;
|
|
22
|
+
/** Extensions the artifact-asset token proxy will serve — images + fonts only.
|
|
23
|
+
* An artifact embeds these; a same-repo doc/dataset/binary referenced in text
|
|
24
|
+
* must NOT become a 12h bearer URL. */
|
|
25
|
+
const PROXYABLE_ASSET_EXTS = new Set([
|
|
26
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg', '.ico', '.avif',
|
|
27
|
+
'.woff', '.woff2', '.ttf', '.otf',
|
|
28
|
+
]);
|
|
10
29
|
const MIME_TYPES = {
|
|
11
30
|
'.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
|
|
12
31
|
'.gif': 'image/gif', '.webp': 'image/webp', '.svg': 'image/svg+xml',
|
|
13
32
|
'.ico': 'image/x-icon', '.avif': 'image/avif',
|
|
33
|
+
'.woff': 'font/woff', '.woff2': 'font/woff2',
|
|
34
|
+
'.ttf': 'font/ttf', '.otf': 'font/otf',
|
|
14
35
|
};
|
|
36
|
+
function isFontExtension(filename) {
|
|
37
|
+
return ['.woff', '.woff2', '.ttf', '.otf'].includes(extname(filename).toLowerCase());
|
|
38
|
+
}
|
|
39
|
+
function addFontResourceHeaders(reply, filename) {
|
|
40
|
+
if (!isFontExtension(filename))
|
|
41
|
+
return;
|
|
42
|
+
reply.header('Access-Control-Allow-Origin', '*');
|
|
43
|
+
reply.header('Cross-Origin-Resource-Policy', 'cross-origin');
|
|
44
|
+
}
|
|
15
45
|
function hasRepoAccess(req, repoName, workspaceManager, authService) {
|
|
16
46
|
const user = req.user;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
47
|
+
return access.hasRepoAccess(user, repoName, workspaceManager, authService);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Serve an immutable-key asset's bytes once authorization has already been
|
|
51
|
+
* decided by the caller. This is the shared serving entry behind BOTH the
|
|
52
|
+
* authed `/api/assets/:assetId/:filename` route (authz = hasRepoAccess) and the
|
|
53
|
+
* public `/api/share/:token/asset/...` route (authz = valid token + repo scope).
|
|
54
|
+
*
|
|
55
|
+
* It does serving ONLY — R2 302-redirect vs local stream, path-segment
|
|
56
|
+
* hardening, and cache headers. The index lookup, filename match, and the
|
|
57
|
+
* access decision stay in each route so neither surface can drift into serving
|
|
58
|
+
* a file the other wouldn't authorize.
|
|
59
|
+
*
|
|
60
|
+
* Mechanically this is a THIN WRAPPER over the generic `serveBackendObject`
|
|
61
|
+
* primitive (the same primitive that backs conversation-scratch images), so
|
|
62
|
+
* both surfaces share one serving codepath. Two asset-specific concerns stay
|
|
63
|
+
* here as pre-steps rather than leaking into the generic primitive:
|
|
64
|
+
* 1. The R2 FONT branch — fonts are proxied through the app origin (not 302'd)
|
|
65
|
+
* with cross-origin CORS headers, and HEAD is answered from the indexed
|
|
66
|
+
* size WITHOUT downloading the bytes. This is unique to the catalog.
|
|
67
|
+
* 2. The catalog's stricter local segment hardening (rejects a `.`-prefixed
|
|
68
|
+
* filename, `/` or `\` in either segment), preserved verbatim.
|
|
69
|
+
*
|
|
70
|
+
* The owning `repo` must be resolved by the caller (from the asset index) so the
|
|
71
|
+
* correct graph/backend is selected. Always fail-closed with 404 — never leak
|
|
72
|
+
* existence.
|
|
73
|
+
*/
|
|
74
|
+
export async function serveIndexedAsset(workspaceManager, reply, opts) {
|
|
75
|
+
const { assetId, filename, repo, isHead, size } = opts;
|
|
76
|
+
let graph;
|
|
77
|
+
try {
|
|
78
|
+
graph = workspaceManager.getGraph(repo);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
82
|
+
}
|
|
83
|
+
const storage = graph.getAssetStorage();
|
|
84
|
+
// Artifact-asset token path: uniform same-origin serve for BOTH backends,
|
|
85
|
+
// with private/no-cache + a strong (content-immutable) ETag so revocation is
|
|
86
|
+
// enforced on every revalidation while a 304 avoids re-download. Restricted
|
|
87
|
+
// to images + fonts (no Range/video this pass).
|
|
88
|
+
if (opts.proxyR2) {
|
|
89
|
+
const ext = extname(filename).toLowerCase();
|
|
90
|
+
// Token proxy is restricted to images + fonts (the only types an artifact
|
|
91
|
+
// legitimately embeds). Refuse anything else so a same-repo doc/dataset/etc.
|
|
92
|
+
// referenced in artifact text can't be turned into a bearer URL.
|
|
93
|
+
if (!PROXYABLE_ASSET_EXTS.has(ext)) {
|
|
94
|
+
reply.log?.warn?.({ assetId, filename, ext }, 'artifact-asset proxy refused (non-image/font)');
|
|
95
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
96
|
+
}
|
|
97
|
+
const etag = `"${assetId}/${filename}"`;
|
|
98
|
+
reply.header('ETag', etag);
|
|
99
|
+
reply.header('Cache-Control', 'private, no-cache');
|
|
100
|
+
reply.header('X-Content-Type-Options', 'nosniff');
|
|
101
|
+
// Neutralize active content (legacy/migrated SVGs) served same-origin.
|
|
102
|
+
reply.header('Content-Security-Policy', "default-src 'none'; style-src 'unsafe-inline'; sandbox");
|
|
103
|
+
reply.header('Content-Type', MIME_TYPES[ext] || 'application/octet-stream');
|
|
104
|
+
addFontResourceHeaders(reply, filename);
|
|
105
|
+
if (opts.ifNoneMatch === etag)
|
|
106
|
+
return reply.status(304).send();
|
|
107
|
+
try {
|
|
108
|
+
if (graph.getAssetStorage() === 'r2') {
|
|
109
|
+
// The index `size` is advisory and often null (migrate-assets/sweep
|
|
110
|
+
// never set it), so DON'T fail-closed on unknown size — the normal
|
|
111
|
+
// serve path doesn't either. Pre-reject only a KNOWN-oversize asset;
|
|
112
|
+
// for HEAD with a known size, answer without downloading.
|
|
113
|
+
if (typeof size === 'number' && size > ARTIFACT_PROXY_MAX_BYTES) {
|
|
114
|
+
reply.log?.warn?.({ assetId, filename, size }, 'artifact-asset proxy refused (oversized)');
|
|
115
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
116
|
+
}
|
|
117
|
+
if (isHead) {
|
|
118
|
+
if (typeof size === 'number' && size > 0)
|
|
119
|
+
reply.header('Content-Length', size);
|
|
120
|
+
return reply.send();
|
|
121
|
+
}
|
|
122
|
+
const bytes = await graph.downloadAssetById(assetId, filename);
|
|
123
|
+
// Post-download cap bounds what we serve when size was unknown.
|
|
124
|
+
if (bytes.length > ARTIFACT_PROXY_MAX_BYTES) {
|
|
125
|
+
reply.log?.warn?.({ assetId, filename, len: bytes.length }, 'artifact-asset proxy refused (oversized after download)');
|
|
126
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
127
|
+
}
|
|
128
|
+
reply.header('Content-Length', bytes.length);
|
|
129
|
+
return reply.send(bytes);
|
|
130
|
+
}
|
|
131
|
+
// Local backend: stream from disk (same path-hardening as below).
|
|
132
|
+
if (filename.includes('..') || filename.includes('/') || filename.includes('\\') || filename.startsWith('.') ||
|
|
133
|
+
assetId.includes('..') || assetId.includes('/') || assetId.includes('\\')) {
|
|
134
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
135
|
+
}
|
|
136
|
+
const base = workspaceManager.workspacePath ?? process.cwd();
|
|
137
|
+
const fp = join(base, '.studiograph', 'assets', assetId, filename);
|
|
138
|
+
if (!existsSync(fp))
|
|
139
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
140
|
+
reply.header('Content-Length', statSync(fp).size);
|
|
141
|
+
if (isHead)
|
|
142
|
+
return reply.send();
|
|
143
|
+
return reply.send(createReadStream(fp));
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
reply.log?.warn?.({ err, assetId, filename }, 'artifact-asset proxy serve failed');
|
|
147
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// (1) R2 font pre-step — catalog-only CORS proxy + HEAD-from-size optimization.
|
|
151
|
+
if (storage === 'r2' && isFontExtension(filename)) {
|
|
152
|
+
try {
|
|
153
|
+
const ext = extname(filename).toLowerCase();
|
|
154
|
+
if (isHead && typeof size === 'number') {
|
|
155
|
+
reply.header('Content-Type', MIME_TYPES[ext] || 'application/octet-stream');
|
|
156
|
+
reply.header('Content-Length', size);
|
|
157
|
+
reply.header('Cache-Control', 'private, max-age=14400');
|
|
158
|
+
reply.header('X-Content-Type-Options', 'nosniff');
|
|
159
|
+
addFontResourceHeaders(reply, filename);
|
|
160
|
+
return reply.send();
|
|
161
|
+
}
|
|
162
|
+
const bytes = await graph.downloadAssetById(assetId, filename);
|
|
163
|
+
reply.header('Content-Type', MIME_TYPES[ext] || 'application/octet-stream');
|
|
164
|
+
reply.header('Content-Length', bytes.length);
|
|
165
|
+
reply.header('Cache-Control', 'private, max-age=14400');
|
|
166
|
+
reply.header('X-Content-Type-Options', 'nosniff');
|
|
167
|
+
addFontResourceHeaders(reply, filename);
|
|
168
|
+
if (isHead)
|
|
169
|
+
return reply.send();
|
|
170
|
+
return reply.send(bytes);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
reply.log?.warn?.({ err, assetId, filename }, 'asset r2 signed-url generation failed');
|
|
174
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// (2) Local-backend catalog segment hardening (stricter than the generic
|
|
178
|
+
// primitive: also rejects a `.`-prefixed filename). Applied before delegation
|
|
179
|
+
// so observable behavior is identical to the pre-unification serve path.
|
|
180
|
+
if (storage !== 'r2') {
|
|
181
|
+
if (filename.includes('..') || filename.includes('/') || filename.includes('\\') || filename.startsWith('.') ||
|
|
182
|
+
assetId.includes('..') || assetId.includes('/') || assetId.includes('\\')) {
|
|
183
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// Delegate to the shared primitive. The catalog key is `{assetId}/{filename}`,
|
|
187
|
+
// per-backend cache differs (R2 private/4h vs local immutable/1y), fonts on the
|
|
188
|
+
// local path keep their CORS headers, and the R2 signer stays the catalog one.
|
|
189
|
+
return serveBackendObject(workspaceManager, reply, {
|
|
190
|
+
repo,
|
|
191
|
+
key: `${assetId}/${filename}`,
|
|
192
|
+
filename,
|
|
193
|
+
isHead,
|
|
194
|
+
cacheControl: { r2: 'private, max-age=14400', local: 'public, max-age=31536000, immutable' },
|
|
195
|
+
fontHeaders: true,
|
|
196
|
+
notFoundError: 'Asset not found',
|
|
197
|
+
signR2Url: (method) => graph.getAssetSignedUrlByAssetId(assetId, filename, method),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Generic backend-object serving primitive (composer-vision-attach / E2a). Serves
|
|
202
|
+
* bytes for a RAW backend key once the caller has decided authorization: R2 → 302
|
|
203
|
+
* to a method-bound signed URL; local → stream from `.studiograph/assets/{key}`
|
|
204
|
+
* with a sandbox CSP + nosniff. The R2 signer is INJECTED so the caller controls
|
|
205
|
+
* which keyspace is signed (catalog asset vs conversation-scratch image), and the
|
|
206
|
+
* cache header may be a single value or a per-backend pair. Always fail-closed 404.
|
|
207
|
+
*
|
|
208
|
+
* This is the single serving codepath behind BOTH `serveIndexedAsset` (the
|
|
209
|
+
* catalog, which layers an R2 font pre-step + stricter segment hardening on top)
|
|
210
|
+
* and the conversation-scratch image route.
|
|
211
|
+
*/
|
|
212
|
+
export async function serveBackendObject(workspaceManager, reply, opts) {
|
|
213
|
+
const { repo, key, filename, contentType, isHead, cacheControl, fontHeaders, signR2Url } = opts;
|
|
214
|
+
const notFound = opts.notFoundError ?? 'Not found';
|
|
215
|
+
const r2Cache = typeof cacheControl === 'string' ? cacheControl : cacheControl.r2;
|
|
216
|
+
const localCache = typeof cacheControl === 'string' ? cacheControl : cacheControl.local;
|
|
217
|
+
let graph;
|
|
218
|
+
try {
|
|
219
|
+
graph = workspaceManager.getGraph(repo);
|
|
25
220
|
}
|
|
26
|
-
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
221
|
+
catch {
|
|
222
|
+
return reply.status(404).send({ error: notFound });
|
|
223
|
+
}
|
|
224
|
+
if (graph.getAssetStorage() === 'r2') {
|
|
225
|
+
try {
|
|
226
|
+
const signed = await signR2Url(isHead ? 'HEAD' : 'GET');
|
|
227
|
+
reply.header('Cache-Control', r2Cache);
|
|
228
|
+
return reply.redirect(signed, 302);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
reply.log?.warn?.({ err, key }, 'backend object r2 signed-url generation failed');
|
|
232
|
+
return reply.status(404).send({ error: notFound });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Local backend: stream from .studiograph/assets/{key}. Defense-in-depth
|
|
236
|
+
// segment validation (the key is stored server-side, but never trust it).
|
|
237
|
+
for (const seg of key.split('/')) {
|
|
238
|
+
if (seg === '' || seg === '.' || seg === '..' || seg.includes('\\')) {
|
|
239
|
+
return reply.status(404).send({ error: notFound });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const localBase = workspaceManager.workspacePath ?? process.cwd();
|
|
243
|
+
const filePath = join(localBase, '.studiograph', 'assets', key);
|
|
244
|
+
if (!existsSync(filePath)) {
|
|
245
|
+
return reply.status(404).send({ error: notFound });
|
|
246
|
+
}
|
|
247
|
+
const ext = extname(filename).toLowerCase();
|
|
248
|
+
reply.header('Content-Type', contentType || MIME_TYPES[ext] || 'application/octet-stream');
|
|
249
|
+
reply.header('Content-Length', statSync(filePath).size);
|
|
250
|
+
reply.header('Cache-Control', localCache);
|
|
251
|
+
if (fontHeaders)
|
|
252
|
+
addFontResourceHeaders(reply, filename);
|
|
253
|
+
// Neutralize active content served from the app origin. SVG uploads are
|
|
254
|
+
// blocked, but legacy/migrated/indexed SVGs would otherwise run script when
|
|
255
|
+
// opened directly as image/svg+xml — now reachable anonymously via the share
|
|
256
|
+
// route. A sandbox CSP blocks script + plugins on direct navigation (img
|
|
257
|
+
// embeds still render, since <img> never executes SVG script), and nosniff
|
|
258
|
+
// stops a mislabeled asset from being re-interpreted as HTML.
|
|
259
|
+
reply.header('Content-Security-Policy', "default-src 'none'; style-src 'unsafe-inline'; sandbox");
|
|
260
|
+
reply.header('X-Content-Type-Options', 'nosniff');
|
|
261
|
+
if (isHead)
|
|
262
|
+
return reply.send();
|
|
263
|
+
return reply.send(createReadStream(filePath));
|
|
31
264
|
}
|
|
32
265
|
export async function registerAssetApiRoutes(fastify, workspaceManager, authService) {
|
|
33
266
|
// POST /api/repos/:repo/entities/:type/:id/assets — upload an image
|
|
34
267
|
fastify.post('/api/repos/:repo/entities/:type/:id/assets', async (req, reply) => {
|
|
35
268
|
const { repo, type, id } = req.params;
|
|
36
269
|
if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
|
|
37
|
-
return reply.status(404).send({ error: `
|
|
270
|
+
return reply.status(404).send({ error: `Folder "${repo}" not found` });
|
|
38
271
|
}
|
|
39
272
|
let graph;
|
|
40
273
|
try {
|
|
@@ -49,15 +282,21 @@ export async function registerAssetApiRoutes(fastify, workspaceManager, authServ
|
|
|
49
282
|
if (!file.mimetype.startsWith('image/')) {
|
|
50
283
|
return reply.status(400).send({ error: 'Only image files are supported' });
|
|
51
284
|
}
|
|
285
|
+
// Block SVG: stored XSS — SVG can contain <script> and the GET route
|
|
286
|
+
// serves it inline as image/svg+xml, executing in the app origin.
|
|
287
|
+
if (file.mimetype === 'image/svg+xml' || file.filename?.toLowerCase().endsWith('.svg')) {
|
|
288
|
+
return reply.status(415).send({ error: 'SVG uploads are not supported (stored XSS risk).' });
|
|
289
|
+
}
|
|
52
290
|
const buffer = await file.toBuffer();
|
|
53
291
|
const filename = `${Date.now()}-${file.filename.replace(/[^a-zA-Z0-9._-]/g, '_')}`;
|
|
292
|
+
const uploader = req.user;
|
|
54
293
|
try {
|
|
55
|
-
const asset = await graph.uploadAsset(type, id, filename, buffer
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return reply.send({ url, filename: asset.filename, size: asset.size });
|
|
294
|
+
const asset = await graph.uploadAsset(type, id, filename, buffer, undefined, {
|
|
295
|
+
uploaderUserId: uploader?.id != null ? String(uploader.id) : null,
|
|
296
|
+
});
|
|
297
|
+
// uploadAsset returns the canonical public URL (immutable-key scheme
|
|
298
|
+
// when the index is wired, legacy otherwise) — don't re-derive it.
|
|
299
|
+
return reply.send({ url: asset.publicUrl, filename: asset.filename, size: asset.size });
|
|
61
300
|
}
|
|
62
301
|
catch (err) {
|
|
63
302
|
if (err.message?.includes('not configured')) {
|
|
@@ -66,66 +305,398 @@ export async function registerAssetApiRoutes(fastify, workspaceManager, authServ
|
|
|
66
305
|
return reply.status(400).send({ error: err.message });
|
|
67
306
|
}
|
|
68
307
|
});
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
308
|
+
// POST /api/repos/:repo/entities/artifact/:id/asset-token — mint a short-lived
|
|
309
|
+
// (12h), ARTIFACT-SCOPED capability token so a sandboxed iframe (opaque origin,
|
|
310
|
+
// no cookie) can load the exact images this artifact embeds. Authz: a normal
|
|
311
|
+
// session with repo access. The allowlist is the set of indexed assets the
|
|
312
|
+
// bundle references that actually live in THIS repo — cross-repo / unknown /
|
|
313
|
+
// filename-mismatched refs are dropped (fail-closed).
|
|
314
|
+
fastify.post('/api/repos/:repo/entities/artifact/:id/asset-token', async (req, reply) => {
|
|
315
|
+
const { repo, id } = req.params;
|
|
316
|
+
const user = req.user;
|
|
317
|
+
if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
|
|
318
|
+
return reply.status(404).send({ error: `Folder "${repo}" not found` });
|
|
76
319
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
320
|
+
let graph;
|
|
321
|
+
try {
|
|
322
|
+
graph = workspaceManager.getGraph(repo);
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
return reply.status(404).send({ error: err.message });
|
|
326
|
+
}
|
|
327
|
+
// Allowlist source = the EXACT version being rendered. For a history
|
|
328
|
+
// preview the client passes the version's commit; we derive the allowlist
|
|
329
|
+
// from the artifact body AT THAT COMMIT (server-trusted, not client refs),
|
|
330
|
+
// so a preview token authorizes the previewed version's assets — not the
|
|
331
|
+
// current ones. Otherwise use the live body.
|
|
332
|
+
const commit = typeof req.query.commit === 'string' && /^[0-9a-f]{7,40}$/i.test(req.query.commit)
|
|
333
|
+
? req.query.commit : null;
|
|
334
|
+
let text;
|
|
335
|
+
if (commit) {
|
|
336
|
+
const atCommit = graph.getEntityAtCommit('artifact', id, commit);
|
|
337
|
+
if (!atCommit)
|
|
338
|
+
return reply.status(404).send({ error: 'Artifact version not found' });
|
|
339
|
+
text = atCommit.content;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
let entity;
|
|
343
|
+
try {
|
|
344
|
+
entity = graph.get('artifact', id);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
return reply.status(404).send({ error: 'Artifact not found' });
|
|
348
|
+
}
|
|
349
|
+
if (!entity)
|
|
350
|
+
return reply.status(404).send({ error: 'Artifact not found' });
|
|
351
|
+
text = entity.document?.content ?? '';
|
|
352
|
+
}
|
|
353
|
+
return reply.send(mintArtifactAssetTokenForText(workspaceManager, authService, repo, id, text, user ? String(user.id) : '0'));
|
|
354
|
+
});
|
|
355
|
+
// GET / HEAD /api/assets/:assetId/:filename — IMMUTABLE-KEY serve route.
|
|
356
|
+
//
|
|
357
|
+
// New scheme: assets are keyed `{assetId}/{filename}` (assetId = UUIDv4) and
|
|
358
|
+
// ownership/authz lives in the AssetIndexService, not in the URL. Two path
|
|
359
|
+
// segments after /api/assets/ vs three for the legacy route below — Fastify
|
|
360
|
+
// disambiguates by arity, so both coexist during the migration cutover.
|
|
361
|
+
//
|
|
362
|
+
// Authz: index.get(assetId) → repo → hasRepoAccess. Fail-closed on a miss.
|
|
363
|
+
// The filename in the URL MUST match the indexed filename, otherwise a valid
|
|
364
|
+
// assetId would authorize arbitrary keys under that UUID directory.
|
|
365
|
+
fastify.route({
|
|
366
|
+
method: ['GET', 'HEAD'],
|
|
367
|
+
url: '/api/assets/:assetId/:filename',
|
|
368
|
+
handler: async (req, reply) => {
|
|
369
|
+
const { assetId, filename } = req.params;
|
|
370
|
+
const isHead = req.method === 'HEAD';
|
|
371
|
+
const ifNoneMatch = req.headers['if-none-match'];
|
|
372
|
+
const row = workspaceManager.getAssetIndex().get(assetId);
|
|
373
|
+
// Fail-closed: unknown id, or a filename that doesn't match the indexed
|
|
374
|
+
// one, both 404 (never leak existence; never authorize a sibling key).
|
|
375
|
+
if (!row || row.filename !== filename) {
|
|
376
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
377
|
+
}
|
|
378
|
+
const repoConfig = workspaceManager.getRepoConfig(row.repo);
|
|
379
|
+
const isPublicWorkspaceFont = isFontExtension(filename) &&
|
|
380
|
+
repoConfig?.system === true &&
|
|
381
|
+
repoConfig?.workspace_wide === true;
|
|
382
|
+
const user = req.user;
|
|
383
|
+
if (!isPublicWorkspaceFont && !user && authService.hasUsers()) {
|
|
384
|
+
// No session cookie/Bearer (e.g. a sandboxed artifact iframe). Try the
|
|
385
|
+
// artifact-asset capability token before failing. The preHandler only
|
|
386
|
+
// verified the signature + 2-seg shape and stashed the payload as a
|
|
387
|
+
// CANDIDATE — the authoritative checks happen HERE against the resolved
|
|
388
|
+
// row: repo match + exact-asset allowlist + the token's user STILL has
|
|
389
|
+
// repo access (revocation enforced per-serve). Serve same-origin.
|
|
390
|
+
const tok = req.artifactAssetToken;
|
|
391
|
+
if (tok) {
|
|
392
|
+
const tokenUser = authService.findUserById(Number(tok.userId));
|
|
393
|
+
if (tok.repo === row.repo &&
|
|
394
|
+
tok.assets.includes(artifactAssetKey(assetId, filename)) &&
|
|
395
|
+
tokenUser &&
|
|
396
|
+
access.hasRepoAccess(tokenUser, row.repo, workspaceManager, authService)) {
|
|
397
|
+
return serveIndexedAsset(workspaceManager, reply, {
|
|
398
|
+
assetId, filename, repo: row.repo, isHead, size: row.size, proxyR2: true, ifNoneMatch,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
403
|
+
}
|
|
404
|
+
if (!isPublicWorkspaceFont && !hasRepoAccess(req, row.repo, workspaceManager, authService)) {
|
|
405
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
406
|
+
}
|
|
407
|
+
// Authz decided above (folder access); shared helper does the serving.
|
|
408
|
+
return serveIndexedAsset(workspaceManager, reply, { assetId, filename, repo: row.repo, isHead, size: row.size });
|
|
409
|
+
},
|
|
84
410
|
});
|
|
85
|
-
// GET /api/
|
|
411
|
+
// GET / HEAD /api/assets/:type/:id/:filename — LEGACY serve route (kept for
|
|
412
|
+
// the migration cutover window; removed once legacy-hit logs drain to 0).
|
|
413
|
+
//
|
|
414
|
+
// Per-repo authz: the asset storage layout is workspace-global
|
|
415
|
+
// (`.studiograph/assets/{type}/{id}/{filename}` for local;
|
|
416
|
+
// `{type}/{id}/{filename}` keys for R2) — the path doesn't carry repo
|
|
417
|
+
// information. To enforce that a user with access to folder A can't
|
|
418
|
+
// read assets owned by folder B, we resolve the owning repo from the
|
|
419
|
+
// entity ID via workspaceManager.findEntityById (the same primitive
|
|
420
|
+
// the agent's create_entity uses for cross-repo dedup checks), then
|
|
421
|
+
// run hasRepoAccess against that repo. Both miss and forbid return 404
|
|
422
|
+
// — never leak entity IDs as an existence oracle.
|
|
423
|
+
//
|
|
424
|
+
// Stage 2 (this commit): the route is now the single chokepoint for
|
|
425
|
+
// both backends. Local files stream directly. R2-backed assets get a
|
|
426
|
+
// 302 redirect to a short-lived pre-signed URL, so the underlying
|
|
427
|
+
// bucket can be flipped to private without breaking the workflow.
|
|
428
|
+
// Both GET and HEAD are registered — link unfurlers, OpenGraph
|
|
429
|
+
// crawlers, and CDN healthchecks issue HEAD against this URL and
|
|
430
|
+
// expect the same 302 (signed for HEAD) or local headers.
|
|
431
|
+
fastify.route({
|
|
432
|
+
method: ['GET', 'HEAD'],
|
|
433
|
+
url: '/api/assets/:type/:id/:filename',
|
|
434
|
+
handler: async (req, reply) => {
|
|
435
|
+
const { type, id, filename } = req.params;
|
|
436
|
+
const isHead = req.method === 'HEAD';
|
|
437
|
+
// Legacy-scheme hit. Monitored during cutover: once this drains to 0
|
|
438
|
+
// (all bodies migrated to /api/assets/{assetId}/...), this route is
|
|
439
|
+
// removed. Tagged so it's greppable in logs / metrics.
|
|
440
|
+
req.log?.info?.({ type, id, filename }, 'asset legacy-scheme route hit');
|
|
441
|
+
// Legacy keys carry NO repo identity, and entity IDs are unique within
|
|
442
|
+
// (repo, type) but NOT across the workspace — a case-study in `portfolio`
|
|
443
|
+
// and a reference in `analogous-experiences` can share an id. The old
|
|
444
|
+
// first-match `findEntityByTypeAndId` would authorize against an arbitrary
|
|
445
|
+
// owner, so on a slug collision a request could be authorized via an
|
|
446
|
+
// accessible repo yet serve a *different* repo's blob (codex P1). FAIL
|
|
447
|
+
// CLOSED: resolve every repo that owns (type,id); serve only when exactly
|
|
448
|
+
// one repo owns it AND that repo is accessible. (Immutable assets use the
|
|
449
|
+
// /api/assets/{assetId}/... route and are unaffected.)
|
|
450
|
+
const owners = [];
|
|
451
|
+
for (const cfg of workspaceManager.getAllRepoConfigs()) {
|
|
452
|
+
let g;
|
|
453
|
+
try {
|
|
454
|
+
g = workspaceManager.getGraph(cfg.name);
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
if (g.getEntityFilePath(type, id))
|
|
460
|
+
owners.push(cfg.name);
|
|
461
|
+
}
|
|
462
|
+
if (owners.length !== 1) {
|
|
463
|
+
return reply.status(404).send({ error: 'Asset not found' }); // ambiguous / none → fail closed
|
|
464
|
+
}
|
|
465
|
+
const repoName = owners[0];
|
|
466
|
+
if (!hasRepoAccess(req, repoName, workspaceManager, authService)) {
|
|
467
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
468
|
+
}
|
|
469
|
+
let graph;
|
|
470
|
+
try {
|
|
471
|
+
graph = workspaceManager.getGraph(repoName);
|
|
472
|
+
}
|
|
473
|
+
catch {
|
|
474
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
475
|
+
}
|
|
476
|
+
// R2 backend: 302 to a short-lived pre-signed URL. Cache the 302
|
|
477
|
+
// for the lifetime of the signed URL (4 hours) — this is the
|
|
478
|
+
// load-bearing perf optimization. Signed URLs are unique per
|
|
479
|
+
// request (each carries its own X-Amz-Signature), so without
|
|
480
|
+
// caching the redirect, every <img> tag fires a fresh proxy
|
|
481
|
+
// roundtrip AND a fresh R2 download (the signed URL is different
|
|
482
|
+
// each time, so the browser's image cache miss-matches and
|
|
483
|
+
// re-downloads bytes it already has). Caching the 302 means
|
|
484
|
+
// subsequent requests in the same 4-hour window resolve to the
|
|
485
|
+
// SAME signed URL → cached image bytes → zero network. Single
|
|
486
|
+
// line, multi-second page-load improvement.
|
|
487
|
+
//
|
|
488
|
+
// Trade-off: a signed URL cached in the browser keeps working
|
|
489
|
+
// for up to 4 hours after the user loses folder access. Same
|
|
490
|
+
// threat model the 4-hour signed-URL TTL already accepted —
|
|
491
|
+
// fresh URLs lived 4 hours, now cached URLs do too. `private`
|
|
492
|
+
// keeps shared caches (CDNs, corporate proxies) from holding
|
|
493
|
+
// the 302; only the originating browser does.
|
|
494
|
+
//
|
|
495
|
+
// Pre-signed URLs are method-bound, so HEAD requests need a
|
|
496
|
+
// HEAD-signed URL.
|
|
497
|
+
if (graph.getAssetStorage() === 'r2') {
|
|
498
|
+
try {
|
|
499
|
+
const signed = await graph.getAssetSignedUrl(type, id, filename, undefined, undefined, isHead ? 'HEAD' : 'GET');
|
|
500
|
+
reply.header('Cache-Control', 'private, max-age=14400');
|
|
501
|
+
return reply.redirect(signed, 302);
|
|
502
|
+
}
|
|
503
|
+
catch (err) {
|
|
504
|
+
// Surface SDK / network failures so credential rotation issues,
|
|
505
|
+
// R2 outages, etc. aren't silently presented to users as
|
|
506
|
+
// 'Asset not found'. The user-facing response stays 404 so
|
|
507
|
+
// entity IDs aren't leaked as an existence oracle.
|
|
508
|
+
req.log?.warn?.({ err, type, id, filename }, 'asset r2 signed-url generation failed');
|
|
509
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
// Local backend: stream the file directly. Defense-in-depth — the
|
|
513
|
+
// R2 branch already validates filename inside AssetService, so
|
|
514
|
+
// hold the local branch to the same standard. Fastify's segment
|
|
515
|
+
// matcher blocks `/` in path params, but a literal `..` or hidden
|
|
516
|
+
// dotfile would otherwise be passed through to `join` here.
|
|
517
|
+
if (filename.includes('..') || filename.includes('/') || filename.includes('\\') || filename.startsWith('.') ||
|
|
518
|
+
id.includes('..') || id.includes('/') || id.includes('\\') ||
|
|
519
|
+
type.includes('..') || type.includes('/') || type.includes('\\')) {
|
|
520
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
521
|
+
}
|
|
522
|
+
const workspacePath = workspaceManager.workspacePath ?? process.cwd();
|
|
523
|
+
const filePath = join(workspacePath, '.studiograph', 'assets', type, id, filename);
|
|
524
|
+
if (!existsSync(filePath)) {
|
|
525
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
526
|
+
}
|
|
527
|
+
const ext = extname(filename).toLowerCase();
|
|
528
|
+
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
529
|
+
const stats = statSync(filePath);
|
|
530
|
+
reply.header('Content-Type', contentType);
|
|
531
|
+
reply.header('Content-Length', stats.size);
|
|
532
|
+
reply.header('Cache-Control', 'public, max-age=31536000, immutable');
|
|
533
|
+
// HEAD: send headers without a body. Fastify auto-strips body on
|
|
534
|
+
// HEAD, but being explicit keeps the file handle from opening.
|
|
535
|
+
if (isHead)
|
|
536
|
+
return reply.send();
|
|
537
|
+
return reply.send(createReadStream(filePath));
|
|
538
|
+
},
|
|
539
|
+
});
|
|
540
|
+
// GET /api/repos/:repo/assets — list all assets in a folder. The listing
|
|
541
|
+
// logic (backend walk + immutable-index merge + name resolution) lives in
|
|
542
|
+
// listRepoAssets() so the MCP `list_assets` tool returns an identical shape.
|
|
86
543
|
fastify.get('/api/repos/:repo/assets', async (req, reply) => {
|
|
87
544
|
const { repo } = req.params;
|
|
88
545
|
if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
|
|
89
|
-
return reply.status(404).send({ error: `
|
|
546
|
+
return reply.status(404).send({ error: `Folder "${repo}" not found` });
|
|
90
547
|
}
|
|
91
|
-
|
|
548
|
+
// Verify the repo exists before delegating; listRepoAssets swallows
|
|
549
|
+
// missing-graph errors as [], which is right for the bulk endpoint but
|
|
550
|
+
// wrong for the single-repo one (we want a 404).
|
|
92
551
|
try {
|
|
93
|
-
|
|
552
|
+
workspaceManager.getGraph(repo);
|
|
94
553
|
}
|
|
95
554
|
catch (err) {
|
|
96
555
|
return reply.status(404).send({ error: err.message });
|
|
97
556
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
557
|
+
return { assets: await listRepoAssets(repo, workspaceManager) };
|
|
558
|
+
});
|
|
559
|
+
// GET /api/workspace/assets — bulk asset lists for ALL accessible repos in
|
|
560
|
+
// one round-trip. Replaces 15+ parallel /api/repos/:repo/assets calls so
|
|
561
|
+
// GalleryView's thumbnail effect doesn't saturate the browser HTTP/1.1
|
|
562
|
+
// 6-slot queue and block other navigation fetches.
|
|
563
|
+
// Response shape: `{ [repoName]: RepoAsset[] }`.
|
|
564
|
+
fastify.get('/api/workspace/assets', async (req, reply) => {
|
|
565
|
+
const user = req.user;
|
|
566
|
+
// Use the shared access-control helper so personal-folder restrictions
|
|
567
|
+
// and workspace-owner-on-shared-folder semantics apply consistently.
|
|
568
|
+
const accessible = access.getAccessibleRepos(user, workspaceManager, authService).map(r => r.name);
|
|
569
|
+
// One bucket walk for the whole workspace (see listWorkspaceAssets) rather
|
|
570
|
+
// than a per-repo listAll fan-out. Seed every accessible repo so folders
|
|
571
|
+
// with no assets still appear as empty arrays (shape-stable for callers).
|
|
572
|
+
const result = {};
|
|
573
|
+
for (const repo of accessible)
|
|
574
|
+
result[repo] = [];
|
|
575
|
+
const flat = await listWorkspaceAssets(accessible, workspaceManager);
|
|
576
|
+
for (const { repo, ...asset } of flat)
|
|
577
|
+
(result[repo] ??= []).push(asset);
|
|
578
|
+
return reply.send(result);
|
|
579
|
+
});
|
|
580
|
+
// GET /api/workspace/assets/manager — the dedicated asset-manager view.
|
|
581
|
+
//
|
|
582
|
+
// Distinct from /api/workspace/assets (kept shape-stable for GalleryView's
|
|
583
|
+
// thumbnail effect). This composes the SAME primitives (getAccessibleRepos +
|
|
584
|
+
// listRepoAssets + assetDeleteDenial) into the shape the /assets UI needs:
|
|
585
|
+
// - `folders`: every accessible folder's metadata (display name +
|
|
586
|
+
// workspace_wide + system flags) so the client can render the folder
|
|
587
|
+
// filter, group headers, the Workspace/folder-scoped visibility badge,
|
|
588
|
+
// and pin the shared store first — the system store is excluded from
|
|
589
|
+
// /api/repos so the client can't get this elsewhere.
|
|
590
|
+
// - `assets`: a FLAT list annotated with the owning `repo` and a per-asset
|
|
591
|
+
// `canDelete` decision (uploader/owner gate, via the same authz primitive
|
|
592
|
+
// the DELETE route enforces) so the UI can disable delete with a reason
|
|
593
|
+
// instead of letting the user hit a 403.
|
|
594
|
+
fastify.get('/api/workspace/assets/manager', async (req, reply) => {
|
|
595
|
+
const user = req.user;
|
|
596
|
+
workspaceManager.reloadConfig();
|
|
597
|
+
const accessible = access.getAccessibleRepos(user, workspaceManager, authService);
|
|
598
|
+
const index = workspaceManager.getAssetIndex?.();
|
|
599
|
+
const folders = accessible.map(r => ({
|
|
600
|
+
repo: r.name,
|
|
601
|
+
displayName: r.display_name ?? null,
|
|
602
|
+
workspaceWide: r.workspace_wide === true,
|
|
603
|
+
system: r.system === true,
|
|
604
|
+
}));
|
|
605
|
+
const configByRepo = new Map(accessible.map(r => [r.name, r]));
|
|
606
|
+
// listWorkspaceAssets does ONE bucket walk for the whole workspace (instead
|
|
607
|
+
// of one per folder) and already dedupes legacy/immutable pairs per repo.
|
|
608
|
+
// Here we just layer on the per-asset delete decision so the UI can disable
|
|
609
|
+
// delete with a reason instead of letting the user hit a 403.
|
|
610
|
+
const flat = await listWorkspaceAssets(accessible.map(r => r.name), workspaceManager);
|
|
611
|
+
const assets = flat.map((a) => {
|
|
612
|
+
const r = configByRepo.get(a.repo);
|
|
613
|
+
// Index-tracked assets (incl. free-standing folder/workspace-owned ones)
|
|
614
|
+
// carry an assetId — gate per-asset on that id. Legacy un-indexed blobs
|
|
615
|
+
// fall back to the entity-coordinate gate, keyed to the exact filename.
|
|
616
|
+
const denial = a.assetId
|
|
617
|
+
? assetDeleteDenialByAssetId(index, r, user, a.assetId)
|
|
618
|
+
: assetDeleteDenial(index, r, user, a.repo, a.entityType, a.entityId, a.filename);
|
|
619
|
+
return { ...a, canDelete: denial === null, denialReason: denial };
|
|
620
|
+
});
|
|
621
|
+
return reply.send({ folders, assets });
|
|
622
|
+
});
|
|
623
|
+
// GET /api/workspace/assets/:assetId/references — entries whose markdown
|
|
624
|
+
// body (or frontmatter) references this asset's URL. Scans accessible
|
|
625
|
+
// repos via graph.search, which already includes body-content scanning,
|
|
626
|
+
// so no schema change is needed. Filters self-reference: a STANDALONE
|
|
627
|
+
// asset's own wrapper entity has the URL in its `asset_url` frontmatter
|
|
628
|
+
// and would self-match — drop it. Embedded assets keep their parent
|
|
629
|
+
// entity in results (the parent IS the only "use", and clicking it is
|
|
630
|
+
// the user's path to managing the image's context).
|
|
631
|
+
fastify.get('/api/workspace/assets/:assetId/references', async (req, reply) => {
|
|
632
|
+
const { assetId } = req.params;
|
|
633
|
+
const user = req.user;
|
|
634
|
+
const index = workspaceManager.getAssetIndex?.();
|
|
635
|
+
const owner = index?.get(assetId) ?? null;
|
|
636
|
+
const accessible = access.getAccessibleRepos(user, workspaceManager, authService);
|
|
637
|
+
// The immutable-asset URL pattern. assetId is a UUID so this string
|
|
638
|
+
// is unique enough that a substring match across markdown bodies is
|
|
639
|
+
// a reliable "this entry references the asset" signal.
|
|
640
|
+
const pattern = `/api/assets/${assetId}/`;
|
|
641
|
+
const references = [];
|
|
642
|
+
for (const repo of accessible) {
|
|
643
|
+
let graph;
|
|
101
644
|
try {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
645
|
+
graph = workspaceManager.getGraph(repo.name);
|
|
646
|
+
}
|
|
647
|
+
catch {
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
const matches = graph.search({ query: pattern });
|
|
651
|
+
for (const e of matches) {
|
|
652
|
+
// Skip self: a standalone asset's own entity holds the URL in
|
|
653
|
+
// its `asset_url` frontmatter so it always matches its own
|
|
654
|
+
// search. Embedded assets fall through (their owner is the
|
|
655
|
+
// parent entry, which IS a legitimate reference to show).
|
|
656
|
+
if (owner &&
|
|
657
|
+
owner.ownerType === 'asset' &&
|
|
658
|
+
owner.repo === repo.name &&
|
|
659
|
+
e.id === owner.ownerId)
|
|
660
|
+
continue;
|
|
661
|
+
const data = e.data;
|
|
662
|
+
const title = typeof data.name === 'string' && data.name.trim() !== ''
|
|
663
|
+
? data.name
|
|
664
|
+
: e.id;
|
|
665
|
+
references.push({
|
|
666
|
+
repo: repo.name,
|
|
667
|
+
entityType: e.entityType,
|
|
668
|
+
entityId: e.id,
|
|
669
|
+
title,
|
|
670
|
+
});
|
|
118
671
|
}
|
|
119
|
-
catch { /* no assets or service not configured */ }
|
|
120
672
|
}
|
|
121
|
-
return {
|
|
673
|
+
return reply.send({ references });
|
|
674
|
+
});
|
|
675
|
+
// GET /api/workspace/assets/store — resolve (lazy-create) the reserved
|
|
676
|
+
// Workspace Assets store and return its repo name. The manager's upload
|
|
677
|
+
// affordance calls this, then POSTs the file to /api/repos/<repo>/assets.
|
|
678
|
+
// This is how an existing workspace backfills the store on first use (D3 /
|
|
679
|
+
// resolveWorkspaceAssetsRepo) — never a boot sweep. Authed users only; the
|
|
680
|
+
// store is workspace_wide so any member can upload once it exists.
|
|
681
|
+
fastify.get('/api/workspace/assets/store', async (_req, reply) => {
|
|
682
|
+
// Match the upload route's auth posture: open-mode (no user) is allowed.
|
|
683
|
+
const workspace = new Workspace(workspaceManager.getWorkspacePath());
|
|
684
|
+
const repo = await resolveWorkspaceAssetsRepo(workspace, workspaceManager);
|
|
685
|
+
return reply.send({ repo });
|
|
122
686
|
});
|
|
123
687
|
// DELETE /api/repos/:repo/entities/:type/:id/assets/:filename — delete an asset
|
|
124
688
|
fastify.delete('/api/repos/:repo/entities/:type/:id/assets/:filename', async (req, reply) => {
|
|
125
689
|
const { repo, type, id, filename } = req.params;
|
|
126
690
|
if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
|
|
127
|
-
return reply.status(404).send({ error: `
|
|
691
|
+
return reply.status(404).send({ error: `Folder "${repo}" not found` });
|
|
128
692
|
}
|
|
693
|
+
// Workspace Assets store delete gate (codex P1 / D2): workspace_wide grants
|
|
694
|
+
// every member write, so this filename delete needs the same uploader/owner
|
|
695
|
+
// gate the entity-delete route applies — otherwise a direct API call deletes
|
|
696
|
+
// a shared/legacy file asset the manager already marks canDelete:false.
|
|
697
|
+
const denial = assetDeleteDenial(workspaceManager.getAssetIndex?.(), workspaceManager.getRepoConfig(repo), req.user, repo, type, id, filename);
|
|
698
|
+
if (denial)
|
|
699
|
+
return reply.status(403).send({ error: denial });
|
|
129
700
|
let graph;
|
|
130
701
|
try {
|
|
131
702
|
graph = workspaceManager.getGraph(repo);
|
|
@@ -135,11 +706,140 @@ export async function registerAssetApiRoutes(fastify, workspaceManager, authServ
|
|
|
135
706
|
}
|
|
136
707
|
try {
|
|
137
708
|
await graph.deleteAsset(type, id, filename);
|
|
709
|
+
// A legacy blob just disappeared — drop the cached bucket walk so the
|
|
710
|
+
// manager stops listing it before the next process restart (P2).
|
|
711
|
+
clearLegacyBucketCache();
|
|
712
|
+
return { success: true };
|
|
713
|
+
}
|
|
714
|
+
catch (err) {
|
|
715
|
+
return reply.status(404).send({ error: err.message });
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
// DELETE /api/repos/:repo/assets/:assetId/:filename — delete a free-standing
|
|
719
|
+
// (folder/workspace-owned) asset by its immutable id. Folder-owned assets
|
|
720
|
+
// have no owning entity, so the entity-coordinate route above can't address
|
|
721
|
+
// them (it rejects 'folder' as an EntityType). This removes the blob + index
|
|
722
|
+
// row in one step.
|
|
723
|
+
fastify.delete('/api/repos/:repo/assets/:assetId/:filename', async (req, reply) => {
|
|
724
|
+
const { repo, assetId, filename } = req.params;
|
|
725
|
+
if (!hasRepoAccess(req, repo, workspaceManager, authService)) {
|
|
726
|
+
return reply.status(404).send({ error: `Folder "${repo}" not found` });
|
|
727
|
+
}
|
|
728
|
+
// Confirm the asset exists and actually belongs to this folder — don't let
|
|
729
|
+
// access to one folder delete another folder's asset by id.
|
|
730
|
+
const index = workspaceManager.getAssetIndex?.();
|
|
731
|
+
const row = index?.get(assetId);
|
|
732
|
+
if (!row || row.repo !== repo) {
|
|
733
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
734
|
+
}
|
|
735
|
+
// Same uploader/owner gate as the filename route, keyed on the asset row.
|
|
736
|
+
const denial = assetDeleteDenialByAssetId(index, workspaceManager.getRepoConfig(repo), req.user, assetId);
|
|
737
|
+
if (denial)
|
|
738
|
+
return reply.status(403).send({ error: denial });
|
|
739
|
+
// Filename must match the stored row — the key is `{assetId}/{filename}`.
|
|
740
|
+
if (row.filename !== filename) {
|
|
741
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
742
|
+
}
|
|
743
|
+
let graph;
|
|
744
|
+
try {
|
|
745
|
+
graph = workspaceManager.getGraph(repo);
|
|
746
|
+
}
|
|
747
|
+
catch (err) {
|
|
748
|
+
return reply.status(404).send({ error: err.message });
|
|
749
|
+
}
|
|
750
|
+
try {
|
|
751
|
+
await graph.deleteAssetById(assetId, filename);
|
|
138
752
|
return { success: true };
|
|
139
753
|
}
|
|
140
754
|
catch (err) {
|
|
141
755
|
return reply.status(404).send({ error: err.message });
|
|
142
756
|
}
|
|
143
757
|
});
|
|
758
|
+
// POST /api/repos/:repo/assets/:assetId/create-entry — the second import
|
|
759
|
+
// "door": create a typed entity (auto-derived type, re-extracted body) that
|
|
760
|
+
// embeds an EXISTING free-standing asset, in the asset's own folder. Makes
|
|
761
|
+
// deletion reversible — a surviving asset can be re-embedded into a new entry.
|
|
762
|
+
fastify.post('/api/repos/:repo/assets/:assetId/create-entry', async (req, reply) => {
|
|
763
|
+
const { repo, assetId } = req.params;
|
|
764
|
+
const user = req.user;
|
|
765
|
+
// Creating an entry is a write — require folder write access.
|
|
766
|
+
if (!access.canWriteRepo(user, repo, workspaceManager, authService)) {
|
|
767
|
+
return reply.status(403).send({ error: 'Editor access required to create an entry here.' });
|
|
768
|
+
}
|
|
769
|
+
const index = workspaceManager.getAssetIndex?.();
|
|
770
|
+
const row = index?.get(assetId);
|
|
771
|
+
if (!row || row.repo !== repo) {
|
|
772
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
773
|
+
}
|
|
774
|
+
// Only free-standing assets get the second import door (parity with the
|
|
775
|
+
// delete-by-id + move routes). Re-embedding an entity-OWNED asset would
|
|
776
|
+
// leave the new entry's embed deletable by the owning entity's
|
|
777
|
+
// cleanupOwnedAssets — so refuse it here, not just in the UI.
|
|
778
|
+
if (!isFreeStandingOwner(row.ownerType)) {
|
|
779
|
+
return reply.status(400).send({ error: 'Only free-standing assets can be made into a new entry.' });
|
|
780
|
+
}
|
|
781
|
+
let graph;
|
|
782
|
+
try {
|
|
783
|
+
graph = workspaceManager.getGraph(repo);
|
|
784
|
+
}
|
|
785
|
+
catch (err) {
|
|
786
|
+
return reply.status(404).send({ error: err.message });
|
|
787
|
+
}
|
|
788
|
+
try {
|
|
789
|
+
const result = await createEntityFromAsset({
|
|
790
|
+
graph,
|
|
791
|
+
assetId,
|
|
792
|
+
name: req.body?.name,
|
|
793
|
+
user: reqGitUser(req),
|
|
794
|
+
});
|
|
795
|
+
return { entities: result.entities, assetId: result.assetId, asset_url: result.asset_url };
|
|
796
|
+
}
|
|
797
|
+
catch (err) {
|
|
798
|
+
return reply.status(400).send({ error: err.message ?? 'Could not create entry from asset' });
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
// POST /api/repos/:repo/assets/:assetId/move — relocate a free-standing asset
|
|
802
|
+
// to another folder. Index-only (the blob key is repo-independent). Requires
|
|
803
|
+
// write access to BOTH the source and target folders; only free-standing
|
|
804
|
+
// (folder/workspace-owned) assets can move — entity-embedded assets move with
|
|
805
|
+
// their owning entry.
|
|
806
|
+
fastify.post('/api/repos/:repo/assets/:assetId/move', async (req, reply) => {
|
|
807
|
+
const { repo, assetId } = req.params;
|
|
808
|
+
const targetRepo = req.body?.targetRepo;
|
|
809
|
+
const user = req.user;
|
|
810
|
+
if (!targetRepo)
|
|
811
|
+
return reply.status(400).send({ error: 'targetRepo is required' });
|
|
812
|
+
if (!access.canWriteRepo(user, repo, workspaceManager, authService)) {
|
|
813
|
+
return reply.status(404).send({ error: `Folder "${repo}" not found` });
|
|
814
|
+
}
|
|
815
|
+
if (!access.canWriteRepo(user, targetRepo, workspaceManager, authService)) {
|
|
816
|
+
return reply.status(403).send({ error: `Editor access required to move into "${targetRepo}".` });
|
|
817
|
+
}
|
|
818
|
+
const index = workspaceManager.getAssetIndex?.();
|
|
819
|
+
const row = index?.get(assetId);
|
|
820
|
+
if (!index || !row || row.repo !== repo) {
|
|
821
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
822
|
+
}
|
|
823
|
+
if (!isFreeStandingOwner(row.ownerType)) {
|
|
824
|
+
return reply.status(400).send({ error: 'Only free-standing assets can be moved on their own.' });
|
|
825
|
+
}
|
|
826
|
+
// Moving OUT of a workspace_wide store reassigns the asset's repo, after
|
|
827
|
+
// which the tighter uploader/owner delete rule no longer applies — so the
|
|
828
|
+
// move itself must honor that rule (mirrors the entity-move guard in
|
|
829
|
+
// graph-api). A no-op for non-workspace_wide sources, so normal
|
|
830
|
+
// folder→folder moves are unaffected.
|
|
831
|
+
const denial = assetDeleteDenialByAssetId(index, workspaceManager.getRepoConfig(repo), user, assetId);
|
|
832
|
+
if (denial)
|
|
833
|
+
return reply.status(403).send({ error: denial });
|
|
834
|
+
if (targetRepo === repo)
|
|
835
|
+
return { success: true }; // no-op
|
|
836
|
+
// Honor the result — a concurrent delete/move could have removed the row
|
|
837
|
+
// between the get() above and here; reporting success then would make the
|
|
838
|
+
// asset appear moved when it wasn't.
|
|
839
|
+
if (!index.moveByAssetId(assetId, targetRepo)) {
|
|
840
|
+
return reply.status(404).send({ error: 'Asset not found' });
|
|
841
|
+
}
|
|
842
|
+
return { success: true };
|
|
843
|
+
});
|
|
144
844
|
}
|
|
145
845
|
//# sourceMappingURL=asset-api.js.map
|