studiograph 1.3.48-next.21 → 1.3.48-next.211
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -16
- package/dist/agent/agent-pool.d.ts +87 -3
- package/dist/agent/agent-pool.js +188 -15
- package/dist/agent/agent-pool.js.map +1 -1
- package/dist/agent/followups.d.ts +34 -0
- package/dist/agent/followups.js +52 -0
- package/dist/agent/followups.js.map +1 -0
- package/dist/agent/orchestrator.d.ts +101 -4
- package/dist/agent/orchestrator.js +464 -66
- package/dist/agent/orchestrator.js.map +1 -1
- package/dist/agent/prompts/entity-types-section.d.ts +34 -0
- package/dist/agent/prompts/entity-types-section.js +76 -0
- package/dist/agent/prompts/entity-types-section.js.map +1 -0
- package/dist/agent/prompts/system.md +112 -54
- package/dist/agent/skill-loader.d.ts +30 -17
- package/dist/agent/skill-loader.js +63 -30
- package/dist/agent/skill-loader.js.map +1 -1
- package/dist/agent/skills/artifact-canvas/skill.md +152 -0
- package/dist/agent/skills/entity-schema.md +75 -431
- package/dist/agent/skills/interview/entity-templates.md +38 -37
- package/dist/agent/skills/interview/question-domains.md +54 -49
- package/dist/agent/skills/interview/skill.md +84 -16
- package/dist/agent/skills/schema-rules.md +28 -28
- package/dist/agent/tools/artifact-tools.d.ts +124 -0
- package/dist/agent/tools/artifact-tools.js +207 -0
- package/dist/agent/tools/artifact-tools.js.map +1 -0
- package/dist/agent/tools/capture-tools.d.ts +31 -0
- package/dist/agent/tools/capture-tools.js +40 -0
- package/dist/agent/tools/capture-tools.js.map +1 -0
- package/dist/agent/tools/folder-tools.d.ts +47 -0
- package/dist/agent/tools/folder-tools.js +249 -0
- package/dist/agent/tools/folder-tools.js.map +1 -0
- package/dist/agent/tools/fs-tools.d.ts +6 -5
- package/dist/agent/tools/fs-tools.js +5 -5
- package/dist/agent/tools/fs-tools.js.map +1 -1
- package/dist/agent/tools/graph-tools.d.ts +48 -72
- package/dist/agent/tools/graph-tools.js +852 -329
- package/dist/agent/tools/graph-tools.js.map +1 -1
- package/dist/agent/tools/history-tools.d.ts +95 -0
- package/dist/agent/tools/history-tools.js +461 -0
- package/dist/agent/tools/history-tools.js.map +1 -0
- package/dist/agent/tools/load-skill.d.ts +6 -5
- package/dist/agent/tools/load-skill.js +3 -3
- package/dist/agent/tools/load-skill.js.map +1 -1
- package/dist/agent/tools/ops-tools.d.ts +5 -4
- package/dist/agent/tools/ops-tools.js +130 -236
- package/dist/agent/tools/ops-tools.js.map +1 -1
- package/dist/agent/tools/permission-tools.d.ts +87 -17
- package/dist/agent/tools/permission-tools.js +233 -43
- package/dist/agent/tools/permission-tools.js.map +1 -1
- package/dist/agent/tools/tool-loader.js +5 -4
- package/dist/agent/tools/tool-loader.js.map +1 -1
- package/dist/agent/tools/web-tools.js +58 -9
- package/dist/agent/tools/web-tools.js.map +1 -1
- package/dist/cli/commands/about.d.ts +1 -0
- package/dist/cli/commands/about.js +55 -3
- package/dist/cli/commands/about.js.map +1 -1
- package/dist/cli/commands/admin/audit-workspace.d.ts +23 -0
- package/dist/cli/commands/admin/audit-workspace.js +133 -0
- package/dist/cli/commands/admin/audit-workspace.js.map +1 -0
- package/dist/cli/commands/admin/audit.d.ts +21 -0
- package/dist/cli/commands/admin/audit.js +174 -0
- package/dist/cli/commands/admin/audit.js.map +1 -0
- package/dist/cli/commands/admin/backup.d.ts +54 -0
- package/dist/cli/commands/admin/backup.js +207 -0
- package/dist/cli/commands/admin/backup.js.map +1 -0
- package/dist/cli/commands/admin/folder.d.ts +11 -0
- package/dist/cli/commands/{collection.js → admin/folder.js} +33 -32
- package/dist/cli/commands/admin/folder.js.map +1 -0
- package/dist/cli/commands/admin/index.d.ts +16 -0
- package/dist/cli/commands/admin/index.js +46 -0
- package/dist/cli/commands/admin/index.js.map +1 -0
- package/dist/cli/commands/admin/migrate-assets.d.ts +53 -0
- package/dist/cli/commands/admin/migrate-assets.js +184 -0
- package/dist/cli/commands/admin/migrate-assets.js.map +1 -0
- package/dist/cli/commands/admin/migrate.d.ts +56 -0
- package/dist/cli/commands/admin/migrate.js +263 -0
- package/dist/cli/commands/admin/migrate.js.map +1 -0
- package/dist/cli/commands/admin/restore.d.ts +24 -0
- package/dist/cli/commands/admin/restore.js +228 -0
- package/dist/cli/commands/admin/restore.js.map +1 -0
- package/dist/cli/commands/admin/secrets.d.ts +23 -0
- package/dist/cli/commands/admin/secrets.js +256 -0
- package/dist/cli/commands/admin/secrets.js.map +1 -0
- package/dist/cli/commands/admin/settings.d.ts +28 -0
- package/dist/cli/commands/admin/settings.js +284 -0
- package/dist/cli/commands/admin/settings.js.map +1 -0
- package/dist/cli/commands/admin/token.d.ts +42 -0
- package/dist/cli/commands/admin/token.js +126 -0
- package/dist/cli/commands/admin/token.js.map +1 -0
- package/dist/cli/commands/admin/transfer-ownership.d.ts +15 -0
- package/dist/cli/commands/admin/transfer-ownership.js +106 -0
- package/dist/cli/commands/admin/transfer-ownership.js.map +1 -0
- package/dist/cli/commands/admin/user.d.ts +11 -0
- package/dist/cli/commands/admin/user.js +187 -0
- package/dist/cli/commands/admin/user.js.map +1 -0
- package/dist/cli/commands/clone.d.ts +25 -3
- package/dist/cli/commands/clone.js +142 -36
- package/dist/cli/commands/clone.js.map +1 -1
- package/dist/cli/commands/config.d.ts +23 -107
- package/dist/cli/commands/config.js +52 -260
- package/dist/cli/commands/config.js.map +1 -1
- package/dist/cli/commands/connector.d.ts +10 -13
- package/dist/cli/commands/connector.js +16 -58
- package/dist/cli/commands/connector.js.map +1 -1
- package/dist/cli/commands/deploy.js +215 -68
- package/dist/cli/commands/deploy.js.map +1 -1
- package/dist/cli/commands/index.js +5 -1
- package/dist/cli/commands/index.js.map +1 -1
- package/dist/cli/commands/init.js +10 -30
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/mcp.js +54 -6
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/r2.d.ts +3 -0
- package/dist/cli/commands/r2.js +223 -204
- package/dist/cli/commands/r2.js.map +1 -1
- package/dist/cli/commands/redeploy.js +65 -12
- package/dist/cli/commands/redeploy.js.map +1 -1
- package/dist/cli/commands/reset.d.ts +22 -6
- package/dist/cli/commands/reset.js +132 -34
- package/dist/cli/commands/reset.js.map +1 -1
- package/dist/cli/commands/serve.js +231 -26
- package/dist/cli/commands/serve.js.map +1 -1
- package/dist/cli/commands/sync.d.ts +1 -1
- package/dist/cli/commands/sync.js +237 -227
- package/dist/cli/commands/sync.js.map +1 -1
- package/dist/cli/crypto-bundle.d.ts +39 -0
- package/dist/cli/crypto-bundle.js +94 -0
- package/dist/cli/crypto-bundle.js.map +1 -0
- package/dist/cli/index.js +24 -26
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/railway-pin.d.ts +68 -0
- package/dist/cli/railway-pin.js +96 -0
- package/dist/cli/railway-pin.js.map +1 -0
- package/dist/cli/railway-provisioning.d.ts +53 -0
- package/dist/cli/railway-provisioning.js +85 -0
- package/dist/cli/railway-provisioning.js.map +1 -0
- package/dist/cli/setup-wizard.d.ts +30 -49
- package/dist/cli/setup-wizard.js +39 -40
- package/dist/cli/setup-wizard.js.map +1 -1
- package/dist/core/accent-palette.d.ts +22 -0
- package/dist/core/accent-palette.js +47 -0
- package/dist/core/accent-palette.js.map +1 -0
- package/dist/core/artifact-asset-urls.d.ts +51 -0
- package/dist/core/artifact-asset-urls.js +79 -0
- package/dist/core/artifact-asset-urls.js.map +1 -0
- package/dist/core/artifact-bundle.d.ts +69 -0
- package/dist/core/artifact-bundle.js +305 -0
- package/dist/core/artifact-bundle.js.map +1 -0
- package/dist/core/artifact-escape.d.ts +5 -0
- package/dist/core/artifact-escape.js +26 -0
- package/dist/core/artifact-escape.js.map +1 -0
- package/dist/core/artifact-export.d.ts +17 -0
- package/dist/core/artifact-export.js +529 -0
- package/dist/core/artifact-export.js.map +1 -0
- package/dist/core/cell-anchor.d.ts +29 -0
- package/dist/core/cell-anchor.js +53 -0
- package/dist/core/cell-anchor.js.map +1 -0
- package/dist/core/comment-anchor.d.ts +41 -0
- package/dist/core/comment-anchor.js +147 -0
- package/dist/core/comment-anchor.js.map +1 -0
- package/dist/core/commit-messages.d.ts +57 -0
- package/dist/core/commit-messages.js +85 -0
- package/dist/core/commit-messages.js.map +1 -0
- package/dist/core/embeds.d.ts +96 -0
- package/dist/core/embeds.js +196 -0
- package/dist/core/embeds.js.map +1 -0
- package/dist/core/entity-body-templates.d.ts +21 -0
- package/dist/core/entity-body-templates.js +67 -0
- package/dist/core/entity-body-templates.js.map +1 -0
- package/dist/core/entity-types-catalog.d.ts +65 -0
- package/dist/core/entity-types-catalog.js +142 -0
- package/dist/core/entity-types-catalog.js.map +1 -0
- package/dist/core/field-library.d.ts +62 -0
- package/dist/core/field-library.js +129 -0
- package/dist/core/field-library.js.map +1 -0
- package/dist/core/folder-paths.d.ts +41 -0
- package/dist/core/folder-paths.js +95 -0
- package/dist/core/folder-paths.js.map +1 -0
- package/dist/core/folder-sidecar.d.ts +36 -0
- package/dist/core/folder-sidecar.js +91 -0
- package/dist/core/folder-sidecar.js.map +1 -0
- package/dist/core/format-registry.d.ts +170 -0
- package/dist/core/format-registry.js +412 -0
- package/dist/core/format-registry.js.map +1 -0
- package/dist/core/graph.d.ts +773 -14
- package/dist/core/graph.js +2269 -308
- package/dist/core/graph.js.map +1 -1
- package/dist/core/history-diff.d.ts +35 -0
- package/dist/core/history-diff.js +114 -0
- package/dist/core/history-diff.js.map +1 -0
- package/dist/core/refs.d.ts +41 -0
- package/dist/core/refs.js +80 -0
- package/dist/core/refs.js.map +1 -0
- package/dist/core/schema-registry.d.ts +66 -0
- package/dist/core/schema-registry.js +198 -37
- package/dist/core/schema-registry.js.map +1 -1
- package/dist/core/schemas/connector.d.ts +67 -0
- package/dist/core/schemas/connector.js +100 -0
- package/dist/core/schemas/connector.js.map +1 -0
- package/dist/core/schemas/workspace.d.ts +122 -0
- package/dist/core/schemas/workspace.js +211 -0
- package/dist/core/schemas/workspace.js.map +1 -0
- package/dist/core/secrets/SecretStore.d.ts +77 -0
- package/dist/core/secrets/SecretStore.js +13 -0
- package/dist/core/secrets/SecretStore.js.map +1 -0
- package/dist/core/secrets/SettingsResolver.d.ts +54 -0
- package/dist/core/secrets/SettingsResolver.js +80 -0
- package/dist/core/secrets/SettingsResolver.js.map +1 -0
- package/dist/core/secrets/SettingsStore.d.ts +30 -0
- package/dist/core/secrets/SettingsStore.js +9 -0
- package/dist/core/secrets/SettingsStore.js.map +1 -0
- package/dist/core/secrets/SqliteEncryptedStore.d.ts +90 -0
- package/dist/core/secrets/SqliteEncryptedStore.js +598 -0
- package/dist/core/secrets/SqliteEncryptedStore.js.map +1 -0
- package/dist/core/secrets/audit.d.ts +36 -0
- package/dist/core/secrets/audit.js +60 -0
- package/dist/core/secrets/audit.js.map +1 -0
- package/dist/core/secrets/auth-db-migration.d.ts +129 -0
- package/dist/core/secrets/auth-db-migration.js +653 -0
- package/dist/core/secrets/auth-db-migration.js.map +1 -0
- package/dist/core/secrets/bundle.d.ts +141 -0
- package/dist/core/secrets/bundle.js +435 -0
- package/dist/core/secrets/bundle.js.map +1 -0
- package/dist/core/secrets/dual-write.d.ts +81 -0
- package/dist/core/secrets/dual-write.js +247 -0
- package/dist/core/secrets/dual-write.js.map +1 -0
- package/dist/core/secrets/encryption.d.ts +44 -0
- package/dist/core/secrets/encryption.js +97 -0
- package/dist/core/secrets/encryption.js.map +1 -0
- package/dist/core/secrets/index.d.ts +49 -0
- package/dist/core/secrets/index.js +74 -0
- package/dist/core/secrets/index.js.map +1 -0
- package/dist/core/secrets/master-key.d.ts +38 -0
- package/dist/core/secrets/master-key.js +122 -0
- package/dist/core/secrets/master-key.js.map +1 -0
- package/dist/core/secrets/probes/anthropic.d.ts +98 -0
- package/dist/core/secrets/probes/anthropic.js +300 -0
- package/dist/core/secrets/probes/anthropic.js.map +1 -0
- package/dist/core/secrets/probes/brave.d.ts +9 -0
- package/dist/core/secrets/probes/brave.js +15 -0
- package/dist/core/secrets/probes/brave.js.map +1 -0
- package/dist/core/secrets/probes/r2.d.ts +15 -0
- package/dist/core/secrets/probes/r2.js +14 -0
- package/dist/core/secrets/probes/r2.js.map +1 -0
- package/dist/core/secrets/probes/tavily.d.ts +18 -0
- package/dist/core/secrets/probes/tavily.js +58 -0
- package/dist/core/secrets/probes/tavily.js.map +1 -0
- package/dist/core/secrets/probes/voyage.d.ts +13 -0
- package/dist/core/secrets/probes/voyage.js +52 -0
- package/dist/core/secrets/probes/voyage.js.map +1 -0
- package/dist/core/secrets/r2-structural-migration.d.ts +39 -0
- package/dist/core/secrets/r2-structural-migration.js +109 -0
- package/dist/core/secrets/r2-structural-migration.js.map +1 -0
- package/dist/core/secrets/read-flip.d.ts +59 -0
- package/dist/core/secrets/read-flip.js +87 -0
- package/dist/core/secrets/read-flip.js.map +1 -0
- package/dist/core/secrets/registry.d.ts +188 -0
- package/dist/core/secrets/registry.js +616 -0
- package/dist/core/secrets/registry.js.map +1 -0
- package/dist/core/types.d.ts +77 -483
- package/dist/core/types.js +25 -3
- package/dist/core/types.js.map +1 -1
- package/dist/core/user-config.d.ts +75 -12
- package/dist/core/user-config.js +155 -14
- package/dist/core/user-config.js.map +1 -1
- package/dist/core/validation.d.ts +786 -1373
- package/dist/core/validation.js +458 -147
- package/dist/core/validation.js.map +1 -1
- package/dist/core/workspace-manager.d.ts +87 -14
- package/dist/core/workspace-manager.js +222 -94
- package/dist/core/workspace-manager.js.map +1 -1
- package/dist/core/workspace.d.ts +19 -5
- package/dist/core/workspace.js +67 -39
- package/dist/core/workspace.js.map +1 -1
- package/dist/mcp/comment-virtual-entity.d.ts +36 -0
- package/dist/mcp/comment-virtual-entity.js +71 -0
- package/dist/mcp/comment-virtual-entity.js.map +1 -0
- package/dist/mcp/connector-manager.d.ts +72 -2
- package/dist/mcp/connector-manager.js +211 -32
- package/dist/mcp/connector-manager.js.map +1 -1
- package/dist/mcp/oauth-provider.d.ts +0 -5
- package/dist/mcp/oauth-provider.js +10 -22
- package/dist/mcp/oauth-provider.js.map +1 -1
- package/dist/mcp/oauth-registry.d.ts +46 -8
- package/dist/mcp/oauth-registry.js +52 -13
- package/dist/mcp/oauth-registry.js.map +1 -1
- package/dist/mcp/server-discovery.d.ts +73 -0
- package/dist/mcp/server-discovery.js +164 -0
- package/dist/mcp/server-discovery.js.map +1 -0
- package/dist/mcp/server.d.ts +24 -3
- package/dist/mcp/server.js +53 -10
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/state-signer.d.ts +62 -0
- package/dist/mcp/state-signer.js +205 -0
- package/dist/mcp/state-signer.js.map +1 -0
- package/dist/mcp/stdio-proxy.d.ts +67 -0
- package/dist/mcp/stdio-proxy.js +149 -0
- package/dist/mcp/stdio-proxy.js.map +1 -0
- package/dist/mcp/tools.d.ts +65 -2
- package/dist/mcp/tools.js +1772 -124
- package/dist/mcp/tools.js.map +1 -1
- package/dist/server/__tests__/helpers/graph-api.d.ts +16 -0
- package/dist/server/__tests__/helpers/graph-api.js +16 -0
- package/dist/server/__tests__/helpers/graph-api.js.map +1 -0
- package/dist/server/artifact-asset-mint.d.ts +41 -0
- package/dist/server/artifact-asset-mint.js +59 -0
- package/dist/server/artifact-asset-mint.js.map +1 -0
- package/dist/server/asset-index-queue.d.ts +98 -0
- package/dist/server/asset-index-queue.js +193 -0
- package/dist/server/asset-index-queue.js.map +1 -0
- package/dist/server/body-write-coordinator.d.ts +161 -0
- package/dist/server/body-write-coordinator.js +182 -0
- package/dist/server/body-write-coordinator.js.map +1 -0
- package/dist/server/cloud-billing-flow.d.ts +32 -0
- package/dist/server/cloud-billing-flow.js +75 -0
- package/dist/server/cloud-billing-flow.js.map +1 -0
- package/dist/server/commit-audit.d.ts +26 -0
- package/dist/server/commit-audit.js +30 -0
- package/dist/server/commit-audit.js.map +1 -0
- package/dist/server/index.d.ts +29 -3
- package/dist/server/index.js +657 -269
- package/dist/server/index.js.map +1 -1
- package/dist/server/middleware/sanitize.d.ts +46 -0
- package/dist/server/middleware/sanitize.js +182 -0
- package/dist/server/middleware/sanitize.js.map +1 -0
- package/dist/server/routes/artifact-export-api.d.ts +11 -0
- package/dist/server/routes/artifact-export-api.js +159 -0
- package/dist/server/routes/artifact-export-api.js.map +1 -0
- package/dist/server/routes/asset-api.d.ts +76 -1
- package/dist/server/routes/asset-api.js +761 -61
- package/dist/server/routes/asset-api.js.map +1 -1
- package/dist/server/routes/audit-api.d.ts +24 -0
- package/dist/server/routes/audit-api.js +117 -0
- package/dist/server/routes/audit-api.js.map +1 -0
- package/dist/server/routes/auth-api.js +532 -63
- package/dist/server/routes/auth-api.js.map +1 -1
- package/dist/server/routes/capture-api.d.ts +10 -3
- package/dist/server/routes/capture-api.js +213 -25
- package/dist/server/routes/capture-api.js.map +1 -1
- package/dist/server/routes/chat-sessions-api.d.ts +13 -0
- package/dist/server/routes/chat-sessions-api.js +455 -0
- package/dist/server/routes/chat-sessions-api.js.map +1 -0
- package/dist/server/routes/chat.d.ts +35 -1
- package/dist/server/routes/chat.js +699 -54
- package/dist/server/routes/chat.js.map +1 -1
- package/dist/server/routes/comments-api.d.ts +15 -0
- package/dist/server/routes/comments-api.js +444 -0
- package/dist/server/routes/comments-api.js.map +1 -0
- package/dist/server/routes/config-api.d.ts +3 -2
- package/dist/server/routes/config-api.js +179 -49
- package/dist/server/routes/config-api.js.map +1 -1
- package/dist/server/routes/connectors-api.js +177 -42
- package/dist/server/routes/connectors-api.js.map +1 -1
- package/dist/server/routes/event-ingest-api.d.ts +15 -0
- package/dist/server/routes/{meeting-ingest-api.js → event-ingest-api.js} +35 -20
- package/dist/server/routes/event-ingest-api.js.map +1 -0
- package/dist/server/routes/field-library-api.d.ts +28 -0
- package/dist/server/routes/field-library-api.js +126 -0
- package/dist/server/routes/field-library-api.js.map +1 -0
- package/dist/server/routes/git-http.d.ts +2 -2
- package/dist/server/routes/git-http.js +86 -39
- package/dist/server/routes/git-http.js.map +1 -1
- package/dist/server/routes/graph-api-access.d.ts +57 -0
- package/dist/server/routes/graph-api-access.js +112 -0
- package/dist/server/routes/graph-api-access.js.map +1 -0
- package/dist/server/routes/graph-api.d.ts +1 -1
- package/dist/server/routes/graph-api.js +1455 -325
- package/dist/server/routes/graph-api.js.map +1 -1
- package/dist/server/routes/health.d.ts +18 -0
- package/dist/server/routes/health.js +74 -0
- package/dist/server/routes/health.js.map +1 -0
- package/dist/server/routes/import-batch.d.ts +24 -0
- package/dist/server/routes/import-batch.js +33 -0
- package/dist/server/routes/import-batch.js.map +1 -0
- package/dist/server/routes/insights-api.d.ts +9 -2
- package/dist/server/routes/insights-api.js +355 -61
- package/dist/server/routes/insights-api.js.map +1 -1
- package/dist/server/routes/mcp.d.ts +7 -3
- package/dist/server/routes/mcp.js +24 -8
- package/dist/server/routes/mcp.js.map +1 -1
- package/dist/server/routes/oauth-api.d.ts +67 -0
- package/dist/server/routes/oauth-api.js +421 -0
- package/dist/server/routes/oauth-api.js.map +1 -0
- package/dist/server/routes/permissions-api.d.ts +9 -2
- package/dist/server/routes/permissions-api.js +87 -31
- package/dist/server/routes/permissions-api.js.map +1 -1
- package/dist/server/routes/r2-api.d.ts +25 -0
- package/dist/server/routes/r2-api.js +276 -0
- package/dist/server/routes/r2-api.js.map +1 -0
- package/dist/server/routes/secrets-api.d.ts +36 -0
- package/dist/server/routes/secrets-api.js +308 -0
- package/dist/server/routes/secrets-api.js.map +1 -0
- package/dist/server/routes/settings-api.d.ts +29 -0
- package/dist/server/routes/settings-api.js +180 -0
- package/dist/server/routes/settings-api.js.map +1 -0
- package/dist/server/routes/share-api.d.ts +32 -0
- package/dist/server/routes/share-api.js +234 -0
- package/dist/server/routes/share-api.js.map +1 -0
- package/dist/server/routes/views-api.js +51 -0
- package/dist/server/routes/views-api.js.map +1 -1
- package/dist/server/routes/workspace-api.d.ts +2 -1
- package/dist/server/routes/workspace-api.js +142 -23
- package/dist/server/routes/workspace-api.js.map +1 -1
- package/dist/server/routes/ws.d.ts +3 -2
- package/dist/server/routes/ws.js +68 -17
- package/dist/server/routes/ws.js.map +1 -1
- package/dist/server/session-manager.d.ts +48 -5
- package/dist/server/session-manager.js +182 -14
- package/dist/server/session-manager.js.map +1 -1
- package/dist/server/ws-hub.d.ts +124 -37
- package/dist/server/ws-hub.js +0 -0
- package/dist/server/ws-hub.js.map +1 -1
- package/dist/server/yjs-manager.d.ts +278 -7
- package/dist/server/yjs-manager.js +830 -38
- package/dist/server/yjs-manager.js.map +1 -1
- package/dist/server/yjs-persistence.d.ts +165 -0
- package/dist/server/yjs-persistence.js +557 -0
- package/dist/server/yjs-persistence.js.map +1 -0
- package/dist/server/yjs-text-diff.d.ts +32 -0
- package/dist/server/yjs-text-diff.js +61 -0
- package/dist/server/yjs-text-diff.js.map +1 -0
- package/dist/services/access-control.d.ts +73 -0
- package/dist/services/access-control.js +165 -0
- package/dist/services/access-control.js.map +1 -0
- package/dist/services/artifact-asset-token.d.ts +69 -0
- package/dist/services/artifact-asset-token.js +94 -0
- package/dist/services/artifact-asset-token.js.map +1 -0
- package/dist/services/artifact-library-sources.d.ts +3 -0
- package/dist/services/artifact-library-sources.js +44 -0
- package/dist/services/artifact-library-sources.js.map +1 -0
- package/dist/services/artifact-render-export.d.ts +97 -0
- package/dist/services/artifact-render-export.js +467 -0
- package/dist/services/artifact-render-export.js.map +1 -0
- package/dist/services/asset-caption-service.d.ts +103 -0
- package/dist/services/asset-caption-service.js +186 -0
- package/dist/services/asset-caption-service.js.map +1 -0
- package/dist/services/asset-delete-authz.d.ts +31 -0
- package/dist/services/asset-delete-authz.js +61 -0
- package/dist/services/asset-delete-authz.js.map +1 -0
- package/dist/services/asset-finalize-service.d.ts +58 -0
- package/dist/services/asset-finalize-service.js +216 -0
- package/dist/services/asset-finalize-service.js.map +1 -0
- package/dist/services/asset-import-service.d.ts +111 -0
- package/dist/services/asset-import-service.js +394 -0
- package/dist/services/asset-import-service.js.map +1 -0
- package/dist/services/asset-index-sweep.d.ts +95 -0
- package/dist/services/asset-index-sweep.js +249 -0
- package/dist/services/asset-index-sweep.js.map +1 -0
- package/dist/services/asset-lifecycle-check.d.ts +22 -0
- package/dist/services/asset-lifecycle-check.js +80 -0
- package/dist/services/asset-lifecycle-check.js.map +1 -0
- package/dist/services/asset-listing.d.ts +72 -0
- package/dist/services/asset-listing.js +321 -0
- package/dist/services/asset-listing.js.map +1 -0
- package/dist/services/asset-presign-service.d.ts +68 -0
- package/dist/services/asset-presign-service.js +124 -0
- package/dist/services/asset-presign-service.js.map +1 -0
- package/dist/services/asset-sidecar-service.d.ts +28 -0
- package/dist/services/asset-sidecar-service.js +79 -0
- package/dist/services/asset-sidecar-service.js.map +1 -0
- package/dist/services/asset-upload-errors.d.ts +41 -0
- package/dist/services/asset-upload-errors.js +45 -0
- package/dist/services/asset-upload-errors.js.map +1 -0
- package/dist/services/asset-upload-service.d.ts +56 -0
- package/dist/services/asset-upload-service.js +200 -0
- package/dist/services/asset-upload-service.js.map +1 -0
- package/dist/services/asset-upload-token.d.ts +58 -0
- package/dist/services/asset-upload-token.js +75 -0
- package/dist/services/asset-upload-token.js.map +1 -0
- package/dist/services/assets/asset-index-service.d.ts +127 -0
- package/dist/services/assets/asset-index-service.js +227 -0
- package/dist/services/assets/asset-index-service.js.map +1 -0
- package/dist/services/assets/base.d.ts +128 -2
- package/dist/services/assets/base.js +98 -1
- package/dist/services/assets/base.js.map +1 -1
- package/dist/services/assets/index.d.ts +114 -1
- package/dist/services/assets/index.js +221 -0
- package/dist/services/assets/index.js.map +1 -1
- package/dist/services/assets/local.d.ts +16 -1
- package/dist/services/assets/local.js +110 -1
- package/dist/services/assets/local.js.map +1 -1
- package/dist/services/assets/r2-sync.d.ts +88 -0
- package/dist/services/assets/r2-sync.js +412 -0
- package/dist/services/assets/r2-sync.js.map +1 -0
- package/dist/services/assets/r2.d.ts +87 -1
- package/dist/services/assets/r2.js +203 -1
- package/dist/services/assets/r2.js.map +1 -1
- package/dist/services/auth-service.d.ts +312 -45
- package/dist/services/auth-service.js +1011 -195
- package/dist/services/auth-service.js.map +1 -1
- package/dist/services/chat-session-service.d.ts +188 -0
- package/dist/services/chat-session-service.js +512 -0
- package/dist/services/chat-session-service.js.map +1 -0
- package/dist/services/cloud-inference-billing.d.ts +64 -0
- package/dist/services/cloud-inference-billing.js +313 -0
- package/dist/services/cloud-inference-billing.js.map +1 -0
- package/dist/services/comment-service.d.ts +97 -0
- package/dist/services/comment-service.js +341 -0
- package/dist/services/comment-service.js.map +1 -0
- package/dist/services/comment-virtual-entity.d.ts +36 -0
- package/dist/services/comment-virtual-entity.js +71 -0
- package/dist/services/comment-virtual-entity.js.map +1 -0
- package/dist/services/convert-service.d.ts +64 -0
- package/dist/services/convert-service.js +68 -0
- package/dist/services/convert-service.js.map +1 -0
- package/dist/services/csv-service.d.ts +22 -17
- package/dist/services/csv-service.js +55 -92
- package/dist/services/csv-service.js.map +1 -1
- package/dist/services/entity-mutations.d.ts +213 -0
- package/dist/services/entity-mutations.js +380 -0
- package/dist/services/entity-mutations.js.map +1 -0
- package/dist/services/{meeting-processor.d.ts → event-processor.d.ts} +15 -8
- package/dist/services/{meeting-processor.js → event-processor.js} +124 -65
- package/dist/services/event-processor.js.map +1 -0
- package/dist/services/extract/docx.d.ts +1 -0
- package/dist/services/extract/docx.js +233 -0
- package/dist/services/extract/docx.js.map +1 -0
- package/dist/services/extract/index.d.ts +9 -0
- package/dist/services/extract/index.js +10 -0
- package/dist/services/extract/index.js.map +1 -0
- package/dist/services/extract/pdf.d.ts +13 -0
- package/dist/services/extract/pdf.js +69 -0
- package/dist/services/extract/pdf.js.map +1 -0
- package/dist/services/folder-creation.d.ts +33 -0
- package/dist/services/folder-creation.js +103 -0
- package/dist/services/folder-creation.js.map +1 -0
- package/dist/services/git.d.ts +58 -0
- package/dist/services/git.js +194 -55
- package/dist/services/git.js.map +1 -1
- package/dist/services/graph-maintenance.js +4 -4
- package/dist/services/graph-maintenance.js.map +1 -1
- package/dist/services/import-service.d.ts +51 -2
- package/dist/services/import-service.js +254 -107
- package/dist/services/import-service.js.map +1 -1
- package/dist/services/lint-service.js +10 -17
- package/dist/services/lint-service.js.map +1 -1
- package/dist/services/markdown.d.ts +12 -1
- package/dist/services/markdown.js +77 -9
- package/dist/services/markdown.js.map +1 -1
- package/dist/services/mention-detector.d.ts +23 -0
- package/dist/services/mention-detector.js +47 -0
- package/dist/services/mention-detector.js.map +1 -0
- package/dist/services/model-capabilities.d.ts +41 -0
- package/dist/services/model-capabilities.js +107 -0
- package/dist/services/model-capabilities.js.map +1 -0
- package/dist/services/notification-broadcast.d.ts +32 -0
- package/dist/services/notification-broadcast.js +38 -0
- package/dist/services/notification-broadcast.js.map +1 -0
- package/dist/services/notification-service.d.ts +41 -0
- package/dist/services/notification-service.js +100 -0
- package/dist/services/notification-service.js.map +1 -0
- package/dist/services/oauth-server-store.d.ts +147 -0
- package/dist/services/oauth-server-store.js +319 -0
- package/dist/services/oauth-server-store.js.map +1 -0
- package/dist/services/orphan-service.js +2 -2
- package/dist/services/orphan-service.js.map +1 -1
- package/dist/services/personal-folder.d.ts +40 -0
- package/dist/services/personal-folder.js +101 -0
- package/dist/services/personal-folder.js.map +1 -0
- package/dist/services/scratch-asset-promote.d.ts +57 -0
- package/dist/services/scratch-asset-promote.js +95 -0
- package/dist/services/scratch-asset-promote.js.map +1 -0
- package/dist/services/scratch-attachment-service.d.ts +196 -0
- package/dist/services/scratch-attachment-service.js +347 -0
- package/dist/services/scratch-attachment-service.js.map +1 -0
- package/dist/services/share-link-service.d.ts +57 -0
- package/dist/services/share-link-service.js +142 -0
- package/dist/services/share-link-service.js.map +1 -0
- package/dist/services/user-person-bridge.d.ts +136 -0
- package/dist/services/user-person-bridge.js +340 -0
- package/dist/services/user-person-bridge.js.map +1 -0
- package/dist/services/vector-service.d.ts +282 -5
- package/dist/services/vector-service.js +585 -29
- package/dist/services/vector-service.js.map +1 -1
- package/dist/services/workspace-access.d.ts +108 -0
- package/dist/services/workspace-access.js +149 -0
- package/dist/services/workspace-access.js.map +1 -0
- package/dist/services/workspace-assets-folder.d.ts +46 -0
- package/dist/services/workspace-assets-folder.js +75 -0
- package/dist/services/workspace-assets-folder.js.map +1 -0
- package/dist/utils/git.d.ts +17 -2
- package/dist/utils/git.js +23 -7
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/log.d.ts +42 -0
- package/dist/utils/log.js +137 -0
- package/dist/utils/log.js.map +1 -0
- package/dist/utils/preflight.js +2 -2
- package/dist/utils/preflight.js.map +1 -1
- package/dist/utils/version-checker.d.ts +12 -19
- package/dist/utils/version-checker.js +14 -104
- package/dist/utils/version-checker.js.map +1 -1
- package/dist/web/_app/immutable/assets/0.Bs_z3Z86.css +2 -0
- package/dist/web/_app/immutable/assets/12.DlIf1mKh.css +1 -0
- package/dist/web/_app/immutable/assets/14.7d-Q37wc.css +1 -0
- package/dist/web/_app/immutable/assets/15.BwBFdc1D.css +1 -0
- package/dist/web/_app/immutable/assets/16.CmLrbzZp.css +1 -0
- package/dist/web/_app/immutable/assets/17.DcuK5ZVm.css +1 -0
- package/dist/web/_app/immutable/assets/2.DATnR31c.css +1 -0
- package/dist/web/_app/immutable/assets/3.Cz4H8n_O.css +1 -0
- package/dist/web/_app/immutable/assets/4.aDHteTDy.css +1 -0
- package/dist/web/_app/immutable/assets/6.CF1i5Bon.css +1 -0
- package/dist/web/_app/immutable/assets/ArtifactEntityView.C4ooklFD.css +1 -0
- package/dist/web/_app/immutable/assets/AssetLibraryPicker.BOFW0MvF.css +1 -0
- package/dist/web/_app/immutable/assets/BulkActionsBar.cmKvHzRK.css +1 -0
- package/dist/web/_app/immutable/assets/CalendarView.lBCvS3X5.css +1 -0
- package/dist/web/_app/immutable/assets/CodeMirrorEditor.D6hf2pNJ.css +1 -0
- package/dist/web/_app/immutable/assets/CopyField.DVGZtw4U.css +1 -0
- package/dist/web/_app/immutable/assets/FolderMembersPanel.kiYnDFHu.css +1 -0
- package/dist/web/_app/immutable/assets/FolderMutationDialogs.BMRF6Z3z.css +1 -0
- package/dist/web/_app/immutable/assets/FolderPicker.Cp46GHe2.css +1 -0
- package/dist/web/_app/immutable/assets/GalleryView.WBGxxUQc.css +1 -0
- package/dist/web/_app/immutable/assets/GraphView.BJtGL9py.css +1 -0
- package/dist/web/_app/immutable/assets/KanbanView.BOaI5KFL.css +1 -0
- package/dist/web/_app/immutable/assets/Link.vV0ne105.css +1 -0
- package/dist/web/_app/immutable/assets/Markdown.BZnsSOSf.css +1 -0
- package/dist/web/_app/immutable/assets/Rail.ilusFgD4.css +1 -0
- package/dist/web/_app/immutable/assets/SettingsDialog.D7-BO6S_.css +1 -0
- package/dist/web/_app/immutable/assets/Spinner.UBfl0pab.css +1 -0
- package/dist/web/_app/immutable/assets/StopFilledAlt.fNlDN65D.css +1 -0
- package/dist/web/_app/immutable/assets/TimelineView.xAZBJhHw.css +1 -0
- package/dist/web/_app/immutable/assets/WorkspaceView.C8ErUJAY.css +1 -0
- package/dist/web/_app/immutable/assets/button.BlLm4Dg1.css +1 -0
- package/dist/web/_app/immutable/assets/history-summary.LpmHXasl.css +1 -0
- package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-400-normal.CvHOgSBP.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-400-normal.DMJ8VG8y.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-500-normal.CB9ihrfo.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-mono-latin-500-normal.DSY6xOcd.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-italic.CZTNEAuW.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-italic.CsGl1sm0.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-normal.CDDApCn2.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-400-normal.CYLoc0-x.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-italic.BNK2_mGO.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-italic.DpEwFAQM.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-normal.6ng42L7E.woff2 +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-500-normal.BgVn5rGT.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-600-normal.Cu4Hd6ag.woff +0 -0
- package/dist/web/_app/immutable/assets/ibm-plex-sans-latin-600-normal.CuJfVYMP.woff2 +0 -0
- package/dist/web/_app/immutable/assets/inline-list.cqga56xT.css +1 -0
- package/dist/web/_app/immutable/assets/list-columns.BgnafZ4K.css +1 -0
- package/dist/web/_app/immutable/assets/realtime.DDCWxn3B.css +1 -0
- package/dist/web/_app/immutable/assets/select.B90KUqR4.css +1 -0
- package/dist/web/_app/immutable/assets/sheet.RF9RBmml.css +1 -0
- package/dist/web/_app/immutable/chunks/22qYtvr82.js +1 -0
- package/dist/web/_app/immutable/chunks/2f1VclTm.js +1 -0
- package/dist/web/_app/immutable/chunks/4xgUPoGj2.js +2 -0
- package/dist/web/_app/immutable/chunks/5CYQk4xR.js +1 -0
- package/dist/web/_app/immutable/chunks/6S9WOky52.js +1 -0
- package/dist/web/_app/immutable/chunks/9K8VREGP.js +1 -0
- package/dist/web/_app/immutable/chunks/B-n5q6ku.js +1 -0
- package/dist/web/_app/immutable/chunks/B0p2fSEV2.js +1 -0
- package/dist/web/_app/immutable/chunks/B2oi87jG.js +2 -0
- package/dist/web/_app/immutable/chunks/B5Rb52QU.js +1 -0
- package/dist/web/_app/immutable/chunks/B6Lt-qaJ.js +1 -0
- package/dist/web/_app/immutable/chunks/B6O-q2CN.js +1 -0
- package/dist/web/_app/immutable/chunks/B6qeIQZz.js +2 -0
- package/dist/web/_app/immutable/chunks/B9KfEsQw2.js +1 -0
- package/dist/web/_app/immutable/chunks/BB-RSeXQ.js +1 -0
- package/dist/web/_app/immutable/chunks/BCQQzpbj.js +1 -0
- package/dist/web/_app/immutable/chunks/BGdzd-Tc.js +1 -0
- package/dist/web/_app/immutable/chunks/BLs1h0Rh2.js +1 -0
- package/dist/web/_app/immutable/chunks/BPzV_xOS2.js +1 -0
- package/dist/web/_app/immutable/chunks/BT2C6q8n.js +1 -0
- package/dist/web/_app/immutable/chunks/BUesbLq2.js +1 -0
- package/dist/web/_app/immutable/chunks/BUrnwOw4.js +2 -0
- package/dist/web/_app/immutable/chunks/BV-iCKqa.js +1 -0
- package/dist/web/_app/immutable/chunks/BVCJtXyc.js +1 -0
- package/dist/web/_app/immutable/chunks/BY9a3GSt.js +6 -0
- package/dist/web/_app/immutable/chunks/BZi3uKF4.js +1 -0
- package/dist/web/_app/immutable/chunks/BbfHGU9n2.js +1 -0
- package/dist/web/_app/immutable/chunks/Bbqu2-5G2.js +1 -0
- package/dist/web/_app/immutable/chunks/BbsX1Slr.js +3 -0
- package/dist/web/_app/immutable/chunks/BcwjqHaZ2.js +1 -0
- package/dist/web/_app/immutable/chunks/BizeEpTx.js +2 -0
- package/dist/web/_app/immutable/chunks/BlgjVmFM2.js +14 -0
- package/dist/web/_app/immutable/chunks/BocSRgv12.js +1 -0
- package/dist/web/_app/immutable/chunks/BojBYqc3.js +1 -0
- package/dist/web/_app/immutable/chunks/BpCG9m962.js +1 -0
- package/dist/web/_app/immutable/chunks/BqvnBg_y.js +1 -0
- package/dist/web/_app/immutable/chunks/BsT0J1QD.js +1 -0
- package/dist/web/_app/immutable/chunks/BsW72fyR2.js +1 -0
- package/dist/web/_app/immutable/chunks/Bti_XKwx.js +2692 -0
- package/dist/web/_app/immutable/chunks/Bua97Had2.js +2 -0
- package/dist/web/_app/immutable/chunks/BuhW5Fr0.js +689 -0
- package/dist/web/_app/immutable/chunks/Bx74EEyn2.js +1 -0
- package/dist/web/_app/immutable/chunks/BxPN0T61.js +1 -0
- package/dist/web/_app/immutable/chunks/Bz9lrgGs.js +1 -0
- package/dist/web/_app/immutable/chunks/BzaxZ6j12.js +1 -0
- package/dist/web/_app/immutable/chunks/C1B4xDs0.js +1 -0
- package/dist/web/_app/immutable/chunks/C1trcC8M.js +7 -0
- package/dist/web/_app/immutable/chunks/C9tvkk7Y2.js +22 -0
- package/dist/web/_app/immutable/chunks/CBQjdDZf.js +1 -0
- package/dist/web/_app/immutable/chunks/CDeazMFW2.js +2 -0
- package/dist/web/_app/immutable/chunks/CEahbtnP.js +1 -0
- package/dist/web/_app/immutable/chunks/CGjVCd0e.js +8 -0
- package/dist/web/_app/immutable/chunks/CICK9maP.js +1 -0
- package/dist/web/_app/immutable/chunks/CIoNEHQ82.js +184 -0
- package/dist/web/_app/immutable/chunks/CP8_U450.js +1 -0
- package/dist/web/_app/immutable/chunks/CQ1eQ93t2.js +6 -0
- package/dist/web/_app/immutable/chunks/CUFQMEBn2.js +1 -0
- package/dist/web/_app/immutable/chunks/CYEYDNwO2.js +1 -0
- package/dist/web/_app/immutable/chunks/CYyIP7kP2.js +66 -0
- package/dist/web/_app/immutable/chunks/CZgFaimi2.js +83 -0
- package/dist/web/_app/immutable/chunks/CZqvCTCv.js +1 -0
- package/dist/web/_app/immutable/chunks/CcI9jYBV.js +1 -0
- package/dist/web/_app/immutable/chunks/CcWaWbrI2.js +1 -0
- package/dist/web/_app/immutable/chunks/CdtzRVZK.js +1 -0
- package/dist/web/_app/immutable/chunks/CfYG72Hc.js +1 -0
- package/dist/web/_app/immutable/chunks/Ch1kmm3J2.js +1 -0
- package/dist/web/_app/immutable/chunks/CmSSN61R2.js +1 -0
- package/dist/web/_app/immutable/chunks/Cqm5wAin.js +1 -0
- package/dist/web/_app/immutable/chunks/CuYPXpWU.js +1 -0
- package/dist/web/_app/immutable/chunks/CvSEodTA.js +1 -0
- package/dist/web/_app/immutable/chunks/CvqqrTrj.js +1 -0
- package/dist/web/_app/immutable/chunks/CwAmWt6h.js +1 -0
- package/dist/web/_app/immutable/chunks/CwP3Oa7F.js +1 -0
- package/dist/web/_app/immutable/chunks/CyTluew1.js +2 -0
- package/dist/web/_app/immutable/chunks/CzXdOgVl2.js +7 -0
- package/dist/web/_app/immutable/chunks/D0IAituJ.js +1 -0
- package/dist/web/_app/immutable/chunks/D0W7c6Sg.js +185 -0
- package/dist/web/_app/immutable/chunks/D0rBhnqB2.js +1 -0
- package/dist/web/_app/immutable/chunks/D1-GZSN1.js +1 -0
- package/dist/web/_app/immutable/chunks/D1yvtriI.js +1 -0
- package/dist/web/_app/immutable/chunks/D25NMRpl.js +1 -0
- package/dist/web/_app/immutable/chunks/D8wZ2xul.js +4 -0
- package/dist/web/_app/immutable/chunks/DA3HEX3X.js +1 -0
- package/dist/web/_app/immutable/chunks/DD9wj4ca2.js +1 -0
- package/dist/web/_app/immutable/chunks/DJ1YeiJL2.js +1 -0
- package/dist/web/_app/immutable/chunks/DKFZnpVC.js +5 -0
- package/dist/web/_app/immutable/chunks/DKJl40hl.js +1 -0
- package/dist/web/_app/immutable/chunks/DPUVu0T0.js +1 -0
- package/dist/web/_app/immutable/chunks/DQMWa2t7.js +1 -0
- package/dist/web/_app/immutable/chunks/DXsFrDer2.js +1 -0
- package/dist/web/_app/immutable/chunks/DYc5KW3F.js +1 -0
- package/dist/web/_app/immutable/chunks/DZcTwXBW.js +1 -0
- package/dist/web/_app/immutable/chunks/DcPrzUMJ.js +1 -0
- package/dist/web/_app/immutable/chunks/DdPO8kRu2.js +2 -0
- package/dist/web/_app/immutable/chunks/DeOXyJZw2.js +1 -0
- package/dist/web/_app/immutable/chunks/DfufUFR1.js +2 -0
- package/dist/web/_app/immutable/chunks/DiQg3ing.js +1 -0
- package/dist/web/_app/immutable/chunks/DjIqa1_y.js +2 -0
- package/dist/web/_app/immutable/chunks/DkJ1dkxV2.js +1 -0
- package/dist/web/_app/immutable/chunks/Dl63IUnH2.js +224 -0
- package/dist/web/_app/immutable/chunks/DmFdD6zT.js +1 -0
- package/dist/web/_app/immutable/chunks/Dn3R8S-U.js +1 -0
- package/dist/web/_app/immutable/chunks/DpNr1GQ2.js +1 -0
- package/dist/web/_app/immutable/chunks/DpVNDHXz.js +2 -0
- package/dist/web/_app/immutable/chunks/DuxziNnw.js +1 -0
- package/dist/web/_app/immutable/chunks/Dv3A3Wcj.js +1 -0
- package/dist/web/_app/immutable/chunks/FuiJnZG0.js +1 -0
- package/dist/web/_app/immutable/chunks/Izkulod7.js +1 -0
- package/dist/web/_app/immutable/chunks/KZY6ongq2.js +2 -0
- package/dist/web/_app/immutable/chunks/LYL8znYR2.js +23 -0
- package/dist/web/_app/immutable/chunks/PhN6tXuJ2.js +1 -0
- package/dist/web/_app/immutable/chunks/PyLyCpuL.js +1 -0
- package/dist/web/_app/immutable/chunks/RK4gCWN0.js +2 -0
- package/dist/web/_app/immutable/chunks/VsBngTf4.js +2 -0
- package/dist/web/_app/immutable/chunks/XcWejFm_.js +5 -0
- package/dist/web/_app/immutable/chunks/al_nidE9.js +1 -0
- package/dist/web/_app/immutable/chunks/e50-izZO2.js +1 -0
- package/dist/web/_app/immutable/chunks/hG-QCarB.js +1 -0
- package/dist/web/_app/immutable/chunks/hStvCHkX.js +1 -0
- package/dist/web/_app/immutable/chunks/iJVY9p0y.js +3 -0
- package/dist/web/_app/immutable/chunks/kNaey6uv.js +1 -0
- package/dist/web/_app/immutable/chunks/pIXNe0C1.js +1 -0
- package/dist/web/_app/immutable/chunks/pNSRq_1B.js +2 -0
- package/dist/web/_app/immutable/chunks/qwr5boV8.js +1 -0
- package/dist/web/_app/immutable/chunks/rjjyuGhb.js +1 -0
- package/dist/web/_app/immutable/chunks/sg-T-FoN.js +1 -0
- package/dist/web/_app/immutable/chunks/uBgoVlsx.js +3 -0
- package/dist/web/_app/immutable/chunks/ulshNz6x.js +2 -0
- package/dist/web/_app/immutable/chunks/xY0c-UfG.js +3 -0
- package/dist/web/_app/immutable/chunks/xeNvx4Bl2.js +1 -0
- package/dist/web/_app/immutable/chunks/xihTtKlq.js +1 -0
- package/dist/web/_app/immutable/entry/app.pNUW-q4U.js +2 -0
- package/dist/web/_app/immutable/entry/start.DxGtIGG_.js +1 -0
- package/dist/web/_app/immutable/nodes/0.D6ZCY_R6.js +2 -0
- package/dist/web/_app/immutable/nodes/1.BryH3TNr.js +1 -0
- package/dist/web/_app/immutable/nodes/10.CMZMvR17.js +1 -0
- package/dist/web/_app/immutable/nodes/11.QHx_JShx.js +1 -0
- package/dist/web/_app/immutable/nodes/12.CiRWbb4X.js +1 -0
- package/dist/web/_app/immutable/nodes/13.CYrHXCA8.js +1 -0
- package/dist/web/_app/immutable/nodes/14.K18eIE5Z.js +1 -0
- package/dist/web/_app/immutable/nodes/15.DxgnZ61h.js +1 -0
- package/dist/web/_app/immutable/nodes/16.Dn6iuWIo.js +1 -0
- package/dist/web/_app/immutable/nodes/17.viwS7vTR.js +77 -0
- package/dist/web/_app/immutable/nodes/2.BQkQ_uUM.js +122 -0
- package/dist/web/_app/immutable/nodes/3.B4pqXsqq.js +6 -0
- package/dist/web/_app/immutable/nodes/4.CKXEgIAP.js +11 -0
- package/dist/web/_app/immutable/nodes/5.A2TASl4Q.js +1 -0
- package/dist/web/_app/immutable/nodes/6.DaIRO3SI.js +6 -0
- package/dist/web/_app/immutable/nodes/7.ga2UvFm-.js +1 -0
- package/dist/web/_app/immutable/nodes/8.BTEWT81u.js +1 -0
- package/dist/web/_app/immutable/nodes/9.BVxo0E0c.js +1 -0
- package/dist/web/_app/version.json +1 -1
- package/dist/web/favicon-16.png +0 -0
- package/dist/web/favicon-180.png +0 -0
- package/dist/web/favicon-32.png +0 -0
- package/dist/web/favicon-48.png +0 -0
- package/dist/web/favicon-512.png +0 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/favicon.svg +8 -0
- package/dist/web/index.html +56 -21
- package/dist/web/studiograph-logomark.svg +29 -0
- package/package.json +35 -14
- package/dist/agent/skills/enrich-entities.md +0 -136
- package/dist/agent/skills/sync-configuration.md +0 -119
- package/dist/agent/skills/sync-setup.md +0 -82
- package/dist/agent/tools/message-tools.d.ts +0 -42
- package/dist/agent/tools/message-tools.js +0 -106
- package/dist/agent/tools/message-tools.js.map +0 -1
- package/dist/cli/commands/app.d.ts +0 -7
- package/dist/cli/commands/app.js +0 -268
- package/dist/cli/commands/app.js.map +0 -1
- package/dist/cli/commands/clear.d.ts +0 -5
- package/dist/cli/commands/clear.js +0 -36
- package/dist/cli/commands/clear.js.map +0 -1
- package/dist/cli/commands/collection.d.ts +0 -10
- package/dist/cli/commands/collection.js.map +0 -1
- package/dist/cli/commands/join.d.ts +0 -9
- package/dist/cli/commands/join.js +0 -255
- package/dist/cli/commands/join.js.map +0 -1
- package/dist/cli/commands/start.d.ts +0 -7
- package/dist/cli/commands/start.js +0 -584
- package/dist/cli/commands/start.js.map +0 -1
- package/dist/cli/commands/update.d.ts +0 -8
- package/dist/cli/commands/update.js +0 -155
- package/dist/cli/commands/update.js.map +0 -1
- package/dist/cli/commands/user.d.ts +0 -7
- package/dist/cli/commands/user.js +0 -175
- package/dist/cli/commands/user.js.map +0 -1
- package/dist/cli/scaffolding.d.ts +0 -12
- package/dist/cli/scaffolding.js +0 -397
- package/dist/cli/scaffolding.js.map +0 -1
- package/dist/integrations/registry.d.ts +0 -8
- package/dist/integrations/registry.js +0 -7
- package/dist/integrations/registry.js.map +0 -1
- package/dist/integrations/types.d.ts +0 -43
- package/dist/integrations/types.js +0 -5
- package/dist/integrations/types.js.map +0 -1
- package/dist/lib/lib/utils.d.ts +0 -2
- package/dist/lib/lib/utils.js +0 -6
- package/dist/lib/lib/utils.js.map +0 -1
- package/dist/server/chrome/chrome.css +0 -691
- package/dist/server/chrome/chrome.js +0 -374
- package/dist/server/commit-scheduler.d.ts +0 -39
- package/dist/server/commit-scheduler.js +0 -113
- package/dist/server/commit-scheduler.js.map +0 -1
- package/dist/server/plugin-loader.d.ts +0 -53
- package/dist/server/plugin-loader.js +0 -155
- package/dist/server/plugin-loader.js.map +0 -1
- package/dist/server/routes/meeting-ingest-api.d.ts +0 -14
- package/dist/server/routes/meeting-ingest-api.js.map +0 -1
- package/dist/server/routes/messages-api.d.ts +0 -15
- package/dist/server/routes/messages-api.js +0 -385
- package/dist/server/routes/messages-api.js.map +0 -1
- package/dist/services/batch-processor.d.ts +0 -49
- package/dist/services/batch-processor.js +0 -166
- package/dist/services/batch-processor.js.map +0 -1
- package/dist/services/briefing.d.ts +0 -26
- package/dist/services/briefing.js +0 -230
- package/dist/services/briefing.js.map +0 -1
- package/dist/services/heartbeat.d.ts +0 -28
- package/dist/services/heartbeat.js +0 -183
- package/dist/services/heartbeat.js.map +0 -1
- package/dist/services/image-import-service.d.ts +0 -24
- package/dist/services/image-import-service.js +0 -108
- package/dist/services/image-import-service.js.map +0 -1
- package/dist/services/insights.d.ts +0 -38
- package/dist/services/insights.js +0 -269
- package/dist/services/insights.js.map +0 -1
- package/dist/services/meeting-processor.js.map +0 -1
- package/dist/services/message-service.d.ts +0 -68
- package/dist/services/message-service.js +0 -220
- package/dist/services/message-service.js.map +0 -1
- package/dist/utils/workspace-config.d.ts +0 -8
- package/dist/utils/workspace-config.js +0 -22
- package/dist/utils/workspace-config.js.map +0 -1
- package/dist/web/_app/immutable/assets/0.uPSqtsC5.css +0 -1
- package/dist/web/_app/immutable/assets/11.2jzI3cZY.css +0 -1
- package/dist/web/_app/immutable/assets/12.CT4xL4K3.css +0 -1
- package/dist/web/_app/immutable/assets/13.BUrHkYry.css +0 -1
- package/dist/web/_app/immutable/assets/2.DpQWr6q6.css +0 -1
- package/dist/web/_app/immutable/assets/3.BxdqT7zi.css +0 -1
- package/dist/web/_app/immutable/assets/4.DdNqjd0T.css +0 -1
- package/dist/web/_app/immutable/assets/5.DFeGxLYf.css +0 -1
- package/dist/web/_app/immutable/assets/6.DXZr_rI_.css +0 -1
- package/dist/web/_app/immutable/assets/GraphView.D9ePpZez.css +0 -1
- package/dist/web/_app/immutable/assets/Toaster.rN8r_Hzv.css +0 -1
- package/dist/web/_app/immutable/assets/ViewToolbar.BWE03S64.css +0 -1
- package/dist/web/_app/immutable/assets/WorkspaceView.CgGDi_Zb.css +0 -1
- package/dist/web/_app/immutable/assets/wikilinks.CzMBkUMB.css +0 -1
- package/dist/web/_app/immutable/chunks/-jG4Obyh.js +0 -4
- package/dist/web/_app/immutable/chunks/2nrc8f_O.js +0 -1
- package/dist/web/_app/immutable/chunks/7S2LGqoP.js +0 -2
- package/dist/web/_app/immutable/chunks/8Q3ZJIYu.js +0 -1
- package/dist/web/_app/immutable/chunks/B8ydT5xX.js +0 -2
- package/dist/web/_app/immutable/chunks/BB_5th5W.js +0 -3383
- package/dist/web/_app/immutable/chunks/BEn6N5c2.js +0 -1
- package/dist/web/_app/immutable/chunks/BFNNbCAM.js +0 -1
- package/dist/web/_app/immutable/chunks/BFZZEUd5.js +0 -1
- package/dist/web/_app/immutable/chunks/BGk8kfOE.js +0 -2
- package/dist/web/_app/immutable/chunks/BIBCr278.js +0 -1
- package/dist/web/_app/immutable/chunks/BPCkkwqe.js +0 -1
- package/dist/web/_app/immutable/chunks/BhlvzGQx.js +0 -1
- package/dist/web/_app/immutable/chunks/BlMknQeF.js +0 -1
- package/dist/web/_app/immutable/chunks/BpjqrlCg.js +0 -1
- package/dist/web/_app/immutable/chunks/BrebtJNG.js +0 -18
- package/dist/web/_app/immutable/chunks/ByBZ7JIT.js +0 -1
- package/dist/web/_app/immutable/chunks/Bz0ZjVQE.js +0 -2
- package/dist/web/_app/immutable/chunks/BzJTZOK2.js +0 -1
- package/dist/web/_app/immutable/chunks/C0LVZhvn.js +0 -1
- package/dist/web/_app/immutable/chunks/C3uBrOS5.js +0 -1
- package/dist/web/_app/immutable/chunks/C8R1sDpW.js +0 -1
- package/dist/web/_app/immutable/chunks/CDRHrs0g.js +0 -1
- package/dist/web/_app/immutable/chunks/CN5_py1I.js +0 -1
- package/dist/web/_app/immutable/chunks/CPne482a.js +0 -1
- package/dist/web/_app/immutable/chunks/CSVbGg_b.js +0 -5
- package/dist/web/_app/immutable/chunks/CWyU-seV.js +0 -1
- package/dist/web/_app/immutable/chunks/CX_61fY8.js +0 -5
- package/dist/web/_app/immutable/chunks/CYrVHOHA.js +0 -1
- package/dist/web/_app/immutable/chunks/ChsiIm-E.js +0 -1
- package/dist/web/_app/immutable/chunks/CojKppbh.js +0 -1
- package/dist/web/_app/immutable/chunks/CqkleIqs.js +0 -1
- package/dist/web/_app/immutable/chunks/CtYLtD7K.js +0 -1
- package/dist/web/_app/immutable/chunks/D-HDy5qS.js +0 -1
- package/dist/web/_app/immutable/chunks/D5aIYSkd.js +0 -1
- package/dist/web/_app/immutable/chunks/DBBXARVf.js +0 -1
- package/dist/web/_app/immutable/chunks/DFYOFE3o.js +0 -1
- package/dist/web/_app/immutable/chunks/DKaafS50.js +0 -1
- package/dist/web/_app/immutable/chunks/DUMOo1Pn.js +0 -1
- package/dist/web/_app/immutable/chunks/DWX9f6AF.js +0 -1
- package/dist/web/_app/immutable/chunks/DY8-EONP.js +0 -1
- package/dist/web/_app/immutable/chunks/DkoHPElM.js +0 -1
- package/dist/web/_app/immutable/chunks/DnL9f0lc.js +0 -7
- package/dist/web/_app/immutable/chunks/DsnmJJEf.js +0 -1
- package/dist/web/_app/immutable/chunks/DujahU3W.js +0 -1
- package/dist/web/_app/immutable/chunks/FBn-ES5k.js +0 -1
- package/dist/web/_app/immutable/chunks/HQpwSOb3.js +0 -1
- package/dist/web/_app/immutable/chunks/KyjzlWRN.js +0 -2
- package/dist/web/_app/immutable/chunks/PPVm8Dsz.js +0 -1
- package/dist/web/_app/immutable/chunks/UwYfmrPx.js +0 -2
- package/dist/web/_app/immutable/chunks/W25ZVI8L.js +0 -2
- package/dist/web/_app/immutable/chunks/WSUKABI_.js +0 -1
- package/dist/web/_app/immutable/chunks/XjsuEnNE.js +0 -5
- package/dist/web/_app/immutable/chunks/ZD6ARAtb.js +0 -1
- package/dist/web/_app/immutable/chunks/pBTf4stg.js +0 -2
- package/dist/web/_app/immutable/chunks/rGIarcnp.js +0 -1
- package/dist/web/_app/immutable/chunks/vxEtkL8z.js +0 -1
- package/dist/web/_app/immutable/chunks/wDIGUaoU.js +0 -3
- package/dist/web/_app/immutable/entry/app.ZHiTM8WS.js +0 -2
- package/dist/web/_app/immutable/entry/start.C9-FfAVc.js +0 -1
- package/dist/web/_app/immutable/nodes/0.D92Orz8k.js +0 -2
- package/dist/web/_app/immutable/nodes/1.BMdyglM_.js +0 -1
- package/dist/web/_app/immutable/nodes/10.BilVDHSL.js +0 -1
- package/dist/web/_app/immutable/nodes/11.CS9E0Hic.js +0 -1
- package/dist/web/_app/immutable/nodes/12.D3BOxYzG.js +0 -1
- package/dist/web/_app/immutable/nodes/13.CClMKYtN.js +0 -1
- package/dist/web/_app/immutable/nodes/2.C8KKElPL.js +0 -267
- package/dist/web/_app/immutable/nodes/3.C5n5uiWU.js +0 -5
- package/dist/web/_app/immutable/nodes/4.DcGVvgiL.js +0 -11
- package/dist/web/_app/immutable/nodes/5.DxIjnFQO.js +0 -4
- package/dist/web/_app/immutable/nodes/6.lkh6kKLj.js +0 -1
- package/dist/web/_app/immutable/nodes/7.Bg7SbfhA.js +0 -1
- package/dist/web/_app/immutable/nodes/8.DcXWCNBE.js +0 -1
- package/dist/web/_app/immutable/nodes/9.Cefi8Jfm.js +0 -1
|
@@ -7,11 +7,20 @@
|
|
|
7
7
|
* - Manage wikilinks
|
|
8
8
|
* - Upload assets
|
|
9
9
|
*/
|
|
10
|
-
import { Type as T } from '
|
|
10
|
+
import { Type as T } from 'typebox';
|
|
11
11
|
import { ENTITY_SCHEMAS } from '../../core/validation.js';
|
|
12
|
+
import { describeZodObject } from '../../core/schema-registry.js';
|
|
12
13
|
import { lintWorkspace } from '../../services/lint-service.js';
|
|
13
14
|
import { findOrphans } from '../../services/orphan-service.js';
|
|
14
|
-
import
|
|
15
|
+
import * as access from '../../services/access-control.js';
|
|
16
|
+
import { isCuratedEntityType } from '../../core/entity-types-catalog.js';
|
|
17
|
+
import { getAgentAllowedEntityTypes } from '../prompts/entity-types-section.js';
|
|
18
|
+
import { BridgeError, assertCanChangeUserHandle, validateUserHandleInPayload } from '../../services/user-person-bridge.js';
|
|
19
|
+
import { buildActor, prepareCreatePayload, prepareUpdatePayload, diagnosedUpdate, DroppedAssetError, assertEntityRefs, EntityRefError, UnknownUserRefError, } from '../../services/entity-mutations.js';
|
|
20
|
+
import { listRepoAssets } from '../../services/asset-listing.js';
|
|
21
|
+
import { assetEmbed } from '../../core/embeds.js';
|
|
22
|
+
import * as commentVirtualEntity from '../../services/comment-virtual-entity.js';
|
|
23
|
+
import { promoteScratchFileToAssets, PromoteScratchError } from '../../services/scratch-asset-promote.js';
|
|
15
24
|
/**
|
|
16
25
|
* Helper to wrap tool results in AgentToolResult format
|
|
17
26
|
*/
|
|
@@ -31,139 +40,251 @@ function cleanData(data) {
|
|
|
31
40
|
return rest;
|
|
32
41
|
}
|
|
33
42
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
43
|
+
* Undo JSON-string escapes that sometimes leak into content when the LLM
|
|
44
|
+
* serializes markdown as a quoted string instead of raw text. Handles `\n`,
|
|
45
|
+
* `\t`, `\r`, `\"`, `\\` in a single pass so escape sequences aren't
|
|
46
|
+
* re-processed. Also strips stray surrounding quotes from a fully-quoted
|
|
47
|
+
* payload.
|
|
37
48
|
*/
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (!user)
|
|
52
|
-
return null; // API key / open mode → allow
|
|
53
|
-
if (isAdminOrOwner(user.role))
|
|
54
|
-
return null;
|
|
55
|
-
return 'This operation requires admin access.';
|
|
49
|
+
function sanitizeLlmContent(content) {
|
|
50
|
+
if (!content)
|
|
51
|
+
return '';
|
|
52
|
+
const unescaped = content.replace(/\\([ntr"\\])/g, (_, c) => {
|
|
53
|
+
if (c === 'n')
|
|
54
|
+
return '\n';
|
|
55
|
+
if (c === 't')
|
|
56
|
+
return '\t';
|
|
57
|
+
if (c === 'r')
|
|
58
|
+
return '\r';
|
|
59
|
+
return c;
|
|
60
|
+
});
|
|
61
|
+
return unescaped.replace(/^"+|"+$/g, '');
|
|
56
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Per-request user context for folder access filtering.
|
|
65
|
+
* When getCurrentUser returns a user, tools filter results to only
|
|
66
|
+
* folders the user is a member of (via folder_access).
|
|
67
|
+
*/
|
|
57
68
|
function getAccessibleRepoNames(workspaceManager, getCurrentUser, authService) {
|
|
58
69
|
const user = getCurrentUser();
|
|
59
70
|
if (!user)
|
|
60
71
|
return null; // API key / open mode → no filtering
|
|
61
72
|
if (!authService)
|
|
62
73
|
return null;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const hidden = allRepos.filter(r => !canSeePrivateRepo(r, user)).map(r => r.name);
|
|
67
|
-
if (hidden.length === 0)
|
|
68
|
-
return null; // No filtering needed
|
|
69
|
-
const visible = new Set(allRepos.filter(r => canSeePrivateRepo(r, user)).map(r => r.name));
|
|
70
|
-
return visible;
|
|
71
|
-
}
|
|
72
|
-
const allowed = authService.getUserCollections(user.id);
|
|
73
|
-
const visible = allRepos.filter(r => canSeePrivateRepo(r, user) && (r.private && r.owner_id === user.id || allowed.includes(r.name)));
|
|
74
|
-
return new Set(visible.map(r => r.name));
|
|
74
|
+
// Delegate to the shared helper so personal-folder restrictions and
|
|
75
|
+
// owner-on-shared-folder semantics apply consistently with the HTTP layer.
|
|
76
|
+
return new Set(access.getAccessibleRepos(user, workspaceManager, authService).map(r => r.name));
|
|
75
77
|
}
|
|
76
78
|
function canAccessRepo(repoName, workspaceManager, getCurrentUser, authService) {
|
|
77
79
|
const user = getCurrentUser();
|
|
78
|
-
const repoConfig = workspaceManager.getRepoConfig(repoName);
|
|
79
|
-
if (!canSeePrivateRepo(repoConfig, user))
|
|
80
|
-
return false;
|
|
81
80
|
if (!user)
|
|
82
81
|
return true;
|
|
83
|
-
if (repoConfig?.private && repoConfig?.owner_id === user.id)
|
|
84
|
-
return true;
|
|
85
|
-
if (isAdminOrOwner(user.role))
|
|
86
|
-
return true;
|
|
87
82
|
if (!authService)
|
|
88
83
|
return true;
|
|
89
|
-
return
|
|
84
|
+
return access.hasRepoAccess(user, repoName, workspaceManager, authService);
|
|
90
85
|
}
|
|
91
|
-
export function createGraphTools(workspaceManager, gitUser, workspacePath, onHighlight, onMutation, getCurrentUser, authService
|
|
86
|
+
export function createGraphTools(workspaceManager, gitUser, workspacePath, onHighlight, onMutation, getCurrentUser, authService,
|
|
87
|
+
/**
|
|
88
|
+
* Called at the start (`enter`) and end (`leave`) of any mutation tool
|
|
89
|
+
* call. Surfaces the active-agent chip in the sidebar via the WS hub. The
|
|
90
|
+
* orchestrator wires this through to wsHub.handleAgentActivity.
|
|
91
|
+
*/
|
|
92
|
+
onAgentActivity,
|
|
93
|
+
/**
|
|
94
|
+
* Optional CommentService. When supplied, the generic read tools
|
|
95
|
+
* (`get_entity`, `list_entities`, `get_related`) dispatch the virtual
|
|
96
|
+
* `comment` entity type through the shared adapter — exactly as the MCP
|
|
97
|
+
* surface does. A graceful no-op when absent (stdio / tests), so comments
|
|
98
|
+
* simply don't appear rather than erroring.
|
|
99
|
+
*/
|
|
100
|
+
commentService,
|
|
101
|
+
/**
|
|
102
|
+
* Optional ChatSessionService. When supplied (web chat), enables
|
|
103
|
+
* `save_file_to_assets` to promote a conversation-scratch image into the
|
|
104
|
+
* curated asset library. A graceful no-op when absent (stdio / tests).
|
|
105
|
+
*/
|
|
106
|
+
chatSessionService,
|
|
107
|
+
/**
|
|
108
|
+
* Resolves the active turn's RAW client session id. Lets `save_file_to_assets`
|
|
109
|
+
* scope "this image" to the current conversation's scratch images rather than
|
|
110
|
+
* the user's other chats. Returns null outside a web chat turn.
|
|
111
|
+
*/
|
|
112
|
+
getActiveSessionId) {
|
|
92
113
|
const getUser = getCurrentUser ?? (() => null);
|
|
93
114
|
const authSvc = authService ?? null;
|
|
94
|
-
|
|
115
|
+
const commentSvc = commentService ?? null;
|
|
116
|
+
const chatSessionSvc = chatSessionService ?? null;
|
|
117
|
+
const getSessionId = getActiveSessionId ?? (() => null);
|
|
118
|
+
/**
|
|
119
|
+
* Resolve the identity to stamp on writes. If the tool call is running on
|
|
120
|
+
* behalf of a real user (chat / quick-capture from the web), credit them
|
|
121
|
+
* by their handle via the shared `buildActor` so MCP and agent surfaces
|
|
122
|
+
* produce byte-identical git authors for the same user. Fall back to the
|
|
123
|
+
* orchestrator-level gitUser for unauthenticated contexts (CLI, tests,
|
|
124
|
+
* server-only automations).
|
|
125
|
+
*/
|
|
126
|
+
function effectiveGitUser() {
|
|
127
|
+
const user = getUser();
|
|
128
|
+
if (user?.handle)
|
|
129
|
+
return buildActor(user);
|
|
130
|
+
return gitUser;
|
|
131
|
+
}
|
|
132
|
+
/** Resolve "private" or "entries" to the current user's actual private folder name. */
|
|
95
133
|
function resolvePrivateRepo(repoName) {
|
|
96
134
|
if (repoName !== 'private' && repoName !== 'entries')
|
|
97
135
|
return repoName;
|
|
98
136
|
const user = getUser();
|
|
99
137
|
if (!user)
|
|
100
138
|
return repoName;
|
|
101
|
-
const
|
|
102
|
-
return
|
|
139
|
+
const personalRepo = workspaceManager.getUserPersonalRepo(user.id);
|
|
140
|
+
return personalRepo?.name ?? repoName;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Eager skills are inlined into every system prompt for everyone with folder
|
|
144
|
+
* access. Creating one in a shared folder requires admin/owner so a rogue
|
|
145
|
+
* member can't bloat everyone's context. Eager skills in the user's own
|
|
146
|
+
* private folder are fine — they only bloat that user's own prompt.
|
|
147
|
+
* Returns an error string or null if the operation is allowed.
|
|
148
|
+
*/
|
|
149
|
+
function checkEagerSkillPermission(entityType, data, repoName) {
|
|
150
|
+
if (entityType !== 'skill')
|
|
151
|
+
return null;
|
|
152
|
+
if (!data || data.loading !== 'eager')
|
|
153
|
+
return null;
|
|
154
|
+
const user = getUser();
|
|
155
|
+
if (!user)
|
|
156
|
+
return null; // non-authenticated contexts (CLI, tests) bypass
|
|
157
|
+
const personalRepo = workspaceManager.getUserPersonalRepo(user.id);
|
|
158
|
+
if (personalRepo && personalRepo.name === repoName)
|
|
159
|
+
return null;
|
|
160
|
+
if (authSvc?.isFolderAdmin(user.id, repoName))
|
|
161
|
+
return null;
|
|
162
|
+
if (user.role === 'owner')
|
|
163
|
+
return null;
|
|
164
|
+
return `Creating an eager skill in a shared folder requires folder admin or workspace owner. Save this skill to your private folder (repo: "private") or set loading to "on-demand".`;
|
|
165
|
+
}
|
|
166
|
+
// Compute the set of entity types the agent is allowed to create in this
|
|
167
|
+
// workspace: the full canonical catalog plus any custom types defined via
|
|
168
|
+
// schema_extensions. The launch-time allowlist was removed — the catalog
|
|
169
|
+
// now grows with the user via the New Entry picker's progressive disclosure.
|
|
170
|
+
const builtinTypeKeys = new Set(Object.keys(ENTITY_SCHEMAS));
|
|
171
|
+
const customWorkspaceTypes = workspaceManager.getSchemaRegistry().getAllTypes()
|
|
172
|
+
.filter(t => !builtinTypeKeys.has(t));
|
|
173
|
+
const agentAllowedTypes = getAgentAllowedEntityTypes(customWorkspaceTypes);
|
|
174
|
+
const agentAllowedTypeSet = new Set(agentAllowedTypes);
|
|
175
|
+
function rejectDisallowedCreateType(entityType) {
|
|
176
|
+
if (agentAllowedTypeSet.has(entityType))
|
|
177
|
+
return null;
|
|
178
|
+
const hint = isCuratedEntityType(entityType)
|
|
179
|
+
? '' // shouldn't happen for any other curated type — they're all allowed
|
|
180
|
+
: ` Allowed types: ${agentAllowedTypes.join(', ')}.`;
|
|
181
|
+
return wrapToolResult({
|
|
182
|
+
success: false,
|
|
183
|
+
error: `Entry type "${entityType}" is not a known canonical or workspace custom type.${hint}`,
|
|
184
|
+
});
|
|
103
185
|
}
|
|
104
186
|
return [
|
|
105
187
|
// Create entity
|
|
106
188
|
{
|
|
107
189
|
name: 'create_entity',
|
|
108
190
|
label: 'Create Entry',
|
|
109
|
-
description: 'Create a new entry in a
|
|
191
|
+
description: 'Create a new entry in a folder. Always validate required fields before calling. Folder is auto-resolved for app-defined entry types.',
|
|
110
192
|
parameters: T.Object({
|
|
111
|
-
repo: T.Optional(T.String({ description: '
|
|
112
|
-
entityType: T.
|
|
193
|
+
repo: T.Optional(T.String({ description: 'Folder name (auto-resolved for app entity types)' })),
|
|
194
|
+
entityType: T.Union(agentAllowedTypes.map(t => T.Literal(t)), { description: `Entity type. Must be one of: ${agentAllowedTypes.join(', ')}.` }),
|
|
113
195
|
entityId: T.String({ description: 'Unique entity ID in kebab-case' }),
|
|
114
196
|
data: T.Object({}, { description: 'Entity data (YAML frontmatter)', additionalProperties: true }),
|
|
115
|
-
content: T.Optional(T.String({ description: 'Markdown content (optional)' })),
|
|
197
|
+
content: T.Optional(T.String({ description: 'Markdown content (optional). Write one line per paragraph — newlines only for real paragraph/list breaks, not to wrap prose at a column width (entries render source line breaks literally).' })),
|
|
198
|
+
folder_path: T.Optional(T.String({ description: 'Repo-relative subfolder for the entry (e.g. "clients/acme"). Omit to land at repo root. Parent folders are auto-created.' })),
|
|
116
199
|
commitMessage: T.Optional(T.String({ description: 'Custom commit message (optional)' })),
|
|
117
200
|
}),
|
|
118
201
|
execute: async (toolCallId, params) => {
|
|
119
202
|
try {
|
|
203
|
+
// Defense in depth: LLMs occasionally pass strings that don't match the
|
|
204
|
+
// enum schema. Runtime check ensures the launch allowlist is enforced
|
|
205
|
+
// even if the schema layer is bypassed.
|
|
206
|
+
const typeRejection = rejectDisallowedCreateType(params.entityType);
|
|
207
|
+
if (typeRejection)
|
|
208
|
+
return typeRejection;
|
|
120
209
|
let repoName = params.repo;
|
|
121
210
|
if (!repoName) {
|
|
122
|
-
|
|
123
|
-
if (!repoName)
|
|
124
|
-
return wrapToolResult({ success: false, error: `repo is required — could not auto-resolve repo for type "${params.entityType}"` });
|
|
211
|
+
return wrapToolResult({ success: false, error: `repo is required — must specify which folder to use for type "${params.entityType}"` });
|
|
125
212
|
}
|
|
126
213
|
repoName = resolvePrivateRepo(repoName);
|
|
127
214
|
if (!canAccessRepo(repoName, workspaceManager, getUser, authSvc)) {
|
|
128
|
-
return wrapToolResult({ success: false, error: `
|
|
215
|
+
return wrapToolResult({ success: false, error: `Folder "${repoName}" not found` });
|
|
129
216
|
}
|
|
217
|
+
const eagerError = checkEagerSkillPermission(params.entityType, params.data, repoName);
|
|
218
|
+
if (eagerError)
|
|
219
|
+
return wrapToolResult({ success: false, error: eagerError });
|
|
130
220
|
const graph = workspaceManager.getGraph(repoName);
|
|
131
221
|
// Workspace-level dedup: warn if an entity with this ID already exists elsewhere
|
|
132
222
|
const existing = workspaceManager.findEntityById(params.entityId);
|
|
133
223
|
if (existing && existing.repoName !== repoName) {
|
|
134
|
-
return wrapToolResult({ success: false, error: `An entity with ID "${params.entityId}" already exists in the "${existing.repoName}"
|
|
224
|
+
return wrapToolResult({ success: false, error: `An entity with ID "${params.entityId}" already exists in the "${existing.repoName}" folder (type: ${existing.data?.entity_type}). Use a unique ID or link to the existing entity with [[${params.entityId}]].` });
|
|
135
225
|
}
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
226
|
+
const actor = effectiveGitUser();
|
|
227
|
+
const sanitizedContent = sanitizeLlmContent(params.content);
|
|
228
|
+
// Workspace-wide uniqueness check for `user_handle` on person
|
|
229
|
+
// entities (one user ↔ one person, workspace-wide), plus owner-
|
|
230
|
+
// only authz so a non-owner agent session can't stamp a
|
|
231
|
+
// pre-linked person via generic create_entity.
|
|
232
|
+
if (params.entityType === 'person') {
|
|
233
|
+
try {
|
|
234
|
+
const incoming = params.data ?? {};
|
|
235
|
+
validateUserHandleInPayload(workspaceManager, incoming);
|
|
236
|
+
if (Object.prototype.hasOwnProperty.call(incoming, 'user_handle')) {
|
|
237
|
+
assertCanChangeUserHandle(getUser(), '', incoming.user_handle);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
if (err instanceof BridgeError) {
|
|
242
|
+
return wrapToolResult({ success: false, error: err.message, code: err.code, ...err.details });
|
|
243
|
+
}
|
|
244
|
+
throw err;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Scope ref fields (today: task.assignee → person). Reject a wikilink
|
|
248
|
+
// that resolves to a non-person; forward references are allowed.
|
|
249
|
+
try {
|
|
250
|
+
assertEntityRefs(workspaceManager, params.entityType, params.data ?? {}, authSvc ? { userExists: (id) => !!authSvc.findUserById(Number(id)) } : undefined);
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
if (err instanceof EntityRefError || err instanceof UnknownUserRefError) {
|
|
254
|
+
return wrapToolResult({ success: false, error: err.message, code: err.code, field: err.field });
|
|
255
|
+
}
|
|
256
|
+
throw err;
|
|
257
|
+
}
|
|
258
|
+
const options = prepareCreatePayload({
|
|
149
259
|
entityType: params.entityType,
|
|
150
260
|
entityId: params.entityId,
|
|
151
|
-
data:
|
|
261
|
+
data: params.data ?? {},
|
|
152
262
|
content: sanitizedContent,
|
|
263
|
+
folderPath: params.folder_path,
|
|
153
264
|
commitMessage: params.commitMessage,
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
265
|
+
}, actor);
|
|
266
|
+
// Surface a live "<user>'s agent is acting" chip in the sidebar for
|
|
267
|
+
// the duration of the mutation. `leave` is always emitted via the
|
|
268
|
+
// inner finally so a thrown error doesn't leave the chip stuck.
|
|
269
|
+
onAgentActivity?.('enter', repoName);
|
|
270
|
+
try {
|
|
271
|
+
const entity = await graph.create(options);
|
|
272
|
+
onMutation?.({ action: 'created', repo: repoName, entityType: params.entityType, entityId: params.entityId });
|
|
273
|
+
return wrapToolResult({
|
|
274
|
+
success: true,
|
|
275
|
+
entity: {
|
|
276
|
+
entityId: entity.id,
|
|
277
|
+
entityType: entity.entityType,
|
|
278
|
+
repo: repoName,
|
|
279
|
+
path: entity.path,
|
|
280
|
+
data: cleanData(entity.data),
|
|
281
|
+
},
|
|
282
|
+
message: `Created ${params.entityType}: ${params.entityId} in ${repoName}. Auto-committed — ready to push.`,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
onAgentActivity?.('leave', repoName);
|
|
287
|
+
}
|
|
167
288
|
}
|
|
168
289
|
catch (error) {
|
|
169
290
|
return wrapToolResult({
|
|
@@ -179,7 +300,7 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
179
300
|
label: 'Get Entry',
|
|
180
301
|
description: 'Retrieve full entry details. Use repo, entityType, entityId from search/list results. entityType is optional — if omitted it is read from the file.',
|
|
181
302
|
parameters: T.Object({
|
|
182
|
-
repo: T.String({ description: '
|
|
303
|
+
repo: T.String({ description: 'Folder name' }),
|
|
183
304
|
entityType: T.Optional(T.String({ description: 'Entity type (optional — resolved from frontmatter if omitted)' })),
|
|
184
305
|
entityId: T.String({ description: 'Entity ID' }),
|
|
185
306
|
}),
|
|
@@ -187,12 +308,50 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
187
308
|
try {
|
|
188
309
|
const repo = resolvePrivateRepo(params.repo);
|
|
189
310
|
if (!canAccessRepo(repo, workspaceManager, getUser, authSvc)) {
|
|
190
|
-
return wrapToolResult({ success: false, error: `
|
|
311
|
+
return wrapToolResult({ success: false, error: `Folder "${repo}" not found` });
|
|
312
|
+
}
|
|
313
|
+
// Comments live in SQLite, not the filesystem graph — dispatch
|
|
314
|
+
// through the shared virtual-entity adapter (mirrors the MCP surface).
|
|
315
|
+
if (params.entityType === 'comment' && commentSvc) {
|
|
316
|
+
const envelope = commentVirtualEntity.getComment(commentSvc, params.entityId);
|
|
317
|
+
if (!envelope || envelope.data.repo !== repo) {
|
|
318
|
+
return wrapToolResult({ success: false, error: `Comment "${params.entityId}" not found` });
|
|
319
|
+
}
|
|
320
|
+
return wrapToolResult({
|
|
321
|
+
success: true,
|
|
322
|
+
entity: {
|
|
323
|
+
entityId: envelope.id,
|
|
324
|
+
entityType: envelope.entityType,
|
|
325
|
+
repo,
|
|
326
|
+
data: cleanData(envelope.data),
|
|
327
|
+
content: envelope.document.content,
|
|
328
|
+
wikilinks: envelope.document.wikilinks,
|
|
329
|
+
},
|
|
330
|
+
});
|
|
191
331
|
}
|
|
192
332
|
const graph = workspaceManager.getGraph(repo);
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
333
|
+
// If the agent passes the wrong entityType (e.g. based on stale
|
|
334
|
+
// search results), graph.get computes the wrong filesystem path
|
|
335
|
+
// and throws. Fall back to getById, which scans all type subdirs
|
|
336
|
+
// under the folder and resolves the actual location.
|
|
337
|
+
let entity;
|
|
338
|
+
if (params.entityType) {
|
|
339
|
+
try {
|
|
340
|
+
entity = graph.get(params.entityType, params.entityId);
|
|
341
|
+
}
|
|
342
|
+
catch {
|
|
343
|
+
entity = graph.getById(params.entityId);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
entity = graph.getById(params.entityId);
|
|
348
|
+
}
|
|
349
|
+
// Prefer the live editor body when a seeded Y.Doc is open. graph.get
|
|
350
|
+
// reads disk, which lags the editor by the autosave debounce — a
|
|
351
|
+
// user's freshly-placed image embed lives in the Y.Doc before it's
|
|
352
|
+
// committed. Returning disk content is what let the agent edit then
|
|
353
|
+
// resend a body missing those embeds, silently dropping images.
|
|
354
|
+
const liveBody = graph.getLiveBody(entity.entityType, entity.id);
|
|
196
355
|
return wrapToolResult({
|
|
197
356
|
success: true,
|
|
198
357
|
entity: {
|
|
@@ -201,7 +360,7 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
201
360
|
repo,
|
|
202
361
|
path: entity.path,
|
|
203
362
|
data: cleanData(entity.data),
|
|
204
|
-
content: entity.document.content,
|
|
363
|
+
content: liveBody ?? entity.document.content,
|
|
205
364
|
wikilinks: entity.document.wikilinks,
|
|
206
365
|
},
|
|
207
366
|
});
|
|
@@ -218,73 +377,435 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
218
377
|
{
|
|
219
378
|
name: 'update_entity',
|
|
220
379
|
label: 'Update Entry',
|
|
221
|
-
description: 'Update an existing entry.
|
|
380
|
+
description: 'Update an existing entry. Folder is auto-resolved for app-defined types.',
|
|
222
381
|
parameters: T.Object({
|
|
223
|
-
repo: T.Optional(T.String({ description: '
|
|
382
|
+
repo: T.Optional(T.String({ description: 'Folder name (auto-resolved for app entity types)' })),
|
|
224
383
|
entityType: T.String({ description: 'Entity type' }),
|
|
225
384
|
entityId: T.String({ description: 'Entity ID' }),
|
|
226
385
|
data: T.Optional(T.Object({}, { description: 'Updated data fields', additionalProperties: true })),
|
|
227
|
-
content: T.Optional(T.String({ description: 'Updated markdown
|
|
386
|
+
content: T.Optional(T.String({ description: 'Updated markdown body (replaces the entire body). Write one line per paragraph — newlines only for real paragraph/list breaks, not to wrap prose at a column width (entries render source line breaks literally).' })),
|
|
228
387
|
commitMessage: T.Optional(T.String({ description: 'Custom commit message' })),
|
|
388
|
+
allowImageRemoval: T.Optional(T.Boolean({ description: 'Set true only to deliberately delete image/asset embeds the document currently has; otherwise such a whole-body write is rejected (ASSET_REMOVAL_BLOCKED). Prefer get_entity then patch_entity for small edits.' })),
|
|
229
389
|
}),
|
|
230
390
|
execute: async (toolCallId, params) => {
|
|
231
391
|
try {
|
|
232
392
|
let repoName = params.repo;
|
|
233
393
|
if (!repoName) {
|
|
234
|
-
|
|
235
|
-
if (!repoName)
|
|
236
|
-
return wrapToolResult({ success: false, error: `repo is required — could not auto-resolve repo for type "${params.entityType}"` });
|
|
394
|
+
return wrapToolResult({ success: false, error: `repo is required — must specify which folder to use for type "${params.entityType}"` });
|
|
237
395
|
}
|
|
238
396
|
repoName = resolvePrivateRepo(repoName);
|
|
239
397
|
if (!canAccessRepo(repoName, workspaceManager, getUser, authSvc)) {
|
|
240
|
-
return wrapToolResult({ success: false, error: `
|
|
398
|
+
return wrapToolResult({ success: false, error: `Folder "${repoName}" not found` });
|
|
241
399
|
}
|
|
400
|
+
const eagerError = checkEagerSkillPermission(params.entityType, params.data, repoName);
|
|
401
|
+
if (eagerError)
|
|
402
|
+
return wrapToolResult({ success: false, error: eagerError });
|
|
242
403
|
const graph = workspaceManager.getGraph(repoName);
|
|
243
|
-
|
|
244
|
-
const dataWithMeta = params.data
|
|
245
|
-
? {
|
|
246
|
-
...params.data,
|
|
247
|
-
updated_at: new Date().toISOString(),
|
|
248
|
-
updated_by: gitUser.id,
|
|
249
|
-
}
|
|
250
|
-
: undefined;
|
|
251
|
-
// Sanitize LLM JSON output: double-escaped newlines and stray quotes
|
|
404
|
+
const actor = effectiveGitUser();
|
|
252
405
|
const sanitizedContent = params.content !== undefined
|
|
253
|
-
? params.content
|
|
254
|
-
|
|
406
|
+
? sanitizeLlmContent(params.content)
|
|
407
|
+
: params.content;
|
|
408
|
+
// Workspace-wide uniqueness check + owner-only authz for
|
|
409
|
+
// `user_handle` on person entities. Generic update_entity must
|
|
410
|
+
// not be a back door around the ownerOnly() gate on the dedicated
|
|
411
|
+
// link/unlink tools — otherwise a member could ask the agent to
|
|
412
|
+
// "set this person's user_handle to mine" and bypass the gate.
|
|
413
|
+
if (params.entityType === 'person' && params.data) {
|
|
414
|
+
try {
|
|
415
|
+
validateUserHandleInPayload(workspaceManager, params.data, { repo: repoName, entityId: params.entityId });
|
|
416
|
+
if (Object.prototype.hasOwnProperty.call(params.data, 'user_handle')) {
|
|
417
|
+
let currentHandle = '';
|
|
418
|
+
try {
|
|
419
|
+
const current = graph.get('person', params.entityId);
|
|
420
|
+
currentHandle = (current?.data?.user_handle ?? '');
|
|
421
|
+
}
|
|
422
|
+
catch { /* new entity / missing — treat as empty */ }
|
|
423
|
+
assertCanChangeUserHandle(getUser(), currentHandle, params.data.user_handle);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
catch (err) {
|
|
427
|
+
if (err instanceof BridgeError) {
|
|
428
|
+
return wrapToolResult({ success: false, error: err.message, code: err.code, ...err.details });
|
|
429
|
+
}
|
|
430
|
+
throw err;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// Scope ref fields (today: task.assignee → person). Reject a wikilink
|
|
434
|
+
// that resolves to a non-person; forward references are allowed.
|
|
435
|
+
try {
|
|
436
|
+
assertEntityRefs(workspaceManager, params.entityType, params.data, authSvc ? { userExists: (id) => !!authSvc.findUserById(Number(id)) } : undefined);
|
|
437
|
+
}
|
|
438
|
+
catch (err) {
|
|
439
|
+
if (err instanceof EntityRefError || err instanceof UnknownUserRefError) {
|
|
440
|
+
return wrapToolResult({ success: false, error: err.message, code: err.code, field: err.field });
|
|
441
|
+
}
|
|
442
|
+
throw err;
|
|
443
|
+
}
|
|
444
|
+
const options = prepareUpdatePayload({
|
|
255
445
|
entityType: params.entityType,
|
|
256
446
|
entityId: params.entityId,
|
|
257
|
-
data:
|
|
447
|
+
data: params.data,
|
|
258
448
|
content: sanitizedContent,
|
|
259
449
|
commitMessage: params.commitMessage,
|
|
450
|
+
}, actor);
|
|
451
|
+
onAgentActivity?.('enter', repoName);
|
|
452
|
+
try {
|
|
453
|
+
// Shared diagnosed update (c6ef9297 contract): throws
|
|
454
|
+
// SilentDropError when the caller intended a change that left the
|
|
455
|
+
// file byte-identical, surfaced via the outer catch so the agent
|
|
456
|
+
// isn't misled by a success envelope.
|
|
457
|
+
const { entity, frontmatter_changed, content_changed, noop, broadcast } = await diagnosedUpdate(graph, options, { data: params.data, content: sanitizedContent }, { allowAssetRemoval: params.allowImageRemoval });
|
|
458
|
+
onMutation?.({ action: 'updated', repo: repoName, entityType: params.entityType, entityId: params.entityId });
|
|
459
|
+
return wrapToolResult({
|
|
460
|
+
success: true,
|
|
461
|
+
entity: {
|
|
462
|
+
entityId: entity.id,
|
|
463
|
+
entityType: entity.entityType,
|
|
464
|
+
repo: repoName,
|
|
465
|
+
data: cleanData(entity.data),
|
|
466
|
+
},
|
|
467
|
+
frontmatter_changed,
|
|
468
|
+
content_changed,
|
|
469
|
+
noop,
|
|
470
|
+
broadcast,
|
|
471
|
+
message: noop
|
|
472
|
+
? `No change to ${params.entityType}: ${params.entityId} — on-disk state already matched.`
|
|
473
|
+
: `Updated ${params.entityType}: ${params.entityId}. Auto-committed — ready to push.`,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
finally {
|
|
477
|
+
onAgentActivity?.('leave', repoName);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
if (error instanceof DroppedAssetError) {
|
|
482
|
+
return wrapToolResult({
|
|
483
|
+
success: false,
|
|
484
|
+
code: 'ASSET_REMOVAL_BLOCKED',
|
|
485
|
+
droppedAssets: error.dropped,
|
|
486
|
+
error: error.message,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
return wrapToolResult({
|
|
490
|
+
success: false,
|
|
491
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
260
492
|
});
|
|
261
|
-
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
// Surgical body edit (find/replace) — parity with the MCP patch_entity.
|
|
497
|
+
// Lets the in-app agent change a small slice of a long entry without
|
|
498
|
+
// regenerating (and risking dropping image embeds from) the whole body.
|
|
499
|
+
{
|
|
500
|
+
name: 'patch_entity',
|
|
501
|
+
label: 'Patch Entry',
|
|
502
|
+
description: 'Surgically edit an entry body by replacing an exact substring — without rewriting the whole body. ' +
|
|
503
|
+
'Prefer this over update_entity for small edits (rename a heading, fix a sentence): it edits the live ' +
|
|
504
|
+
'editor body and cannot drop image/asset embeds it did not touch. Use expectedCount to guard against ' +
|
|
505
|
+
'ambiguous matches. For data/frontmatter changes use update_entity.',
|
|
506
|
+
parameters: T.Object({
|
|
507
|
+
repo: T.String({ description: 'Folder name' }),
|
|
508
|
+
entityType: T.String({ description: 'Entity type' }),
|
|
509
|
+
entityId: T.String({ description: 'Entity ID' }),
|
|
510
|
+
find: T.String({ description: 'Exact substring to locate in the current body (verbatim, including whitespace).' }),
|
|
511
|
+
replace: T.String({ description: 'Replacement text. Empty string deletes the matched substring.' }),
|
|
512
|
+
expectedCount: T.Optional(T.Number({ description: 'Optional safety check: require `find` to occur exactly this many times, else reject with NO_MATCH / AMBIGUOUS_MATCH.' })),
|
|
513
|
+
commitMessage: T.Optional(T.String({ description: 'Custom commit message' })),
|
|
514
|
+
}),
|
|
515
|
+
execute: async (toolCallId, params) => {
|
|
516
|
+
try {
|
|
517
|
+
const repoName = resolvePrivateRepo(params.repo);
|
|
518
|
+
if (!canAccessRepo(repoName, workspaceManager, getUser, authSvc)) {
|
|
519
|
+
return wrapToolResult({ success: false, error: `Folder "${repoName}" not found` });
|
|
520
|
+
}
|
|
521
|
+
if (params.find === '') {
|
|
522
|
+
return wrapToolResult({ success: false, error: 'patch_entity requires a non-empty `find` string' });
|
|
523
|
+
}
|
|
524
|
+
if (params.find === params.replace) {
|
|
525
|
+
return wrapToolResult({ success: false, error: 'patch_entity `find` and `replace` are identical — nothing to do' });
|
|
526
|
+
}
|
|
527
|
+
const graph = workspaceManager.getGraph(repoName);
|
|
528
|
+
const entity = graph.get(params.entityType, params.entityId);
|
|
529
|
+
// Patch against the live editor body when one is open, so we don't
|
|
530
|
+
// reintroduce the stale-read problem (editing a body that lags the
|
|
531
|
+
// user's in-editor typing / freshly-placed embeds).
|
|
532
|
+
const currentBody = graph.getLiveBody(params.entityType, params.entityId) ?? entity.document.content;
|
|
533
|
+
// Count literal (non-regex) occurrences.
|
|
534
|
+
let count = 0;
|
|
535
|
+
for (let i = currentBody.indexOf(params.find); i !== -1; i = currentBody.indexOf(params.find, i + params.find.length))
|
|
536
|
+
count++;
|
|
537
|
+
if (count === 0) {
|
|
538
|
+
return wrapToolResult({ success: false, code: 'NO_MATCH', error: `\`find\` substring not present in ${params.entityType}/${params.entityId}.` });
|
|
539
|
+
}
|
|
540
|
+
if (params.expectedCount !== undefined && count !== params.expectedCount) {
|
|
541
|
+
return wrapToolResult({ success: false, code: 'AMBIGUOUS_MATCH', occurrences: count, error: `Expected ${params.expectedCount} occurrence(s) of \`find\`, found ${count}.` });
|
|
542
|
+
}
|
|
543
|
+
const newBody = currentBody.split(params.find).join(params.replace);
|
|
544
|
+
const actor = effectiveGitUser();
|
|
545
|
+
const options = prepareUpdatePayload({
|
|
546
|
+
entityType: params.entityType,
|
|
547
|
+
entityId: params.entityId,
|
|
548
|
+
content: newBody,
|
|
549
|
+
commitMessage: params.commitMessage,
|
|
550
|
+
}, actor);
|
|
551
|
+
onAgentActivity?.('enter', repoName);
|
|
552
|
+
try {
|
|
553
|
+
const { entity: updated, frontmatter_changed, content_changed, noop, broadcast } = await diagnosedUpdate(graph, options, { content: newBody });
|
|
554
|
+
onMutation?.({ action: 'updated', repo: repoName, entityType: params.entityType, entityId: params.entityId });
|
|
555
|
+
return wrapToolResult({
|
|
556
|
+
success: true,
|
|
557
|
+
entity: {
|
|
558
|
+
entityId: updated.id,
|
|
559
|
+
entityType: updated.entityType,
|
|
560
|
+
repo: repoName,
|
|
561
|
+
data: cleanData(updated.data),
|
|
562
|
+
},
|
|
563
|
+
occurrences: count,
|
|
564
|
+
frontmatter_changed,
|
|
565
|
+
content_changed,
|
|
566
|
+
noop,
|
|
567
|
+
broadcast,
|
|
568
|
+
message: `Patched ${params.entityType}: ${params.entityId} (${count} occurrence${count === 1 ? '' : 's'}). Auto-committed — ready to push.`,
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
finally {
|
|
572
|
+
onAgentActivity?.('leave', repoName);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
if (error instanceof DroppedAssetError) {
|
|
577
|
+
return wrapToolResult({ success: false, code: 'ASSET_REMOVAL_BLOCKED', droppedAssets: error.dropped, error: error.message });
|
|
578
|
+
}
|
|
579
|
+
return wrapToolResult({ success: false, error: error instanceof Error ? error.message : 'Unknown error' });
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
// List assets already uploaded to a folder
|
|
584
|
+
{
|
|
585
|
+
name: 'list_assets',
|
|
586
|
+
label: 'List Assets',
|
|
587
|
+
description: 'List images/files already uploaded to a folder, with a ready-to-paste markdown embed for each. ' +
|
|
588
|
+
'Use this to find an asset a user placed (e.g. a screenshot) so you can embed it into an entry — ' +
|
|
589
|
+
'assets live in storage (R2/local), NOT on the local filesystem, so import_file cannot find them. ' +
|
|
590
|
+
'Each result has an `embed` string: use it verbatim and NEVER write ![[filename]] transclusion syntax ' +
|
|
591
|
+
'(it does not render). Optionally filter by entityType/entityId. This tool only references existing ' +
|
|
592
|
+
'assets — uploads happen via the workspace UI or import_file.',
|
|
593
|
+
parameters: T.Object({
|
|
594
|
+
repo: T.String({ description: 'Folder name' }),
|
|
595
|
+
entityType: T.Optional(T.String({ description: 'Filter to assets owned by entries of this type' })),
|
|
596
|
+
entityId: T.Optional(T.String({ description: 'Filter to assets owned by this entry id' })),
|
|
597
|
+
}),
|
|
598
|
+
execute: async (toolCallId, params) => {
|
|
599
|
+
try {
|
|
600
|
+
const repoName = resolvePrivateRepo(params.repo);
|
|
601
|
+
if (!canAccessRepo(repoName, workspaceManager, getUser, authSvc)) {
|
|
602
|
+
return wrapToolResult({ success: false, error: `Folder "${repoName}" not found` });
|
|
603
|
+
}
|
|
604
|
+
let assets = await listRepoAssets(repoName, workspaceManager);
|
|
605
|
+
if (params.entityType)
|
|
606
|
+
assets = assets.filter(a => a.entityType === params.entityType);
|
|
607
|
+
if (params.entityId)
|
|
608
|
+
assets = assets.filter(a => a.entityId === params.entityId);
|
|
262
609
|
return wrapToolResult({
|
|
263
610
|
success: true,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
611
|
+
repo: repoName,
|
|
612
|
+
count: assets.length,
|
|
613
|
+
assets: assets.map(a => ({
|
|
614
|
+
asset_id: a.assetId,
|
|
615
|
+
filename: a.filename,
|
|
616
|
+
mime_type: a.contentType,
|
|
617
|
+
entityType: a.entityType,
|
|
618
|
+
entityId: a.entityId,
|
|
619
|
+
entityName: a.entityName,
|
|
620
|
+
url: a.url,
|
|
621
|
+
embed: a.embed,
|
|
622
|
+
})),
|
|
271
623
|
});
|
|
272
624
|
}
|
|
273
625
|
catch (error) {
|
|
626
|
+
return wrapToolResult({ success: false, error: error instanceof Error ? error.message : 'Unknown error' });
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
// Embed an already-uploaded asset into an entry body
|
|
631
|
+
{
|
|
632
|
+
name: 'attach_asset_to_entity',
|
|
633
|
+
label: 'Attach Asset',
|
|
634
|
+
description: 'Embed an already-uploaded asset (from list_assets) into an entry body using its render-safe markdown, ' +
|
|
635
|
+
'without rewriting the whole body. mode "append" adds it at the end; mode "replace_placeholder" swaps dead ' +
|
|
636
|
+
'placeholder text (e.g. an ![[Foo.png]] written earlier) for the working embed. Drop-guarded: rejects with ' +
|
|
637
|
+
'ASSET_REMOVAL_BLOCKED if it would remove other image embeds (append only — replace_placeholder is exempt ' +
|
|
638
|
+
'since the placeholder it removes is the dead reference being fixed).',
|
|
639
|
+
parameters: T.Object({
|
|
640
|
+
repo: T.String({ description: 'Folder name' }),
|
|
641
|
+
entityType: T.String({ description: 'Entity type of the target entry' }),
|
|
642
|
+
entityId: T.String({ description: 'Entity ID of the target entry' }),
|
|
643
|
+
url: T.String({ description: 'Asset url, taken verbatim from list_assets' }),
|
|
644
|
+
alt: T.Optional(T.String({ description: 'Alt text / caption. Defaults to the asset filename.' })),
|
|
645
|
+
mode: T.Optional(T.Union([T.Literal('append'), T.Literal('replace_placeholder')], { description: 'append (default) or replace_placeholder' })),
|
|
646
|
+
placeholder: T.Optional(T.String({ description: 'Required for replace_placeholder: exact text to replace' })),
|
|
647
|
+
expectedCount: T.Optional(T.Number({ description: 'For replace_placeholder: require this many occurrences, else reject' })),
|
|
648
|
+
}),
|
|
649
|
+
execute: async (toolCallId, params) => {
|
|
650
|
+
try {
|
|
651
|
+
const repoName = resolvePrivateRepo(params.repo);
|
|
652
|
+
if (!canAccessRepo(repoName, workspaceManager, getUser, authSvc)) {
|
|
653
|
+
return wrapToolResult({ success: false, error: `Folder "${repoName}" not found` });
|
|
654
|
+
}
|
|
655
|
+
const url = params.url ?? '';
|
|
656
|
+
if (!url.trim())
|
|
657
|
+
return wrapToolResult({ success: false, error: 'url is required' });
|
|
658
|
+
const mode = params.mode ?? 'append';
|
|
659
|
+
if (mode === 'replace_placeholder' && !params.placeholder) {
|
|
660
|
+
return wrapToolResult({ success: false, error: 'mode "replace_placeholder" requires a non-empty placeholder' });
|
|
661
|
+
}
|
|
662
|
+
const filename = params.alt ?? (url.split('/').pop() || 'asset').split('?')[0];
|
|
663
|
+
const embed = assetEmbed(filename, url);
|
|
664
|
+
const graph = workspaceManager.getGraph(repoName);
|
|
665
|
+
const entity = graph.get(params.entityType, params.entityId);
|
|
666
|
+
const currentBody = graph.getLiveBody(params.entityType, params.entityId) ?? entity.document.content;
|
|
667
|
+
let newBody;
|
|
668
|
+
let occurrences;
|
|
669
|
+
let allowAssetRemoval = false;
|
|
670
|
+
if (mode === 'replace_placeholder') {
|
|
671
|
+
const ph = params.placeholder;
|
|
672
|
+
let count = 0;
|
|
673
|
+
for (let i = currentBody.indexOf(ph); i !== -1; i = currentBody.indexOf(ph, i + ph.length))
|
|
674
|
+
count++;
|
|
675
|
+
if (count === 0) {
|
|
676
|
+
return wrapToolResult({ success: false, code: 'NO_MATCH', error: `placeholder not present in ${params.entityType}/${params.entityId}.` });
|
|
677
|
+
}
|
|
678
|
+
if (params.expectedCount !== undefined && count !== params.expectedCount) {
|
|
679
|
+
return wrapToolResult({ success: false, code: 'AMBIGUOUS_MATCH', occurrences: count, error: `Expected ${params.expectedCount} occurrence(s) of placeholder, found ${count}.` });
|
|
680
|
+
}
|
|
681
|
+
occurrences = count;
|
|
682
|
+
newBody = currentBody.split(ph).join(embed);
|
|
683
|
+
// The placeholder we're removing is itself a recognized embed
|
|
684
|
+
// target; swapping it for the working embed is the whole point.
|
|
685
|
+
allowAssetRemoval = true;
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
if (currentBody.includes(embed)) {
|
|
689
|
+
return wrapToolResult({ success: false, code: 'ALREADY_PRESENT', error: `Asset embed already present in ${params.entityType}/${params.entityId}.` });
|
|
690
|
+
}
|
|
691
|
+
newBody = currentBody.trimEnd() + '\n\n' + embed + '\n';
|
|
692
|
+
}
|
|
693
|
+
const options = prepareUpdatePayload({
|
|
694
|
+
entityType: params.entityType,
|
|
695
|
+
entityId: params.entityId,
|
|
696
|
+
content: newBody,
|
|
697
|
+
}, effectiveGitUser());
|
|
698
|
+
onAgentActivity?.('enter', repoName);
|
|
699
|
+
try {
|
|
700
|
+
const { entity: updated, frontmatter_changed, content_changed, noop, broadcast } = await diagnosedUpdate(graph, options, { content: newBody }, { allowAssetRemoval });
|
|
701
|
+
onMutation?.({ action: 'updated', repo: repoName, entityType: params.entityType, entityId: params.entityId });
|
|
702
|
+
return wrapToolResult({
|
|
703
|
+
success: true,
|
|
704
|
+
entity: { entityId: updated.id, entityType: updated.entityType, repo: repoName },
|
|
705
|
+
embed,
|
|
706
|
+
mode,
|
|
707
|
+
occurrences,
|
|
708
|
+
frontmatter_changed,
|
|
709
|
+
content_changed,
|
|
710
|
+
noop,
|
|
711
|
+
broadcast,
|
|
712
|
+
message: `Embedded ${filename} into ${params.entityType}: ${params.entityId}. Auto-committed — ready to push.`,
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
finally {
|
|
716
|
+
onAgentActivity?.('leave', repoName);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
if (error instanceof DroppedAssetError) {
|
|
721
|
+
return wrapToolResult({ success: false, code: 'ASSET_REMOVAL_BLOCKED', droppedAssets: error.dropped, error: error.message });
|
|
722
|
+
}
|
|
723
|
+
return wrapToolResult({ success: false, error: error instanceof Error ? error.message : 'Unknown error' });
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
},
|
|
727
|
+
// Promote a composer/chat scratch file (image or doc) into the asset library.
|
|
728
|
+
{
|
|
729
|
+
name: 'save_file_to_assets',
|
|
730
|
+
label: 'Save File to Assets',
|
|
731
|
+
description: 'Save a file the user attached to THIS chat (a composer/scratch image or document) into the curated ' +
|
|
732
|
+
'asset library so it persists beyond the conversation. Use this when the user says something like "save ' +
|
|
733
|
+
'this to my assets". By default it saves the most-recently-attached file to the shared Assets folder ' +
|
|
734
|
+
'(the user can then move it wherever they like); pass `filename` to disambiguate when several are ' +
|
|
735
|
+
'attached, or `folder` to save into a specific folder you have write access to. Only works for files ' +
|
|
736
|
+
'the user attached to the chat (and not ones already picked from the library) — it cannot save arbitrary ' +
|
|
737
|
+
'URLs or filesystem paths.',
|
|
738
|
+
parameters: T.Object({
|
|
739
|
+
filename: T.Optional(T.String({ description: 'Which attached file to save, by filename (defaults to the most recent).' })),
|
|
740
|
+
folder: T.Optional(T.String({ description: 'Destination folder name. Defaults to the shared Assets library.' })),
|
|
741
|
+
}),
|
|
742
|
+
execute: async (toolCallId, params) => {
|
|
743
|
+
try {
|
|
744
|
+
const user = getUser();
|
|
745
|
+
if (!user)
|
|
746
|
+
return wrapToolResult({ success: false, error: 'Saving files requires a signed-in user.' });
|
|
747
|
+
if (!chatSessionSvc) {
|
|
748
|
+
return wrapToolResult({ success: false, error: 'Saving chat files is not available in this context.' });
|
|
749
|
+
}
|
|
750
|
+
const sessionId = getSessionId();
|
|
751
|
+
if (!sessionId) {
|
|
752
|
+
return wrapToolResult({ success: false, code: 'NO_SESSION', error: 'No active conversation to save a file from.' });
|
|
753
|
+
}
|
|
754
|
+
// Scope to THIS conversation's OWN uploads (not library picks, which are
|
|
755
|
+
// already saved; not the user's other chats).
|
|
756
|
+
const files = chatSessionSvc.getScratchFilesBySession(sessionId)
|
|
757
|
+
.filter((f) => f.userId === user.id && f.source === 'upload');
|
|
758
|
+
if (files.length === 0) {
|
|
759
|
+
return wrapToolResult({ success: false, code: 'NO_FILE', error: 'No uploaded file is attached to this chat to save.' });
|
|
760
|
+
}
|
|
761
|
+
// Resolve which scratch file: by filename if given, else most recent.
|
|
762
|
+
let target = files[0];
|
|
763
|
+
if (params.filename) {
|
|
764
|
+
const want = String(params.filename).toLowerCase();
|
|
765
|
+
const match = files.find(f => f.filename.toLowerCase() === want)
|
|
766
|
+
?? files.find(f => f.filename.toLowerCase().includes(want));
|
|
767
|
+
if (!match) {
|
|
768
|
+
return wrapToolResult({
|
|
769
|
+
success: false, code: 'NO_MATCH',
|
|
770
|
+
error: `No attached file matches "${params.filename}".`,
|
|
771
|
+
attached: files.map(f => f.filename),
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
target = match;
|
|
775
|
+
}
|
|
776
|
+
const folder = params.folder ? resolvePrivateRepo(params.folder) : undefined;
|
|
777
|
+
const result = await promoteScratchFileToAssets(workspaceManager, chatSessionSvc, authSvc, {
|
|
778
|
+
sessionId: target.sessionId,
|
|
779
|
+
fileId: target.fileId,
|
|
780
|
+
user,
|
|
781
|
+
targetRepo: folder,
|
|
782
|
+
});
|
|
783
|
+
onMutation?.({ action: 'updated', repo: result.repo, entityType: 'material', entityId: result.assetId, repoSync: true });
|
|
274
784
|
return wrapToolResult({
|
|
275
|
-
success:
|
|
276
|
-
|
|
785
|
+
success: true,
|
|
786
|
+
asset_id: result.assetId,
|
|
787
|
+
filename: result.filename,
|
|
788
|
+
name: result.displayName,
|
|
789
|
+
url: result.url,
|
|
790
|
+
repo: result.repo,
|
|
791
|
+
message: `Saved "${result.displayName}" to the ${result.repo} folder. You can move it to another folder from the asset library.`,
|
|
277
792
|
});
|
|
278
793
|
}
|
|
794
|
+
catch (error) {
|
|
795
|
+
if (error instanceof PromoteScratchError) {
|
|
796
|
+
return wrapToolResult({ success: false, code: error.code, error: error.message });
|
|
797
|
+
}
|
|
798
|
+
return wrapToolResult({ success: false, error: error instanceof Error ? error.message : 'Unknown error' });
|
|
799
|
+
}
|
|
279
800
|
},
|
|
280
801
|
},
|
|
281
802
|
// Delete entity
|
|
282
803
|
{
|
|
283
804
|
name: 'delete_entity',
|
|
284
805
|
label: 'Delete Entry',
|
|
285
|
-
description: 'Delete an entry. Use with caution!
|
|
806
|
+
description: 'Delete an entry. Use with caution! Folder is auto-resolved for app-defined types.',
|
|
286
807
|
parameters: T.Object({
|
|
287
|
-
repo: T.Optional(T.String({ description: '
|
|
808
|
+
repo: T.Optional(T.String({ description: 'Folder name (auto-resolved for app entity types)' })),
|
|
288
809
|
entityType: T.String({ description: 'Entity type' }),
|
|
289
810
|
entityId: T.String({ description: 'Entity ID' }),
|
|
290
811
|
commitMessage: T.Optional(T.String({ description: 'Custom commit message' })),
|
|
@@ -293,21 +814,39 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
293
814
|
try {
|
|
294
815
|
let repoName = params.repo;
|
|
295
816
|
if (!repoName) {
|
|
296
|
-
|
|
297
|
-
if (!repoName)
|
|
298
|
-
return wrapToolResult({ success: false, error: `repo is required — could not auto-resolve repo for type "${params.entityType}"` });
|
|
817
|
+
return wrapToolResult({ success: false, error: `repo is required — must specify which folder to use for type "${params.entityType}"` });
|
|
299
818
|
}
|
|
300
819
|
repoName = resolvePrivateRepo(repoName);
|
|
301
820
|
if (!canAccessRepo(repoName, workspaceManager, getUser, authSvc)) {
|
|
302
|
-
return wrapToolResult({ success: false, error: `
|
|
821
|
+
return wrapToolResult({ success: false, error: `Folder "${repoName}" not found` });
|
|
303
822
|
}
|
|
304
823
|
const graph = workspaceManager.getGraph(repoName);
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
824
|
+
// Soft bridge-awareness: deleting a person linked to an auth account
|
|
825
|
+
// orphans that user. Don't block — surface a warning so the agent can
|
|
826
|
+
// tell the user, and the Profile panel catches the orphan after.
|
|
827
|
+
let bridgeWarning;
|
|
828
|
+
if (params.entityType === 'person') {
|
|
829
|
+
try {
|
|
830
|
+
const handle = graph.get('person', params.entityId)?.data?.user_handle;
|
|
831
|
+
if (typeof handle === 'string' && handle.length > 0) {
|
|
832
|
+
bridgeWarning = `Note: this record was linked to the "${handle}" user account. Deleting it unlinks them — their "My Tasks", @mentions, and assignments stop resolving until they link a new record.`;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
catch { /* not found — let graph.delete surface it */ }
|
|
836
|
+
}
|
|
837
|
+
onAgentActivity?.('enter', repoName);
|
|
838
|
+
try {
|
|
839
|
+
await graph.delete(params.entityType, params.entityId, params.commitMessage);
|
|
840
|
+
onMutation?.({ action: 'deleted', repo: repoName, entityType: params.entityType, entityId: params.entityId });
|
|
841
|
+
return wrapToolResult({
|
|
842
|
+
success: true,
|
|
843
|
+
message: `Deleted ${params.entityType}: ${params.entityId}. Auto-committed — ready to push.`,
|
|
844
|
+
...(bridgeWarning ? { warning: bridgeWarning } : {}),
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
finally {
|
|
848
|
+
onAgentActivity?.('leave', repoName);
|
|
849
|
+
}
|
|
311
850
|
}
|
|
312
851
|
catch (error) {
|
|
313
852
|
return wrapToolResult({
|
|
@@ -321,46 +860,94 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
321
860
|
{
|
|
322
861
|
name: 'import_file',
|
|
323
862
|
label: 'Import File',
|
|
324
|
-
description: 'Import a local file as a Studiograph entry.
|
|
863
|
+
description: 'Import a local file as a Studiograph entry. Text formats become typed entries (.md/.txt → note, .csv → dataset). Binary formats become typed entries that embed a free-standing asset: images/opaque files → material, PDF/Word → document, spreadsheets → dataset.',
|
|
325
864
|
parameters: T.Object({
|
|
326
865
|
path: T.String({ description: 'Absolute path or ~/relative path to the file' }),
|
|
327
|
-
|
|
328
|
-
entityType: T.Optional(T.String({ description: 'Override entity type (default: auto-detect from extension)' })),
|
|
866
|
+
folder: T.String({ description: 'Target folder name' }),
|
|
867
|
+
entityType: T.Optional(T.String({ description: 'Override entity type (default: auto-detect from extension; ignored for binary imports, which route by kind)' })),
|
|
329
868
|
entityId: T.Optional(T.String({ description: 'Override entity ID (default: from filename)' })),
|
|
330
869
|
}),
|
|
331
870
|
execute: async (toolCallId, params) => {
|
|
332
871
|
try {
|
|
333
|
-
if (!canAccessRepo(params.
|
|
334
|
-
return wrapToolResult({ success: false, error: `
|
|
872
|
+
if (!canAccessRepo(params.folder, workspaceManager, getUser, authSvc)) {
|
|
873
|
+
return wrapToolResult({ success: false, error: `Folder "${params.folder}" not found` });
|
|
335
874
|
}
|
|
336
875
|
const { readFileSync, existsSync } = await import('fs');
|
|
337
|
-
const { resolve } = await import('path');
|
|
876
|
+
const { resolve, basename, sep } = await import('path');
|
|
338
877
|
const { homedir } = await import('os');
|
|
339
878
|
const { importFile, isSupportedFile } = await import('../../services/import-service.js');
|
|
879
|
+
const { isAssetUploadExtension } = await import('../../services/assets/base.js');
|
|
880
|
+
const { importAssetAsEntity } = await import('../../services/asset-import-service.js');
|
|
340
881
|
const filePath = params.path.startsWith('~/')
|
|
341
882
|
? resolve(homedir(), params.path.slice(2))
|
|
342
883
|
: resolve(params.path);
|
|
343
884
|
if (!existsSync(filePath)) {
|
|
344
885
|
return wrapToolResult({ success: false, error: `File not found: ${filePath}` });
|
|
345
886
|
}
|
|
887
|
+
const graph = workspaceManager.getGraph(params.folder);
|
|
888
|
+
const fname = basename(filePath);
|
|
889
|
+
// Path-exfil guard: the agent reads bytes off disk and uploads
|
|
890
|
+
// them as a workspace asset, accessible via /api/assets/* by any
|
|
891
|
+
// folder member. Without an allowlist, an LLM prompt injection in
|
|
892
|
+
// an imported note can ask the agent to grab ~/.ssh/id_rsa, .env
|
|
893
|
+
// files, kubeconfigs, etc. by renaming them to a permitted
|
|
894
|
+
// extension. Restrict to roots a user would plausibly drop files
|
|
895
|
+
// from: the workspace itself, ~/Downloads, ~/Documents.
|
|
896
|
+
const home = homedir();
|
|
897
|
+
const workspaceRoot = graph.getWorkspaceRoot();
|
|
898
|
+
const allowedRoots = [
|
|
899
|
+
workspaceRoot,
|
|
900
|
+
resolve(home, 'Downloads'),
|
|
901
|
+
resolve(home, 'Documents'),
|
|
902
|
+
].filter((r) => Boolean(r)).map(r => r.endsWith(sep) ? r : r + sep);
|
|
903
|
+
const isInAllowedRoot = allowedRoots.some(root => filePath === root.slice(0, -1) || filePath.startsWith(root));
|
|
904
|
+
if (!isInAllowedRoot) {
|
|
905
|
+
return wrapToolResult({
|
|
906
|
+
success: false,
|
|
907
|
+
error: `Path "${filePath}" is outside the import allowlist. Files must live under the workspace, ~/Downloads, or ~/Documents.`,
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
// Binary extensions → asset-as-embed import: store a free-standing
|
|
911
|
+
// asset + create the typed entry(ies) that embed it (image→material,
|
|
912
|
+
// doc/pdf→document, xlsx→dataset×N). The text-import path doesn't know
|
|
913
|
+
// how to handle .indd / .pptx / etc. Parity with the web import
|
|
914
|
+
// dispatcher (importAssetAsEntity).
|
|
915
|
+
if (isAssetUploadExtension(fname)) {
|
|
916
|
+
const buffer = readFileSync(filePath);
|
|
917
|
+
const result = await importAssetAsEntity({
|
|
918
|
+
graph,
|
|
919
|
+
filename: fname,
|
|
920
|
+
buffer,
|
|
921
|
+
});
|
|
922
|
+
for (const e of result.entities) {
|
|
923
|
+
onMutation?.({ action: 'created', repo: params.folder, entityType: e.entityType, entityId: e.entityId });
|
|
924
|
+
}
|
|
925
|
+
const first = result.entities[0];
|
|
926
|
+
return wrapToolResult({
|
|
927
|
+
success: true,
|
|
928
|
+
entityType: first?.entityType,
|
|
929
|
+
entityId: first?.entityId,
|
|
930
|
+
assetId: result.assetId,
|
|
931
|
+
entities: result.entities.map(e => ({ entityType: e.entityType, entityId: e.entityId })),
|
|
932
|
+
message: `Imported ${fname} as ${result.entities.map(e => `${e.entityType}/${e.entityId}`).join(', ')} in ${params.folder}`,
|
|
933
|
+
});
|
|
934
|
+
}
|
|
346
935
|
if (!isSupportedFile(filePath)) {
|
|
347
|
-
return wrapToolResult({ success: false, error: `Unsupported file type
|
|
936
|
+
return wrapToolResult({ success: false, error: `Unsupported file type for ${fname}` });
|
|
348
937
|
}
|
|
349
|
-
const graph = workspaceManager.getGraph(params.collection);
|
|
350
938
|
const content = readFileSync(filePath);
|
|
351
|
-
const
|
|
352
|
-
const result = await importFile(graph, basename(filePath), content, {
|
|
939
|
+
const result = await importFile(graph, fname, content, {
|
|
353
940
|
entityType: params.entityType,
|
|
354
941
|
entityId: params.entityId,
|
|
355
942
|
});
|
|
356
943
|
if (result.status === 'created') {
|
|
357
|
-
onMutation?.({ action: 'created', repo: params.
|
|
944
|
+
onMutation?.({ action: 'created', repo: params.folder, entityType: result.entityType, entityId: result.entityId });
|
|
358
945
|
}
|
|
359
946
|
return wrapToolResult({
|
|
360
947
|
success: result.status === 'created',
|
|
361
948
|
...result,
|
|
362
949
|
message: result.status === 'created'
|
|
363
|
-
? `Imported ${
|
|
950
|
+
? `Imported ${fname} as ${result.entityType}/${result.entityId} in ${params.folder}`
|
|
364
951
|
: result.error,
|
|
365
952
|
});
|
|
366
953
|
}
|
|
@@ -376,9 +963,9 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
376
963
|
{
|
|
377
964
|
name: 'rename_entity',
|
|
378
965
|
label: 'Rename Entry',
|
|
379
|
-
description: 'Rename an entry by changing its ID. Preserves all data, content, and source_ref. The old ID will no longer exist.
|
|
966
|
+
description: 'Rename an entry by changing its ID. Preserves all data, content, and source_ref. The old ID will no longer exist. Folder is auto-resolved for app-defined types.',
|
|
380
967
|
parameters: T.Object({
|
|
381
|
-
repo: T.Optional(T.String({ description: '
|
|
968
|
+
repo: T.Optional(T.String({ description: 'Folder name (auto-resolved for app entity types)' })),
|
|
382
969
|
entityType: T.String({ description: 'Entity type' }),
|
|
383
970
|
oldId: T.String({ description: 'Current entity ID' }),
|
|
384
971
|
newId: T.String({ description: 'New entity ID (kebab-case)' }),
|
|
@@ -387,13 +974,11 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
387
974
|
try {
|
|
388
975
|
let repoName = params.repo;
|
|
389
976
|
if (!repoName) {
|
|
390
|
-
|
|
391
|
-
if (!repoName)
|
|
392
|
-
return wrapToolResult({ success: false, error: `repo is required — could not auto-resolve repo for type "${params.entityType}"` });
|
|
977
|
+
return wrapToolResult({ success: false, error: `repo is required — must specify which folder to use for type "${params.entityType}"` });
|
|
393
978
|
}
|
|
394
979
|
repoName = resolvePrivateRepo(repoName);
|
|
395
980
|
if (!canAccessRepo(repoName, workspaceManager, getUser, authSvc)) {
|
|
396
|
-
return wrapToolResult({ success: false, error: `
|
|
981
|
+
return wrapToolResult({ success: false, error: `Folder "${repoName}" not found` });
|
|
397
982
|
}
|
|
398
983
|
const graph = workspaceManager.getGraph(repoName);
|
|
399
984
|
const result = await graph.rename(params.entityType, params.oldId, params.newId);
|
|
@@ -413,41 +998,41 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
413
998
|
}
|
|
414
999
|
},
|
|
415
1000
|
},
|
|
416
|
-
//
|
|
1001
|
+
// Transfer entity across repos (cross-repo move). Distinct from
|
|
1002
|
+
// folder-tools' `move_entity`, which relocates within one repo.
|
|
417
1003
|
{
|
|
418
|
-
name: '
|
|
419
|
-
label: '
|
|
420
|
-
description: 'Move an entry from one
|
|
1004
|
+
name: 'transfer_entity',
|
|
1005
|
+
label: 'Transfer Entry',
|
|
1006
|
+
description: 'Move an entry from one repo to another. Preserves all data, content, and wikilinks. Requires access to both folders. For relocating within the same repo (folder_path change), use move_entity instead.',
|
|
421
1007
|
parameters: T.Object({
|
|
422
|
-
sourceRepo: T.String({ description: 'Source
|
|
423
|
-
targetRepo: T.String({ description: 'Target
|
|
1008
|
+
sourceRepo: T.String({ description: 'Source repo name' }),
|
|
1009
|
+
targetRepo: T.String({ description: 'Target repo name' }),
|
|
424
1010
|
entityType: T.String({ description: 'Entity type' }),
|
|
425
1011
|
entityId: T.String({ description: 'Entity ID' }),
|
|
426
1012
|
commitMessage: T.Optional(T.String({ description: 'Custom commit message (optional)' })),
|
|
427
1013
|
}),
|
|
428
1014
|
execute: async (toolCallId, params) => {
|
|
429
1015
|
try {
|
|
430
|
-
const denied = adminOnly(getUser);
|
|
431
|
-
if (denied)
|
|
432
|
-
return wrapToolResult({ success: false, error: denied });
|
|
433
1016
|
if (!canAccessRepo(params.sourceRepo, workspaceManager, getUser, authSvc)) {
|
|
434
|
-
return wrapToolResult({ success: false, error: `
|
|
1017
|
+
return wrapToolResult({ success: false, error: `Folder "${params.sourceRepo}" not found` });
|
|
435
1018
|
}
|
|
436
1019
|
if (!canAccessRepo(params.targetRepo, workspaceManager, getUser, authSvc)) {
|
|
437
|
-
return wrapToolResult({ success: false, error: `
|
|
1020
|
+
return wrapToolResult({ success: false, error: `Folder "${params.targetRepo}" not found` });
|
|
438
1021
|
}
|
|
439
1022
|
const sourceGraph = workspaceManager.getGraph(params.sourceRepo);
|
|
440
1023
|
const targetGraph = workspaceManager.getGraph(params.targetRepo);
|
|
441
1024
|
// Read the full entity from source
|
|
442
1025
|
const entity = sourceGraph.get(params.entityType, params.entityId);
|
|
443
1026
|
// Create in target repo
|
|
1027
|
+
const actor = effectiveGitUser();
|
|
444
1028
|
const now = new Date().toISOString();
|
|
445
1029
|
await targetGraph.create({
|
|
446
1030
|
entityType: params.entityType,
|
|
447
1031
|
entityId: params.entityId,
|
|
448
|
-
data: { ...entity.data, updated_at: now, updated_by:
|
|
1032
|
+
data: { ...entity.data, updated_at: now, updated_by: actor.id },
|
|
449
1033
|
content: entity.document.content || '',
|
|
450
1034
|
commitMessage: params.commitMessage || `Move ${params.entityType} ${params.entityId} from ${params.sourceRepo} to ${params.targetRepo}`,
|
|
1035
|
+
user: actor,
|
|
451
1036
|
});
|
|
452
1037
|
// Delete from source repo
|
|
453
1038
|
await sourceGraph.delete(params.entityType, params.entityId, `Move ${params.entityType} ${params.entityId} to ${params.targetRepo}`);
|
|
@@ -470,11 +1055,11 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
470
1055
|
{
|
|
471
1056
|
name: 'search_entities',
|
|
472
1057
|
label: 'Search Entries',
|
|
473
|
-
description: 'Search for entries across
|
|
1058
|
+
description: 'Search for entries across folders with filters.',
|
|
474
1059
|
parameters: T.Object({
|
|
475
1060
|
query: T.Optional(T.String({ description: 'Search query (full-text)' })),
|
|
476
1061
|
entityType: T.Optional(T.String({ description: 'Filter by entity type' })),
|
|
477
|
-
repoNames: T.Optional(T.Array(T.String(), { description: 'Limit to specific
|
|
1062
|
+
repoNames: T.Optional(T.Array(T.String(), { description: 'Limit to specific folders' })),
|
|
478
1063
|
visibility: T.Optional(T.Array(T.String(), { description: 'Filter by visibility (public, team, admin, restricted)' })),
|
|
479
1064
|
tags: T.Optional(T.Array(T.String(), { description: 'Filter by tags' })),
|
|
480
1065
|
dateRange: T.Optional(T.Object({
|
|
@@ -486,7 +1071,7 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
486
1071
|
}),
|
|
487
1072
|
execute: async (toolCallId, params) => {
|
|
488
1073
|
try {
|
|
489
|
-
// Scope search to accessible
|
|
1074
|
+
// Scope search to accessible folders
|
|
490
1075
|
const allowed = getAccessibleRepoNames(workspaceManager, getUser, authSvc);
|
|
491
1076
|
let repoNames = params.repoNames?.map((r) => resolvePrivateRepo(r));
|
|
492
1077
|
if (allowed) {
|
|
@@ -534,8 +1119,32 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
534
1119
|
}),
|
|
535
1120
|
execute: async (toolCallId, params) => {
|
|
536
1121
|
try {
|
|
1122
|
+
// Comments live in SQLite, not the filesystem graph — dispatch
|
|
1123
|
+
// through the shared virtual-entity adapter (mirrors the MCP surface),
|
|
1124
|
+
// filtering to the folders the caller can see.
|
|
1125
|
+
if (params.entityType === 'comment' && commentSvc) {
|
|
1126
|
+
const allowedComments = getAccessibleRepoNames(workspaceManager, getUser, authSvc);
|
|
1127
|
+
// Filter to accessible folders BEFORE applying the caller's limit:
|
|
1128
|
+
// a pre-filter SQL cap could be consumed entirely by comments in
|
|
1129
|
+
// folders the caller can't see, starving the visible ones. The
|
|
1130
|
+
// service still bounds the underlying fetch (<=500) as a backstop.
|
|
1131
|
+
const visible = commentVirtualEntity
|
|
1132
|
+
.listComments(commentSvc, {})
|
|
1133
|
+
.filter(e => !allowedComments || allowedComments.has(e.data.repo));
|
|
1134
|
+
const envelopes = params.limit ? visible.slice(0, params.limit) : visible;
|
|
1135
|
+
return wrapToolResult({
|
|
1136
|
+
success: true,
|
|
1137
|
+
count: envelopes.length,
|
|
1138
|
+
results: envelopes.map(e => ({
|
|
1139
|
+
repo: e.data.repo,
|
|
1140
|
+
entityId: e.id,
|
|
1141
|
+
entityType: e.entityType,
|
|
1142
|
+
data: cleanData(e.data),
|
|
1143
|
+
})),
|
|
1144
|
+
});
|
|
1145
|
+
}
|
|
537
1146
|
let results = workspaceManager.list(params.entityType, params.limit);
|
|
538
|
-
// Filter to accessible
|
|
1147
|
+
// Filter to accessible folders
|
|
539
1148
|
const allowed = getAccessibleRepoNames(workspaceManager, getUser, authSvc);
|
|
540
1149
|
if (allowed) {
|
|
541
1150
|
results = results.filter(r => allowed.has(r.repoName));
|
|
@@ -563,9 +1172,9 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
563
1172
|
{
|
|
564
1173
|
name: 'get_related',
|
|
565
1174
|
label: 'Get Related Entries',
|
|
566
|
-
description: 'Get entries related to a given entry via wikilinks (cross-
|
|
1175
|
+
description: 'Get entries related to a given entry via wikilinks (cross-folder supported). Pass repo, entityType, entityId from search/list results.',
|
|
567
1176
|
parameters: T.Object({
|
|
568
|
-
repo: T.String({ description: '
|
|
1177
|
+
repo: T.String({ description: 'Folder name' }),
|
|
569
1178
|
entityType: T.String({ description: 'Entity type' }),
|
|
570
1179
|
entityId: T.String({ description: 'Entity ID' }),
|
|
571
1180
|
}),
|
|
@@ -573,23 +1182,38 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
573
1182
|
try {
|
|
574
1183
|
const repo = resolvePrivateRepo(params.repo);
|
|
575
1184
|
if (!canAccessRepo(repo, workspaceManager, getUser, authSvc)) {
|
|
576
|
-
return wrapToolResult({ success: false, error: `
|
|
1185
|
+
return wrapToolResult({ success: false, error: `Folder "${repo}" not found` });
|
|
577
1186
|
}
|
|
578
1187
|
let related = workspaceManager.getRelated(repo, params.entityType, params.entityId);
|
|
579
|
-
// Filter cross-
|
|
1188
|
+
// Filter cross-folder results to accessible repos
|
|
580
1189
|
const allowed = getAccessibleRepoNames(workspaceManager, getUser, authSvc);
|
|
581
1190
|
if (allowed) {
|
|
582
1191
|
related = related.filter(r => allowed.has(r.repoName));
|
|
583
1192
|
}
|
|
1193
|
+
const relatedResults = related.map(r => ({
|
|
1194
|
+
repo: r.repoName,
|
|
1195
|
+
entityId: r.id,
|
|
1196
|
+
entityType: r.entityType,
|
|
1197
|
+
data: cleanData(r.data),
|
|
1198
|
+
}));
|
|
1199
|
+
// Comments anchored to this entry surface as related entries
|
|
1200
|
+
// (mirrors the MCP find_related incoming branch). The caller already
|
|
1201
|
+
// passed canAccessRepo for `repo`, so these are in-scope.
|
|
1202
|
+
if (commentSvc) {
|
|
1203
|
+
const anchored = commentVirtualEntity.findRelatedComments(commentSvc, repo, params.entityType, params.entityId);
|
|
1204
|
+
for (const e of anchored) {
|
|
1205
|
+
relatedResults.push({
|
|
1206
|
+
repo,
|
|
1207
|
+
entityId: e.id,
|
|
1208
|
+
entityType: e.entityType,
|
|
1209
|
+
data: cleanData(e.data),
|
|
1210
|
+
});
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
584
1213
|
return wrapToolResult({
|
|
585
1214
|
success: true,
|
|
586
|
-
count:
|
|
587
|
-
related:
|
|
588
|
-
repo: r.repoName,
|
|
589
|
-
entityId: r.id,
|
|
590
|
-
entityType: r.entityType,
|
|
591
|
-
data: cleanData(r.data),
|
|
592
|
-
})),
|
|
1215
|
+
count: relatedResults.length,
|
|
1216
|
+
related: relatedResults,
|
|
593
1217
|
});
|
|
594
1218
|
}
|
|
595
1219
|
catch (error) {
|
|
@@ -604,19 +1228,19 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
604
1228
|
{
|
|
605
1229
|
name: 'get_backlinks',
|
|
606
1230
|
label: 'Get Backlinks',
|
|
607
|
-
description: 'Get entries that link to a given entry (cross-
|
|
1231
|
+
description: 'Get entries that link to a given entry (cross-folder supported). Pass repo, entityType, entityId from search/list results.',
|
|
608
1232
|
parameters: T.Object({
|
|
609
|
-
repo: T.String({ description: '
|
|
1233
|
+
repo: T.String({ description: 'Folder name' }),
|
|
610
1234
|
entityType: T.String({ description: 'Entity type' }),
|
|
611
1235
|
entityId: T.String({ description: 'Entity ID' }),
|
|
612
1236
|
}),
|
|
613
1237
|
execute: async (toolCallId, params) => {
|
|
614
1238
|
try {
|
|
615
1239
|
if (!canAccessRepo(params.repo, workspaceManager, getUser, authSvc)) {
|
|
616
|
-
return wrapToolResult({ success: false, error: `
|
|
1240
|
+
return wrapToolResult({ success: false, error: `Folder "${params.repo}" not found` });
|
|
617
1241
|
}
|
|
618
1242
|
let backlinks = workspaceManager.getBacklinks(params.repo, params.entityType, params.entityId);
|
|
619
|
-
// Filter cross-
|
|
1243
|
+
// Filter cross-folder results to accessible repos
|
|
620
1244
|
const allowed = getAccessibleRepoNames(workspaceManager, getUser, authSvc);
|
|
621
1245
|
if (allowed) {
|
|
622
1246
|
backlinks = backlinks.filter(r => allowed.has(r.repoName));
|
|
@@ -644,16 +1268,16 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
644
1268
|
{
|
|
645
1269
|
name: 'validate_wikilinks',
|
|
646
1270
|
label: 'Validate Wikilinks',
|
|
647
|
-
description: 'Validate all wikilinks in an entry (checks same
|
|
1271
|
+
description: 'Validate all wikilinks in an entry (checks same folder and cross-folder)',
|
|
648
1272
|
parameters: T.Object({
|
|
649
|
-
repo: T.String({ description: '
|
|
1273
|
+
repo: T.String({ description: 'Folder name' }),
|
|
650
1274
|
entityType: T.String({ description: 'Entity type' }),
|
|
651
1275
|
entityId: T.String({ description: 'Entity ID' }),
|
|
652
1276
|
}),
|
|
653
1277
|
execute: async (toolCallId, params) => {
|
|
654
1278
|
try {
|
|
655
1279
|
if (!canAccessRepo(params.repo, workspaceManager, getUser, authSvc)) {
|
|
656
|
-
return wrapToolResult({ success: false, error: `
|
|
1280
|
+
return wrapToolResult({ success: false, error: `Folder "${params.repo}" not found` });
|
|
657
1281
|
}
|
|
658
1282
|
const validation = workspaceManager.validateWikilinks(params.repo, params.entityType, params.entityId);
|
|
659
1283
|
return wrapToolResult({
|
|
@@ -688,7 +1312,7 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
688
1312
|
const summary = workspaceManager.getSummary();
|
|
689
1313
|
const allowed = getAccessibleRepoNames(workspaceManager, getUser, authSvc);
|
|
690
1314
|
if (allowed) {
|
|
691
|
-
// Filter summary to accessible
|
|
1315
|
+
// Filter summary to accessible folders only
|
|
692
1316
|
const filteredByRepo = {};
|
|
693
1317
|
let filteredTotal = 0;
|
|
694
1318
|
for (const [repo, count] of Object.entries(summary.entitiesByRepo)) {
|
|
@@ -700,19 +1324,19 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
700
1324
|
return wrapToolResult({
|
|
701
1325
|
success: true,
|
|
702
1326
|
summary: {
|
|
703
|
-
|
|
1327
|
+
totalFolders: allowed.size,
|
|
704
1328
|
totalEntities: filteredTotal,
|
|
705
|
-
|
|
1329
|
+
entitiesByFolder: filteredByRepo,
|
|
706
1330
|
},
|
|
707
1331
|
});
|
|
708
1332
|
}
|
|
709
1333
|
return wrapToolResult({
|
|
710
1334
|
success: true,
|
|
711
1335
|
summary: {
|
|
712
|
-
|
|
1336
|
+
totalFolders: summary.totalRepos,
|
|
713
1337
|
totalEntities: summary.totalEntities,
|
|
714
1338
|
entitiesByType: summary.entitiesByType,
|
|
715
|
-
|
|
1339
|
+
entitiesByFolder: summary.entitiesByRepo,
|
|
716
1340
|
},
|
|
717
1341
|
});
|
|
718
1342
|
}
|
|
@@ -724,15 +1348,15 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
724
1348
|
}
|
|
725
1349
|
},
|
|
726
1350
|
},
|
|
727
|
-
// List
|
|
1351
|
+
// List folders
|
|
728
1352
|
{
|
|
729
1353
|
name: 'list_repos',
|
|
730
|
-
label: 'List
|
|
731
|
-
description: 'List all
|
|
1354
|
+
label: 'List Folders',
|
|
1355
|
+
description: 'List all folders in the workspace',
|
|
732
1356
|
parameters: T.Object({}),
|
|
733
1357
|
execute: async (toolCallId, params) => {
|
|
734
1358
|
try {
|
|
735
|
-
let repos = workspaceManager.
|
|
1359
|
+
let repos = workspaceManager.getContentRepoConfigs();
|
|
736
1360
|
const allowed = getAccessibleRepoNames(workspaceManager, getUser, authSvc);
|
|
737
1361
|
if (allowed) {
|
|
738
1362
|
repos = repos.filter(r => allowed.has(r.name));
|
|
@@ -740,12 +1364,12 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
740
1364
|
return wrapToolResult({
|
|
741
1365
|
success: true,
|
|
742
1366
|
count: repos.length,
|
|
743
|
-
|
|
1367
|
+
folders: repos.map(r => ({
|
|
744
1368
|
name: r.name,
|
|
745
1369
|
type: r.type,
|
|
746
1370
|
description: r.description,
|
|
747
1371
|
path: r.path,
|
|
748
|
-
|
|
1372
|
+
personal: r.personal || false,
|
|
749
1373
|
})),
|
|
750
1374
|
});
|
|
751
1375
|
}
|
|
@@ -757,40 +1381,35 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
757
1381
|
}
|
|
758
1382
|
},
|
|
759
1383
|
},
|
|
760
|
-
// Create a new
|
|
1384
|
+
// Create a new folder
|
|
761
1385
|
{
|
|
762
|
-
name: '
|
|
763
|
-
label: 'Create
|
|
764
|
-
description: 'Create a new
|
|
1386
|
+
name: 'create_folder',
|
|
1387
|
+
label: 'Create Folder',
|
|
1388
|
+
description: 'Create a new shared folder in the workspace. Folders are git-backed containers for entries. New folders are shared with teammates who have access; the creator is granted admin. Every user already has a personal "My Folder" folder auto-provisioned — do not use this tool to create another one.',
|
|
765
1389
|
parameters: T.Object({
|
|
766
|
-
name: T.String({ description: '
|
|
767
|
-
description: T.Optional(T.String({ description: 'Short description of the
|
|
768
|
-
private: T.Optional(T.Boolean({ description: 'If true, creates a private collection visible only to you' })),
|
|
1390
|
+
name: T.String({ description: 'Folder name in kebab-case (e.g., "client-projects")' }),
|
|
1391
|
+
description: T.Optional(T.String({ description: 'Short description of the folder' })),
|
|
769
1392
|
}),
|
|
770
1393
|
execute: async (toolCallId, params) => {
|
|
771
1394
|
try {
|
|
772
1395
|
const user = getUser();
|
|
773
|
-
// Non-private collections require admin; private collections can be created by anyone
|
|
774
|
-
if (!params.private && user && !isAdminOrOwner(user.role)) {
|
|
775
|
-
return wrapToolResult({ success: false, error: 'Only admins can create collections' });
|
|
776
|
-
}
|
|
777
1396
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(params.name)) {
|
|
778
|
-
return wrapToolResult({ success: false, error: '
|
|
1397
|
+
return wrapToolResult({ success: false, error: 'Folder name must be kebab-case (lowercase letters, numbers, hyphens)' });
|
|
779
1398
|
}
|
|
780
1399
|
const { Workspace } = await import('../../core/workspace.js');
|
|
781
1400
|
const workspace = new Workspace(workspacePath);
|
|
782
1401
|
const repo = await workspace.registerRepo({
|
|
783
1402
|
name: params.name,
|
|
784
1403
|
description: params.description,
|
|
785
|
-
|
|
786
|
-
...(params.private && user ? { owner_id: user.id } : {}),
|
|
1404
|
+
owner_id: user?.id,
|
|
787
1405
|
});
|
|
788
|
-
// Reload workspace manager to pick up the new collection
|
|
789
1406
|
workspaceManager.reloadConfig();
|
|
790
|
-
|
|
1407
|
+
if (user && authSvc)
|
|
1408
|
+
authSvc.grantFolderAccess(user.id, repo.name, 'admin');
|
|
1409
|
+
onMutation?.({ action: 'created', repo: repo.name, entityType: '_folder', entityId: repo.name });
|
|
791
1410
|
return wrapToolResult({
|
|
792
1411
|
success: true,
|
|
793
|
-
|
|
1412
|
+
folder: { name: repo.name, path: repo.path, description: repo.description },
|
|
794
1413
|
});
|
|
795
1414
|
}
|
|
796
1415
|
catch (error) {
|
|
@@ -804,7 +1423,7 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
804
1423
|
label: 'Query Dataset',
|
|
805
1424
|
description: 'Query rows from a dataset entity with optional filtering',
|
|
806
1425
|
parameters: T.Object({
|
|
807
|
-
repo: T.String({ description: '
|
|
1426
|
+
repo: T.String({ description: 'Folder name' }),
|
|
808
1427
|
entityId: T.String({ description: 'Dataset entity ID' }),
|
|
809
1428
|
filter: T.Optional(T.Object({}, { description: 'Filter conditions (column: value or { gte, lte, contains })', additionalProperties: true })),
|
|
810
1429
|
limit: T.Optional(T.Number({ description: 'Max rows to return' })),
|
|
@@ -812,21 +1431,24 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
812
1431
|
execute: async (toolCallId, params) => {
|
|
813
1432
|
try {
|
|
814
1433
|
if (!canAccessRepo(params.repo, workspaceManager, getUser, authSvc)) {
|
|
815
|
-
return wrapToolResult({ success: false, error: `
|
|
1434
|
+
return wrapToolResult({ success: false, error: `Folder "${params.repo}" not found` });
|
|
816
1435
|
}
|
|
817
1436
|
const graph = workspaceManager.getGraph(params.repo);
|
|
818
|
-
|
|
819
|
-
|
|
1437
|
+
// Validates the entity exists and is a dataset before querying.
|
|
1438
|
+
graph.get('dataset', params.entityId);
|
|
820
1439
|
let rows = graph.getDatasetRows(params.entityId, params.filter);
|
|
821
1440
|
const total = rows.length;
|
|
822
1441
|
if (params.limit && params.limit > 0) {
|
|
823
1442
|
rows = rows.slice(0, params.limit);
|
|
824
1443
|
}
|
|
1444
|
+
// Columns from the header, not the result rows — so a zero-match
|
|
1445
|
+
// filter or header-only dataset still reports the real columns.
|
|
1446
|
+
const columns = graph.getDatasetColumns(params.entityId);
|
|
825
1447
|
return wrapToolResult({
|
|
826
1448
|
success: true,
|
|
827
1449
|
rows,
|
|
828
1450
|
total,
|
|
829
|
-
|
|
1451
|
+
columns,
|
|
830
1452
|
});
|
|
831
1453
|
}
|
|
832
1454
|
catch (error) {
|
|
@@ -843,7 +1465,7 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
843
1465
|
label: 'Append Rows',
|
|
844
1466
|
description: 'Append rows to a dataset entity',
|
|
845
1467
|
parameters: T.Object({
|
|
846
|
-
repo: T.String({ description: '
|
|
1468
|
+
repo: T.String({ description: 'Folder name' }),
|
|
847
1469
|
entityId: T.String({ description: 'Dataset entity ID' }),
|
|
848
1470
|
rows: T.Array(T.Object({}, { additionalProperties: true }), { description: 'Rows to append' }),
|
|
849
1471
|
commitMessage: T.Optional(T.String({ description: 'Custom commit message' })),
|
|
@@ -851,13 +1473,14 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
851
1473
|
execute: async (toolCallId, params) => {
|
|
852
1474
|
try {
|
|
853
1475
|
if (!canAccessRepo(params.repo, workspaceManager, getUser, authSvc)) {
|
|
854
|
-
return wrapToolResult({ success: false, error: `
|
|
1476
|
+
return wrapToolResult({ success: false, error: `Folder "${params.repo}" not found` });
|
|
855
1477
|
}
|
|
856
1478
|
const graph = workspaceManager.getGraph(params.repo);
|
|
857
1479
|
// Get current row count before appending
|
|
858
1480
|
const beforeRows = graph.getDatasetRows(params.entityId);
|
|
859
1481
|
await graph.appendDatasetRows(params.entityId, params.rows, params.commitMessage);
|
|
860
1482
|
const afterRows = graph.getDatasetRows(params.entityId);
|
|
1483
|
+
onMutation?.({ action: 'updated', repo: params.repo, entityType: 'dataset', entityId: params.entityId });
|
|
861
1484
|
return wrapToolResult({
|
|
862
1485
|
success: true,
|
|
863
1486
|
rowsAdded: afterRows.length - beforeRows.length,
|
|
@@ -877,30 +1500,22 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
877
1500
|
{
|
|
878
1501
|
name: 'get_entity_schema',
|
|
879
1502
|
label: 'Get Entry Schema',
|
|
880
|
-
description: 'Get the schema
|
|
1503
|
+
description: 'Get the authoritative schema for an entry type. Returns each field with its type, whether it is optional, the description from the Zod schema, the full enum value list when applicable, and any default. Call this before writing enum-shaped fields (stage, status, category, priority, etc.) — the validator only accepts the values listed under `enum`.',
|
|
881
1504
|
parameters: T.Object({
|
|
882
1505
|
entityType: T.String({ description: 'Entity type' }),
|
|
883
1506
|
}),
|
|
884
1507
|
execute: async (toolCallId, params) => {
|
|
885
1508
|
try {
|
|
886
|
-
const
|
|
887
|
-
|
|
1509
|
+
const registry = workspaceManager.getSchemaRegistry();
|
|
1510
|
+
const knownTypes = new Set(registry.getAllTypes());
|
|
1511
|
+
if (!knownTypes.has(params.entityType)) {
|
|
888
1512
|
return wrapToolResult({
|
|
889
1513
|
success: false,
|
|
890
|
-
error: `Unknown entity type: ${params.entityType}`,
|
|
1514
|
+
error: `Unknown entity type: ${params.entityType}. Known types: ${[...knownTypes].sort().join(', ')}`,
|
|
891
1515
|
});
|
|
892
1516
|
}
|
|
893
|
-
|
|
894
|
-
const
|
|
895
|
-
const fields = {};
|
|
896
|
-
for (const [key, value] of Object.entries(shape)) {
|
|
897
|
-
const field = value;
|
|
898
|
-
fields[key] = {
|
|
899
|
-
type: field._def.typeName || 'unknown',
|
|
900
|
-
optional: field.isOptional() || false,
|
|
901
|
-
description: field._def.description || '',
|
|
902
|
-
};
|
|
903
|
-
}
|
|
1517
|
+
const schema = registry.getSchema(params.entityType);
|
|
1518
|
+
const fields = describeZodObject(schema);
|
|
904
1519
|
return wrapToolResult({
|
|
905
1520
|
success: true,
|
|
906
1521
|
entityType: params.entityType,
|
|
@@ -919,15 +1534,15 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
919
1534
|
{
|
|
920
1535
|
name: 'lint_entities',
|
|
921
1536
|
label: 'Lint Entries',
|
|
922
|
-
description: 'Check for missing recommended fields across entries. Returns incomplete entries with their missing fields. Use to discover entries that need enrichment. Optionally filter by
|
|
1537
|
+
description: 'Check for missing recommended fields across entries. Returns incomplete entries with their missing fields. Use to discover entries that need enrichment. Optionally filter by folder or entry type.',
|
|
923
1538
|
parameters: T.Object({
|
|
924
|
-
repo: T.Optional(T.String({ description: 'Optional: limit to specific
|
|
1539
|
+
repo: T.Optional(T.String({ description: 'Optional: limit to specific folder' })),
|
|
925
1540
|
entityType: T.Optional(T.String({ description: 'Optional: limit to specific entity type (e.g. "project", "deal")' })),
|
|
926
1541
|
}),
|
|
927
1542
|
execute: async (toolCallId, params) => {
|
|
928
1543
|
try {
|
|
929
1544
|
if (params.repo && !canAccessRepo(params.repo, workspaceManager, getUser, authSvc)) {
|
|
930
|
-
return wrapToolResult({ success: false, error: `
|
|
1545
|
+
return wrapToolResult({ success: false, error: `Folder "${params.repo}" not found` });
|
|
931
1546
|
}
|
|
932
1547
|
let issues = await lintWorkspace(workspaceManager, {
|
|
933
1548
|
repo: params.repo,
|
|
@@ -977,11 +1592,11 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
977
1592
|
parameters: T.Object({
|
|
978
1593
|
entityTypes: T.Optional(T.Array(T.String(), { description: 'Entity types to check (e.g. ["person", "client"])' })),
|
|
979
1594
|
minAgeDays: T.Optional(T.Number({ description: 'Only flag entities older than N days' })),
|
|
980
|
-
repos: T.Optional(T.Array(T.String(), { description: '
|
|
1595
|
+
repos: T.Optional(T.Array(T.String(), { description: 'Folder names to scan (default: all)' })),
|
|
981
1596
|
}),
|
|
982
1597
|
execute: async (toolCallId, params) => {
|
|
983
1598
|
try {
|
|
984
|
-
// Scope to accessible
|
|
1599
|
+
// Scope to accessible folders
|
|
985
1600
|
const allowed = getAccessibleRepoNames(workspaceManager, getUser, authSvc);
|
|
986
1601
|
let repos = params.repos;
|
|
987
1602
|
if (allowed) {
|
|
@@ -1032,98 +1647,6 @@ export function createGraphTools(workspaceManager, gitUser, workspacePath, onHig
|
|
|
1032
1647
|
});
|
|
1033
1648
|
},
|
|
1034
1649
|
},
|
|
1035
|
-
// ── View Management Tools ──
|
|
1036
|
-
{
|
|
1037
|
-
name: 'list_views',
|
|
1038
|
-
label: 'List Views',
|
|
1039
|
-
description: 'List the current user\'s saved views.',
|
|
1040
|
-
parameters: T.Object({}),
|
|
1041
|
-
execute: async () => {
|
|
1042
|
-
if (!authSvc)
|
|
1043
|
-
return wrapToolResult({ success: false, error: 'Auth service not available' });
|
|
1044
|
-
const userId = getUser()?.id ?? 0;
|
|
1045
|
-
const views = authSvc.getUserViews(userId);
|
|
1046
|
-
return wrapToolResult({ views });
|
|
1047
|
-
},
|
|
1048
|
-
},
|
|
1049
|
-
{
|
|
1050
|
-
name: 'create_view',
|
|
1051
|
-
label: 'Create View',
|
|
1052
|
-
description: 'Create a new saved view for the current user. Views appear as tabs in the toolbar.',
|
|
1053
|
-
parameters: T.Object({
|
|
1054
|
-
name: T.String({ description: 'View name (e.g. "My Tasks", "Open Projects")' }),
|
|
1055
|
-
selectedType: T.Optional(T.String({ description: 'Entity type filter (e.g. "task", "project"). Omit for all types.' })),
|
|
1056
|
-
groupBy: T.Optional(T.String({ description: 'Column key to group by (e.g. "status", "priority")' })),
|
|
1057
|
-
}),
|
|
1058
|
-
execute: async (_toolCallId, params) => {
|
|
1059
|
-
if (!authSvc)
|
|
1060
|
-
return wrapToolResult({ success: false, error: 'Auth service not available' });
|
|
1061
|
-
const userId = getUser()?.id ?? 0;
|
|
1062
|
-
const config = {
|
|
1063
|
-
selectedType: params.selectedType ?? null,
|
|
1064
|
-
groupBy: params.groupBy ?? null,
|
|
1065
|
-
sortKey: 'name',
|
|
1066
|
-
sortAsc: true,
|
|
1067
|
-
filters: { text: {}, enum: {}, date: {} },
|
|
1068
|
-
};
|
|
1069
|
-
const view = authSvc.createUserView(userId, params.name, 'list', config);
|
|
1070
|
-
return wrapToolResult({
|
|
1071
|
-
success: true,
|
|
1072
|
-
view: { id: view.id, name: view.name, viewType: view.viewType, config: view.config },
|
|
1073
|
-
message: `Created view "${params.name}". It will appear as a tab in the toolbar.`,
|
|
1074
|
-
});
|
|
1075
|
-
},
|
|
1076
|
-
},
|
|
1077
|
-
{
|
|
1078
|
-
name: 'update_view',
|
|
1079
|
-
label: 'Update View',
|
|
1080
|
-
description: 'Update an existing saved view\'s name or configuration.',
|
|
1081
|
-
parameters: T.Object({
|
|
1082
|
-
id: T.Number({ description: 'View ID' }),
|
|
1083
|
-
name: T.Optional(T.String({ description: 'New view name' })),
|
|
1084
|
-
selectedType: T.Optional(T.String({ description: 'Entity type filter (use "all" to clear)' })),
|
|
1085
|
-
groupBy: T.Optional(T.String({ description: 'Column key to group by (use "none" to clear)' })),
|
|
1086
|
-
}),
|
|
1087
|
-
execute: async (_toolCallId, params) => {
|
|
1088
|
-
if (!authSvc)
|
|
1089
|
-
return wrapToolResult({ success: false, error: 'Auth service not available' });
|
|
1090
|
-
const userId = getUser()?.id ?? 0;
|
|
1091
|
-
const existing = authSvc.getUserView(params.id, userId);
|
|
1092
|
-
if (!existing)
|
|
1093
|
-
return wrapToolResult({ success: false, error: `View ${params.id} not found` });
|
|
1094
|
-
const fields = {};
|
|
1095
|
-
if (params.name)
|
|
1096
|
-
fields.name = params.name;
|
|
1097
|
-
if (params.selectedType !== undefined || params.groupBy !== undefined) {
|
|
1098
|
-
const config = { ...existing.config };
|
|
1099
|
-
if (params.selectedType !== undefined)
|
|
1100
|
-
config.selectedType = params.selectedType === 'all' ? null : params.selectedType;
|
|
1101
|
-
if (params.groupBy !== undefined)
|
|
1102
|
-
config.groupBy = params.groupBy === 'none' ? null : params.groupBy;
|
|
1103
|
-
fields.config = config;
|
|
1104
|
-
}
|
|
1105
|
-
authSvc.updateUserView(params.id, userId, fields);
|
|
1106
|
-
return wrapToolResult({ success: true, message: `Updated view "${existing.name}".` });
|
|
1107
|
-
},
|
|
1108
|
-
},
|
|
1109
|
-
{
|
|
1110
|
-
name: 'delete_view',
|
|
1111
|
-
label: 'Delete View',
|
|
1112
|
-
description: 'Delete a saved view.',
|
|
1113
|
-
parameters: T.Object({
|
|
1114
|
-
id: T.Number({ description: 'View ID to delete' }),
|
|
1115
|
-
}),
|
|
1116
|
-
execute: async (_toolCallId, params) => {
|
|
1117
|
-
if (!authSvc)
|
|
1118
|
-
return wrapToolResult({ success: false, error: 'Auth service not available' });
|
|
1119
|
-
const userId = getUser()?.id ?? 0;
|
|
1120
|
-
const existing = authSvc.getUserView(params.id, userId);
|
|
1121
|
-
if (!existing)
|
|
1122
|
-
return wrapToolResult({ success: false, error: `View ${params.id} not found` });
|
|
1123
|
-
authSvc.deleteUserView(params.id, userId);
|
|
1124
|
-
return wrapToolResult({ success: true, message: `Deleted view "${existing.name}".` });
|
|
1125
|
-
},
|
|
1126
|
-
},
|
|
1127
1650
|
// Mark onboarding complete
|
|
1128
1651
|
{
|
|
1129
1652
|
name: 'mark_onboarded',
|