clay-server 5.2.0-beta.1 → 5.2.0-beta.10
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/lib/account-auth-socket.js +1 -0
- package/lib/builtin-mates.js +19 -16
- package/lib/bundled-skills/clay-agent-browser/SKILL.md +6 -2
- package/lib/canonical-clay-record-authority.js +15 -0
- package/lib/canonical-clay-scheduling-authority.js +19 -0
- package/lib/claude-hook-installer.js +1 -239
- package/lib/claude-jsonl-watcher.js +1 -204
- package/lib/clay-control-presentation-binding.js +96 -0
- package/lib/clay-control-presentation.js +128 -0
- package/lib/clay-control-target-projection.js +26 -0
- package/lib/clay-mate-chat-receipts.js +26 -0
- package/lib/clay-mate-chat.js +88 -0
- package/lib/clay-mate-knowledge-input.js +26 -0
- package/lib/clay-mate-knowledge.js +95 -0
- package/lib/clay-mate-lifecycle.js +95 -0
- package/lib/clay-product-control-input.js +33 -0
- package/lib/clay-product-control-tools.js +51 -0
- package/lib/clay-product-control.js +247 -0
- package/lib/clay-project-lifecycle.js +154 -0
- package/lib/clay-project-record-input.js +44 -0
- package/lib/clay-project-records.js +84 -0
- package/lib/clay-scheduling-input.js +21 -0
- package/lib/clay-scheduling-receipts.js +26 -0
- package/lib/clay-scheduling.js +119 -0
- package/lib/clay-screen-controls.js +104 -0
- package/lib/clay-skill-install.js +1 -33
- package/lib/clay-workspace-controls.js +84 -0
- package/lib/clay-workspace-input.js +17 -0
- package/lib/clay-workspace-receipts.js +26 -0
- package/lib/cli-sessions.js +1 -300
- package/lib/codex-defaults.js +1 -28
- package/lib/context-usage.js +3 -2
- package/lib/contextual-screen-snapshot.js +38 -0
- package/lib/daemon-project-access.js +11 -0
- package/lib/daemon-project-lifecycle-callbacks.js +15 -0
- package/lib/daemon-project-lifecycle.js +262 -0
- package/lib/daemon-projects.js +1 -1
- package/lib/daemon.js +78 -276
- package/lib/default-ai-runtime.js +4 -8
- package/lib/dm-digest-cursor.js +120 -0
- package/lib/email-mcp-server.js +1 -1
- package/lib/issues-service.js +29 -15
- package/lib/kiro-defaults.js +1 -22
- package/lib/knowledge-db-import-validation.js +46 -0
- package/lib/knowledge-db-import.js +99 -0
- package/lib/knowledge-db-service.js +24 -33
- package/lib/knowledge-db-storage-nedb-maintenance.js +99 -0
- package/lib/knowledge-db-storage-nedb.js +66 -0
- package/lib/knowledge-db-storage.js +72 -0
- package/lib/knowledge-db-validation.js +0 -0
- package/lib/knowledge-document-index.js +2 -1
- package/lib/knowledge-file-store.js +7 -1
- package/lib/knowledge-product-operations.js +46 -0
- package/lib/mate-avatar-catalog.js +12 -0
- package/lib/mate-avatar-migration.js +41 -0
- package/lib/mate-context-service.js +138 -0
- package/lib/mate-context-tools.js +21 -0
- package/lib/mate-creation-mcp-server.js +2 -0
- package/lib/mate-knowledge-canonical-access.js +10 -0
- package/lib/mate-knowledge-mate-bound.js +53 -0
- package/lib/mate-knowledge-mcp-server.js +55 -8
- package/lib/mate-knowledge-service.js +16 -34
- package/lib/mate-knowledge-text-tools.js +106 -0
- package/lib/mate-lifecycle-service.js +94 -0
- package/lib/mate-message-delivery.js +43 -0
- package/lib/mate-ready-creation.js +9 -1
- package/lib/mate-schedule-input.js +29 -0
- package/lib/mate-schedule-lifecycle.js +151 -0
- package/lib/mate-scheduling.js +103 -0
- package/lib/mates.js +18 -6
- package/lib/mcp-agent-tools.js +117 -0
- package/lib/mcp-connection-store.js +119 -15
- package/lib/mcp-import-credentials.js +31 -0
- package/lib/mcp-local-transport.js +13 -0
- package/lib/mcp-oauth.js +8 -6
- package/lib/mcp-proxy.js +2 -32
- package/lib/mcp-query-bridge.js +30 -0
- package/lib/mcp-remote-connections.js +74 -20
- package/lib/mcp-source-adapters.js +1 -0
- package/lib/mcp-source-context.js +1 -0
- package/lib/mcp-source-discovery.js +1 -0
- package/lib/mcp-source-plugins.js +1 -0
- package/lib/mcp-source-restrictions.js +1 -0
- package/lib/mcp-tool-policy.js +9 -0
- package/lib/notes-checklist.js +50 -0
- package/lib/notes.js +49 -3
- package/lib/os-user-diagnostics.js +2 -4
- package/lib/os-users.js +24 -70
- package/lib/pane-pending-interactions.js +20 -0
- package/lib/pane-session-activation.js +114 -0
- package/lib/pane-session-transport.js +26 -0
- package/lib/project-admin-overview.js +51 -0
- package/lib/project-ask-user.js +8 -4
- package/lib/project-connection.js +14 -10
- package/lib/project-control-git-view.js +12 -0
- package/lib/project-debate.js +1 -0
- package/lib/project-delegated-follow-up.js +2 -1
- package/lib/project-directory-claim.js +65 -0
- package/lib/project-file-operations.js +61 -0
- package/lib/project-filesystem.js +15 -85
- package/lib/project-full-access.js +23 -2
- package/lib/project-http.js +7 -4
- package/lib/project-knowledge.js +32 -18
- package/lib/project-lifecycle-operations.js +98 -0
- package/lib/project-lifecycle-process-witness.js +32 -0
- package/lib/project-lifecycle-runtime.js +50 -0
- package/lib/project-linux-access.js +22 -0
- package/lib/project-logs-canonical-clay.js +27 -0
- package/lib/project-logs-clay-query.js +16 -0
- package/lib/project-logs-schema.js +4 -2
- package/lib/project-logs-service.js +8 -7
- package/lib/project-logs.js +1 -0
- package/lib/project-loop.js +1 -0
- package/lib/project-mate-chat-control.js +55 -0
- package/lib/project-mate-creation-proposal.js +18 -3
- package/lib/project-mate-interaction.js +40 -62
- package/lib/project-mcp.js +54 -16
- package/lib/project-models.js +14 -31
- package/lib/project-notifications.js +76 -17
- package/lib/project-ownership.js +77 -0
- package/lib/project-pair-result-delivery.js +14 -3
- package/lib/project-pair-usage.js +12 -13
- package/lib/project-pending-message-queue.js +4 -1
- package/lib/project-product-control.js +89 -0
- package/lib/project-product-session-queue.js +46 -0
- package/lib/project-push-access.js +28 -0
- package/lib/project-scheduled-message-queue.js +30 -0
- package/lib/project-scheduled-messages.js +67 -68
- package/lib/project-scheduled-task-control.js +83 -0
- package/lib/project-scheduled-tasks.js +13 -2
- package/lib/project-scheduling-access.js +10 -0
- package/lib/project-session-actions.js +65 -0
- package/lib/project-session-delete.js +8 -1
- package/lib/project-session-document.js +27 -7
- package/lib/project-session-github.js +63 -19
- package/lib/project-session-history-actions.js +167 -0
- package/lib/project-session-linear.js +14 -6
- package/lib/project-session-notes.js +7 -3
- package/lib/project-session-pair.js +1 -1
- package/lib/project-session-runtime-actions.js +64 -0
- package/lib/project-session-spawn.js +2 -3
- package/lib/project-session-vendor.js +55 -0
- package/lib/project-sessions.js +103 -272
- package/lib/project-shared-browser.js +78 -17
- package/lib/project-studio-presentation.js +58 -0
- package/lib/project-user-message.js +67 -1
- package/lib/project-vendor-login.js +6 -36
- package/lib/project-workspace-operations.js +126 -0
- package/lib/project-workspace-query.js +19 -2
- package/lib/project.js +178 -45
- package/lib/public/app.js +46 -0
- package/lib/public/appearance-bootstrap.js +16 -0
- package/lib/public/css/clay-tool-activity.css +51 -0
- package/lib/public/css/clay-workspace.css +29 -0
- package/lib/public/css/command-palette.css +12 -33
- package/lib/public/css/contextual-clay.css +240 -0
- package/lib/public/css/home-debate-planning.css +2 -0
- package/lib/public/css/home-mate-creation.css +5 -0
- package/lib/public/css/input.css +6 -0
- package/lib/public/css/mate-avatar-gallery.css +12 -0
- package/lib/public/css/mate-conversation.css +10 -5
- package/lib/public/css/messages.css +6 -2
- package/lib/public/css/overlays.css +4 -2
- package/lib/public/css/project-admin-overview.css +13 -0
- package/lib/public/css/session-folders.css +11 -0
- package/lib/public/css/session-github.css +46 -1
- package/lib/public/css/shared-browser.css +35 -1
- package/lib/public/css/sticky-notes.css +87 -1
- package/lib/public/index.html +11 -5
- package/lib/public/mate-avatars/B01.png +0 -0
- package/lib/public/mate-avatars/B02.png +0 -0
- package/lib/public/mate-avatars/B03.png +0 -0
- package/lib/public/mate-avatars/B04.png +0 -0
- package/lib/public/mate-avatars/B05.png +0 -0
- package/lib/public/mate-avatars/B06.png +0 -0
- package/lib/public/mate-avatars/B07.png +0 -0
- package/lib/public/mate-avatars/B08.png +0 -0
- package/lib/public/mate-avatars/B09.png +0 -0
- package/lib/public/mate-avatars/B10.png +0 -0
- package/lib/public/mate-avatars/B11.png +0 -0
- package/lib/public/mate-avatars/B12.png +0 -0
- package/lib/public/mate-avatars/B13.png +0 -0
- package/lib/public/mate-avatars/B14.png +0 -0
- package/lib/public/mate-avatars/B15.png +0 -0
- package/lib/public/mate-avatars/B16.png +0 -0
- package/lib/public/mate-avatars/B17.png +0 -0
- package/lib/public/mate-avatars/B18.png +0 -0
- package/lib/public/mate-avatars/B19.png +0 -0
- package/lib/public/mate-avatars/B20.png +0 -0
- package/lib/public/mate-avatars/B21.png +0 -0
- package/lib/public/mate-avatars/B22.png +0 -0
- package/lib/public/mate-avatars/B23.png +0 -0
- package/lib/public/mate-avatars/B24.png +0 -0
- package/lib/public/mate-avatars/B25.png +0 -0
- package/lib/public/mate-avatars/B26.png +0 -0
- package/lib/public/mate-avatars/B27.png +0 -0
- package/lib/public/mate-avatars/B28.png +0 -0
- package/lib/public/mate-avatars/B29.png +0 -0
- package/lib/public/mate-avatars/B30.png +0 -0
- package/lib/public/mate-avatars/B31.png +0 -0
- package/lib/public/mate-avatars/B32.png +0 -0
- package/lib/public/mate-avatars/B33.png +0 -0
- package/lib/public/mate-avatars/B34.png +0 -0
- package/lib/public/mate-avatars/B35.png +0 -0
- package/lib/public/mate-avatars/B36.png +0 -0
- package/lib/public/mate-avatars/B37.png +0 -0
- package/lib/public/mate-avatars/B38.png +0 -0
- package/lib/public/mate-avatars/B39.png +0 -0
- package/lib/public/mate-avatars/B40.png +0 -0
- package/lib/public/mate-avatars/B41.png +0 -0
- package/lib/public/mate-avatars/B42.png +0 -0
- package/lib/public/mate-avatars/B43.png +0 -0
- package/lib/public/mate-avatars/B44.png +0 -0
- package/lib/public/mate-avatars/B45.png +0 -0
- package/lib/public/mate-avatars/B46.png +0 -0
- package/lib/public/mate-avatars/B47.png +0 -0
- package/lib/public/mate-avatars/B48.png +0 -0
- package/lib/public/mate-avatars/C01.png +0 -0
- package/lib/public/mate-avatars/C02.png +0 -0
- package/lib/public/mate-avatars/C03.png +0 -0
- package/lib/public/mate-avatars/C04.png +0 -0
- package/lib/public/mate-avatars/C05.png +0 -0
- package/lib/public/mate-avatars/C06.png +0 -0
- package/lib/public/mate-avatars/C07.png +0 -0
- package/lib/public/mate-avatars/C08.png +0 -0
- package/lib/public/mate-avatars/C09.png +0 -0
- package/lib/public/mate-avatars/C10.png +0 -0
- package/lib/public/mate-avatars/C11.png +0 -0
- package/lib/public/mate-avatars/C12.png +0 -0
- package/lib/public/mate-avatars/C13.png +0 -0
- package/lib/public/mate-avatars/C14.png +0 -0
- package/lib/public/mate-avatars/C15.png +0 -0
- package/lib/public/mate-avatars/C16.png +0 -0
- package/lib/public/mate-avatars/C17.png +0 -0
- package/lib/public/mate-avatars/C18.png +0 -0
- package/lib/public/mate-avatars/C19.png +0 -0
- package/lib/public/mate-avatars/C20.png +0 -0
- package/lib/public/mate-avatars/C21.png +0 -0
- package/lib/public/mate-avatars/C22.png +0 -0
- package/lib/public/mate-avatars/C23.png +0 -0
- package/lib/public/mate-avatars/C24.png +0 -0
- package/lib/public/mate-avatars/C25.png +0 -0
- package/lib/public/mate-avatars/C26.png +0 -0
- package/lib/public/mate-avatars/C27.png +0 -0
- package/lib/public/mate-avatars/C28.png +0 -0
- package/lib/public/mate-avatars/C29.png +0 -0
- package/lib/public/mate-avatars/C30.png +0 -0
- package/lib/public/mate-avatars/C31.png +0 -0
- package/lib/public/mate-avatars/C32.png +0 -0
- package/lib/public/mate-avatars/C33.png +0 -0
- package/lib/public/mate-avatars/C34.png +0 -0
- package/lib/public/mate-avatars/C35.png +0 -0
- package/lib/public/mate-avatars/C36.png +0 -0
- package/lib/public/mate-avatars/C37.png +0 -0
- package/lib/public/mate-avatars/C38.png +0 -0
- package/lib/public/mate-avatars/C39.png +0 -0
- package/lib/public/mate-avatars/C40.png +0 -0
- package/lib/public/mate-avatars/C41.png +0 -0
- package/lib/public/mate-avatars/C42.png +0 -0
- package/lib/public/mate-avatars/C43.png +0 -0
- package/lib/public/mate-avatars/C44.png +0 -0
- package/lib/public/mate-avatars/C45.png +0 -0
- package/lib/public/mate-avatars/C46.png +0 -0
- package/lib/public/mate-avatars/C47.png +0 -0
- package/lib/public/mate-avatars/C48.png +0 -0
- package/lib/public/mate-avatars/catalog.json +482 -0
- package/lib/public/mate-avatars/lead-clay.png +0 -0
- package/lib/public/modules/admin-settings-projects.js +6 -2
- package/lib/public/modules/app-connection.js +11 -1
- package/lib/public/modules/app-favicon.js +18 -9
- package/lib/public/modules/app-header.js +7 -4
- package/lib/public/modules/app-loop-ui.js +2 -0
- package/lib/public/modules/app-message-router.js +22 -1
- package/lib/public/modules/app-messages.js +115 -17
- package/lib/public/modules/app-misc.js +3 -1
- package/lib/public/modules/app-notifications.js +11 -5
- package/lib/public/modules/app-panels.js +61 -123
- package/lib/public/modules/app-projects.js +13 -0
- package/lib/public/modules/app-rate-limit.js +7 -27
- package/lib/public/modules/app-rendering.js +4 -3
- package/lib/public/modules/avatar.js +15 -41
- package/lib/public/modules/chat-render-runtime.js +20 -0
- package/lib/public/modules/clay-browser-links.js +47 -0
- package/lib/public/modules/clay-control-activity.js +214 -0
- package/lib/public/modules/clay-control-copy.js +36 -0
- package/lib/public/modules/clay-control-detail-targets.js +44 -0
- package/lib/public/modules/clay-control-object-targets.js +52 -0
- package/lib/public/modules/clay-control-target-visibility.js +12 -0
- package/lib/public/modules/clay-control-targets.js +44 -0
- package/lib/public/modules/clay-log-links.js +23 -0
- package/lib/public/modules/clay-navigation-render.js +52 -0
- package/lib/public/modules/clay-owner-navigation.js +33 -0
- package/lib/public/modules/clay-session-links.js +31 -0
- package/lib/public/modules/clay-studio-bridge.js +20 -0
- package/lib/public/modules/clay-studio-document.js +67 -0
- package/lib/public/modules/clay-studio-render.js +146 -0
- package/lib/public/modules/clay-tool-activity.js +140 -0
- package/lib/public/modules/clay-tool-presentation.js +342 -0
- package/lib/public/modules/command-palette.js +162 -40
- package/lib/public/modules/contextual-ask-session.js +26 -0
- package/lib/public/modules/contextual-clay-geometry.js +155 -0
- package/lib/public/modules/contextual-clay-session.js +130 -0
- package/lib/public/modules/contextual-clay-workspace.js +169 -0
- package/lib/public/modules/contextual-clay.js +250 -0
- package/lib/public/modules/contextual-composer.js +91 -0
- package/lib/public/modules/contextual-mate-context.js +117 -0
- package/lib/public/modules/contextual-mate-hint.js +40 -0
- package/lib/public/modules/contextual-mate-identity.js +23 -0
- package/lib/public/modules/contextual-mate-sessions.js +134 -0
- package/lib/public/modules/contextual-search.js +137 -0
- package/lib/public/modules/contextual-session-citations.js +58 -0
- package/lib/public/modules/debate.js +1 -0
- package/lib/public/modules/default-ai.js +11 -3
- package/lib/public/modules/elicitation-form.js +5 -0
- package/lib/public/modules/filebrowser.js +20 -1
- package/lib/public/modules/generation-stop.js +8 -5
- package/lib/public/modules/git-panel.js +3 -1
- package/lib/public/modules/history-replay-batch.js +13 -1
- package/lib/public/modules/home-debate-live.js +2 -0
- package/lib/public/modules/home-debate-planning.js +7 -7
- package/lib/public/modules/home-mate-avatar-editor.js +127 -208
- package/lib/public/modules/home-mate-chat.js +8 -6
- package/lib/public/modules/home-mate-creation.js +73 -1
- package/lib/public/modules/home-mate-settings.js +19 -12
- package/lib/public/modules/icon-strip-tooltip.js +2 -1
- package/lib/public/modules/input.js +88 -10
- package/lib/public/modules/issues-render.js +3 -2
- package/lib/public/modules/knowledge-db-workbench.js +4 -4
- package/lib/public/modules/markdown.js +6 -34
- package/lib/public/modules/mate-avatar-catalog.js +97 -0
- package/lib/public/modules/mate-knowledge-workbench.js +1 -1
- package/lib/public/modules/mate-management.js +40 -7
- package/lib/public/modules/mcp-connection-view.js +31 -13
- package/lib/public/modules/mcp-discovery-view.js +31 -0
- package/lib/public/modules/mcp-ui.js +23 -4
- package/lib/public/modules/mention.js +3 -0
- package/lib/public/modules/message-delivery.js +20 -2
- package/lib/public/modules/model-picker.js +2 -0
- package/lib/public/modules/pane-activation.js +214 -0
- package/lib/public/modules/pane-binding.js +82 -0
- package/lib/public/modules/pane-bridge.js +7 -4
- package/lib/public/modules/pane-browser-bridge.js +22 -0
- package/lib/public/modules/pane-file-bridge.js +4 -0
- package/lib/public/modules/pane-issue-bridge.js +4 -1
- package/lib/public/modules/pending-message-queue.js +1 -0
- package/lib/public/modules/permission-control.js +5 -4
- package/lib/public/modules/personal-settings-dialog.js +7 -1
- package/lib/public/modules/product-review.js +26 -0
- package/lib/public/modules/profile.js +1 -238
- package/lib/public/modules/project-access-recovery.js +17 -0
- package/lib/public/modules/project-admin-overview.js +21 -0
- package/lib/public/modules/project-chat-identity.js +13 -4
- package/lib/public/modules/project-logs-render.js +3 -0
- package/lib/public/modules/project-mate-navigation.js +144 -22
- package/lib/public/modules/rewind.js +5 -0
- package/lib/public/modules/scheduled-tasks.js +2 -0
- package/lib/public/modules/scheduler.js +2 -0
- package/lib/public/modules/search-clay-chat.js +5 -440
- package/lib/public/modules/session-actions.js +5 -2
- package/lib/public/modules/session-context-menu.js +5 -0
- package/lib/public/modules/session-create-form.js +10 -10
- package/lib/public/modules/session-folder-layout.js +1 -1
- package/lib/public/modules/session-folder-view-menu.js +9 -0
- package/lib/public/modules/session-github.js +85 -14
- package/lib/public/modules/session-rich-drafts.js +21 -0
- package/lib/public/modules/session-switch-defer.js +67 -0
- package/lib/public/modules/session-work-primary.js +51 -0
- package/lib/public/modules/settings-defaults.js +27 -28
- package/lib/public/modules/shared-browser-settings.js +112 -0
- package/lib/public/modules/shared-browser-tabs.js +13 -3
- package/lib/public/modules/shared-browser.js +29 -6
- package/lib/public/modules/sidebar-mobile.js +4 -3
- package/lib/public/modules/sidebar-projects.js +1 -0
- package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
- package/lib/public/modules/sidebar-sessions.js +1 -0
- package/lib/public/modules/split-group-restore.js +68 -0
- package/lib/public/modules/split-pair-ui.js +1 -0
- package/lib/public/modules/split-pane-reconciler.js +15 -0
- package/lib/public/modules/split-pane-renderer.js +87 -6
- package/lib/public/modules/split-session-boundary.js +1 -1
- package/lib/public/modules/split-view.js +60 -68
- package/lib/public/modules/sticky-note-markdown.js +72 -6
- package/lib/public/modules/sticky-notes-actions.js +30 -0
- package/lib/public/modules/sticky-notes-browser.js +9 -2
- package/lib/public/modules/sticky-notes-card.js +23 -7
- package/lib/public/modules/sticky-notes-checklist.js +116 -0
- package/lib/public/modules/sticky-notes-editor.js +108 -44
- package/lib/public/modules/sticky-notes-shared.js +50 -2
- package/lib/public/modules/sticky-notes-state.js +66 -0
- package/lib/public/modules/sticky-notes.js +34 -51
- package/lib/public/modules/stt-controller.js +4 -1
- package/lib/public/modules/stt-recording.js +1 -1
- package/lib/public/modules/stt.js +7 -2
- package/lib/public/modules/terminal.js +9 -0
- package/lib/public/modules/theme.js +8 -1
- package/lib/public/modules/thinking-lifecycle.js +33 -3
- package/lib/public/modules/thinking-view.js +23 -1
- package/lib/public/modules/tools.js +60 -11
- package/lib/public/modules/tui-grab.js +1 -2
- package/lib/public/modules/user-settings-appearance.js +10 -3
- package/lib/public/modules/user-settings-editors.js +1 -1
- package/lib/public/modules/user-settings-layout.js +11 -2
- package/lib/public/modules/user-settings-preferences.js +6 -2
- package/lib/public/modules/user-settings-profile.js +12 -2
- package/lib/public/modules/user-settings.js +3 -2
- package/lib/public/modules/vendor-login-browser.js +5 -3
- package/lib/public/modules/vendor-login.js +2 -2
- package/lib/public/modules/vendor-priority.js +15 -1
- package/lib/public/modules/wireframe-workbench.js +5 -2
- package/lib/public/muse-avatar.svg +4 -0
- package/lib/public/style.css +5 -0
- package/lib/public/sw.js +3 -1
- package/lib/safe-bash-commands.js +3 -45
- package/lib/scheduled-task-records.js +3 -1
- package/lib/scheduler.js +2 -0
- package/lib/sdk-bridge.js +342 -401
- package/lib/sdk-foreground-admission.js +16 -0
- package/lib/sdk-mcp-descriptors.js +2 -37
- package/lib/sdk-message-processor.js +52 -17
- package/lib/sdk-quota-snapshot.js +1 -1
- package/lib/sdk-response-recovery.js +1 -0
- package/lib/sdk-worker.js +2 -677
- package/lib/server-admin.js +63 -16
- package/lib/server-appearance-settings.js +13 -1
- package/lib/server-contextual-clay-settings.js +40 -0
- package/lib/server-contextual-mate-sessions.js +70 -0
- package/lib/server-dm.js +2 -1
- package/lib/server-global-ws.js +10 -20
- package/lib/server-home-chat-events.js +18 -4
- package/lib/server-home-chat.js +22 -20
- package/lib/server-home-clay-entry.js +23 -4
- package/lib/server-home-clay-session-links.js +29 -9
- package/lib/server-home-mate-creation.js +1 -0
- package/lib/server-home-mcp-consent.js +14 -0
- package/lib/server-mates.js +25 -62
- package/lib/server-owner-ui.js +118 -0
- package/lib/server-path-access.js +38 -0
- package/lib/server-project-removal.js +22 -0
- package/lib/server-settings.js +6 -1
- package/lib/server.js +163 -43
- package/lib/session-config-transition.js +45 -0
- package/lib/session-folders.js +7 -2
- package/lib/session-fork-history.js +21 -0
- package/lib/session-github.js +9 -2
- package/lib/session-hygiene.js +3 -35
- package/lib/session-permission-mode.js +4 -3
- package/lib/session-skill-scope.js +11 -0
- package/lib/session-visibility.js +5 -2
- package/lib/session-work-primary.js +50 -0
- package/lib/sessions.js +64 -99
- package/lib/shared-browser-cookie-sync.js +32 -0
- package/lib/shared-browser-cookies.js +79 -0
- package/lib/shared-browser-runtime.js +25 -9
- package/lib/shared-browser-storage-sync.js +43 -0
- package/lib/shared-browser-storage.js +101 -0
- package/lib/shared-browser-tools.js +2 -1
- package/lib/shared-browser-worker.js +46 -10
- package/lib/terminal-manager.js +65 -6
- package/lib/tool-llm.js +1 -1
- package/lib/tui-transcript-index.js +1 -132
- package/lib/users-contextual-clay-preferences.js +31 -0
- package/lib/users-permissions.js +2 -8
- package/lib/users.js +7 -0
- package/lib/vendor-login-browser.js +1 -11
- package/lib/workspace-assignment-service.js +25 -0
- package/lib/workspace-query-access.js +4 -1
- package/lib/workspace-query-mcp-server.js +1 -1
- package/lib/workspace-query-service.js +13 -2
- package/lib/ws-request.js +1 -0
- package/lib/ws-schema.js +18 -2
- package/lib/yoke/acp-agent-profiles.js +7 -5
- package/lib/yoke/adapters/antigravity.js +3 -14
- package/lib/yoke/adapters/claude-query-contract.js +9 -0
- package/lib/yoke/adapters/claude.js +54 -50
- package/lib/yoke/adapters/codex-query-contract.js +24 -0
- package/lib/yoke/adapters/codex.js +53 -111
- package/lib/yoke/adapters/kiro.js +3 -1
- package/lib/yoke/adapters/muse-query-contract.js +7 -0
- package/lib/yoke/adapters/muse.js +122 -0
- package/lib/yoke/auth-events.js +17 -0
- package/lib/yoke/claude-hook-installer.js +271 -0
- package/lib/yoke/claude-jsonl-watcher.js +204 -0
- package/lib/yoke/claude-maintenance.js +55 -0
- package/lib/yoke/claude-permission-patterns.js +53 -0
- package/lib/yoke/claude-response-recovery.js +155 -0
- package/lib/yoke/claude-transcript-hygiene.js +34 -0
- package/lib/yoke/claude-worker-startup.js +28 -0
- package/lib/yoke/clay-skill-install.js +33 -0
- package/lib/yoke/cli-sessions.js +300 -0
- package/lib/yoke/codex-app-server.js +7 -12
- package/lib/yoke/codex-idle-recovery.js +81 -1
- package/lib/yoke/codex-recovery-admission.js +164 -0
- package/lib/yoke/configuration.js +13 -0
- package/lib/yoke/discovery.js +70 -0
- package/lib/yoke/errors.js +8 -0
- package/lib/yoke/host-environment.js +7 -0
- package/lib/yoke/index.js +9 -51
- package/lib/yoke/interface.js +116 -0
- package/lib/yoke/kiro-acp-server.js +5 -5
- package/lib/yoke/legacy-sessions.js +89 -0
- package/lib/yoke/login-protocol.js +25 -0
- package/lib/yoke/maintenance.js +12 -0
- package/lib/yoke/mcp-proxy.js +32 -0
- package/lib/yoke/mcp-source-adapters.js +91 -0
- package/lib/yoke/mcp-source-context.js +42 -0
- package/lib/yoke/mcp-source-discovery.js +169 -0
- package/lib/yoke/mcp-source-plugins.js +68 -0
- package/lib/yoke/mcp-source-restrictions.js +47 -0
- package/lib/yoke/mcp-tool-policy-normalize.js +28 -0
- package/lib/yoke/model-preferences.js +6 -0
- package/lib/yoke/muse-artifacts.js +36 -0
- package/lib/yoke/muse-controls.js +50 -0
- package/lib/yoke/muse-input.js +64 -0
- package/lib/yoke/muse-items.js +157 -0
- package/lib/yoke/muse-policy.js +52 -0
- package/lib/yoke/muse-query.js +428 -0
- package/lib/yoke/muse-runtime.js +211 -0
- package/lib/yoke/muse-services.js +79 -0
- package/lib/yoke/muse-skills.js +79 -0
- package/lib/yoke/muse-turns.js +71 -0
- package/lib/yoke/options/acp.js +2 -0
- package/lib/yoke/options/antigravity.js +2 -0
- package/lib/yoke/options/claude.js +21 -0
- package/lib/yoke/options/codex-defaults.js +29 -0
- package/lib/yoke/options/codex-settings.js +21 -0
- package/lib/yoke/options/codex.js +6 -0
- package/lib/yoke/options/kiro-defaults.js +22 -0
- package/lib/yoke/options/kiro.js +5 -0
- package/lib/yoke/options/muse.js +5 -0
- package/lib/yoke/provider-contract.js +5 -0
- package/lib/yoke/providers/antigravity-executable.js +8 -0
- package/lib/yoke/providers/claude-executable.js +40 -0
- package/lib/yoke/providers/claude-model-descriptions.js +14 -0
- package/lib/yoke/providers/claude-process.js +97 -0
- package/lib/yoke/providers/claude-provision.js +75 -0
- package/lib/yoke/providers/codex-errors.js +8 -0
- package/lib/yoke/providers/muse-executable.js +17 -0
- package/lib/yoke/provisioning.js +6 -0
- package/lib/yoke/query-options.js +10 -0
- package/lib/yoke/response-recovery.js +41 -0
- package/lib/yoke/sdk-mcp-descriptors.js +37 -0
- package/lib/yoke/sdk-server-factory.js +7 -0
- package/lib/yoke/session-history.js +8 -0
- package/lib/yoke/session-tool-transport.js +34 -13
- package/lib/yoke/settings.js +23 -0
- package/lib/yoke/source-locations.js +4 -0
- package/lib/yoke/title-query.js +18 -7
- package/lib/yoke/tui-transcript-index.js +132 -0
- package/lib/yoke/usage.js +48 -0
- package/lib/yoke/user-input.js +13 -0
- package/lib/yoke/vendor-registry.js +81 -1
- package/package.json +10 -4
package/lib/os-users.js
CHANGED
|
@@ -153,6 +153,19 @@ function fsAsUser(op, args, osUserInfo) {
|
|
|
153
153
|
});
|
|
154
154
|
var binOutput = execFileSync(binSpawn.command, binSpawn.args, binSpawn.options);
|
|
155
155
|
return { buffer: Buffer.from(binOutput.trim(), "base64") };
|
|
156
|
+
} else if (op === 'write_exact') {
|
|
157
|
+
script = [
|
|
158
|
+
"var fs = require('fs'), crypto = require('crypto'), path = require('path');",
|
|
159
|
+
"var f = " + JSON.stringify(args.file) + "; var expected = " + JSON.stringify(args.expected) + ";",
|
|
160
|
+
"var root = " + JSON.stringify(args.root) + "; var relative = path.relative(root, f);",
|
|
161
|
+
"function checkPath() { if (fs.realpathSync(root) !== root || path.isAbsolute(relative) || relative.split(path.sep).indexOf('..') >= 0) throw new Error('The canonical file boundary changed.'); var at = root; relative.split(path.sep).forEach(function (part) { at = path.join(at, part); if (fs.lstatSync(at).isSymbolicLink()) throw new Error('File parent symlinks are unavailable.'); }); }",
|
|
162
|
+
"checkPath();",
|
|
163
|
+
"var fd = fs.openSync(f, fs.constants.O_RDWR | fs.constants.O_NOFOLLOW);",
|
|
164
|
+
"try { var s = fs.fstatSync(fd);",
|
|
165
|
+
"if (!s.isFile() || s.nlink !== 1 || s.dev !== expected.dev || s.ino !== expected.ino || s.mode !== expected.mode || s.uid !== expected.uid || s.gid !== expected.gid || s.size !== expected.size || s.mtimeMs !== expected.mtime || crypto.createHash('sha256').update(fs.readFileSync(fd, 'utf8')).digest('hex') !== expected.hash) throw new Error('The exact file revision changed.');",
|
|
166
|
+
"checkPath(); fs.ftruncateSync(fd, 0); var bytes = Buffer.from(" + JSON.stringify(args.content) + ", 'utf8'), offset = 0; while (offset < bytes.length) { var written = fs.writeSync(fd, bytes, offset, bytes.length - offset, offset); if (!written) throw new Error('File write made no progress.'); offset += written; } fs.fsyncSync(fd); } finally { fs.closeSync(fd); }",
|
|
167
|
+
"process.stdout.write(JSON.stringify({ ok: true }));",
|
|
168
|
+
].join(' ');
|
|
156
169
|
} else if (op === "write") {
|
|
157
170
|
script = [
|
|
158
171
|
"var fs = require('fs');",
|
|
@@ -267,18 +280,18 @@ function isHomeDirectory(dirPath) {
|
|
|
267
280
|
* Uses setfacl to add recursive + default ACL entries.
|
|
268
281
|
* Skips home directories to avoid slow recursive ACL on large trees.
|
|
269
282
|
*/
|
|
270
|
-
function grantProjectAccess(projectPath, linuxUser) {
|
|
283
|
+
function grantProjectAccess(projectPath, linuxUser, options) {
|
|
271
284
|
if (isHomeDirectory(projectPath)) {
|
|
272
285
|
console.log("[os-users] Skipping ACL for home directory: " + projectPath);
|
|
273
|
-
return;
|
|
286
|
+
return true;
|
|
274
287
|
}
|
|
275
288
|
if (!isSafeLinuxUsername(linuxUser)) {
|
|
276
289
|
console.error("[os-users] Invalid Linux username for ACL grant: " + linuxUser);
|
|
277
|
-
return;
|
|
290
|
+
return false;
|
|
278
291
|
}
|
|
279
292
|
var cacheKey = aclCacheKey(projectPath, linuxUser);
|
|
280
|
-
if (_aclGrantCache[cacheKey]) {
|
|
281
|
-
return;
|
|
293
|
+
if (_aclGrantCache[cacheKey] && !(options && options.force)) {
|
|
294
|
+
return true;
|
|
282
295
|
}
|
|
283
296
|
try {
|
|
284
297
|
// Recursive ACL for existing files
|
|
@@ -295,6 +308,7 @@ function grantProjectAccess(projectPath, linuxUser) {
|
|
|
295
308
|
});
|
|
296
309
|
_aclGrantCache[cacheKey] = true;
|
|
297
310
|
console.log("[os-users] Granted ACL access for " + linuxUser + " on " + projectPath);
|
|
311
|
+
return true;
|
|
298
312
|
} catch (e) {
|
|
299
313
|
delete _aclGrantCache[cacheKey];
|
|
300
314
|
var errMsg = (e.stderr || e.message || "").toString();
|
|
@@ -305,6 +319,7 @@ function grantProjectAccess(projectPath, linuxUser) {
|
|
|
305
319
|
} else {
|
|
306
320
|
console.error("[os-users] Failed to grant ACL access for " + linuxUser + " on " + projectPath + ": " + errMsg);
|
|
307
321
|
}
|
|
322
|
+
return false;
|
|
308
323
|
}
|
|
309
324
|
}
|
|
310
325
|
|
|
@@ -460,72 +475,11 @@ function getLinuxUserUid(username) {
|
|
|
460
475
|
* hardcoded ~/.local/bin/claude path.
|
|
461
476
|
* Returns true if claude resolves, false otherwise.
|
|
462
477
|
*/
|
|
463
|
-
function
|
|
464
|
-
|
|
465
|
-
try {
|
|
466
|
-
var out = execFileSync("su", ["-", linuxName, "-c", "command -v claude"], {
|
|
467
|
-
encoding: "utf8",
|
|
468
|
-
timeout: 10000,
|
|
469
|
-
stdio: "pipe",
|
|
470
|
-
}).trim();
|
|
471
|
-
return out.length > 0;
|
|
472
|
-
} catch (e) {
|
|
473
|
-
return false;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
/**
|
|
478
|
-
* Install Claude CLI for a Linux user account.
|
|
479
|
-
* Downloads and runs the install script, then ensures PATH is configured.
|
|
480
|
-
* Non-fatal: logs errors but does not throw.
|
|
481
|
-
* Skips entirely when claude is already resolvable for the user, or when
|
|
482
|
-
* CLAY_SKIP_CLAUDE_INSTALL is set (bring-your-own-claude).
|
|
483
|
-
*/
|
|
484
|
-
function installClaudeCli(linuxName) {
|
|
485
|
-
if (process.env.CLAY_SKIP_CLAUDE_INSTALL) {
|
|
486
|
-
console.log("[os-users] CLAY_SKIP_CLAUDE_INSTALL set, skipping Claude CLI install for " + linuxName);
|
|
487
|
-
return;
|
|
488
|
-
}
|
|
489
|
-
if (claudeAvailableForUser(linuxName)) {
|
|
490
|
-
console.log("[os-users] Claude CLI already available for " + linuxName + ", skipping install");
|
|
491
|
-
return;
|
|
492
|
-
}
|
|
493
|
-
try {
|
|
494
|
-
if (!isSafeLinuxUsername(linuxName)) throw new Error("Invalid Linux username");
|
|
495
|
-
// Download and run the Claude CLI install script as the target user
|
|
496
|
-
execFileSync("su", ["-", linuxName, "-c", "curl -fsSL https://claude.ai/install.sh | bash"], {
|
|
497
|
-
encoding: "utf8",
|
|
498
|
-
timeout: 60000,
|
|
499
|
-
stdio: "pipe",
|
|
500
|
-
});
|
|
501
|
-
console.log("[os-users] Claude CLI installed for " + linuxName);
|
|
502
|
-
} catch (e) {
|
|
503
|
-
var msg = (e.stderr || e.message || "").trim();
|
|
504
|
-
console.error("[os-users] Failed to install Claude CLI for " + linuxName + ": " + msg);
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// Append PATH export to the user's shell config if not already present
|
|
509
|
-
try {
|
|
510
|
-
var userInfo = resolveOsUserInfo(linuxName);
|
|
511
|
-
var home = userInfo.home || ("/home/" + linuxName);
|
|
512
|
-
var rcPath = fs.existsSync(path.join(home, ".zshrc")) ? path.join(home, ".zshrc") : path.join(home, ".bashrc");
|
|
513
|
-
var exportLine = 'export PATH="$HOME/.local/bin:$PATH"';
|
|
514
|
-
var existing = "";
|
|
515
|
-
try { existing = fs.readFileSync(rcPath, "utf8"); } catch (e2) {}
|
|
516
|
-
if (existing.indexOf(exportLine) === -1) {
|
|
517
|
-
var prefix = existing && !/\n$/.test(existing) ? "\n" : "";
|
|
518
|
-
fs.appendFileSync(rcPath, prefix + exportLine + "\n", "utf8");
|
|
519
|
-
try { fs.chownSync(rcPath, userInfo.uid, userInfo.gid); } catch (e3) {}
|
|
520
|
-
console.log("[os-users] PATH export appended to " + rcPath + " for " + linuxName);
|
|
521
|
-
} else {
|
|
522
|
-
console.log("[os-users] PATH already configured in " + rcPath + " for " + linuxName);
|
|
523
|
-
}
|
|
524
|
-
} catch (e) {
|
|
525
|
-
var rcMsg = (e.stderr || e.message || "").trim();
|
|
526
|
-
console.error("[os-users] Failed to configure PATH for " + linuxName + ": " + rcMsg);
|
|
527
|
-
}
|
|
478
|
+
function defaultRuntimeProvisioning() {
|
|
479
|
+
return require("./yoke/provisioning").forProvider("claude", { isSafeLinuxUsername: isSafeLinuxUsername, resolveOsUserInfo: resolveOsUserInfo });
|
|
528
480
|
}
|
|
481
|
+
function claudeAvailableForUser(user) { return defaultRuntimeProvisioning().available(user); }
|
|
482
|
+
function installClaudeCli(user) { return defaultRuntimeProvisioning().install(user); }
|
|
529
483
|
|
|
530
484
|
/**
|
|
531
485
|
* Provision a Linux user account for a Clay user.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Pending requests can precede the initial history window. Restate their exact
|
|
2
|
+
// display payloads before snapshot completion, without resolving or replaying
|
|
3
|
+
// any human decision. Client renderers deduplicate by their original IDs.
|
|
4
|
+
function send(session, emit) {
|
|
5
|
+
Object.keys(session.pendingAskUser || {}).forEach(function (id) {
|
|
6
|
+
var pending = session.pendingAskUser[id];
|
|
7
|
+
var input = pending.input || { questions: pending.request && pending.request.questions || [] };
|
|
8
|
+
emit({ type: 'ask_user_pending', toolId: id, input: input });
|
|
9
|
+
});
|
|
10
|
+
Object.keys(session.pendingElicitations || {}).forEach(function (id) {
|
|
11
|
+
var request = session.pendingElicitations[id].request || {};
|
|
12
|
+
emit({ type: 'elicitation_request', requestId: id, serverName: request.serverName, message: request.message,
|
|
13
|
+
mode: request.mode || 'form', url: request.url || null, elicitationId: request.elicitationId || null, requestedSchema: request.requestedSchema || null });
|
|
14
|
+
});
|
|
15
|
+
Object.keys(session.pendingUserDialogs || {}).forEach(function (id) {
|
|
16
|
+
var request = session.pendingUserDialogs[id].request || {};
|
|
17
|
+
emit({ type: 'user_dialog_request', requestId: id, dialogKind: request.dialogKind, payload: request.payload || {}, toolUseId: request.toolUseID || null });
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
module.exports = { send: send };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
var provenance = require('./session-provenance');
|
|
2
|
+
|
|
3
|
+
// This opt-in protocol only describes an authenticated ordinary pane snapshot.
|
|
4
|
+
// It grants no action authority and is never a visible-render acknowledgement.
|
|
5
|
+
function begin(ctx, ws, msg) {
|
|
6
|
+
if (!Object.prototype.hasOwnProperty.call(msg, 'paneActivation')) {
|
|
7
|
+
ws._clayPaneActivation = null;
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
var request = msg.paneActivation;
|
|
11
|
+
var session = ctx.sm.sessions.get(msg.id);
|
|
12
|
+
var userId = ws._clayUser && ws._clayUser.id || null;
|
|
13
|
+
var worker = provenance.isWorker(session);
|
|
14
|
+
var parentSession = worker && Array.from(ctx.sm.sessions.values()).find(function (parent) { return parent.sessionOriginId === session.sessionProvenance.parentSessionOriginId && !provenance.isWorker(parent); });
|
|
15
|
+
var identity = session && {
|
|
16
|
+
requestId: request && request.requestId,
|
|
17
|
+
generation: request && request.generation,
|
|
18
|
+
projectSlug: ctx.slug,
|
|
19
|
+
viewerId: userId,
|
|
20
|
+
ownerId: session.ownerId || null,
|
|
21
|
+
sessionId: session.localId,
|
|
22
|
+
sessionOriginId: session.sessionOriginId,
|
|
23
|
+
role: worker ? 'worker' : 'driver',
|
|
24
|
+
workerGeneration: worker ? session.sessionProvenance.generation : null,
|
|
25
|
+
parentSessionOriginId: worker ? session.sessionProvenance.parentSessionOriginId : null,
|
|
26
|
+
groupStamp: groupStamp(),
|
|
27
|
+
};
|
|
28
|
+
function groupStamp() {
|
|
29
|
+
var group = ctx.splitStore && ctx.splitStore.groupForMember(msg.id);
|
|
30
|
+
return group ? JSON.stringify({ id: group.id, members: group.members, pair: group.pair || null }) : null;
|
|
31
|
+
}
|
|
32
|
+
function allowed() {
|
|
33
|
+
if (typeof ws._clayAuthCheck === 'function' && !ws._clayAuthCheck()) return false;
|
|
34
|
+
var access = ctx.getProjectAccess ? ctx.getProjectAccess() : { visibility: 'public' };
|
|
35
|
+
if (!ctx.usersModule.isMultiUser()) return !session.ownerId;
|
|
36
|
+
return !!userId &&
|
|
37
|
+
!!ctx.usersModule.findUserById(userId) &&
|
|
38
|
+
ctx.usersModule.canAccessProject(userId, access) &&
|
|
39
|
+
ctx.usersModule.canAccessSession(userId, session, access);
|
|
40
|
+
}
|
|
41
|
+
var valid = request && typeof request === 'object' &&
|
|
42
|
+
typeof request.requestId === 'string' && /^[a-zA-Z0-9_-]{1,96}$/.test(request.requestId) &&
|
|
43
|
+
Number.isSafeInteger(request.generation) && request.generation > (ws._clayPaneActivationGeneration || 0) &&
|
|
44
|
+
ws._clayPane === true && !ws._clayContextual && !ctx.isMate && ws.readyState === 1 &&
|
|
45
|
+
session && session.sessionOriginId && !session.isMate &&
|
|
46
|
+
(session.runtimeMode || session.mode || 'gui') === 'gui' && !session.tuiSuspended &&
|
|
47
|
+
request.projectSlug === identity.projectSlug && request.viewerId === identity.viewerId &&
|
|
48
|
+
request.ownerId === identity.ownerId && request.sessionOriginId === identity.sessionOriginId &&
|
|
49
|
+
request.role === identity.role && request.workerGeneration === identity.workerGeneration &&
|
|
50
|
+
(!ctx.splitStore || request.groupStamp === identity.groupStamp && request.parentSessionOriginId === identity.parentSessionOriginId) &&
|
|
51
|
+
(!worker || Number.isSafeInteger(identity.workerGeneration) && identity.workerGeneration > 0) && allowed();
|
|
52
|
+
if (!valid) {
|
|
53
|
+
// Do not disclose whether a foreign or removed session exists.
|
|
54
|
+
ctx.sendTo(ws, { type: 'pane_activation_rejected', requestId: request && typeof request.requestId === 'string' ? request.requestId.slice(0, 96) : null });
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
ws._clayPaneActivationGeneration = identity.generation;
|
|
58
|
+
ws._clayPaneActivation = identity;
|
|
59
|
+
function current() {
|
|
60
|
+
return ws.readyState === 1 && ws._clayPane === true && !ws._clayContextual && !ctx.isMate && !session.isMate &&
|
|
61
|
+
ws._clayPaneActivation === identity &&
|
|
62
|
+
ctx.sm.sessions.get(msg.id) === session && session.sessionOriginId === identity.sessionOriginId &&
|
|
63
|
+
(ws._clayUser && ws._clayUser.id || null) === userId &&
|
|
64
|
+
(session.ownerId || null) === identity.ownerId &&
|
|
65
|
+
(session.runtimeMode || session.mode || 'gui') === 'gui' && !session.tuiSuspended &&
|
|
66
|
+
(provenance.isWorker(session) ? 'worker' : 'driver') === identity.role &&
|
|
67
|
+
(provenance.isWorker(session) ? session.sessionProvenance.generation : null) === identity.workerGeneration &&
|
|
68
|
+
groupStamp() === identity.groupStamp &&
|
|
69
|
+
(!worker || parentSession && ctx.sm.sessions.get(parentSession.localId) === parentSession &&
|
|
70
|
+
session.sessionProvenance.parentSessionOriginId === identity.parentSessionOriginId && parentSession.sessionOriginId === identity.parentSessionOriginId &&
|
|
71
|
+
!provenance.isWorker(parentSession) && (parentSession.ownerId || null) === identity.ownerId) &&
|
|
72
|
+
allowed();
|
|
73
|
+
}
|
|
74
|
+
function send(message) {
|
|
75
|
+
if (current() && ws._clayActiveSession === session.localId) ctx.sendTo(ws, Object.assign({}, message, { paneActivation: identity }));
|
|
76
|
+
}
|
|
77
|
+
var historyItems = [], historyText = 0;
|
|
78
|
+
// Only synchronous replay uses this bounded envelope. Each actual disclosure
|
|
79
|
+
// rechecks live authorization; nothing is cached across a send or async work.
|
|
80
|
+
send.history = function (message) {
|
|
81
|
+
if (message.type === 'history_done') {
|
|
82
|
+
flushHistory();
|
|
83
|
+
send(message);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
historyItems.push(message);
|
|
87
|
+
historyText += typeof message.text === 'string' ? message.text.length : 0;
|
|
88
|
+
if (historyItems.length >= 100 || historyText >= 65536) flushHistory();
|
|
89
|
+
};
|
|
90
|
+
function flushHistory() {
|
|
91
|
+
if (!historyItems.length) return;
|
|
92
|
+
var items = historyItems;
|
|
93
|
+
historyItems = []; historyText = 0;
|
|
94
|
+
send({ type: 'pane_history_batch', items: items });
|
|
95
|
+
}
|
|
96
|
+
if (current()) ctx.sendTo(ws, { type: 'pane_activation_started', paneActivation: identity });
|
|
97
|
+
ws._clayPaneActivationValid = function () { return current() && ws._clayActiveSession === identity.sessionId; };
|
|
98
|
+
return {
|
|
99
|
+
validate: function () {
|
|
100
|
+
if (current()) return true;
|
|
101
|
+
ctx.sendTo(ws, { type: 'pane_activation_rejected', requestId: identity.requestId });
|
|
102
|
+
ws._clayPaneActivation = null;
|
|
103
|
+
return false;
|
|
104
|
+
},
|
|
105
|
+
send: send,
|
|
106
|
+
finish: function () {
|
|
107
|
+
// Includes history listeners, status, permissions and context sources.
|
|
108
|
+
// Consumers still need to observe their own DOM/composer readiness.
|
|
109
|
+
if (ws._clayActiveSession === session.localId) send({ type: 'pane_activation_snapshot_done' });
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = { begin: begin };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
var AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage;
|
|
2
|
+
var requests = new AsyncLocalStorage();
|
|
3
|
+
|
|
4
|
+
function same(a, b) {
|
|
5
|
+
return !!a && !!b && ['requestId', 'generation', 'projectSlug', 'viewerId', 'ownerId', 'sessionId', 'sessionOriginId', 'role', 'workerGeneration', 'groupStamp', 'parentSessionOriginId'].every(function (key) { return a[key] === b[key]; });
|
|
6
|
+
}
|
|
7
|
+
function run(ws, msg, dispatch) {
|
|
8
|
+
var binding = ws._clayPaneActivation;
|
|
9
|
+
if (!binding && msg.paneBinding || binding && msg.type === 'switch_session' && !msg.paneActivation) return;
|
|
10
|
+
if (binding && ['switch_session', 'ping', 'pong', 'clay_ui_ready'].indexOf(msg.type) < 0) {
|
|
11
|
+
if (!same(binding, msg.paneBinding) || !ws._clayPaneActivationValid || !ws._clayPaneActivationValid()) return;
|
|
12
|
+
}
|
|
13
|
+
return requests.run({ ws: ws, binding: binding || null }, function () { return dispatch(ws, msg); });
|
|
14
|
+
}
|
|
15
|
+
// A direct asynchronous reply retains the request's original binding. Live
|
|
16
|
+
// provider events instead supply their authoritative source session explicitly.
|
|
17
|
+
function outbound(ws, message, sessionId) {
|
|
18
|
+
if (!message || message.paneActivation || /^pane_activation_/.test(message.type)) return message;
|
|
19
|
+
var scope = requests.getStore();
|
|
20
|
+
var binding = sessionId === undefined ? scope && scope.ws === ws && scope.binding : ws._clayPaneActivation;
|
|
21
|
+
if (!binding) return message;
|
|
22
|
+
if (sessionId !== undefined && binding.sessionId !== sessionId) return null;
|
|
23
|
+
if (!same(binding, ws._clayPaneActivation) || !ws._clayPaneActivationValid || !ws._clayPaneActivationValid()) return null;
|
|
24
|
+
return Object.assign({}, message, { paneActivation: binding });
|
|
25
|
+
}
|
|
26
|
+
module.exports = { run: run, outbound: outbound, same: same };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// This projection is an allowlist. Never add runtime/session/path objects here.
|
|
2
|
+
function projectMetadata(status, access, users) {
|
|
3
|
+
if (!status || status.isMate || !access || access.error) return null;
|
|
4
|
+
var owner = access.ownerId && users.findUserById(access.ownerId);
|
|
5
|
+
return {
|
|
6
|
+
slug: status.slug,
|
|
7
|
+
project: status.title || status.project || status.slug,
|
|
8
|
+
title: status.title || status.project || status.slug,
|
|
9
|
+
projectOwnerId: owner ? owner.id : null,
|
|
10
|
+
ownerName: owner ? (owner.displayName || owner.username) : "Ownership needs recovery",
|
|
11
|
+
visibility: access.visibility,
|
|
12
|
+
contentAccess: false,
|
|
13
|
+
administrativeAccess: true,
|
|
14
|
+
ownershipRecovery: access.ownershipRecovery === "required" || !owner,
|
|
15
|
+
runtimeHealth: "Registered",
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function metadataFor(user, status, access, users) {
|
|
20
|
+
if (!user || !users.findUserById(user.id) || user.role !== "admin" || !status || status.isMate || /^mate-/.test(status.slug)) return null;
|
|
21
|
+
return projectMetadata(status, access, users);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function escape(value) {
|
|
25
|
+
return String(value || "").replace(/[&<>"']/g, function (character) {
|
|
26
|
+
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[character];
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function page(metadata, projects) {
|
|
31
|
+
var privacyText = metadata.ownershipRecovery
|
|
32
|
+
? "Content remains restricted because no verified owner is available. Ask the server operator to verify the account’s OS UID and real home/project mapping, then run the documented ownership repair."
|
|
33
|
+
: "Conversations, files and terminals are private. Ask the owner to share project access.";
|
|
34
|
+
var accessText = metadata.ownershipRecovery
|
|
35
|
+
? "Ownership needs recovery. The server operator must independently verify or repair the authoritative account-to-OS mapping before ownership repair can succeed. Ambiguous records stay restricted; administrator status cannot claim this project. See the Project ownership and administrator access guide."
|
|
36
|
+
: "Only the project owner can change ownership, visibility and sharing. Administrator status does not grant access to private content.";
|
|
37
|
+
var strip = projects.filter(function (project) { return !project.isMate; }).map(function (project) {
|
|
38
|
+
return '<a class="icon-strip-item' + (project.slug === metadata.slug ? ' active' : '') + '" href="/p/' + encodeURIComponent(project.slug) + '/" title="' + escape(project.title || project.project) + '"><span class="icon-strip-name">' + escape((project.title || project.project || project.slug).substring(0, 2)) + '</span></a>';
|
|
39
|
+
}).join("");
|
|
40
|
+
return '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>' + escape(metadata.title) + ' · Clay</title>' +
|
|
41
|
+
'<script src="/appearance-bootstrap.js"></script><link rel="stylesheet" href="/style.css"><link rel="stylesheet" href="/css/project-admin-overview.css"></head><body>' +
|
|
42
|
+
'<div id="layout"><header id="top-bar"><a href="/" class="top-bar-brand-link" aria-label="Clay home"><span class="top-bar-brand-wordmark"></span></a></header>' +
|
|
43
|
+
'<div id="layout-body"><nav id="icon-strip" aria-label="Projects"><div class="icon-strip-projects">' + strip + '</div></nav>' +
|
|
44
|
+
'<main id="main-area" class="project-admin-overview"><div class="project-admin-canvas"><h1>' + escape(metadata.title) + '</h1>' +
|
|
45
|
+
'<div role="tablist" aria-label="Project information"><button type="button" role="tab" aria-selected="true" id="overview-tab" aria-controls="overview-panel">Overview</button><button type="button" role="tab" aria-selected="false" id="access-tab" aria-controls="access-panel">Access</button></div>' +
|
|
46
|
+
'<section id="overview-panel" role="tabpanel" aria-labelledby="overview-tab"><dl><dt>Owner</dt><dd>' + escape(metadata.ownerName) + '</dd><dt>Visibility</dt><dd>' + escape(metadata.visibility) + '</dd><dt>Status</dt><dd>' + (metadata.ownershipRecovery ? 'Ownership needs recovery' : escape(metadata.runtimeHealth)) + '</dd><dt>Your access</dt><dd>Administrative metadata only</dd></dl><p>' + escape(privacyText) + '</p><p class="project-admin-secondary">Resource usage is unavailable for this project.</p></section>' +
|
|
47
|
+
'<section id="access-panel" role="tabpanel" aria-labelledby="access-tab" hidden><h2>Administrative metadata only</h2><p>' + escape(accessText) + '</p></section></div></main></div></div>' +
|
|
48
|
+
'<script type="module" src="/modules/project-admin-overview.js"></script></body></html>';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { metadataFor: metadataFor, page: page };
|
package/lib/project-ask-user.js
CHANGED
|
@@ -25,8 +25,9 @@ function attachAskUser(ctx) {
|
|
|
25
25
|
respond.cancel("Autonomous mode. Make your own decision.");
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
|
-
var questions = planningQuestions(boundSession, request.questions);
|
|
28
|
+
var questions = ["mcp-import", "product-control"].indexOf(request.source) >= 0 ? request.questions : planningQuestions(boundSession, request.questions);
|
|
29
29
|
var input = { questions: questions };
|
|
30
|
+
if (request.source === 'mcp-import') input.source = 'mcp-import';
|
|
30
31
|
if (!boundSession.pendingAskUser) boundSession.pendingAskUser = {};
|
|
31
32
|
var pending = {
|
|
32
33
|
input: input,
|
|
@@ -38,15 +39,18 @@ function attachAskUser(ctx) {
|
|
|
38
39
|
};
|
|
39
40
|
boundSession.pendingAskUser[request.id] = pending;
|
|
40
41
|
if (respond && typeof respond.onSettle === "function") {
|
|
41
|
-
respond.onSettle(function () {
|
|
42
|
+
respond.onSettle(function (result) {
|
|
43
|
+
if (request.source === 'product-control' && result.status !== 'submitted') ctx.record(boundSession, { type: 'product_control_question_expired', toolId: request.id, error: 'This exact change review expired or was cancelled. No change was authorized.' });
|
|
44
|
+
if (request.source === 'mate-schedule' && result.status !== 'submitted') ctx.record(boundSession, { type: 'mate_schedule_question_expired', toolId: request.id, error: 'This schedule review expired. Request a new review.' });
|
|
45
|
+
if (request.source === 'mcp-import' && result.status !== 'submitted') ctx.record(boundSession, { type: 'mcp_import_consent_expired', toolId: request.id, source: 'mcp-import', error: 'Import consent expired or was cancelled. No connection was imported.' });
|
|
42
46
|
if (boundSession.pendingAskUser && boundSession.pendingAskUser[request.id] === pending) delete boundSession.pendingAskUser[request.id];
|
|
43
47
|
});
|
|
44
48
|
}
|
|
45
49
|
if (!askUserIdentity.hasRecordedAskUserRequest(boundSession.history, request.id, input)) {
|
|
46
|
-
ctx.record(boundSession, { type: "tool_executing", id: request.id, name: "AskUserQuestion", input: input });
|
|
50
|
+
ctx.record(boundSession, { type: "tool_executing", id: request.id, name: "AskUserQuestion", input: input, source: request.source });
|
|
47
51
|
}
|
|
48
52
|
var router = ctx.getWorkerQuestionRouter && ctx.getWorkerQuestionRouter();
|
|
49
|
-
if (router) router.routeQuestion(boundSession, request, respond, function (answers, driverId) {
|
|
53
|
+
if (router && request.source !== "mcp-import" && request.source !== "mate-schedule" && request.source !== "product-control") router.routeQuestion(boundSession, request, respond, function (answers, driverId) {
|
|
50
54
|
var displayAnswers = {};
|
|
51
55
|
request.questions.forEach(function (question, index) { displayAnswers[index] = answers[question.id].join(", "); });
|
|
52
56
|
ctx.record(boundSession, { type: "ask_user_answered", toolId: request.id, answers: displayAnswers, answeredByDriver: driverId });
|
|
@@ -3,7 +3,7 @@ var path = require("path");
|
|
|
3
3
|
var usersModule = require("./users");
|
|
4
4
|
var userPresence = require("./user-presence");
|
|
5
5
|
var emailAccounts = require("./email-accounts");
|
|
6
|
-
var
|
|
6
|
+
var providerSettings = require("./yoke/settings");
|
|
7
7
|
var yoke = require("./yoke");
|
|
8
8
|
var permissionModes = require("./session-permission-mode");
|
|
9
9
|
var sessionProvenance = require("./session-provenance");
|
|
@@ -143,6 +143,9 @@ function attachConnection(ctx) {
|
|
|
143
143
|
var _warmedUp = false;
|
|
144
144
|
|
|
145
145
|
function findRestoredActiveSession(ws, wsUser, allSessions) {
|
|
146
|
+
if (ws._clayContextual && ws._clayPaneSession) {
|
|
147
|
+
return { active: allSessions.find(function (session) { return session.localId === ws._clayPaneSession; }) || null, storedPresence: null };
|
|
148
|
+
}
|
|
146
149
|
var active = null;
|
|
147
150
|
var presenceKey = wsUser ? wsUser.id : "_default";
|
|
148
151
|
var storedPresence = userPresence.getPresence(slug, presenceKey);
|
|
@@ -202,7 +205,7 @@ function attachConnection(ctx) {
|
|
|
202
205
|
} else if (!usersModule.isMultiUser()) {
|
|
203
206
|
allSessions = allSessions.filter(function (s) { return !s.ownerId; });
|
|
204
207
|
}
|
|
205
|
-
var restoredState = ws._clayPane
|
|
208
|
+
var restoredState = ws._clayPane && !ws._clayContextual
|
|
206
209
|
? { active: null, storedPresence: null }
|
|
207
210
|
: findRestoredActiveSession(ws, wsUser, allSessions);
|
|
208
211
|
var restoredActive = restoredState.active;
|
|
@@ -217,7 +220,7 @@ function attachConnection(ctx) {
|
|
|
217
220
|
sm.saveSessionFile(restoredActive);
|
|
218
221
|
}
|
|
219
222
|
var initialModels = (sm.modelsByVendor && sm.modelsByVendor[initialVendor]) || sm.availableModels || [];
|
|
220
|
-
sendTo(ws, { type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, dangerouslySkipPermissions: dangerouslySkipPermissions, osUsers: osUsers, lanHost: lanHost, projectCount: _filteredProjects.length, projects: _filteredProjects, projectOwnerId: projectOwnerId, ownerLocked: ownerLocked, vendors: yoke.
|
|
223
|
+
sendTo(ws, { type: "info", controlRuntimeId: require("./clay-control-target-projection").runtime(sm), cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, dangerouslySkipPermissions: dangerouslySkipPermissions, osUsers: osUsers, lanHost: lanHost, projectCount: _filteredProjects.length, projects: _filteredProjects, projectOwnerId: projectOwnerId, ownerLocked: ownerLocked, vendors: require("./yoke/vendor-registry").publicInfo(), isMate: isMate, mateId: isMate ? mateId : null, mateDisplayName: isMate ? (mateDisplayName || title || project || "Mate") : "" });
|
|
221
224
|
if (typeof sendCursorSharingState === "function") sendCursorSharingState(ws);
|
|
222
225
|
// Update notifications are pushed on a scheduled interval (see
|
|
223
226
|
// scheduleUpdateBroadcast). We no longer push on connect to avoid
|
|
@@ -234,8 +237,8 @@ function attachConnection(ctx) {
|
|
|
234
237
|
var initialPermissionState = permissionModes.clientState(restoredActive, sm.currentPermissionMode, sm.defaultVendor);
|
|
235
238
|
sendTo(ws, { type: "config_state", model: initialModelInfo.model, vendor: initialVendor, sessionId: restoredActive ? restoredActive.localId : null, mode: initialPermissionState.requestedPermissionMode, effectivePermissionMode: initialPermissionState.effectivePermissionMode, permissionCapabilities: initialPermissionState.permissionCapabilities, mcpPermissionModeOverrides: initialPermissionState.mcpPermissionModeOverrides, effort: initialEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
|
|
236
239
|
sendTo(ws, { type: "last_vendor", vendor: sm.lastVendor || "" });
|
|
237
|
-
|
|
238
|
-
sendTo(ws, { type: "term_list", terminals: tm.list() });
|
|
240
|
+
providerSettings.messages(sm).forEach(function(message) { sendTo(ws, message); });
|
|
241
|
+
sendTo(ws, { type: "term_list", terminals: tm.list(true) });
|
|
239
242
|
// Any login flow already running in this project, so a reconnecting client
|
|
240
243
|
// re-attaches to the existing login terminal instead of starting another.
|
|
241
244
|
if (_vendorLogin) _vendorLogin.sendStateTo(ws);
|
|
@@ -267,7 +270,7 @@ function attachConnection(ctx) {
|
|
|
267
270
|
var presenceKey = wsUser ? wsUser.id : "_default";
|
|
268
271
|
var storedPresence = restoredState.storedPresence;
|
|
269
272
|
var autoCreated = false;
|
|
270
|
-
if (!active && !ws._clayPane) {
|
|
273
|
+
if (!active && (!ws._clayPane || (ws._clayContextual && !ws._clayPaneSession))) {
|
|
271
274
|
var autoOpts = {};
|
|
272
275
|
if (wsUser && usersModule.isMultiUser()) autoOpts.ownerId = wsUser.id;
|
|
273
276
|
active = sm.createSession(autoOpts, ws);
|
|
@@ -301,12 +304,13 @@ function attachConnection(ctx) {
|
|
|
301
304
|
}
|
|
302
305
|
sendTo(ws, { type: "history_meta", total: total, from: fromIndex });
|
|
303
306
|
for (var i = fromIndex; i < total; i++) {
|
|
304
|
-
sendTo(ws, hydrateImageRefs(active.history[i]));
|
|
307
|
+
sendTo(ws, Object.assign({}, hydrateImageRefs(require("./yoke/usage").clientEvent(active.history[i], active.vendor || "claude")), { _historyIndex: i }));
|
|
305
308
|
}
|
|
306
|
-
var _lastUsage = null, _lastModelUsage = null, _lastCost = null, _lastStreamInputTokens = null;
|
|
309
|
+
var _lastContext = null, _lastUsage = null, _lastModelUsage = null, _lastCost = null, _lastStreamInputTokens = null;
|
|
307
310
|
for (var _ri = total - 1; _ri >= 0; _ri--) {
|
|
308
311
|
if (active.history[_ri].type === "result") {
|
|
309
|
-
var _r = active.history[_ri];
|
|
312
|
+
var _r = require("./yoke/usage").clientEvent(active.history[_ri], active.vendor || "claude");
|
|
313
|
+
_lastContext = _r.context;
|
|
310
314
|
_lastUsage = _r.usage || null;
|
|
311
315
|
_lastModelUsage = _r.modelUsage || null;
|
|
312
316
|
_lastCost = _r.cost != null ? _r.cost : null;
|
|
@@ -314,7 +318,7 @@ function attachConnection(ctx) {
|
|
|
314
318
|
break;
|
|
315
319
|
}
|
|
316
320
|
}
|
|
317
|
-
sendTo(ws, { type: "history_done", lastUsage: _lastUsage, lastModelUsage: _lastModelUsage, lastCost: _lastCost, lastStreamInputTokens: _lastStreamInputTokens, contextUsage: active.lastContextUsage ||
|
|
321
|
+
sendTo(ws, { type: "history_done", controlHistoryRevision: require("./clay-control-target-projection").digest(active.history || []), sessionId: active.localId, sessionOriginId: active.sessionOriginId || null, lastUsage: _lastUsage, lastModelUsage: _lastModelUsage, lastCost: _lastCost, lastStreamInputTokens: _lastStreamInputTokens, context: _lastContext, contextUsage: require("./yoke/usage").context(active.lastContextUsage, active.vendor || "claude") });
|
|
318
322
|
if (scheduledMessages) scheduledMessages.sendState(active, ws);
|
|
319
323
|
if (pairResultRecovery) pairResultRecovery.sendState(active, ws);
|
|
320
324
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
var fs = require('fs'), path = require('path');
|
|
2
|
+
var projection = require('./clay-control-target-projection');
|
|
3
|
+
function project(service, principal, status, manager) {
|
|
4
|
+
var before = service.rootIdentity(principal, 'git');
|
|
5
|
+
var directory = fs.realpathSync(status.gitDir), stat = fs.statSync(directory), indexPath = path.join(directory, 'index');
|
|
6
|
+
var index = fs.existsSync(indexPath) ? require('crypto').createHash('sha256').update(fs.readFileSync(indexPath)).digest('hex') : null;
|
|
7
|
+
var view = { runtimeId: projection.runtime(manager), repository: projection.digest([directory, [stat.dev, stat.ino, stat.mode, stat.uid, stat.gid]]),
|
|
8
|
+
files: status.files.slice(0, 200).map(function (file) { return { path: file.path, revision: projection.digest([status.oid, index, file]) }; }) };
|
|
9
|
+
if (service.rootIdentity(principal, 'git') !== before) throw new Error('Workspace identity changed during projection.');
|
|
10
|
+
return view;
|
|
11
|
+
}
|
|
12
|
+
module.exports = { project: project };
|
package/lib/project-debate.js
CHANGED
|
@@ -774,6 +774,7 @@ function attachDebate(ctx) {
|
|
|
774
774
|
var identityMate = matesModule.getMate(debate.mateCtx, msg.mateId);
|
|
775
775
|
var identityProfile = identityMate && identityMate.profile ? identityMate.profile : (identityMate || {});
|
|
776
776
|
if (!msg.mateName) msg.mateName = identity.name;
|
|
777
|
+
if (!msg.avatarCatalogId) msg.avatarCatalogId = identityProfile.avatarCatalogId || "";
|
|
777
778
|
if (!msg.avatarStyle) msg.avatarStyle = identity.avatarStyle;
|
|
778
779
|
if (!msg.avatarSeed) msg.avatarSeed = identity.avatarSeed;
|
|
779
780
|
if (!msg.avatarColor) msg.avatarColor = identity.avatarColor;
|
|
@@ -12,7 +12,7 @@ function unsafeReason(session, isPaired) {
|
|
|
12
12
|
if (session.homeDebatePlanning || session.homeDebatePhase || session.debateSetupMode || session.debateState || session._debate) return "Debate sessions cannot receive project follow-ups.";
|
|
13
13
|
if (session.loop || session.spawn || session.handoff || session.assignment || session.singleTurn || session._pairDelegation || isPaired) return "Orchestrated sessions cannot receive project follow-ups.";
|
|
14
14
|
if (session.dangerouslySkipPermissions || session.permissionMode === "bypassPermissions") return "Full-access sessions cannot receive project follow-ups.";
|
|
15
|
-
if (session.destroying || session.scheduledMessage || session.rateLimitAutoContinuePending || session.taskStopRequested || session._runtimeRefreshRequested || session.pendingPush && session.pendingPush.length) return "Target session is not safely resumable right now.";
|
|
15
|
+
if (session.destroying || session.scheduledMessage || session.rateLimitAutoContinuePending || session.taskStopRequested || session._pendingMessageDrainPaused || session._runtimeRefreshRequested || session.pendingPush && session.pendingPush.length) return "Target session is not safely resumable right now.";
|
|
16
16
|
return "";
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -36,6 +36,7 @@ function attachDelegatedFollowUp(ctx) {
|
|
|
36
36
|
var paired = !!(ctx.isPairedSession && ctx.isPairedSession(session));
|
|
37
37
|
var reason = unsafeReason(session, paired);
|
|
38
38
|
if (reason) throw new Error(reason);
|
|
39
|
+
if (ctx.isPendingQueuePaused && ctx.isPendingQueuePaused(session)) throw new Error("The Driver queue is stopped; explicit owner resume is required.");
|
|
39
40
|
if (!ctx.canResumeVendor(session.vendor)) throw new Error("Target session provider cannot safely resume this conversation.");
|
|
40
41
|
return { session: session, sessionRef: targetSessionRef, title: session.title || "Existing session" };
|
|
41
42
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Reserve a new project directory before running Git or changing ownership.
|
|
2
|
+
// Filesystem identity checks also keep failed attempts from deleting targets
|
|
3
|
+
// replaced by another actor. Hostile OS/root mutation is outside this boundary.
|
|
4
|
+
var fs = require("fs");
|
|
5
|
+
var path = require("path");
|
|
6
|
+
|
|
7
|
+
function canonical(candidate) {
|
|
8
|
+
var current = path.resolve(candidate);
|
|
9
|
+
var missing = [];
|
|
10
|
+
while (true) {
|
|
11
|
+
try { return path.join.apply(path, [fs.realpathSync(current)].concat(missing)); }
|
|
12
|
+
catch (error) {
|
|
13
|
+
if (error.code !== "ENOENT") throw error;
|
|
14
|
+
// A dangling symlink is an existing object, not a missing directory.
|
|
15
|
+
try { fs.lstatSync(current); throw new Error("A project path contains a dangling symlink."); }
|
|
16
|
+
catch (statError) { if (statError.code !== "ENOENT") throw statError; }
|
|
17
|
+
var parent = path.dirname(current);
|
|
18
|
+
if (parent === current) throw error;
|
|
19
|
+
missing.unshift(path.basename(current));
|
|
20
|
+
current = parent;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function inside(root, candidate) {
|
|
26
|
+
var relative = path.relative(root, candidate);
|
|
27
|
+
return relative !== ".." && relative.indexOf(".." + path.sep) !== 0 && !path.isAbsolute(relative);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function rejectIntersection(target, projects, userId) {
|
|
31
|
+
(projects || []).forEach(function (project) {
|
|
32
|
+
var root = canonical(project.path);
|
|
33
|
+
if (target === root || inside(target, root) || (project.ownerId !== userId && inside(root, target))) {
|
|
34
|
+
throw new Error("The destination intersects a registered project.");
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function claim(options) {
|
|
40
|
+
var slug = options.slug;
|
|
41
|
+
if (!slug || slug === "." || slug === ".." || path.basename(slug) !== slug) throw new Error("Invalid project directory name.");
|
|
42
|
+
var base = canonical(options.baseDir);
|
|
43
|
+
var target = path.join(base, slug);
|
|
44
|
+
rejectIntersection(target, options.projects, options.userId);
|
|
45
|
+
fs.mkdirSync(base, { recursive: true });
|
|
46
|
+
if (fs.realpathSync(options.baseDir) !== base) throw new Error("The project base directory changed.");
|
|
47
|
+
rejectIntersection(target, options.projects, options.userId);
|
|
48
|
+
// EEXIST rejects directories, files, symlinks and concurrent claims alike.
|
|
49
|
+
fs.mkdirSync(target, { mode: 0o700 });
|
|
50
|
+
var identity = fs.lstatSync(target);
|
|
51
|
+
function assertOwned() {
|
|
52
|
+
var current = fs.lstatSync(target);
|
|
53
|
+
if (!current.isDirectory() || current.isSymbolicLink() || current.dev !== identity.dev || current.ino !== identity.ino || fs.realpathSync(base) !== base) {
|
|
54
|
+
throw new Error("The claimed project directory changed.");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function cleanup() {
|
|
58
|
+
try { assertOwned(); } catch (error) { return false; }
|
|
59
|
+
fs.rmSync(target, { recursive: true });
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return { path: target, assertOwned: assertOwned, cleanup: cleanup };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { claim: claim, canonical: canonical };
|