studiograph 1.3.47 → 1.3.48-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -16
- package/dist/agent/agent-pool.d.ts +117 -0
- package/dist/agent/agent-pool.js +287 -0
- package/dist/agent/agent-pool.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 +113 -22
- package/dist/agent/orchestrator.js +573 -181
- package/dist/agent/orchestrator.js.map +1 -1
- package/dist/agent/prompts/entity-types-section.d.ts +36 -0
- package/dist/agent/prompts/entity-types-section.js +90 -0
- package/dist/agent/prompts/entity-types-section.js.map +1 -0
- package/dist/agent/prompts/system.md +148 -69
- package/dist/agent/skill-loader.d.ts +30 -17
- package/dist/agent/skill-loader.js +63 -30
- package/dist/agent/skill-loader.js.map +1 -1
- package/dist/agent/skills/entity-schema.md +77 -433
- package/dist/agent/skills/interview/entity-templates.md +193 -0
- package/dist/agent/skills/interview/question-domains.md +391 -0
- package/dist/agent/skills/interview/skill.md +204 -0
- package/dist/agent/skills/schema-rules.md +54 -0
- package/dist/agent/tools/capture-tools.d.ts +31 -0
- package/dist/agent/tools/capture-tools.js +40 -0
- package/dist/agent/tools/capture-tools.js.map +1 -0
- package/dist/agent/tools/folder-tools.d.ts +47 -0
- package/dist/agent/tools/folder-tools.js +249 -0
- package/dist/agent/tools/folder-tools.js.map +1 -0
- package/dist/agent/tools/fs-tools.d.ts +7 -6
- package/dist/agent/tools/fs-tools.js +5 -4
- package/dist/agent/tools/fs-tools.js.map +1 -1
- package/dist/agent/tools/graph-tools.d.ts +21 -58
- package/dist/agent/tools/graph-tools.js +635 -332
- package/dist/agent/tools/graph-tools.js.map +1 -1
- package/dist/agent/tools/history-tools.d.ts +95 -0
- package/dist/agent/tools/history-tools.js +461 -0
- package/dist/agent/tools/history-tools.js.map +1 -0
- package/dist/agent/tools/load-skill.d.ts +6 -5
- package/dist/agent/tools/load-skill.js +3 -3
- package/dist/agent/tools/load-skill.js.map +1 -1
- package/dist/agent/tools/ops-tools.d.ts +5 -4
- package/dist/agent/tools/ops-tools.js +130 -236
- package/dist/agent/tools/ops-tools.js.map +1 -1
- package/dist/agent/tools/permission-tools.d.ts +87 -17
- package/dist/agent/tools/permission-tools.js +233 -38
- 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.d.ts +18 -0
- package/dist/agent/tools/web-tools.js +283 -0
- package/dist/agent/tools/web-tools.js.map +1 -0
- 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 +186 -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 +46 -0
- package/dist/cli/commands/admin/index.js.map +1 -0
- package/dist/cli/commands/admin/migrate-assets.d.ts +53 -0
- package/dist/cli/commands/admin/migrate-assets.js +184 -0
- package/dist/cli/commands/admin/migrate-assets.js.map +1 -0
- package/dist/cli/commands/admin/migrate.d.ts +56 -0
- package/dist/cli/commands/admin/migrate.js +263 -0
- package/dist/cli/commands/admin/migrate.js.map +1 -0
- package/dist/cli/commands/admin/restore.d.ts +24 -0
- package/dist/cli/commands/admin/restore.js +228 -0
- package/dist/cli/commands/admin/restore.js.map +1 -0
- package/dist/cli/commands/admin/secrets.d.ts +23 -0
- package/dist/cli/commands/admin/secrets.js +256 -0
- package/dist/cli/commands/admin/secrets.js.map +1 -0
- package/dist/cli/commands/admin/settings.d.ts +28 -0
- package/dist/cli/commands/admin/settings.js +284 -0
- package/dist/cli/commands/admin/settings.js.map +1 -0
- package/dist/cli/commands/admin/token.d.ts +42 -0
- package/dist/cli/commands/admin/token.js +126 -0
- package/dist/cli/commands/admin/token.js.map +1 -0
- package/dist/cli/commands/admin/transfer-ownership.d.ts +15 -0
- package/dist/cli/commands/admin/transfer-ownership.js +106 -0
- package/dist/cli/commands/admin/transfer-ownership.js.map +1 -0
- package/dist/cli/commands/admin/user.d.ts +11 -0
- package/dist/cli/commands/admin/user.js +187 -0
- package/dist/cli/commands/admin/user.js.map +1 -0
- package/dist/cli/commands/clone.d.ts +25 -3
- package/dist/cli/commands/clone.js +142 -36
- package/dist/cli/commands/clone.js.map +1 -1
- package/dist/cli/commands/commit.d.ts +1 -1
- package/dist/cli/commands/commit.js +24 -11
- package/dist/cli/commands/commit.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 +2 -2
- 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 +12 -4
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/orphans.js +14 -30
- package/dist/cli/commands/orphans.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 +142 -63
- package/dist/cli/commands/reset.js.map +1 -1
- package/dist/cli/commands/serve.js +209 -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 +372 -202
- 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 -35
- 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 +33 -52
- package/dist/cli/setup-wizard.js +43 -68
- package/dist/cli/setup-wizard.js.map +1 -1
- package/dist/core/accent-palette.d.ts +22 -0
- package/dist/core/accent-palette.js +47 -0
- package/dist/core/accent-palette.js.map +1 -0
- package/dist/core/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 +56 -0
- package/dist/core/embeds.js +103 -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 +53 -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 +140 -0
- package/dist/core/entity-types-catalog.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/graph.d.ts +675 -14
- package/dist/core/graph.js +2118 -314
- 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/schema-registry.d.ts +59 -0
- package/dist/core/schema-registry.js +198 -0
- 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/voyage.d.ts +13 -0
- package/dist/core/secrets/probes/voyage.js +52 -0
- package/dist/core/secrets/probes/voyage.js.map +1 -0
- package/dist/core/secrets/r2-structural-migration.d.ts +39 -0
- package/dist/core/secrets/r2-structural-migration.js +109 -0
- package/dist/core/secrets/r2-structural-migration.js.map +1 -0
- package/dist/core/secrets/read-flip.d.ts +59 -0
- package/dist/core/secrets/read-flip.js +87 -0
- package/dist/core/secrets/read-flip.js.map +1 -0
- package/dist/core/secrets/registry.d.ts +188 -0
- package/dist/core/secrets/registry.js +598 -0
- package/dist/core/secrets/registry.js.map +1 -0
- package/dist/core/types.d.ts +77 -455
- package/dist/core/types.js +17 -3
- package/dist/core/types.js.map +1 -1
- package/dist/core/user-config.d.ts +75 -7
- package/dist/core/user-config.js +155 -7
- package/dist/core/user-config.js.map +1 -1
- package/dist/core/validation.d.ts +717 -1791
- package/dist/core/validation.js +357 -228
- package/dist/core/validation.js.map +1 -1
- package/dist/core/workspace-manager.d.ts +79 -12
- package/dist/core/workspace-manager.js +213 -73
- package/dist/core/workspace-manager.js.map +1 -1
- package/dist/core/workspace.d.ts +19 -5
- package/dist/core/workspace.js +69 -35
- 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 +94 -13
- package/dist/mcp/connector-manager.js +283 -62
- package/dist/mcp/connector-manager.js.map +1 -1
- package/dist/mcp/oauth-provider.d.ts +27 -33
- package/dist/mcp/oauth-provider.js +65 -134
- package/dist/mcp/oauth-provider.js.map +1 -1
- package/dist/mcp/oauth-registry.d.ts +62 -0
- package/dist/mcp/oauth-registry.js +85 -0
- package/dist/mcp/oauth-registry.js.map +1 -0
- package/dist/mcp/server.d.ts +11 -3
- package/dist/mcp/server.js +30 -5
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/state-signer.d.ts +41 -0
- package/dist/mcp/state-signer.js +120 -0
- package/dist/mcp/state-signer.js.map +1 -0
- package/dist/mcp/tools.d.ts +35 -2
- package/dist/mcp/tools.js +1051 -117
- package/dist/mcp/tools.js.map +1 -1
- package/dist/server/__tests__/helpers/graph-api.d.ts +18 -0
- package/dist/server/__tests__/helpers/graph-api.js +16 -0
- package/dist/server/__tests__/helpers/graph-api.js.map +1 -0
- package/dist/server/body-write-coordinator.d.ts +151 -0
- package/dist/server/body-write-coordinator.js +161 -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 +14 -3
- package/dist/server/index.js +451 -193
- package/dist/server/index.js.map +1 -1
- package/dist/server/middleware/sanitize.d.ts +46 -0
- package/dist/server/middleware/sanitize.js +171 -0
- package/dist/server/middleware/sanitize.js.map +1 -0
- package/dist/server/routes/asset-api.d.ts +10 -0
- package/dist/server/routes/asset-api.js +294 -0
- package/dist/server/routes/asset-api.js.map +1 -0
- package/dist/server/routes/audit-api.d.ts +24 -0
- package/dist/server/routes/audit-api.js +117 -0
- package/dist/server/routes/audit-api.js.map +1 -0
- package/dist/server/routes/auth-api.d.ts +3 -2
- package/dist/server/routes/auth-api.js +519 -38
- package/dist/server/routes/auth-api.js.map +1 -1
- package/dist/server/routes/capture-api.d.ts +18 -0
- package/dist/server/routes/capture-api.js +257 -0
- package/dist/server/routes/capture-api.js.map +1 -0
- package/dist/server/routes/chat.d.ts +4 -1
- package/dist/server/routes/chat.js +298 -88
- 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 +21 -0
- package/dist/server/routes/config-api.js +256 -0
- package/dist/server/routes/config-api.js.map +1 -0
- package/dist/server/routes/connectors-api.d.ts +17 -0
- package/dist/server/routes/connectors-api.js +410 -0
- package/dist/server/routes/connectors-api.js.map +1 -0
- 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/git-http.d.ts +3 -3
- package/dist/server/routes/git-http.js +86 -38
- package/dist/server/routes/git-http.js.map +1 -1
- package/dist/server/routes/graph-api-access.d.ts +35 -0
- package/dist/server/routes/graph-api-access.js +105 -0
- package/dist/server/routes/graph-api-access.js.map +1 -0
- package/dist/server/routes/graph-api.d.ts +3 -2
- package/dist/server/routes/graph-api.js +1321 -263
- package/dist/server/routes/graph-api.js.map +1 -1
- package/dist/server/routes/health.d.ts +18 -0
- package/dist/server/routes/health.js +74 -0
- package/dist/server/routes/health.js.map +1 -0
- package/dist/server/routes/insights-api.d.ts +1 -2
- package/dist/server/routes/insights-api.js +149 -41
- package/dist/server/routes/insights-api.js.map +1 -1
- package/dist/server/routes/mcp.d.ts +7 -3
- package/dist/server/routes/mcp.js +22 -6
- package/dist/server/routes/mcp.js.map +1 -1
- package/dist/server/routes/permissions-api.d.ts +9 -2
- package/dist/server/routes/permissions-api.js +87 -30
- package/dist/server/routes/permissions-api.js.map +1 -1
- package/dist/server/routes/r2-api.d.ts +25 -0
- package/dist/server/routes/r2-api.js +276 -0
- package/dist/server/routes/r2-api.js.map +1 -0
- package/dist/server/routes/secrets-api.d.ts +36 -0
- package/dist/server/routes/secrets-api.js +308 -0
- package/dist/server/routes/secrets-api.js.map +1 -0
- package/dist/server/routes/settings-api.d.ts +29 -0
- package/dist/server/routes/settings-api.js +180 -0
- package/dist/server/routes/settings-api.js.map +1 -0
- package/dist/server/routes/url-api.d.ts +7 -0
- package/dist/server/routes/url-api.js +74 -0
- package/dist/server/routes/url-api.js.map +1 -0
- package/dist/server/routes/views-api.js +81 -7
- 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 +124 -35
- 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 +64 -16
- 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 -15
- package/dist/server/session-manager.js.map +1 -1
- package/dist/server/ws-hub.d.ts +138 -14
- package/dist/server/ws-hub.js +0 -0
- package/dist/server/ws-hub.js.map +1 -1
- package/dist/server/yjs-manager.d.ts +285 -7
- package/dist/server/yjs-manager.js +907 -36
- package/dist/server/yjs-manager.js.map +1 -1
- package/dist/server/yjs-persistence.d.ts +165 -0
- package/dist/server/yjs-persistence.js +557 -0
- package/dist/server/yjs-persistence.js.map +1 -0
- package/dist/server/yjs-text-diff.d.ts +32 -0
- package/dist/server/yjs-text-diff.js +61 -0
- package/dist/server/yjs-text-diff.js.map +1 -0
- package/dist/services/access-control.d.ts +73 -0
- package/dist/services/access-control.js +146 -0
- package/dist/services/access-control.js.map +1 -0
- package/dist/services/asset-listing.d.ts +39 -0
- package/dist/services/asset-listing.js +125 -0
- package/dist/services/asset-listing.js.map +1 -0
- package/dist/services/asset-upload-service.d.ts +53 -0
- package/dist/services/asset-upload-service.js +201 -0
- package/dist/services/asset-upload-service.js.map +1 -0
- package/dist/services/assets/asset-index-service.d.ts +82 -0
- package/dist/services/assets/asset-index-service.js +167 -0
- package/dist/services/assets/asset-index-service.js.map +1 -0
- package/dist/services/assets/base.d.ts +44 -0
- package/dist/services/assets/base.js +55 -0
- package/dist/services/assets/base.js.map +1 -1
- package/dist/services/assets/index.d.ts +69 -1
- package/dist/services/assets/index.js +125 -0
- package/dist/services/assets/index.js.map +1 -1
- package/dist/services/assets/local.d.ts +12 -1
- package/dist/services/assets/local.js +100 -1
- package/dist/services/assets/local.js.map +1 -1
- package/dist/services/assets/r2-sync.d.ts +88 -0
- package/dist/services/assets/r2-sync.js +412 -0
- package/dist/services/assets/r2-sync.js.map +1 -0
- package/dist/services/assets/r2.d.ts +34 -1
- package/dist/services/assets/r2.js +93 -1
- package/dist/services/assets/r2.js.map +1 -1
- package/dist/services/auth-service.d.ts +314 -36
- package/dist/services/auth-service.js +1126 -139
- package/dist/services/auth-service.js.map +1 -1
- package/dist/services/bookmark-import-service.d.ts +49 -0
- package/dist/services/bookmark-import-service.js +250 -0
- package/dist/services/bookmark-import-service.js.map +1 -0
- package/dist/services/cloud-inference-billing.d.ts +64 -0
- package/dist/services/cloud-inference-billing.js +313 -0
- package/dist/services/cloud-inference-billing.js.map +1 -0
- package/dist/services/comment-service.d.ts +97 -0
- package/dist/services/comment-service.js +341 -0
- package/dist/services/comment-service.js.map +1 -0
- package/dist/services/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 +161 -0
- package/dist/services/entity-mutations.js +296 -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 +441 -0
- package/dist/services/event-processor.js.map +1 -0
- package/dist/services/folder-creation.d.ts +33 -0
- package/dist/services/folder-creation.js +103 -0
- package/dist/services/folder-creation.js.map +1 -0
- package/dist/services/git.d.ts +54 -0
- package/dist/services/git.js +188 -54
- package/dist/services/git.js.map +1 -1
- package/dist/services/graph-maintenance.d.ts +42 -0
- package/dist/services/graph-maintenance.js +143 -0
- package/dist/services/graph-maintenance.js.map +1 -0
- package/dist/services/import-service.d.ts +45 -2
- package/dist/services/import-service.js +304 -100
- package/dist/services/import-service.js.map +1 -1
- package/dist/services/lint-service.js +9 -16
- package/dist/services/lint-service.js.map +1 -1
- package/dist/services/markdown.d.ts +12 -1
- package/dist/services/markdown.js +71 -6
- package/dist/services/markdown.js.map +1 -1
- package/dist/services/memory-service.d.ts +22 -14
- package/dist/services/memory-service.js +53 -35
- package/dist/services/memory-service.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/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/personal-folder.d.ts +40 -0
- package/dist/services/personal-folder.js +101 -0
- package/dist/services/personal-folder.js.map +1 -0
- package/dist/services/user-person-bridge.d.ts +118 -0
- package/dist/services/user-person-bridge.js +306 -0
- package/dist/services/user-person-bridge.js.map +1 -0
- package/dist/services/vector-service.d.ts +146 -6
- package/dist/services/vector-service.js +276 -13
- package/dist/services/vector-service.js.map +1 -1
- package/dist/services/workspace-access.d.ts +106 -0
- package/dist/services/workspace-access.js +149 -0
- package/dist/services/workspace-access.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.DOumm94J.css +2 -0
- package/dist/web/_app/immutable/assets/13.DlIf1mKh.css +1 -0
- package/dist/web/_app/immutable/assets/14.7d-Q37wc.css +1 -0
- package/dist/web/_app/immutable/assets/15.CmLrbzZp.css +1 -0
- package/dist/web/_app/immutable/assets/2.BftNiAA4.css +1 -0
- package/dist/web/_app/immutable/assets/3.CV0abekd.css +1 -0
- package/dist/web/_app/immutable/assets/4.BgXzUTTk.css +1 -0
- package/dist/web/_app/immutable/assets/9.gsF-Ne6_.css +1 -0
- package/dist/web/_app/immutable/assets/AgentOverlay.CpvH9rhf.css +1 -0
- package/dist/web/_app/immutable/assets/AssetManagerModal.BKJfPAPk.css +1 -0
- package/dist/web/_app/immutable/assets/CalendarView.bNewSxkH.css +1 -0
- package/dist/web/_app/immutable/assets/ChevronDown.Bz0FjM7b.css +1 -0
- package/dist/web/_app/immutable/assets/FolderMembersPanel.DX1oBeF_.css +1 -0
- package/dist/web/_app/immutable/assets/FolderMutationDialogs.BMRF6Z3z.css +1 -0
- package/dist/web/_app/immutable/assets/FolderPicker.B5KdvRYl.css +1 -0
- package/dist/web/_app/immutable/assets/GalleryView.DgvSjS0I.css +1 -0
- package/dist/web/_app/immutable/assets/GraphView.BJtGL9py.css +1 -0
- package/dist/web/_app/immutable/assets/KanbanView.D0QbZFd6.css +1 -0
- package/dist/web/_app/immutable/assets/SettingsDialog.DYWYMFSO.css +1 -0
- package/dist/web/_app/immutable/assets/Spinner.UBfl0pab.css +1 -0
- package/dist/web/_app/immutable/assets/StopFilledAlt.CkPrHdPD.css +1 -0
- package/dist/web/_app/immutable/assets/TimelineView.DA-H6dvv.css +1 -0
- package/dist/web/_app/immutable/assets/WorkspaceView.BS1ildu7.css +1 -0
- package/dist/web/_app/immutable/assets/button.BlLm4Dg1.css +1 -0
- package/dist/web/_app/immutable/assets/formatting.vV0ne105.css +1 -0
- package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-400-normal.CvHOgSBP.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-400-normal.DMJ8VG8y.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-500-normal.CB9ihrfo.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-500-normal.DSY6xOcd.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-italic.CZTNEAuW.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-italic.CsGl1sm0.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-normal.CDDApCn2.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-normal.CYLoc0-x.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-italic.BNK2_mGO.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-italic.DpEwFAQM.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-normal.6ng42L7E.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-normal.BgVn5rGT.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-600-normal.Cu4Hd6ag.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-600-normal.CuJfVYMP.woff2 +0 -0
- package/dist/web/_app/immutable/assets/inline-list.cqga56xT.css +1 -0
- package/dist/web/_app/immutable/assets/list-columns.BrRrAiZ7.css +1 -0
- package/dist/web/_app/immutable/assets/realtime.DDCWxn3B.css +1 -0
- package/dist/web/_app/immutable/assets/sheet.DJPuqnTg.css +1 -0
- package/dist/web/_app/immutable/assets/speech-dictation.C5CYFhS1.css +1 -0
- package/dist/web/_app/immutable/chunks/0edH8sO82.js +1 -0
- package/dist/web/_app/immutable/chunks/0iDDDyaZ.js +1 -0
- package/dist/web/_app/immutable/chunks/7-y0CE1z.js +1 -0
- package/dist/web/_app/immutable/chunks/7Bu427my2.js +184 -0
- package/dist/web/_app/immutable/chunks/B-V5eMSw2.js +1 -0
- package/dist/web/_app/immutable/chunks/B-i9_YVM.js +1 -0
- package/dist/web/_app/immutable/chunks/B15-kvCe.js +1 -0
- package/dist/web/_app/immutable/chunks/B4cFqqUS.js +3 -0
- package/dist/web/_app/immutable/chunks/B6Lt-qaJ.js +1 -0
- package/dist/web/_app/immutable/chunks/B7hznOQp.js +1 -0
- package/dist/web/_app/immutable/chunks/B7tbGyiU2.js +1 -0
- package/dist/web/_app/immutable/chunks/B8-Lm86o2.js +1 -0
- package/dist/web/_app/immutable/chunks/BBxt8lpa.js +1 -0
- package/dist/web/_app/immutable/chunks/BCaRi2AF.js +1 -0
- package/dist/web/_app/immutable/chunks/BJReLGMs.js +1 -0
- package/dist/web/_app/immutable/chunks/BKzV82vD2.js +83 -0
- package/dist/web/_app/immutable/chunks/BLHrJvbG.js +1 -0
- package/dist/web/_app/immutable/chunks/BQULA6er.js +4 -0
- package/dist/web/_app/immutable/chunks/BQW-EDgP2.js +224 -0
- package/dist/web/_app/immutable/chunks/BR6jMpLB.js +1 -0
- package/dist/web/_app/immutable/chunks/BUBMGU8t2.js +2 -0
- package/dist/web/_app/immutable/chunks/BVnuLlVg.js +1 -0
- package/dist/web/_app/immutable/chunks/BYokDzwE.js +1 -0
- package/dist/web/_app/immutable/chunks/B_Avv9jQ2.js +1 -0
- package/dist/web/_app/immutable/chunks/Bbp76jaT2.js +1 -0
- package/dist/web/_app/immutable/chunks/BddNmQ8j.js +1 -0
- package/dist/web/_app/immutable/chunks/BiJjH5mJ.js +2 -0
- package/dist/web/_app/immutable/chunks/BlyFGPFV.js +22 -0
- package/dist/web/_app/immutable/chunks/Bt88HrRx.js +1 -0
- package/dist/web/_app/immutable/chunks/Bu2iU6hh.js +67 -0
- package/dist/web/_app/immutable/chunks/Buih1pxI.js +1 -0
- package/dist/web/_app/immutable/chunks/BwwLJVvR.js +1 -0
- package/dist/web/_app/immutable/chunks/C0RpyyYZ.js +7 -0
- package/dist/web/_app/immutable/chunks/C2l-EXsT2.js +1 -0
- package/dist/web/_app/immutable/chunks/C35TIzaB.js +1 -0
- package/dist/web/_app/immutable/chunks/C3OdK8z_2.js +1 -0
- package/dist/web/_app/immutable/chunks/C6Nx0jpM.js +1 -0
- package/dist/web/_app/immutable/chunks/CA7O0J_u2.js +1 -0
- package/dist/web/_app/immutable/chunks/CAwjrHdk.js +23 -0
- package/dist/web/_app/immutable/chunks/CCLR-AJL2.js +185 -0
- package/dist/web/_app/immutable/chunks/CD0X7XPW.js +6 -0
- package/dist/web/_app/immutable/chunks/CDTjuBSm2.js +1 -0
- package/dist/web/_app/immutable/chunks/CSUrYOis.js +1 -0
- package/dist/web/_app/immutable/chunks/CUPSJObs.js +1 -0
- package/dist/web/_app/immutable/chunks/CUoGvuxK2.js +2 -0
- package/dist/web/_app/immutable/chunks/CWh8aehb.js +1 -0
- package/dist/web/_app/immutable/chunks/CY4XVBP0.js +1 -0
- package/dist/web/_app/immutable/chunks/CYTTTma0.js +3 -0
- package/dist/web/_app/immutable/chunks/CaoriuNq.js +1 -0
- package/dist/web/_app/immutable/chunks/CcsSt9AW2.js +1 -0
- package/dist/web/_app/immutable/chunks/Cd4VfAZC.js +2 -0
- package/dist/web/_app/immutable/chunks/CePZ1985.js +1 -0
- package/dist/web/_app/immutable/chunks/Cei8CKT4.js +1 -0
- package/dist/web/_app/immutable/chunks/CheXrbDk.js +1 -0
- package/dist/web/_app/immutable/chunks/CkLfUXwy.js +1 -0
- package/dist/web/_app/immutable/chunks/Cl-W0JYt.js +2 -0
- package/dist/web/_app/immutable/chunks/Cna7tYp-.js +1 -0
- package/dist/web/_app/immutable/chunks/Czwe4sGU2.js +3 -0
- package/dist/web/_app/immutable/chunks/D41CbXmM.js +1 -0
- package/dist/web/_app/immutable/chunks/D83_Ss42.js +1 -0
- package/dist/web/_app/immutable/chunks/D9NgbGbO2.js +9 -0
- package/dist/web/_app/immutable/chunks/DA-BuZei.js +2 -0
- package/dist/web/_app/immutable/chunks/DAXW5iLp.js +1 -0
- package/dist/web/_app/immutable/chunks/DCNJWlho2.js +1 -0
- package/dist/web/_app/immutable/chunks/DCtgLRl52.js +1 -0
- package/dist/web/_app/immutable/chunks/DH0epITQ2.js +1 -0
- package/dist/web/_app/immutable/chunks/DIb5rAPU2.js +1 -0
- package/dist/web/_app/immutable/chunks/DOmH4G9o2.js +14 -0
- package/dist/web/_app/immutable/chunks/DQMWa2t7.js +1 -0
- package/dist/web/_app/immutable/chunks/DQ_Xokap2.js +1 -0
- package/dist/web/_app/immutable/chunks/DR1K6PIm.js +1 -0
- package/dist/web/_app/immutable/chunks/DTCHhxD5.js +1 -0
- package/dist/web/_app/immutable/chunks/DU7XA-cL2.js +1 -0
- package/dist/web/_app/immutable/chunks/DVcy5Pp82.js +1 -0
- package/dist/web/_app/immutable/chunks/DWygwzcO2.js +1 -0
- package/dist/web/_app/immutable/chunks/DYXDRmoo2.js +1 -0
- package/dist/web/_app/immutable/chunks/DZEmjBTb.js +5 -0
- package/dist/web/_app/immutable/chunks/DaoOQFPb.js +1 -0
- package/dist/web/_app/immutable/chunks/DdfvtDOY.js +1 -0
- package/dist/web/_app/immutable/chunks/DevZTY1l.js +1 -0
- package/dist/web/_app/immutable/chunks/DiMQob8p.js +1 -0
- package/dist/web/_app/immutable/chunks/DiQJ9DLy.js +1 -0
- package/dist/web/_app/immutable/chunks/Dj5oP_ik.js +2 -0
- package/dist/web/_app/immutable/chunks/DjTJ4D_0.js +3 -0
- package/dist/web/_app/immutable/chunks/Dk4TeNwu.js +1 -0
- package/dist/web/_app/immutable/chunks/Dng3OcRd2.js +1 -0
- package/dist/web/_app/immutable/chunks/Do1OCNEG.js +1 -0
- package/dist/web/_app/immutable/chunks/Do6qOOng.js +1 -0
- package/dist/web/_app/immutable/chunks/DoQwt-f9.js +1 -0
- package/dist/web/_app/immutable/chunks/Dqg3k9HI2.js +1 -0
- package/dist/web/_app/immutable/chunks/DsnqOT222.js +1 -0
- package/dist/web/_app/immutable/chunks/DvJSCDuZ2.js +1 -0
- package/dist/web/_app/immutable/chunks/DvPuaTCl.js +5 -0
- package/dist/web/_app/immutable/chunks/DvYQu1SV.js +1 -0
- package/dist/web/_app/immutable/chunks/HdjeJPly2.js +1 -0
- package/dist/web/_app/immutable/chunks/JDw4tOUY.js +1 -0
- package/dist/web/_app/immutable/chunks/KBbDg4lS2.js +1 -0
- package/dist/web/_app/immutable/chunks/KQXQLJf32.js +1 -0
- package/dist/web/_app/immutable/chunks/LGnNalpn2.js +2 -0
- package/dist/web/_app/immutable/chunks/SH5Fg0Xu.js +1 -0
- package/dist/web/_app/immutable/chunks/Shyla9c12.js +2 -0
- package/dist/web/_app/immutable/chunks/VQbAelHN.js +1 -0
- package/dist/web/_app/immutable/chunks/apNT0aug2.js +1 -0
- package/dist/web/_app/immutable/chunks/e0ySQvQ6.js +1 -0
- package/dist/web/_app/immutable/chunks/eNB_sIHC2.js +1 -0
- package/dist/web/_app/immutable/chunks/g09ZrevW2.js +1 -0
- package/dist/web/_app/immutable/chunks/h4WWBpcD2.js +2692 -0
- package/dist/web/_app/immutable/chunks/kLub81jO.js +1 -0
- package/dist/web/_app/immutable/chunks/kW0z_eo62.js +1 -0
- package/dist/web/_app/immutable/chunks/oBQOivfH.js +1 -0
- package/dist/web/_app/immutable/chunks/ss8hcwTT.js +1 -0
- package/dist/web/_app/immutable/chunks/sub7ikQV.js +2 -0
- package/dist/web/_app/immutable/chunks/uuFM-ygL.js +1 -0
- package/dist/web/_app/immutable/chunks/v7me-ydg.js +2 -0
- package/dist/web/_app/immutable/chunks/xihTtKlq.js +1 -0
- package/dist/web/_app/immutable/chunks/yWi6xIp8.js +1 -0
- package/dist/web/_app/immutable/chunks/zl16E4yc2.js +1 -0
- package/dist/web/_app/immutable/entry/app.Bgniax9U.js +2 -0
- package/dist/web/_app/immutable/entry/start.DeD3x0AC.js +1 -0
- package/dist/web/_app/immutable/nodes/0.D7f7eDDY.js +2 -0
- package/dist/web/_app/immutable/nodes/1.nDAGsQWA.js +1 -0
- package/dist/web/_app/immutable/nodes/10.DFbhcPKT.js +1 -0
- package/dist/web/_app/immutable/nodes/11.CDErrkBt.js +1 -0
- package/dist/web/_app/immutable/nodes/12.QmphOXE2.js +1 -0
- package/dist/web/_app/immutable/nodes/13.Bq5rDhPs.js +1 -0
- package/dist/web/_app/immutable/nodes/14.BlcHqgr6.js +1 -0
- package/dist/web/_app/immutable/nodes/15.COK_-D-x.js +1 -0
- package/dist/web/_app/immutable/nodes/2.DOsCYNHa.js +109 -0
- package/dist/web/_app/immutable/nodes/3.BgE1sIyr.js +2 -0
- package/dist/web/_app/immutable/nodes/4.DvgaHeI4.js +11 -0
- package/dist/web/_app/immutable/nodes/5.CrjeBZES.js +1 -0
- package/dist/web/_app/immutable/nodes/6.DoHxsiEE.js +1 -0
- package/dist/web/_app/immutable/nodes/7.04fnxKIo.js +1 -0
- package/dist/web/_app/immutable/nodes/8.D35_cY6C.js +1 -0
- package/dist/web/_app/immutable/nodes/9.BRionwW6.js +1 -0
- package/dist/web/_app/version.json +1 -1
- package/dist/web/favicon-16.png +0 -0
- package/dist/web/favicon-180.png +0 -0
- package/dist/web/favicon-32.png +0 -0
- package/dist/web/favicon-48.png +0 -0
- package/dist/web/favicon-512.png +0 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/favicon.svg +8 -0
- package/dist/web/index.html +56 -19
- package/dist/web/studiograph-logomark.svg +29 -0
- package/package.json +34 -14
- 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/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 -187
- 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 -589
- 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 -153
- 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/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 -15
- package/dist/integrations/registry.js +0 -27
- package/dist/integrations/registry.js.map +0 -1
- package/dist/integrations/types.d.ts +0 -38
- package/dist/integrations/types.js +0 -9
- 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/server/chrome/chrome.css +0 -691
- 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/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/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 -15
- package/dist/server/routes/meeting-ingest-api.js +0 -323
- package/dist/server/routes/meeting-ingest-api.js.map +0 -1
- package/dist/server/routes/sync-api.d.ts +0 -26
- package/dist/server/routes/sync-api.js +0 -814
- package/dist/server/routes/sync-api.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/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/insights.d.ts +0 -38
- package/dist/services/insights.js +0 -246
- package/dist/services/insights.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.DdizXOKi.css +0 -1
- package/dist/web/_app/immutable/assets/10.BUrHkYry.css +0 -1
- package/dist/web/_app/immutable/assets/2.n7-yW_Fs.css +0 -1
- package/dist/web/_app/immutable/assets/3.BtaZagnv.css +0 -1
- package/dist/web/_app/immutable/assets/4.DT7X0x5S.css +0 -1
- package/dist/web/_app/immutable/assets/5.DFeGxLYf.css +0 -1
- package/dist/web/_app/immutable/assets/6.CquhUpOu.css +0 -1
- package/dist/web/_app/immutable/assets/7.CjbDRbuG.css +0 -1
- package/dist/web/_app/immutable/assets/8.S1QdUGNM.css +0 -1
- package/dist/web/_app/immutable/assets/9.CT4xL4K3.css +0 -1
- package/dist/web/_app/immutable/assets/Copy.FS8bdfxr.css +0 -1
- package/dist/web/_app/immutable/assets/Toaster.rN8r_Hzv.css +0 -1
- package/dist/web/_app/immutable/assets/ViewToolbar.D-QW2oKe.css +0 -1
- package/dist/web/_app/immutable/assets/index.D6tMDJWk.css +0 -1
- package/dist/web/_app/immutable/chunks/37NdyH6G.js +0 -32
- package/dist/web/_app/immutable/chunks/4Hhzb6pv.js +0 -1
- package/dist/web/_app/immutable/chunks/72bZS3G9.js +0 -1
- package/dist/web/_app/immutable/chunks/7S2LGqoP.js +0 -2
- package/dist/web/_app/immutable/chunks/B-7hHz7B.js +0 -1
- package/dist/web/_app/immutable/chunks/B340SUKR.js +0 -1
- package/dist/web/_app/immutable/chunks/BB_5th5W.js +0 -3383
- package/dist/web/_app/immutable/chunks/BDoWDZaG.js +0 -1
- package/dist/web/_app/immutable/chunks/BFyl1ZHO.js +0 -1
- package/dist/web/_app/immutable/chunks/BS2rFAVq.js +0 -1
- package/dist/web/_app/immutable/chunks/BSsDC_p4.js +0 -1
- package/dist/web/_app/immutable/chunks/BTX2Jr8_.js +0 -1
- package/dist/web/_app/immutable/chunks/BWNY8zw6.js +0 -2
- package/dist/web/_app/immutable/chunks/BdPUVvAi.js +0 -1
- package/dist/web/_app/immutable/chunks/BmsJVLyy.js +0 -1
- package/dist/web/_app/immutable/chunks/BoAosqzE.js +0 -1
- package/dist/web/_app/immutable/chunks/Bs-tL5OR.js +0 -1
- package/dist/web/_app/immutable/chunks/Bsacq2UX.js +0 -1
- package/dist/web/_app/immutable/chunks/BuewkNdS.js +0 -4
- package/dist/web/_app/immutable/chunks/ByunV8un.js +0 -1
- package/dist/web/_app/immutable/chunks/C7VoTosa.js +0 -2
- package/dist/web/_app/immutable/chunks/CDV5Q7RN.js +0 -1
- package/dist/web/_app/immutable/chunks/CJ8__CWt.js +0 -1
- package/dist/web/_app/immutable/chunks/CJUKmosC.js +0 -5
- package/dist/web/_app/immutable/chunks/CSVbGg_b.js +0 -5
- package/dist/web/_app/immutable/chunks/CT6oIU-C.js +0 -1
- package/dist/web/_app/immutable/chunks/CTRxNfZk.js +0 -1
- package/dist/web/_app/immutable/chunks/CWQRQXxy.js +0 -1
- package/dist/web/_app/immutable/chunks/CXH6iFbA.js +0 -1
- package/dist/web/_app/immutable/chunks/CX_61fY8.js +0 -5
- package/dist/web/_app/immutable/chunks/CYrVHOHA.js +0 -1
- package/dist/web/_app/immutable/chunks/CdeYcEuU.js +0 -1
- package/dist/web/_app/immutable/chunks/CmCtpZ1Y.js +0 -1
- package/dist/web/_app/immutable/chunks/CqkleIqs.js +0 -1
- package/dist/web/_app/immutable/chunks/CrdV0ttd.js +0 -1
- package/dist/web/_app/immutable/chunks/CscoF-YL.js +0 -18
- package/dist/web/_app/immutable/chunks/Cx4YdFMk.js +0 -1
- package/dist/web/_app/immutable/chunks/D7ZqAKi9.js +0 -3
- package/dist/web/_app/immutable/chunks/DGbA7IXh.js +0 -2
- package/dist/web/_app/immutable/chunks/DbbB6Up5.js +0 -1
- package/dist/web/_app/immutable/chunks/DnL9f0lc.js +0 -7
- package/dist/web/_app/immutable/chunks/DtM5cEDu.js +0 -1
- package/dist/web/_app/immutable/chunks/Eqahi7_S.js +0 -2
- package/dist/web/_app/immutable/chunks/FFlLQIiS.js +0 -1
- package/dist/web/_app/immutable/chunks/O9Eb9k00.js +0 -1
- package/dist/web/_app/immutable/chunks/PPVm8Dsz.js +0 -1
- package/dist/web/_app/immutable/chunks/QF8MAmnc.js +0 -1
- package/dist/web/_app/immutable/chunks/UjzxCpxn.js +0 -1
- package/dist/web/_app/immutable/chunks/WSUKABI_.js +0 -1
- package/dist/web/_app/immutable/chunks/Y90OgS-9.js +0 -1
- package/dist/web/_app/immutable/chunks/cLyZlkXv.js +0 -1
- package/dist/web/_app/immutable/chunks/iDtAARVW.js +0 -2
- package/dist/web/_app/immutable/chunks/oC_W7W7p.js +0 -1
- package/dist/web/_app/immutable/chunks/rJdWIgh-.js +0 -1
- package/dist/web/_app/immutable/chunks/tvOsokaa.js +0 -1
- package/dist/web/_app/immutable/chunks/uLEXaPwE.js +0 -1
- package/dist/web/_app/immutable/chunks/vSaooFHB.js +0 -1
- package/dist/web/_app/immutable/entry/app.DAR-mtfg.js +0 -2
- package/dist/web/_app/immutable/entry/start.xDnyRaR1.js +0 -1
- package/dist/web/_app/immutable/nodes/0.D0aV-Zro.js +0 -2
- package/dist/web/_app/immutable/nodes/1.C7QoM8qD.js +0 -1
- package/dist/web/_app/immutable/nodes/10.DrDOAjOD.js +0 -1
- package/dist/web/_app/immutable/nodes/2.B0OgNuKW.js +0 -168
- package/dist/web/_app/immutable/nodes/3.BZ4qHGZi.js +0 -1
- package/dist/web/_app/immutable/nodes/4.hm-mxuuj.js +0 -12
- package/dist/web/_app/immutable/nodes/5.Bdbt4fjx.js +0 -4
- package/dist/web/_app/immutable/nodes/6.BXrCI40c.js +0 -2
- package/dist/web/_app/immutable/nodes/7.Chd4i9bm.js +0 -2
- package/dist/web/_app/immutable/nodes/8.B8SF-aNs.js +0 -1
- package/dist/web/_app/immutable/nodes/9.YDeu-RnO.js +0 -1
package/dist/mcp/tools.js
CHANGED
|
@@ -5,11 +5,28 @@
|
|
|
5
5
|
* methods — no new business logic. Results are returned as JSON text so the
|
|
6
6
|
* MCP client (Claude Desktop) can process structured data.
|
|
7
7
|
*
|
|
8
|
+
* Every tool enforces per-user access control via `services/access-control.ts`.
|
|
9
|
+
* Callers (the HTTP /mcp route, the stdio MCP server) must resolve a user
|
|
10
|
+
* identity before registering tools — `registerTools` requires a user in its
|
|
11
|
+
* `McpAccessContext` argument so there is no anonymous MCP path.
|
|
12
|
+
*
|
|
8
13
|
* Note: `inputSchema` is cast to `any` at each call site because the MCP SDK's
|
|
9
14
|
* ZodRawShapeCompat type does not accept ZodObject in TypeScript's strict mode.
|
|
10
15
|
* Runtime schema validation is handled correctly by the SDK regardless.
|
|
11
16
|
*/
|
|
12
17
|
import { z } from 'zod';
|
|
18
|
+
import { ENTITY_SCHEMAS } from '../core/validation.js';
|
|
19
|
+
import { getAccessibleRepos, hasRepoAccess, canWriteRepo, getAccessibleEntityIds, filterWikilinks, } from '../services/access-control.js';
|
|
20
|
+
import { createSharedFolder, FolderCreationError } from '../services/folder-creation.js';
|
|
21
|
+
import { validateFolderPath } from '../core/folder-paths.js';
|
|
22
|
+
import { existsSync } from 'fs';
|
|
23
|
+
import { join } from 'path';
|
|
24
|
+
import { isCuratedEntityType } from '../core/entity-types-catalog.js';
|
|
25
|
+
import { getAgentAllowedEntityTypes } from '../agent/prompts/entity-types-section.js';
|
|
26
|
+
import * as commentVirtualEntity from './comment-virtual-entity.js';
|
|
27
|
+
import { buildActor, prepareCreatePayload, prepareUpdatePayload, diagnosedUpdate, DroppedAssetError, } from '../services/entity-mutations.js';
|
|
28
|
+
import { extractAssetEmbeds, stripFrontmatter, assetEmbed } from '../core/embeds.js';
|
|
29
|
+
import { listRepoAssets } from '../services/asset-listing.js';
|
|
13
30
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
14
31
|
function ok(data) {
|
|
15
32
|
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
@@ -18,30 +35,161 @@ function fail(err) {
|
|
|
18
35
|
const msg = err instanceof Error ? err.message : String(err);
|
|
19
36
|
return { content: [{ type: 'text', text: msg }], isError: true };
|
|
20
37
|
}
|
|
38
|
+
/** Deny-without-leaking helper — same wording as a missing repo/entity. */
|
|
39
|
+
function notFound(repo) {
|
|
40
|
+
return fail(new Error(`Folder "${repo}" not found`));
|
|
41
|
+
}
|
|
21
42
|
// Strip frontmatter fields that duplicate the top-level id/type keys
|
|
22
43
|
function shape(id, entityType, data, extra) {
|
|
23
44
|
const { id: _id, entity_type: _et, ...rest } = data;
|
|
24
45
|
return { id, type: entityType, ...rest, ...extra };
|
|
25
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Default subfolders for entity types when the caller omits `folder_path`.
|
|
49
|
+
* Existing entries with no folder live at the repo root; new entries of these
|
|
50
|
+
* types route into a typed subfolder so the repo stays organised by default.
|
|
51
|
+
* An explicit `folder_path: ""` (or any explicit value) overrides this map.
|
|
52
|
+
*/
|
|
53
|
+
const DEFAULT_FOLDER_BY_TYPE = {
|
|
54
|
+
person: 'People',
|
|
55
|
+
organization: 'Organizations',
|
|
56
|
+
};
|
|
57
|
+
/** Convert an absolute entity path to a repo-relative POSIX path. */
|
|
58
|
+
function relPathOf(absPath, repoRoot) {
|
|
59
|
+
return absPath.startsWith(repoRoot + '/') ? absPath.slice(repoRoot.length + 1) : absPath;
|
|
60
|
+
}
|
|
26
61
|
// ── tool registration ─────────────────────────────────────────────────────────
|
|
27
|
-
export function registerTools(server, wm, authService) {
|
|
62
|
+
export function registerTools(server, wm, authService, ctx) {
|
|
63
|
+
const { user, wsHub, apiTokenName, commentService } = ctx;
|
|
64
|
+
// External-agent identity: "<owner>'s <token name>", or "<owner>'s agent"
|
|
65
|
+
// as a fallback when the token has no name (older tokens or contexts that
|
|
66
|
+
// didn't thread the token through). `userId` lets the client paint the chip
|
|
67
|
+
// with the same accent as the owning user's avatar.
|
|
68
|
+
const agentLabel = apiTokenName
|
|
69
|
+
? `${user.displayName}'s ${apiTokenName}`
|
|
70
|
+
: `${user.displayName}'s agent`;
|
|
71
|
+
const agentIdentity = {
|
|
72
|
+
kind: 'external',
|
|
73
|
+
label: agentLabel,
|
|
74
|
+
userId: String(user.id),
|
|
75
|
+
};
|
|
76
|
+
// Match the broadcast shape used by src/server/routes/graph-api.ts so the
|
|
77
|
+
// realtime listener in web/src/lib/stores/realtime.svelte.ts treats MCP
|
|
78
|
+
// mutations identically to web-UI mutations. The `agent` tag lets clients
|
|
79
|
+
// distinguish agent-driven mutations from human-driven ones.
|
|
80
|
+
function emitEntityChange(action, repo, entityType, entityId) {
|
|
81
|
+
wsHub?.broadcast({
|
|
82
|
+
type: 'entity_change',
|
|
83
|
+
action,
|
|
84
|
+
repo,
|
|
85
|
+
entityType,
|
|
86
|
+
entityId,
|
|
87
|
+
actor: user.displayName,
|
|
88
|
+
source: 'api',
|
|
89
|
+
agent: agentIdentity,
|
|
90
|
+
timestamp: new Date().toISOString(),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Wrap a mutation tool call so the sidebar can show a live "X's agent is
|
|
95
|
+
* acting" chip in the affected folder. `enter` fires before the work,
|
|
96
|
+
* `leave` after — wsHub TTL-cleans on crash. `verb` shapes the chip copy.
|
|
97
|
+
*/
|
|
98
|
+
async function withAgentActivity(repo, verb, fn) {
|
|
99
|
+
wsHub?.handleAgentActivity('enter', repo, agentIdentity, verb);
|
|
100
|
+
try {
|
|
101
|
+
return await fn();
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
wsHub?.handleAgentActivity('leave', repo, agentIdentity, verb);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Entity types the MCP surface may create. Matches the agent's allowlist so
|
|
108
|
+
// a workspace looks identical across the main app and MCP clients: the full
|
|
109
|
+
// canonical catalog plus workspace custom types. See
|
|
110
|
+
// src/core/entity-types-catalog.ts.
|
|
111
|
+
const builtinTypeKeys = new Set(Object.keys(ENTITY_SCHEMAS));
|
|
112
|
+
const customWorkspaceTypes = wm.getSchemaRegistry().getAllTypes()
|
|
113
|
+
.filter(t => !builtinTypeKeys.has(t));
|
|
114
|
+
const mcpAllowedCreateTypes = getAgentAllowedEntityTypes(customWorkspaceTypes);
|
|
115
|
+
const mcpAllowedSet = new Set(mcpAllowedCreateTypes);
|
|
28
116
|
// ── list_repos ────────────────────────────────────────────────────────────
|
|
29
|
-
server.registerTool('list_repos', { description: 'List
|
|
117
|
+
server.registerTool('list_repos', { description: 'List folders in the Studiograph workspace that you have access to' }, async () => ok(getAccessibleRepos(user, wm, authService)));
|
|
118
|
+
// ── create_repo ───────────────────────────────────────────────────────────
|
|
119
|
+
//
|
|
120
|
+
// Creates a new shared folder in the workspace and grants the
|
|
121
|
+
// caller folder-admin access. Shares its implementation with the web route
|
|
122
|
+
// POST /api/repos via services/folder-creation.ts so both surfaces behave
|
|
123
|
+
// identically. After creation, use create_entity with the new folder name
|
|
124
|
+
// to add entries. Folder names are immutable slugs; the display name is
|
|
125
|
+
// editable later via the web UI.
|
|
126
|
+
const createRepoSchema = z.object({
|
|
127
|
+
name: z.string().describe('Folder slug — lowercase kebab-case (letters, digits, hyphens; must start with a letter or digit). ' +
|
|
128
|
+
'This becomes the folder id and directory name; it cannot be changed without a rename.'),
|
|
129
|
+
display_name: z.string().optional().describe('Human-readable name shown in the UI (e.g. "Client Projects"). Defaults to the slug.'),
|
|
130
|
+
description: z.string().optional().describe('Short description of what this folder is for.'),
|
|
131
|
+
});
|
|
132
|
+
server.registerTool('create_repo', {
|
|
133
|
+
description: 'Create a new shared folder in the workspace. Use this when an agent or ' +
|
|
134
|
+
'user needs a fresh container for a new project, client area, or topic and no existing ' +
|
|
135
|
+
'folder fits. The caller is automatically granted folder-admin access. ' +
|
|
136
|
+
'Follow up with create_entity (passing the new folder name as `repo`) to seed entries. ' +
|
|
137
|
+
'Folder names must be kebab-case and unique; the private per-user "entries-<id>" ' +
|
|
138
|
+
'namespace is reserved and will be rejected.',
|
|
139
|
+
inputSchema: createRepoSchema,
|
|
140
|
+
}, async (args) => {
|
|
141
|
+
const { name, display_name, description } = createRepoSchema.parse(args);
|
|
142
|
+
try {
|
|
143
|
+
const repo = await createSharedFolder(wm, authService, { name, display_name, description }, user, wsHub);
|
|
144
|
+
return ok({ success: true, ...repo });
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
if (err instanceof FolderCreationError) {
|
|
148
|
+
return fail(new Error(err.message));
|
|
149
|
+
}
|
|
150
|
+
return fail(err);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
30
153
|
// ── list_entities ─────────────────────────────────────────────────────────
|
|
31
154
|
const listEntitiesSchema = z.object({
|
|
32
|
-
repo: z.string().optional().describe('
|
|
33
|
-
entity_type: z.string().optional().describe('Entity type filter (project, client, person,
|
|
155
|
+
repo: z.string().optional().describe('Folder name — omit to list across all folders'),
|
|
156
|
+
entity_type: z.string().optional().describe('Entity type filter (project, client, person, event…)'),
|
|
34
157
|
limit: z.number().int().positive().optional().describe('Maximum number of results'),
|
|
35
158
|
});
|
|
36
|
-
server.registerTool('list_entities', { description: 'List entities in a
|
|
159
|
+
server.registerTool('list_entities', { description: 'List entities in a folder (or all folders). Optionally filter by entity type.', inputSchema: listEntitiesSchema }, async (args) => {
|
|
37
160
|
const { repo, entity_type, limit } = listEntitiesSchema.parse(args);
|
|
38
161
|
try {
|
|
162
|
+
// Comments live in SQLite, not the filesystem graph — dispatch through
|
|
163
|
+
// the virtual-entity adapter when the caller asks for them explicitly.
|
|
164
|
+
if (entity_type === 'comment' && commentService) {
|
|
165
|
+
if (repo && !hasRepoAccess(user, repo, wm, authService))
|
|
166
|
+
return notFound(repo);
|
|
167
|
+
const accessibleNames = new Set(getAccessibleRepos(user, wm, authService).map(r => r.name));
|
|
168
|
+
const envelopes = commentVirtualEntity.listComments(commentService, { repo, limit });
|
|
169
|
+
const filtered = envelopes.filter(e => accessibleNames.has(e.data.repo));
|
|
170
|
+
return ok(filtered.map(e => shape(e.id, e.entityType, e.data, { repo: e.data.repo })));
|
|
171
|
+
}
|
|
39
172
|
if (repo) {
|
|
173
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
174
|
+
return notFound(repo);
|
|
40
175
|
const entities = wm.getGraph(repo).list(entity_type, limit);
|
|
41
176
|
return ok(entities.map(e => shape(e.id, e.entityType, e.data)));
|
|
42
177
|
}
|
|
43
|
-
|
|
44
|
-
|
|
178
|
+
// No repo specified → iterate only over the user's accessible folders.
|
|
179
|
+
const accessible = getAccessibleRepos(user, wm, authService);
|
|
180
|
+
const results = [];
|
|
181
|
+
for (const r of accessible) {
|
|
182
|
+
try {
|
|
183
|
+
const list = wm.getGraph(r.name).list(entity_type);
|
|
184
|
+
for (const e of list) {
|
|
185
|
+
results.push(shape(e.id, e.entityType, e.data, { repo: r.name }));
|
|
186
|
+
if (limit && results.length >= limit)
|
|
187
|
+
return ok(results);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch { /* skip unmounted repos */ }
|
|
191
|
+
}
|
|
192
|
+
return ok(results);
|
|
45
193
|
}
|
|
46
194
|
catch (err) {
|
|
47
195
|
return fail(err);
|
|
@@ -49,15 +197,34 @@ export function registerTools(server, wm, authService) {
|
|
|
49
197
|
});
|
|
50
198
|
// ── get_entity ────────────────────────────────────────────────────────────
|
|
51
199
|
const getEntitySchema = z.object({
|
|
52
|
-
repo: z.string().describe('
|
|
200
|
+
repo: z.string().describe('Folder name'),
|
|
53
201
|
entity_type: z.string().describe('Entity type (project, client, person…)'),
|
|
54
202
|
entity_id: z.string().describe('Entity ID (kebab-case slug)'),
|
|
55
203
|
});
|
|
56
204
|
server.registerTool('get_entity', { description: 'Get a specific entity — includes full markdown content and frontmatter', inputSchema: getEntitySchema }, async (args) => {
|
|
57
205
|
const { repo, entity_type, entity_id } = getEntitySchema.parse(args);
|
|
206
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
207
|
+
return notFound(repo);
|
|
58
208
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
209
|
+
if (entity_type === 'comment' && commentService) {
|
|
210
|
+
const envelope = commentVirtualEntity.getComment(commentService, entity_id);
|
|
211
|
+
if (!envelope)
|
|
212
|
+
return fail(new Error(`Comment "${entity_id}" not found`));
|
|
213
|
+
if (envelope.data.repo !== repo)
|
|
214
|
+
return notFound(repo);
|
|
215
|
+
return ok({ id: envelope.id, type: envelope.entityType, data: envelope.data, content: envelope.document.content });
|
|
216
|
+
}
|
|
217
|
+
const graph = wm.getGraph(repo);
|
|
218
|
+
const entity = graph.get(entity_type, entity_id);
|
|
219
|
+
// Prefer the live editor body when a seeded Y.Doc is open: graph.get
|
|
220
|
+
// reads disk, which lags the editor by the autosave debounce, so a
|
|
221
|
+
// user's freshly-placed image/asset embed may not be on disk yet.
|
|
222
|
+
// Returning disk content here is what let agents edit-then-overwrite
|
|
223
|
+
// and silently drop those embeds. null => no live doc, disk is
|
|
224
|
+
// authoritative.
|
|
225
|
+
const liveBody = graph.getLiveBody(entity_type, entity_id);
|
|
226
|
+
const content = liveBody ?? entity.document.content;
|
|
227
|
+
return ok({ id: entity.id, type: entity.entityType, data: entity.data, content });
|
|
61
228
|
}
|
|
62
229
|
catch (err) {
|
|
63
230
|
return fail(err);
|
|
@@ -67,13 +234,19 @@ export function registerTools(server, wm, authService) {
|
|
|
67
234
|
const searchSchema = z.object({
|
|
68
235
|
query: z.string().optional().describe('Text search query'),
|
|
69
236
|
entity_type: z.string().optional().describe('Filter by entity type'),
|
|
70
|
-
repos: z.array(z.string()).optional().describe('Limit to specific
|
|
237
|
+
repos: z.array(z.string()).optional().describe('Limit to specific folder names'),
|
|
71
238
|
limit: z.number().int().positive().optional().describe('Maximum number of results'),
|
|
72
239
|
});
|
|
73
|
-
server.registerTool('search_entities', { description: 'Full-text search for entities across
|
|
240
|
+
server.registerTool('search_entities', { description: 'Full-text search for entities across folders you can access', inputSchema: searchSchema }, async (args) => {
|
|
74
241
|
const { query, entity_type, repos, limit } = searchSchema.parse(args);
|
|
75
242
|
try {
|
|
76
|
-
const
|
|
243
|
+
const accessibleNames = new Set(getAccessibleRepos(user, wm, authService).map(r => r.name));
|
|
244
|
+
const scoped = repos
|
|
245
|
+
? repos.filter(r => accessibleNames.has(r))
|
|
246
|
+
: [...accessibleNames];
|
|
247
|
+
if (scoped.length === 0)
|
|
248
|
+
return ok([]);
|
|
249
|
+
const results = await wm.search({ query, entityType: entity_type, repoNames: scoped, limit });
|
|
77
250
|
return ok(results.map(e => shape(e.id, e.entityType, e.data, { repo: e.repoName })));
|
|
78
251
|
}
|
|
79
252
|
catch (err) {
|
|
@@ -81,62 +254,538 @@ export function registerTools(server, wm, authService) {
|
|
|
81
254
|
}
|
|
82
255
|
});
|
|
83
256
|
// ── create_entity ─────────────────────────────────────────────────────────
|
|
257
|
+
// Constrain `entity_type` to the allowlist so MCP clients (e.g. Claude Desktop)
|
|
258
|
+
// see the same type surface as the main agent. Existing canonical-type entities
|
|
259
|
+
// remain readable/updatable via other tools.
|
|
260
|
+
const createEntityTypeSchema = mcpAllowedCreateTypes.length > 0
|
|
261
|
+
? z.enum(mcpAllowedCreateTypes)
|
|
262
|
+
: z.string();
|
|
84
263
|
const createSchema = z.object({
|
|
85
|
-
repo: z.string().describe('
|
|
86
|
-
entity_type:
|
|
264
|
+
repo: z.string().describe('Folder name'),
|
|
265
|
+
entity_type: createEntityTypeSchema.describe(`Entity type. Allowed: ${mcpAllowedCreateTypes.join(', ')}.`),
|
|
87
266
|
entity_id: z.string().describe('Entity ID (kebab-case slug)'),
|
|
88
|
-
data: z.record(z.unknown()).describe('
|
|
267
|
+
data: z.record(z.string(), z.unknown()).describe('Type-specific frontmatter fields (e.g. for person: name, email, organization, role). ' +
|
|
268
|
+
'Base fields (entity_id, entity_type, created_at, updated_at, created_by, updated_by) ' +
|
|
269
|
+
'are stamped automatically — do not include them.'),
|
|
89
270
|
content: z.string().optional().describe('Markdown body content'),
|
|
271
|
+
folder_path: z.string().optional().describe('Repo-relative subfolder to place the entry in (e.g. "People", "clients/acme"). ' +
|
|
272
|
+
'Omit to use the per-type default (person → People, organization → Organizations); ' +
|
|
273
|
+
'pass an empty string to force the repo root. Parent folders are auto-created.'),
|
|
90
274
|
});
|
|
91
|
-
server.registerTool('create_entity', {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
275
|
+
server.registerTool('create_entity', {
|
|
276
|
+
description: 'Create a new entity in a folder. Commits to git automatically. ' +
|
|
277
|
+
'Base frontmatter (entity_id, created_at, updated_at, created_by, updated_by) is ' +
|
|
278
|
+
'stamped automatically — `data` should only carry type-specific fields. ' +
|
|
279
|
+
'For person entries, `status` accepts synonyms (active/prospective/inactive/past/etc.) ' +
|
|
280
|
+
'and normalizes to `current` | `former`; defaults to `current` when omitted. ' +
|
|
281
|
+
'Without `folder_path`, person entries route to People/ and organization entries to Organizations/; ' +
|
|
282
|
+
'other types land at the repo root. Pass `folder_path: ""` to force the repo root, ' +
|
|
283
|
+
'or any subfolder string (e.g. "Clients/Acme") to place it explicitly. ' +
|
|
284
|
+
'Response includes the final repo-relative `path`.',
|
|
285
|
+
inputSchema: createSchema,
|
|
286
|
+
}, async (args) => {
|
|
287
|
+
const { repo, entity_type, entity_id, data, content, folder_path } = createSchema.parse(args);
|
|
288
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
289
|
+
return notFound(repo);
|
|
290
|
+
if (!canWriteRepo(user, repo, wm, authService)) {
|
|
291
|
+
return fail(new Error(`You do not have write access to folder "${repo}"`));
|
|
101
292
|
}
|
|
102
|
-
|
|
103
|
-
|
|
293
|
+
// Defense in depth: MCP SDK may not enforce zod enum strictly on all clients.
|
|
294
|
+
if (!mcpAllowedSet.has(entity_type)) {
|
|
295
|
+
if (entity_type === 'asset') {
|
|
296
|
+
// The enum already excludes `asset` (see getAgentAllowedEntityTypes).
|
|
297
|
+
// This handles MCP clients that bypass the enum schema.
|
|
298
|
+
return fail(new Error('Asset entities cannot be created via create_entity — they require a binary blob plus a sidecar text file. Use the workspace upload UI or POST /api/repos/:repo/assets directly.'));
|
|
299
|
+
}
|
|
300
|
+
const hint = isCuratedEntityType(entity_type)
|
|
301
|
+
? '' // shouldn't happen for any other curated type — they're all allowed
|
|
302
|
+
: ` Allowed: ${mcpAllowedCreateTypes.join(', ')}.`;
|
|
303
|
+
return fail(new Error(`Entity type "${entity_type}" is not a known canonical or workspace custom type.${hint}`));
|
|
104
304
|
}
|
|
305
|
+
// Explicit folder_path (including "") wins; otherwise apply the per-type
|
|
306
|
+
// default so the agent doesn't have to know the workspace convention.
|
|
307
|
+
const effectiveFolder = folder_path !== undefined
|
|
308
|
+
? folder_path
|
|
309
|
+
: (DEFAULT_FOLDER_BY_TYPE[entity_type] ?? '');
|
|
310
|
+
const actor = buildActor(user);
|
|
311
|
+
const options = prepareCreatePayload({
|
|
312
|
+
entityType: entity_type,
|
|
313
|
+
entityId: entity_id,
|
|
314
|
+
data: data,
|
|
315
|
+
content,
|
|
316
|
+
folderPath: effectiveFolder || undefined,
|
|
317
|
+
}, actor);
|
|
318
|
+
return withAgentActivity(repo, 'creating', async () => {
|
|
319
|
+
try {
|
|
320
|
+
const graph = wm.getGraph(repo);
|
|
321
|
+
// Detect implicit subfolder creation: if the entity lands in a folder
|
|
322
|
+
// that didn't exist yet, the entity_change below only refreshes the
|
|
323
|
+
// entry list — clients need a _folder event to refetch the folder
|
|
324
|
+
// tree (sidebar + list/gallery folder rows). Normalize the same way
|
|
325
|
+
// graph.create does so the existence check matches the real path.
|
|
326
|
+
const normFolder = effectiveFolder ? validateFolderPath(effectiveFolder) : '';
|
|
327
|
+
const folderExisted = !normFolder || existsSync(join(graph.getRepoPath(), normFolder));
|
|
328
|
+
const entity = await graph.create(options);
|
|
329
|
+
emitEntityChange('created', repo, entity.entityType, entity.id);
|
|
330
|
+
if (!folderExisted)
|
|
331
|
+
emitEntityChange('created', repo, '_folder', normFolder);
|
|
332
|
+
return ok({
|
|
333
|
+
success: true,
|
|
334
|
+
id: entity.id,
|
|
335
|
+
type: entity.entityType,
|
|
336
|
+
repo,
|
|
337
|
+
path: relPathOf(entity.path, graph.getRepoPath()),
|
|
338
|
+
folder_path: entity.folder_path,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
return fail(err);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
105
345
|
});
|
|
106
346
|
// ── update_entity ─────────────────────────────────────────────────────────
|
|
347
|
+
//
|
|
348
|
+
// Bare success envelopes were misleading agents: callers couldn't tell
|
|
349
|
+
// whether their write landed, whether validation silently dropped a field,
|
|
350
|
+
// or whether they'd just rewritten the file with identical bytes. The
|
|
351
|
+
// response now includes path, commit, before/after hashes, per-section
|
|
352
|
+
// changed flags, and the post-write entity snapshot so agents can verify
|
|
353
|
+
// their intent matches reality without a follow-up get_entity round-trip.
|
|
354
|
+
//
|
|
355
|
+
// No-op detection compares semantic frontmatter (excluding auto-stamped
|
|
356
|
+
// `updated_at` / `updated_by`) and raw content. A no-op is reported as
|
|
357
|
+
// success with `noop: true` — not an error — so idempotent re-runs are
|
|
358
|
+
// explicit. If the caller passed data or content but the on-disk file is
|
|
359
|
+
// byte-identical after the write, that's a silent-drop signal: the call
|
|
360
|
+
// fails loudly with a message that names which inputs were ignored.
|
|
107
361
|
const updateSchema = z.object({
|
|
108
|
-
repo: z.string().describe('
|
|
362
|
+
repo: z.string().describe('Folder name'),
|
|
363
|
+
entity_type: z.string().describe('Entity type'),
|
|
364
|
+
entity_id: z.string().describe('Entity ID'),
|
|
365
|
+
data: z.record(z.string(), z.unknown()).optional().describe('Frontmatter fields to update (merged with existing)'),
|
|
366
|
+
content: z.string().optional().describe('New markdown body content (replaces the entire body)'),
|
|
367
|
+
allow_image_removal: z.boolean().optional().describe('Whole-body writes are rejected with code ASSET_REMOVAL_BLOCKED when the submitted content would drop ' +
|
|
368
|
+
'image/attachment embeds that the current document still has (a common silent-data-loss bug when an agent ' +
|
|
369
|
+
'edits stale content). Set true ONLY when you intend to delete those embeds. Prefer patch_entity for small edits.'),
|
|
370
|
+
});
|
|
371
|
+
server.registerTool('update_entity', {
|
|
372
|
+
description: 'Update an existing entity. Data fields are merged with existing frontmatter; content replaces the markdown body. ' +
|
|
373
|
+
'Callers do not need to resend immutable base fields (entity_id, created_at, created_by) — they are preserved from disk. ' +
|
|
374
|
+
'`updated_at` and `updated_by` are stamped automatically. ' +
|
|
375
|
+
'For person entries, `status` synonyms are normalized to `current` | `former` when supplied. ' +
|
|
376
|
+
'Returns the post-write entity snapshot plus diagnostic flags (frontmatter_changed, content_changed, noop, commit) ' +
|
|
377
|
+
'so the caller can verify the change landed without a separate get_entity call.',
|
|
378
|
+
inputSchema: updateSchema,
|
|
379
|
+
}, async (args) => {
|
|
380
|
+
const { repo, entity_type, entity_id, data, content, allow_image_removal } = updateSchema.parse(args);
|
|
381
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
382
|
+
return notFound(repo);
|
|
383
|
+
if (!canWriteRepo(user, repo, wm, authService)) {
|
|
384
|
+
return fail(new Error(`You do not have write access to folder "${repo}"`));
|
|
385
|
+
}
|
|
386
|
+
if (data === undefined && content === undefined) {
|
|
387
|
+
return fail(new Error('update_entity requires at least one of `data` or `content`'));
|
|
388
|
+
}
|
|
389
|
+
const actor = buildActor(user);
|
|
390
|
+
const options = prepareUpdatePayload({
|
|
391
|
+
entityType: entity_type,
|
|
392
|
+
entityId: entity_id,
|
|
393
|
+
data: data,
|
|
394
|
+
content,
|
|
395
|
+
}, actor);
|
|
396
|
+
return withAgentActivity(repo, 'editing', async () => {
|
|
397
|
+
try {
|
|
398
|
+
const graph = wm.getGraph(repo);
|
|
399
|
+
// Diagnosed update (shared with the agent surface): snapshots
|
|
400
|
+
// before, runs the server-write path (disk + commit serialize via
|
|
401
|
+
// the per-doc lock; live clients get the post-commit broadcast),
|
|
402
|
+
// and throws SilentDropError when the caller intended a change
|
|
403
|
+
// that produced no on-disk diff. The outer catch maps that to fail.
|
|
404
|
+
const { entity, frontmatter_changed, content_changed, noop, broadcast } = await diagnosedUpdate(graph, options, { data, content }, { allowAssetRemoval: allow_image_removal });
|
|
405
|
+
emitEntityChange('updated', repo, entity.entityType, entity.id);
|
|
406
|
+
// Repo-relative path so external agents don't see the workspace
|
|
407
|
+
// absolute path. The HEAD commit is captured after graph.update
|
|
408
|
+
// commits the transaction — null when noGit is on or no commit
|
|
409
|
+
// was produced (e.g. transaction self-cleared because there was
|
|
410
|
+
// no staged diff).
|
|
411
|
+
const repoRoot = graph.getRepoPath();
|
|
412
|
+
const relPath = entity.path.startsWith(repoRoot + '/')
|
|
413
|
+
? entity.path.slice(repoRoot.length + 1)
|
|
414
|
+
: entity.path;
|
|
415
|
+
const commit = graph.getGitService()?.getHeadCommit() ?? null;
|
|
416
|
+
return ok({
|
|
417
|
+
success: true,
|
|
418
|
+
id: entity.id,
|
|
419
|
+
type: entity.entityType,
|
|
420
|
+
repo,
|
|
421
|
+
path: relPath,
|
|
422
|
+
commit,
|
|
423
|
+
frontmatter_changed,
|
|
424
|
+
content_changed,
|
|
425
|
+
noop,
|
|
426
|
+
broadcast,
|
|
427
|
+
updated_at: entity.data.updated_at ?? null,
|
|
428
|
+
entity: {
|
|
429
|
+
id: entity.id,
|
|
430
|
+
type: entity.entityType,
|
|
431
|
+
data: entity.data,
|
|
432
|
+
content: entity.document.content,
|
|
433
|
+
},
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
catch (err) {
|
|
437
|
+
if (err instanceof DroppedAssetError) {
|
|
438
|
+
return ok({
|
|
439
|
+
success: false,
|
|
440
|
+
code: 'ASSET_REMOVAL_BLOCKED',
|
|
441
|
+
droppedAssets: err.dropped,
|
|
442
|
+
message: err.message,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
return fail(err);
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
// ── patch_entity ──────────────────────────────────────────────────────────
|
|
450
|
+
//
|
|
451
|
+
// Surgical body edit: replace an exact substring without resending the
|
|
452
|
+
// whole body. The motivating failure was an agent fetching content,
|
|
453
|
+
// renaming a heading, and resending the entire body — which dropped
|
|
454
|
+
// editor-placed image embeds it never saw. patch_entity can't drop what it
|
|
455
|
+
// doesn't send: it reads the CURRENT body (live editor when open, else
|
|
456
|
+
// disk), applies the literal find→replace, and writes the result through
|
|
457
|
+
// the same guarded server-write path. The asset-drop guard still runs, so
|
|
458
|
+
// a patch that happens to delete an embed is rejected unless the substring
|
|
459
|
+
// is itself the embed being intentionally removed.
|
|
460
|
+
const patchEntitySchema = z.object({
|
|
461
|
+
repo: z.string().describe('Folder name'),
|
|
109
462
|
entity_type: z.string().describe('Entity type'),
|
|
110
463
|
entity_id: z.string().describe('Entity ID'),
|
|
111
|
-
|
|
112
|
-
|
|
464
|
+
find: z.string().describe('Exact substring to locate in the current body (verbatim, including whitespace).'),
|
|
465
|
+
replace: z.string().describe('Replacement text. Empty string deletes the matched substring.'),
|
|
466
|
+
expected_count: z.number().int().positive().optional().describe('Optional safety check: require `find` to occur exactly this many times. The patch is rejected with ' +
|
|
467
|
+
'NO_MATCH / AMBIGUOUS_MATCH when the actual count differs, so you never edit the wrong occurrence.'),
|
|
468
|
+
});
|
|
469
|
+
server.registerTool('patch_entity', {
|
|
470
|
+
description: 'Surgically edit an entry body by replacing an exact substring — without rewriting the whole body. ' +
|
|
471
|
+
'Prefer this over update_entity for small edits (renaming a heading, fixing a sentence): it operates on ' +
|
|
472
|
+
'the live editor body and cannot drop image/asset embeds it did not touch. Returns the same diagnostic ' +
|
|
473
|
+
'flags as update_entity. Use `expected_count` to guard against ambiguous matches.',
|
|
474
|
+
inputSchema: patchEntitySchema,
|
|
475
|
+
}, async (args) => {
|
|
476
|
+
const { repo, entity_type, entity_id, find, replace, expected_count } = patchEntitySchema.parse(args);
|
|
477
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
478
|
+
return notFound(repo);
|
|
479
|
+
if (!canWriteRepo(user, repo, wm, authService)) {
|
|
480
|
+
return fail(new Error(`You do not have write access to folder "${repo}"`));
|
|
481
|
+
}
|
|
482
|
+
if (find === '')
|
|
483
|
+
return fail(new Error('patch_entity requires a non-empty `find` string'));
|
|
484
|
+
if (find === replace)
|
|
485
|
+
return fail(new Error('patch_entity `find` and `replace` are identical — nothing to do'));
|
|
486
|
+
return withAgentActivity(repo, 'editing', async () => {
|
|
487
|
+
try {
|
|
488
|
+
const graph = wm.getGraph(repo);
|
|
489
|
+
const entity = graph.get(entity_type, entity_id);
|
|
490
|
+
// Patch against the live editor body when one is open, so we don't
|
|
491
|
+
// reintroduce the stale-read problem we're fixing.
|
|
492
|
+
const currentBody = graph.getLiveBody(entity_type, entity_id) ?? entity.document.content;
|
|
493
|
+
// Count occurrences of the literal substring (no regex).
|
|
494
|
+
let count = 0;
|
|
495
|
+
for (let i = currentBody.indexOf(find); i !== -1; i = currentBody.indexOf(find, i + find.length))
|
|
496
|
+
count++;
|
|
497
|
+
if (count === 0) {
|
|
498
|
+
return ok({ success: false, code: 'NO_MATCH', message: `\`find\` substring not present in ${entity_type}/${entity_id}.` });
|
|
499
|
+
}
|
|
500
|
+
if (expected_count !== undefined && count !== expected_count) {
|
|
501
|
+
return ok({ success: false, code: 'AMBIGUOUS_MATCH', occurrences: count, message: `Expected ${expected_count} occurrence(s) of \`find\`, found ${count}.` });
|
|
502
|
+
}
|
|
503
|
+
const newBody = currentBody.split(find).join(replace);
|
|
504
|
+
const options = prepareUpdatePayload({ entityType: entity_type, entityId: entity_id, content: newBody }, buildActor(user));
|
|
505
|
+
const { entity: updated, frontmatter_changed, content_changed, noop, broadcast } = await diagnosedUpdate(graph, options, { content: newBody });
|
|
506
|
+
emitEntityChange('updated', repo, updated.entityType, updated.id);
|
|
507
|
+
const repoRoot = graph.getRepoPath();
|
|
508
|
+
const relPath = updated.path.startsWith(repoRoot + '/') ? updated.path.slice(repoRoot.length + 1) : updated.path;
|
|
509
|
+
return ok({
|
|
510
|
+
success: true,
|
|
511
|
+
id: updated.id, type: updated.entityType, repo, path: relPath,
|
|
512
|
+
commit: graph.getGitService()?.getHeadCommit() ?? null,
|
|
513
|
+
occurrences: count, frontmatter_changed, content_changed, noop, broadcast,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
catch (err) {
|
|
517
|
+
if (err instanceof DroppedAssetError) {
|
|
518
|
+
return ok({ success: false, code: 'ASSET_REMOVAL_BLOCKED', droppedAssets: err.dropped, message: err.message });
|
|
519
|
+
}
|
|
520
|
+
return fail(err);
|
|
521
|
+
}
|
|
522
|
+
});
|
|
113
523
|
});
|
|
114
|
-
|
|
115
|
-
|
|
524
|
+
// ── list_assets ─────────────────────────────────────────────────────────
|
|
525
|
+
//
|
|
526
|
+
// Read-only discovery of assets (images, PDFs, files) already uploaded to a
|
|
527
|
+
// folder. Shares listRepoAssets() with the HTTP asset endpoints, so the
|
|
528
|
+
// shape — including the render-safe `embed` string — is identical. Assets
|
|
529
|
+
// are created only through the upload routes / workspace UI; this tool never
|
|
530
|
+
// creates one, it only surfaces existing ones so an agent can reference them
|
|
531
|
+
// in an entry body without guessing a URL or embed syntax.
|
|
532
|
+
const listAssetsSchema = z.object({
|
|
533
|
+
repo: z.string().describe('Folder name'),
|
|
534
|
+
entity_type: z.string().optional().describe('Filter to assets owned by entries of this type'),
|
|
535
|
+
entity_id: z.string().optional().describe('Filter to assets owned by this entry id'),
|
|
536
|
+
});
|
|
537
|
+
server.registerTool('list_assets', {
|
|
538
|
+
description: 'List assets (images, PDFs, files) already uploaded to a folder. Each result includes a ready-to-paste ' +
|
|
539
|
+
'`embed` — standard markdown `` — that renders in the editor. To display an asset in an entry, ' +
|
|
540
|
+
'place that exact `embed` string via attach_asset_to_entity (or patch_entity for append/insert). ' +
|
|
541
|
+
'NEVER write `![[filename]]` transclusion syntax: it does not resolve to a stored asset and shows as dead ' +
|
|
542
|
+
'text. Assets are uploaded via the workspace UI — this tool only references existing ones. Optionally ' +
|
|
543
|
+
'filter by entity_type and/or entity_id to list a single entry\'s assets.',
|
|
544
|
+
inputSchema: listAssetsSchema,
|
|
545
|
+
}, async (args) => {
|
|
546
|
+
const { repo, entity_type, entity_id } = listAssetsSchema.parse(args);
|
|
547
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
548
|
+
return notFound(repo);
|
|
116
549
|
try {
|
|
117
|
-
|
|
118
|
-
entityType: entity_type,
|
|
119
|
-
entityId: entity_id,
|
|
120
|
-
data: data,
|
|
121
|
-
content,
|
|
122
|
-
});
|
|
123
|
-
return ok({ success: true, id: entity.id, type: entity.entityType });
|
|
550
|
+
wm.getGraph(repo);
|
|
124
551
|
}
|
|
125
|
-
catch
|
|
126
|
-
return
|
|
552
|
+
catch {
|
|
553
|
+
return notFound(repo);
|
|
554
|
+
}
|
|
555
|
+
let assets = await listRepoAssets(repo, wm);
|
|
556
|
+
if (entity_type)
|
|
557
|
+
assets = assets.filter(a => a.entityType === entity_type);
|
|
558
|
+
if (entity_id)
|
|
559
|
+
assets = assets.filter(a => a.entityId === entity_id);
|
|
560
|
+
return ok({
|
|
561
|
+
repo,
|
|
562
|
+
count: assets.length,
|
|
563
|
+
assets: assets.map(a => ({
|
|
564
|
+
asset_id: a.assetId,
|
|
565
|
+
filename: a.filename,
|
|
566
|
+
mime_type: a.contentType,
|
|
567
|
+
entity_type: a.entityType,
|
|
568
|
+
entity_id: a.entityId,
|
|
569
|
+
entity_name: a.entityName,
|
|
570
|
+
url: a.url,
|
|
571
|
+
embed: a.embed,
|
|
572
|
+
})),
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
// ── attach_asset_to_entity ──────────────────────────────────────────────
|
|
576
|
+
//
|
|
577
|
+
// Place an already-uploaded asset (from list_assets) into an entry body
|
|
578
|
+
// without rewriting the whole body. Thin convenience over the patch_entity
|
|
579
|
+
// machinery: it builds the render-safe `` embed and either
|
|
580
|
+
// appends it or swaps a placeholder for it, then writes through the same
|
|
581
|
+
// guarded server-write path.
|
|
582
|
+
//
|
|
583
|
+
// `replace_placeholder` is the sanctioned way to fix a dead `![[Foo.png]]`
|
|
584
|
+
// placeholder. Note: that swap necessarily *drops* the transclusion target
|
|
585
|
+
// (`Foo.png`) the asset-drop guard recognizes, so a plain patch_entity of
|
|
586
|
+
// the same span would be rejected with ASSET_REMOVAL_BLOCKED. Here we pass
|
|
587
|
+
// allowAssetRemoval for replace_placeholder only — the agent is explicitly
|
|
588
|
+
// targeting that placeholder and we're re-adding the image as a working
|
|
589
|
+
// embed, so no real asset is lost. `append` keeps the guard on.
|
|
590
|
+
const attachAssetSchema = z.object({
|
|
591
|
+
repo: z.string().describe('Folder name'),
|
|
592
|
+
entity_type: z.string().describe('Entity type of the target entry'),
|
|
593
|
+
entity_id: z.string().describe('Entity ID of the target entry'),
|
|
594
|
+
url: z.string().describe('Asset url, taken verbatim from list_assets (e.g. /api/assets/<id>/<file>)'),
|
|
595
|
+
alt: z.string().optional().describe('Alt text / caption for the embed. Defaults to the asset filename.'),
|
|
596
|
+
mode: z.enum(['append', 'replace_placeholder']).default('append').describe('"append" adds the embed at the end of the body. "replace_placeholder" swaps `placeholder` text ' +
|
|
597
|
+
'(e.g. a dead `![[Foo.png]]` written earlier) for the working embed.'),
|
|
598
|
+
placeholder: z.string().optional().describe('Required when mode="replace_placeholder": exact text to replace.'),
|
|
599
|
+
expected_count: z.number().int().positive().optional().describe('For replace_placeholder: require `placeholder` to occur exactly this many times, else reject (AMBIGUOUS_MATCH).'),
|
|
600
|
+
});
|
|
601
|
+
server.registerTool('attach_asset_to_entity', {
|
|
602
|
+
description: 'Embed an already-uploaded asset (from list_assets) into an entry body using its render-safe markdown, ' +
|
|
603
|
+
'without rewriting the whole body. mode="append" adds it at the end; mode="replace_placeholder" swaps dead ' +
|
|
604
|
+
'placeholder text (e.g. an `![[Foo.png]]` an agent wrote earlier) for the working embed. Drop-guarded on ' +
|
|
605
|
+
'append: rejects with ASSET_REMOVAL_BLOCKED if it would remove other image embeds.',
|
|
606
|
+
inputSchema: attachAssetSchema,
|
|
607
|
+
}, async (args) => {
|
|
608
|
+
const { repo, entity_type, entity_id, url, alt, mode, placeholder, expected_count } = attachAssetSchema.parse(args);
|
|
609
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
610
|
+
return notFound(repo);
|
|
611
|
+
if (!canWriteRepo(user, repo, wm, authService)) {
|
|
612
|
+
return fail(new Error(`You do not have write access to folder "${repo}"`));
|
|
613
|
+
}
|
|
614
|
+
if (!url.trim())
|
|
615
|
+
return fail(new Error('attach_asset_to_entity requires a non-empty `url`'));
|
|
616
|
+
if (mode === 'replace_placeholder' && !placeholder) {
|
|
617
|
+
return fail(new Error('mode="replace_placeholder" requires a non-empty `placeholder`'));
|
|
618
|
+
}
|
|
619
|
+
const filename = alt ?? (url.split('/').pop() || 'asset').split('?')[0];
|
|
620
|
+
const embed = assetEmbed(filename, url);
|
|
621
|
+
return withAgentActivity(repo, 'editing', async () => {
|
|
622
|
+
try {
|
|
623
|
+
const graph = wm.getGraph(repo);
|
|
624
|
+
const entity = graph.get(entity_type, entity_id);
|
|
625
|
+
const currentBody = graph.getLiveBody(entity_type, entity_id) ?? entity.document.content;
|
|
626
|
+
let newBody;
|
|
627
|
+
let occurrences;
|
|
628
|
+
let allowAssetRemoval = false;
|
|
629
|
+
if (mode === 'replace_placeholder') {
|
|
630
|
+
const ph = placeholder;
|
|
631
|
+
let count = 0;
|
|
632
|
+
for (let i = currentBody.indexOf(ph); i !== -1; i = currentBody.indexOf(ph, i + ph.length))
|
|
633
|
+
count++;
|
|
634
|
+
if (count === 0) {
|
|
635
|
+
return ok({ success: false, code: 'NO_MATCH', message: `placeholder not present in ${entity_type}/${entity_id}.` });
|
|
636
|
+
}
|
|
637
|
+
if (expected_count !== undefined && count !== expected_count) {
|
|
638
|
+
return ok({ success: false, code: 'AMBIGUOUS_MATCH', occurrences: count, message: `Expected ${expected_count} occurrence(s) of placeholder, found ${count}.` });
|
|
639
|
+
}
|
|
640
|
+
occurrences = count;
|
|
641
|
+
newBody = currentBody.split(ph).join(embed);
|
|
642
|
+
// The placeholder we're removing is itself a recognized embed
|
|
643
|
+
// target; swapping it for the working embed is the whole point.
|
|
644
|
+
allowAssetRemoval = true;
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
if (currentBody.includes(embed)) {
|
|
648
|
+
return ok({ success: false, code: 'ALREADY_PRESENT', message: `Asset embed already present in ${entity_type}/${entity_id}.` });
|
|
649
|
+
}
|
|
650
|
+
newBody = currentBody.trimEnd() + '\n\n' + embed + '\n';
|
|
651
|
+
}
|
|
652
|
+
const options = prepareUpdatePayload({ entityType: entity_type, entityId: entity_id, content: newBody }, buildActor(user));
|
|
653
|
+
const { entity: updated, frontmatter_changed, content_changed, noop, broadcast } = await diagnosedUpdate(graph, options, { content: newBody }, { allowAssetRemoval });
|
|
654
|
+
emitEntityChange('updated', repo, updated.entityType, updated.id);
|
|
655
|
+
const repoRoot = graph.getRepoPath();
|
|
656
|
+
const relPath = updated.path.startsWith(repoRoot + '/') ? updated.path.slice(repoRoot.length + 1) : updated.path;
|
|
657
|
+
return ok({
|
|
658
|
+
success: true,
|
|
659
|
+
id: updated.id, type: updated.entityType, repo, path: relPath,
|
|
660
|
+
embed, mode, occurrences,
|
|
661
|
+
commit: graph.getGitService()?.getHeadCommit() ?? null,
|
|
662
|
+
frontmatter_changed, content_changed, noop, broadcast,
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
catch (err) {
|
|
666
|
+
if (err instanceof DroppedAssetError) {
|
|
667
|
+
return ok({ success: false, code: 'ASSET_REMOVAL_BLOCKED', droppedAssets: err.dropped, message: err.message });
|
|
668
|
+
}
|
|
669
|
+
return fail(err);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
});
|
|
673
|
+
// ── move_entity ───────────────────────────────────────────────────────────
|
|
674
|
+
//
|
|
675
|
+
// Relocate an entity to a different folder within the same repo. Wraps
|
|
676
|
+
// graph.move() which renames the file on disk and commits the rename so
|
|
677
|
+
// git's similarity detection preserves history. The (type, id) identity
|
|
678
|
+
// is unchanged — get_entity, search, list, validate_wikilinks, and the
|
|
679
|
+
// history tools all walk the entire repo tree and continue to find the
|
|
680
|
+
// entry at its new path. For cross-repo transfers, the web UI's bulk-move
|
|
681
|
+
// dialog or POST /api/repos/:repo/transfer is the right surface.
|
|
682
|
+
const moveEntitySchema = z.object({
|
|
683
|
+
repo: z.string().describe('Folder name containing the entry'),
|
|
684
|
+
entity_type: z.string().describe('Entity type'),
|
|
685
|
+
entity_id: z.string().describe('Entity ID'),
|
|
686
|
+
folder_path: z.string().describe('Target repo-relative folder path (e.g. "People", "Clients/Acme"). ' +
|
|
687
|
+
'Empty string moves the entry to the repo root. Parent folders are auto-created.'),
|
|
688
|
+
overwrite: z.boolean().optional().describe('When true, replace an existing entry at the destination. The occupant ' +
|
|
689
|
+
'must itself be a parseable entity — README/.gitkeep/etc. are never ' +
|
|
690
|
+
'clobbered. Defaults to false; the call errors with "Path already ' +
|
|
691
|
+
'occupied" when the destination is taken.'),
|
|
692
|
+
});
|
|
693
|
+
server.registerTool('move_entity', {
|
|
694
|
+
description: 'Move an existing entry to a different folder within the same folder/repo. ' +
|
|
695
|
+
'Preserves frontmatter, markdown content, entity ID, and git history (file is renamed, not rewritten). ' +
|
|
696
|
+
'Use this to organise entries into subfolders after creation — e.g. moving a person into People/. ' +
|
|
697
|
+
'By default, errors if the destination already has an entry; pass `overwrite: true` to replace.',
|
|
698
|
+
inputSchema: moveEntitySchema,
|
|
699
|
+
}, async (args) => {
|
|
700
|
+
const { repo, entity_type, entity_id, folder_path, overwrite } = moveEntitySchema.parse(args);
|
|
701
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
702
|
+
return notFound(repo);
|
|
703
|
+
if (!canWriteRepo(user, repo, wm, authService)) {
|
|
704
|
+
return fail(new Error(`You do not have write access to folder "${repo}"`));
|
|
127
705
|
}
|
|
706
|
+
const actor = buildActor(user);
|
|
707
|
+
return withAgentActivity(repo, 'editing', async () => {
|
|
708
|
+
try {
|
|
709
|
+
const graph = wm.getGraph(repo);
|
|
710
|
+
// If the destination folder doesn't exist yet, the move creates it —
|
|
711
|
+
// emit a _folder event (alongside the entry update) so clients
|
|
712
|
+
// refetch the folder tree, not just the entry list.
|
|
713
|
+
const normTarget = folder_path ? validateFolderPath(folder_path) : '';
|
|
714
|
+
const targetExisted = !normTarget || existsSync(join(graph.getRepoPath(), normTarget));
|
|
715
|
+
const moved = await graph.move(entity_type, entity_id, folder_path, undefined, actor, overwrite);
|
|
716
|
+
emitEntityChange('updated', repo, moved.entityType, moved.id);
|
|
717
|
+
if (!targetExisted)
|
|
718
|
+
emitEntityChange('created', repo, '_folder', normTarget);
|
|
719
|
+
return ok({
|
|
720
|
+
success: true,
|
|
721
|
+
id: moved.id,
|
|
722
|
+
type: moved.entityType,
|
|
723
|
+
repo,
|
|
724
|
+
path: relPathOf(moved.path, graph.getRepoPath()),
|
|
725
|
+
folder_path: moved.folder_path,
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
catch (err) {
|
|
729
|
+
return fail(err);
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
});
|
|
733
|
+
// ── create_subfolder ──────────────────────────────────────────────────────
|
|
734
|
+
//
|
|
735
|
+
// Create a nested subfolder inside an existing folder/collection. Wraps
|
|
736
|
+
// graph.createFolder(). Distinct from create_repo, which registers a new
|
|
737
|
+
// top-level collection. Emits the _folder sentinel so live clients refetch
|
|
738
|
+
// the folder tree (sidebar + list/gallery folder rows) without a reload.
|
|
739
|
+
const createSubfolderSchema = z.object({
|
|
740
|
+
repo: z.string().describe('Folder name (collection) to create the subfolder inside'),
|
|
741
|
+
path: z.string().describe('Repo-relative POSIX path of the new subfolder (e.g. "Clients/Acme"). Parent segments are auto-created.'),
|
|
742
|
+
display_name: z.string().optional().describe('Optional human-readable label. Omit when the title-cased folder name is the right display ' +
|
|
743
|
+
'(pass it for casing the slug can\'t express, e.g. "HR" or "Schema_OS").'),
|
|
744
|
+
});
|
|
745
|
+
server.registerTool('create_subfolder', {
|
|
746
|
+
description: 'Create a new subfolder inside an existing folder/collection. Path is repo-relative POSIX (e.g. "Clients/Acme"); parent segments are auto-created. ' +
|
|
747
|
+
'Use it to organise entries, then place entries inside via create_entity (folder_path) or move_entity. ' +
|
|
748
|
+
'For a brand-new top-level collection, use create_repo instead.',
|
|
749
|
+
inputSchema: createSubfolderSchema,
|
|
750
|
+
}, async (args) => {
|
|
751
|
+
const { repo, path, display_name } = createSubfolderSchema.parse(args);
|
|
752
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
753
|
+
return notFound(repo);
|
|
754
|
+
if (!canWriteRepo(user, repo, wm, authService)) {
|
|
755
|
+
return fail(new Error(`You do not have write access to folder "${repo}"`));
|
|
756
|
+
}
|
|
757
|
+
const actor = buildActor(user);
|
|
758
|
+
return withAgentActivity(repo, 'creating', async () => {
|
|
759
|
+
try {
|
|
760
|
+
const graph = wm.getGraph(repo);
|
|
761
|
+
await graph.createFolder(path, undefined, actor, { displayName: display_name });
|
|
762
|
+
emitEntityChange('created', repo, '_folder', path);
|
|
763
|
+
return ok({ success: true, repo, path, folder_path: path });
|
|
764
|
+
}
|
|
765
|
+
catch (err) {
|
|
766
|
+
return fail(err);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
128
769
|
});
|
|
129
770
|
// ── validate_wikilinks ────────────────────────────────────────────────────
|
|
130
771
|
const validateWikilinksSchema = z.object({
|
|
131
|
-
repo: z.string().describe('
|
|
772
|
+
repo: z.string().describe('Folder name'),
|
|
132
773
|
entity_type: z.string().describe('Entity type'),
|
|
133
774
|
entity_id: z.string().describe('Entity ID'),
|
|
134
775
|
});
|
|
135
776
|
server.registerTool('validate_wikilinks', { description: 'Check which wikilinks in an entity resolve to real entities and which are broken', inputSchema: validateWikilinksSchema }, async (args) => {
|
|
136
777
|
const { repo, entity_type, entity_id } = validateWikilinksSchema.parse(args);
|
|
778
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
779
|
+
return notFound(repo);
|
|
137
780
|
try {
|
|
138
781
|
const result = wm.getGraph(repo).validateWikilinks(entity_type, entity_id);
|
|
139
|
-
|
|
782
|
+
const accessibleIds = getAccessibleEntityIds(user, wm, authService);
|
|
783
|
+
const valid = filterWikilinks(result.valid, accessibleIds);
|
|
784
|
+
// Anything the user can't see is reported as broken rather than valid —
|
|
785
|
+
// it shouldn't leak the target's existence.
|
|
786
|
+
const hiddenFromValid = result.valid.filter(id => !valid.includes(id));
|
|
787
|
+
const broken = [...result.broken, ...hiddenFromValid];
|
|
788
|
+
return ok({ entity_id, valid, broken, total: valid.length + broken.length });
|
|
140
789
|
}
|
|
141
790
|
catch (err) {
|
|
142
791
|
return fail(err);
|
|
@@ -144,7 +793,7 @@ export function registerTools(server, wm, authService) {
|
|
|
144
793
|
});
|
|
145
794
|
// ── find_related ──────────────────────────────────────────────────────────
|
|
146
795
|
const relatedSchema = z.object({
|
|
147
|
-
repo: z.string().describe('
|
|
796
|
+
repo: z.string().describe('Folder name'),
|
|
148
797
|
entity_type: z.string().describe('Entity type'),
|
|
149
798
|
entity_id: z.string().describe('Entity ID'),
|
|
150
799
|
direction: z.enum(['outgoing', 'incoming', 'both']).default('both')
|
|
@@ -152,15 +801,33 @@ export function registerTools(server, wm, authService) {
|
|
|
152
801
|
});
|
|
153
802
|
server.registerTool('find_related', { description: 'Find entities related via wikilinks (outgoing) or backlinks (incoming)', inputSchema: relatedSchema }, async (args) => {
|
|
154
803
|
const { repo, entity_type, entity_id, direction } = relatedSchema.parse(args);
|
|
804
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
805
|
+
return notFound(repo);
|
|
155
806
|
try {
|
|
807
|
+
const accessibleIds = getAccessibleEntityIds(user, wm, authService);
|
|
156
808
|
const results = [];
|
|
157
809
|
if (direction === 'outgoing' || direction === 'both') {
|
|
158
810
|
const related = wm.getGraph(repo).getRelated(entity_type, entity_id);
|
|
159
|
-
|
|
811
|
+
for (const e of related) {
|
|
812
|
+
if (accessibleIds && !accessibleIds.has(e.id))
|
|
813
|
+
continue;
|
|
814
|
+
results.push(shape(e.id, e.entityType, e.data, { direction: 'outgoing' }));
|
|
815
|
+
}
|
|
160
816
|
}
|
|
161
817
|
if (direction === 'incoming' || direction === 'both') {
|
|
818
|
+
// Comments anchored to this entry surface as incoming backlinks.
|
|
819
|
+
if (commentService) {
|
|
820
|
+
const anchored = commentVirtualEntity.findRelatedComments(commentService, repo, entity_type, entity_id);
|
|
821
|
+
for (const e of anchored) {
|
|
822
|
+
results.push(shape(e.id, e.entityType, e.data, { direction: 'incoming', repo }));
|
|
823
|
+
}
|
|
824
|
+
}
|
|
162
825
|
const backlinks = wm.getBacklinks(repo, entity_type, entity_id);
|
|
163
|
-
|
|
826
|
+
for (const e of backlinks) {
|
|
827
|
+
if (!hasRepoAccess(user, e.repoName, wm, authService))
|
|
828
|
+
continue;
|
|
829
|
+
results.push(shape(e.id, e.entityType, e.data, { direction: 'incoming', repo: e.repoName }));
|
|
830
|
+
}
|
|
164
831
|
}
|
|
165
832
|
return ok(results);
|
|
166
833
|
}
|
|
@@ -168,84 +835,351 @@ export function registerTools(server, wm, authService) {
|
|
|
168
835
|
return fail(err);
|
|
169
836
|
}
|
|
170
837
|
});
|
|
171
|
-
// ──
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
838
|
+
// ── list_skills ──────────────────────────────────────────────────────────
|
|
839
|
+
//
|
|
840
|
+
// Surfaces user-authored `skill` entities to external MCP clients so
|
|
841
|
+
// connected agents (Claude Desktop, Cursor, etc.) can discover and run
|
|
842
|
+
// the workspace's codified workflows. Only graph skills (entities) are
|
|
843
|
+
// exposed; bundled filesystem skills (onboarding/interview/enrichment
|
|
844
|
+
// prompts) stay internal to Studiograph's own agent.
|
|
845
|
+
const listSkillsSchema = z.object({
|
|
846
|
+
repo: z.string().optional().describe('Folder name — omit to list across all folders you have access to'),
|
|
847
|
+
});
|
|
848
|
+
function projectSkill(entity, repo) {
|
|
849
|
+
const data = entity.data;
|
|
850
|
+
return {
|
|
851
|
+
id: entity.id,
|
|
852
|
+
name: data.name || entity.id,
|
|
853
|
+
description: data.description ?? '',
|
|
854
|
+
repo,
|
|
855
|
+
loading: data.loading === 'eager' ? 'eager' : 'on-demand',
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
// `graph.list('skill')` is lenient — root-level .md files without a
|
|
859
|
+
// declared `entity_type` (READMEs, CHANGELOGs) get tagged as whatever the
|
|
860
|
+
// caller asks for. For MCP we want only real skills, so require the
|
|
861
|
+
// frontmatter to explicitly say `entity_type: skill`.
|
|
862
|
+
function isRealSkill(entity) {
|
|
863
|
+
return entity.data.entity_type === 'skill';
|
|
864
|
+
}
|
|
865
|
+
server.registerTool('list_skills', {
|
|
866
|
+
description: 'List skills (reusable agent instructions) available in the Studiograph workspace. ' +
|
|
867
|
+
'Skills are user-authored markdown — call get_skill to read the full body and follow ' +
|
|
868
|
+
'its instructions. Optionally filter by folder.',
|
|
869
|
+
inputSchema: listSkillsSchema,
|
|
870
|
+
}, async (args) => {
|
|
871
|
+
const { repo } = listSkillsSchema.parse(args);
|
|
872
|
+
try {
|
|
873
|
+
if (repo) {
|
|
874
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
875
|
+
return notFound(repo);
|
|
876
|
+
const skills = wm.getGraph(repo).list('skill');
|
|
877
|
+
return ok(skills.filter(isRealSkill).map(e => projectSkill(e, repo)));
|
|
176
878
|
}
|
|
177
|
-
|
|
178
|
-
|
|
879
|
+
const accessible = getAccessibleRepos(user, wm, authService);
|
|
880
|
+
const results = [];
|
|
881
|
+
for (const r of accessible) {
|
|
882
|
+
try {
|
|
883
|
+
const skills = wm.getGraph(r.name).list('skill');
|
|
884
|
+
for (const e of skills) {
|
|
885
|
+
if (isRealSkill(e))
|
|
886
|
+
results.push(projectSkill(e, r.name));
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
catch { /* skip unmounted repos */ }
|
|
179
890
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
891
|
+
return ok(results);
|
|
892
|
+
}
|
|
893
|
+
catch (err) {
|
|
894
|
+
return fail(err);
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
// ── get_skill ────────────────────────────────────────────────────────────
|
|
898
|
+
//
|
|
899
|
+
// Returns the full markdown body of a skill alongside its metadata. The
|
|
900
|
+
// body is instructions to follow — the MCP client is the runtime. No
|
|
901
|
+
// server-side agent loop, no parameter substitution in v1.
|
|
902
|
+
const getSkillSchema = z.object({
|
|
903
|
+
name: z.string().describe('Skill id (the entity_id of the skill entity)'),
|
|
904
|
+
repo: z.string().optional().describe('Folder to look in. Omit to search across all accessible folders.'),
|
|
905
|
+
});
|
|
906
|
+
server.registerTool('get_skill', {
|
|
907
|
+
description: 'Fetch the full content of a skill. Returns the skill\'s markdown body along with its ' +
|
|
908
|
+
'metadata. The body contains instructions to follow — read it and apply it to the current task.',
|
|
909
|
+
inputSchema: getSkillSchema,
|
|
910
|
+
}, async (args) => {
|
|
911
|
+
const { name, repo } = getSkillSchema.parse(args);
|
|
912
|
+
try {
|
|
913
|
+
if (repo) {
|
|
914
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
915
|
+
return notFound(repo);
|
|
916
|
+
try {
|
|
917
|
+
const entity = wm.getGraph(repo).get('skill', name);
|
|
918
|
+
if (!isRealSkill({ data: entity.data })) {
|
|
919
|
+
return fail(new Error(`Skill "${name}" not found`));
|
|
920
|
+
}
|
|
921
|
+
return ok({
|
|
922
|
+
...projectSkill({ id: entity.id, data: entity.data }, repo),
|
|
923
|
+
content: entity.document.content,
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
catch {
|
|
927
|
+
return fail(new Error(`Skill "${name}" not found`));
|
|
928
|
+
}
|
|
198
929
|
}
|
|
199
|
-
|
|
200
|
-
|
|
930
|
+
// No repo — search accessible folders for the skill by id.
|
|
931
|
+
const accessible = getAccessibleRepos(user, wm, authService);
|
|
932
|
+
const matches = [];
|
|
933
|
+
for (const r of accessible) {
|
|
934
|
+
try {
|
|
935
|
+
const entity = wm.getGraph(r.name).get('skill', name);
|
|
936
|
+
if (!isRealSkill({ data: entity.data }))
|
|
937
|
+
continue;
|
|
938
|
+
matches.push({ repo: r.name, entity });
|
|
939
|
+
}
|
|
940
|
+
catch { /* not in this repo */ }
|
|
201
941
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
942
|
+
if (matches.length === 0)
|
|
943
|
+
return fail(new Error(`Skill "${name}" not found`));
|
|
944
|
+
if (matches.length > 1) {
|
|
945
|
+
const folders = matches.map(m => m.repo).join(', ');
|
|
946
|
+
return fail(new Error(`Skill "${name}" exists in multiple folders (${folders}). Specify repo.`));
|
|
947
|
+
}
|
|
948
|
+
const { repo: matchRepo, entity } = matches[0];
|
|
949
|
+
return ok({
|
|
950
|
+
...projectSkill({ id: entity.id, data: entity.data }, matchRepo),
|
|
951
|
+
content: entity.document.content,
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
catch (err) {
|
|
955
|
+
return fail(err);
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
// ── Git history tools ──────────────────────────────────────────────────────
|
|
959
|
+
//
|
|
960
|
+
// Mirrors the agent's history-tools.ts surface so external MCP clients get
|
|
961
|
+
// the same primitives. Reads gated on hasRepoAccess; restore_file gated on
|
|
962
|
+
// canWriteRepo. Single-entity scope only — multi-entity rollback is
|
|
963
|
+
// intentionally absent (deferred to a future revert_commit).
|
|
964
|
+
const listFileHistorySchema = z.object({
|
|
965
|
+
repo: z.string().describe('Folder name'),
|
|
966
|
+
entity_type: z.string().describe('Entity type'),
|
|
967
|
+
entity_id: z.string().describe('Entry ID'),
|
|
968
|
+
limit: z.number().optional().describe('Max commits (default 20, max 100)'),
|
|
969
|
+
});
|
|
970
|
+
server.registerTool('list_file_history', { description: 'List commits that touched a single entry, newest first.', inputSchema: listFileHistorySchema }, async (args) => {
|
|
971
|
+
const { repo, entity_type, entity_id, limit } = listFileHistorySchema.parse(args);
|
|
972
|
+
try {
|
|
973
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
974
|
+
return fail(`Folder "${repo}" not found`);
|
|
975
|
+
const graph = wm.getGraph(repo);
|
|
976
|
+
const gitService = graph.getGitService();
|
|
977
|
+
if (!gitService)
|
|
978
|
+
return fail('Folder is not git-backed');
|
|
979
|
+
let entityPath;
|
|
211
980
|
try {
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const fields = {};
|
|
216
|
-
if (name)
|
|
217
|
-
fields.name = name;
|
|
218
|
-
if (selected_type !== undefined || group_by !== undefined) {
|
|
219
|
-
const config = { ...existing.config };
|
|
220
|
-
if (selected_type !== undefined)
|
|
221
|
-
config.selectedType = selected_type === 'all' ? null : selected_type;
|
|
222
|
-
if (group_by !== undefined)
|
|
223
|
-
config.groupBy = group_by === 'none' ? null : group_by;
|
|
224
|
-
fields.config = config;
|
|
225
|
-
}
|
|
226
|
-
authService.updateUserView(id, 0, fields);
|
|
227
|
-
return ok({ success: true });
|
|
981
|
+
const entity = graph.get(entity_type, entity_id);
|
|
982
|
+
const { relative: relativePath } = await import('path');
|
|
983
|
+
entityPath = relativePath(graph.getRepoPath(), entity.path);
|
|
228
984
|
}
|
|
229
|
-
catch
|
|
230
|
-
|
|
985
|
+
catch {
|
|
986
|
+
const idLower = `${entity_id}.md`.toLowerCase();
|
|
987
|
+
const match = gitService.listRecentDeletions(30).find((d) => d.filePath.toLowerCase().endsWith(idLower));
|
|
988
|
+
if (!match)
|
|
989
|
+
return fail(`Entry ${entity_type}/${entity_id} not found in "${repo}".`);
|
|
990
|
+
entityPath = match.filePath;
|
|
231
991
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
992
|
+
const cap = Math.min(100, Math.max(1, limit ?? 20));
|
|
993
|
+
const commits = gitService.getFileHistory(entityPath, cap).map((c) => ({
|
|
994
|
+
hash: c.hash.slice(0, 8),
|
|
995
|
+
fullHash: c.hash,
|
|
996
|
+
author: c.author,
|
|
997
|
+
timestamp: c.timestamp,
|
|
998
|
+
message: c.message,
|
|
999
|
+
}));
|
|
1000
|
+
return ok({ repo, entityType: entity_type, entityId: entity_id, commits });
|
|
1001
|
+
}
|
|
1002
|
+
catch (err) {
|
|
1003
|
+
return fail(err);
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
const listFolderHistorySchema = z.object({
|
|
1007
|
+
repo: z.string().describe('Folder name'),
|
|
1008
|
+
limit: z.number().optional().describe('Max commits (default 30, max 200)'),
|
|
1009
|
+
});
|
|
1010
|
+
server.registerTool('list_folder_history', { description: 'List recent commits across an entire folder.', inputSchema: listFolderHistorySchema }, async (args) => {
|
|
1011
|
+
const { repo, limit } = listFolderHistorySchema.parse(args);
|
|
1012
|
+
try {
|
|
1013
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
1014
|
+
return fail(`Folder "${repo}" not found`);
|
|
1015
|
+
const gitService = wm.getGraph(repo).getGitService();
|
|
1016
|
+
if (!gitService)
|
|
1017
|
+
return fail('Folder is not git-backed');
|
|
1018
|
+
const cap = Math.min(200, Math.max(1, limit ?? 30));
|
|
1019
|
+
const commits = gitService.getHistory(cap).map((c) => ({
|
|
1020
|
+
hash: c.hash.slice(0, 8),
|
|
1021
|
+
fullHash: c.hash,
|
|
1022
|
+
author: c.author,
|
|
1023
|
+
timestamp: c.timestamp,
|
|
1024
|
+
message: c.message,
|
|
1025
|
+
}));
|
|
1026
|
+
return ok({ repo, commits });
|
|
1027
|
+
}
|
|
1028
|
+
catch (err) {
|
|
1029
|
+
return fail(err);
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
const listRecentDeletionsSchema = z.object({
|
|
1033
|
+
repo: z.string().describe('Folder name'),
|
|
1034
|
+
within_days: z.number().optional().describe('Lookback window (default 7, max 90)'),
|
|
1035
|
+
});
|
|
1036
|
+
server.registerTool('list_recent_deletions', { description: 'List entries deleted within the last N days. Use before restore_file to find a target.', inputSchema: listRecentDeletionsSchema }, async (args) => {
|
|
1037
|
+
const { repo, within_days } = listRecentDeletionsSchema.parse(args);
|
|
1038
|
+
try {
|
|
1039
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
1040
|
+
return fail(`Folder "${repo}" not found`);
|
|
1041
|
+
const gitService = wm.getGraph(repo).getGitService();
|
|
1042
|
+
if (!gitService)
|
|
1043
|
+
return fail('Folder is not git-backed');
|
|
1044
|
+
const days = Math.min(90, Math.max(1, within_days ?? 7));
|
|
1045
|
+
const raw = gitService.listRecentDeletions(days);
|
|
1046
|
+
// Naive coords: use filename basename for entity_id; entity_type
|
|
1047
|
+
// sniffed from frontmatter at lastVersionCommit.
|
|
1048
|
+
const deletions = raw.map((d) => {
|
|
1049
|
+
const idMatch = d.filePath.match(/([^/]+)\.md$/i);
|
|
1050
|
+
const entityId = idMatch ? idMatch[1] : null;
|
|
1051
|
+
const content = entityId ? gitService.getFileAtCommit(d.filePath, d.lastVersionCommit) : null;
|
|
1052
|
+
let entityType = null;
|
|
1053
|
+
if (content) {
|
|
1054
|
+
const fm = content.match(/^---\s*\n([\s\S]*?)\n---/);
|
|
1055
|
+
const tm = fm?.[1].match(/^entity_type:\s*['"]?([\w-]+)['"]?\s*$/m);
|
|
1056
|
+
if (tm)
|
|
1057
|
+
entityType = tm[1];
|
|
1058
|
+
}
|
|
1059
|
+
if (!entityId || !entityType)
|
|
1060
|
+
return null;
|
|
1061
|
+
return {
|
|
1062
|
+
entityType,
|
|
1063
|
+
entityId,
|
|
1064
|
+
deletedAt: d.deletedAt,
|
|
1065
|
+
deletedBy: d.deletedBy.name,
|
|
1066
|
+
deletedInCommit: d.deletedAtCommit.slice(0, 8),
|
|
1067
|
+
lastVersionCommit: d.lastVersionCommit,
|
|
1068
|
+
commitMessage: d.commitMessage,
|
|
1069
|
+
path: d.filePath,
|
|
1070
|
+
};
|
|
1071
|
+
}).filter((x) => x !== null);
|
|
1072
|
+
return ok({ repo, withinDays: days, deletions });
|
|
1073
|
+
}
|
|
1074
|
+
catch (err) {
|
|
1075
|
+
return fail(err);
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
const readFileAtCommitSchema = z.object({
|
|
1079
|
+
repo: z.string().describe('Folder name'),
|
|
1080
|
+
entity_type: z.string().describe('Entity type'),
|
|
1081
|
+
entity_id: z.string().describe('Entry ID'),
|
|
1082
|
+
commit_hash: z.string().describe('Commit hash (short or full)'),
|
|
1083
|
+
});
|
|
1084
|
+
server.registerTool('read_file_at_commit', { description: 'Read an entry\'s full content at a specific commit.', inputSchema: readFileAtCommitSchema }, async (args) => {
|
|
1085
|
+
const { repo, entity_type, entity_id, commit_hash } = readFileAtCommitSchema.parse(args);
|
|
1086
|
+
try {
|
|
1087
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
1088
|
+
return fail(`Folder "${repo}" not found`);
|
|
1089
|
+
const graph = wm.getGraph(repo);
|
|
1090
|
+
const found = graph.getEntityAtCommit(entity_type, entity_id, commit_hash);
|
|
1091
|
+
if (!found)
|
|
1092
|
+
return fail(`Entry ${entity_type}/${entity_id} not found at commit ${commit_hash}`);
|
|
1093
|
+
// Surface the image/asset embeds present in the body at this commit so
|
|
1094
|
+
// a caller can verify whether images existed here without diffing the
|
|
1095
|
+
// raw markdown by eye. Asset *binaries* aren't in git — these are the
|
|
1096
|
+
// body references, the only image evidence git retains.
|
|
1097
|
+
const embeds = extractAssetEmbeds(stripFrontmatter(found.content));
|
|
1098
|
+
return ok({ repo, entityType: entity_type, entityId: entity_id, commitHash: commit_hash, path: found.path, content: found.content, embeds, embedCount: embeds.length });
|
|
1099
|
+
}
|
|
1100
|
+
catch (err) {
|
|
1101
|
+
return fail(err);
|
|
1102
|
+
}
|
|
1103
|
+
});
|
|
1104
|
+
const restoreFileSchema = z.object({
|
|
1105
|
+
repo: z.string().describe('Folder name'),
|
|
1106
|
+
entity_type: z.string().describe('Entity type'),
|
|
1107
|
+
entity_id: z.string().describe('Entry ID'),
|
|
1108
|
+
commit_hash: z.string().describe('Commit hash to restore from'),
|
|
1109
|
+
confirm: z.boolean().optional().describe('Set to true ONLY after the user has approved the preview. Defaults to false.'),
|
|
1110
|
+
});
|
|
1111
|
+
server.registerTool('restore_file', { description: 'Restore an entry to its content at a specific commit. Preview-then-confirm: first call returns the preview payload, re-invoke with confirm: true to execute.', inputSchema: restoreFileSchema }, async (args) => {
|
|
1112
|
+
const { repo, entity_type, entity_id, commit_hash, confirm } = restoreFileSchema.parse(args);
|
|
1113
|
+
try {
|
|
1114
|
+
if (!hasRepoAccess(user, repo, wm, authService))
|
|
1115
|
+
return fail(`Folder "${repo}" not found`);
|
|
1116
|
+
if (!canWriteRepo(user, repo, wm, authService))
|
|
1117
|
+
return fail(`You don't have permission to write to "${repo}". Editor or admin role required.`);
|
|
1118
|
+
const graph = wm.getGraph(repo);
|
|
1119
|
+
if (confirm !== true) {
|
|
1120
|
+
const found = graph.getEntityAtCommit(entity_type, entity_id, commit_hash);
|
|
1121
|
+
if (!found)
|
|
1122
|
+
return fail(`Entry ${entity_type}/${entity_id} not found at commit ${commit_hash}`);
|
|
1123
|
+
let action = 'recreate';
|
|
1124
|
+
try {
|
|
1125
|
+
graph.get(entity_type, entity_id);
|
|
1126
|
+
action = 'overwrite';
|
|
1127
|
+
}
|
|
1128
|
+
catch { }
|
|
1129
|
+
const sizeBytes = Buffer.byteLength(found.content, 'utf-8');
|
|
1130
|
+
const shortHash = commit_hash.length > 8 ? commit_hash.slice(0, 8) : commit_hash;
|
|
1131
|
+
// Embeds the restored body will re-link. Restore rewrites the .md
|
|
1132
|
+
// body only; asset binaries live outside git and are NOT touched by
|
|
1133
|
+
// restore — they remain wherever they are (intact unless separately
|
|
1134
|
+
// deleted), so restoring the body re-links any that still exist.
|
|
1135
|
+
const embeds = extractAssetEmbeds(stripFrontmatter(found.content));
|
|
1136
|
+
const assetNote = embeds.length
|
|
1137
|
+
? `Restores the body and re-links ${embeds.length} embedded asset reference(s): ${embeds.join(', ')}. ` +
|
|
1138
|
+
`Asset binaries are stored outside git and are not modified by restore (they remain intact unless separately deleted). `
|
|
1139
|
+
: '';
|
|
1140
|
+
return ok({
|
|
1141
|
+
success: false,
|
|
1142
|
+
code: 'PREVIEW_REQUIRED',
|
|
1143
|
+
preview: { action, targetPath: found.path, sizeBytes, commitHash: shortHash, embeds, embedCount: embeds.length },
|
|
1144
|
+
message: `Will ${action} ${entity_type}/${entity_id} from ${shortHash} (${sizeBytes} bytes). ${assetNote}Re-invoke with confirm: true after approval.`,
|
|
1145
|
+
});
|
|
244
1146
|
}
|
|
245
|
-
|
|
246
|
-
|
|
1147
|
+
const result = await withAgentActivity(repo, 'editing', async () => {
|
|
1148
|
+
const { PathConflictError } = await import('../core/graph.js');
|
|
1149
|
+
try {
|
|
1150
|
+
return await graph.restoreFile(entity_type, entity_id, commit_hash);
|
|
1151
|
+
}
|
|
1152
|
+
catch (err) {
|
|
1153
|
+
if (err instanceof PathConflictError) {
|
|
1154
|
+
return { conflict: { path: err.path, occupiedBy: err.occupiedBy } };
|
|
1155
|
+
}
|
|
1156
|
+
throw err;
|
|
1157
|
+
}
|
|
1158
|
+
});
|
|
1159
|
+
if ('conflict' in result) {
|
|
1160
|
+
return ok({
|
|
1161
|
+
success: false,
|
|
1162
|
+
code: 'PATH_CONFLICT',
|
|
1163
|
+
conflict: result.conflict,
|
|
1164
|
+
message: `Cannot restore — ${result.conflict.path} is occupied by ${result.conflict.occupiedBy.entityType}/${result.conflict.occupiedBy.entityId}.`,
|
|
1165
|
+
});
|
|
247
1166
|
}
|
|
248
|
-
|
|
249
|
-
|
|
1167
|
+
emitEntityChange(result.action === 'recreate' ? 'created' : 'updated', repo, entity_type, entity_id);
|
|
1168
|
+
const shortHash = commit_hash.length > 8 ? commit_hash.slice(0, 8) : commit_hash;
|
|
1169
|
+
return ok({
|
|
1170
|
+
success: true,
|
|
1171
|
+
repo,
|
|
1172
|
+
entityType: entity_type,
|
|
1173
|
+
entityId: entity_id,
|
|
1174
|
+
action: result.action,
|
|
1175
|
+
path: result.path,
|
|
1176
|
+
commitHash: shortHash,
|
|
1177
|
+
message: `Restored ${entity_type}/${entity_id} from ${shortHash} (${result.action}).`,
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
catch (err) {
|
|
1181
|
+
return fail(err);
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
250
1184
|
}
|
|
251
1185
|
//# sourceMappingURL=tools.js.map
|