studiograph 1.3.48-next.26 → 1.3.48-next.261
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 +27 -17
- package/dist/agent/agent-pool.d.ts +120 -4
- package/dist/agent/agent-pool.js +358 -18
- package/dist/agent/agent-pool.js.map +1 -1
- package/dist/agent/context-window.d.ts +113 -0
- package/dist/agent/context-window.js +459 -0
- package/dist/agent/context-window.js.map +1 -0
- 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 +110 -11
- package/dist/agent/orchestrator.js +547 -75
- package/dist/agent/orchestrator.js.map +1 -1
- package/dist/agent/prompts/entity-types-section.d.ts +18 -9
- package/dist/agent/prompts/entity-types-section.js +42 -18
- package/dist/agent/prompts/entity-types-section.js.map +1 -1
- package/dist/agent/prompts/system.md +107 -19
- package/dist/agent/skill-loader.js +3 -9
- package/dist/agent/skill-loader.js.map +1 -1
- package/dist/agent/skills/artifact-canvas/skill.md +257 -0
- package/dist/agent/skills/create-skill.md +39 -0
- package/dist/agent/skills/entity-schema.md +67 -150
- package/dist/agent/skills/interview/skill.md +44 -29
- package/dist/agent/skills/paged-documents/skill.md +147 -0
- package/dist/agent/skills/schema-rules.md +28 -28
- package/dist/agent/tools/artifact-tools.d.ts +237 -0
- package/dist/agent/tools/artifact-tools.js +407 -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 +50 -0
- package/dist/agent/tools/folder-tools.js +340 -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 +1 -1
- package/dist/agent/tools/fs-tools.js.map +1 -1
- package/dist/agent/tools/graph-tools.d.ts +45 -73
- package/dist/agent/tools/graph-tools.js +765 -239
- 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 +465 -0
- package/dist/agent/tools/history-tools.js.map +1 -0
- package/dist/agent/tools/load-skill.d.ts +4 -3
- package/dist/agent/tools/load-skill.js +1 -1
- package/dist/agent/tools/load-skill.js.map +1 -1
- package/dist/agent/tools/ops-tools.d.ts +2 -1
- package/dist/agent/tools/ops-tools.js +125 -25
- package/dist/agent/tools/ops-tools.js.map +1 -1
- package/dist/agent/tools/permission-tools.d.ts +69 -21
- package/dist/agent/tools/permission-tools.js +183 -18
- 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/admin/folder.js +187 -0
- 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 +48 -0
- package/dist/cli/commands/admin/index.js.map +1 -0
- package/dist/cli/commands/admin/migrate-asset-owners.d.ts +56 -0
- package/dist/cli/commands/admin/migrate-asset-owners.js +86 -0
- package/dist/cli/commands/admin/migrate-asset-owners.js.map +1 -0
- package/dist/cli/commands/admin/migrate-assets.d.ts +62 -0
- package/dist/cli/commands/admin/migrate-assets.js +237 -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 +23 -3
- package/dist/cli/commands/clone.js +140 -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 +7 -4
- 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 +242 -24
- package/dist/cli/commands/serve.js.map +1 -1
- package/dist/cli/commands/sync.d.ts +1 -1
- package/dist/cli/commands/sync.js +230 -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 -23
- 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/cli/theme.d.ts +1 -1
- package/dist/core/accent-palette.d.ts +7 -18
- package/dist/core/accent-palette.js +27 -38
- package/dist/core/accent-palette.js.map +1 -1
- package/dist/core/artifact-asset-resolution.d.ts +90 -0
- package/dist/core/artifact-asset-resolution.js +138 -0
- package/dist/core/artifact-asset-resolution.js.map +1 -0
- package/dist/core/artifact-asset-urls.d.ts +111 -0
- package/dist/core/artifact-asset-urls.js +174 -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 +83 -0
- package/dist/core/artifact-export.js +1567 -0
- package/dist/core/artifact-export.js.map +1 -0
- package/dist/core/artifact-frame-document.d.ts +105 -0
- package/dist/core/artifact-frame-document.js +1427 -0
- package/dist/core/artifact-frame-document.js.map +1 -0
- package/dist/core/artifact-frame-layout.d.ts +79 -0
- package/dist/core/artifact-frame-layout.js +94 -0
- package/dist/core/artifact-frame-layout.js.map +1 -0
- package/dist/core/artifact-frame-model.d.ts +198 -0
- package/dist/core/artifact-frame-model.js +603 -0
- package/dist/core/artifact-frame-model.js.map +1 -0
- package/dist/core/artifact-frame-selectors.d.ts +42 -0
- package/dist/core/artifact-frame-selectors.js +65 -0
- package/dist/core/artifact-frame-selectors.js.map +1 -0
- package/dist/core/artifact-libraries.d.ts +23 -0
- package/dist/core/artifact-libraries.js +229 -0
- package/dist/core/artifact-libraries.js.map +1 -0
- package/dist/core/artifact-manifest.d.ts +64 -0
- package/dist/core/artifact-manifest.js +88 -0
- package/dist/core/artifact-manifest.js.map +1 -0
- package/dist/core/artifact-paged-css.d.ts +64 -0
- package/dist/core/artifact-paged-css.js +253 -0
- package/dist/core/artifact-paged-css.js.map +1 -0
- package/dist/core/artifact-patch.d.ts +58 -0
- package/dist/core/artifact-patch.js +309 -0
- package/dist/core/artifact-patch.js.map +1 -0
- package/dist/core/artifact-warnings.d.ts +51 -0
- package/dist/core/artifact-warnings.js +165 -0
- package/dist/core/artifact-warnings.js.map +1 -0
- package/dist/core/breakpoints.d.ts +27 -0
- package/dist/core/breakpoints.js +28 -0
- package/dist/core/breakpoints.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 +108 -0
- package/dist/core/embeds.js +226 -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 +66 -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/font-family.d.ts +62 -0
- package/dist/core/font-family.js +84 -0
- package/dist/core/font-family.js.map +1 -0
- package/dist/core/format-registry.d.ts +187 -0
- package/dist/core/format-registry.js +411 -0
- package/dist/core/format-registry.js.map +1 -0
- package/dist/core/graph.d.ts +770 -30
- package/dist/core/graph.js +2282 -386
- 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/markdown-strip.d.ts +22 -0
- package/dist/core/markdown-strip.js +51 -0
- package/dist/core/markdown-strip.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 +237 -0
- package/dist/core/secrets/registry.js +710 -0
- package/dist/core/secrets/registry.js.map +1 -0
- package/dist/core/seed-folder.d.ts +59 -0
- package/dist/core/seed-folder.js +76 -0
- package/dist/core/seed-folder.js.map +1 -0
- package/dist/core/types.d.ts +88 -480
- package/dist/core/types.js +53 -6
- package/dist/core/types.js.map +1 -1
- package/dist/core/user-config.d.ts +81 -14
- package/dist/core/user-config.js +147 -17
- package/dist/core/user-config.js.map +1 -1
- package/dist/core/validation.d.ts +828 -1472
- package/dist/core/validation.js +475 -163
- package/dist/core/validation.js.map +1 -1
- package/dist/core/view-scope.d.ts +88 -0
- package/dist/core/view-scope.js +71 -0
- package/dist/core/view-scope.js.map +1 -0
- package/dist/core/workspace-manager.d.ts +156 -11
- package/dist/core/workspace-manager.js +364 -92
- package/dist/core/workspace-manager.js.map +1 -1
- package/dist/core/workspace.d.ts +19 -5
- package/dist/core/workspace.js +72 -56
- 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 +31 -25
- 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 +73 -2
- package/dist/mcp/tools.js +2289 -115
- package/dist/mcp/tools.js.map +1 -1
- package/dist/server/__tests__/helpers/graph-api.d.ts +17 -0
- package/dist/server/__tests__/helpers/graph-api.js +17 -0
- package/dist/server/__tests__/helpers/graph-api.js.map +1 -0
- package/dist/server/__tests__/helpers/multipart.d.ts +9 -0
- package/dist/server/__tests__/helpers/multipart.js +14 -0
- package/dist/server/__tests__/helpers/multipart.js.map +1 -0
- package/dist/server/artifact-asset-mint.d.ts +59 -0
- package/dist/server/artifact-asset-mint.js +127 -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 +50 -1
- package/dist/server/index.js +811 -229
- package/dist/server/index.js.map +1 -1
- package/dist/server/middleware/sanitize.d.ts +46 -0
- package/dist/server/middleware/sanitize.js +195 -0
- package/dist/server/middleware/sanitize.js.map +1 -0
- package/dist/server/process-guards.d.ts +37 -0
- package/dist/server/process-guards.js +44 -0
- package/dist/server/process-guards.js.map +1 -0
- package/dist/server/routes/ai-usage-api.d.ts +24 -0
- package/dist/server/routes/ai-usage-api.js +68 -0
- package/dist/server/routes/ai-usage-api.js.map +1 -0
- package/dist/server/routes/artifact-export-api.d.ts +11 -0
- package/dist/server/routes/artifact-export-api.js +180 -0
- package/dist/server/routes/artifact-export-api.js.map +1 -0
- package/dist/server/routes/artifact-patch-api.d.ts +19 -0
- package/dist/server/routes/artifact-patch-api.js +114 -0
- package/dist/server/routes/artifact-patch-api.js.map +1 -0
- package/dist/server/routes/artifact-selection-api.d.ts +15 -0
- package/dist/server/routes/artifact-selection-api.js +90 -0
- package/dist/server/routes/artifact-selection-api.js.map +1 -0
- package/dist/server/routes/asset-api.d.ts +88 -2
- package/dist/server/routes/asset-api.js +858 -112
- 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 +113 -0
- package/dist/server/routes/audit-api.js.map +1 -0
- package/dist/server/routes/auth-api.js +536 -44
- package/dist/server/routes/auth-api.js.map +1 -1
- package/dist/server/routes/capture-api.js +81 -9
- 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 +462 -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 +858 -68
- 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 +183 -57
- package/dist/server/routes/config-api.js.map +1 -1
- package/dist/server/routes/connectors-api.js +178 -45
- 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/event-ingest-api.js +125 -0
- 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 +3 -3
- package/dist/server/routes/git-http.js +90 -30
- package/dist/server/routes/git-http.js.map +1 -1
- package/dist/server/routes/graph-api-access.d.ts +63 -0
- package/dist/server/routes/graph-api-access.js +115 -0
- package/dist/server/routes/graph-api-access.js.map +1 -0
- package/dist/server/routes/graph-api.d.ts +2 -3
- package/dist/server/routes/graph-api.js +1341 -423
- 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 +116 -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 +352 -66
- package/dist/server/routes/insights-api.js.map +1 -1
- package/dist/server/routes/mcp.d.ts +8 -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 +8 -3
- package/dist/server/routes/permissions-api.js +43 -12
- 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 +273 -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 +305 -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 +177 -0
- package/dist/server/routes/settings-api.js.map +1 -0
- package/dist/server/routes/share-api.d.ts +30 -6
- package/dist/server/routes/share-api.js +240 -31
- package/dist/server/routes/share-api.js.map +1 -1
- package/dist/server/routes/views-api.d.ts +11 -2
- package/dist/server/routes/views-api.js +123 -22
- 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 +150 -23
- package/dist/server/routes/workspace-api.js.map +1 -1
- package/dist/server/routes/ws.d.ts +2 -1
- package/dist/server/routes/ws.js +73 -20
- 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 +135 -56
- package/dist/server/ws-hub.js +0 -0
- package/dist/server/ws-hub.js.map +1 -1
- package/dist/server/yjs-manager.d.ts +275 -18
- package/dist/server/yjs-manager.js +819 -70
- 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 +121 -0
- package/dist/services/access-control.js +194 -0
- package/dist/services/access-control.js.map +1 -0
- package/dist/services/ai-usage-ledger.d.ts +75 -0
- package/dist/services/ai-usage-ledger.js +250 -0
- package/dist/services/ai-usage-ledger.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-inspect-metadata.d.ts +21 -0
- package/dist/services/artifact-inspect-metadata.js +66 -0
- package/dist/services/artifact-inspect-metadata.js.map +1 -0
- package/dist/services/artifact-library-sources.d.ts +33 -0
- package/dist/services/artifact-library-sources.js +91 -0
- package/dist/services/artifact-library-sources.js.map +1 -0
- package/dist/services/artifact-patch-runner.d.ts +52 -0
- package/dist/services/artifact-patch-runner.js +74 -0
- package/dist/services/artifact-patch-runner.js.map +1 -0
- package/dist/services/artifact-render-export.d.ts +230 -0
- package/dist/services/artifact-render-export.js +1003 -0
- package/dist/services/artifact-render-export.js.map +1 -0
- package/dist/services/artifact-selection-store.d.ts +68 -0
- package/dist/services/artifact-selection-store.js +90 -0
- package/dist/services/artifact-selection-store.js.map +1 -0
- package/dist/services/artifact-store.d.ts +118 -0
- package/dist/services/artifact-store.js +234 -0
- package/dist/services/artifact-store.js.map +1 -0
- package/dist/services/asset-caption-service.d.ts +103 -0
- package/dist/services/asset-caption-service.js +200 -0
- package/dist/services/asset-caption-service.js.map +1 -0
- package/dist/services/asset-delete-authz.d.ts +32 -0
- package/dist/services/asset-delete-authz.js +62 -0
- package/dist/services/asset-delete-authz.js.map +1 -0
- package/dist/services/asset-delete-impact.d.ts +63 -0
- package/dist/services/asset-delete-impact.js +96 -0
- package/dist/services/asset-delete-impact.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 +116 -0
- package/dist/services/asset-import-service.js +436 -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 +271 -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 +63 -0
- package/dist/services/asset-listing.js +124 -0
- package/dist/services/asset-listing.js.map +1 -0
- package/dist/services/asset-move-impact.d.ts +49 -0
- package/dist/services/asset-move-impact.js +62 -0
- package/dist/services/asset-move-impact.js.map +1 -0
- package/dist/services/asset-presign-service.d.ts +62 -0
- package/dist/services/asset-presign-service.js +126 -0
- package/dist/services/asset-presign-service.js.map +1 -0
- package/dist/services/asset-reference-index.d.ts +39 -0
- package/dist/services/asset-reference-index.js +33 -0
- package/dist/services/asset-reference-index.js.map +1 -0
- package/dist/services/asset-reference-scan.d.ts +57 -0
- package/dist/services/asset-reference-scan.js +150 -0
- package/dist/services/asset-reference-scan.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 +158 -0
- package/dist/services/assets/asset-index-service.js +293 -0
- package/dist/services/assets/asset-index-service.js.map +1 -0
- package/dist/services/assets/base.d.ts +119 -4
- package/dist/services/assets/base.js +148 -3
- package/dist/services/assets/base.js.map +1 -1
- package/dist/services/assets/index.d.ts +90 -0
- package/dist/services/assets/index.js +202 -0
- package/dist/services/assets/index.js.map +1 -1
- package/dist/services/assets/local.d.ts +9 -0
- package/dist/services/assets/local.js +30 -4
- 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 +80 -0
- package/dist/services/assets/r2.js +159 -4
- package/dist/services/assets/r2.js.map +1 -1
- package/dist/services/auth-service.d.ts +278 -51
- package/dist/services/auth-service.js +995 -272
- package/dist/services/auth-service.js.map +1 -1
- package/dist/services/chat-session-service.d.ts +189 -0
- package/dist/services/chat-session-service.js +544 -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 +321 -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 +313 -0
- package/dist/services/entity-mutations.js +531 -0
- package/dist/services/entity-mutations.js.map +1 -0
- package/dist/services/event-processor.d.ts +104 -0
- package/dist/services/event-processor.js +451 -0
- 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/fonts/font-metadata.d.ts +42 -0
- package/dist/services/fonts/font-metadata.js +126 -0
- package/dist/services/fonts/font-metadata.js.map +1 -0
- package/dist/services/fonts/font-skill-service.d.ts +66 -0
- package/dist/services/fonts/font-skill-service.js +137 -0
- package/dist/services/fonts/font-skill-service.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 +49 -0
- package/dist/services/personal-folder.js +122 -0
- package/dist/services/personal-folder.js.map +1 -0
- package/dist/services/playwright-bootstrap.d.ts +78 -0
- package/dist/services/playwright-bootstrap.js +219 -0
- package/dist/services/playwright-bootstrap.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 +201 -0
- package/dist/services/scratch-attachment-service.js +354 -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 +341 -0
- package/dist/services/user-person-bridge.js.map +1 -0
- package/dist/services/utility-model-resolver.d.ts +37 -0
- package/dist/services/utility-model-resolver.js +67 -0
- package/dist/services/utility-model-resolver.js.map +1 -0
- package/dist/services/vector-service.d.ts +282 -5
- package/dist/services/vector-service.js +594 -39
- package/dist/services/vector-service.js.map +1 -1
- package/dist/services/workspace-access.d.ts +115 -0
- package/dist/services/workspace-access.js +154 -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.B8rPC4xg.css +2 -0
- package/dist/web/_app/immutable/assets/12.DlIf1mKh.css +1 -0
- package/dist/web/_app/immutable/assets/13.WUaKJbVc.css +1 -0
- package/dist/web/_app/immutable/assets/15.CItnu3nw.css +1 -0
- package/dist/web/_app/immutable/assets/16.C3czeWWx.css +1 -0
- package/dist/web/_app/immutable/assets/17.CHiwoAMO.css +1 -0
- package/dist/web/_app/immutable/assets/18.fUGuXQ0K.css +1 -0
- package/dist/web/_app/immutable/assets/19.DcuK5ZVm.css +1 -0
- package/dist/web/_app/immutable/assets/2.bJqlVFXX.css +1 -0
- package/dist/web/_app/immutable/assets/3.DDfEAhbB.css +1 -0
- package/dist/web/_app/immutable/assets/4.Dtodt1Fe.css +1 -0
- package/dist/web/_app/immutable/assets/6.JPVpkzLZ.css +1 -0
- package/dist/web/_app/immutable/assets/AssetLibraryPicker.BWH260L8.css +1 -0
- package/dist/web/_app/immutable/assets/BulkActionsBar.CXSM7tO7.css +1 -0
- package/dist/web/_app/immutable/assets/CalendarView.Cch9MMQW.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.BvV1lBCg.css +1 -0
- package/dist/web/_app/immutable/assets/FolderMutationDialogs.De2NQ55E.css +1 -0
- package/dist/web/_app/immutable/assets/FolderPicker.Cq1tMnu_.css +1 -0
- package/dist/web/_app/immutable/assets/FolderTreeField.DTSzn15s.css +1 -0
- package/dist/web/_app/immutable/assets/FrameCanvas.Clh5Xdr7.css +1 -0
- package/dist/web/_app/immutable/assets/GalleryView.Cqu3-wtr.css +1 -0
- package/dist/web/_app/immutable/assets/GraphView.BJtGL9py.css +1 -0
- package/dist/web/_app/immutable/assets/GridCell.czY6eDb_.css +1 -0
- package/dist/web/_app/immutable/assets/KanbanView.C5X8TbQk.css +1 -0
- package/dist/web/_app/immutable/assets/Link.vV0ne105.css +1 -0
- package/dist/web/_app/immutable/assets/Markdown.CSYKSAm0.css +1 -0
- package/dist/web/_app/immutable/assets/Rail.Dyxy_E0O.css +1 -0
- package/dist/web/_app/immutable/assets/RepoCheckList.BLeJ79Eg.css +1 -0
- package/dist/web/_app/immutable/assets/SaveViewDialog.Bb9E81xd.css +1 -0
- package/dist/web/_app/immutable/assets/SearchInput.DTB6W35f.css +1 -0
- package/dist/web/_app/immutable/assets/SettingsDialog.CHzfQoo_.css +1 -0
- package/dist/web/_app/immutable/assets/Spinner.UBfl0pab.css +1 -0
- package/dist/web/_app/immutable/assets/StopFilledAlt.CjuwgVxu.css +1 -0
- package/dist/web/_app/immutable/assets/TimelineView.u4YqiztR.css +1 -0
- package/dist/web/_app/immutable/assets/Version.BQq1Fihu.css +1 -0
- package/dist/web/_app/immutable/assets/ViewBanner.DWswnFdi.css +1 -0
- package/dist/web/_app/immutable/assets/WorkspaceView.Cd6TS6oo.css +1 -0
- package/dist/web/_app/immutable/assets/button.DOg1smbV.css +1 -0
- package/dist/web/_app/immutable/assets/checkbox.CAcHq8G6.css +1 -0
- package/dist/web/_app/immutable/assets/download-with-progress.BZlFTn09.css +1 -0
- package/dist/web/_app/immutable/assets/filters.RgfXKVpA.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/list-columns.BgnafZ4K.css +1 -0
- package/dist/web/_app/immutable/chunks/-MYqXlc3.js +185 -0
- package/dist/web/_app/immutable/chunks/-Tbr-96c2.js +1 -0
- package/dist/web/_app/immutable/chunks/1QRy7-wo.js +1 -0
- package/dist/web/_app/immutable/chunks/1_BWKAKA2.js +23 -0
- package/dist/web/_app/immutable/chunks/2z2ekE6D2.js +2 -0
- package/dist/web/_app/immutable/chunks/6guf_P8c.js +1 -0
- package/dist/web/_app/immutable/chunks/7l950wkn.js +1 -0
- package/dist/web/_app/immutable/chunks/9Sn2PwSn2.js +1025 -0
- package/dist/web/_app/immutable/chunks/AFZSy-Vk.js +2 -0
- package/dist/web/_app/immutable/chunks/B06uE1qJ.js +1 -0
- package/dist/web/_app/immutable/chunks/B2cjR0rG.js +83 -0
- package/dist/web/_app/immutable/chunks/B3u4QMoU.js +1 -0
- package/dist/web/_app/immutable/chunks/B4PrlkmE.js +1 -0
- package/dist/web/_app/immutable/chunks/B5UH1FNe.js +3 -0
- package/dist/web/_app/immutable/chunks/B5sxjq1L2.js +1 -0
- package/dist/web/_app/immutable/chunks/B6Lt-qaJ.js +1 -0
- package/dist/web/_app/immutable/chunks/B6wo7Stk2.js +1 -0
- package/dist/web/_app/immutable/chunks/B9KfEsQw2.js +1 -0
- package/dist/web/_app/immutable/chunks/B9MY422N.js +1 -0
- package/dist/web/_app/immutable/chunks/B9VQbssY.js +3 -0
- package/dist/web/_app/immutable/chunks/BBrP9GiZ.js +1 -0
- package/dist/web/_app/immutable/chunks/BEGTD2EL2.js +1 -0
- package/dist/web/_app/immutable/chunks/BEyPhM3C.js +1 -0
- package/dist/web/_app/immutable/chunks/BIeOkmiV.js +1 -0
- package/dist/web/_app/immutable/chunks/BIwe25Lk.js +1 -0
- package/dist/web/_app/immutable/chunks/BO6-nRV9.js +1 -0
- package/dist/web/_app/immutable/chunks/BPILs-6H.js +1 -0
- package/dist/web/_app/immutable/chunks/BPRQzMGs.js +1 -0
- package/dist/web/_app/immutable/chunks/BQE7bWgF.js +1 -0
- package/dist/web/_app/immutable/chunks/BU5TCj7m.js +12 -0
- package/dist/web/_app/immutable/chunks/BZDKT2Qw2.js +2 -0
- package/dist/web/_app/immutable/chunks/B_y9A6Kg.js +4 -0
- package/dist/web/_app/immutable/chunks/BdR7t2GN.js +3 -0
- package/dist/web/_app/immutable/chunks/BnYohOts2.js +1 -0
- package/dist/web/_app/immutable/chunks/Bpmx7Umc.js +1 -0
- package/dist/web/_app/immutable/chunks/BqButnP4.js +1 -0
- package/dist/web/_app/immutable/chunks/BtDSRU36.js +1 -0
- package/dist/web/_app/immutable/chunks/Bvr6nSm92.js +1 -0
- package/dist/web/_app/immutable/chunks/BvyVm7-j.js +184 -0
- package/dist/web/_app/immutable/chunks/Bx6T8G5n2.js +1 -0
- package/dist/web/_app/immutable/chunks/BxeOdaUQ.js +1 -0
- package/dist/web/_app/immutable/chunks/C1B4xDs0.js +1 -0
- package/dist/web/_app/immutable/chunks/C2yz630b.js +2 -0
- package/dist/web/_app/immutable/chunks/C5YoDj-e.js +1 -0
- package/dist/web/_app/immutable/chunks/C6d9nn3k.js +1 -0
- package/dist/web/_app/immutable/chunks/C85NG5lS.js +1 -0
- package/dist/web/_app/immutable/chunks/C8ZiSqQm.js +1 -0
- package/dist/web/_app/immutable/chunks/CB-t1Bv-.js +1 -0
- package/dist/web/_app/immutable/chunks/CG8YKaqc.js +5 -0
- package/dist/web/_app/immutable/chunks/CJbr-v9X.js +1 -0
- package/dist/web/_app/immutable/chunks/CKGHeJzq.js +1 -0
- package/dist/web/_app/immutable/chunks/CKwV6HKc.js +1 -0
- package/dist/web/_app/immutable/chunks/CLninaDG2.js +2 -0
- package/dist/web/_app/immutable/chunks/CM400nLL.js +7 -0
- package/dist/web/_app/immutable/chunks/CPvxN2G62.js +1 -0
- package/dist/web/_app/immutable/chunks/CQW66fYJ.js +1 -0
- package/dist/web/_app/immutable/chunks/CQl_RUVN2.js +1 -0
- package/dist/web/_app/immutable/chunks/CR1JY_cb.js +6 -0
- package/dist/web/_app/immutable/chunks/CT35tdLp.js +1 -0
- package/dist/web/_app/immutable/chunks/CVKrGJ6C.js +1 -0
- package/dist/web/_app/immutable/chunks/CWIHttTH.js +1 -0
- package/dist/web/_app/immutable/chunks/CWZSexLW.js +1 -0
- package/dist/web/_app/immutable/chunks/CXPMb3zd.js +1 -0
- package/dist/web/_app/immutable/chunks/CZBW5wi82.js +1 -0
- package/dist/web/_app/immutable/chunks/CaimSvvF.js +5 -0
- package/dist/web/_app/immutable/chunks/CcWaWbrI2.js +1 -0
- package/dist/web/_app/immutable/chunks/CcgYvk_3.js +1 -0
- package/dist/web/_app/immutable/chunks/Cg7m1gqN.js +1 -0
- package/dist/web/_app/immutable/chunks/CgCz4Fj6.js +1 -0
- package/dist/web/_app/immutable/chunks/CgOgBK5j.js +1 -0
- package/dist/web/_app/immutable/chunks/ChS3kWO-.js +2 -0
- package/dist/web/_app/immutable/chunks/CkivrILi.js +1 -0
- package/dist/web/_app/immutable/chunks/CmWflMFc.js +1 -0
- package/dist/web/_app/immutable/chunks/Cm_yhEon2.js +1 -0
- package/dist/web/_app/immutable/chunks/CnoUnj6-2.js +1 -0
- package/dist/web/_app/immutable/chunks/CqyFdLMa.js +1 -0
- package/dist/web/_app/immutable/chunks/CsIWxmio2.js +22 -0
- package/dist/web/_app/immutable/chunks/CtgdtZ0G2.js +1 -0
- package/dist/web/_app/immutable/chunks/CuYL65Bn.js +2 -0
- package/dist/web/_app/immutable/chunks/CuatNdzp.js +2 -0
- package/dist/web/_app/immutable/chunks/Cx5IiiDO2.js +2 -0
- package/dist/web/_app/immutable/chunks/Cxdio_Al.js +2692 -0
- package/dist/web/_app/immutable/chunks/CyTgggiM2.js +1 -0
- package/dist/web/_app/immutable/chunks/D-vvXTU3.js +2 -0
- package/dist/web/_app/immutable/chunks/D0_Iu3Uj.js +2 -0
- package/dist/web/_app/immutable/chunks/D4vfNy_f.js +1 -0
- package/dist/web/_app/immutable/chunks/D6toh9Mr.js +1 -0
- package/dist/web/_app/immutable/chunks/DAsarOLg2.js +1 -0
- package/dist/web/_app/immutable/chunks/DBGaXAP8.js +1 -0
- package/dist/web/_app/immutable/chunks/DCgKWtld.js +1 -0
- package/dist/web/_app/immutable/chunks/DG7uGoZb2.js +58 -0
- package/dist/web/_app/immutable/chunks/DIsSxB_C.js +1 -0
- package/dist/web/_app/immutable/chunks/DJTvBXur.js +2 -0
- package/dist/web/_app/immutable/chunks/DLuIHCPq.js +1 -0
- package/dist/web/_app/immutable/chunks/DMFH9Cnt2.js +1 -0
- package/dist/web/_app/immutable/chunks/DNLoQniz.js +1 -0
- package/dist/web/_app/immutable/chunks/DPLkAijI2.js +1 -0
- package/dist/web/_app/immutable/chunks/DQMWa2t7.js +1 -0
- package/dist/web/_app/immutable/chunks/DTOO9nUj.js +1 -0
- package/dist/web/_app/immutable/chunks/DaB0hU8_.js +1 -0
- package/dist/web/_app/immutable/chunks/Dav8Lgyy2.js +5 -0
- package/dist/web/_app/immutable/chunks/Ddgu2Sjp.js +1 -0
- package/dist/web/_app/immutable/chunks/DfUXQOZ92.js +1 -0
- package/dist/web/_app/immutable/chunks/DgP0Pxes.js +224 -0
- package/dist/web/_app/immutable/chunks/DhD1HF7R.js +1 -0
- package/dist/web/_app/immutable/chunks/Dhmw5GUH.js +1 -0
- package/dist/web/_app/immutable/chunks/DiBVOY752.js +1 -0
- package/dist/web/_app/immutable/chunks/DiMsLIh3.js +1 -0
- package/dist/web/_app/immutable/chunks/DiiXREv7.js +1 -0
- package/dist/web/_app/immutable/chunks/DjXtgWTP.js +1 -0
- package/dist/web/_app/immutable/chunks/Dk2-JtmU2.js +1 -0
- package/dist/web/_app/immutable/chunks/Dl0bYyV_.js +1 -0
- package/dist/web/_app/immutable/chunks/DqKYbIld2.js +1 -0
- package/dist/web/_app/immutable/chunks/DtXJCaQc2.js +1 -0
- package/dist/web/_app/immutable/chunks/DwuBUEvU.js +1 -0
- package/dist/web/_app/immutable/chunks/F5xZdLlB.js +2 -0
- package/dist/web/_app/immutable/chunks/F92HmKX8.js +1 -0
- package/dist/web/_app/immutable/chunks/FISIvbqq.js +3 -0
- package/dist/web/_app/immutable/chunks/GC5Izn9E2.js +1 -0
- package/dist/web/_app/immutable/chunks/IMJutqSu2.js +1 -0
- package/dist/web/_app/immutable/chunks/K5YHt1WL2.js +1 -0
- package/dist/web/_app/immutable/chunks/MAYbL-xn2.js +6 -0
- package/dist/web/_app/immutable/chunks/MMtHlrry.js +1 -0
- package/dist/web/_app/immutable/chunks/MtN4vJa02.js +1 -0
- package/dist/web/_app/immutable/chunks/Oglnp7sm.js +14 -0
- package/dist/web/_app/immutable/chunks/PwUOTfSv.js +2 -0
- package/dist/web/_app/immutable/chunks/QsO256-z.js +1 -0
- package/dist/web/_app/immutable/chunks/RA-JwTzo.js +1 -0
- package/dist/web/_app/immutable/chunks/atFSAfcd2.js +2 -0
- package/dist/web/_app/immutable/chunks/j_uHTBnD.js +1 -0
- package/dist/web/_app/immutable/chunks/joKsoiFC.js +1 -0
- package/dist/web/_app/immutable/chunks/kNaey6uv.js +1 -0
- package/dist/web/_app/immutable/chunks/nqo2e2Yv.js +1 -0
- package/dist/web/_app/immutable/chunks/oIZupUnz.js +1 -0
- package/dist/web/_app/immutable/chunks/pPsy4008.js +1 -0
- package/dist/web/_app/immutable/chunks/qevfNcid.js +1 -0
- package/dist/web/_app/immutable/chunks/rjjyuGhb.js +1 -0
- package/dist/web/_app/immutable/chunks/sUAtda6y2.js +2 -0
- package/dist/web/_app/immutable/chunks/snCOoD78.js +1 -0
- package/dist/web/_app/immutable/chunks/uHaS74te.js +1 -0
- package/dist/web/_app/immutable/chunks/w5tsuIav.js +5 -0
- package/dist/web/_app/immutable/chunks/xihTtKlq.js +1 -0
- package/dist/web/_app/immutable/entry/app.1tcNTKI-.js +2 -0
- package/dist/web/_app/immutable/entry/start.BJMsySI1.js +1 -0
- package/dist/web/_app/immutable/nodes/0.v0W5DL7W.js +2 -0
- package/dist/web/_app/immutable/nodes/1.81Rb3Ith.js +1 -0
- package/dist/web/_app/immutable/nodes/10.CAlZNFOq.js +1 -0
- package/dist/web/_app/immutable/nodes/11.CCfUginI.js +1 -0
- package/dist/web/_app/immutable/nodes/12.BJLboAyk.js +1 -0
- package/dist/web/_app/immutable/nodes/13.Cz4UTl2G.js +1 -0
- package/dist/web/_app/immutable/nodes/14.DJwakC0o.js +1 -0
- package/dist/web/_app/immutable/nodes/15.D2mNE6C-.js +1 -0
- package/dist/web/_app/immutable/nodes/16.BjohAUt9.js +1 -0
- package/dist/web/_app/immutable/nodes/17.BI2F37bS.js +1 -0
- package/dist/web/_app/immutable/nodes/18.DsOT0tDo.js +1 -0
- package/dist/web/_app/immutable/nodes/19.BiJWLOUT.js +77 -0
- package/dist/web/_app/immutable/nodes/2.Cd72nG2V.js +124 -0
- package/dist/web/_app/immutable/nodes/3.BIT9mm9r.js +6 -0
- package/dist/web/_app/immutable/nodes/4.Cn7r0Poe.js +16 -0
- package/dist/web/_app/immutable/nodes/5.CkJNdCZG.js +1 -0
- package/dist/web/_app/immutable/nodes/6.1rbIRQgQ.js +6 -0
- package/dist/web/_app/immutable/nodes/7.w17Zl36i.js +1 -0
- package/dist/web/_app/immutable/nodes/8.DqTsYuU-.js +1 -0
- package/dist/web/_app/immutable/nodes/9.BJhYZPfc.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 +59 -20
- package/dist/web/studiograph-logomark.svg +29 -0
- package/package.json +44 -15
- package/dist/agent/role-loader.d.ts +0 -24
- package/dist/agent/role-loader.js +0 -67
- package/dist/agent/role-loader.js.map +0 -1
- package/dist/agent/skills/create-deck/content-guide.md +0 -238
- package/dist/agent/skills/create-deck/deck-cadence.md +0 -105
- package/dist/agent/skills/create-deck/diagrams.md +0 -431
- package/dist/agent/skills/create-deck/skill.md +0 -89
- package/dist/agent/skills/create-deck/slide-selection.md +0 -115
- package/dist/agent/skills/create-deck/slide-types.md +0 -638
- package/dist/agent/skills/create-document/skill.md +0 -229
- package/dist/agent/skills/enrich-entities.md +0 -136
- package/dist/agent/skills/obsidian-source-setup.md +0 -246
- package/dist/agent/skills/skill-loader.d.ts +0 -48
- package/dist/agent/skills/skill-loader.js +0 -166
- package/dist/agent/skills/skill-loader.js.map +0 -1
- 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 -96
- package/dist/agent/tools/message-tools.js.map +0 -1
- package/dist/agent/tools/sync-tools.d.ts +0 -35
- package/dist/agent/tools/sync-tools.js +0 -992
- package/dist/agent/tools/sync-tools.js.map +0 -1
- package/dist/auth/github.d.ts +0 -56
- package/dist/auth/github.js +0 -169
- package/dist/auth/github.js.map +0 -1
- package/dist/cli/commands/access.d.ts +0 -11
- package/dist/cli/commands/access.js +0 -156
- package/dist/cli/commands/access.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/auth.d.ts +0 -10
- package/dist/cli/commands/auth.js +0 -79
- package/dist/cli/commands/auth.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 +0 -185
- package/dist/cli/commands/collection.js.map +0 -1
- package/dist/cli/commands/enrich.d.ts +0 -11
- package/dist/cli/commands/enrich.js +0 -135
- package/dist/cli/commands/enrich.js.map +0 -1
- package/dist/cli/commands/graphrag.d.ts +0 -12
- package/dist/cli/commands/graphrag.js +0 -122
- package/dist/cli/commands/graphrag.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/members.d.ts +0 -11
- package/dist/cli/commands/members.js +0 -230
- package/dist/cli/commands/members.js.map +0 -1
- package/dist/cli/commands/org.d.ts +0 -10
- package/dist/cli/commands/org.js +0 -132
- package/dist/cli/commands/org.js.map +0 -1
- package/dist/cli/commands/provision.d.ts +0 -8
- package/dist/cli/commands/provision.js +0 -116
- package/dist/cli/commands/provision.js.map +0 -1
- package/dist/cli/commands/resolve.d.ts +0 -8
- package/dist/cli/commands/resolve.js +0 -85
- package/dist/cli/commands/resolve.js.map +0 -1
- package/dist/cli/commands/review.d.ts +0 -19
- package/dist/cli/commands/review.js +0 -128
- package/dist/cli/commands/review.js.map +0 -1
- package/dist/cli/commands/source.d.ts +0 -16
- package/dist/cli/commands/source.js +0 -159
- package/dist/cli/commands/source.js.map +0 -1
- package/dist/cli/commands/start.d.ts +0 -7
- package/dist/cli/commands/start.js +0 -576
- package/dist/cli/commands/start.js.map +0 -1
- package/dist/cli/commands/sync-collection.d.ts +0 -14
- package/dist/cli/commands/sync-collection.js +0 -355
- package/dist/cli/commands/sync-collection.js.map +0 -1
- package/dist/cli/commands/sync-entities.d.ts +0 -13
- package/dist/cli/commands/sync-entities.js +0 -242
- package/dist/cli/commands/sync-entities.js.map +0 -1
- package/dist/cli/commands/team.d.ts +0 -12
- package/dist/cli/commands/team.js +0 -185
- package/dist/cli/commands/team.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 -169
- 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/cli/sync-review-interactive.d.ts +0 -31
- package/dist/cli/sync-review-interactive.js +0 -393
- package/dist/cli/sync-review-interactive.js.map +0 -1
- package/dist/core/feature-flags.d.ts +0 -17
- package/dist/core/feature-flags.js +0 -15
- package/dist/core/feature-flags.js.map +0 -1
- package/dist/core/features.d.ts +0 -16
- package/dist/core/features.js +0 -48
- package/dist/core/features.js.map +0 -1
- package/dist/core/launch-entity-types.d.ts +0 -23
- package/dist/core/launch-entity-types.js +0 -48
- package/dist/core/launch-entity-types.js.map +0 -1
- package/dist/core/migration-runner.d.ts +0 -42
- package/dist/core/migration-runner.js +0 -232
- package/dist/core/migration-runner.js.map +0 -1
- package/dist/core/migration-types.d.ts +0 -101
- package/dist/core/migration-types.js +0 -21
- package/dist/core/migration-types.js.map +0 -1
- package/dist/core/migrations/20260219-formalize-memory-location.d.ts +0 -2
- package/dist/core/migrations/20260219-formalize-memory-location.js +0 -35
- package/dist/core/migrations/20260219-formalize-memory-location.js.map +0 -1
- package/dist/core/migrations/20260220-add-workspace-metadata.d.ts +0 -12
- package/dist/core/migrations/20260220-add-workspace-metadata.js +0 -65
- package/dist/core/migrations/20260220-add-workspace-metadata.js.map +0 -1
- package/dist/core/migrations/20260220-add-workspace-readme.d.ts +0 -11
- package/dist/core/migrations/20260220-add-workspace-readme.js +0 -82
- package/dist/core/migrations/20260220-add-workspace-readme.js.map +0 -1
- package/dist/core/migrations/20260220-migrate-yaml-to-json.d.ts +0 -9
- package/dist/core/migrations/20260220-migrate-yaml-to-json.js +0 -64
- package/dist/core/migrations/20260220-migrate-yaml-to-json.js.map +0 -1
- package/dist/core/migrations/index.d.ts +0 -11
- package/dist/core/migrations/index.js +0 -23
- package/dist/core/migrations/index.js.map +0 -1
- package/dist/integrations/asana.d.ts +0 -26
- package/dist/integrations/asana.js +0 -78
- package/dist/integrations/asana.js.map +0 -1
- package/dist/integrations/figma-local.d.ts +0 -16
- package/dist/integrations/figma-local.js +0 -10
- package/dist/integrations/figma-local.js.map +0 -1
- package/dist/integrations/figma.d.ts +0 -17
- package/dist/integrations/figma.js +0 -17
- package/dist/integrations/figma.js.map +0 -1
- package/dist/integrations/granola.d.ts +0 -24
- package/dist/integrations/granola.js +0 -60
- package/dist/integrations/granola.js.map +0 -1
- package/dist/integrations/linear.d.ts +0 -14
- package/dist/integrations/linear.js +0 -80
- package/dist/integrations/linear.js.map +0 -1
- package/dist/integrations/paper-local.d.ts +0 -14
- package/dist/integrations/paper-local.js +0 -10
- package/dist/integrations/paper-local.js.map +0 -1
- package/dist/integrations/paper.d.ts +0 -2
- package/dist/integrations/paper.js +0 -10
- package/dist/integrations/paper.js.map +0 -1
- package/dist/integrations/pipedrive.d.ts +0 -26
- package/dist/integrations/pipedrive.js +0 -97
- package/dist/integrations/pipedrive.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/mcp/connectors/asana.d.ts +0 -2
- package/dist/mcp/connectors/asana.js +0 -20
- package/dist/mcp/connectors/asana.js.map +0 -1
- package/dist/mcp/connectors/definitions.d.ts +0 -45
- package/dist/mcp/connectors/definitions.js +0 -32
- package/dist/mcp/connectors/definitions.js.map +0 -1
- package/dist/mcp/connectors/figma.d.ts +0 -5
- package/dist/mcp/connectors/figma.js +0 -21
- package/dist/mcp/connectors/figma.js.map +0 -1
- package/dist/mcp/connectors/gdrive.d.ts +0 -2
- package/dist/mcp/connectors/gdrive.js +0 -20
- package/dist/mcp/connectors/gdrive.js.map +0 -1
- package/dist/mcp/connectors/granola.d.ts +0 -2
- package/dist/mcp/connectors/granola.js +0 -12
- package/dist/mcp/connectors/granola.js.map +0 -1
- package/dist/mcp/connectors/linear.d.ts +0 -2
- package/dist/mcp/connectors/linear.js +0 -19
- package/dist/mcp/connectors/linear.js.map +0 -1
- package/dist/mcp/connectors/obsidian.d.ts +0 -2
- package/dist/mcp/connectors/obsidian.js +0 -19
- package/dist/mcp/connectors/obsidian.js.map +0 -1
- package/dist/mcp/connectors/pipedrive.d.ts +0 -2
- package/dist/mcp/connectors/pipedrive.js +0 -20
- package/dist/mcp/connectors/pipedrive.js.map +0 -1
- package/dist/mcp/connectors/slack.d.ts +0 -2
- package/dist/mcp/connectors/slack.js +0 -21
- package/dist/mcp/connectors/slack.js.map +0 -1
- package/dist/mcp/server-oauth-provider.d.ts +0 -56
- package/dist/mcp/server-oauth-provider.js +0 -142
- package/dist/mcp/server-oauth-provider.js.map +0 -1
- package/dist/renderers/documents/branding.d.ts +0 -2
- package/dist/renderers/documents/branding.js +0 -42
- package/dist/renderers/documents/branding.js.map +0 -1
- package/dist/renderers/documents/entity-types.json +0 -14
- package/dist/renderers/documents/parser/frontmatter.d.ts +0 -5
- package/dist/renderers/documents/parser/frontmatter.js +0 -90
- package/dist/renderers/documents/parser/frontmatter.js.map +0 -1
- package/dist/renderers/documents/parser/index.d.ts +0 -5
- package/dist/renderers/documents/parser/index.js +0 -142
- package/dist/renderers/documents/parser/index.js.map +0 -1
- package/dist/renderers/documents/parser/sections.d.ts +0 -18
- package/dist/renderers/documents/parser/sections.js +0 -131
- package/dist/renderers/documents/parser/sections.js.map +0 -1
- package/dist/renderers/documents/pdf/generator.d.ts +0 -13
- package/dist/renderers/documents/pdf/generator.js +0 -79
- package/dist/renderers/documents/pdf/generator.js.map +0 -1
- package/dist/renderers/documents/plugin.d.ts +0 -2
- package/dist/renderers/documents/plugin.js +0 -63
- package/dist/renderers/documents/plugin.js.map +0 -1
- package/dist/renderers/documents/public/branding/logo.svg +0 -3
- package/dist/renderers/documents/public/editor/chrome.css +0 -461
- package/dist/renderers/documents/public/editor/chrome.js +0 -524
- package/dist/renderers/documents/public/editor/editor.css +0 -751
- package/dist/renderers/documents/public/editor/editor.html +0 -140
- package/dist/renderers/documents/public/editor/editor.js +0 -953
- package/dist/renderers/documents/public/fonts/fonts.css +0 -145
- package/dist/renderers/documents/public/viewer/viewer.css +0 -1
- package/dist/renderers/documents/public/viewer/viewer.js +0 -4
- package/dist/renderers/documents/renderer/back.d.ts +0 -8
- package/dist/renderers/documents/renderer/back.js +0 -28
- package/dist/renderers/documents/renderer/back.js.map +0 -1
- package/dist/renderers/documents/renderer/content.d.ts +0 -7
- package/dist/renderers/documents/renderer/content.js +0 -25
- package/dist/renderers/documents/renderer/content.js.map +0 -1
- package/dist/renderers/documents/renderer/cover.d.ts +0 -15
- package/dist/renderers/documents/renderer/cover.js +0 -43
- package/dist/renderers/documents/renderer/cover.js.map +0 -1
- package/dist/renderers/documents/renderer/helpers.d.ts +0 -1
- package/dist/renderers/documents/renderer/helpers.js +0 -11
- package/dist/renderers/documents/renderer/helpers.js.map +0 -1
- package/dist/renderers/documents/renderer/index.d.ts +0 -32
- package/dist/renderers/documents/renderer/index.js +0 -80
- package/dist/renderers/documents/renderer/index.js.map +0 -1
- package/dist/renderers/documents/renderer/styles.d.ts +0 -16
- package/dist/renderers/documents/renderer/styles.js +0 -546
- package/dist/renderers/documents/renderer/styles.js.map +0 -1
- package/dist/renderers/documents/routes/api.d.ts +0 -2
- package/dist/renderers/documents/routes/api.js +0 -587
- package/dist/renderers/documents/routes/api.js.map +0 -1
- package/dist/renderers/documents/routes/editor.d.ts +0 -2
- package/dist/renderers/documents/routes/editor.js +0 -31
- package/dist/renderers/documents/routes/editor.js.map +0 -1
- package/dist/renderers/documents/routes/viewer.d.ts +0 -2
- package/dist/renderers/documents/routes/viewer.js +0 -67
- package/dist/renderers/documents/routes/viewer.js.map +0 -1
- package/dist/renderers/documents/types.d.ts +0 -61
- package/dist/renderers/documents/types.js +0 -2
- package/dist/renderers/documents/types.js.map +0 -1
- package/dist/renderers/index.d.ts +0 -24
- package/dist/renderers/index.js +0 -66
- package/dist/renderers/index.js.map +0 -1
- package/dist/renderers/slides/design-system/tokens.d.ts +0 -108
- package/dist/renderers/slides/design-system/tokens.js +0 -44
- package/dist/renderers/slides/design-system/tokens.js.map +0 -1
- package/dist/renderers/slides/entity-types.json +0 -14
- package/dist/renderers/slides/parser/comment-parser.d.ts +0 -10
- package/dist/renderers/slides/parser/comment-parser.js +0 -89
- package/dist/renderers/slides/parser/comment-parser.js.map +0 -1
- package/dist/renderers/slides/parser/frontmatter.d.ts +0 -10
- package/dist/renderers/slides/parser/frontmatter.js +0 -93
- package/dist/renderers/slides/parser/frontmatter.js.map +0 -1
- package/dist/renderers/slides/parser/index.d.ts +0 -7
- package/dist/renderers/slides/parser/index.js +0 -569
- package/dist/renderers/slides/parser/index.js.map +0 -1
- package/dist/renderers/slides/parser/slide-splitter.d.ts +0 -6
- package/dist/renderers/slides/parser/slide-splitter.js +0 -54
- package/dist/renderers/slides/parser/slide-splitter.js.map +0 -1
- package/dist/renderers/slides/parser/type-detector.d.ts +0 -6
- package/dist/renderers/slides/parser/type-detector.js +0 -70
- package/dist/renderers/slides/parser/type-detector.js.map +0 -1
- package/dist/renderers/slides/pdf/generator.d.ts +0 -11
- package/dist/renderers/slides/pdf/generator.js +0 -105
- package/dist/renderers/slides/pdf/generator.js.map +0 -1
- package/dist/renderers/slides/plugin.d.ts +0 -2
- package/dist/renderers/slides/plugin.js +0 -70
- package/dist/renderers/slides/plugin.js.map +0 -1
- package/dist/renderers/slides/public/editor/.gitkeep +0 -0
- package/dist/renderers/slides/public/editor/chrome.css +0 -461
- package/dist/renderers/slides/public/editor/chrome.js +0 -519
- package/dist/renderers/slides/public/editor/editor.css +0 -896
- package/dist/renderers/slides/public/editor/editor.html +0 -131
- package/dist/renderers/slides/public/editor/editor.js +0 -1038
- package/dist/renderers/slides/public/fonts/fonts.css +0 -145
- package/dist/renderers/slides/public/viewer/.gitkeep +0 -0
- package/dist/renderers/slides/public/viewer/chrome.css +0 -62
- package/dist/renderers/slides/public/viewer/transitions.css +0 -1
- package/dist/renderers/slides/public/viewer/viewer.css +0 -2145
- package/dist/renderers/slides/public/viewer/viewer.js +0 -303
- package/dist/renderers/slides/renderer/chrome/browser-frame.d.ts +0 -5
- package/dist/renderers/slides/renderer/chrome/browser-frame.js +0 -22
- package/dist/renderers/slides/renderer/chrome/browser-frame.js.map +0 -1
- package/dist/renderers/slides/renderer/chrome/mobile-frame.d.ts +0 -6
- package/dist/renderers/slides/renderer/chrome/mobile-frame.js +0 -18
- package/dist/renderers/slides/renderer/chrome/mobile-frame.js.map +0 -1
- package/dist/renderers/slides/renderer/helpers.d.ts +0 -13
- package/dist/renderers/slides/renderer/helpers.js +0 -33
- package/dist/renderers/slides/renderer/helpers.js.map +0 -1
- package/dist/renderers/slides/renderer/index.d.ts +0 -16
- package/dist/renderers/slides/renderer/index.js +0 -160
- package/dist/renderers/slides/renderer/index.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/agenda.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/agenda.js +0 -18
- package/dist/renderers/slides/renderer/layouts/agenda.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/blank.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/blank.js +0 -4
- package/dist/renderers/slides/renderer/layouts/blank.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/columns.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/columns.js +0 -32
- package/dist/renderers/slides/renderer/layouts/columns.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/cover.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/cover.js +0 -18
- package/dist/renderers/slides/renderer/layouts/cover.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/diagram-concentric.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/diagram-concentric.js +0 -120
- package/dist/renderers/slides/renderer/layouts/diagram-concentric.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/diagram-matrix.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/diagram-matrix.js +0 -158
- package/dist/renderers/slides/renderer/layouts/diagram-matrix.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/diagram-pyramid.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/diagram-pyramid.js +0 -86
- package/dist/renderers/slides/renderer/layouts/diagram-pyramid.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/diagram-sitemap.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/diagram-sitemap.js +0 -146
- package/dist/renderers/slides/renderer/layouts/diagram-sitemap.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/diagram-venn.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/diagram-venn.js +0 -100
- package/dist/renderers/slides/renderer/layouts/diagram-venn.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/diagram.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/diagram.js +0 -21
- package/dist/renderers/slides/renderer/layouts/diagram.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/divider.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/divider.js +0 -4
- package/dist/renderers/slides/renderer/layouts/divider.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/end.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/end.js +0 -8
- package/dist/renderers/slides/renderer/layouts/end.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/framework.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/framework.js +0 -47
- package/dist/renderers/slides/renderer/layouts/framework.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/image-browser-2up.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/image-browser-2up.js +0 -38
- package/dist/renderers/slides/renderer/layouts/image-browser-2up.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/image-browser-6up.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/image-browser-6up.js +0 -30
- package/dist/renderers/slides/renderer/layouts/image-browser-6up.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/image-browser.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/image-browser.js +0 -18
- package/dist/renderers/slides/renderer/layouts/image-browser.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/image-bullets.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/image-bullets.js +0 -26
- package/dist/renderers/slides/renderer/layouts/image-bullets.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/image-device-pair.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/image-device-pair.js +0 -34
- package/dist/renderers/slides/renderer/layouts/image-device-pair.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/image-full.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/image-full.js +0 -36
- package/dist/renderers/slides/renderer/layouts/image-full.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/image-grid-2x2.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/image-grid-2x2.js +0 -23
- package/dist/renderers/slides/renderer/layouts/image-grid-2x2.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/image-keywords.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/image-keywords.js +0 -31
- package/dist/renderers/slides/renderer/layouts/image-keywords.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/image-mobile-3up.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/image-mobile-3up.js +0 -22
- package/dist/renderers/slides/renderer/layouts/image-mobile-3up.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/image-mobile.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/image-mobile.js +0 -17
- package/dist/renderers/slides/renderer/layouts/image-mobile.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/image-split-right.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/image-split-right.js +0 -19
- package/dist/renderers/slides/renderer/layouts/image-split-right.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/image-split.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/image-split.js +0 -19
- package/dist/renderers/slides/renderer/layouts/image-split.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/introductions.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/introductions.js +0 -41
- package/dist/renderers/slides/renderer/layouts/introductions.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/moodboard.d.ts +0 -15
- package/dist/renderers/slides/renderer/layouts/moodboard.js +0 -63
- package/dist/renderers/slides/renderer/layouts/moodboard.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/next-steps.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/next-steps.js +0 -13
- package/dist/renderers/slides/renderer/layouts/next-steps.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/pullquote.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/pullquote.js +0 -17
- package/dist/renderers/slides/renderer/layouts/pullquote.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/schedule.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/schedule.js +0 -58
- package/dist/renderers/slides/renderer/layouts/schedule.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/section.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/section.js +0 -18
- package/dist/renderers/slides/renderer/layouts/section.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/statement.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/statement.js +0 -13
- package/dist/renderers/slides/renderer/layouts/statement.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/stats.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/stats.js +0 -26
- package/dist/renderers/slides/renderer/layouts/stats.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/table.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/table.js +0 -23
- package/dist/renderers/slides/renderer/layouts/table.js.map +0 -1
- package/dist/renderers/slides/renderer/layouts/text-body.d.ts +0 -2
- package/dist/renderers/slides/renderer/layouts/text-body.js +0 -14
- package/dist/renderers/slides/renderer/layouts/text-body.js.map +0 -1
- package/dist/renderers/slides/renderer/placeholder.d.ts +0 -5
- package/dist/renderers/slides/renderer/placeholder.js +0 -12
- package/dist/renderers/slides/renderer/placeholder.js.map +0 -1
- package/dist/renderers/slides/routes/api.d.ts +0 -2
- package/dist/renderers/slides/routes/api.js +0 -568
- package/dist/renderers/slides/routes/api.js.map +0 -1
- package/dist/renderers/slides/routes/editor.d.ts +0 -2
- package/dist/renderers/slides/routes/editor.js +0 -32
- package/dist/renderers/slides/routes/editor.js.map +0 -1
- package/dist/renderers/slides/routes/viewer.d.ts +0 -2
- package/dist/renderers/slides/routes/viewer.js +0 -67
- package/dist/renderers/slides/routes/viewer.js.map +0 -1
- package/dist/renderers/slides/types.d.ts +0 -111
- package/dist/renderers/slides/types.js +0 -2
- package/dist/renderers/slides/types.js.map +0 -1
- package/dist/server/agents/agent-loader.d.ts +0 -16
- package/dist/server/agents/agent-loader.js +0 -175
- package/dist/server/agents/agent-loader.js.map +0 -1
- package/dist/server/agents/agent-runner.d.ts +0 -33
- package/dist/server/agents/agent-runner.js +0 -187
- package/dist/server/agents/agent-runner.js.map +0 -1
- package/dist/server/agents/agent-runtime.d.ts +0 -39
- package/dist/server/agents/agent-runtime.js +0 -214
- package/dist/server/agents/agent-runtime.js.map +0 -1
- package/dist/server/agents/base-agent.d.ts +0 -30
- package/dist/server/agents/base-agent.js +0 -88
- package/dist/server/agents/base-agent.js.map +0 -1
- package/dist/server/agents/executive-digest.d.ts +0 -16
- package/dist/server/agents/executive-digest.js +0 -115
- package/dist/server/agents/executive-digest.js.map +0 -1
- package/dist/server/agents/graph-steward.d.ts +0 -16
- package/dist/server/agents/graph-steward.js +0 -59
- package/dist/server/agents/graph-steward.js.map +0 -1
- package/dist/server/agents/meeting-followup.d.ts +0 -16
- package/dist/server/agents/meeting-followup.js +0 -88
- package/dist/server/agents/meeting-followup.js.map +0 -1
- package/dist/server/agents/pipeline-monitor.d.ts +0 -15
- package/dist/server/agents/pipeline-monitor.js +0 -86
- package/dist/server/agents/pipeline-monitor.js.map +0 -1
- package/dist/server/agents/types.d.ts +0 -81
- package/dist/server/agents/types.js +0 -41
- package/dist/server/agents/types.js.map +0 -1
- package/dist/server/chrome/chrome.css +0 -701
- package/dist/server/chrome/chrome.js +0 -374
- package/dist/server/collab-authority.d.ts +0 -70
- package/dist/server/collab-authority.js +0 -218
- package/dist/server/collab-authority.js.map +0 -1
- package/dist/server/collab.d.ts +0 -29
- package/dist/server/collab.js +0 -195
- package/dist/server/collab.js.map +0 -1
- 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/agents-api.d.ts +0 -10
- package/dist/server/routes/agents-api.js +0 -25
- package/dist/server/routes/agents-api.js.map +0 -1
- package/dist/server/routes/collab.d.ts +0 -6
- package/dist/server/routes/collab.js +0 -10
- package/dist/server/routes/collab.js.map +0 -1
- package/dist/server/routes/features-api.d.ts +0 -10
- package/dist/server/routes/features-api.js +0 -15
- package/dist/server/routes/features-api.js.map +0 -1
- package/dist/server/routes/git-api.d.ts +0 -9
- package/dist/server/routes/git-api.js +0 -82
- package/dist/server/routes/git-api.js.map +0 -1
- package/dist/server/routes/meeting-ingest-api.d.ts +0 -14
- package/dist/server/routes/meeting-ingest-api.js +0 -117
- 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 -569
- package/dist/server/routes/messages-api.js.map +0 -1
- package/dist/server/routes/sync-api.d.ts +0 -26
- package/dist/server/routes/sync-api.js +0 -848
- package/dist/server/routes/sync-api.js.map +0 -1
- package/dist/server/routes/view-public.d.ts +0 -10
- package/dist/server/routes/view-public.js +0 -107
- package/dist/server/routes/view-public.js.map +0 -1
- package/dist/server/routes/webhook.d.ts +0 -14
- package/dist/server/routes/webhook.js +0 -102
- package/dist/server/routes/webhook.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 -206
- package/dist/services/briefing.js.map +0 -1
- package/dist/services/github-provisioner.d.ts +0 -36
- package/dist/services/github-provisioner.js +0 -126
- package/dist/services/github-provisioner.js.map +0 -1
- package/dist/services/github-service.d.ts +0 -99
- package/dist/services/github-service.js +0 -310
- package/dist/services/github-service.js.map +0 -1
- package/dist/services/heartbeat.d.ts +0 -35
- package/dist/services/heartbeat.js +0 -236
- 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.d.ts +0 -97
- package/dist/services/meeting-processor.js +0 -382
- package/dist/services/meeting-processor.js.map +0 -1
- package/dist/services/message-service.d.ts +0 -117
- package/dist/services/message-service.js +0 -414
- package/dist/services/message-service.js.map +0 -1
- package/dist/services/sync/collection-sync.d.ts +0 -60
- package/dist/services/sync/collection-sync.js +0 -509
- package/dist/services/sync/collection-sync.js.map +0 -1
- package/dist/services/sync/commit.d.ts +0 -60
- package/dist/services/sync/commit.js +0 -354
- package/dist/services/sync/commit.js.map +0 -1
- package/dist/services/sync/context-index.d.ts +0 -69
- package/dist/services/sync/context-index.js +0 -280
- package/dist/services/sync/context-index.js.map +0 -1
- package/dist/services/sync/data-fetcher.d.ts +0 -31
- package/dist/services/sync/data-fetcher.js +0 -12
- package/dist/services/sync/data-fetcher.js.map +0 -1
- package/dist/services/sync/derive.d.ts +0 -34
- package/dist/services/sync/derive.js +0 -164
- package/dist/services/sync/derive.js.map +0 -1
- package/dist/services/sync/enrichment-state.d.ts +0 -31
- package/dist/services/sync/enrichment-state.js +0 -63
- package/dist/services/sync/enrichment-state.js.map +0 -1
- package/dist/services/sync/enrichment.d.ts +0 -25
- package/dist/services/sync/enrichment.js +0 -121
- package/dist/services/sync/enrichment.js.map +0 -1
- package/dist/services/sync/entity-refresh.d.ts +0 -30
- package/dist/services/sync/entity-refresh.js +0 -275
- package/dist/services/sync/entity-refresh.js.map +0 -1
- package/dist/services/sync/frontmatter-extractor.d.ts +0 -40
- package/dist/services/sync/frontmatter-extractor.js +0 -279
- package/dist/services/sync/frontmatter-extractor.js.map +0 -1
- package/dist/services/sync/graph-match-state.d.ts +0 -33
- package/dist/services/sync/graph-match-state.js +0 -61
- package/dist/services/sync/graph-match-state.js.map +0 -1
- package/dist/services/sync/graph-match.d.ts +0 -53
- package/dist/services/sync/graph-match.js +0 -316
- package/dist/services/sync/graph-match.js.map +0 -1
- package/dist/services/sync/graphrag-client.d.ts +0 -43
- package/dist/services/sync/graphrag-client.js +0 -94
- package/dist/services/sync/graphrag-client.js.map +0 -1
- package/dist/services/sync/graphrag-config.d.ts +0 -16
- package/dist/services/sync/graphrag-config.js +0 -39
- package/dist/services/sync/graphrag-config.js.map +0 -1
- package/dist/services/sync/graphrag-context.d.ts +0 -14
- package/dist/services/sync/graphrag-context.js +0 -109
- package/dist/services/sync/graphrag-context.js.map +0 -1
- package/dist/services/sync/graphrag-indexer.d.ts +0 -30
- package/dist/services/sync/graphrag-indexer.js +0 -358
- package/dist/services/sync/graphrag-indexer.js.map +0 -1
- package/dist/services/sync/llm.d.ts +0 -32
- package/dist/services/sync/llm.js +0 -115
- package/dist/services/sync/llm.js.map +0 -1
- package/dist/services/sync/mcp-client.d.ts +0 -71
- package/dist/services/sync/mcp-client.js +0 -299
- package/dist/services/sync/mcp-client.js.map +0 -1
- package/dist/services/sync/model-factory.d.ts +0 -13
- package/dist/services/sync/model-factory.js +0 -38
- package/dist/services/sync/model-factory.js.map +0 -1
- package/dist/services/sync/name-quality.d.ts +0 -31
- package/dist/services/sync/name-quality.js +0 -60
- package/dist/services/sync/name-quality.js.map +0 -1
- package/dist/services/sync/output-schemas.d.ts +0 -92
- package/dist/services/sync/output-schemas.js +0 -43
- package/dist/services/sync/output-schemas.js.map +0 -1
- package/dist/services/sync/prompts.d.ts +0 -19
- package/dist/services/sync/prompts.js +0 -128
- package/dist/services/sync/prompts.js.map +0 -1
- package/dist/services/sync/reconciler.d.ts +0 -48
- package/dist/services/sync/reconciler.js +0 -294
- package/dist/services/sync/reconciler.js.map +0 -1
- package/dist/services/sync/rest-client.d.ts +0 -40
- package/dist/services/sync/rest-client.js +0 -100
- package/dist/services/sync/rest-client.js.map +0 -1
- package/dist/services/sync/source-config.d.ts +0 -67
- package/dist/services/sync/source-config.js +0 -304
- package/dist/services/sync/source-config.js.map +0 -1
- package/dist/services/sync/source-definitions/asana.d.ts +0 -14
- package/dist/services/sync/source-definitions/asana.js +0 -42
- package/dist/services/sync/source-definitions/asana.js.map +0 -1
- package/dist/services/sync/source-definitions/definitions.d.ts +0 -8
- package/dist/services/sync/source-definitions/definitions.js +0 -8
- package/dist/services/sync/source-definitions/definitions.js.map +0 -1
- package/dist/services/sync/source-definitions/gdrive.d.ts +0 -16
- package/dist/services/sync/source-definitions/gdrive.js +0 -68
- package/dist/services/sync/source-definitions/gdrive.js.map +0 -1
- package/dist/services/sync/source-definitions/granola.d.ts +0 -2
- package/dist/services/sync/source-definitions/granola.js +0 -21
- package/dist/services/sync/source-definitions/granola.js.map +0 -1
- package/dist/services/sync/source-definitions/linear.d.ts +0 -2
- package/dist/services/sync/source-definitions/linear.js +0 -62
- package/dist/services/sync/source-definitions/linear.js.map +0 -1
- package/dist/services/sync/source-definitions/obsidian.d.ts +0 -2
- package/dist/services/sync/source-definitions/obsidian.js +0 -55
- package/dist/services/sync/source-definitions/obsidian.js.map +0 -1
- package/dist/services/sync/source-definitions/pipedrive.d.ts +0 -2
- package/dist/services/sync/source-definitions/pipedrive.js +0 -43
- package/dist/services/sync/source-definitions/pipedrive.js.map +0 -1
- package/dist/services/sync/staging.d.ts +0 -53
- package/dist/services/sync/staging.js +0 -130
- package/dist/services/sync/staging.js.map +0 -1
- package/dist/services/sync/structured-extractor.d.ts +0 -57
- package/dist/services/sync/structured-extractor.js +0 -584
- package/dist/services/sync/structured-extractor.js.map +0 -1
- package/dist/services/sync/sync-runner.d.ts +0 -32
- package/dist/services/sync/sync-runner.js +0 -195
- package/dist/services/sync/sync-runner.js.map +0 -1
- package/dist/services/sync/sync-state.d.ts +0 -43
- package/dist/services/sync/sync-state.js +0 -154
- package/dist/services/sync/sync-state.js.map +0 -1
- package/dist/services/sync/types.d.ts +0 -381
- package/dist/services/sync/types.js +0 -8
- package/dist/services/sync/types.js.map +0 -1
- package/dist/services/sync/unstructured-extractor.d.ts +0 -27
- package/dist/services/sync/unstructured-extractor.js +0 -185
- package/dist/services/sync/unstructured-extractor.js.map +0 -1
- package/dist/utils/merge-resolver.d.ts +0 -59
- package/dist/utils/merge-resolver.js +0 -303
- package/dist/utils/merge-resolver.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.B3eM5J6m.css +0 -1
- package/dist/web/_app/immutable/assets/10.2jzI3cZY.css +0 -1
- package/dist/web/_app/immutable/assets/11.BbBNo8jg.css +0 -1
- package/dist/web/_app/immutable/assets/12.vV-8Nqud.css +0 -1
- package/dist/web/_app/immutable/assets/2.CDsrnCq3.css +0 -1
- package/dist/web/_app/immutable/assets/3.Ih7su2rl.css +0 -1
- package/dist/web/_app/immutable/assets/4.CwXIQz3p.css +0 -1
- package/dist/web/_app/immutable/assets/ChevronLeft.BwM9mwS4.css +0 -1
- package/dist/web/_app/immutable/assets/GraphView.CRV5cekT.css +0 -1
- package/dist/web/_app/immutable/assets/Toaster.rN8r_Hzv.css +0 -1
- package/dist/web/_app/immutable/assets/ViewToolbar.CsWTe0yn.css +0 -1
- package/dist/web/_app/immutable/assets/WorkspaceView.iWtzksrr.css +0 -1
- package/dist/web/_app/immutable/chunks/0kuZ2gtb.js +0 -1
- package/dist/web/_app/immutable/chunks/1Ea1UXwM.js +0 -2
- package/dist/web/_app/immutable/chunks/284Zhbif.js +0 -1
- package/dist/web/_app/immutable/chunks/7S2LGqoP.js +0 -2
- package/dist/web/_app/immutable/chunks/7WZENMwt.js +0 -1
- package/dist/web/_app/immutable/chunks/B0qHb2ee.js +0 -1
- package/dist/web/_app/immutable/chunks/B45od9dM.js +0 -2
- package/dist/web/_app/immutable/chunks/B4hXkypI.js +0 -1
- package/dist/web/_app/immutable/chunks/BB_5th5W.js +0 -3383
- package/dist/web/_app/immutable/chunks/BDvg8O5k.js +0 -5
- package/dist/web/_app/immutable/chunks/BGiCmAMQ.js +0 -4
- package/dist/web/_app/immutable/chunks/BHgT2Ede.js +0 -1
- package/dist/web/_app/immutable/chunks/BQ1nK9ru.js +0 -1
- package/dist/web/_app/immutable/chunks/BS0-13V2.js +0 -1
- package/dist/web/_app/immutable/chunks/BSiC7hSS.js +0 -1
- package/dist/web/_app/immutable/chunks/BkxW0BJZ.js +0 -1
- package/dist/web/_app/immutable/chunks/Bp7FKkGt.js +0 -1
- package/dist/web/_app/immutable/chunks/BrebtJNG.js +0 -18
- package/dist/web/_app/immutable/chunks/Bz0ZjVQE.js +0 -2
- package/dist/web/_app/immutable/chunks/C0LVZhvn.js +0 -1
- package/dist/web/_app/immutable/chunks/C0Zx4tMe.js +0 -1
- package/dist/web/_app/immutable/chunks/CHJcPZZK.js +0 -2
- package/dist/web/_app/immutable/chunks/CRpK3grw.js +0 -2
- package/dist/web/_app/immutable/chunks/CSVbGg_b.js +0 -5
- 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/CdIKiXW6.js +0 -1
- package/dist/web/_app/immutable/chunks/CqkleIqs.js +0 -1
- package/dist/web/_app/immutable/chunks/D14M5KlG.js +0 -1
- package/dist/web/_app/immutable/chunks/D71c9o7J.js +0 -2
- package/dist/web/_app/immutable/chunks/DFYOFE3o.js +0 -1
- package/dist/web/_app/immutable/chunks/DHJAqtPb.js +0 -1
- package/dist/web/_app/immutable/chunks/DOqcDZIJ.js +0 -1
- package/dist/web/_app/immutable/chunks/DQNtFKv6.js +0 -1
- package/dist/web/_app/immutable/chunks/DZHWps9n.js +0 -1
- package/dist/web/_app/immutable/chunks/DjLrjK2H.js +0 -1
- package/dist/web/_app/immutable/chunks/DjvJRtva.js +0 -1
- package/dist/web/_app/immutable/chunks/DnL9f0lc.js +0 -7
- package/dist/web/_app/immutable/chunks/Dqq8gyOz.js +0 -1
- package/dist/web/_app/immutable/chunks/DsnmJJEf.js +0 -1
- package/dist/web/_app/immutable/chunks/DtG1tx2A.js +0 -1
- package/dist/web/_app/immutable/chunks/Dxmmz-Kc.js +0 -1
- package/dist/web/_app/immutable/chunks/DzVOENkg.js +0 -1
- package/dist/web/_app/immutable/chunks/Ee5s0qSS.js +0 -5
- package/dist/web/_app/immutable/chunks/KlRV1tKP.js +0 -1
- package/dist/web/_app/immutable/chunks/L7dfS1-w.js +0 -1
- package/dist/web/_app/immutable/chunks/MaQouOBH.js +0 -2
- package/dist/web/_app/immutable/chunks/PPVm8Dsz.js +0 -1
- package/dist/web/_app/immutable/chunks/WSUKABI_.js +0 -1
- package/dist/web/_app/immutable/chunks/_7ZfIgbM.js +0 -1
- package/dist/web/_app/immutable/chunks/pNJmbo_B.js +0 -1
- package/dist/web/_app/immutable/chunks/q4I7EnCp.js +0 -1
- package/dist/web/_app/immutable/chunks/qE6Hm01v.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/chunks/wZWzupWT.js +0 -1
- package/dist/web/_app/immutable/entry/app.Dc6h105F.js +0 -2
- package/dist/web/_app/immutable/entry/start.D4codAU1.js +0 -1
- package/dist/web/_app/immutable/nodes/0.BM3j5xaK.js +0 -2
- package/dist/web/_app/immutable/nodes/1.CZ2dT63B.js +0 -1
- package/dist/web/_app/immutable/nodes/10.AJJtsnPU.js +0 -1
- package/dist/web/_app/immutable/nodes/11.B7SEgGLF.js +0 -1
- package/dist/web/_app/immutable/nodes/12.BcJKl4UQ.js +0 -1
- package/dist/web/_app/immutable/nodes/2.jp27tyTq.js +0 -269
- package/dist/web/_app/immutable/nodes/3.I7SH_06w.js +0 -5
- package/dist/web/_app/immutable/nodes/4.Bdphc-UJ.js +0 -11
- package/dist/web/_app/immutable/nodes/5.CXpOErnP.js +0 -1
- package/dist/web/_app/immutable/nodes/6.1wcSxmEA.js +0 -1
- package/dist/web/_app/immutable/nodes/7.D0b7WYTp.js +0 -1
- package/dist/web/_app/immutable/nodes/8.D0Gxvy3F.js +0 -1
- package/dist/web/_app/immutable/nodes/9.B5Nr92OR.js +0 -1
|
@@ -2,132 +2,65 @@
|
|
|
2
2
|
* Graph API routes
|
|
3
3
|
*
|
|
4
4
|
* REST façade over WorkspaceManager.
|
|
5
|
-
* Enforces per-
|
|
5
|
+
* Enforces per-folder access when users are authenticated via JWT.
|
|
6
6
|
* API key / open mode → full access (no filtering).
|
|
7
7
|
*/
|
|
8
|
+
import { isArchivedRepo } from '../../core/types.js';
|
|
8
9
|
import { isWorkspaceOwner } from '../../services/auth-service.js';
|
|
10
|
+
import { WorkspaceAccessService } from '../../services/workspace-access.js';
|
|
9
11
|
import { Workspace } from '../../core/workspace.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const user = req.user;
|
|
39
|
-
if (!user)
|
|
40
|
-
return true; // API key / open mode → admin-equivalent
|
|
41
|
-
if (authService.isFolderAdmin(user.id, repoName))
|
|
42
|
-
return true;
|
|
43
|
-
return isWorkspaceOwner(user.role);
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Build a set of all entity IDs visible to the current user.
|
|
47
|
-
* Used to filter wikilinks so users don't see links to entities
|
|
48
|
-
* in collections they can't access.
|
|
49
|
-
*/
|
|
50
|
-
function getAccessibleEntityIds(req, workspaceManager, authService) {
|
|
51
|
-
const user = req.user;
|
|
52
|
-
if (!user)
|
|
53
|
-
return null; // API key / open mode → no filtering
|
|
54
|
-
const repos = getAccessibleRepos(req, workspaceManager, authService);
|
|
55
|
-
const ids = new Set();
|
|
56
|
-
for (const repo of repos) {
|
|
57
|
-
try {
|
|
58
|
-
const graph = workspaceManager.getGraph(repo.name);
|
|
59
|
-
for (const e of graph.search({})) {
|
|
60
|
-
ids.add(e.id);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
catch { /* skip */ }
|
|
64
|
-
}
|
|
65
|
-
return ids;
|
|
66
|
-
}
|
|
67
|
-
/** Filter wikilinks to only those pointing to accessible entities. */
|
|
68
|
-
function filterWikilinks(wikilinks, accessibleIds) {
|
|
69
|
-
if (!accessibleIds)
|
|
70
|
-
return wikilinks;
|
|
71
|
-
return wikilinks.filter(id => accessibleIds.has(id));
|
|
72
|
-
}
|
|
73
|
-
/**
|
|
74
|
-
* Guard against non-admin members creating eager skills in shared folders —
|
|
75
|
-
* they would bloat the system prompt for everyone with folder access.
|
|
76
|
-
* Returns an error string or null if the operation is allowed.
|
|
77
|
-
*/
|
|
78
|
-
function checkEagerSkillPermission(req, entityType, data, repoName, workspaceManager, authService) {
|
|
79
|
-
if (entityType !== 'skill')
|
|
80
|
-
return null;
|
|
81
|
-
if (!data || data.loading !== 'eager')
|
|
82
|
-
return null;
|
|
83
|
-
const user = req.user;
|
|
84
|
-
if (!user)
|
|
85
|
-
return null; // API key / open mode
|
|
86
|
-
const privateRepo = workspaceManager.getUserPrivateRepo(user.id);
|
|
87
|
-
if (privateRepo && privateRepo.name === repoName)
|
|
88
|
-
return null;
|
|
89
|
-
if (isFolderAdminOrOwner(req, repoName, authService))
|
|
90
|
-
return null;
|
|
91
|
-
return 'Creating an eager skill in a shared folder requires folder admin or workspace owner. Save this skill to your private folder or set loading to "on-demand".';
|
|
92
|
-
}
|
|
93
|
-
export async function registerGraphApiRoutes(fastify, workspaceManager, authService, wsHub, sessionManager, yjsManager) {
|
|
12
|
+
import { seedFolderName, seedFolderUserId, resolveFolderDisplayName } from '../../core/seed-folder.js';
|
|
13
|
+
import { createSharedFolder, FolderCreationError } from '../../services/folder-creation.js';
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
|
|
15
|
+
import { basename, dirname, join } from 'path';
|
|
16
|
+
import { RESERVED_ENTITY_TYPES } from '../../core/validation.js';
|
|
17
|
+
import { validateFolderPath } from '../../core/folder-paths.js';
|
|
18
|
+
import { deriveAssetDisplayName, stripFrontmatter } from '../../core/embeds.js';
|
|
19
|
+
import { parseFrontmatterCacheFree } from '../../core/history-diff.js';
|
|
20
|
+
import { StaleBaseRevisionError } from '../../core/graph.js';
|
|
21
|
+
import { checkEagerSkillPermission, filterWikilinks, getAccessibleEntityIds, getAccessibleRepos, getContentRepos, graphForAdmin as resolveGraphForAdmin, graphForRead as resolveGraphForRead, graphForWrite as resolveGraphForWrite, hasRepoAccess, isFolderAdminOrOwner, reqGitUser, reqUser, } from './graph-api-access.js';
|
|
22
|
+
import { buildFontSkill, isFontAssetRow } from '../../services/fonts/font-skill-service.js';
|
|
23
|
+
import { BridgeError, assertCanChangeUserHandle, validateUserHandleInPayload } from '../../services/user-person-bridge.js';
|
|
24
|
+
import { assertEntityRefs, EntityRefError, UnknownUserRefError } from '../../services/entity-mutations.js';
|
|
25
|
+
import { assetDeleteDenial } from '../../services/asset-delete-authz.js';
|
|
26
|
+
import { log } from '../../utils/log.js';
|
|
27
|
+
export async function registerGraphApiRoutes(fastify, workspaceManager, authService, wsHub, sessionManager) {
|
|
28
|
+
// ARCHITECTURAL BOUNDARY: every per-route graph resolution flows through
|
|
29
|
+
// this service so the act of getting a graph IS the access check. Do not
|
|
30
|
+
// call `workspaceManager.getGraph(repo)` from a request-scoped handler —
|
|
31
|
+
// use `accessService.graphFor*(reqUser(req), repo)` instead. A security
|
|
32
|
+
// audit (2026-05) flagged the previous opt-in pattern (route had to
|
|
33
|
+
// remember `hasRepoAccess()` before `getGraph()`) as an architectural
|
|
34
|
+
// risk; if you find yourself reaching for the raw WM here, that's the
|
|
35
|
+
// smell the audit was about.
|
|
36
|
+
const accessService = new WorkspaceAccessService(workspaceManager, authService);
|
|
37
|
+
const graphForRead = (req, reply, repoName) => resolveGraphForRead(accessService, req, reply, repoName);
|
|
38
|
+
const graphForWrite = (req, reply, repoName) => resolveGraphForWrite(accessService, req, reply, repoName);
|
|
39
|
+
const graphForAdmin = (req, reply, repoName) => resolveGraphForAdmin(accessService, req, reply, repoName);
|
|
94
40
|
// POST /api/repos — any authenticated user can create a folder.
|
|
95
41
|
// The creator is granted folder-admin access automatically.
|
|
42
|
+
// Shared logic lives in services/folder-creation.ts so the MCP tool
|
|
43
|
+
// create_repo behaves identically.
|
|
96
44
|
fastify.post('/api/repos', async (req, reply) => {
|
|
97
45
|
const user = req.user;
|
|
98
46
|
const { name, display_name, description } = req.body || {};
|
|
99
|
-
if (!name || !/^[a-z0-9][a-z0-9-]*$/.test(name)) {
|
|
100
|
-
return reply.status(400).send({ error: 'Folder name must be kebab-case' });
|
|
101
|
-
}
|
|
102
47
|
try {
|
|
103
|
-
const
|
|
104
|
-
// Folders created here are shared team folders. Every user already
|
|
105
|
-
// has one auto-provisioned private "My Entries" folder (see
|
|
106
|
-
// ensurePrivateCollection in auth-api.ts) — passing private=true
|
|
107
|
-
// here would collide with the one-private-per-user rule.
|
|
108
|
-
const repo = await workspace.registerRepo({
|
|
109
|
-
name,
|
|
110
|
-
display_name: display_name || undefined,
|
|
111
|
-
description,
|
|
112
|
-
owner_id: user?.id,
|
|
113
|
-
});
|
|
114
|
-
workspaceManager.reloadConfig();
|
|
115
|
-
if (user)
|
|
116
|
-
authService.grantFolderAccess(user.id, name, 'admin');
|
|
117
|
-
wsHub?.broadcast({
|
|
118
|
-
type: 'collection_created', repo: name,
|
|
119
|
-
actor: user?.displayName ?? 'API',
|
|
120
|
-
timestamp: new Date().toISOString(),
|
|
121
|
-
});
|
|
48
|
+
const repo = await createSharedFolder(workspaceManager, authService, { name, display_name, description }, user, wsHub);
|
|
122
49
|
return reply.status(201).send(repo);
|
|
123
50
|
}
|
|
124
51
|
catch (err) {
|
|
125
|
-
|
|
52
|
+
if (err instanceof FolderCreationError) {
|
|
53
|
+
const status = err.code === 'invalid_name' ? 400
|
|
54
|
+
: err.code === 'duplicate' ? 409
|
|
55
|
+
: 500;
|
|
56
|
+
return reply.status(status).send({ error: err.message });
|
|
57
|
+
}
|
|
58
|
+
return reply.status(500).send({ error: err?.message ?? String(err) });
|
|
126
59
|
}
|
|
127
60
|
});
|
|
128
61
|
// DELETE /api/repos/:repo — folder admin or workspace owner only.
|
|
129
62
|
fastify.delete('/api/repos/:repo', async (req, reply) => {
|
|
130
|
-
if (!isFolderAdminOrOwner(req, req.params.repo, authService)) {
|
|
63
|
+
if (!isFolderAdminOrOwner(req, req.params.repo, workspaceManager, authService)) {
|
|
131
64
|
return reply.status(403).send({ error: 'Folder admin access required' });
|
|
132
65
|
}
|
|
133
66
|
const { repo } = req.params;
|
|
@@ -139,11 +72,63 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
139
72
|
return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
|
|
140
73
|
}
|
|
141
74
|
const repoConfig = config.repos[repoIndex];
|
|
75
|
+
// System folders (the reserved Workspace Assets store) are an app
|
|
76
|
+
// invariant — undeletable, so nothing needs to recreate them on boot.
|
|
77
|
+
if (repoConfig.system) {
|
|
78
|
+
return reply.status(403).send({ error: 'This is a system folder and cannot be deleted.' });
|
|
79
|
+
}
|
|
142
80
|
const repoPath = join(workspaceManager['workspacePath'], repoConfig.path);
|
|
81
|
+
// Reclaim the repo's assets (assets.db rows + blobs) BEFORE destroying
|
|
82
|
+
// the repo dir. Assets are workspace-global — the index lives in
|
|
83
|
+
// .studiograph/assets.db and blobs live outside the repo dir (R2 bucket,
|
|
84
|
+
// or .studiograph/assets/{id}/ locally) — so the rmSync below does NOT
|
|
85
|
+
// reclaim them. Skipping this leaves orphan assets.db rows with a dead
|
|
86
|
+
// repo reference (the Meld-class bug) and orphan blobs. deleteAssetById
|
|
87
|
+
// deletes the blob + index row + caption state and is self-isolating
|
|
88
|
+
// (never throws), so a failed blob delete still drops the index row.
|
|
89
|
+
const assetIndex = workspaceManager.getAssetIndex();
|
|
90
|
+
try {
|
|
91
|
+
const graph = workspaceManager.getGraph(repo);
|
|
92
|
+
for (const row of assetIndex.listByRepo(repo)) {
|
|
93
|
+
// Per-row isolation: one bad blob delete must not abort the sweep
|
|
94
|
+
// and strand the rest. (deleteAssetById is already self-isolating,
|
|
95
|
+
// but a rejected await would otherwise break the loop.)
|
|
96
|
+
try {
|
|
97
|
+
await graph.deleteAssetById(row.assetId, row.filename);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
log.warn(`[repo-delete] blob ${row.assetId} for ${repo} failed: ${err?.message ?? err}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
log.warn(`[repo-delete] asset blob cleanup for ${repo} failed: ${err?.message ?? err}`);
|
|
106
|
+
}
|
|
107
|
+
// Drop the index rows UNCONDITIONALLY — even if the blob loop above
|
|
108
|
+
// threw (e.g. getGraph failed, or no asset backend is configured so
|
|
109
|
+
// deleteAssetById short-circuits), orphan index rows with a dead repo
|
|
110
|
+
// reference are exactly the Meld-class bug we're closing. Idempotent
|
|
111
|
+
// when the loop already removed them.
|
|
112
|
+
try {
|
|
113
|
+
assetIndex.deleteByRepo(repo);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
log.warn(`[repo-delete] asset index cleanup for ${repo} failed: ${err?.message ?? err}`);
|
|
117
|
+
}
|
|
143
118
|
// Remove from config
|
|
144
119
|
config.repos.splice(repoIndex, 1);
|
|
145
120
|
workspace.writeConfig(config);
|
|
146
|
-
workspace.pushWorkspaceConfig(`Remove
|
|
121
|
+
workspace.pushWorkspaceConfig(`Remove folder: ${repo}`);
|
|
122
|
+
// Durable seed deletion: if a user deletes THEIR OWN seed "My Folder",
|
|
123
|
+
// record a tombstone so the boot sweep + login provisioning don't
|
|
124
|
+
// resurrect it. Only the seed's owner deleting it counts — when a folder
|
|
125
|
+
// admin/workspace owner deletes someone else's (e.g. shared) seed, we do
|
|
126
|
+
// NOT tombstone, so that user's home re-provisions on their next login
|
|
127
|
+
// rather than being permanently removed by another person's action.
|
|
128
|
+
const seedUserId = seedFolderUserId(repo);
|
|
129
|
+
if (seedUserId != null && seedUserId === reqUser(req)?.id) {
|
|
130
|
+
authService.setSeedFolderDeleted(seedUserId, true);
|
|
131
|
+
}
|
|
147
132
|
// Delete files
|
|
148
133
|
const { rmSync } = await import('fs');
|
|
149
134
|
if (existsSync(repoPath)) {
|
|
@@ -151,7 +136,7 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
151
136
|
}
|
|
152
137
|
workspaceManager.reloadConfig();
|
|
153
138
|
wsHub?.broadcast({
|
|
154
|
-
type: '
|
|
139
|
+
type: 'folder_deleted', repo,
|
|
155
140
|
actor: req.user?.displayName ?? 'API',
|
|
156
141
|
timestamp: new Date().toISOString(),
|
|
157
142
|
});
|
|
@@ -161,17 +146,82 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
161
146
|
return reply.status(500).send({ error: err.message });
|
|
162
147
|
}
|
|
163
148
|
});
|
|
149
|
+
// POST /api/repos/:repo/archive — reversible repo-level hide. Folder admin or
|
|
150
|
+
// workspace owner only. Unlike DELETE (permanent, human/web-only), archive is
|
|
151
|
+
// lossless + identity-preserving and is also exposed to agents/MCP. The
|
|
152
|
+
// archived folder drops out of the sidebar + knowledge surfaces on the next
|
|
153
|
+
// /api/repos fetch; the live-refresh + "Archived folders" management panel are
|
|
154
|
+
// a separate Tier-2 web PR.
|
|
155
|
+
fastify.post('/api/repos/:repo/archive', async (req, reply) => {
|
|
156
|
+
if (!isFolderAdminOrOwner(req, req.params.repo, workspaceManager, authService)) {
|
|
157
|
+
return reply.status(403).send({ error: 'Folder admin access required' });
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const updated = await workspaceManager.setRepoArchived(req.params.repo, true, reqUser(req)?.handle);
|
|
161
|
+
return reply.send({ success: true, repo: updated });
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
const status = err?.message?.includes('not found') ? 404
|
|
165
|
+
: err?.message?.includes('System folders') ? 403
|
|
166
|
+
: 500;
|
|
167
|
+
return reply.status(status).send({ error: err.message });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
// POST /api/repos/:repo/unarchive — restore an archived folder. Same authz as
|
|
171
|
+
// archive (archived folders keep their folder_access rows, so folder-admin
|
|
172
|
+
// still resolves).
|
|
173
|
+
fastify.post('/api/repos/:repo/unarchive', async (req, reply) => {
|
|
174
|
+
if (!isFolderAdminOrOwner(req, req.params.repo, workspaceManager, authService)) {
|
|
175
|
+
return reply.status(403).send({ error: 'Folder admin access required' });
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const updated = await workspaceManager.setRepoArchived(req.params.repo, false, reqUser(req)?.handle);
|
|
179
|
+
return reply.send({ success: true, repo: updated });
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
// Map the system-folder rejection to 403 consistently with /archive
|
|
183
|
+
// (setRepoArchived guards `system` before the idempotency no-op, so even
|
|
184
|
+
// an unarchive on a system folder surfaces it).
|
|
185
|
+
const status = err?.message?.includes('not found') ? 404
|
|
186
|
+
: err?.message?.includes('System folders') ? 403
|
|
187
|
+
: 500;
|
|
188
|
+
return reply.status(status).send({ error: err.message });
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
// GET /api/repos/archived — list archived folders the caller can manage.
|
|
192
|
+
// Archived folders are excluded from /api/repos (getContentRepos), so this is
|
|
193
|
+
// the ONLY way the web Settings UI can see them to restore / permanently
|
|
194
|
+
// delete. Visibility mirrors who can unarchive: folder-admin or workspace
|
|
195
|
+
// owner on each folder (isFolderAdminOrOwner) — not just any member. Returns
|
|
196
|
+
// the archive metadata + member count + caller role for the Settings rows.
|
|
197
|
+
fastify.get('/api/repos/archived', async (req, reply) => {
|
|
198
|
+
workspaceManager.reloadConfig();
|
|
199
|
+
const user = reqUser(req);
|
|
200
|
+
const archived = workspaceManager.getAllRepoConfigs()
|
|
201
|
+
.filter(r => isArchivedRepo(r) && isFolderAdminOrOwner(req, r.name, workspaceManager, authService));
|
|
202
|
+
const enriched = archived.map(r => ({
|
|
203
|
+
name: r.name,
|
|
204
|
+
// Null when there's no explicit display name — the client applies the
|
|
205
|
+
// same title-cased-slug fallback the active folder cards use.
|
|
206
|
+
display_name: r.display_name ?? null,
|
|
207
|
+
description: r.description,
|
|
208
|
+
archived_at: r.archived_at ?? null,
|
|
209
|
+
archived_by: r.archived_by ?? null,
|
|
210
|
+
member_count: authService.getFolderMembers(r.name).length,
|
|
211
|
+
caller_role: user ? authService.getFolderRole(user.id, r.name) : null,
|
|
212
|
+
}));
|
|
213
|
+
return reply.send(enriched);
|
|
214
|
+
});
|
|
164
215
|
// PATCH /api/repos/:repo — rename/update sync rules. Folder admin or workspace owner only.
|
|
165
216
|
fastify.patch('/api/repos/:repo', async (req, reply) => {
|
|
166
|
-
if (!isFolderAdminOrOwner(req, req.params.repo, authService)) {
|
|
217
|
+
if (!isFolderAdminOrOwner(req, req.params.repo, workspaceManager, authService)) {
|
|
167
218
|
return reply.status(403).send({ error: 'Folder admin access required' });
|
|
168
219
|
}
|
|
169
220
|
const { repo } = req.params;
|
|
170
221
|
const body = req.body || {};
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
222
|
+
// Only name/display_name/sync are applied; any other field (incl. the
|
|
223
|
+
// deprecated/inert `personal`) is ignored.
|
|
224
|
+
const { name: newName, display_name: newDisplayName, sync: newSync } = body;
|
|
175
225
|
if (newName === undefined && newSync === undefined && newDisplayName === undefined) {
|
|
176
226
|
return reply.status(400).send({ error: 'Nothing to update' });
|
|
177
227
|
}
|
|
@@ -185,6 +235,11 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
185
235
|
if (!repoConfig) {
|
|
186
236
|
return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
|
|
187
237
|
}
|
|
238
|
+
// System folders (the reserved Workspace Assets store) have a reserved
|
|
239
|
+
// name + fixed display; renaming/reconfiguring them is not allowed.
|
|
240
|
+
if (repoConfig.system) {
|
|
241
|
+
return reply.status(403).send({ error: 'This is a system folder and cannot be renamed or reconfigured.' });
|
|
242
|
+
}
|
|
188
243
|
// Handle rename
|
|
189
244
|
if (newName !== undefined) {
|
|
190
245
|
if (config.repos.some(r => r.name === newName)) {
|
|
@@ -207,7 +262,7 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
207
262
|
if (existsSync(markerPath)) {
|
|
208
263
|
writeFS(markerPath, JSON.stringify({ repo_name: newName }, null, 2), 'utf-8');
|
|
209
264
|
}
|
|
210
|
-
// Update
|
|
265
|
+
// Update folder_access to follow the rename
|
|
211
266
|
authService.renameFolder(repo, newName);
|
|
212
267
|
}
|
|
213
268
|
// Handle display name update (without rename)
|
|
@@ -219,7 +274,7 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
219
274
|
repoConfig.sync = newSync.length > 0 ? newSync : undefined;
|
|
220
275
|
}
|
|
221
276
|
workspace.writeConfig(config);
|
|
222
|
-
workspace.pushWorkspaceConfig(newName !== undefined ? `Rename
|
|
277
|
+
workspace.pushWorkspaceConfig(newName !== undefined ? `Rename folder: ${repo} → ${newName}` : `Update folder: ${repo}`);
|
|
223
278
|
workspaceManager.reloadConfig();
|
|
224
279
|
return reply.send(repoConfig);
|
|
225
280
|
}
|
|
@@ -229,22 +284,57 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
229
284
|
});
|
|
230
285
|
// GET /api/repos — list accessible repos (re-reads config to pick up CLI changes).
|
|
231
286
|
// Augments each repo with the caller's folder role ('admin' | 'editor'),
|
|
232
|
-
// member_count,
|
|
233
|
-
//
|
|
234
|
-
//
|
|
287
|
+
// member_count, entity_types (distinct types present), and folders (the
|
|
288
|
+
// flat list of nested-folder paths in the repo, used by the sidebar to
|
|
289
|
+
// render the active-path expansion).
|
|
235
290
|
fastify.get('/api/repos', async (req, reply) => {
|
|
236
291
|
workspaceManager.reloadConfig();
|
|
237
|
-
|
|
292
|
+
// System folders (the reserved Workspace Assets store) are accessible but
|
|
293
|
+
// excluded from the sidebar — they're surfaced via the asset manager, not
|
|
294
|
+
// the folder list. They stay in getAccessibleRepos so authz + the asset
|
|
295
|
+
// manager (/api/workspace/assets) still see them.
|
|
296
|
+
const repos = getContentRepos(req, workspaceManager, authService);
|
|
238
297
|
const user = req.user;
|
|
298
|
+
const users = authService.listUsers();
|
|
239
299
|
const enriched = repos.map(r => {
|
|
240
300
|
const memberCount = authService.getFolderMembers(r.name).length;
|
|
241
301
|
const callerRole = user ? authService.getFolderRole(user.id, r.name) : null;
|
|
242
302
|
let entityTypes = [];
|
|
303
|
+
// `folders` carries the per-folder display_name override (when one
|
|
304
|
+
// exists in the folder's .studiograph-folder.json sidecar); the client
|
|
305
|
+
// falls back to toDisplayName(slug) when display_name is absent.
|
|
306
|
+
let folders = [];
|
|
243
307
|
try {
|
|
244
|
-
|
|
308
|
+
// Direct WM access OK here — `repos` came from getAccessibleRepos
|
|
309
|
+
// above, so every iteration is over a folder the caller can read.
|
|
310
|
+
const graph = workspaceManager.getGraph(r.name);
|
|
311
|
+
entityTypes = graph.listEntityTypes();
|
|
312
|
+
folders = graph.listFolders().map(f => {
|
|
313
|
+
const entry = { path: f.path };
|
|
314
|
+
if (f.display_name)
|
|
315
|
+
entry.display_name = f.display_name;
|
|
316
|
+
if (f.id)
|
|
317
|
+
entry.id = f.id;
|
|
318
|
+
return entry;
|
|
319
|
+
});
|
|
245
320
|
}
|
|
246
321
|
catch { /* repo not mounted */ }
|
|
247
|
-
return {
|
|
322
|
+
return {
|
|
323
|
+
...r,
|
|
324
|
+
// Seed folders read as their owner's "{name}'s Folder" for everyone
|
|
325
|
+
// (incl. the owner); every other folder keeps its stored display_name.
|
|
326
|
+
display_name: resolveFolderDisplayName(r, users),
|
|
327
|
+
caller_role: callerRole,
|
|
328
|
+
member_count: memberCount,
|
|
329
|
+
// True only for THE CALLER's own seed "My Folder" (resolvable as
|
|
330
|
+
// repo:"private") — not just any entries-<id> folder, so a peer's seed
|
|
331
|
+
// shared with the caller is never mistaken for the caller's home (the
|
|
332
|
+
// capture default keys off this). "Solo/private" is derived separately
|
|
333
|
+
// on the client from member_count === 1.
|
|
334
|
+
is_seed: !!user && r.name === seedFolderName(user.id),
|
|
335
|
+
entity_types: entityTypes,
|
|
336
|
+
folders,
|
|
337
|
+
};
|
|
248
338
|
});
|
|
249
339
|
return reply.send(enriched);
|
|
250
340
|
});
|
|
@@ -258,6 +348,7 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
258
348
|
}
|
|
259
349
|
workspaceManager.reloadConfig();
|
|
260
350
|
const all = workspaceManager.getAllRepoConfigs();
|
|
351
|
+
const users = authService.listUsers();
|
|
261
352
|
const result = all.map(r => {
|
|
262
353
|
const members = authService.getFolderMembers(r.name);
|
|
263
354
|
const admins = members.filter(m => m.role === 'admin');
|
|
@@ -265,13 +356,16 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
265
356
|
? members.find(m => m.user.id === r.owner_id)?.user
|
|
266
357
|
: admins[0]?.user;
|
|
267
358
|
let entryCount = 0;
|
|
359
|
+
// Direct WM access OK — this endpoint is owner-only (gated above) and
|
|
360
|
+
// returns metadata for every folder the workspace owner needs to see
|
|
361
|
+
// for admin purposes. No content is leaked: only entry counts.
|
|
268
362
|
try {
|
|
269
363
|
entryCount = workspaceManager.getGraph(r.name).search({}).length;
|
|
270
364
|
}
|
|
271
365
|
catch { /* skip */ }
|
|
272
366
|
return {
|
|
273
367
|
name: r.name,
|
|
274
|
-
display_name: r
|
|
368
|
+
display_name: resolveFolderDisplayName(r, users) ?? r.name,
|
|
275
369
|
description: r.description,
|
|
276
370
|
member_count: members.length,
|
|
277
371
|
admin_count: admins.length,
|
|
@@ -282,44 +376,45 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
282
376
|
});
|
|
283
377
|
return reply.send(result);
|
|
284
378
|
});
|
|
379
|
+
// Build the lightweight summary objects used by /entities?summary=true.
|
|
380
|
+
// Extracted so the per-repo and bulk endpoints share the same shape.
|
|
381
|
+
const EXCLUDED_SUMMARY_FIELDS = new Set([
|
|
382
|
+
'entity_type', 'entity_id', 'created_by', 'updated_by',
|
|
383
|
+
'description', 'content', 'context', 'rationale', 'alternatives', 'consequences',
|
|
384
|
+
'steps', 'guidelines', 'bio', 'agenda', 'summary', 'decision', 'action_items',
|
|
385
|
+
]);
|
|
386
|
+
function buildSummaries(entities, accessibleIds) {
|
|
387
|
+
return entities.map(e => {
|
|
388
|
+
const extra = Object.fromEntries(Object.entries(e.data ?? {}).filter(([k, v]) => !EXCLUDED_SUMMARY_FIELDS.has(k) && k !== 'name' && k !== 'created_at' && k !== 'updated_at' &&
|
|
389
|
+
(typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || Array.isArray(v) || v instanceof Date)));
|
|
390
|
+
return {
|
|
391
|
+
id: e.id,
|
|
392
|
+
entityType: e.entityType,
|
|
393
|
+
folder_path: e.folder_path ?? '',
|
|
394
|
+
data: {
|
|
395
|
+
name: e.data.name,
|
|
396
|
+
created_at: e.data.created_at,
|
|
397
|
+
updated_at: e.data.updated_at,
|
|
398
|
+
...extra,
|
|
399
|
+
},
|
|
400
|
+
document: { wikilinks: filterWikilinks(e.document?.wikilinks ?? [], accessibleIds) },
|
|
401
|
+
};
|
|
402
|
+
});
|
|
403
|
+
}
|
|
285
404
|
// GET /api/repos/:repo/entities — list all entities in a repo
|
|
286
405
|
// ?summary=true returns lightweight objects (no content, minimal data) for sidebar/graph
|
|
287
406
|
fastify.get('/api/repos/:repo/entities', async (req, reply) => {
|
|
288
407
|
const { repo } = req.params;
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
296
|
-
catch (err) {
|
|
297
|
-
return reply.status(404).send({ error: err.message });
|
|
298
|
-
}
|
|
408
|
+
// Access enforced by graphForRead \u2014 do not call workspaceManager.getGraph
|
|
409
|
+
// directly here. The audit (2026-05) flagged direct WM access from routes
|
|
410
|
+
// as the primary architectural risk; the service is the gate.
|
|
411
|
+
const graph = graphForRead(req, reply, repo);
|
|
412
|
+
if (!graph)
|
|
413
|
+
return;
|
|
299
414
|
const entities = graph.search({});
|
|
300
415
|
const accessibleIds = getAccessibleEntityIds(req, workspaceManager, authService);
|
|
301
416
|
if (req.query.summary === 'true') {
|
|
302
|
-
|
|
303
|
-
'entity_type', 'entity_id', 'created_by', 'updated_by',
|
|
304
|
-
'description', 'content', 'context', 'rationale', 'alternatives', 'consequences',
|
|
305
|
-
'steps', 'guidelines', 'bio', 'agenda', 'summary', 'decision', 'action_items',
|
|
306
|
-
]);
|
|
307
|
-
const summaries = entities.map(e => {
|
|
308
|
-
const extra = Object.fromEntries(Object.entries(e.data ?? {}).filter(([k, v]) => !EXCLUDED_SUMMARY_FIELDS.has(k) && k !== 'name' && k !== 'created_at' && k !== 'updated_at' &&
|
|
309
|
-
(typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean' || Array.isArray(v) || v instanceof Date)));
|
|
310
|
-
return {
|
|
311
|
-
id: e.id,
|
|
312
|
-
entityType: e.entityType,
|
|
313
|
-
data: {
|
|
314
|
-
name: e.data.name,
|
|
315
|
-
created_at: e.data.created_at,
|
|
316
|
-
updated_at: e.data.updated_at,
|
|
317
|
-
...extra,
|
|
318
|
-
},
|
|
319
|
-
document: { wikilinks: filterWikilinks(e.document?.wikilinks ?? [], accessibleIds) },
|
|
320
|
-
};
|
|
321
|
-
});
|
|
322
|
-
return reply.send(summaries);
|
|
417
|
+
return reply.send(buildSummaries(entities, accessibleIds));
|
|
323
418
|
}
|
|
324
419
|
// Full response — filter wikilinks in document
|
|
325
420
|
const filtered = entities.map(e => {
|
|
@@ -329,19 +424,57 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
329
424
|
});
|
|
330
425
|
return reply.send(filtered);
|
|
331
426
|
});
|
|
427
|
+
// GET /api/workspace/entities?summary=true — bulk entity summaries for ALL
|
|
428
|
+
// accessible repos in a single round-trip. Replaces 15+ parallel
|
|
429
|
+
// /api/repos/:repo/entities?summary=true calls. Important on dev where each
|
|
430
|
+
// request has Vite middleware overhead and the browser's HTTP/1.1 6-slot
|
|
431
|
+
// limit serializes them, blocking other navigation fetches.
|
|
432
|
+
// Response shape: `{ [repoName]: EntitySummary[] }`.
|
|
433
|
+
fastify.get('/api/workspace/entities', async (req, reply) => {
|
|
434
|
+
// Mirror the per-repo endpoint's contract: only summary=true is supported
|
|
435
|
+
// here. Returning full bodies for the whole workspace would be huge and
|
|
436
|
+
// isn't a use case we have today.
|
|
437
|
+
if (req.query.summary !== 'true') {
|
|
438
|
+
return reply.status(400).send({ error: 'summary=true is required for the bulk entities endpoint' });
|
|
439
|
+
}
|
|
440
|
+
const repos = getContentRepos(req, workspaceManager, authService);
|
|
441
|
+
// Single-pass: collect the per-repo entity arrays AND the accessible-ids
|
|
442
|
+
// set in one walk. The previous shape called graph.search({}) twice per
|
|
443
|
+
// repo (once via getAccessibleEntityIds, once in this loop), doubling
|
|
444
|
+
// disk I/O on the most-trafficked endpoint.
|
|
445
|
+
const user = reqUser(req);
|
|
446
|
+
const collectIds = user !== undefined; // unauthenticated → no wikilink filtering
|
|
447
|
+
const accessibleIds = collectIds ? new Set() : null;
|
|
448
|
+
const perRepoEntities = [];
|
|
449
|
+
for (const r of repos) {
|
|
450
|
+
let graph;
|
|
451
|
+
// Direct WM access OK — `repos` is the access-filtered list above.
|
|
452
|
+
try {
|
|
453
|
+
graph = workspaceManager.getGraph(r.name);
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
continue;
|
|
457
|
+
} // skip unmounted repos rather than failing the whole request
|
|
458
|
+
const entities = graph.search({});
|
|
459
|
+
perRepoEntities.push({ name: r.name, entities });
|
|
460
|
+
if (accessibleIds) {
|
|
461
|
+
for (const e of entities)
|
|
462
|
+
accessibleIds.add(e.id);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
const result = {};
|
|
466
|
+
for (const { name, entities } of perRepoEntities) {
|
|
467
|
+
result[name] = buildSummaries(entities, accessibleIds);
|
|
468
|
+
}
|
|
469
|
+
return reply.send(result);
|
|
470
|
+
});
|
|
332
471
|
// GET /api/repos/:repo/entities/:type — list entities of a type
|
|
333
472
|
fastify.get('/api/repos/:repo/entities/:type', async (req, reply) => {
|
|
334
473
|
const { repo, type } = req.params;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
try {
|
|
340
|
-
graph = workspaceManager.getGraph(repo);
|
|
341
|
-
}
|
|
342
|
-
catch (err) {
|
|
343
|
-
return reply.status(404).send({ error: err.message });
|
|
344
|
-
}
|
|
474
|
+
// Access enforced by graphForRead (WorkspaceAccessService).
|
|
475
|
+
const graph = graphForRead(req, reply, repo);
|
|
476
|
+
if (!graph)
|
|
477
|
+
return;
|
|
345
478
|
const entities = graph.search({ entityType: type });
|
|
346
479
|
const accessibleIds = getAccessibleEntityIds(req, workspaceManager, authService);
|
|
347
480
|
const filtered = entities.map(e => {
|
|
@@ -354,16 +487,10 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
354
487
|
// GET /api/repos/:repo/entities/:type/:id — get single entity
|
|
355
488
|
fastify.get('/api/repos/:repo/entities/:type/:id', async (req, reply) => {
|
|
356
489
|
const { repo, type, id } = req.params;
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
try {
|
|
362
|
-
graph = workspaceManager.getGraph(repo);
|
|
363
|
-
}
|
|
364
|
-
catch (err) {
|
|
365
|
-
return reply.status(404).send({ error: err.message });
|
|
366
|
-
}
|
|
490
|
+
// Access enforced by graphForRead (WorkspaceAccessService).
|
|
491
|
+
const graph = graphForRead(req, reply, repo);
|
|
492
|
+
if (!graph)
|
|
493
|
+
return;
|
|
367
494
|
let entity;
|
|
368
495
|
try {
|
|
369
496
|
entity = graph.get(type, id);
|
|
@@ -371,6 +498,12 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
371
498
|
catch (err) {
|
|
372
499
|
return reply.status(404).send({ error: err.message ?? `Entity not found: ${type}/${id}` });
|
|
373
500
|
}
|
|
501
|
+
// graph.get may also return null for a missing entity without throwing
|
|
502
|
+
// (depends on the backing graph implementation). Treat that as 404 so
|
|
503
|
+
// we don't blow up on the wikilinks access below.
|
|
504
|
+
if (!entity) {
|
|
505
|
+
return reply.status(404).send({ error: `Entity not found: ${type}/${id}` });
|
|
506
|
+
}
|
|
374
507
|
if (entity.document?.wikilinks?.length) {
|
|
375
508
|
const accessibleIds = getAccessibleEntityIds(req, workspaceManager, authService);
|
|
376
509
|
return reply.send({
|
|
@@ -380,36 +513,185 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
380
513
|
}
|
|
381
514
|
return reply.send(entity);
|
|
382
515
|
});
|
|
516
|
+
// GET /api/repos/:repo/entities/:type/:id/source — download the raw
|
|
517
|
+
// markdown file (frontmatter + body verbatim, exactly as on disk). For
|
|
518
|
+
// artifacts the body IS the embedded bundle JSON, so the download is a
|
|
519
|
+
// portable single-file copy of the whole entry. Read-gated like every
|
|
520
|
+
// other entity read; serialization fidelity comes from serving the disk
|
|
521
|
+
// bytes rather than re-emitting parsed frontmatter.
|
|
522
|
+
fastify.get('/api/repos/:repo/entities/:type/:id/source', async (req, reply) => {
|
|
523
|
+
const { repo, type, id } = req.params;
|
|
524
|
+
// Access enforced by graphForRead (WorkspaceAccessService).
|
|
525
|
+
const graph = graphForRead(req, reply, repo);
|
|
526
|
+
if (!graph)
|
|
527
|
+
return;
|
|
528
|
+
const filePath = graph.getEntityFilePath(type, id);
|
|
529
|
+
if (!filePath || !existsSync(filePath)) {
|
|
530
|
+
return reply.status(404).send({ error: `Entity not found: ${type}/${id}` });
|
|
531
|
+
}
|
|
532
|
+
const raw = readFileSync(filePath);
|
|
533
|
+
// Name the download from the resolved on-disk basename, sanitized to a
|
|
534
|
+
// safe quoted-string token. Created/renamed ids are kebab-case slugs, but
|
|
535
|
+
// this route resolves EXISTING files via the index — a hand-authored/
|
|
536
|
+
// legacy/imported basename can carry characters that corrupt the header
|
|
537
|
+
// or, with CR/LF, allow header injection. Strip ALL ASCII control chars
|
|
538
|
+
// (Node's validateHeaderValue rejects NUL/VT/FF/DEL etc. → ERR_INVALID_CHAR
|
|
539
|
+
// at send time, not just CR/LF) plus the quoted-string delimiters
|
|
540
|
+
// (" \ ;). Fall back to a safe default if nothing usable remains.
|
|
541
|
+
const safeName = (basename(filePath) || `${id}.md`).replace(/[\x00-\x1f\x7f";\\]/g, '').trim() || `${id}.md`;
|
|
542
|
+
reply.header('Content-Disposition', `attachment; filename="${safeName}"`);
|
|
543
|
+
reply.header('Content-Type', 'text/markdown; charset=utf-8');
|
|
544
|
+
reply.header('X-Content-Type-Options', 'nosniff');
|
|
545
|
+
return reply.send(raw);
|
|
546
|
+
});
|
|
383
547
|
// GET /api/repos/:repo/entities/:type/:id/history — entity commit history
|
|
384
548
|
fastify.get('/api/repos/:repo/entities/:type/:id/history', async (req, reply) => {
|
|
385
549
|
const { repo, type, id } = req.params;
|
|
386
|
-
|
|
387
|
-
|
|
550
|
+
// Access enforced by graphForRead (WorkspaceAccessService).
|
|
551
|
+
const graph = graphForRead(req, reply, repo);
|
|
552
|
+
if (!graph)
|
|
553
|
+
return;
|
|
554
|
+
const gitService = graph.getGitService();
|
|
555
|
+
if (!gitService) {
|
|
556
|
+
return reply.send([]);
|
|
388
557
|
}
|
|
389
|
-
|
|
558
|
+
const limit = Math.min(parseInt(req.query.limit ?? '20', 10) || 20, 50);
|
|
559
|
+
// Resolve the actual on-disk path via graph.get() — it walks nested
|
|
560
|
+
// folders to find the file. The previous hardcoded `${plural}/${id}.md`
|
|
561
|
+
// missed any entry not at the legacy flat path (entries created in a
|
|
562
|
+
// sub-folder, MCP entries that landed elsewhere, etc.) and silently
|
|
563
|
+
// returned an empty history. Convert absolute path → repo-relative
|
|
564
|
+
// for git log.
|
|
565
|
+
let filePath;
|
|
390
566
|
try {
|
|
391
|
-
|
|
567
|
+
const entity = graph.get(type, id);
|
|
568
|
+
const repoRoot = graph.getRepoPath();
|
|
569
|
+
filePath = entity.path.startsWith(repoRoot + '/')
|
|
570
|
+
? entity.path.slice(repoRoot.length + 1)
|
|
571
|
+
: entity.path;
|
|
392
572
|
}
|
|
393
573
|
catch (err) {
|
|
394
574
|
return reply.status(404).send({ error: err.message });
|
|
395
575
|
}
|
|
396
|
-
const gitService = graph.getGitService();
|
|
397
|
-
if (!gitService) {
|
|
398
|
-
return reply.send([]);
|
|
399
|
-
}
|
|
400
|
-
const limit = Math.min(parseInt(req.query.limit ?? '20', 10) || 20, 50);
|
|
401
|
-
const { PLURAL_TO_SINGULAR } = await import('../../core/validation.js');
|
|
402
|
-
// Reverse lookup: singular type → plural folder name
|
|
403
|
-
const pluralDir = Object.entries(PLURAL_TO_SINGULAR).find(([, v]) => v === type)?.[0] ?? `${type}s`;
|
|
404
|
-
const filePath = `${pluralDir}/${id}.md`;
|
|
405
576
|
try {
|
|
406
577
|
const history = gitService.getFileHistory(filePath, limit);
|
|
578
|
+
// Opt-in per-commit change summary for the History panel. Off by
|
|
579
|
+
// default so the eager gating fetch on every entity-page load stays
|
|
580
|
+
// cheap; the panel requests it only when expanded.
|
|
581
|
+
if (req.query.changes === 'true') {
|
|
582
|
+
return reply.send(history.map((c) => ({ ...c, changes: graph.getCommitChange(filePath, c.hash) })));
|
|
583
|
+
}
|
|
407
584
|
return reply.send(history);
|
|
408
585
|
}
|
|
409
586
|
catch {
|
|
410
587
|
return reply.send([]);
|
|
411
588
|
}
|
|
412
589
|
});
|
|
590
|
+
// GET /api/repos/:repo/entities/:type/:id/at-commit/:hash — full body content
|
|
591
|
+
// of an entry as it was at a past commit, for the History "version preview"
|
|
592
|
+
// (read-only Live Preview render). Same read gate as /history.
|
|
593
|
+
//
|
|
594
|
+
// Reuses graph.getEntityAtCommit, which walks the commit tree by IDENTITY
|
|
595
|
+
// (entityType, entityId) — so a rename/move between then and now resolves to
|
|
596
|
+
// the right historical file without the route duplicating path-slicing, and a
|
|
597
|
+
// commit where the entry didn't yet exist at this identity resolves to null.
|
|
598
|
+
// null → typed 404 VERSION_NOT_AT_PATH so the UI can show an honest "this
|
|
599
|
+
// version isn't viewable here" message instead of a misleading generic error.
|
|
600
|
+
fastify.get('/api/repos/:repo/entities/:type/:id/at-commit/:hash', async (req, reply) => {
|
|
601
|
+
const { repo, type, id, hash } = req.params;
|
|
602
|
+
// Defense-in-depth: the hash always comes from our own history list, but
|
|
603
|
+
// validate the shape before it reaches git.
|
|
604
|
+
if (!/^[0-9a-f]{7,40}$/.test(hash)) {
|
|
605
|
+
return reply.status(400).send({ error: 'Invalid commit hash' });
|
|
606
|
+
}
|
|
607
|
+
// Access enforced by graphForRead (WorkspaceAccessService).
|
|
608
|
+
const graph = graphForRead(req, reply, repo);
|
|
609
|
+
if (!graph)
|
|
610
|
+
return;
|
|
611
|
+
const hit = graph.getEntityAtCommit(type, id, hash);
|
|
612
|
+
if (hit === null) {
|
|
613
|
+
return reply.status(404).send({ error: 'VERSION_NOT_AT_PATH' });
|
|
614
|
+
}
|
|
615
|
+
// Editor content is body-only (frontmatter stripped) — match it so the
|
|
616
|
+
// preview renders exactly what the live editor would. The parsed
|
|
617
|
+
// frontmatter rides along so the preview can also show the title + fields
|
|
618
|
+
// as they were at this commit. parseFrontmatterCacheFree (NOT the shared
|
|
619
|
+
// gray-matter MarkdownService) is required for historical content — the
|
|
620
|
+
// shared parser's module-level cache returns stale values for past commits.
|
|
621
|
+
return reply.send({
|
|
622
|
+
body: stripFrontmatter(hit.content),
|
|
623
|
+
frontmatter: parseFrontmatterCacheFree(hit.content),
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
// POST /api/repos/:repo/entities/:type/:id/restore — restore an entry to
|
|
627
|
+
// its content at a past commit. Single-entity scope. Editor-or-admin role
|
|
628
|
+
// required (canWriteRepo). Returns 200 with the restore result, 404 if
|
|
629
|
+
// the entry doesn't exist at that commit, 409 PATH_CONFLICT if a different
|
|
630
|
+
// entry now occupies the target path, 403 if the caller lacks write access.
|
|
631
|
+
fastify.post('/api/repos/:repo/entities/:type/:id/restore', async (req, reply) => {
|
|
632
|
+
const { repo, type, id } = req.params;
|
|
633
|
+
const { commitHash } = req.body ?? {};
|
|
634
|
+
if (!commitHash || typeof commitHash !== 'string') {
|
|
635
|
+
return reply.status(400).send({ error: 'commitHash is required' });
|
|
636
|
+
}
|
|
637
|
+
// Access enforced by graphForWrite: 404 if no read access, 403 if no write.
|
|
638
|
+
const graph = graphForWrite(req, reply, repo);
|
|
639
|
+
if (!graph)
|
|
640
|
+
return;
|
|
641
|
+
// Flush any deferred autosave for this repo first — otherwise a restore
|
|
642
|
+
// could overwrite a pending (uncommitted) edit on disk and commit only
|
|
643
|
+
// the restored version, silently discarding the in-flight work. Matches
|
|
644
|
+
// rename/move/change-type, which all flush before mutating.
|
|
645
|
+
if (sessionManager)
|
|
646
|
+
await sessionManager.flush(repo);
|
|
647
|
+
try {
|
|
648
|
+
// restoreFile reconciles any live editor Y.Doc against the restored disk
|
|
649
|
+
// content internally (via the BodyWriteCoordinator's per-doc lock), so an
|
|
650
|
+
// open editor shows the restored version instead of syncing stale content
|
|
651
|
+
// back over it. That lives in graph.restoreFile so every restore caller
|
|
652
|
+
// (this route, the agent tool, the MCP tool) gets it.
|
|
653
|
+
const result = await graph.restoreFile(type, id, commitHash, reqGitUser(req));
|
|
654
|
+
wsHub?.broadcast({
|
|
655
|
+
type: 'entity_change',
|
|
656
|
+
action: result.action === 'recreate' ? 'created' : 'updated',
|
|
657
|
+
repo,
|
|
658
|
+
entityType: type,
|
|
659
|
+
entityId: id,
|
|
660
|
+
actor: reqUser(req)?.displayName ?? 'API',
|
|
661
|
+
source: 'api',
|
|
662
|
+
timestamp: new Date().toISOString(),
|
|
663
|
+
});
|
|
664
|
+
return reply.send({
|
|
665
|
+
success: true,
|
|
666
|
+
action: result.action,
|
|
667
|
+
path: result.path,
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
catch (err) {
|
|
671
|
+
// PathConflictError carries an error code distinct from generic
|
|
672
|
+
// failures so the UI can render an actionable conflict message.
|
|
673
|
+
if (err?.code === 'PATH_CONFLICT' && err?.occupiedBy) {
|
|
674
|
+
return reply.status(409).send({
|
|
675
|
+
error: 'PATH_CONFLICT',
|
|
676
|
+
path: err.path,
|
|
677
|
+
occupiedBy: err.occupiedBy,
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
const message = err instanceof Error ? err.message : 'Restore failed';
|
|
681
|
+
// "not found at commit" / "did not exist at commit" → 404 so the
|
|
682
|
+
// client can show "that version isn't available" instead of a
|
|
683
|
+
// generic 500.
|
|
684
|
+
if (/not found at commit|did not exist at commit/i.test(message)) {
|
|
685
|
+
return reply.status(404).send({ error: message });
|
|
686
|
+
}
|
|
687
|
+
// Type-mismatch refusal (restoring would silently change entity_type)
|
|
688
|
+
// is a client-actionable conflict, not a server fault → 409.
|
|
689
|
+
if (/would change its type|was type '/i.test(message)) {
|
|
690
|
+
return reply.status(409).send({ error: message });
|
|
691
|
+
}
|
|
692
|
+
return reply.status(500).send({ error: message });
|
|
693
|
+
}
|
|
694
|
+
});
|
|
413
695
|
// GET /api/schema/:entityType/fields — field metadata for an entity type
|
|
414
696
|
fastify.get('/api/schema/:entityType/fields', async (req, reply) => {
|
|
415
697
|
const { entityType } = req.params;
|
|
@@ -417,20 +699,25 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
417
699
|
const fields = registry.getFieldMetadata(entityType);
|
|
418
700
|
return reply.send(fields);
|
|
419
701
|
});
|
|
702
|
+
// GET /api/formats — the format registry, serialized for the client.
|
|
703
|
+
//
|
|
704
|
+
// Single source of truth for ingestion: the web client builds its accept
|
|
705
|
+
// lists, disposition matrix, and supported-types copy from this instead of
|
|
706
|
+
// maintaining parallel (and drifting) constants. Mirrors the
|
|
707
|
+
// GET /api/schema/:type/fields pattern. Workspace-invariant + auth-gated
|
|
708
|
+
// by the global middleware; no per-repo access needed.
|
|
709
|
+
fastify.get('/api/formats', async (_req, reply) => {
|
|
710
|
+
const { serializeForClient } = await import('../../core/format-registry.js');
|
|
711
|
+
return reply.send(serializeForClient());
|
|
712
|
+
});
|
|
420
713
|
// GET /api/repos/:repo/search?q=&type=&tags= — search within a repo
|
|
421
714
|
fastify.get('/api/repos/:repo/search', async (req, reply) => {
|
|
422
715
|
const { repo } = req.params;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
716
|
+
// Access enforced by graphForRead (WorkspaceAccessService).
|
|
717
|
+
const graph = graphForRead(req, reply, repo);
|
|
718
|
+
if (!graph)
|
|
719
|
+
return;
|
|
426
720
|
const { q, type, tags } = req.query;
|
|
427
|
-
let graph;
|
|
428
|
-
try {
|
|
429
|
-
graph = workspaceManager.getGraph(repo);
|
|
430
|
-
}
|
|
431
|
-
catch (err) {
|
|
432
|
-
return reply.status(404).send({ error: err.message });
|
|
433
|
-
}
|
|
434
721
|
const results = graph.search({
|
|
435
722
|
query: q,
|
|
436
723
|
entityType: type,
|
|
@@ -446,19 +733,18 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
446
733
|
});
|
|
447
734
|
// GET /api/graph — full workspace graph topology (nodes + edges)
|
|
448
735
|
fastify.get('/api/graph', async (req, reply) => {
|
|
449
|
-
const repos =
|
|
736
|
+
const repos = getContentRepos(req, workspaceManager, authService);
|
|
450
737
|
const nodes = [];
|
|
451
738
|
const edgeSet = new Set();
|
|
452
739
|
const edges = [];
|
|
453
740
|
const entityIds = new Set();
|
|
454
741
|
for (const repoConfig of repos) {
|
|
455
742
|
try {
|
|
743
|
+
// Direct WM access OK — `repos` came from getAccessibleRepos above.
|
|
456
744
|
const graph = workspaceManager.getGraph(repoConfig.name);
|
|
457
745
|
const entities = graph.search({});
|
|
458
746
|
for (const entity of entities) {
|
|
459
747
|
const nodeId = entity.id;
|
|
460
|
-
if (nodeId.toLowerCase().startsWith('readme'))
|
|
461
|
-
continue;
|
|
462
748
|
if (!entityIds.has(nodeId)) {
|
|
463
749
|
entityIds.add(nodeId);
|
|
464
750
|
nodes.push({
|
|
@@ -486,10 +772,10 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
486
772
|
const validEdges = edges.filter(e => entityIds.has(e.source) && entityIds.has(e.target));
|
|
487
773
|
return reply.send({ nodes, edges: validEdges });
|
|
488
774
|
});
|
|
489
|
-
// GET /api/search?q=&type=&tags=&repos= — cross-repo search
|
|
775
|
+
// GET /api/search?q=&type=&tags=&repos=&mode= — cross-repo search
|
|
490
776
|
fastify.get('/api/search', async (req, reply) => {
|
|
491
|
-
const { q, type, tags, repos } = req.query;
|
|
492
|
-
const accessibleNames = new Set(
|
|
777
|
+
const { q, type, tags, repos, mode } = req.query;
|
|
778
|
+
const accessibleNames = new Set(getContentRepos(req, workspaceManager, authService).map(r => r.name));
|
|
493
779
|
// Filter requested repos to only accessible ones
|
|
494
780
|
let repoNames = repos ? repos.split(',').map(r => r.trim()) : undefined;
|
|
495
781
|
if (repoNames) {
|
|
@@ -498,12 +784,35 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
498
784
|
else {
|
|
499
785
|
repoNames = [...accessibleNames];
|
|
500
786
|
}
|
|
787
|
+
const keywordMode = mode === 'keyword';
|
|
501
788
|
const results = await workspaceManager.search({
|
|
502
789
|
query: q,
|
|
503
790
|
entityType: type,
|
|
504
791
|
tags: tags ? tags.split(',').map(t => t.trim()) : undefined,
|
|
505
792
|
repoNames,
|
|
793
|
+
mode: keywordMode ? 'keyword' : undefined,
|
|
794
|
+
// Autosuggest dropdown caps at 20 visible rows; capping server-side
|
|
795
|
+
// avoids serializing a long tail the client throws away.
|
|
796
|
+
limit: keywordMode ? 20 : undefined,
|
|
506
797
|
});
|
|
798
|
+
// Autosuggest doesn't need the markdown body or wikilinks — the
|
|
799
|
+
// dropdown renders {name, type, repo} only. Returning a slim shape
|
|
800
|
+
// cuts response size from ~hundreds of KB to a few KB on big
|
|
801
|
+
// workspaces, and skips the per-entity wikilink visibility filter.
|
|
802
|
+
if (keywordMode) {
|
|
803
|
+
const slim = results.map(e => ({
|
|
804
|
+
id: e.id,
|
|
805
|
+
path: e.path,
|
|
806
|
+
folder_path: e.folder_path,
|
|
807
|
+
entityType: e.entityType,
|
|
808
|
+
repoName: e.repoName,
|
|
809
|
+
data: {
|
|
810
|
+
name: e.data?.name,
|
|
811
|
+
updated_at: e.data?.updated_at,
|
|
812
|
+
},
|
|
813
|
+
}));
|
|
814
|
+
return reply.send(slim);
|
|
815
|
+
}
|
|
507
816
|
const accessibleIds = getAccessibleEntityIds(req, workspaceManager, authService);
|
|
508
817
|
const filtered = results.map((e) => {
|
|
509
818
|
if (!e.document?.wikilinks?.length)
|
|
@@ -515,23 +824,29 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
515
824
|
// PUT /api/repos/:repo/entities/:type/:id — update entity (data and/or content)
|
|
516
825
|
fastify.put('/api/repos/:repo/entities/:type/:id', async (req, reply) => {
|
|
517
826
|
const { repo, type, id } = req.params;
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
827
|
+
// Access enforced by graphForWrite \u2014 PUT updates an entity, write required.
|
|
828
|
+
const graph = graphForWrite(req, reply, repo);
|
|
829
|
+
if (!graph)
|
|
830
|
+
return;
|
|
521
831
|
const eagerError = checkEagerSkillPermission(req, type, req.body.data, repo, workspaceManager, authService);
|
|
522
832
|
if (eagerError)
|
|
523
833
|
return reply.status(403).send({ error: eagerError });
|
|
524
|
-
let graph;
|
|
525
834
|
try {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
//
|
|
835
|
+
// Optimistic concurrency. Two preconditions:
|
|
836
|
+
// - `X-Studiograph-Base-Body-Hash` (Phase 3a): sha256 hex of the
|
|
837
|
+
// body the client last loaded. Enforced INSIDE the per-doc lock
|
|
838
|
+
// by graph.update via `baseRevision`, so it composes with
|
|
839
|
+
// serialization — two body-writing PUTs from the same baseline
|
|
840
|
+
// can't both pass and back-stomp each other.
|
|
841
|
+
// - `If-Unmodified-Since` (legacy): kept for non-web clients;
|
|
842
|
+
// RFC-7231 second-precision so aliases sub-millisecond writes,
|
|
843
|
+
// which is why the body-hash form is preferred.
|
|
844
|
+
// Body-hash header is IGNORED on metadata-only PUTs — a stale body
|
|
845
|
+
// baseline is irrelevant when the PUT doesn't touch the body, and
|
|
846
|
+
// enforcing it would cause spurious 412s after an agent body-write.
|
|
847
|
+
const baseBodyHash = req.headers['x-studiograph-base-body-hash'];
|
|
533
848
|
const ifUnmodifiedSince = req.headers['if-unmodified-since'];
|
|
534
|
-
if (ifUnmodifiedSince) {
|
|
849
|
+
if (ifUnmodifiedSince && !baseBodyHash) {
|
|
535
850
|
const current = graph.get(type, id);
|
|
536
851
|
const entityUpdatedAt = current?.data?.updated_at;
|
|
537
852
|
if (entityUpdatedAt && new Date(entityUpdatedAt) > new Date(ifUnmodifiedSince)) {
|
|
@@ -539,6 +854,40 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
539
854
|
}
|
|
540
855
|
}
|
|
541
856
|
const gitUser = reqGitUser(req);
|
|
857
|
+
// Workspace-wide uniqueness check for `user_handle` on person entities.
|
|
858
|
+
// Skip the entity being updated so we don't reject it for clashing
|
|
859
|
+
// with itself. Also gate WHO can change the bridge: only workspace
|
|
860
|
+
// owners may stamp/clear/rewrite user_handle. Other folder-writers
|
|
861
|
+
// see the field but can't edit it (defense against impersonation
|
|
862
|
+
// through the agent's Current User prompt).
|
|
863
|
+
if (type === 'person' && req.body.data) {
|
|
864
|
+
try {
|
|
865
|
+
validateUserHandleInPayload(workspaceManager, req.body.data, { repo, entityId: id });
|
|
866
|
+
if (Object.prototype.hasOwnProperty.call(req.body.data, 'user_handle')) {
|
|
867
|
+
const current = graph.get('person', id);
|
|
868
|
+
const currentHandle = (current?.data?.user_handle ?? '');
|
|
869
|
+
assertCanChangeUserHandle(reqUser(req), currentHandle, req.body.data.user_handle);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
catch (err) {
|
|
873
|
+
if (err instanceof BridgeError) {
|
|
874
|
+
const status = err.code === 'OWNER_REQUIRED' ? 403 : 409;
|
|
875
|
+
return reply.status(status).send({ error: err.message, code: err.code, ...err.details });
|
|
876
|
+
}
|
|
877
|
+
throw err;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
// Scope ref fields (today: task.assignee → person). Rejects a wikilink
|
|
881
|
+
// that resolves to a non-person; forward references are allowed.
|
|
882
|
+
try {
|
|
883
|
+
assertEntityRefs(workspaceManager, type, req.body.data, { userExists: (id) => !!authService.findUserById(Number(id)) });
|
|
884
|
+
}
|
|
885
|
+
catch (err) {
|
|
886
|
+
if (err instanceof EntityRefError || err instanceof UnknownUserRefError) {
|
|
887
|
+
return reply.status(400).send({ error: err.message, code: err.code, field: err.field });
|
|
888
|
+
}
|
|
889
|
+
throw err;
|
|
890
|
+
}
|
|
542
891
|
const updated = await graph.update({
|
|
543
892
|
entityType: type,
|
|
544
893
|
entityId: id,
|
|
@@ -547,16 +896,16 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
547
896
|
commitMessage: `Update ${type}/${id} from web UI`,
|
|
548
897
|
user: gitUser,
|
|
549
898
|
skipCommit: !!sessionManager, // defer commit if scheduler is active
|
|
899
|
+
source: 'autosave',
|
|
900
|
+
// Forward the body-hash precondition into graph.update so the
|
|
901
|
+
// check runs INSIDE the per-doc lock — see Phase 3 P1 fix.
|
|
902
|
+
// Only meaningful for body writes; ignored when content is
|
|
903
|
+
// undefined (graph.update gates on content !== undefined too).
|
|
904
|
+
baseRevision: req.body.content !== undefined ? baseBodyHash : undefined,
|
|
550
905
|
});
|
|
551
906
|
if (sessionManager && gitUser) {
|
|
552
907
|
sessionManager.markDirty(repo, gitUser, type, id, updated.path);
|
|
553
908
|
}
|
|
554
|
-
// Push the new content into any active Y.Doc so other open tabs /
|
|
555
|
-
// browsers for the same entity see the update and the CRDT doesn't
|
|
556
|
-
// blend stale state on the next resync. No-op when no Y.Doc is live.
|
|
557
|
-
if (req.body.content !== undefined) {
|
|
558
|
-
yjsManager?.syncDocFromDisk(`${repo}/${type}/${id}`);
|
|
559
|
-
}
|
|
560
909
|
wsHub?.broadcast({
|
|
561
910
|
type: 'entity_change', action: 'updated', repo, entityType: type, entityId: id,
|
|
562
911
|
actor: req.user?.displayName ?? 'API',
|
|
@@ -565,26 +914,36 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
565
914
|
return reply.send(updated);
|
|
566
915
|
}
|
|
567
916
|
catch (err) {
|
|
917
|
+
// Translate the locked-section baseRevision mismatch into the
|
|
918
|
+
// same 412 shape the route used to emit pre-lock. Preserves the
|
|
919
|
+
// client-side contract (code: STALE_BODY_HASH + currentBodyHash
|
|
920
|
+
// + entity).
|
|
921
|
+
if (err instanceof StaleBaseRevisionError) {
|
|
922
|
+
const current = graph.get(type, id);
|
|
923
|
+
return reply.status(412).send({
|
|
924
|
+
error: 'Precondition Failed: body was modified',
|
|
925
|
+
code: 'STALE_BODY_HASH',
|
|
926
|
+
currentBodyHash: err.actual,
|
|
927
|
+
entity: current,
|
|
928
|
+
});
|
|
929
|
+
}
|
|
568
930
|
return reply.status(400).send({ error: err.message });
|
|
569
931
|
}
|
|
570
932
|
});
|
|
571
|
-
// POST /api/repos/:repo/change-type — change entity type
|
|
933
|
+
// POST /api/repos/:repo/change-type — change entity type. Rewrites the
|
|
934
|
+
// `entity_type` frontmatter + swaps type-specific fields IN PLACE (the file
|
|
935
|
+
// stays in its current folder; type lives in frontmatter, not the path). The
|
|
936
|
+
// (type,id) identity still moves, so the client renavigates to the new-type URL.
|
|
572
937
|
fastify.post('/api/repos/:repo/change-type', async (req, reply) => {
|
|
573
938
|
const { repo } = req.params;
|
|
574
939
|
const { entityType: type, entityId: id, newType } = req.body ?? {};
|
|
575
940
|
if (!type || !id || !newType) {
|
|
576
941
|
return reply.status(400).send({ error: 'entityType, entityId, and newType are required' });
|
|
577
942
|
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
try {
|
|
583
|
-
graph = workspaceManager.getGraph(repo);
|
|
584
|
-
}
|
|
585
|
-
catch (err) {
|
|
586
|
-
return reply.status(404).send({ error: err.message });
|
|
587
|
-
}
|
|
943
|
+
// Access enforced by graphForRead (WorkspaceAccessService).
|
|
944
|
+
const graph = graphForRead(req, reply, repo);
|
|
945
|
+
if (!graph)
|
|
946
|
+
return;
|
|
588
947
|
try {
|
|
589
948
|
// Flush any pending session saves before moving the file
|
|
590
949
|
if (sessionManager) {
|
|
@@ -622,21 +981,15 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
622
981
|
if (id === newId) {
|
|
623
982
|
return reply.status(400).send({ error: 'New ID must differ from the current ID' });
|
|
624
983
|
}
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
984
|
+
// Access enforced by graphForWrite (WorkspaceAccessService).
|
|
985
|
+
const graph = graphForWrite(req, reply, repo);
|
|
986
|
+
if (!graph)
|
|
987
|
+
return;
|
|
628
988
|
// Workspace-wide uniqueness — ids must be unique across all folders
|
|
629
989
|
const conflict = workspaceManager.findEntityById(newId);
|
|
630
990
|
if (conflict) {
|
|
631
991
|
return reply.status(409).send({ error: `An entry with id "${newId}" already exists in folder "${conflict.repoName}".` });
|
|
632
992
|
}
|
|
633
|
-
let graph;
|
|
634
|
-
try {
|
|
635
|
-
graph = workspaceManager.getGraph(repo);
|
|
636
|
-
}
|
|
637
|
-
catch (err) {
|
|
638
|
-
return reply.status(404).send({ error: err.message });
|
|
639
|
-
}
|
|
640
993
|
try {
|
|
641
994
|
// Flush pending session saves so we don't PUT to the old path after rename
|
|
642
995
|
if (sessionManager) {
|
|
@@ -650,6 +1003,7 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
650
1003
|
const refactorMsg = `Refactor wikilinks: ${id} \u2192 ${newId}`;
|
|
651
1004
|
for (const other of getAccessibleRepos(req, workspaceManager, authService)) {
|
|
652
1005
|
try {
|
|
1006
|
+
// Direct WM access OK — iterating the access-filtered list.
|
|
653
1007
|
const otherGraph = workspaceManager.getGraph(other.name);
|
|
654
1008
|
await otherGraph.refactorWikilink(id, newId, refactorMsg);
|
|
655
1009
|
}
|
|
@@ -672,45 +1026,63 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
672
1026
|
return reply.status(status).send({ error: err.message });
|
|
673
1027
|
}
|
|
674
1028
|
});
|
|
675
|
-
// POST /api/repos/:repo/transfer — move or copy entity to another
|
|
1029
|
+
// POST /api/repos/:repo/transfer — move or copy entity to another folder
|
|
676
1030
|
fastify.post('/api/repos/:repo/transfer', async (req, reply) => {
|
|
677
1031
|
const sourceRepo = req.params.repo;
|
|
678
|
-
const { entityType, entityId, targetRepo, mode } = req.body ?? {};
|
|
1032
|
+
const { entityType, entityId, targetRepo, mode, targetFolderPath } = req.body ?? {};
|
|
679
1033
|
if (!entityType || !entityId || !targetRepo || !mode) {
|
|
680
1034
|
return reply.status(400).send({ error: 'entityType, entityId, targetRepo, and mode are required' });
|
|
681
1035
|
}
|
|
682
1036
|
if (sourceRepo === targetRepo) {
|
|
683
1037
|
return reply.status(400).send({ error: 'Source and target folders must be different' });
|
|
684
1038
|
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
1039
|
+
// Workspace Assets store ownership gate (codex P1 / D2). Moving an `asset`
|
|
1040
|
+
// OUT of a workspace_wide store reassigns its index repo, after which the
|
|
1041
|
+
// uploader/owner delete rule no longer applies — so the move itself must
|
|
1042
|
+
// honor that rule. assetDeleteDenial is a no-op for non-workspace_wide
|
|
1043
|
+
// sources, so normal folder→folder moves are unaffected.
|
|
1044
|
+
if (entityType === 'asset') {
|
|
1045
|
+
const denial = assetDeleteDenial(workspaceManager.getAssetIndex?.(), workspaceManager.getRepoConfig(sourceRepo), reqUser(req), sourceRepo, entityType, entityId);
|
|
1046
|
+
if (denial)
|
|
1047
|
+
return reply.status(403).send({ error: denial });
|
|
1048
|
+
}
|
|
1049
|
+
// Access enforced by graphForWrite — both source (delete on move)
|
|
1050
|
+
// and target (create) require write access. Resolved up-front so a
|
|
1051
|
+
// target-side denial never surfaces only after source has been
|
|
1052
|
+
// mutated. Audit (2026-05) flagged the previous opt-in pattern.
|
|
1053
|
+
const sourceGraph = graphForWrite(req, reply, sourceRepo);
|
|
1054
|
+
if (!sourceGraph)
|
|
1055
|
+
return;
|
|
1056
|
+
const targetGraph = graphForWrite(req, reply, targetRepo);
|
|
1057
|
+
if (!targetGraph)
|
|
1058
|
+
return;
|
|
692
1059
|
try {
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}
|
|
699
|
-
try {
|
|
700
|
-
// Flush pending saves
|
|
701
|
-
if (sessionManager)
|
|
1060
|
+
// Phase 0 — prevalidate. Audit (2026-05) required this: previously
|
|
1061
|
+
// the route created in target, broadcast `created`, and only then
|
|
1062
|
+
// tried to delete from source. A delete failure left a duplicate
|
|
1063
|
+
// entry the client had already been told about.
|
|
1064
|
+
if (sessionManager) {
|
|
702
1065
|
await sessionManager.flush(sourceRepo);
|
|
703
|
-
|
|
1066
|
+
if (targetRepo !== sourceRepo)
|
|
1067
|
+
await sessionManager.flush(targetRepo);
|
|
1068
|
+
}
|
|
1069
|
+
// Source must exist; target must not collide. Both checks run BEFORE
|
|
1070
|
+
// any mutation so we never enter phase 2 unless phase 1 will succeed.
|
|
704
1071
|
const source = sourceGraph.get(entityType, entityId);
|
|
705
|
-
|
|
1072
|
+
const sourcePath = source.path;
|
|
1073
|
+
const sourceRawMarkdown = readFileSync(sourcePath, 'utf-8');
|
|
706
1074
|
try {
|
|
707
1075
|
targetGraph.get(entityType, entityId);
|
|
708
1076
|
return reply.status(409).send({ error: `"${entityId}" already exists in ${targetRepo}` });
|
|
709
1077
|
}
|
|
710
1078
|
catch { /* not found = good */ }
|
|
711
|
-
// Create in target
|
|
712
1079
|
const gitUser = reqGitUser(req);
|
|
713
1080
|
const { entity_type, entity_id, ...dataFields } = source.data;
|
|
1081
|
+
// Phase 1 — create in target. No broadcast yet: a half-completed
|
|
1082
|
+
// move (created in target, source still present) must NOT be
|
|
1083
|
+
// observable to clients, or they will display two copies until the
|
|
1084
|
+
// next refresh. `targetFolderPath` lets the caller drop the entry
|
|
1085
|
+
// straight into a subfolder of the target repo.
|
|
714
1086
|
const created = await targetGraph.create({
|
|
715
1087
|
entityType: entityType,
|
|
716
1088
|
entityId,
|
|
@@ -718,21 +1090,120 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
718
1090
|
content: source.document?.content ?? '',
|
|
719
1091
|
commitMessage: `${mode === 'move' ? 'Move' : 'Copy'} ${entityType}: ${entityId} from ${sourceRepo}`,
|
|
720
1092
|
user: gitUser,
|
|
1093
|
+
folderPath: targetFolderPath,
|
|
721
1094
|
});
|
|
1095
|
+
// Phase 2 — for `move` only: delete from source. If this fails, run
|
|
1096
|
+
// a compensating delete on target so we don't leave a duplicate.
|
|
1097
|
+
// The compensating delete is best-effort; if it also fails, surface
|
|
1098
|
+
// a 500 with both error messages so the residue is visible (the
|
|
1099
|
+
// previous behavior silently succeeded and left orphans).
|
|
1100
|
+
if (mode === 'move') {
|
|
1101
|
+
try {
|
|
1102
|
+
// delete() never touches assets (referenced, not owned), so the
|
|
1103
|
+
// moved entity's blobs are safe through the source-side delete.
|
|
1104
|
+
await sourceGraph.delete(entityType, entityId, undefined, gitUser);
|
|
1105
|
+
// Re-point ownership to the target repo ONLY after the source
|
|
1106
|
+
// delete succeeds. Doing it after means a failed source delete
|
|
1107
|
+
// (handled below) leaves the index pointing at the source repo,
|
|
1108
|
+
// which is correct for the entity we're about to restore there.
|
|
1109
|
+
// Blobs/URLs are unchanged (immutable keys); only the index's
|
|
1110
|
+
// repo column moves, which is what serve-time authz reads.
|
|
1111
|
+
workspaceManager.getAssetIndex?.()?.reassignOwner(sourceRepo, entityType, entityId, { repo: targetRepo });
|
|
1112
|
+
}
|
|
1113
|
+
catch (deleteErr) {
|
|
1114
|
+
const deleteMsg = deleteErr instanceof Error ? deleteErr.message : String(deleteErr);
|
|
1115
|
+
let recoveryMsg = null;
|
|
1116
|
+
try {
|
|
1117
|
+
let restoreSource = !existsSync(sourcePath);
|
|
1118
|
+
if (!restoreSource) {
|
|
1119
|
+
try {
|
|
1120
|
+
restoreSource = readFileSync(sourcePath, 'utf-8') !== sourceRawMarkdown;
|
|
1121
|
+
}
|
|
1122
|
+
catch {
|
|
1123
|
+
restoreSource = true;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
if (restoreSource) {
|
|
1127
|
+
mkdirSync(dirname(sourcePath), { recursive: true });
|
|
1128
|
+
writeFileSync(sourcePath, sourceRawMarkdown, 'utf-8');
|
|
1129
|
+
const recoveryPaths = [sourcePath];
|
|
1130
|
+
const folderMarkerPath = join(dirname(sourcePath), '.gitkeep');
|
|
1131
|
+
if (existsSync(folderMarkerPath)) {
|
|
1132
|
+
rmSync(folderMarkerPath, { force: true });
|
|
1133
|
+
recoveryPaths.push(folderMarkerPath);
|
|
1134
|
+
}
|
|
1135
|
+
const sourceAny = sourceGraph;
|
|
1136
|
+
const recoveryGit = sourceAny.gitService;
|
|
1137
|
+
const recoveryUser = gitUser ?? sourceAny.gitUser;
|
|
1138
|
+
if (!recoveryGit || !recoveryUser) {
|
|
1139
|
+
throw new Error('source graph git service or git user unavailable');
|
|
1140
|
+
}
|
|
1141
|
+
recoveryGit.commitFiles(recoveryPaths, `Recover ${entityType}: ${entityId} after failed move to ${targetRepo}`, recoveryUser);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
catch (recoveryErr) {
|
|
1145
|
+
recoveryMsg = recoveryErr instanceof Error ? recoveryErr.message : String(recoveryErr);
|
|
1146
|
+
log.error('[critical] cross-repo move source recovery failed', {
|
|
1147
|
+
sourceRepo,
|
|
1148
|
+
targetRepo,
|
|
1149
|
+
entityType,
|
|
1150
|
+
entityId,
|
|
1151
|
+
sourcePath,
|
|
1152
|
+
deleteError: deleteMsg,
|
|
1153
|
+
recoveryError: recoveryMsg,
|
|
1154
|
+
capturedContent: sourceRawMarkdown,
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
try {
|
|
1158
|
+
// Compensating delete never touches assets either — the
|
|
1159
|
+
// recovered source entity keeps pointing at live URLs.
|
|
1160
|
+
await targetGraph.delete(entityType, entityId, undefined, gitUser);
|
|
1161
|
+
if (recoveryMsg) {
|
|
1162
|
+
return reply.status(500).send({
|
|
1163
|
+
error: `Move failed: source delete of ${entityType}/${entityId} failed (${deleteMsg}). Target rolled back, but source recovery commit failed (${recoveryMsg}); manual source repo recovery is required.`,
|
|
1164
|
+
code: 'MOVE_SOURCE_RECOVERY_FAILED',
|
|
1165
|
+
sourceRepo,
|
|
1166
|
+
targetRepo,
|
|
1167
|
+
});
|
|
1168
|
+
}
|
|
1169
|
+
return reply.status(500).send({
|
|
1170
|
+
error: `Move failed: source delete of ${entityType}/${entityId} failed (${deleteMsg}). Target rolled back; folders are back to their pre-move state.`,
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
catch (compErr) {
|
|
1174
|
+
const compMsg = compErr instanceof Error ? compErr.message : String(compErr);
|
|
1175
|
+
if (recoveryMsg) {
|
|
1176
|
+
return reply.status(500).send({
|
|
1177
|
+
error: `Move failed and could not be rolled back. Source delete failed (${deleteMsg}); source recovery commit failed (${recoveryMsg}); compensating target delete also failed (${compMsg}). Captured source content was logged for manual recovery.`,
|
|
1178
|
+
code: 'MOVE_RECOVERY_AND_ROLLBACK_FAILED',
|
|
1179
|
+
sourceRepo,
|
|
1180
|
+
targetRepo,
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
return reply.status(500).send({
|
|
1184
|
+
error: `Move failed and could not be rolled back. Source delete failed (${deleteMsg}); compensating target delete also failed (${compMsg}). Entry now exists in BOTH ${sourceRepo} and ${targetRepo} — manual cleanup required.`,
|
|
1185
|
+
code: 'MOVE_DUPLICATE',
|
|
1186
|
+
sourceRepo,
|
|
1187
|
+
targetRepo,
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
// Phase 3 — both phases succeeded; only now broadcast. Order:
|
|
1193
|
+
// source-delete first so subscribers don't briefly observe the
|
|
1194
|
+
// entity in both folders during the network race.
|
|
722
1195
|
const actor = req.user?.displayName ?? 'API';
|
|
723
1196
|
const ts = new Date().toISOString();
|
|
724
|
-
wsHub?.broadcast({
|
|
725
|
-
type: 'entity_change', action: 'created', repo: targetRepo, entityType, entityId,
|
|
726
|
-
actor, source: 'api', timestamp: ts,
|
|
727
|
-
});
|
|
728
|
-
// Delete from source if moving
|
|
729
1197
|
if (mode === 'move') {
|
|
730
|
-
await sourceGraph.delete(entityType, entityId, undefined, gitUser);
|
|
731
1198
|
wsHub?.broadcast({
|
|
732
1199
|
type: 'entity_change', action: 'deleted', repo: sourceRepo, entityType, entityId,
|
|
733
1200
|
actor, source: 'api', timestamp: ts,
|
|
734
1201
|
});
|
|
735
1202
|
}
|
|
1203
|
+
wsHub?.broadcast({
|
|
1204
|
+
type: 'entity_change', action: 'created', repo: targetRepo, entityType, entityId,
|
|
1205
|
+
actor, source: 'api', timestamp: ts,
|
|
1206
|
+
});
|
|
736
1207
|
return reply.send({
|
|
737
1208
|
success: true,
|
|
738
1209
|
entity: created,
|
|
@@ -743,27 +1214,211 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
743
1214
|
return reply.status(400).send({ error: err.message });
|
|
744
1215
|
}
|
|
745
1216
|
});
|
|
746
|
-
//
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
1217
|
+
// POST /api/repos/:repo/transfer-entities — bulk move a list of entries
|
|
1218
|
+
// into a single target folder (in this or another repo). The web UI's
|
|
1219
|
+
// multi-select Move flow used to loop and POST per entry; this collapses
|
|
1220
|
+
// to one round-trip per source repo and one commit per repo touched.
|
|
1221
|
+
fastify.post('/api/repos/:repo/transfer-entities', async (req, reply) => {
|
|
1222
|
+
const sourceRepo = req.params.repo;
|
|
1223
|
+
const { entries, targetRepo, targetFolder } = req.body ?? {};
|
|
1224
|
+
if (!Array.isArray(entries) || !targetRepo || targetFolder === undefined) {
|
|
1225
|
+
return reply.status(400).send({ error: 'entries, targetRepo, and targetFolder are required' });
|
|
1226
|
+
}
|
|
1227
|
+
// Workspace Assets store ownership gate (codex P1 / D2): same rule as the
|
|
1228
|
+
// single /transfer route, applied per asset entry. No-op when the source
|
|
1229
|
+
// isn't workspace_wide.
|
|
1230
|
+
for (const e of entries) {
|
|
1231
|
+
if (e.entityType !== 'asset')
|
|
1232
|
+
continue;
|
|
1233
|
+
const denial = assetDeleteDenial(workspaceManager.getAssetIndex?.(), workspaceManager.getRepoConfig(sourceRepo), reqUser(req), sourceRepo, e.entityType, e.entityId);
|
|
1234
|
+
if (denial)
|
|
1235
|
+
return reply.status(403).send({ error: denial });
|
|
1236
|
+
}
|
|
1237
|
+
// Access enforced by graphForWrite — both source (mutate) and
|
|
1238
|
+
// target (write) require write access. Resolved up-front so a
|
|
1239
|
+
// target-side denial never surfaces only after source mutation.
|
|
1240
|
+
const sourceGraph = graphForWrite(req, reply, sourceRepo);
|
|
1241
|
+
if (!sourceGraph)
|
|
1242
|
+
return;
|
|
1243
|
+
const targetGraph = graphForWrite(req, reply, targetRepo);
|
|
1244
|
+
if (!targetGraph)
|
|
1245
|
+
return;
|
|
1246
|
+
try {
|
|
1247
|
+
if (sessionManager) {
|
|
1248
|
+
await sessionManager.flush(sourceRepo);
|
|
1249
|
+
if (targetRepo !== sourceRepo)
|
|
1250
|
+
await sessionManager.flush(targetRepo);
|
|
1251
|
+
}
|
|
1252
|
+
const result = await sourceGraph.moveEntitiesToRepo(entries.map(e => ({ entityType: e.entityType, entityId: e.entityId })), targetGraph, targetFolder, { user: reqGitUser(req) });
|
|
1253
|
+
const actor = req.user?.displayName ?? 'API';
|
|
1254
|
+
const ts = new Date().toISOString();
|
|
1255
|
+
const summary = `moved ${entries.length} ${entries.length === 1 ? 'entry' : 'entries'}`;
|
|
1256
|
+
wsHub?.broadcast({ type: 'repo_sync', repo: sourceRepo, source: 'api', summary, workspaceWide: workspaceManager.getRepoConfig(sourceRepo)?.workspace_wide === true, actor, timestamp: ts });
|
|
1257
|
+
if (targetRepo !== sourceRepo) {
|
|
1258
|
+
wsHub?.broadcast({ type: 'repo_sync', repo: targetRepo, source: 'api', summary, workspaceWide: workspaceManager.getRepoConfig(targetRepo)?.workspace_wide === true, actor, timestamp: ts });
|
|
1259
|
+
}
|
|
1260
|
+
return reply.send({ success: true, ...result });
|
|
1261
|
+
}
|
|
1262
|
+
catch (err) {
|
|
1263
|
+
const status = err?.name === 'InvalidFolderPathError' ? 400
|
|
1264
|
+
: err.message?.includes('not found') ? 404
|
|
1265
|
+
: err.message?.includes('already occupied') ? 409
|
|
1266
|
+
: 400;
|
|
1267
|
+
return reply.status(status).send({ error: err.message });
|
|
751
1268
|
}
|
|
752
|
-
|
|
1269
|
+
});
|
|
1270
|
+
// POST /api/repos/:repo/transfer-folder — move an entire folder (with all
|
|
1271
|
+
// descendants) into another repo in one shot. Replaces the per-entry loop
|
|
1272
|
+
// the web UI used to run, which produced 2 git commits per entry; this
|
|
1273
|
+
// route performs a single FS-level rename and one commit per repo.
|
|
1274
|
+
fastify.post('/api/repos/:repo/transfer-folder', async (req, reply) => {
|
|
1275
|
+
const sourceRepo = req.params.repo;
|
|
1276
|
+
const { sourceFolder, targetRepo, targetFolder } = req.body ?? {};
|
|
1277
|
+
if (!sourceFolder || !targetRepo || targetFolder === undefined) {
|
|
1278
|
+
return reply.status(400).send({ error: 'sourceFolder, targetRepo, and targetFolder are required' });
|
|
1279
|
+
}
|
|
1280
|
+
if (sourceRepo === targetRepo && sourceFolder === targetFolder) {
|
|
1281
|
+
return reply.status(400).send({ error: 'Source and target are the same' });
|
|
1282
|
+
}
|
|
1283
|
+
// Workspace Assets store gate (codex P1 / D2). Moving a folder OUT of a
|
|
1284
|
+
// workspace_wide store relocates+reassigns every asset under it, escaping
|
|
1285
|
+
// the per-asset uploader/owner delete rule. A folder-level bulk move in
|
|
1286
|
+
// the shared store is an admin action — restrict it to a workspace owner.
|
|
1287
|
+
// (No-op for normal folders; the manager never folder-moves the store.)
|
|
1288
|
+
if (workspaceManager.getRepoConfig(sourceRepo)?.workspace_wide && !isWorkspaceOwner(reqUser(req)?.role ?? '')) {
|
|
1289
|
+
return reply.status(403).send({ error: 'Only a workspace owner can move folders out of the shared Assets store.' });
|
|
1290
|
+
}
|
|
1291
|
+
// Access enforced by graphForWrite — both source (mutate) and
|
|
1292
|
+
// target (write) require write access. Resolved up-front so a
|
|
1293
|
+
// target-side denial never surfaces only after source mutation.
|
|
1294
|
+
const sourceGraph = graphForWrite(req, reply, sourceRepo);
|
|
1295
|
+
if (!sourceGraph)
|
|
1296
|
+
return;
|
|
1297
|
+
const targetGraph = graphForWrite(req, reply, targetRepo);
|
|
1298
|
+
if (!targetGraph)
|
|
1299
|
+
return;
|
|
753
1300
|
try {
|
|
754
|
-
|
|
1301
|
+
// Flush any deferred saves on either side so we don't strand a
|
|
1302
|
+
// pending edit in the source folder mid-move.
|
|
1303
|
+
if (sessionManager) {
|
|
1304
|
+
await sessionManager.flush(sourceRepo);
|
|
1305
|
+
if (targetRepo !== sourceRepo)
|
|
1306
|
+
await sessionManager.flush(targetRepo);
|
|
1307
|
+
}
|
|
1308
|
+
const stats = await sourceGraph.moveFolderToRepo(sourceFolder, targetGraph, targetFolder, { user: reqGitUser(req) });
|
|
1309
|
+
const actor = req.user?.displayName ?? 'API';
|
|
1310
|
+
const ts = new Date().toISOString();
|
|
1311
|
+
// One refresh-trigger per repo. The receiving and sending lists
|
|
1312
|
+
// both need to update; using `repo_sync` keeps the realtime
|
|
1313
|
+
// handler simple — it already calls workspace.refreshEntities
|
|
1314
|
+
// on this event type. Avoids emitting 2× entries-moved entity_change
|
|
1315
|
+
// broadcasts (one created, one deleted) per file.
|
|
1316
|
+
wsHub?.broadcast({ type: 'repo_sync', repo: sourceRepo, source: 'api', summary: 'moved a folder', workspaceWide: workspaceManager.getRepoConfig(sourceRepo)?.workspace_wide === true, actor, timestamp: ts });
|
|
1317
|
+
if (targetRepo !== sourceRepo) {
|
|
1318
|
+
wsHub?.broadcast({ type: 'repo_sync', repo: targetRepo, source: 'api', summary: 'moved a folder', workspaceWide: workspaceManager.getRepoConfig(targetRepo)?.workspace_wide === true, actor, timestamp: ts });
|
|
1319
|
+
}
|
|
1320
|
+
return reply.send({ success: true, ...stats });
|
|
755
1321
|
}
|
|
756
1322
|
catch (err) {
|
|
757
|
-
|
|
1323
|
+
const status = err?.name === 'InvalidFolderPathError' ? 400
|
|
1324
|
+
: err.message?.includes('not found') ? 404
|
|
1325
|
+
: err.message?.includes('not empty') ? 409
|
|
1326
|
+
: 400;
|
|
1327
|
+
return reply.status(status).send({ error: err.message });
|
|
758
1328
|
}
|
|
1329
|
+
});
|
|
1330
|
+
// Workspace Assets store delete gate (codex P1 / D2) — uploader/owner only.
|
|
1331
|
+
// Pure decision lives in services/asset-delete-authz.ts (unit-tested); this
|
|
1332
|
+
// closure binds it to the route's workspaceManager.
|
|
1333
|
+
const denyAssetDelete = (user, repo, entityType, entityId) => assetDeleteDenial(workspaceManager.getAssetIndex?.(), workspaceManager.getRepoConfig(repo), user, repo, entityType, entityId);
|
|
1334
|
+
// DELETE /api/repos/:repo/entities/:type/:id — delete an entity
|
|
1335
|
+
fastify.delete('/api/repos/:repo/entities/:type/:id', async (req, reply) => {
|
|
1336
|
+
const { repo, type, id } = req.params;
|
|
1337
|
+
const denial = denyAssetDelete(reqUser(req), repo, type, id);
|
|
1338
|
+
if (denial)
|
|
1339
|
+
return reply.status(403).send({ error: denial });
|
|
1340
|
+
// Access enforced by graphForRead (WorkspaceAccessService).
|
|
1341
|
+
const graph = graphForRead(req, reply, repo);
|
|
1342
|
+
if (!graph)
|
|
1343
|
+
return;
|
|
759
1344
|
try {
|
|
1345
|
+
// Soft bridge-awareness: deleting a person that's linked to an auth
|
|
1346
|
+
// account orphans that user (their "My Tasks"/@mentions stop
|
|
1347
|
+
// resolving). We don't BLOCK the delete — the link is a soft
|
|
1348
|
+
// relationship — but we surface a warning so the caller knows, and the
|
|
1349
|
+
// orphan is then caught by the Profile panel's unlinked affordance.
|
|
1350
|
+
let bridgeWarning;
|
|
1351
|
+
if (type === 'person') {
|
|
1352
|
+
try {
|
|
1353
|
+
const handle = graph.get('person', id)?.data?.user_handle;
|
|
1354
|
+
if (typeof handle === 'string' && handle.length > 0) {
|
|
1355
|
+
bridgeWarning = `This record was linked to the "${handle}" account. Deleting it unlinks that user — their "My Tasks", @mentions, and task assignments stop resolving until they link a new record (Settings → Profile → "Create & link").`;
|
|
1356
|
+
log.warn(`[bridge] deleting linked person ${repo}/${id} (was user ${handle})`);
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
catch { /* not readable / already gone — let graph.delete surface it */ }
|
|
1360
|
+
}
|
|
760
1361
|
await graph.delete(type, id, `Delete ${type}/${id} from web UI`, reqGitUser(req));
|
|
1362
|
+
// Entity deletion KEEPS assets (assets-referenced-not-owned P1) — a
|
|
1363
|
+
// former `asset` entity's index row persists as a library asset
|
|
1364
|
+
// (re-homed to its folder by `admin migrate-asset-owners`).
|
|
761
1365
|
wsHub?.broadcast({
|
|
762
1366
|
type: 'entity_change', action: 'deleted', repo, entityType: type, entityId: id,
|
|
763
1367
|
actor: req.user?.displayName ?? 'API',
|
|
764
1368
|
source: 'api', timestamp: new Date().toISOString(),
|
|
765
1369
|
});
|
|
766
|
-
return reply.send({ ok: true });
|
|
1370
|
+
return reply.send({ ok: true, ...(bridgeWarning ? { warning: bridgeWarning } : {}) });
|
|
1371
|
+
}
|
|
1372
|
+
catch (err) {
|
|
1373
|
+
return reply.status(400).send({ error: err.message });
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
// POST /api/repos/:repo/entities/bulk-delete — delete many entities in
|
|
1377
|
+
// one git transaction + one WS broadcast. Replaces N parallel
|
|
1378
|
+
// DELETE /entities/:type/:id calls, which on a 500-entry bulk delete
|
|
1379
|
+
// produced 500 git commits + 500 entity_change broadcasts and froze
|
|
1380
|
+
// the UI for minutes.
|
|
1381
|
+
fastify.post('/api/repos/:repo/entities/bulk-delete', async (req, reply) => {
|
|
1382
|
+
const { repo } = req.params;
|
|
1383
|
+
// Access enforced by graphForWrite (WorkspaceAccessService).
|
|
1384
|
+
const graph = graphForWrite(req, reply, repo);
|
|
1385
|
+
if (!graph)
|
|
1386
|
+
return;
|
|
1387
|
+
const body = req.body;
|
|
1388
|
+
if (!body?.entities || !Array.isArray(body.entities)) {
|
|
1389
|
+
return reply.status(400).send({ error: 'Expected { entities: [{ entityType, entityId }] }' });
|
|
1390
|
+
}
|
|
1391
|
+
// Apply the workspace-Assets delete gate per entity (codex P1 / D2):
|
|
1392
|
+
// uploader/owner-only. Denied entries are reported, not silently dropped.
|
|
1393
|
+
const deleteUser = reqUser(req);
|
|
1394
|
+
const denied = [];
|
|
1395
|
+
const allowed = body.entities.filter(e => {
|
|
1396
|
+
const d = denyAssetDelete(deleteUser, repo, e.entityType, e.entityId);
|
|
1397
|
+
if (d) {
|
|
1398
|
+
denied.push({ entityType: e.entityType, entityId: e.entityId, error: d });
|
|
1399
|
+
return false;
|
|
1400
|
+
}
|
|
1401
|
+
return true;
|
|
1402
|
+
});
|
|
1403
|
+
try {
|
|
1404
|
+
const result = await graph.bulkDelete(allowed.map(e => ({ entityType: e.entityType, entityId: e.entityId })), {
|
|
1405
|
+
user: reqGitUser(req),
|
|
1406
|
+
commitMessage: `Bulk delete ${allowed.length} ${allowed.length === 1 ? 'entry' : 'entries'} from web UI`,
|
|
1407
|
+
});
|
|
1408
|
+
if (result.deleted.length > 0) {
|
|
1409
|
+
wsHub?.broadcast({
|
|
1410
|
+
type: 'repo_sync', repo,
|
|
1411
|
+
actor: req.user?.displayName ?? 'API',
|
|
1412
|
+
source: 'api',
|
|
1413
|
+
summary: `deleted ${result.deleted.length} ${result.deleted.length === 1 ? 'entry' : 'entries'}`,
|
|
1414
|
+
workspaceWide: workspaceManager.getRepoConfig(repo)?.workspace_wide === true,
|
|
1415
|
+
timestamp: new Date().toISOString(),
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
return reply.send({
|
|
1419
|
+
deleted: result.deleted.length,
|
|
1420
|
+
errors: [...result.errors, ...denied],
|
|
1421
|
+
});
|
|
767
1422
|
}
|
|
768
1423
|
catch (err) {
|
|
769
1424
|
return reply.status(400).send({ error: err.message });
|
|
@@ -772,203 +1427,466 @@ export async function registerGraphApiRoutes(fastify, workspaceManager, authServ
|
|
|
772
1427
|
// POST /api/repos/:repo/entities — create a new entity
|
|
773
1428
|
fastify.post('/api/repos/:repo/entities', async (req, reply) => {
|
|
774
1429
|
const { repo } = req.params;
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
1430
|
+
// Access enforced by graphForWrite (WorkspaceAccessService).
|
|
1431
|
+
const graph = graphForWrite(req, reply, repo);
|
|
1432
|
+
if (!graph)
|
|
1433
|
+
return;
|
|
1434
|
+
const { entityType, entityId, data = {}, content = '', folder_path } = req.body;
|
|
779
1435
|
if (!entityType || !entityId) {
|
|
780
1436
|
return reply.status(400).send({ error: 'entityType and entityId are required' });
|
|
781
1437
|
}
|
|
1438
|
+
// Refuse to re-mint a retired type (`asset`, Phase F). Otherwise an
|
|
1439
|
+
// unknown entityType falls back to EntityBaseSchema and writes a
|
|
1440
|
+
// base-only stub of exactly the type asset-as-embed removed.
|
|
1441
|
+
if (RESERVED_ENTITY_TYPES.has(entityType)) {
|
|
1442
|
+
return reply.status(400).send({ error: `Entity type '${entityType}' was retired and can no longer be created.` });
|
|
1443
|
+
}
|
|
782
1444
|
const eagerError = checkEagerSkillPermission(req, entityType, data, repo, workspaceManager, authService);
|
|
783
1445
|
if (eagerError)
|
|
784
1446
|
return reply.status(403).send({ error: eagerError });
|
|
785
|
-
|
|
1447
|
+
// Person entities can carry a `user_handle` pointer to an auth account;
|
|
1448
|
+
// enforce workspace-wide uniqueness AND owner-only authz before
|
|
1449
|
+
// delegating to the per-repo create path. Stamping a handle on a new
|
|
1450
|
+
// entity is the same trust action as stamping it on an existing one,
|
|
1451
|
+
// so non-owners can't create a pre-linked person entity either.
|
|
1452
|
+
if (entityType === 'person') {
|
|
1453
|
+
try {
|
|
1454
|
+
validateUserHandleInPayload(workspaceManager, data);
|
|
1455
|
+
if (Object.prototype.hasOwnProperty.call(data, 'user_handle')) {
|
|
1456
|
+
assertCanChangeUserHandle(reqUser(req), '', data.user_handle);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
catch (err) {
|
|
1460
|
+
if (err instanceof BridgeError) {
|
|
1461
|
+
const status = err.code === 'OWNER_REQUIRED' ? 403 : 409;
|
|
1462
|
+
return reply.status(status).send({ error: err.message, code: err.code, ...err.details });
|
|
1463
|
+
}
|
|
1464
|
+
throw err;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
// Scope ref fields (today: task.assignee → person). Rejects a wikilink
|
|
1468
|
+
// that resolves to a non-person; forward references are allowed.
|
|
786
1469
|
try {
|
|
787
|
-
|
|
1470
|
+
assertEntityRefs(workspaceManager, entityType, data, { userExists: (id) => !!authService.findUserById(Number(id)) });
|
|
788
1471
|
}
|
|
789
1472
|
catch (err) {
|
|
790
|
-
|
|
1473
|
+
if (err instanceof EntityRefError || err instanceof UnknownUserRefError) {
|
|
1474
|
+
return reply.status(400).send({ error: err.message, code: err.code, field: err.field });
|
|
1475
|
+
}
|
|
1476
|
+
throw err;
|
|
791
1477
|
}
|
|
792
1478
|
try {
|
|
1479
|
+
// Detect implicit subfolder creation: if the entry lands in a folder
|
|
1480
|
+
// that didn't exist yet, the entity_change below only refreshes the
|
|
1481
|
+
// entry list — clients need a _folder event to refetch the folder tree
|
|
1482
|
+
// (sidebar + list/gallery folder rows). Mirrors the MCP create_entity
|
|
1483
|
+
// path; normalize the same way graph.create does so the existence
|
|
1484
|
+
// check matches the real on-disk path.
|
|
1485
|
+
const normFolder = folder_path ? validateFolderPath(folder_path) : '';
|
|
1486
|
+
const folderExisted = !normFolder || existsSync(join(graph.getRepoPath(), normFolder));
|
|
1487
|
+
const now = new Date().toISOString();
|
|
1488
|
+
const actorId = reqGitUser(req)?.id ?? 'web';
|
|
1489
|
+
const stampedData = {
|
|
1490
|
+
...data,
|
|
1491
|
+
entity_type: entityType,
|
|
1492
|
+
entity_id: entityId,
|
|
1493
|
+
created_at: data.created_at ?? now,
|
|
1494
|
+
updated_at: data.updated_at ?? now,
|
|
1495
|
+
created_by: data.created_by ?? actorId,
|
|
1496
|
+
updated_by: data.updated_by ?? actorId,
|
|
1497
|
+
};
|
|
793
1498
|
const entity = await graph.create({
|
|
794
1499
|
entityType: entityType,
|
|
795
1500
|
entityId,
|
|
796
|
-
data:
|
|
1501
|
+
data: stampedData,
|
|
797
1502
|
content,
|
|
1503
|
+
folderPath: folder_path,
|
|
798
1504
|
commitMessage: `Create ${entityType}/${entityId} from web UI`,
|
|
799
1505
|
user: reqGitUser(req),
|
|
800
1506
|
});
|
|
1507
|
+
const actor = req.user?.displayName ?? 'API';
|
|
1508
|
+
const ts = new Date().toISOString();
|
|
801
1509
|
wsHub?.broadcast({
|
|
802
1510
|
type: 'entity_change', action: 'created', repo, entityType, entityId,
|
|
803
|
-
actor:
|
|
804
|
-
source: 'api', timestamp: new Date().toISOString(),
|
|
1511
|
+
actor, source: 'api', timestamp: ts,
|
|
805
1512
|
});
|
|
1513
|
+
if (!folderExisted) {
|
|
1514
|
+
wsHub?.broadcast({
|
|
1515
|
+
type: 'entity_change', action: 'created', repo, entityType: '_folder', entityId: normFolder,
|
|
1516
|
+
actor, source: 'api', timestamp: ts,
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
806
1519
|
return reply.status(201).send(entity);
|
|
807
1520
|
}
|
|
808
1521
|
catch (err) {
|
|
809
1522
|
return reply.status(400).send({ error: err.message });
|
|
810
1523
|
}
|
|
811
1524
|
});
|
|
812
|
-
// POST /api/repos/:repo/
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1525
|
+
// POST /api/repos/:repo/font-skills — package selected font assets into a
|
|
1526
|
+
// `skill` entity. The server reads each file's REAL metadata (typographic
|
|
1527
|
+
// family / weight / italic via fontkit), groups faces into families, and
|
|
1528
|
+
// writes a skill whose markdown body carries an orienting note, the font
|
|
1529
|
+
// files as chips, and the derived @font-face block. The skill is then
|
|
1530
|
+
// auto-discovered by the agent (skill index) and used to make on-brand
|
|
1531
|
+
// artifacts. `:repo` is the DESTINATION folder; each asset is downloaded from
|
|
1532
|
+
// its own owning repo.
|
|
1533
|
+
fastify.post('/api/repos/:repo/font-skills', async (req, reply) => {
|
|
817
1534
|
const { repo } = req.params;
|
|
818
|
-
|
|
819
|
-
|
|
1535
|
+
const graph = graphForWrite(req, reply, repo); // dest-folder write access
|
|
1536
|
+
if (!graph)
|
|
1537
|
+
return;
|
|
1538
|
+
const { name, folder_path } = req.body ?? {};
|
|
1539
|
+
const requested = Array.isArray(req.body?.assets) ? req.body.assets : [];
|
|
1540
|
+
if (requested.length === 0) {
|
|
1541
|
+
return reply.status(400).send({ error: 'No font assets supplied.' });
|
|
1542
|
+
}
|
|
1543
|
+
// SECURITY: never trust the client-supplied `repo`. Asset blobs are keyed
|
|
1544
|
+
// by an immutable workspace-global key, so downloadAssetById reads them
|
|
1545
|
+
// regardless of which repo's graph you call — authorization MUST come from
|
|
1546
|
+
// the asset INDEX (its real owning repo + filename), exactly like the
|
|
1547
|
+
// exporter. Resolve every input here; unknown/filename-mismatch refs are
|
|
1548
|
+
// dropped (reported as skipped), and the resolved owning-repo is what the
|
|
1549
|
+
// per-asset read check + download then use.
|
|
1550
|
+
const assetIndex = workspaceManager.getAssetIndex();
|
|
1551
|
+
const indexSkipped = [];
|
|
1552
|
+
const resolvedAssets = [];
|
|
1553
|
+
for (const a of requested) {
|
|
1554
|
+
const row = a?.assetId ? assetIndex.get(a.assetId) : undefined;
|
|
1555
|
+
if (!row || row.filename !== a.filename) {
|
|
1556
|
+
indexSkipped.push({ assetId: a?.assetId ?? '', filename: a?.filename ?? '', reason: 'unknown' });
|
|
1557
|
+
continue;
|
|
1558
|
+
}
|
|
1559
|
+
// Reject non-font rows up front — don't download + fontkit-parse an
|
|
1560
|
+
// arbitrary (possibly large) blob just to discard it as unparseable.
|
|
1561
|
+
if (!isFontAssetRow(row)) {
|
|
1562
|
+
indexSkipped.push({ assetId: a.assetId, filename: a.filename, reason: 'not-a-font' });
|
|
1563
|
+
continue;
|
|
1564
|
+
}
|
|
1565
|
+
resolvedAssets.push({ assetId: a.assetId, filename: a.filename, repo: row.repo });
|
|
820
1566
|
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
1567
|
+
let result;
|
|
1568
|
+
try {
|
|
1569
|
+
result = await buildFontSkill({
|
|
1570
|
+
assets: resolvedAssets,
|
|
1571
|
+
nameOverride: name,
|
|
1572
|
+
// Read authz against the asset's REAL owning repo (from the index),
|
|
1573
|
+
// not the caller-supplied one. download() only runs after canRead.
|
|
1574
|
+
canRead: (assetRepo) => hasRepoAccess(req, assetRepo, workspaceManager, authService),
|
|
1575
|
+
download: (assetRepo, assetId, filename) => workspaceManager.getGraph(assetRepo).downloadAssetById(assetId, filename),
|
|
1576
|
+
});
|
|
829
1577
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1578
|
+
catch (err) {
|
|
1579
|
+
return reply.status(400).send({ error: err?.message ?? String(err) });
|
|
1580
|
+
}
|
|
1581
|
+
const skipped = [...indexSkipped, ...result.skipped];
|
|
1582
|
+
if (!result.draft) {
|
|
1583
|
+
return reply.status(422).send({ error: 'No readable font files in the selection.', skipped });
|
|
1584
|
+
}
|
|
1585
|
+
const draft = result.draft;
|
|
1586
|
+
const normFolder = folder_path ? validateFolderPath(folder_path) : '';
|
|
1587
|
+
const folderExisted = !normFolder || existsSync(join(graph.getRepoPath(), normFolder));
|
|
1588
|
+
const actor = req.user?.displayName ?? 'API';
|
|
1589
|
+
// Avoid clobbering an existing skill at the slug — append -2, -3, ….
|
|
1590
|
+
let entityId = draft.entityId;
|
|
1591
|
+
for (let i = 2; i < 1000; i++) {
|
|
1592
|
+
try {
|
|
1593
|
+
graph.get('skill', entityId);
|
|
1594
|
+
entityId = `${draft.entityId}-${i}`;
|
|
1595
|
+
}
|
|
1596
|
+
catch {
|
|
1597
|
+
break;
|
|
1598
|
+
}
|
|
833
1599
|
}
|
|
834
|
-
const
|
|
1600
|
+
const now = new Date().toISOString();
|
|
1601
|
+
const actorId = reqGitUser(req)?.id ?? 'web';
|
|
835
1602
|
try {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
1603
|
+
await graph.create({
|
|
1604
|
+
entityType: 'skill',
|
|
1605
|
+
entityId,
|
|
1606
|
+
data: {
|
|
1607
|
+
entity_type: 'skill',
|
|
1608
|
+
entity_id: entityId,
|
|
1609
|
+
name: draft.name,
|
|
1610
|
+
description: draft.description,
|
|
1611
|
+
applies_to: ['artifact', 'deck', 'document'],
|
|
1612
|
+
loading: 'on-demand',
|
|
1613
|
+
tags: ['fonts'],
|
|
1614
|
+
created_at: now, updated_at: now, created_by: actorId, updated_by: actorId,
|
|
1615
|
+
},
|
|
1616
|
+
content: draft.content,
|
|
1617
|
+
folderPath: folder_path,
|
|
1618
|
+
commitMessage: `Create font skill ${draft.name} from web UI`,
|
|
1619
|
+
user: reqGitUser(req),
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
catch (err) {
|
|
1623
|
+
return reply.status(400).send({ error: err?.message ?? String(err), skipped });
|
|
1624
|
+
}
|
|
1625
|
+
wsHub?.broadcast({ type: 'entity_change', action: 'created', repo, entityType: 'skill', entityId, actor, source: 'api', timestamp: now });
|
|
1626
|
+
if (!folderExisted) {
|
|
1627
|
+
wsHub?.broadcast({ type: 'entity_change', action: 'created', repo, entityType: '_folder', entityId: normFolder, actor, source: 'api', timestamp: new Date().toISOString() });
|
|
1628
|
+
}
|
|
1629
|
+
return reply.status(201).send({
|
|
1630
|
+
created: { repo, entityId, name: draft.name, families: draft.familyNames, faces: draft.faceCount },
|
|
1631
|
+
skipped,
|
|
1632
|
+
});
|
|
1633
|
+
});
|
|
1634
|
+
// POST /api/repos/:repo/move — move entry to a different folder within the
|
|
1635
|
+
// same repo. Distinct from /transfer (cross-repo) and /rename (id change).
|
|
1636
|
+
// The entry's type and id are unchanged; only its folder_path moves.
|
|
1637
|
+
fastify.post('/api/repos/:repo/move', async (req, reply) => {
|
|
1638
|
+
const { repo } = req.params;
|
|
1639
|
+
const { entityType, entityId, folder_path } = req.body ?? {};
|
|
1640
|
+
if (!entityType || !entityId || folder_path === undefined) {
|
|
1641
|
+
return reply.status(400).send({ error: 'entityType, entityId, and folder_path are required' });
|
|
1642
|
+
}
|
|
1643
|
+
// Access enforced by graphForWrite (WorkspaceAccessService).
|
|
1644
|
+
const graph = graphForWrite(req, reply, repo);
|
|
1645
|
+
if (!graph)
|
|
1646
|
+
return;
|
|
1647
|
+
try {
|
|
1648
|
+
if (sessionManager)
|
|
1649
|
+
await sessionManager.flush(repo);
|
|
1650
|
+
// If the destination folder doesn't exist yet, the move creates it —
|
|
1651
|
+
// emit a _folder event (alongside the entry update) so clients refetch
|
|
1652
|
+
// the folder tree, not just the entry list. Mirrors MCP move_entity.
|
|
1653
|
+
const normTarget = folder_path ? validateFolderPath(folder_path) : '';
|
|
1654
|
+
const targetExisted = !normTarget || existsSync(join(graph.getRepoPath(), normTarget));
|
|
1655
|
+
const moved = await graph.move(entityType, entityId, folder_path, undefined, reqGitUser(req));
|
|
1656
|
+
const actor = req.user?.displayName ?? 'API';
|
|
1657
|
+
const ts = new Date().toISOString();
|
|
1658
|
+
wsHub?.broadcast({
|
|
1659
|
+
type: 'entity_change', action: 'updated', repo, entityType, entityId,
|
|
1660
|
+
actor, source: 'api', timestamp: ts,
|
|
1661
|
+
});
|
|
1662
|
+
if (!targetExisted) {
|
|
1663
|
+
wsHub?.broadcast({
|
|
1664
|
+
type: 'entity_change', action: 'created', repo, entityType: '_folder', entityId: normTarget,
|
|
1665
|
+
actor, source: 'api', timestamp: ts,
|
|
1666
|
+
});
|
|
876
1667
|
}
|
|
877
|
-
|
|
878
|
-
const entityCount = results.reduce((sum, r) => sum + (r.entities?.length ?? 0), 0);
|
|
879
|
-
return {
|
|
880
|
-
success: true,
|
|
881
|
-
imported: created,
|
|
882
|
-
entityCount,
|
|
883
|
-
skipped: results.filter(r => r.status === 'error').length,
|
|
884
|
-
errors: results.filter(r => r.status === 'error').map(r => r.error),
|
|
885
|
-
results,
|
|
886
|
-
};
|
|
1668
|
+
return reply.send({ success: true, entity: moved });
|
|
887
1669
|
}
|
|
888
1670
|
catch (err) {
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1671
|
+
const status = err?.name === 'InvalidFolderPathError' ? 400
|
|
1672
|
+
: err.message?.includes('already occupied') ? 409
|
|
1673
|
+
: err.message?.includes('not found') ? 404
|
|
1674
|
+
: 400;
|
|
1675
|
+
return reply.status(status).send({ error: err.message });
|
|
1676
|
+
}
|
|
1677
|
+
});
|
|
1678
|
+
// POST /api/repos/:repo/folders — create a folder (with .gitkeep). Auto-creates parents.
|
|
1679
|
+
// Optional display_name is written to the folder's .studiograph-folder.json
|
|
1680
|
+
// sidecar when set; otherwise the client falls back to toDisplayName(slug).
|
|
1681
|
+
fastify.post('/api/repos/:repo/folders', async (req, reply) => {
|
|
1682
|
+
const { repo } = req.params;
|
|
1683
|
+
const { path, display_name } = req.body ?? {};
|
|
1684
|
+
if (!path || typeof path !== 'string') {
|
|
1685
|
+
return reply.status(400).send({ error: 'path is required' });
|
|
1686
|
+
}
|
|
1687
|
+
// Access enforced by graphForWrite (WorkspaceAccessService).
|
|
1688
|
+
const graph = graphForWrite(req, reply, repo);
|
|
1689
|
+
if (!graph)
|
|
1690
|
+
return;
|
|
1691
|
+
try {
|
|
1692
|
+
const { alreadyExisted } = await graph.createFolder(path, undefined, reqGitUser(req), {
|
|
1693
|
+
displayName: display_name,
|
|
892
1694
|
});
|
|
1695
|
+
// Emit the _folder sentinel so OTHER connected clients refetch the
|
|
1696
|
+
// folder tree (sidebar + list/gallery folder rows). The local creator
|
|
1697
|
+
// already updated optimistically via workspace.addFolder.
|
|
1698
|
+
wsHub?.broadcast({
|
|
1699
|
+
type: 'entity_change', action: 'created', repo, entityType: '_folder', entityId: validateFolderPath(path),
|
|
1700
|
+
actor: req.user?.displayName ?? 'API',
|
|
1701
|
+
source: 'api', timestamp: new Date().toISOString(),
|
|
1702
|
+
});
|
|
1703
|
+
// createFolder is an idempotent ensure; report reuse so the caller isn't
|
|
1704
|
+
// misled into thinking a duplicate name minted a new folder.
|
|
1705
|
+
return reply.status(201).send({ success: true, path, alreadyExisted });
|
|
1706
|
+
}
|
|
1707
|
+
catch (err) {
|
|
1708
|
+
return reply.status(400).send({ error: err.message });
|
|
893
1709
|
}
|
|
894
1710
|
});
|
|
895
|
-
//
|
|
896
|
-
|
|
1711
|
+
// PATCH /api/repos/:repo/folders — rename / move a folder within the repo,
|
|
1712
|
+
// optionally updating its display_name in the same commit. Three shapes:
|
|
1713
|
+
// { from, to } — slug-only rename / move
|
|
1714
|
+
// { from, to, display_name } — slug change + display update
|
|
1715
|
+
// { from, to: from, display_name } — display-only update (no FS move)
|
|
1716
|
+
// `display_name: ""` (or null) clears the override and reverts to the slug
|
|
1717
|
+
// fallback.
|
|
1718
|
+
fastify.patch('/api/repos/:repo/folders', async (req, reply) => {
|
|
897
1719
|
const { repo } = req.params;
|
|
898
|
-
|
|
899
|
-
|
|
1720
|
+
const { from, to, display_name } = req.body ?? {};
|
|
1721
|
+
if (!from || !to) {
|
|
1722
|
+
return reply.status(400).send({ error: 'from and to are required' });
|
|
1723
|
+
}
|
|
1724
|
+
// Access enforced by graphForWrite (WorkspaceAccessService).
|
|
1725
|
+
const graph = graphForWrite(req, reply, repo);
|
|
1726
|
+
if (!graph)
|
|
1727
|
+
return;
|
|
1728
|
+
try {
|
|
1729
|
+
if (sessionManager)
|
|
1730
|
+
await sessionManager.flush(repo);
|
|
1731
|
+
await graph.renameFolder(from, to, undefined, reqGitUser(req), {
|
|
1732
|
+
displayName: display_name === undefined ? undefined : display_name,
|
|
1733
|
+
});
|
|
1734
|
+
// Rename/move relocates entries on disk, so the _folder sentinel's
|
|
1735
|
+
// refreshRepoAfterFolderMutation (tree + entity summaries) is exactly
|
|
1736
|
+
// what other clients need. Carries the new path as the entityId.
|
|
1737
|
+
wsHub?.broadcast({
|
|
1738
|
+
type: 'entity_change', action: 'updated', repo, entityType: '_folder', entityId: validateFolderPath(to),
|
|
1739
|
+
actor: req.user?.displayName ?? 'API',
|
|
1740
|
+
source: 'api', timestamp: new Date().toISOString(),
|
|
1741
|
+
});
|
|
1742
|
+
return reply.send({ success: true, from, to });
|
|
900
1743
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
1744
|
+
catch (err) {
|
|
1745
|
+
const status = err?.name === 'InvalidFolderPathError' ? 400
|
|
1746
|
+
: err.message?.includes('not found') ? 404
|
|
1747
|
+
: err.message?.includes('already exists') ? 409
|
|
1748
|
+
: 400;
|
|
1749
|
+
return reply.status(status).send({ error: err.message });
|
|
1750
|
+
}
|
|
1751
|
+
});
|
|
1752
|
+
// DELETE /api/repos/:repo/folders?path=X&recursive=true — delete a folder.
|
|
1753
|
+
// Without recursive=true, non-empty folders return 409 with blast-radius
|
|
1754
|
+
// counts so the UI can confirm before retrying.
|
|
1755
|
+
fastify.delete('/api/repos/:repo/folders', async (req, reply) => {
|
|
1756
|
+
const { repo } = req.params;
|
|
1757
|
+
const { path, recursive } = req.query;
|
|
1758
|
+
if (!path) {
|
|
1759
|
+
return reply.status(400).send({ error: 'path query parameter is required' });
|
|
1760
|
+
}
|
|
1761
|
+
// Access enforced by graphForWrite (WorkspaceAccessService).
|
|
1762
|
+
const graph = graphForWrite(req, reply, repo);
|
|
1763
|
+
if (!graph)
|
|
1764
|
+
return;
|
|
1765
|
+
// Workspace Assets store gate (codex P1 / D2). A recursive delete in a
|
|
1766
|
+
// workspace_wide store destroys every asset under the folder, bypassing
|
|
1767
|
+
// the per-asset uploader/owner rule — restrict it to a workspace owner.
|
|
1768
|
+
// (Non-recursive delete is blocked on non-empty folders by FOLDER_NOT_EMPTY,
|
|
1769
|
+
// so it can't take assets with it.)
|
|
1770
|
+
if (recursive === 'true' && workspaceManager.getRepoConfig(repo)?.workspace_wide && !isWorkspaceOwner(reqUser(req)?.role ?? '')) {
|
|
1771
|
+
return reply.status(403).send({ error: 'Only a workspace owner can recursively delete folders in the shared Assets store.' });
|
|
904
1772
|
}
|
|
905
1773
|
try {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1774
|
+
if (sessionManager)
|
|
1775
|
+
await sessionManager.flush(repo);
|
|
1776
|
+
const result = await graph.deleteFolder(path, {
|
|
1777
|
+
recursive: recursive === 'true',
|
|
1778
|
+
user: reqGitUser(req),
|
|
1779
|
+
});
|
|
1780
|
+
// Emit the _folder sentinel so other clients drop the folder (and any
|
|
1781
|
+
// entries it held, via the entity-summary refresh) from their view.
|
|
1782
|
+
wsHub?.broadcast({
|
|
1783
|
+
type: 'entity_change', action: 'deleted', repo, entityType: '_folder', entityId: validateFolderPath(path),
|
|
1784
|
+
actor: req.user?.displayName ?? 'API',
|
|
1785
|
+
source: 'api', timestamp: new Date().toISOString(),
|
|
1786
|
+
});
|
|
1787
|
+
return reply.send({ success: true, ...result });
|
|
1788
|
+
}
|
|
1789
|
+
catch (err) {
|
|
1790
|
+
if (err?.code === 'FOLDER_NOT_EMPTY') {
|
|
1791
|
+
return reply.status(409).send({
|
|
1792
|
+
error: err.message,
|
|
1793
|
+
code: 'FOLDER_NOT_EMPTY',
|
|
1794
|
+
blast_radius: err.blastRadius,
|
|
910
1795
|
});
|
|
911
1796
|
}
|
|
912
|
-
const
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
if (result.created > 0) {
|
|
917
|
-
const git = graph.getGitService();
|
|
918
|
-
if (git) {
|
|
919
|
-
const commitUser = user ?? { id: 'import', name: 'Import', email: 'import@studiograph' };
|
|
920
|
-
git.commitAll(`Import ${result.created} bookmark${result.created !== 1 ? 's' : ''} as references`, commitUser);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
return {
|
|
924
|
-
success: true,
|
|
925
|
-
created: result.created,
|
|
926
|
-
skipped: result.skipped,
|
|
927
|
-
errors: result.errors,
|
|
928
|
-
};
|
|
1797
|
+
const status = err?.name === 'InvalidFolderPathError' ? 400
|
|
1798
|
+
: err.message?.includes('not found') ? 404
|
|
1799
|
+
: 400;
|
|
1800
|
+
return reply.status(status).send({ error: err.message });
|
|
929
1801
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
});
|
|
1802
|
+
});
|
|
1803
|
+
// POST /api/repos/:repo/commit — flush deferred commits (navigate-away signal)
|
|
1804
|
+
fastify.post('/api/repos/:repo/commit', async (req, reply) => {
|
|
1805
|
+
if (!sessionManager) {
|
|
1806
|
+
return reply.send({ committed: false });
|
|
1807
|
+
}
|
|
1808
|
+
const { repo } = req.params;
|
|
1809
|
+
// Access enforced via accessService.canRead \u2014 flush operates on the
|
|
1810
|
+
// SessionManager (no graph instance needed), so a boolean check is
|
|
1811
|
+
// sufficient. Pending commits were already authored by an authenticated
|
|
1812
|
+
// writer; this endpoint just flushes them on navigate-away.
|
|
1813
|
+
if (!accessService.canRead(reqUser(req), repo)) {
|
|
1814
|
+
return reply.status(404).send({ error: `Folder \u201C${repo}\u201D not found` });
|
|
935
1815
|
}
|
|
1816
|
+
const committed = await sessionManager.flush(repo);
|
|
1817
|
+
return reply.send({ committed });
|
|
936
1818
|
});
|
|
937
|
-
// POST /api/repos/:repo/
|
|
938
|
-
|
|
1819
|
+
// POST /api/repos/:repo/assets — generic asset upload.
|
|
1820
|
+
//
|
|
1821
|
+
// Multipart `file` field. Asset-as-embed (B2): stores a FREE-STANDING,
|
|
1822
|
+
// folder-owned asset (assets.db row + immutable-key blob) — NO wrapper
|
|
1823
|
+
// entity. Returns { assetId, asset_url, ... } for the caller to embed.
|
|
1824
|
+
// For an IMAGE, graph.uploadAsset fires the caption/OCR trigger (assetId-
|
|
1825
|
+
// keyed), and the caption folds into any entry that embeds the asset. See
|
|
1826
|
+
// docs/asset-architecture.md.
|
|
1827
|
+
fastify.post('/api/repos/:repo/assets', async (req, reply) => {
|
|
939
1828
|
const { repo } = req.params;
|
|
940
|
-
|
|
941
|
-
|
|
1829
|
+
// Access enforced by graphForWrite (WorkspaceAccessService).
|
|
1830
|
+
const graph = graphForWrite(req, reply, repo);
|
|
1831
|
+
if (!graph)
|
|
1832
|
+
return;
|
|
1833
|
+
// Per-route fileSize override — the global multipart limit (10MB) is
|
|
1834
|
+
// tuned for inline image embeds; full asset uploads accept up to the
|
|
1835
|
+
// 100MB cap enforced inside AssetService.upload().
|
|
1836
|
+
let file;
|
|
1837
|
+
try {
|
|
1838
|
+
file = await req.file({ limits: { fileSize: 100 * 1024 * 1024 } });
|
|
942
1839
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
return reply.status(400).send({ error: 'Expected { files: [{ filename, content }] }' });
|
|
1840
|
+
catch (err) {
|
|
1841
|
+
return reply.status(400).send({ error: err?.message ?? 'Invalid multipart upload' });
|
|
946
1842
|
}
|
|
1843
|
+
if (!file)
|
|
1844
|
+
return reply.status(400).send({ error: 'No file uploaded' });
|
|
1845
|
+
const buffer = await file.toBuffer();
|
|
947
1846
|
try {
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
//
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1847
|
+
// Asset-as-embed (B2): uploading to the Assets store produces a
|
|
1848
|
+
// FREE-STANDING, folder-owned asset — NOT an `asset` entity. The asset
|
|
1849
|
+
// manager lists it directly; the homepage capture embeds its URL into
|
|
1850
|
+
// the entry it creates and deletes it by assetId if unused. Folder-owned
|
|
1851
|
+
// assets are repo-scoped, so the `folder` subpath doesn't apply here.
|
|
1852
|
+
const displayName = req.query.name?.trim()
|
|
1853
|
+
|| deriveAssetDisplayName(file.filename)
|
|
1854
|
+
|| file.filename;
|
|
1855
|
+
const stored = await graph.uploadAsset('material', // ignored for ownership when `owner` is set
|
|
1856
|
+
'asset', // ignored
|
|
1857
|
+
file.filename, buffer, undefined, {
|
|
1858
|
+
owner: { type: 'folder', id: repo },
|
|
1859
|
+
uploaderUserId: reqUser(req)?.id != null ? String(reqUser(req).id) : null,
|
|
1860
|
+
name: displayName,
|
|
1861
|
+
});
|
|
1862
|
+
wsHub?.broadcast({
|
|
1863
|
+
type: 'repo_sync',
|
|
1864
|
+
repo,
|
|
1865
|
+
actor: req.user?.displayName ?? 'API',
|
|
1866
|
+
source: 'api',
|
|
1867
|
+
summary: 'uploaded an asset',
|
|
1868
|
+
workspaceWide: workspaceManager.getRepoConfig(repo)?.workspace_wide === true,
|
|
1869
|
+
timestamp: new Date().toISOString(),
|
|
1870
|
+
});
|
|
1871
|
+
// NOTE: image caption/OCR indexing is NOT derived for this
|
|
1872
|
+
// free-standing asset — the caption sweep + live drain key on `asset`
|
|
1873
|
+
// ENTITIES, which this no longer creates. It stays filename-only in
|
|
1874
|
+
// vector search until the caption pipeline is rewired to be
|
|
1875
|
+
// assets.db-row-keyed (tracked follow-up).
|
|
1876
|
+
return reply.send({
|
|
961
1877
|
success: true,
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1878
|
+
assetId: stored.assetId,
|
|
1879
|
+
asset_url: stored.publicUrl,
|
|
1880
|
+
asset_kind: stored.mediaType,
|
|
1881
|
+
filename: stored.filename,
|
|
1882
|
+
});
|
|
966
1883
|
}
|
|
967
1884
|
catch (err) {
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1885
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1886
|
+
const status = msg.includes('not configured') ? 501
|
|
1887
|
+
: msg.includes('Unsupported asset extension') ? 415
|
|
1888
|
+
: 400;
|
|
1889
|
+
return reply.status(status).send({ success: false, error: msg });
|
|
972
1890
|
}
|
|
973
1891
|
});
|
|
974
1892
|
}
|