kennel 0.2.1 → 0.2.2
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 +18 -5
- package/dist/cli/dispatch.js +9 -0
- package/dist/cli/src/automations/notifications.d.ts +40 -0
- package/dist/cli/src/automations/notifications.js +220 -0
- package/dist/cli/src/cli/dispatch.d.ts +1 -0
- package/dist/cli/src/cli/dispatch.js +125 -0
- package/dist/cli/src/cli/usage.d.ts +1 -0
- package/dist/cli/src/cli/usage.js +25 -0
- package/dist/cli/src/core/api-client.d.ts +13 -0
- package/dist/cli/src/core/api-client.js +1 -0
- package/dist/cli/src/core/command.d.ts +1 -0
- package/dist/cli/src/core/command.js +69 -0
- package/dist/cli/src/core/core-folders.d.ts +1 -0
- package/dist/cli/src/core/core-folders.js +42 -0
- package/dist/cli/src/core/init.d.ts +1 -0
- package/dist/cli/src/core/init.js +141 -0
- package/dist/cli/src/core/runtime.d.ts +14 -0
- package/dist/cli/src/core/runtime.js +1 -0
- package/dist/cli/src/deploy/cloudflare.d.ts +6 -0
- package/dist/cli/src/deploy/cloudflare.js +100 -0
- package/dist/cli/src/deploy/environment.d.ts +2 -0
- package/dist/cli/src/deploy/environment.js +47 -0
- package/dist/cli/src/deploy/smoke-tests.d.ts +4 -0
- package/dist/cli/src/deploy/smoke-tests.js +92 -0
- package/dist/cli/src/deploy/trigger-dev.d.ts +2 -0
- package/dist/cli/src/deploy/trigger-dev.js +255 -0
- package/dist/cli/src/deploy/vercel.d.ts +3 -0
- package/dist/cli/src/deploy/vercel.js +67 -0
- package/dist/cli/src/main.d.ts +1 -0
- package/dist/cli/src/main.js +9 -0
- package/dist/cli/src/mcp/agent-tools.d.ts +8 -0
- package/dist/cli/src/mcp/agent-tools.js +247 -0
- package/dist/cli/src/mcp/config.d.ts +25 -0
- package/dist/cli/src/mcp/config.js +185 -0
- package/dist/cli/src/mcp/function-source.d.ts +7 -0
- package/dist/cli/src/mcp/function-source.js +42 -0
- package/dist/cli/src/mcp/generate.d.ts +1 -0
- package/dist/cli/src/mcp/generate.js +47 -0
- package/dist/cli/src/mcp/install-page.d.ts +12 -0
- package/dist/cli/src/mcp/install-page.js +5 -0
- package/dist/cli/src/notifications/email-worker.d.ts +8 -0
- package/dist/cli/src/notifications/email-worker.js +137 -0
- package/dist/cli/src/package/mcp-data.d.ts +1 -0
- package/dist/cli/src/package/mcp-data.js +1 -0
- package/dist/cli/src/package/mcp-email-worker.d.ts +1 -0
- package/dist/cli/src/package/mcp-email-worker.js +1 -0
- package/dist/cli/src/package/mcp-http.d.ts +1 -0
- package/dist/cli/src/package/mcp-http.js +1 -0
- package/dist/cli/src/package/mcp-stdio.d.ts +1 -0
- package/dist/cli/src/package/mcp-stdio.js +1 -0
- package/dist/cli/src/package/mcp.d.ts +1 -0
- package/dist/cli/src/package/mcp.js +1 -0
- package/dist/cli/src/package/preview.d.ts +9 -0
- package/dist/cli/src/package/preview.js +1 -0
- package/dist/cli/src/plugins/external.d.ts +36 -0
- package/dist/cli/src/plugins/external.js +503 -0
- package/dist/cli/src/publish/cloudflare-init.d.ts +12 -0
- package/dist/cli/src/publish/cloudflare-init.js +101 -0
- package/dist/cli/src/publish/config.d.ts +28 -0
- package/dist/cli/src/publish/config.js +178 -0
- package/dist/cli/src/publish/options.d.ts +13 -0
- package/dist/cli/src/publish/options.js +78 -0
- package/dist/cli/src/publish/vercel-init.d.ts +9 -0
- package/dist/cli/src/publish/vercel-init.js +52 -0
- package/dist/cli/src/shared/json.d.ts +4 -0
- package/dist/cli/src/shared/json.js +56 -0
- package/dist/cli/src/shared/object.d.ts +2 -0
- package/dist/cli/src/shared/object.js +3 -0
- package/dist/cli/src/shared/package-manager.d.ts +11 -0
- package/dist/cli/src/shared/package-manager.js +39 -0
- package/dist/cli/src/shared/path.d.ts +4 -0
- package/dist/cli/src/shared/path.js +40 -0
- package/dist/cli/src/shared/process.d.ts +7 -0
- package/dist/cli/src/shared/process.js +12 -0
- package/dist/cli/src/shared/project-config.d.ts +2 -0
- package/dist/cli/src/shared/project-config.js +26 -0
- package/dist/cli/src/site/dev.d.ts +10 -0
- package/dist/cli/src/site/dev.js +126 -0
- package/dist/cli/src/site/extension.d.ts +8 -0
- package/dist/cli/src/site/extension.js +113 -0
- package/dist/cli/src/site/folder-icons.d.ts +46 -0
- package/dist/cli/src/site/folder-icons.js +86 -0
- package/dist/cli/src/site/hot-viewer.d.ts +2 -0
- package/dist/cli/src/site/hot-viewer.js +231 -0
- package/dist/cli/src/site/kinds.d.ts +1 -0
- package/dist/cli/src/site/kinds.js +1 -0
- package/dist/cli/src/site/mail.d.ts +1 -0
- package/dist/cli/src/site/mail.js +10 -0
- package/dist/cli/src/site/renderers.d.ts +61 -0
- package/dist/cli/src/site/renderers.js +219 -0
- package/dist/cli/src/site/server-plugins.d.ts +54 -0
- package/dist/cli/src/site/server-plugins.js +1 -0
- package/dist/cli/src/site/viewer.d.ts +36 -0
- package/dist/cli/src/site/viewer.js +352 -0
- package/dist/cli/src/site/vitepress.d.ts +1 -0
- package/dist/cli/src/site/vitepress.js +1 -0
- package/dist/cli/usage.js +1 -0
- package/dist/core/api-client.js +75 -44
- package/dist/core/command.js +6 -3
- package/dist/core/core-folders.js +38280 -33
- package/dist/core/init.js +59 -4
- package/dist/core/runtime.js +57163 -41853
- package/dist/package/preview.js +40589 -17894
- package/dist/plugins/external.d.ts +36 -0
- package/dist/plugins/external.js +503 -0
- package/dist/site/server-plugins.js +394 -59
- package/dist/site/viewer.js +18 -5
- package/kernel/apps/web/.env.example +11 -0
- package/kernel/apps/web/components.json +20 -0
- package/kernel/apps/web/eslint.config.mjs +60 -0
- package/kernel/apps/web/index.html +17 -0
- package/kernel/apps/web/package.json +79 -0
- package/kernel/apps/web/public/favicon.svg +6 -0
- package/kernel/apps/web/public/providers/claude.svg +1 -0
- package/kernel/apps/web/public/providers/codex.svg +1 -0
- package/kernel/apps/web/public/providers/cursor.svg +1 -0
- package/kernel/apps/web/public/providers/opencode.svg +1 -0
- package/kernel/apps/web/public/providers/pi.svg +1 -0
- package/kernel/apps/web/server/acp/index.ts +1 -0
- package/kernel/apps/web/server/acp/live-agent-connection.ts +1 -0
- package/kernel/apps/web/server/acp/process-manager.ts +1 -0
- package/kernel/apps/web/server/acp/registry.ts +1 -0
- package/kernel/apps/web/server/acp/sdk.ts +1 -0
- package/kernel/apps/web/server/acp/transport.ts +1 -0
- package/kernel/apps/web/server/acp/types.ts +1 -0
- package/kernel/apps/web/server/acp-live-plugin.ts +30 -0
- package/kernel/apps/web/server/api/document-edit.ts +1 -0
- package/kernel/apps/web/server/api/open-path.ts +1 -0
- package/kernel/apps/web/server/core-plugin.ts +49 -0
- package/kernel/apps/web/server/folder-favicon.ts +1 -0
- package/kernel/apps/web/server/folder-icons.ts +1 -0
- package/kernel/apps/web/server/folder-metadata-plugin.ts +31 -0
- package/kernel/apps/web/server/plugins.ts +1 -0
- package/kernel/apps/web/server/preview-kinds.ts +1 -0
- package/kernel/apps/web/src/ai-input.tsx +62 -0
- package/kernel/apps/web/src/annotations.tsx +430 -0
- package/kernel/apps/web/src/api/client.ts +3 -0
- package/kernel/apps/web/src/api/errors.ts +1 -0
- package/kernel/apps/web/src/api/events.ts +42 -0
- package/kernel/apps/web/src/app.css +332 -0
- package/kernel/apps/web/src/app.tsx +667 -0
- package/kernel/apps/web/src/ask-draft.ts +164 -0
- package/kernel/apps/web/src/ask-tray.tsx +256 -0
- package/kernel/apps/web/src/comment-editor.tsx +93 -0
- package/kernel/apps/web/src/comment-form.tsx +162 -0
- package/kernel/apps/web/src/copy-path.ts +18 -0
- package/kernel/apps/web/src/deferred-ask-tray.tsx +20 -0
- package/kernel/apps/web/src/document-url.ts +46 -0
- package/kernel/apps/web/src/documents-url.ts +23 -0
- package/kernel/apps/web/src/dropped-folder.ts +64 -0
- package/kernel/apps/web/src/external-plugins.ts +3 -0
- package/kernel/apps/web/src/file-viewer.tsx +171 -0
- package/kernel/apps/web/src/folder-filter-icons.tsx +39 -0
- package/kernel/apps/web/src/folder-metadata.ts +76 -0
- package/kernel/apps/web/src/folder-plugin-view.tsx +87 -0
- package/kernel/apps/web/src/folder-query-cache.ts +66 -0
- package/kernel/apps/web/src/host-adapters.ts +209 -0
- package/kernel/apps/web/src/host-bridge.ts +214 -0
- package/kernel/apps/web/src/lib/entity-folder.ts +2 -0
- package/kernel/apps/web/src/linked-session-header.tsx +43 -0
- package/kernel/apps/web/src/live-session-controls.tsx +326 -0
- package/kernel/apps/web/src/local-source.ts +32 -0
- package/kernel/apps/web/src/main.tsx +24 -0
- package/kernel/apps/web/src/markdown-page.tsx +444 -0
- package/kernel/apps/web/src/notes-page.tsx +103 -0
- package/kernel/apps/web/src/plugins.ts +53 -0
- package/kernel/apps/web/src/project-search-dialog.tsx +257 -0
- package/kernel/apps/web/src/project-search.ts +18 -0
- package/kernel/apps/web/src/query-client.ts +4 -0
- package/kernel/apps/web/src/rehype-source-lines.ts +22 -0
- package/kernel/apps/web/src/routes/document.tsx +1 -0
- package/kernel/apps/web/src/routes/entities.tsx +277 -0
- package/kernel/apps/web/src/routes/search.tsx +60 -0
- package/kernel/apps/web/src/selection-context.ts +68 -0
- package/kernel/apps/web/src/selection-input.tsx +58 -0
- package/kernel/apps/web/src/selection-popup.tsx +155 -0
- package/kernel/apps/web/src/session-transcript-page.tsx +152 -0
- package/kernel/apps/web/src/use-annotations.ts +162 -0
- package/kernel/apps/web/src/use-ask-ai.ts +26 -0
- package/kernel/apps/web/src/use-ask-draft.ts +27 -0
- package/kernel/apps/web/src/use-document.ts +37 -0
- package/kernel/apps/web/src/use-folder-notes.ts +49 -0
- package/kernel/apps/web/src/use-linked-session.ts +33 -0
- package/kernel/apps/web/src/view-context.tsx +69 -0
- package/kernel/apps/web/src/view-header.tsx +66 -0
- package/kernel/apps/web/src/viewer-capabilities.ts +33 -0
- package/kernel/apps/web/src/viewer-route.ts +1 -0
- package/kernel/apps/web/src/viewport-height.ts +33 -0
- package/kernel/apps/web/tsconfig.json +24 -0
- package/kernel/apps/web/vite.config.ts +252 -0
- package/kernel/packages/agents/eslint.config.js +10 -0
- package/kernel/packages/agents/package.json +75 -0
- package/kernel/packages/agents/src/agent-card.tsx +334 -0
- package/kernel/packages/agents/src/client.tsx +13 -0
- package/kernel/packages/agents/src/index.ts +18 -0
- package/kernel/packages/agents/src/manifest.ts +7 -0
- package/kernel/packages/agents/src/node.ts +447 -0
- package/kernel/packages/agents/src/profiles.ts +137 -0
- package/kernel/packages/agents/src/resolve.ts +69 -0
- package/kernel/packages/agents/src/server.ts +60 -0
- package/kernel/packages/agents/src/types.ts +82 -0
- package/kernel/packages/agents/src/viewer.tsx +370 -0
- package/kernel/packages/agents/test/profiles.test.mjs +88 -0
- package/kernel/packages/agents/test/resolve.test.mjs +729 -0
- package/kernel/packages/agents/tsconfig.json +17 -0
- package/kernel/packages/api/README.md +73 -0
- package/kernel/packages/api/openapi.json +3368 -0
- package/kernel/packages/api/package.json +41 -0
- package/kernel/packages/api/src/client/errors.ts +1 -0
- package/kernel/packages/api/src/client/index.ts +2 -0
- package/kernel/packages/api/src/contracts/schemas.ts +277 -0
- package/kernel/packages/api/src/contracts.ts +20 -0
- package/kernel/packages/api/src/index.ts +34 -0
- package/kernel/packages/api/src/server/acp/index.ts +35 -0
- package/kernel/packages/api/src/server/acp/live-agent-connection.ts +44 -0
- package/kernel/packages/api/src/server/acp/process-manager.ts +291 -0
- package/kernel/packages/api/src/server/acp/registry.ts +140 -0
- package/kernel/packages/api/src/server/acp/sdk.ts +15 -0
- package/kernel/packages/api/src/server/acp/transport.ts +115 -0
- package/kernel/packages/api/src/server/acp/types.ts +89 -0
- package/kernel/packages/api/src/server/acp-live-plugin.ts +423 -0
- package/kernel/packages/api/src/server/api/create-agent-file.ts +46 -0
- package/kernel/packages/api/src/server/api/document-edit.ts +125 -0
- package/kernel/packages/api/src/server/api/open-path.ts +40 -0
- package/kernel/packages/api/src/server/contracts.ts +85 -0
- package/kernel/packages/api/src/server/entity-folder.ts +353 -0
- package/kernel/packages/api/src/server/folder-favicon.ts +17 -0
- package/kernel/packages/api/src/server/folder-icons.ts +113 -0
- package/kernel/packages/api/src/server/folder-metadata-plugin.ts +1758 -0
- package/kernel/packages/api/src/server/host.ts +21 -0
- package/kernel/packages/api/src/server/index.ts +197 -0
- package/kernel/packages/api/src/server/linked-folder-discovery.ts +158 -0
- package/kernel/packages/api/src/server/plugins.ts +205 -0
- package/kernel/packages/api/src/server/project-discovery.ts +69 -0
- package/kernel/packages/api/src/server/watcher.ts +46 -0
- package/kernel/packages/api/src/shared/selection.ts +16 -0
- package/kernel/packages/api/src/shared/viewer-route.ts +200 -0
- package/kernel/packages/api/test/agents-loading.test.ts +94 -0
- package/kernel/packages/api/test/core.test.ts +364 -0
- package/kernel/packages/api/test/entity-folder.test.ts +131 -0
- package/kernel/packages/api/test/linked-folder-discovery.test.ts +78 -0
- package/kernel/packages/api/test/media-endpoint.test.ts +83 -0
- package/kernel/packages/api/test/partial-loading.test.ts +43 -0
- package/kernel/packages/api/test/project-discovery.test.ts +44 -0
- package/kernel/packages/api/tsconfig.json +11 -0
- package/kernel/packages/api-client/README.md +72 -0
- package/kernel/packages/api-client/package.json +26 -0
- package/kernel/packages/api-client/scripts/generate.ts +68 -0
- package/kernel/packages/api-client/src/errors.ts +59 -0
- package/kernel/packages/api-client/src/generated.d.ts +1248 -0
- package/kernel/packages/api-client/src/index.ts +206 -0
- package/kernel/packages/api-client/src/responses.ts +19 -0
- package/kernel/packages/api-client/test/client.test.ts +143 -0
- package/kernel/packages/api-client/tsconfig.json +12 -0
- package/kernel/packages/folder-plugin/eslint.config.js +10 -0
- package/kernel/packages/folder-plugin/package.json +50 -0
- package/kernel/packages/folder-plugin/src/client.ts +73 -0
- package/kernel/packages/folder-plugin/src/endpoint.ts +273 -0
- package/kernel/packages/folder-plugin/src/index.ts +15 -0
- package/kernel/packages/folder-plugin/src/server.ts +78 -0
- package/kernel/packages/folder-plugin/src/subpath.ts +34 -0
- package/kernel/packages/folder-plugin/src/types.ts +86 -0
- package/kernel/packages/folder-plugin/src/validation.ts +56 -0
- package/kernel/packages/folder-plugin/test/contract.test.mjs +133 -0
- package/kernel/packages/folder-plugin/tsconfig.json +17 -0
- package/kernel/packages/kinds/package.json +24 -0
- package/kernel/packages/kinds/src/kinds.ts +172 -0
- package/kernel/packages/markdown/README.md +40 -0
- package/kernel/packages/markdown/explorer.css +1 -0
- package/kernel/packages/markdown/package.json +30 -0
- package/kernel/packages/markdown/src/documents.ts +1 -0
- package/kernel/packages/markdown/src/explorer.ts +1 -0
- package/kernel/packages/markdown/src/index.ts +2 -0
- package/kernel/packages/markdown/src/metadata.ts +63 -0
- package/kernel/packages/markdown/src/node.ts +1 -0
- package/kernel/packages/markdown/styles.css +1 -0
- package/kernel/packages/notes/SKILL.md +19 -0
- package/kernel/packages/notes/eslint.config.js +10 -0
- package/kernel/packages/notes/package.json +72 -0
- package/kernel/packages/notes/src/client.tsx +12 -0
- package/kernel/packages/notes/src/data/ai-input.ts +58 -0
- package/kernel/packages/notes/src/data/api.ts +40 -0
- package/kernel/packages/notes/src/data/parse.ts +15 -0
- package/kernel/packages/notes/src/data/routing.ts +33 -0
- package/kernel/packages/notes/src/data/use-folder-notes.ts +49 -0
- package/kernel/packages/notes/src/index.ts +2 -0
- package/kernel/packages/notes/src/manifest.ts +6 -0
- package/kernel/packages/notes/src/pages/notes-page.tsx +79 -0
- package/kernel/packages/notes/src/pages/single-note-page.tsx +57 -0
- package/kernel/packages/notes/src/server.ts +88 -0
- package/kernel/packages/notes/src/ui/ai-composer-controls.tsx +70 -0
- package/kernel/packages/notes/src/ui/folder-notes-list.tsx +140 -0
- package/kernel/packages/notes/tsconfig.json +16 -0
- package/kernel/packages/schemas/ICON-POLICY.md +43 -0
- package/kernel/packages/schemas/SKILL.md +185 -0
- package/kernel/packages/schemas/eslint.config.js +10 -0
- package/kernel/packages/schemas/package.json +81 -0
- package/kernel/packages/schemas/src/client.tsx +137 -0
- package/kernel/packages/schemas/src/data/config-file.ts +37 -0
- package/kernel/packages/schemas/src/data/contract.ts +174 -0
- package/kernel/packages/schemas/src/data/favicon.ts +218 -0
- package/kernel/packages/schemas/src/data/fields.ts +96 -0
- package/kernel/packages/schemas/src/data/icon-image.ts +53 -0
- package/kernel/packages/schemas/src/data/icon.ts +97 -0
- package/kernel/packages/schemas/src/data/load.ts +161 -0
- package/kernel/packages/schemas/src/data/params.ts +103 -0
- package/kernel/packages/schemas/src/data/query.ts +33 -0
- package/kernel/packages/schemas/src/data/sort.ts +50 -0
- package/kernel/packages/schemas/src/data/workspace.ts +101 -0
- package/kernel/packages/schemas/src/index.ts +22 -0
- package/kernel/packages/schemas/src/manifest.ts +4 -0
- package/kernel/packages/schemas/src/node.ts +384 -0
- package/kernel/packages/schemas/src/pages/collection.tsx +252 -0
- package/kernel/packages/schemas/src/server.ts +208 -0
- package/kernel/packages/schemas/src/ui/schema-entity-list.tsx +124 -0
- package/kernel/packages/schemas/src/ui/schema-filters.tsx +186 -0
- package/kernel/packages/schemas/src/ui/schema-search.tsx +35 -0
- package/kernel/packages/schemas/src/ui/use-query-param-filter-setting.tsx +15 -0
- package/kernel/packages/schemas/test/contract.test.mjs +26 -0
- package/kernel/packages/schemas/test/fields.test.mjs +187 -0
- package/kernel/packages/schemas/test/load.test.mjs +12 -0
- package/kernel/packages/schemas/test/node.test.mjs +412 -0
- package/kernel/packages/schemas/test/query.test.mjs +134 -0
- package/kernel/packages/schemas/test/workspace.test.mjs +86 -0
- package/kernel/packages/schemas/tsconfig.json +17 -0
- package/kernel/packages/session-finding/package.json +33 -0
- package/kernel/packages/session-finding/src/index.ts +120 -0
- package/kernel/packages/session-finding/test/list-sessions.test.mjs +139 -0
- package/kernel/packages/session-finding/tsconfig.json +12 -0
- package/kernel/packages/ui/LICENSE +21 -0
- package/kernel/packages/ui/components.json +20 -0
- package/kernel/packages/ui/eslint.config.js +8 -0
- package/kernel/packages/ui/package.json +106 -0
- package/kernel/packages/ui/src/chat/file-link.ts +178 -0
- package/kernel/packages/ui/src/chat/index.ts +18 -0
- package/kernel/packages/ui/src/chat/transcript.tsx +348 -0
- package/kernel/packages/ui/src/chat/turns.ts +76 -0
- package/kernel/packages/ui/src/chat/types.ts +23 -0
- package/kernel/packages/ui/src/chat/use-overflowing.ts +56 -0
- package/kernel/packages/ui/src/components/accordion.tsx +74 -0
- package/kernel/packages/ui/src/components/alert-dialog.tsx +164 -0
- package/kernel/packages/ui/src/components/alert.tsx +68 -0
- package/kernel/packages/ui/src/components/app-breadcrumbs.tsx +170 -0
- package/kernel/packages/ui/src/components/app-header.tsx +125 -0
- package/kernel/packages/ui/src/components/app-options.tsx +87 -0
- package/kernel/packages/ui/src/components/app-view-header.tsx +34 -0
- package/kernel/packages/ui/src/components/aspect-ratio.tsx +11 -0
- package/kernel/packages/ui/src/components/avatar.tsx +53 -0
- package/kernel/packages/ui/src/components/badge.tsx +47 -0
- package/kernel/packages/ui/src/components/base-dialog.tsx +3 -0
- package/kernel/packages/ui/src/components/base-tabs.tsx +63 -0
- package/kernel/packages/ui/src/components/breadcrumb.tsx +110 -0
- package/kernel/packages/ui/src/components/button-group.tsx +86 -0
- package/kernel/packages/ui/src/components/button.tsx +65 -0
- package/kernel/packages/ui/src/components/calendar.tsx +240 -0
- package/kernel/packages/ui/src/components/card.tsx +95 -0
- package/kernel/packages/ui/src/components/carousel.tsx +240 -0
- package/kernel/packages/ui/src/components/chart.tsx +375 -0
- package/kernel/packages/ui/src/components/checkbox.tsx +39 -0
- package/kernel/packages/ui/src/components/collapsible.tsx +33 -0
- package/kernel/packages/ui/src/components/collection-icon.tsx +28 -0
- package/kernel/packages/ui/src/components/command.tsx +205 -0
- package/kernel/packages/ui/src/components/content-issues.tsx +63 -0
- package/kernel/packages/ui/src/components/context-menu.tsx +298 -0
- package/kernel/packages/ui/src/components/copy-context.tsx +134 -0
- package/kernel/packages/ui/src/components/dialog.tsx +157 -0
- package/kernel/packages/ui/src/components/drawer.tsx +162 -0
- package/kernel/packages/ui/src/components/dropdown-menu.tsx +303 -0
- package/kernel/packages/ui/src/components/empty.tsx +108 -0
- package/kernel/packages/ui/src/components/field.tsx +260 -0
- package/kernel/packages/ui/src/components/file-preview.tsx +152 -0
- package/kernel/packages/ui/src/components/file-renderer.tsx +466 -0
- package/kernel/packages/ui/src/components/filter-chip-panel.tsx +157 -0
- package/kernel/packages/ui/src/components/filter-select.tsx +127 -0
- package/kernel/packages/ui/src/components/folder-activity.tsx +646 -0
- package/kernel/packages/ui/src/components/folder-file-list.tsx +58 -0
- package/kernel/packages/ui/src/components/folder-filter-bar.tsx +191 -0
- package/kernel/packages/ui/src/components/folder-image.tsx +18 -0
- package/kernel/packages/ui/src/components/folder-landing.tsx +70 -0
- package/kernel/packages/ui/src/components/folder-list.tsx +140 -0
- package/kernel/packages/ui/src/components/folder-metadata.tsx +107 -0
- package/kernel/packages/ui/src/components/folder-mobile-bar.tsx +60 -0
- package/kernel/packages/ui/src/components/folder-project-list.tsx +133 -0
- package/kernel/packages/ui/src/components/folder-session-list.tsx +113 -0
- package/kernel/packages/ui/src/components/folder-session-page.tsx +126 -0
- package/kernel/packages/ui/src/components/folder-sidebar.tsx +52 -0
- package/kernel/packages/ui/src/components/folder-skill-list.tsx +62 -0
- package/kernel/packages/ui/src/components/folder-tabs.tsx +156 -0
- package/kernel/packages/ui/src/components/form.tsx +167 -0
- package/kernel/packages/ui/src/components/hover-card.tsx +52 -0
- package/kernel/packages/ui/src/components/icons.tsx +34 -0
- package/kernel/packages/ui/src/components/input-group.tsx +184 -0
- package/kernel/packages/ui/src/components/input-otp.tsx +90 -0
- package/kernel/packages/ui/src/components/input.tsx +29 -0
- package/kernel/packages/ui/src/components/item.tsx +195 -0
- package/kernel/packages/ui/src/components/kbd.tsx +32 -0
- package/kernel/packages/ui/src/components/label.tsx +27 -0
- package/kernel/packages/ui/src/components/link.tsx +45 -0
- package/kernel/packages/ui/src/components/live-tool-call.tsx +193 -0
- package/kernel/packages/ui/src/components/menubar.tsx +323 -0
- package/kernel/packages/ui/src/components/navigation-menu.tsx +210 -0
- package/kernel/packages/ui/src/components/note-card.tsx +149 -0
- package/kernel/packages/ui/src/components/pagination.tsx +128 -0
- package/kernel/packages/ui/src/components/popover.tsx +56 -0
- package/kernel/packages/ui/src/components/progress.tsx +31 -0
- package/kernel/packages/ui/src/components/radio-group.tsx +54 -0
- package/kernel/packages/ui/src/components/scroll-area.tsx +61 -0
- package/kernel/packages/ui/src/components/select.tsx +219 -0
- package/kernel/packages/ui/src/components/separator.tsx +30 -0
- package/kernel/packages/ui/src/components/session-icon.tsx +124 -0
- package/kernel/packages/ui/src/components/sheet.tsx +157 -0
- package/kernel/packages/ui/src/components/sidebar.tsx +789 -0
- package/kernel/packages/ui/src/components/skeleton.tsx +13 -0
- package/kernel/packages/ui/src/components/slider.tsx +73 -0
- package/kernel/packages/ui/src/components/sonner.tsx +41 -0
- package/kernel/packages/ui/src/components/spinner.tsx +16 -0
- package/kernel/packages/ui/src/components/switch.tsx +40 -0
- package/kernel/packages/ui/src/components/table.tsx +120 -0
- package/kernel/packages/ui/src/components/tabs.tsx +78 -0
- package/kernel/packages/ui/src/components/textarea.tsx +25 -0
- package/kernel/packages/ui/src/components/toggle-group.tsx +87 -0
- package/kernel/packages/ui/src/components/toggle.tsx +48 -0
- package/kernel/packages/ui/src/components/tooltip.tsx +71 -0
- package/kernel/packages/ui/src/components/viewer-options.tsx +166 -0
- package/kernel/packages/ui/src/hooks/use-folder-filter.ts +103 -0
- package/kernel/packages/ui/src/hooks/use-mobile.ts +19 -0
- package/kernel/packages/ui/src/index.ts +13 -0
- package/kernel/packages/ui/src/lib/document-extensions.ts +83 -0
- package/kernel/packages/ui/src/lib/folder-activity.ts +143 -0
- package/kernel/packages/ui/src/lib/folder-filter.ts +284 -0
- package/kernel/packages/ui/src/lib/live-tool-call.tsx +109 -0
- package/kernel/packages/ui/src/lib/media-urls.ts +176 -0
- package/kernel/packages/ui/src/lib/obsidian-links.ts +120 -0
- package/kernel/packages/ui/src/lib/source-components.tsx +65 -0
- package/kernel/packages/ui/src/lib/utils.tsx +7 -0
- package/kernel/packages/ui/src/pages/entity-detail-page.tsx +82 -0
- package/kernel/packages/ui/src/pages/entity-list-page.tsx +50 -0
- package/kernel/packages/ui/src/pages/entity-page.tsx +69 -0
- package/kernel/packages/ui/src/styles/folder-activity.css +2151 -0
- package/kernel/packages/ui/test/copy-context.test.mjs +48 -0
- package/kernel/packages/ui/test/folder-filter.test.mjs +484 -0
- package/kernel/packages/ui/tsconfig.json +15 -0
- package/kernel/plugins/calendar/SKILL.md +173 -0
- package/kernel/plugins/calendar/eslint.config.js +10 -0
- package/kernel/plugins/calendar/package.json +73 -0
- package/kernel/plugins/calendar/schema.json +137 -0
- package/kernel/plugins/calendar/src/calendar.tsx +328 -0
- package/kernel/plugins/calendar/src/client.tsx +139 -0
- package/kernel/plugins/calendar/src/context.ts +48 -0
- package/kernel/plugins/calendar/src/copy-button.tsx +66 -0
- package/kernel/plugins/calendar/src/event-dialog.tsx +270 -0
- package/kernel/plugins/calendar/src/index.ts +4 -0
- package/kernel/plugins/calendar/src/manifest.ts +7 -0
- package/kernel/plugins/calendar/src/parse.ts +610 -0
- package/kernel/plugins/calendar/src/server.ts +160 -0
- package/kernel/plugins/calendar/src/utils.ts +80 -0
- package/kernel/plugins/calendar/src/views.tsx +418 -0
- package/kernel/plugins/calendar/test/context.test.mjs +60 -0
- package/kernel/plugins/calendar/test/package.test.mjs +43 -0
- package/kernel/plugins/calendar/test/parse.test.mjs +392 -0
- package/kernel/plugins/calendar/test/server.test.mjs +126 -0
- package/kernel/plugins/calendar/tsconfig.json +17 -0
- package/kernel/plugins/mail/SKILL.md +97 -0
- package/kernel/plugins/mail/eslint.config.js +10 -0
- package/kernel/plugins/mail/package.json +79 -0
- package/kernel/plugins/mail/src/api.ts +359 -0
- package/kernel/plugins/mail/src/bun-ffi.d.ts +14 -0
- package/kernel/plugins/mail/src/client.tsx +14 -0
- package/kernel/plugins/mail/src/flock.ts +137 -0
- package/kernel/plugins/mail/src/folders.ts +37 -0
- package/kernel/plugins/mail/src/history.ts +485 -0
- package/kernel/plugins/mail/src/index.ts +3 -0
- package/kernel/plugins/mail/src/mail-card.tsx +123 -0
- package/kernel/plugins/mail/src/mail-conversation.tsx +153 -0
- package/kernel/plugins/mail/src/mail-detail-page.tsx +467 -0
- package/kernel/plugins/mail/src/mail-hooks.ts +252 -0
- package/kernel/plugins/mail/src/mail-page.tsx +109 -0
- package/kernel/plugins/mail/src/mail-request.ts +78 -0
- package/kernel/plugins/mail/src/mail-send-box.tsx +175 -0
- package/kernel/plugins/mail/src/mailos.ts +723 -0
- package/kernel/plugins/mail/src/manifest.ts +7 -0
- package/kernel/plugins/mail/src/node.ts +171 -0
- package/kernel/plugins/mail/src/parse.ts +139 -0
- package/kernel/plugins/mail/src/server.ts +125 -0
- package/kernel/plugins/mail/src/types.ts +10 -0
- package/kernel/plugins/mail/src/viewer.tsx +147 -0
- package/kernel/plugins/mail/test/flock.test.mjs +156 -0
- package/kernel/plugins/mail/test/folders.test.mjs +138 -0
- package/kernel/plugins/mail/test/history.test.mjs +460 -0
- package/kernel/plugins/mail/test/mailos.test.mjs +752 -0
- package/kernel/plugins/mail/test/node.test.mjs +207 -0
- package/kernel/plugins/mail/test/package.test.mjs +54 -0
- package/kernel/plugins/mail/test/parse.test.mjs +32 -0
- package/kernel/plugins/mail/test/server.test.mjs +116 -0
- package/kernel/plugins/mail/test/viewer.test.mjs +192 -0
- package/kernel/plugins/mail/tsconfig.json +17 -0
- package/kernel/plugins/projects/SKILL.md +73 -0
- package/kernel/plugins/projects/eslint.config.js +10 -0
- package/kernel/plugins/projects/package.json +70 -0
- package/kernel/plugins/projects/src/client.tsx +17 -0
- package/kernel/plugins/projects/src/data/model.ts +129 -0
- package/kernel/plugins/projects/src/index.ts +17 -0
- package/kernel/plugins/projects/src/manifest.ts +7 -0
- package/kernel/plugins/projects/src/project-context.ts +41 -0
- package/kernel/plugins/projects/src/server.ts +276 -0
- package/kernel/plugins/projects/src/ui/project-card.tsx +61 -0
- package/kernel/plugins/projects/src/ui/projects-page.tsx +102 -0
- package/kernel/plugins/projects/test/package.test.mjs +51 -0
- package/kernel/plugins/projects/tsconfig.json +17 -0
- package/kernel/plugins/tasks/SKILL.md +341 -0
- package/kernel/plugins/tasks/eslint.config.js +10 -0
- package/kernel/plugins/tasks/package.json +74 -0
- package/kernel/plugins/tasks/schema.json +939 -0
- package/kernel/plugins/tasks/src/api.ts +212 -0
- package/kernel/plugins/tasks/src/client.tsx +23 -0
- package/kernel/plugins/tasks/src/data/parse.ts +544 -0
- package/kernel/plugins/tasks/src/data/task-id.ts +56 -0
- package/kernel/plugins/tasks/src/data/todo-model.ts +795 -0
- package/kernel/plugins/tasks/src/index.ts +34 -0
- package/kernel/plugins/tasks/src/manifest.ts +7 -0
- package/kernel/plugins/tasks/src/pages/task-detail.tsx +48 -0
- package/kernel/plugins/tasks/src/pages/task-list.tsx +88 -0
- package/kernel/plugins/tasks/src/server.ts +447 -0
- package/kernel/plugins/tasks/src/styles.css +9 -0
- package/kernel/plugins/tasks/src/ui/compact-todo-list.tsx +372 -0
- package/kernel/plugins/tasks/src/ui/error-details.tsx +78 -0
- package/kernel/plugins/tasks/src/ui/task-page.tsx +392 -0
- package/kernel/plugins/tasks/src/ui/todo-agent-filter.tsx +36 -0
- package/kernel/plugins/tasks/src/ui/todo-filter-bar.tsx +66 -0
- package/kernel/plugins/tasks/src/ui/todo-item.tsx +494 -0
- package/kernel/plugins/tasks/src/ui/todo-status-filter.tsx +60 -0
- package/kernel/plugins/tasks/src/ui/todo-tag-filter.tsx +48 -0
- package/kernel/plugins/tasks/src/ui/todo-warnings.tsx +106 -0
- package/kernel/plugins/tasks/test/api.test.mjs +99 -0
- package/kernel/plugins/tasks/test/folder-sources.test.mjs +46 -0
- package/kernel/plugins/tasks/test/package.test.mjs +54 -0
- package/kernel/plugins/tasks/test/parse.test.mjs +225 -0
- package/kernel/plugins/tasks/test/server.test.mjs +386 -0
- package/kernel/plugins/tasks/tsconfig.json +17 -0
- package/node_modules/@agentclientprotocol/sdk/LICENSE +191 -0
- package/node_modules/@agentclientprotocol/sdk/README.md +70 -0
- package/node_modules/@agentclientprotocol/sdk/dist/acp.d.ts +1835 -0
- package/node_modules/@agentclientprotocol/sdk/dist/acp.js +1885 -0
- package/node_modules/@agentclientprotocol/sdk/dist/acp.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/acp.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/acp.test.js +5036 -0
- package/node_modules/@agentclientprotocol/sdk/dist/acp.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/connection.d.ts +82 -0
- package/node_modules/@agentclientprotocol/sdk/dist/connection.js +428 -0
- package/node_modules/@agentclientprotocol/sdk/dist/connection.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/connection.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/connection.test.js +470 -0
- package/node_modules/@agentclientprotocol/sdk/dist/connection.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.d.ts +26 -0
- package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.js +96 -0
- package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.test.js +77 -0
- package/node_modules/@agentclientprotocol/sdk/dist/cookie-store.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/agent.d.ts +2 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/agent.js +232 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/agent.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/client.d.ts +2 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/client.js +132 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/client.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/dual-version-agent.d.ts +9 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/dual-version-agent.js +221 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/dual-version-agent.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/http-client.d.ts +2 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/http-client.js +86 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/http-client.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/http-server.d.ts +2 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/http-server.js +112 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/http-server.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/ws-client.d.ts +2 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/ws-client.js +88 -0
- package/node_modules/@agentclientprotocol/sdk/dist/examples/ws-client.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/http-stream.d.ts +33 -0
- package/node_modules/@agentclientprotocol/sdk/dist/http-stream.js +435 -0
- package/node_modules/@agentclientprotocol/sdk/dist/http-stream.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/http-stream.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/http-stream.test.js +1380 -0
- package/node_modules/@agentclientprotocol/sdk/dist/http-stream.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.d.ts +603 -0
- package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js +1064 -0
- package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.test.js +851 -0
- package/node_modules/@agentclientprotocol/sdk/dist/jsonrpc.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.d.ts +22 -0
- package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.js +64 -0
- package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.test.js +45 -0
- package/node_modules/@agentclientprotocol/sdk/dist/line-buffer.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.d.ts +17 -0
- package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.js +426 -0
- package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.test.js +499 -0
- package/node_modules/@agentclientprotocol/sdk/dist/node-adapter.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.d.ts +37 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.js +474 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.test.js +535 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol-router.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol.d.ts +11 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol.js +57 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol.test.js +73 -0
- package/node_modules/@agentclientprotocol/sdk/dist/protocol.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.d.ts +199 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.js +211 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/guards.gen.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/index.d.ts +51 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/index.js +52 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/index.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.d.ts +6118 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.js +3 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/types.gen.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.d.ts +14160 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.js +3066 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema/zod.gen.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.d.ts +30 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.js +98 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.test.js +93 -0
- package/node_modules/@agentclientprotocol/sdk/dist/schema-deserialize.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-permission.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-permission.test.js +261 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-permission.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-session-sse.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-session-sse.test.js +490 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-session-sse.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-sse.d.ts +2 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-sse.js +104 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-sse.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-sse.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-sse.test.js +52 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-sse.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-websocket-upgrade.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-websocket-upgrade.test.js +946 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server-websocket-upgrade.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server.d.ts +81 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server.js +380 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server.test.js +1082 -0
- package/node_modules/@agentclientprotocol/sdk/dist/server.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/sse.d.ts +4 -0
- package/node_modules/@agentclientprotocol/sdk/dist/sse.js +91 -0
- package/node_modules/@agentclientprotocol/sdk/dist/sse.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/sse.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/sse.test.js +126 -0
- package/node_modules/@agentclientprotocol/sdk/dist/sse.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/stream.d.ts +35 -0
- package/node_modules/@agentclientprotocol/sdk/dist/stream.js +106 -0
- package/node_modules/@agentclientprotocol/sdk/dist/stream.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/stream.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/stream.test.js +206 -0
- package/node_modules/@agentclientprotocol/sdk/dist/stream.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/streams.d.ts +8 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/streams.js +51 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/streams.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-agent.d.ts +24 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-agent.js +163 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-agent.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-http-server.d.ts +9 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-http-server.js +74 -0
- package/node_modules/@agentclientprotocol/sdk/dist/test-support/test-http-server.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.d.ts +806 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.js +1183 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.test.js +512 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/acp.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/guards.gen.d.ts +954 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/guards.gen.js +1035 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/guards.gen.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/index.d.ts +42 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/index.js +43 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/index.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/types.gen.d.ts +6146 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/types.gen.js +3 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/types.gen.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/zod.gen.d.ts +30552 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/zod.gen.js +3204 -0
- package/node_modules/@agentclientprotocol/sdk/dist/v2/schema/zod.gen.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-server.d.ts +14 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-server.js +358 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-server.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.d.ts +54 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.js +246 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.test.js +833 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-stream.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.d.ts +13 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.js +78 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.test.d.ts +1 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.test.js +42 -0
- package/node_modules/@agentclientprotocol/sdk/dist/ws-utils.test.js.map +1 -0
- package/node_modules/@agentclientprotocol/sdk/package.json +105 -0
- package/node_modules/@agentclientprotocol/sdk/schema/schema.json +8819 -0
- package/node_modules/@agentclientprotocol/sdk/schema/v2/schema.unstable.json +10098 -0
- package/package.json +52 -6
- package/web/assets/{abnfDiagram-VRR7QNED-Nfq-O04D.js → abnfDiagram-VRR7QNED-Bgfb1nEB.js} +1 -1
- package/web/assets/app-DoGr2Zi7.js +2 -0
- package/web/assets/app-options-D-RJiQ-X.js +25 -0
- package/web/assets/app-view-header-BLvLq02T.js +1 -0
- package/web/assets/arc-Dk_KI36B.js +1 -0
- package/web/assets/{architectureDiagram-ZJ3FMSHR-qhRwPNcQ.js → architectureDiagram-ZJ3FMSHR-NR_KhRCf.js} +1 -1
- package/web/assets/ask-tray-TzeTcO6-.js +14 -0
- package/web/assets/{blockDiagram-677ZJIJ3-C56P3bGu.js → blockDiagram-677ZJIJ3-CkLkwx9C.js} +1 -1
- package/web/assets/button-C6arH7zt.js +1 -0
- package/web/assets/{c4Diagram-LMCZKHZV-Ey1M72zD.js → c4Diagram-LMCZKHZV-CFCUH8e_.js} +1 -1
- package/web/assets/channel-uMEjV8qc.js +1 -0
- package/web/assets/{chunk-2Q5K7J3B-a6isk9VU.js → chunk-2Q5K7J3B-B_BPLWsW.js} +1 -1
- package/web/assets/{chunk-32BRIVSS-uy4kdSLL.js → chunk-32BRIVSS-B1VB1hOy.js} +1 -1
- package/web/assets/{chunk-5VM5RSS4-oj2sBFd7.js → chunk-5VM5RSS4-KFcYr8Co.js} +1 -1
- package/web/assets/{chunk-EX3LRPZG-Bzl1ukAi.js → chunk-EX3LRPZG-CpBX3kuK.js} +3 -3
- package/web/assets/{chunk-JWPE2WC7-sUph7I_t.js → chunk-JWPE2WC7-BPSl40wG.js} +1 -1
- package/web/assets/{chunk-MOJQB5TN-C0ERSuVK.js → chunk-MOJQB5TN-D2RcYmoU.js} +1 -1
- package/web/assets/{chunk-RYQCIY6F-Dyw1Hfwp.js → chunk-RYQCIY6F-m7Gyn-t3.js} +1 -1
- package/web/assets/{chunk-V7JOEXUC-DnSz22rb.js → chunk-V7JOEXUC-TDvdopxS.js} +1 -1
- package/web/assets/{chunk-VR4S4FIN-Bp2SNel7.js → chunk-VR4S4FIN-DZTsG51a.js} +1 -1
- package/web/assets/{chunk-XXDRQBXY-Dqtar746.js → chunk-XXDRQBXY-Dp0--RE2.js} +1 -1
- package/web/assets/classDiagram-OUVF2IWQ-fbZocVor.js +1 -0
- package/web/assets/classDiagram-v2-EOCWNBFH-fbZocVor.js +1 -0
- package/web/assets/client-C6Nxdfif.js +2 -0
- package/web/assets/client-CXYfBko5.js +17 -0
- package/web/assets/client-CoKtOq-X.js +7 -0
- package/web/assets/client-DyRQz5RU.js +53 -0
- package/web/assets/client-OY6HUzuD.js +98 -0
- package/web/assets/client-YOYWZIFi.js +34 -0
- package/web/assets/client-j2kQmxsO.js +22 -0
- package/web/assets/command-BugZBGXE.js +38 -0
- package/web/assets/copy-context-C0Uqsp-3.js +1 -0
- package/web/assets/{cose-bilkent-JH36ORCC-Dx1VqMjj.js → cose-bilkent-JH36ORCC-CPu3PI8h.js} +1 -1
- package/web/assets/{cynefin-VYW2F7L2-SjhCWmIV.js → cynefin-VYW2F7L2-DWxftYAm.js} +1 -1
- package/web/assets/{cynefinDiagram-TSTJHNR4-Dvqz2594.js → cynefinDiagram-TSTJHNR4-DsmN2V-p.js} +1 -1
- package/web/assets/{dagre-VKFMJZFB-Ju2m2qXt.js → dagre-VKFMJZFB-COEbApyY.js} +1 -1
- package/web/assets/{diagram-FQU43EPY-DsHKTt0k.js → diagram-FQU43EPY-0O9WGWKB.js} +1 -1
- package/web/assets/{diagram-G47NLZAW-DWHAcLkJ.js → diagram-G47NLZAW-CutEdMVh.js} +1 -1
- package/web/assets/{diagram-NH7WQ7WH-uUMz46ZD.js → diagram-NH7WQ7WH-C9KZc3B2.js} +1 -1
- package/web/assets/{diagram-OA4YK3LP-BRjVzjdp.js → diagram-OA4YK3LP-D8kkPXbY.js} +1 -1
- package/web/assets/{diagram-WEI45ONY-Cb-FhBpQ.js → diagram-WEI45ONY-_YqiTaGP.js} +1 -1
- package/web/assets/document-Dgt5eZFj.js +19 -0
- package/web/assets/{ebnfDiagram-CCIWWBDH-IhhxX3IB.js → ebnfDiagram-CCIWWBDH-JO-zQh6g.js} +1 -1
- package/web/assets/{entities-DUnx74kH.js → entities-C3wz7FCx.js} +12 -12
- package/web/assets/{erDiagram-Q63AITRT-D1frgeWf.js → erDiagram-Q63AITRT-9UjQwJ4P.js} +1 -1
- package/web/assets/file-viewer-DZJ8nYnl.js +1 -0
- package/web/assets/{flowDiagram-23GEKE2U-BiPPM-FY.js → flowDiagram-23GEKE2U-Ca08tnYM.js} +5 -5
- package/web/assets/folder-image-BfGgxo7i.js +1 -0
- package/web/assets/folder-landing-qUKBCnG-.js +54 -0
- package/web/assets/folder-list-BfFjv7rR.js +1 -0
- package/web/assets/folder-metadata-DCw8xzHk.js +2 -0
- package/web/assets/folder-mobile-bar-Ubz4babf.js +1 -0
- package/web/assets/folder-plugin-view-DsWR1zmQ.js +6 -0
- package/web/assets/{ganttDiagram-NO4QXBWP-CmEOlHD7.js → ganttDiagram-NO4QXBWP-KnHzX5W3.js} +1 -1
- package/web/assets/{gitGraphDiagram-IHSO6WYX-tTjcpZpU.js → gitGraphDiagram-IHSO6WYX-Cm8woNtK.js} +1 -1
- package/web/assets/index-BaRnbYRw.js +1 -0
- package/web/assets/index-BexmLxIP.css +1 -0
- package/web/assets/index-Bp9DidgD.js +1 -0
- package/web/assets/{index-eL1k96k8.js → index-CCXE8zc9.js} +1 -1
- package/web/assets/index-CNyy98F8.js +2 -0
- package/web/assets/{index-DmrKjrlM.js → index-CXhBn-ar.js} +1 -1
- package/web/assets/index-DDirZMgx.js +1 -0
- package/web/assets/{infoDiagram-FWYZ7A6U-Ok2sdbc1.js → infoDiagram-FWYZ7A6U-yyqmUtIb.js} +1 -1
- package/web/assets/{ishikawaDiagram-FXEZZL3T-kPnM5psX.js → ishikawaDiagram-FXEZZL3T-BfvR_Zy4.js} +4 -4
- package/web/assets/{journeyDiagram-5HDEW3XC-BkoT0-45.js → journeyDiagram-5HDEW3XC-BKuw06HM.js} +1 -1
- package/web/assets/{kanban-definition-HUTT4EX6-xnm_eFoU.js → kanban-definition-HUTT4EX6-n0010rmQ.js} +8 -8
- package/web/assets/{linear-CLZpgLH-.js → linear-C0d0lT8O.js} +1 -1
- package/web/assets/linked-session-header-BkEweU0Z.js +3 -0
- package/web/assets/{markdown-njUwQKsR.js → markdown-Bw9RgUGR.js} +9 -9
- package/web/assets/{mermaid.core-Bvxj3RoN.js → mermaid.core-BDVbShyO.js} +6 -6
- package/web/assets/metadata-KV0dpp4G.js +1 -0
- package/web/assets/{mindmap-definition-LN4V7U3C-42Uoox79.js → mindmap-definition-LN4V7U3C--_0ks3GF.js} +1 -1
- package/web/assets/note-card-DBfX6gW1.js +6 -0
- package/web/assets/{pegDiagram-2B236MQR-B-GZCw3w.js → pegDiagram-2B236MQR-CBoNIpKR.js} +1 -1
- package/web/assets/{pieDiagram-ENE6RG2P-DRmmssIB.js → pieDiagram-ENE6RG2P-MA5tkYh5.js} +3 -3
- package/web/assets/popover-DBD7FF-W.js +9 -0
- package/web/assets/{project-search-dialog-B730oZ1S.js → project-search-dialog-DM1IkSZu.js} +6 -6
- package/web/assets/{quadrantDiagram-ABIIQ3AL-BfhVQ7V9.js → quadrantDiagram-ABIIQ3AL-CWT1ryqF.js} +1 -1
- package/web/assets/{railroadDiagram-RFXS5EU6-DxKx7XRp.js → railroadDiagram-RFXS5EU6-ZiLB5FGi.js} +1 -1
- package/web/assets/{react-DZzLHL-l.js → react-B9jn2M18.js} +2 -2
- package/web/assets/{requirementDiagram-TGXJPOKE-NUzj9fw1.js → requirementDiagram-TGXJPOKE-CQq9uneq.js} +1 -1
- package/web/assets/{sankeyDiagram-HTMAVEWB-Cy9OMnl-.js → sankeyDiagram-HTMAVEWB-qDG-0JqI.js} +1 -1
- package/web/assets/search-DNrQA5fF.js +2 -0
- package/web/assets/select-D8cqNgcp.js +30 -0
- package/web/assets/selection-popup-DmtTeRs2.js +7 -0
- package/web/assets/{sequenceDiagram-DBY2YBRQ-CBNE9mzn.js → sequenceDiagram-DBY2YBRQ-CiApurLO.js} +1 -1
- package/web/assets/{sizeCapture-X5ZJPWSS-S4HhfX0n.js → sizeCapture-X5ZJPWSS-DZSR5QXm.js} +1 -1
- package/web/assets/stateDiagram-2N3HPSRC-CjJJR9_z.js +1 -0
- package/web/assets/stateDiagram-v2-6OUMAXLB-BpeGSCu9.js +1 -0
- package/web/assets/{swimlanes-5IMT3BWC-DJ2aLWal.js → swimlanes-5IMT3BWC-CKmo1sg6.js} +2 -2
- package/web/assets/swimlanesDiagram-G3AALYLV-D9RjLKb9.js +8 -0
- package/web/assets/tanstack-YzX_Mi37.js +3 -0
- package/web/assets/{timeline-definition-FHXFAJF6-vFDUVPCX.js → timeline-definition-FHXFAJF6-D-NMwS3y.js} +1 -1
- package/web/assets/{use-folder-filter-BbTUlXgG.js → use-folder-filter-LQyXns5Q.js} +1 -1
- package/web/assets/{utils-CwHGE3BB.js → utils-DCaHk0Gm.js} +1 -1
- package/web/assets/{vennDiagram-L72KCM5P-AsnhK2zW.js → vennDiagram-L72KCM5P-CfL6LBWw.js} +1 -1
- package/web/assets/view-context-gyDl_p2V.js +229 -0
- package/web/assets/viewer-capabilities-Bpp8Uvcg.js +1 -0
- package/web/assets/{wardleyDiagram-EHGQE667-Cj1wFbNV.js → wardleyDiagram-EHGQE667-DgldMxaj.js} +1 -1
- package/web/assets/{xychartDiagram-FW5EYKEG-Bbvnutsm.js → xychartDiagram-FW5EYKEG-CIoAIFX8.js} +1 -1
- package/web/favicon.svg +6 -0
- package/web/index.html +5 -4
- package/web/assets/app-CPSmsokP.js +0 -2
- package/web/assets/app-options-XEWvevsz.js +0 -25
- package/web/assets/app-view-header-BKZ90ToF.js +0 -1
- package/web/assets/arc-DbpbZvCt.js +0 -1
- package/web/assets/arrow-left-CFeHb-Z1.js +0 -1
- package/web/assets/ask-tray-D2YHtbPj.js +0 -14
- package/web/assets/button-BJ7z-ZA7.js +0 -1
- package/web/assets/calendar-days-DWh2XMO2.js +0 -1
- package/web/assets/channel-dSRp_qTo.js +0 -1
- package/web/assets/chevron-down-C5oRtqbX.js +0 -1
- package/web/assets/chevron-right-CsPtvh8C.js +0 -1
- package/web/assets/chevron-up-DjpU6Pyv.js +0 -1
- package/web/assets/classDiagram-OUVF2IWQ-B7B2zLzM.js +0 -1
- package/web/assets/classDiagram-v2-EOCWNBFH-B7B2zLzM.js +0 -1
- package/web/assets/client-CAudjYV9.js +0 -34
- package/web/assets/client-Dn9-_Gm_.js +0 -14
- package/web/assets/client-Dxrf2tyq.js +0 -102
- package/web/assets/client-aFI2M4pE.js +0 -53
- package/web/assets/collection-icon-TbOVyORG.js +0 -1
- package/web/assets/collection-page-Du6FKkuU.js +0 -10
- package/web/assets/command-C_g5E1Wx.js +0 -38
- package/web/assets/copy-Ikmr6Iq2.js +0 -1
- package/web/assets/copy-context-Cl1TE2PJ.js +0 -1
- package/web/assets/document-DcjNSJCk.js +0 -2
- package/web/assets/external-link-BXmSXqqF.js +0 -1
- package/web/assets/file-renderer-BRZTrYx8.js +0 -19
- package/web/assets/file-text-CiiSsCDq.js +0 -1
- package/web/assets/file-viewer-C1wAeVDK.js +0 -1
- package/web/assets/folder-image-AXBrHUcU.js +0 -1
- package/web/assets/folder-landing-BhRg7hwn.js +0 -54
- package/web/assets/folder-list-rIdszVde.js +0 -1
- package/web/assets/folder-metadata-puTC5QqJ.js +0 -2
- package/web/assets/folder-mobile-bar-DjjRQo3T.js +0 -1
- package/web/assets/folder-plugin-view-BU_kzR8j.js +0 -6
- package/web/assets/index-DS2Wmd9I.css +0 -1
- package/web/assets/index-DgqzSeIR.js +0 -1
- package/web/assets/index-DnlTDqaa.js +0 -1
- package/web/assets/index-DztTMDFz.js +0 -2
- package/web/assets/index-kY75qc96.js +0 -1
- package/web/assets/index-p38i_yyY.js +0 -1
- package/web/assets/link-2-BWKA6t--.js +0 -1
- package/web/assets/link-DC_1Z6Yy.js +0 -11
- package/web/assets/linked-session-header-DJYWZ_-q.js +0 -3
- package/web/assets/mail-2rCdIFul.js +0 -1
- package/web/assets/markdown-title-dncAYYiP.js +0 -131
- package/web/assets/notes-page-DIW6rgJp.js +0 -10
- package/web/assets/pencil-8cobiSSq.js +0 -1
- package/web/assets/plus-BHV2wJr7.js +0 -1
- package/web/assets/popover-Cmmgj-MJ.js +0 -9
- package/web/assets/search-CvlYUkN6.js +0 -2
- package/web/assets/select-1SvaKQsS.js +0 -30
- package/web/assets/selection-popup-ntjycEVh.js +0 -7
- package/web/assets/sparkles-CBhGREdc.js +0 -1
- package/web/assets/stateDiagram-2N3HPSRC-BEw1Mheu.js +0 -1
- package/web/assets/stateDiagram-v2-6OUMAXLB-BHtjl1iD.js +0 -1
- package/web/assets/swimlanesDiagram-G3AALYLV-Z362aroZ.js +0 -8
- package/web/assets/tanstack-CD46mJkU.js +0 -1
- package/web/assets/view-context-D-fh9AbH.js +0 -81
- package/web/assets/viewer-capabilities-CROIaoqh.js +0 -1
- package/web/assets/x-BUeE--I4.js +0 -1
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/triple-slash-reference -- ambient bun:ffi types for out-of-package consumers (packages/api compiles this file)
|
|
2
|
+
/// <reference path="./bun-ffi.d.ts" />
|
|
3
|
+
import { closeSync, constants, openSync } from "node:fs"
|
|
4
|
+
import { mkdir } from "node:fs/promises"
|
|
5
|
+
import { dirname, resolve } from "node:path"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Real POSIX advisory lock via `flock(2)` on MailOS-compatible lock files.
|
|
9
|
+
*
|
|
10
|
+
* MailOS (`emailos/internal/core/drafts.go` `withLocalDraftLock`) serializes
|
|
11
|
+
* draft mutations with an exclusive `flock` on `<draftDir>/.locks/<base>.lock`
|
|
12
|
+
* opened `O_CREATE|O_RDWR`. Kennel takes the *same* lock so destructive
|
|
13
|
+
* actions (delete/archive critical sections) coordinate with the MailOS
|
|
14
|
+
* binary itself, not just with other Kennel processes.
|
|
15
|
+
*
|
|
16
|
+
* Portability: `flock(2)` is reached through two supported FFI backends —
|
|
17
|
+
* `bun:ffi` when the process runs on Bun (the test runner), and the `koffi`
|
|
18
|
+
* N-API binding when it runs on Node (the Vite dev server / build). Both call
|
|
19
|
+
* the exact same libc symbol on the exact same lock files, so coordination
|
|
20
|
+
* holds across runtimes and against the Go MailOS binary. The wait is
|
|
21
|
+
* bounded so an abandoned holder cannot hang a server request forever.
|
|
22
|
+
*/
|
|
23
|
+
export class FlockTimeoutError extends Error {
|
|
24
|
+
readonly lockPath: string
|
|
25
|
+
|
|
26
|
+
constructor(lockPath: string, timeoutMs: number) {
|
|
27
|
+
super(
|
|
28
|
+
`Timed out after ${timeoutMs}ms waiting for the draft lock ${resolve(lockPath)}`,
|
|
29
|
+
)
|
|
30
|
+
this.name = "FlockTimeoutError"
|
|
31
|
+
this.lockPath = resolve(lockPath)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const LOCK_EX = 2
|
|
36
|
+
const LOCK_NB = 4
|
|
37
|
+
const LOCK_UN = 8
|
|
38
|
+
const EINTR = 4
|
|
39
|
+
const EWOULDBLOCK_DARWIN = 35
|
|
40
|
+
const EWOULDBLOCK_LINUX = 11
|
|
41
|
+
|
|
42
|
+
type FlockFn = (fd: number, operation: number) => number
|
|
43
|
+
|
|
44
|
+
type FlockBackend = {
|
|
45
|
+
flock: FlockFn
|
|
46
|
+
/** Returns the last libc errno; only used on Node (koffi exposes it). */
|
|
47
|
+
errno: () => number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isBunRuntime() {
|
|
51
|
+
return (
|
|
52
|
+
typeof process !== "undefined" &&
|
|
53
|
+
typeof process.versions !== "undefined" &&
|
|
54
|
+
"bun" in process.versions
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const libcPath =
|
|
59
|
+
process.platform === "darwin" ? "/usr/lib/libSystem.B.dylib" : "libc.so.6"
|
|
60
|
+
|
|
61
|
+
let backendPromise: Promise<FlockBackend> | null = null
|
|
62
|
+
|
|
63
|
+
async function loadBackend(): Promise<FlockBackend> {
|
|
64
|
+
backendPromise ??= (async () => {
|
|
65
|
+
if (isBunRuntime()) {
|
|
66
|
+
// bun:ffi does not surface errno; a non-zero flock result on the just
|
|
67
|
+
// opened descriptor is treated as "held elsewhere" (would-block /
|
|
68
|
+
// EINTR) and retried until the bounded deadline, which is the only
|
|
69
|
+
// realistic outcome for a valid descriptor.
|
|
70
|
+
const { dlopen } = await import("bun:ffi")
|
|
71
|
+
const libc = dlopen(libcPath, {
|
|
72
|
+
flock: { args: ["int", "int"], returns: "int" },
|
|
73
|
+
})
|
|
74
|
+
return { flock: libc.symbols.flock as FlockFn, errno: () => 0 }
|
|
75
|
+
}
|
|
76
|
+
// Node: koffi exposes libc errno so would-block/EINTR can be told
|
|
77
|
+
// apart from a real locking error.
|
|
78
|
+
const koffi = (await import("koffi")).default
|
|
79
|
+
const libc = koffi.load(libcPath)
|
|
80
|
+
return {
|
|
81
|
+
flock: libc.func("int flock(int fd, int operation)"),
|
|
82
|
+
errno: koffi.errno,
|
|
83
|
+
}
|
|
84
|
+
})()
|
|
85
|
+
return backendPromise
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const wouldBlockErrno =
|
|
89
|
+
process.platform === "darwin" ? EWOULDBLOCK_DARWIN : EWOULDBLOCK_LINUX
|
|
90
|
+
|
|
91
|
+
function sleep(ms: number) {
|
|
92
|
+
return new Promise((resolveSleep) => {
|
|
93
|
+
setTimeout(resolveSleep, ms)
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Run `action` while holding an exclusive `flock` on `lockPath`. The file is
|
|
99
|
+
* opened `O_RDWR|O_CREAT` (mode `0600`) exactly like MailOS; the lock is
|
|
100
|
+
* released (and the descriptor closed) before `withFlock` resolves, so a
|
|
101
|
+
* crash never leaves a stale advisory lock behind.
|
|
102
|
+
*/
|
|
103
|
+
export async function withFlock<T>(
|
|
104
|
+
lockPath: string,
|
|
105
|
+
timeoutMs: number,
|
|
106
|
+
action: () => Promise<T>,
|
|
107
|
+
): Promise<T> {
|
|
108
|
+
const resolved = resolve(lockPath)
|
|
109
|
+
// MailOS creates the lock directory with `os.MkdirAll(lockDir, 0o700)`.
|
|
110
|
+
await mkdir(dirname(resolved), { mode: 0o700, recursive: true }).catch(
|
|
111
|
+
() => undefined,
|
|
112
|
+
)
|
|
113
|
+
const { flock, errno } = await loadBackend()
|
|
114
|
+
const fd = openSync(resolved, constants.O_RDWR | constants.O_CREAT, 0o600)
|
|
115
|
+
try {
|
|
116
|
+
const deadline = Date.now() + timeoutMs
|
|
117
|
+
for (;;) {
|
|
118
|
+
if (flock(fd, LOCK_EX | LOCK_NB) === 0) break
|
|
119
|
+
const code = errno()
|
|
120
|
+
if (code !== 0 && code !== EINTR && code !== wouldBlockErrno) {
|
|
121
|
+
throw new Error(`flock failed on ${resolved} (errno ${code})`)
|
|
122
|
+
}
|
|
123
|
+
const remaining = deadline - Date.now()
|
|
124
|
+
if (remaining <= 0) {
|
|
125
|
+
throw new FlockTimeoutError(resolved, timeoutMs)
|
|
126
|
+
}
|
|
127
|
+
await sleep(Math.min(50, remaining))
|
|
128
|
+
}
|
|
129
|
+
return await action()
|
|
130
|
+
} finally {
|
|
131
|
+
try {
|
|
132
|
+
flock(fd, LOCK_UN)
|
|
133
|
+
} finally {
|
|
134
|
+
closeSync(fd)
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { FolderFilterChoice } from "@raggle-ai/ui/lib/folder-filter"
|
|
2
|
+
import {
|
|
3
|
+
folderFilterChoices,
|
|
4
|
+
folderFilterLabel,
|
|
5
|
+
} from "@raggle-ai/ui/lib/folder-filter"
|
|
6
|
+
|
|
7
|
+
export type MailFolderChoice = FolderFilterChoice
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Display text for a draft's owning folder relative to the viewed root:
|
|
11
|
+
* basename for root-owned drafts ("example-workspace"), relative path for
|
|
12
|
+
* descendants ("clients/client-one"). Null when the draft has no folder or it
|
|
13
|
+
* does not live under the viewed root.
|
|
14
|
+
*
|
|
15
|
+
* Delegates to the shared folder-filter lib.
|
|
16
|
+
*/
|
|
17
|
+
export function folderLabel(
|
|
18
|
+
folder: string | null,
|
|
19
|
+
rootFolder: string,
|
|
20
|
+
): string | null {
|
|
21
|
+
return folderFilterLabel(folder, rootFolder)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Exact owning-folder choices with per-draft counts for filtering the mail
|
|
26
|
+
* list. Keyed by the full folder path so duplicate leaf names stay
|
|
27
|
+
* distinct; ordered with the root folder first, then descendants
|
|
28
|
+
* alphabetically by relative path.
|
|
29
|
+
*
|
|
30
|
+
* Delegates to the shared folder-filter lib.
|
|
31
|
+
*/
|
|
32
|
+
export function folderChoices(
|
|
33
|
+
items: readonly { folder: string | null }[],
|
|
34
|
+
rootFolder: string,
|
|
35
|
+
): MailFolderChoice[] {
|
|
36
|
+
return folderFilterChoices(items, rootFolder, (item) => item.folder)
|
|
37
|
+
}
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
import {
|
|
3
|
+
lstat,
|
|
4
|
+
mkdir,
|
|
5
|
+
readFile,
|
|
6
|
+
realpath,
|
|
7
|
+
rename,
|
|
8
|
+
unlink,
|
|
9
|
+
writeFile,
|
|
10
|
+
} from "node:fs/promises"
|
|
11
|
+
import { dirname, isAbsolute, relative, resolve, sep } from "node:path"
|
|
12
|
+
|
|
13
|
+
import { FlockTimeoutError, withFlock } from "./flock.ts"
|
|
14
|
+
import { parseMailMarkdown } from "./parse.ts"
|
|
15
|
+
|
|
16
|
+
export type DraftIdentity = {
|
|
17
|
+
id: string
|
|
18
|
+
filePath: string
|
|
19
|
+
folder: string | null
|
|
20
|
+
/** sha256 of the draft bytes the client last loaded. */
|
|
21
|
+
revision: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type DeleteOutcome = "deleted" | "changed" | "missing" | "invalid"
|
|
25
|
+
|
|
26
|
+
export class MailDeleteError extends Error {
|
|
27
|
+
readonly outcome: DeleteOutcome
|
|
28
|
+
|
|
29
|
+
constructor(outcome: DeleteOutcome, message: string) {
|
|
30
|
+
super(message)
|
|
31
|
+
this.name = "MailDeleteError"
|
|
32
|
+
this.outcome = outcome
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* How long delete waits for the MailOS-compatible flock before giving up.
|
|
38
|
+
* MailOS blocks indefinitely; Kennel bounds the wait so an abandoned holder
|
|
39
|
+
* cannot hang a request, and reports the result as a conflict instead.
|
|
40
|
+
*/
|
|
41
|
+
export const deleteLockTimeoutMs = 30_000
|
|
42
|
+
|
|
43
|
+
function validDraftId(id: string) {
|
|
44
|
+
const trimmed = id.trim()
|
|
45
|
+
return (
|
|
46
|
+
Boolean(trimmed) &&
|
|
47
|
+
trimmed !== "." &&
|
|
48
|
+
trimmed !== ".." &&
|
|
49
|
+
!/[\\/]/.test(trimmed)
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function draftRevision(content: string) {
|
|
54
|
+
return createHash("sha256").update(content).digest("hex")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Walk `childPath` (which must be inside canonical `anchor`) and make sure
|
|
59
|
+
* every existing component is a real directory — never a symlink —
|
|
60
|
+
* creating missing components ourselves. This closes the history/locks
|
|
61
|
+
* directory symlink-escape: a planted `history`, `<id>`, or `.locks` symlink
|
|
62
|
+
* is rejected instead of followed.
|
|
63
|
+
*/
|
|
64
|
+
async function ensureRealDirChain(
|
|
65
|
+
anchor: string,
|
|
66
|
+
childPath: string,
|
|
67
|
+
): Promise<void> {
|
|
68
|
+
const rel = relative(anchor, childPath)
|
|
69
|
+
if (!rel || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
70
|
+
throw new MailDeleteError(
|
|
71
|
+
"invalid",
|
|
72
|
+
"Lock/history path escapes the mail folder",
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
let current = anchor
|
|
76
|
+
for (const segment of rel.split(sep)) {
|
|
77
|
+
if (!segment) continue
|
|
78
|
+
current = resolve(current, segment)
|
|
79
|
+
let info = await lstat(current).catch(() => undefined)
|
|
80
|
+
if (!info) {
|
|
81
|
+
await mkdir(current, { mode: 0o700 }).catch(() => undefined)
|
|
82
|
+
info = await lstat(current).catch(() => undefined)
|
|
83
|
+
if (!info || info.isSymbolicLink() || !info.isDirectory()) {
|
|
84
|
+
throw new MailDeleteError(
|
|
85
|
+
"invalid",
|
|
86
|
+
"Could not create a real directory for the draft lock/history",
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
if (info.isSymbolicLink() || !info.isDirectory()) {
|
|
92
|
+
throw new MailDeleteError(
|
|
93
|
+
"invalid",
|
|
94
|
+
"Draft lock/history path must use real directories (no symlinked components)",
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Delete a local draft with a byte-exact, verified history snapshot, under
|
|
102
|
+
* the real MailOS advisory lock.
|
|
103
|
+
*
|
|
104
|
+
* Locking — `withFlock` takes the exclusive `flock(2)` on
|
|
105
|
+
* `<mailDir>/.locks/<id>.md.lock`, the exact file MailOS holds
|
|
106
|
+
* (`emailos/internal/core/drafts.go withLocalDraftLock`) while it
|
|
107
|
+
* snapshots/updates/sends a draft. Kennel's delete/archive critical section
|
|
108
|
+
* therefore serializes against the MailOS binary itself, not only against
|
|
109
|
+
* other Kennel processes.
|
|
110
|
+
*
|
|
111
|
+
* Byte-level guarantees (inside the lock):
|
|
112
|
+
* - the draft file's current bytes must still hash to the revision the client
|
|
113
|
+
* loaded, or the delete is refused ("changed") and newer work survives;
|
|
114
|
+
* - the snapshot written to history/<id>/<revision>.md is byte-identical to
|
|
115
|
+
* the draft content (temp sibling + rename + verified re-read/hash);
|
|
116
|
+
* - pre-existing history is verified byte-for-byte first — corrupt existing
|
|
117
|
+
* history is never overwritten or deleted, and the draft is left in place;
|
|
118
|
+
* - the draft is re-read and re-hashed immediately before the unlink, and its
|
|
119
|
+
* realpath re-verified, so the final unlink only ever removes the exact
|
|
120
|
+
* file whose bytes matched the loaded revision at that moment.
|
|
121
|
+
*
|
|
122
|
+
* Exact guarantee (limitation): a non-cooperating writer that does not take
|
|
123
|
+
* the flock and rewrites the file *after* the final re-read but *before* the
|
|
124
|
+
* unlink can still have newer work deleted — the same two-instruction window
|
|
125
|
+
* MailOS itself has between its locked re-read and remove. No filesystem API
|
|
126
|
+
* can make a byte-compare-and-unlink atomic; the flock + closing re-read +
|
|
127
|
+
* realpath re-check reduce this to the unavoidable sub-microsecond window,
|
|
128
|
+
* and cooperative writers (Kennel, MailOS send/archive/snapshot) are fully
|
|
129
|
+
* serialized.
|
|
130
|
+
*/
|
|
131
|
+
export async function deleteLocalDraftVerified(
|
|
132
|
+
mail: DraftIdentity,
|
|
133
|
+
mailDir: string,
|
|
134
|
+
): Promise<"deleted"> {
|
|
135
|
+
if (!validDraftId(mail.id)) {
|
|
136
|
+
throw new MailDeleteError("invalid", "Invalid mail id")
|
|
137
|
+
}
|
|
138
|
+
const mailDirReal = await realpath(resolve(mailDir))
|
|
139
|
+
const target = resolve(mail.filePath)
|
|
140
|
+
const targetDir = await realpath(dirname(target)).catch(() => null)
|
|
141
|
+
if (targetDir !== mailDirReal) {
|
|
142
|
+
throw new MailDeleteError(
|
|
143
|
+
"invalid",
|
|
144
|
+
"Draft path is outside the mail folder",
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
// The draft file itself must be the plain expected file: a symlink that
|
|
148
|
+
// realpaths somewhere else is rejected rather than followed.
|
|
149
|
+
const expected = resolve(mailDirReal, `${mail.id.trim()}.md`)
|
|
150
|
+
const targetReal = await realpath(target).catch(() => null)
|
|
151
|
+
if (targetReal === null) {
|
|
152
|
+
throw new MailDeleteError("missing", "Draft no longer exists")
|
|
153
|
+
}
|
|
154
|
+
if (targetReal !== expected) {
|
|
155
|
+
throw new MailDeleteError(
|
|
156
|
+
"invalid",
|
|
157
|
+
"Draft path is not a direct file in the mail folder",
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!/^[a-f0-9]{64}$/i.test(mail.revision)) {
|
|
162
|
+
throw new MailDeleteError("invalid", "Invalid draft revision")
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const locksDir = resolve(mailDirReal, ".locks")
|
|
166
|
+
await ensureRealDirChain(mailDirReal, locksDir)
|
|
167
|
+
const lockPath = resolve(locksDir, `${mail.id.trim()}.md.lock`)
|
|
168
|
+
|
|
169
|
+
try {
|
|
170
|
+
return await withFlock(lockPath, deleteLockTimeoutMs, async () => {
|
|
171
|
+
// Under the lock: the re-read, the hash, the snapshot, the re-verify
|
|
172
|
+
// and the unlink are one critical section.
|
|
173
|
+
const content = await readFile(target, "utf8").catch(() => null)
|
|
174
|
+
if (content === null) {
|
|
175
|
+
throw new MailDeleteError("missing", "Draft no longer exists")
|
|
176
|
+
}
|
|
177
|
+
if (draftRevision(content) !== mail.revision.toLowerCase()) {
|
|
178
|
+
throw new MailDeleteError(
|
|
179
|
+
"changed",
|
|
180
|
+
"Draft changed since it was loaded; refusing to delete newer work",
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const historyDir = resolve(mailDirReal, "history", mail.id)
|
|
185
|
+
await ensureRealDirChain(mailDirReal, historyDir)
|
|
186
|
+
const historyPath = resolve(historyDir, `${mail.revision}.md`)
|
|
187
|
+
|
|
188
|
+
const existingLstat = await lstat(historyPath).catch(() => undefined)
|
|
189
|
+
if (existingLstat?.isSymbolicLink()) {
|
|
190
|
+
throw new MailDeleteError(
|
|
191
|
+
"invalid",
|
|
192
|
+
"History snapshot path is a symlink; refusing to touch it",
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
const existing = existingLstat ?? undefined
|
|
196
|
+
if (existing?.isFile()) {
|
|
197
|
+
// Never overwrite or trust an existing snapshot: verify its bytes
|
|
198
|
+
// still hash to the revision, exactly like MailOS
|
|
199
|
+
// (readVerifiedLocalDraftRevisionForPath). A corrupt existing history
|
|
200
|
+
// blocks the delete rather than being clobbered.
|
|
201
|
+
const existingContent = await readFile(historyPath, "utf8").catch(
|
|
202
|
+
() => null,
|
|
203
|
+
)
|
|
204
|
+
if (
|
|
205
|
+
existingContent === null ||
|
|
206
|
+
draftRevision(existingContent) !== mail.revision.toLowerCase()
|
|
207
|
+
) {
|
|
208
|
+
throw new MailDeleteError(
|
|
209
|
+
"changed",
|
|
210
|
+
"Existing draft history does not match this revision; refusing to delete",
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
} else if (existing) {
|
|
214
|
+
throw new MailDeleteError(
|
|
215
|
+
"invalid",
|
|
216
|
+
"History snapshot path is not a regular file",
|
|
217
|
+
)
|
|
218
|
+
} else {
|
|
219
|
+
// Write to a temp sibling then rename so a crash never leaves a
|
|
220
|
+
// partial snapshot under the revision name. "wx" refuses to follow or
|
|
221
|
+
// overwrite a planted temp symlink.
|
|
222
|
+
const tempPath = resolve(historyDir, `${mail.revision}.md.tmp`)
|
|
223
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
224
|
+
try {
|
|
225
|
+
await writeFile(tempPath, content, { flag: "wx", mode: 0o600 })
|
|
226
|
+
break
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error
|
|
229
|
+
const tempInfo = await lstat(tempPath).catch(() => undefined)
|
|
230
|
+
if (attempt === 0 && tempInfo?.isFile()) {
|
|
231
|
+
// A stale temp from an interrupted run: remove and write once
|
|
232
|
+
// more. A symlink/directory is never followed.
|
|
233
|
+
await unlink(tempPath)
|
|
234
|
+
continue
|
|
235
|
+
}
|
|
236
|
+
throw new MailDeleteError(
|
|
237
|
+
"invalid",
|
|
238
|
+
"Temp snapshot path is not a plain file; refusing to write through it",
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const written = await readFile(tempPath, "utf8").catch(() => null)
|
|
243
|
+
if (
|
|
244
|
+
written !== content ||
|
|
245
|
+
draftRevision(written ?? "") !== mail.revision.toLowerCase()
|
|
246
|
+
) {
|
|
247
|
+
throw new MailDeleteError(
|
|
248
|
+
"changed",
|
|
249
|
+
"Draft changed while snapshotting; refusing to delete",
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
await rename(tempPath, historyPath)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Re-verify the draft is byte-identical to the snapshot immediately
|
|
256
|
+
// before unlinking, and that it is still the same regular file; an
|
|
257
|
+
// editor or symlink that raced in is left untouched.
|
|
258
|
+
const current = await readFile(target, "utf8").catch(() => null)
|
|
259
|
+
if (current === null) {
|
|
260
|
+
throw new MailDeleteError("missing", "Draft no longer exists")
|
|
261
|
+
}
|
|
262
|
+
if (draftRevision(current) !== mail.revision.toLowerCase()) {
|
|
263
|
+
throw new MailDeleteError(
|
|
264
|
+
"changed",
|
|
265
|
+
"Draft changed since it was loaded; refusing to delete newer work",
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
const finalReal = await realpath(target).catch(() => null)
|
|
269
|
+
if (finalReal !== expected) {
|
|
270
|
+
throw new MailDeleteError(
|
|
271
|
+
"invalid",
|
|
272
|
+
"Draft path changed identity before deletion; refusing to unlink",
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
await unlink(target)
|
|
277
|
+
return "deleted" as const
|
|
278
|
+
})
|
|
279
|
+
} catch (error) {
|
|
280
|
+
if (error instanceof FlockTimeoutError) {
|
|
281
|
+
throw new MailDeleteError(
|
|
282
|
+
"changed",
|
|
283
|
+
"Draft is locked by another process (MailOS or another delete); try again",
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
throw error
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export type AttachmentRemoveOutcome =
|
|
291
|
+
"removed" | "changed" | "missing" | "invalid"
|
|
292
|
+
|
|
293
|
+
export class MailAttachmentError extends Error {
|
|
294
|
+
readonly outcome: AttachmentRemoveOutcome
|
|
295
|
+
|
|
296
|
+
constructor(outcome: AttachmentRemoveOutcome, message: string) {
|
|
297
|
+
super(message)
|
|
298
|
+
this.name = "MailAttachmentError"
|
|
299
|
+
this.outcome = outcome
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Rewrite the frontmatter `file:` block in place: drop the given attachment
|
|
305
|
+
* path and bump `updated_at`. Key order, quoting, and all other lines stay
|
|
306
|
+
* byte-identical, so MailOS reads the file exactly as before with one fewer
|
|
307
|
+
* linked file. Returns null when there is no `file:` key.
|
|
308
|
+
*/
|
|
309
|
+
function rewriteFileFrontmatter(
|
|
310
|
+
frontmatterRaw: string,
|
|
311
|
+
remaining: string[],
|
|
312
|
+
updatedAt: string,
|
|
313
|
+
): string | null {
|
|
314
|
+
const lines = frontmatterRaw.split("\n")
|
|
315
|
+
const fileIndex = lines.findIndex((line) => /^file:/.test(line))
|
|
316
|
+
if (fileIndex < 0) return null
|
|
317
|
+
|
|
318
|
+
// Eat the block-list item lines directly below an empty `file:` key.
|
|
319
|
+
let end = fileIndex + 1
|
|
320
|
+
if ((lines[fileIndex] ?? "").trim() === "file:") {
|
|
321
|
+
while (end < lines.length) {
|
|
322
|
+
if (!/^\s+- /.test(lines[end] ?? "")) break
|
|
323
|
+
end += 1
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const fileBlock = remaining.length
|
|
328
|
+
? ["file:", ...remaining.map((path) => ` - ${JSON.stringify(path)}`)]
|
|
329
|
+
: ["file: []"]
|
|
330
|
+
lines.splice(fileIndex, end - fileIndex, ...fileBlock)
|
|
331
|
+
|
|
332
|
+
const updatedIndex = lines.findIndex((line) => /^updated_at:/.test(line))
|
|
333
|
+
if (updatedIndex >= 0) {
|
|
334
|
+
lines[updatedIndex] = `updated_at: ${JSON.stringify(updatedAt)}`
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return lines.join("\n")
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Remove one attachment path from a local draft's frontmatter `file:` list.
|
|
342
|
+
* Mirrors deleteLocalDraftVerified's safety pattern: the draft must be a real
|
|
343
|
+
* file in the mail folder, the edit runs under the same MailOS-compatible
|
|
344
|
+
* flock with a byte-exact revision guard, the rewritten file is written to a
|
|
345
|
+
* temp sibling and atomically renamed, and `updated_at` is bumped. The
|
|
346
|
+
* attachment file itself is never touched — only the link in the draft.
|
|
347
|
+
* Returns the new draft revision so callers can continue against fresh data.
|
|
348
|
+
*/
|
|
349
|
+
export async function removeDraftAttachmentVerified(
|
|
350
|
+
mail: DraftIdentity,
|
|
351
|
+
mailDir: string,
|
|
352
|
+
attachment: string,
|
|
353
|
+
): Promise<{ revision: string }> {
|
|
354
|
+
if (!validDraftId(mail.id)) {
|
|
355
|
+
throw new MailAttachmentError("invalid", "Invalid mail id")
|
|
356
|
+
}
|
|
357
|
+
const mailDirReal = await realpath(resolve(mailDir))
|
|
358
|
+
const target = resolve(mail.filePath)
|
|
359
|
+
const targetDir = await realpath(dirname(target)).catch(() => null)
|
|
360
|
+
if (targetDir !== mailDirReal) {
|
|
361
|
+
throw new MailAttachmentError(
|
|
362
|
+
"invalid",
|
|
363
|
+
"Draft path is outside the mail folder",
|
|
364
|
+
)
|
|
365
|
+
}
|
|
366
|
+
const expected = resolve(mailDirReal, `${mail.id.trim()}.md`)
|
|
367
|
+
const targetReal = await realpath(target).catch(() => null)
|
|
368
|
+
if (targetReal === null) {
|
|
369
|
+
throw new MailAttachmentError("missing", "Draft no longer exists")
|
|
370
|
+
}
|
|
371
|
+
if (targetReal !== expected) {
|
|
372
|
+
throw new MailAttachmentError(
|
|
373
|
+
"invalid",
|
|
374
|
+
"Draft path is not a direct file in the mail folder",
|
|
375
|
+
)
|
|
376
|
+
}
|
|
377
|
+
if (!/^[a-f0-9]{64}$/i.test(mail.revision)) {
|
|
378
|
+
throw new MailAttachmentError("invalid", "Invalid draft revision")
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const locksDir = resolve(mailDirReal, ".locks")
|
|
382
|
+
await ensureRealDirChain(mailDirReal, locksDir)
|
|
383
|
+
const lockPath = resolve(locksDir, `${mail.id.trim()}.md.lock`)
|
|
384
|
+
|
|
385
|
+
try {
|
|
386
|
+
return await withFlock(lockPath, deleteLockTimeoutMs, async () => {
|
|
387
|
+
const revision = mail.revision.toLowerCase()
|
|
388
|
+
const content = await readFile(target, "utf8").catch(() => null)
|
|
389
|
+
if (content === null) {
|
|
390
|
+
throw new MailAttachmentError("missing", "Draft no longer exists")
|
|
391
|
+
}
|
|
392
|
+
if (draftRevision(content) !== revision) {
|
|
393
|
+
throw new MailAttachmentError(
|
|
394
|
+
"changed",
|
|
395
|
+
"Draft changed since it was loaded; refusing to edit",
|
|
396
|
+
)
|
|
397
|
+
}
|
|
398
|
+
if (!content.startsWith("---\n")) {
|
|
399
|
+
throw new MailAttachmentError("invalid", "Draft has no frontmatter")
|
|
400
|
+
}
|
|
401
|
+
const end = content.indexOf("\n---\n")
|
|
402
|
+
if (end < 0) {
|
|
403
|
+
throw new MailAttachmentError("invalid", "Draft has no frontmatter")
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const parsed = parseMailMarkdown(content)
|
|
407
|
+
if (!parsed.attachments.includes(attachment)) {
|
|
408
|
+
throw new MailAttachmentError(
|
|
409
|
+
"missing",
|
|
410
|
+
"Attachment is not linked to this draft",
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
const remaining = parsed.attachments.filter((path) => path !== attachment)
|
|
414
|
+
const frontmatterRaw = content.slice(4, end)
|
|
415
|
+
const newFrontmatter = rewriteFileFrontmatter(
|
|
416
|
+
frontmatterRaw,
|
|
417
|
+
remaining,
|
|
418
|
+
new Date().toISOString(),
|
|
419
|
+
)
|
|
420
|
+
if (newFrontmatter === null) {
|
|
421
|
+
throw new MailAttachmentError(
|
|
422
|
+
"invalid",
|
|
423
|
+
"Draft frontmatter has no file list",
|
|
424
|
+
)
|
|
425
|
+
}
|
|
426
|
+
const newContent = `---\n${newFrontmatter}\n---\n${content.slice(
|
|
427
|
+
end + "\n---\n".length,
|
|
428
|
+
)}`
|
|
429
|
+
|
|
430
|
+
const tempPath = resolve(mailDirReal, `${mail.id.trim()}.md.tmp`)
|
|
431
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
432
|
+
try {
|
|
433
|
+
await writeFile(tempPath, newContent, { flag: "wx", mode: 0o600 })
|
|
434
|
+
break
|
|
435
|
+
} catch (error) {
|
|
436
|
+
if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error
|
|
437
|
+
const tempInfo = await lstat(tempPath).catch(() => undefined)
|
|
438
|
+
if (attempt === 0 && tempInfo?.isFile()) {
|
|
439
|
+
await unlink(tempPath)
|
|
440
|
+
continue
|
|
441
|
+
}
|
|
442
|
+
throw new MailAttachmentError(
|
|
443
|
+
"invalid",
|
|
444
|
+
"Temp draft path is not a plain file; refusing to write through it",
|
|
445
|
+
)
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
const written = await readFile(tempPath, "utf8").catch(() => null)
|
|
449
|
+
if (written !== newContent) {
|
|
450
|
+
throw new MailAttachmentError(
|
|
451
|
+
"changed",
|
|
452
|
+
"Draft changed while writing; refusing to save",
|
|
453
|
+
)
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// The draft must still hash to the client's revision immediately
|
|
457
|
+
// before the rename, so a concurrent editor is never overwritten.
|
|
458
|
+
const current = await readFile(target, "utf8").catch(() => null)
|
|
459
|
+
if (current === null) {
|
|
460
|
+
throw new MailAttachmentError("missing", "Draft no longer exists")
|
|
461
|
+
}
|
|
462
|
+
if (draftRevision(current) !== revision) {
|
|
463
|
+
throw new MailAttachmentError(
|
|
464
|
+
"changed",
|
|
465
|
+
"Draft changed since it was loaded; refusing to edit",
|
|
466
|
+
)
|
|
467
|
+
}
|
|
468
|
+
await rename(tempPath, target)
|
|
469
|
+
|
|
470
|
+
return { revision: draftRevision(newContent) }
|
|
471
|
+
})
|
|
472
|
+
} catch (error) {
|
|
473
|
+
if (error instanceof FlockTimeoutError) {
|
|
474
|
+
throw new MailAttachmentError(
|
|
475
|
+
"changed",
|
|
476
|
+
"Draft is locked by another process (MailOS or another edit); try again",
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
throw error
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export function historyDirFor(mail: DraftIdentity, mailDir: string) {
|
|
484
|
+
return resolve(resolve(mailDir), "history", mail.id)
|
|
485
|
+
}
|