clay-server 5.2.0-beta.3 → 5.2.0-beta.5
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 +28 -0
- package/lib/clay-mate-chat-receipts.js +26 -0
- package/lib/clay-mate-chat.js +87 -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 +237 -0
- package/lib/clay-project-lifecycle.js +154 -0
- package/lib/clay-project-record-input.js +44 -0
- package/lib/clay-project-records.js +83 -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 +96 -0
- package/lib/clay-workspace-controls.js +83 -0
- package/lib/clay-workspace-input.js +17 -0
- package/lib/clay-workspace-receipts.js +26 -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 +73 -221
- package/lib/issues-service.js +29 -15
- package/lib/knowledge-db-import.js +5 -4
- package/lib/knowledge-db-service.js +9 -6
- 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-creation-mcp-server.js +2 -0
- package/lib/mate-knowledge-canonical-access.js +10 -0
- package/lib/mate-knowledge-service.js +7 -4
- 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-query-bridge.js +30 -0
- package/lib/mcp-remote-connections.js +74 -20
- package/lib/mcp-source-adapters.js +91 -0
- package/lib/mcp-source-context.js +42 -0
- package/lib/mcp-source-discovery.js +169 -0
- package/lib/mcp-source-plugins.js +68 -0
- package/lib/mcp-source-restrictions.js +47 -0
- package/lib/mcp-tool-policy.js +34 -0
- package/lib/notes-checklist.js +50 -0
- package/lib/notes.js +49 -3
- package/lib/os-users.js +20 -5
- package/lib/project-admin-overview.js +51 -0
- package/lib/project-ask-user.js +8 -4
- 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 +13 -87
- package/lib/project-full-access.js +23 -2
- 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 +8 -0
- package/lib/project-mcp.js +54 -16
- package/lib/project-models.js +14 -31
- package/lib/project-notifications.js +74 -16
- package/lib/project-ownership.js +77 -0
- package/lib/project-pair-result-delivery.js +14 -3
- package/lib/project-pending-message-queue.js +1 -0
- package/lib/project-product-control.js +87 -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 +63 -64
- 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 +8 -3
- package/lib/project-session-github.js +63 -19
- package/lib/project-session-history-actions.js +165 -0
- package/lib/project-session-linear.js +14 -6
- package/lib/project-session-notes.js +7 -3
- package/lib/project-session-runtime-actions.js +63 -0
- package/lib/project-session-vendor.js +54 -0
- package/lib/project-sessions.js +68 -241
- package/lib/project-shared-browser.js +56 -12
- package/lib/project-user-message.js +30 -1
- package/lib/project-workspace-operations.js +126 -0
- package/lib/project-workspace-query.js +9 -2
- package/lib/project.js +144 -37
- package/lib/public/app.js +5 -0
- package/lib/public/appearance-bootstrap.js +14 -0
- package/lib/public/css/clay-tool-activity.css +51 -0
- package/lib/public/css/clay-workspace.css +29 -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/mate-avatar-gallery.css +12 -0
- package/lib/public/css/mate-conversation.css +10 -5
- 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 +61 -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 +8 -1
- package/lib/public/modules/app-message-router.js +4 -0
- package/lib/public/modules/app-messages.js +31 -5
- package/lib/public/modules/app-projects.js +5 -0
- package/lib/public/modules/app-rendering.js +1 -1
- package/lib/public/modules/avatar.js +15 -41
- package/lib/public/modules/chat-render-runtime.js +10 -1
- package/lib/public/modules/clay-browser-links.js +47 -0
- package/lib/public/modules/clay-log-links.js +1 -1
- package/lib/public/modules/clay-owner-navigation.js +27 -0
- package/lib/public/modules/clay-tool-activity.js +140 -0
- package/lib/public/modules/clay-tool-presentation.js +338 -0
- package/lib/public/modules/command-palette.js +2 -2
- package/lib/public/modules/debate.js +1 -0
- package/lib/public/modules/default-ai.js +11 -3
- package/lib/public/modules/history-replay-batch.js +5 -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/markdown.js +4 -0
- package/lib/public/modules/mate-avatar-catalog.js +97 -0
- 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/pane-browser-bridge.js +20 -0
- package/lib/public/modules/personal-settings-dialog.js +7 -1
- 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-mate-navigation.js +99 -14
- package/lib/public/modules/search-clay-chat.js +16 -2
- 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-work-primary.js +51 -0
- 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 +27 -6
- package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
- package/lib/public/modules/split-view.js +2 -0
- 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 +71 -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/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 +36 -7
- 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/style.css +3 -0
- package/lib/public/sw.js +3 -1
- package/lib/scheduled-task-records.js +3 -1
- package/lib/scheduler.js +2 -0
- package/lib/sdk-bridge.js +228 -23
- package/lib/sdk-message-processor.js +2 -0
- package/lib/sdk-response-recovery.js +155 -0
- package/lib/server-admin.js +63 -16
- package/lib/server-appearance-settings.js +13 -1
- 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 +12 -16
- 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 +77 -0
- package/lib/server-path-access.js +38 -0
- package/lib/server-project-removal.js +22 -0
- package/lib/server-settings.js +4 -1
- package/lib/server.js +144 -41
- package/lib/session-config-transition.js +45 -0
- package/lib/session-folders.js +7 -2
- package/lib/session-github.js +9 -2
- package/lib/session-visibility.js +5 -2
- package/lib/session-work-primary.js +50 -0
- package/lib/sessions.js +13 -0
- 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 +52 -0
- package/lib/users-permissions.js +2 -8
- package/lib/users.js +4 -0
- package/lib/workspace-query-access.js +4 -1
- package/lib/ws-schema.js +8 -2
- package/lib/yoke/acp-agent-profiles.js +2 -2
- package/lib/yoke/adapters/claude.js +47 -8
- package/lib/yoke/adapters/codex.js +52 -111
- package/lib/yoke/claude-worker-startup.js +28 -0
- package/lib/yoke/codex-idle-recovery.js +81 -1
- package/lib/yoke/codex-recovery-admission.js +164 -0
- package/lib/yoke/session-tool-transport.js +3 -0
- package/package.json +5 -1
|
@@ -5,12 +5,14 @@ var browserTools = require("./shared-browser-tools");
|
|
|
5
5
|
var createRuntime = require("./shared-browser-runtime").createRuntime;
|
|
6
6
|
|
|
7
7
|
function attachSharedBrowser(ctx) {
|
|
8
|
+
var storageStore = ctx.storageStore || require("./shared-browser-storage").attachStorageStore(require("./config").CONFIG_DIR);
|
|
8
9
|
var records = new Map();
|
|
9
10
|
var viewers = new Map();
|
|
10
11
|
var destroyed = false;
|
|
11
12
|
function allowed(session, ws) {
|
|
12
13
|
try {
|
|
13
|
-
if (
|
|
14
|
+
if (!session || ctx.sm.sessions.get(session.localId) !== session) return false;
|
|
15
|
+
if (ctx.isMate && ctx.usersModule.isMultiUser() && (!ctx.ownerId || session.ownerId !== ctx.ownerId)) return false;
|
|
14
16
|
if (ctx.usersModule.isMultiUser()) {
|
|
15
17
|
var user = ctx.usersModule.findUserById(session.ownerId);
|
|
16
18
|
if (!user || (ws && (!ws._clayUser || String(ws._clayUser.id) !== String(user.id)))) return false;
|
|
@@ -35,7 +37,7 @@ function attachSharedBrowser(ctx) {
|
|
|
35
37
|
function projection(record) {
|
|
36
38
|
if (!record) return null;
|
|
37
39
|
return { id: record.id, phase: record.phase, control: record.control, epoch: record.epoch, handoff: !!record.handoff,
|
|
38
|
-
url: record.url, width: record.width, height: record.height, activity: record.activity || null, activityHistory: record.activityHistory || [], pointer: record.pointer || null, error: record.error || "" };
|
|
40
|
+
browserLink: record.url ? "browser:" + record.url : null, incognito: record.incognito, personalCookiesAvailable: !!ctx.isMate, url: record.url, width: record.width, height: record.height, activity: record.activity || null, activityHistory: record.activityHistory || [], pointer: record.pointer || null, error: record.error || "" };
|
|
39
41
|
}
|
|
40
42
|
function sessionRecords(session) {
|
|
41
43
|
return Array.from(records.values()).filter(function (record) { return record.session === session && recordAllowed(record); });
|
|
@@ -93,6 +95,13 @@ function attachSharedBrowser(ctx) {
|
|
|
93
95
|
if ((record.activity || record.pointer) && record.activityGeneration !== Number(record.session._sdkQueryGeneration || 0)) {
|
|
94
96
|
activityHistory.interruptActivity(record); record.pointer = null; broadcast(record);
|
|
95
97
|
}
|
|
98
|
+
if (message.type === "navigation") {
|
|
99
|
+
try { record.navigationUrl = policy.browserUrl(message.navigationUrl || message.url); } catch (error) { record.navigationUrl = null; }
|
|
100
|
+
}
|
|
101
|
+
if (message.type === "navigation" && !record.incognito) {
|
|
102
|
+
try { storageStore.visit(record.cookieOwner, message.url); }
|
|
103
|
+
catch (error) { end(record, "Could not save personal browsing history"); return; }
|
|
104
|
+
}
|
|
96
105
|
if (message.type === "ready") { record.phase = "live"; record.resolveReady(); }
|
|
97
106
|
if (message.type === "error") { end(record, message.error); return; }
|
|
98
107
|
if (message.url !== undefined) record.url = policy.visibleUrl(message.url);
|
|
@@ -108,10 +117,12 @@ function attachSharedBrowser(ctx) {
|
|
|
108
117
|
}
|
|
109
118
|
// Every new tab starts shared. Exclusive human mode is explicit, per tab, and only the human clears it,
|
|
110
119
|
// so an agent open never reuses (or bypasses) an exclusive tab.
|
|
111
|
-
function create(session, actor, newTab) {
|
|
120
|
+
function create(session, actor, newTab, incognito) {
|
|
112
121
|
requireAccess(session);
|
|
122
|
+
if (incognito !== undefined && typeof incognito !== "boolean") throw new Error("Invalid incognito mode");
|
|
123
|
+
var temporary = !ctx.isMate || incognito === true;
|
|
113
124
|
var tabs = sessionRecords(session);
|
|
114
|
-
var previous = !newTab && tabs.find(function (record) { return record.runtime && (actor === "user" || record.control === "agent"); });
|
|
125
|
+
var previous = !newTab && tabs.find(function (record) { return record.runtime && (incognito === undefined || record.incognito === temporary) && (actor === "user" || record.control === "agent"); });
|
|
115
126
|
if (previous) return previous;
|
|
116
127
|
if (tabs.length >= 12) {
|
|
117
128
|
var expired = tabs.find(function (record) { return !record.runtime; });
|
|
@@ -122,12 +133,15 @@ function attachSharedBrowser(ctx) {
|
|
|
122
133
|
records.forEach(function (record) { if (record.runtime && record.ownerId === session.ownerId) active++; });
|
|
123
134
|
if (active >= 4) throw new Error("End another browser before opening more than four active tabs");
|
|
124
135
|
var record = { session: session, ownerId: session.ownerId, identity: identityKey(session), id: crypto.randomUUID(), phase: "starting", control: "agent", epoch: 0,
|
|
125
|
-
url: "", width: 1280, height: 800, touched: Date.now(), runtime: null, frame: null };
|
|
136
|
+
cookieOwner: ctx.isMate ? (ctx.ownerId || session.ownerId || "default") : null, incognito: temporary, url: "", width: 1280, height: 800, touched: Date.now(), runtime: null, frame: null };
|
|
126
137
|
record.ready = new Promise(function (resolve, reject) { record.resolveReady = resolve; record.rejectReady = reject; });
|
|
127
138
|
record.ready.catch(function () {});
|
|
128
139
|
records.set(record.id, record);
|
|
129
140
|
try {
|
|
130
|
-
record.runtime = (ctx.createRuntime || createRuntime)({ identity: ctx.getIdentity(session),
|
|
141
|
+
record.runtime = (ctx.createRuntime || createRuntime)({ identity: ctx.getIdentity(session), storage: temporary ? null : {
|
|
142
|
+
read: function () { if (!recordAllowed(record)) throw new Error("Browser access was revoked"); return storageStore.read(record.cookieOwner); },
|
|
143
|
+
exchange: function (previous, current) { if (!recordAllowed(record)) throw new Error("Browser access was revoked"); return storageStore.exchange(record.cookieOwner, previous, current); },
|
|
144
|
+
}, onEvent: function (message) { receive(record, message); } });
|
|
131
145
|
} catch (e) { end(record, e.message); }
|
|
132
146
|
broadcast(record, true);
|
|
133
147
|
return record;
|
|
@@ -186,6 +200,7 @@ function attachSharedBrowser(ctx) {
|
|
|
186
200
|
requireAccess(session);
|
|
187
201
|
if (actor === "agent" && Number(session._sdkQueryGeneration || 0) !== generation) throw new Error("Browser result belongs to an older query");
|
|
188
202
|
if (!recordAllowed(record) || records.get(record.id) !== record || record.epoch !== epoch) throw new Error("Browser control changed during the action");
|
|
203
|
+
if (result && result.url !== undefined) record.url = policy.visibleUrl(result.url);
|
|
189
204
|
return result;
|
|
190
205
|
}
|
|
191
206
|
async function tool(session, args) {
|
|
@@ -196,8 +211,10 @@ function attachSharedBrowser(ctx) {
|
|
|
196
211
|
var value;
|
|
197
212
|
if (args.action === "status") value = { browsers: sessionRecords(session).map(projection) };
|
|
198
213
|
else if (args.action === "open") {
|
|
214
|
+
if (args.incognito !== undefined && typeof args.incognito !== "boolean") throw new Error("Invalid incognito mode");
|
|
199
215
|
var url = args.url ? policy.browserUrl(args.url) : null;
|
|
200
|
-
record = args.browserId ? selectRecord(session, args.browserId) : create(session, "agent", args.newTab === true);
|
|
216
|
+
record = args.browserId ? selectRecord(session, args.browserId) : create(session, "agent", args.newTab === true, args.incognito);
|
|
217
|
+
if (args.incognito !== undefined && record.incognito !== (!ctx.isMate || args.incognito)) throw new Error("This tab has a different cookie mode. Open a new tab with the requested mode.");
|
|
201
218
|
if (url) await run(session, record, "agent", "action", { kind: "navigate", url: url }, args.intent);
|
|
202
219
|
value = projection(record);
|
|
203
220
|
} else if (args.action === "finish") {
|
|
@@ -206,14 +223,27 @@ function attachSharedBrowser(ctx) {
|
|
|
206
223
|
} else if (args.action === "inspect") {
|
|
207
224
|
value = await run(session, record, "agent", "inspect", null, args.intent);
|
|
208
225
|
value.browserId = record.id;
|
|
226
|
+
value.browserLink = record.url ? "browser:" + record.url : null;
|
|
209
227
|
var image = value.image; delete value.image;
|
|
210
228
|
return { content: [{ type: "text", text: JSON.stringify(value) }, { type: "image", mimeType: "image/jpeg", data: image }] };
|
|
211
229
|
} else {
|
|
212
230
|
value = await run(session, record, "agent", "action", policy.validateAction(Object.assign({}, args, { kind: args.action })), args.intent);
|
|
213
231
|
}
|
|
232
|
+
if (value && value.url) value.browserLink = "browser:" + policy.visibleUrl(value.url);
|
|
214
233
|
return { content: [{ type: "text", text: JSON.stringify(value) }] };
|
|
215
234
|
} catch (e) { return { isError: true, content: [{ type: "text", text: e.message }] }; }
|
|
216
235
|
}
|
|
236
|
+
async function openLinkedPage(session, url, incognito) {
|
|
237
|
+
var record = sessionRecords(session).find(function (tab) {
|
|
238
|
+
return tab.runtime && ((tab.phase === "live" && (tab.navigationUrl || tab.url) === url) || (tab.linkOpening && tab.linkOpening.url === url));
|
|
239
|
+
});
|
|
240
|
+
if (record) { if (record.linkOpening) await record.linkOpening.promise; return record; }
|
|
241
|
+
record = create(session, "user", true, incognito);
|
|
242
|
+
record.navigationUrl = url;
|
|
243
|
+
record.linkOpening = { url: url, promise: run(session, record, "user", "action", { kind: "navigate", url: url }) };
|
|
244
|
+
try { await record.linkOpening.promise; } finally { record.linkOpening = null; }
|
|
245
|
+
return record;
|
|
246
|
+
}
|
|
217
247
|
function handleMessage(ws, msg) {
|
|
218
248
|
if (!msg.type || msg.type.indexOf("shared_browser_") !== 0) return false;
|
|
219
249
|
var session = ctx.getSessionForWs(ws);
|
|
@@ -221,8 +251,22 @@ function attachSharedBrowser(ctx) {
|
|
|
221
251
|
requireAccess(session, ws);
|
|
222
252
|
if (String(msg.sessionId) !== String(session.localId)) throw new Error("Browser session changed");
|
|
223
253
|
var record;
|
|
254
|
+
if (msg.type === "shared_browser_settings") {
|
|
255
|
+
if (!ctx.isMate) throw new Error("Personal browser settings are available only in Mates");
|
|
256
|
+
if (typeof msg.requestId !== "string" || msg.requestId.length > 100) throw new Error("Invalid browser settings request");
|
|
257
|
+
var owner = ctx.ownerId || session.ownerId || "default";
|
|
258
|
+
var settings = storageStore.manage(owner, msg.operation, msg.target);
|
|
259
|
+
requireAccess(session, ws);
|
|
260
|
+
ctx.sendTo(ws, { type: "shared_browser_settings", sessionId: session.localId, requestId: msg.requestId, settings: settings });
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
224
263
|
if (msg.type === "shared_browser_state_request") { sendState(ws, session, sessionRecords(session)[0]); return; }
|
|
225
|
-
if (msg.type === "shared_browser_open") {
|
|
264
|
+
if (msg.type === "shared_browser_open") {
|
|
265
|
+
var targetUrl = msg.url === undefined ? null : policy.browserUrl(msg.url);
|
|
266
|
+
if (targetUrl) record = await openLinkedPage(session, targetUrl, msg.incognito);
|
|
267
|
+
else record = create(session, "user", msg.newTab === true, msg.incognito);
|
|
268
|
+
sendState(ws, session, record, true, true); return;
|
|
269
|
+
}
|
|
226
270
|
record = selectRecord(session, msg.browserId);
|
|
227
271
|
if (!record || !recordAllowed(record, ws) || msg.browserId !== record.id) throw new Error("Browser session expired");
|
|
228
272
|
if (msg.type === "shared_browser_view") {
|
|
@@ -242,7 +286,7 @@ function attachSharedBrowser(ctx) {
|
|
|
242
286
|
await run(session, record, "user", "action", policy.validateAction(msg.event));
|
|
243
287
|
}
|
|
244
288
|
}).catch(function (e) {
|
|
245
|
-
if (ws.readyState === 1) ctx.sendTo(ws, { type: "shared_browser_error", sessionId: msg.sessionId, error: e.message });
|
|
289
|
+
if (ws.readyState === 1) ctx.sendTo(ws, { type: "shared_browser_error", sessionId: msg.sessionId, error: e.message, requestId: msg.requestId });
|
|
246
290
|
});
|
|
247
291
|
return true;
|
|
248
292
|
}
|
|
@@ -257,7 +301,7 @@ function attachSharedBrowser(ctx) {
|
|
|
257
301
|
sweep.unref();
|
|
258
302
|
function getToolDefs(session) {
|
|
259
303
|
var generation = Number(session && session._sdkQueryGeneration || 0);
|
|
260
|
-
return
|
|
304
|
+
return browserTools.getToolDefs(function (args) {
|
|
261
305
|
if (session && Number(session._sdkQueryGeneration || 0) !== generation) {
|
|
262
306
|
return Promise.resolve({ isError: true, content: [{ type: "text", text: "This browser tool belongs to an older query" }] });
|
|
263
307
|
}
|
|
@@ -267,8 +311,8 @@ function attachSharedBrowser(ctx) {
|
|
|
267
311
|
return {
|
|
268
312
|
handleMessage: handleMessage,
|
|
269
313
|
getToolDefs: getToolDefs,
|
|
270
|
-
getSystemPrompt: function () { return
|
|
271
|
-
createMcpServer: function (adapter, session) { return
|
|
314
|
+
getSystemPrompt: function () { return browserTools.PROMPT; },
|
|
315
|
+
createMcpServer: function (adapter, session) { return !adapter || typeof adapter.createToolServer !== "function" ? null : adapter.createToolServer({ name: "clay-shared-browser", version: "1.0.0", tools: getToolDefs(session) }); },
|
|
272
316
|
destroy: function () { destroyed = true; clearInterval(sweep); records.forEach(function (record) { end(record); }); records.clear(); viewers.clear(); },
|
|
273
317
|
};
|
|
274
318
|
}
|
|
@@ -118,6 +118,12 @@ function attachUserMessage(ctx) {
|
|
|
118
118
|
requestId: msg.requestId || null, ok: !!ok, error: error || null });
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
function noteUpdateResult(ws, msg, ok, note, error) {
|
|
122
|
+
if (!msg.requestId) return;
|
|
123
|
+
sendTo(ws, { type: "note_update_result", projectSlug: slug, noteId: msg.id || null,
|
|
124
|
+
requestId: msg.requestId, ok: !!ok, note: note || null, error: error || null });
|
|
125
|
+
}
|
|
126
|
+
|
|
121
127
|
// --------------- Main handler ---------------
|
|
122
128
|
|
|
123
129
|
function handleUserMessage(ws, msg, forcedSession, internal) {
|
|
@@ -155,7 +161,10 @@ function attachUserMessage(ctx) {
|
|
|
155
161
|
}
|
|
156
162
|
|
|
157
163
|
if (msg.type === "note_update") {
|
|
158
|
-
if (!msg.id)
|
|
164
|
+
if (!msg.id) {
|
|
165
|
+
noteUpdateResult(ws, msg, false, null, "Note id is required");
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
159
168
|
// A legacy `hidden` update is a lifecycle transition, so the actor is
|
|
160
169
|
// derived from the bound socket rather than taken from the message.
|
|
161
170
|
var noteChanges = msg;
|
|
@@ -166,10 +175,24 @@ function attachUserMessage(ctx) {
|
|
|
166
175
|
if (updated) {
|
|
167
176
|
send({ type: "note_updated", note: updated });
|
|
168
177
|
if (msg.text !== undefined || msg.hidden !== undefined) syncNotesKnowledge();
|
|
178
|
+
noteUpdateResult(ws, msg, true, updated, null);
|
|
179
|
+
} else {
|
|
180
|
+
noteUpdateResult(ws, msg, false, null, "Note update could not be saved");
|
|
169
181
|
}
|
|
170
182
|
return true;
|
|
171
183
|
}
|
|
172
184
|
|
|
185
|
+
if (msg.type === "note_close" && msg.humanClose === true) {
|
|
186
|
+
if (!canHumanManageNotes(ws)) { noteUpdateResult(ws, msg, false, null, "You are not authorized to close notes in this project."); return true; }
|
|
187
|
+
var result = nm.closeHuman(msg.id, msg, notesLifecycle.userActor(ws && ws._clayUser));
|
|
188
|
+
if (!result) { noteUpdateResult(ws, msg, false, null, "The note changed or could not be saved. Review it and retry."); return true; }
|
|
189
|
+
if (result.removed) send({ type: "note_deleted", id: result.removed.id });
|
|
190
|
+
else send({ type: "note_updated", note: result.note });
|
|
191
|
+
noteUpdateResult(ws, msg, true, result.note || null, null);
|
|
192
|
+
syncNotesKnowledge();
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
173
196
|
// Completing a note closes it. `note_delete` is the retired spelling and is
|
|
174
197
|
// deliberately routed to the same non-destructive close, so an older client
|
|
175
198
|
// still on the wire cannot erase a record.
|
|
@@ -443,6 +466,10 @@ function attachUserMessage(ctx) {
|
|
|
443
466
|
finishPendingEarly(false, "Pending messages cannot be delivered to a Driver-operated Split Worker");
|
|
444
467
|
return true;
|
|
445
468
|
}
|
|
469
|
+
if (!pendingInternal && ctx.resumeLifecycleProject) {
|
|
470
|
+
try { ctx.resumeLifecycleProject(ws); }
|
|
471
|
+
catch (error) { sendTo(ws, { type: "error", text: error.message }); return true; }
|
|
472
|
+
}
|
|
446
473
|
if (!pendingInternal && session._driverContinuationLease && pendingMessageQueue) {
|
|
447
474
|
var continuationQueued = pendingMessageQueue.admit(session, msg, ws && ws._clayUser);
|
|
448
475
|
if (continuationQueued.ok) {
|
|
@@ -719,6 +746,7 @@ function attachUserMessage(ctx) {
|
|
|
719
746
|
|
|
720
747
|
function pendingBlockReason() {
|
|
721
748
|
if (!pendingInternal) return null;
|
|
749
|
+
if (ctx.isLifecycleExecutionPaused && ctx.isLifecycleExecutionPaused()) return "Pending message dispatch paused";
|
|
722
750
|
if (!pendingMessageQueue.isClaimed(session, pendingInternal.item.id)) return "Pending message was cancelled";
|
|
723
751
|
if (pendingMessageQueue.isPaused(session) || session._pendingMessageDrainPaused || session.taskStopRequested) return "Pending message dispatch paused";
|
|
724
752
|
if (typeof ctx.authorizePendingDispatch !== "function" || !ctx.authorizePendingDispatch(session, ws && ws._clayUser, pendingInternal.item)) return "Pending message authorization changed";
|
|
@@ -941,6 +969,7 @@ function attachUserMessage(ctx) {
|
|
|
941
969
|
}
|
|
942
970
|
|
|
943
971
|
function consumePendingMessage(session) {
|
|
972
|
+
if (ctx.isLifecycleExecutionPaused && ctx.isLifecycleExecutionPaused()) return false;
|
|
944
973
|
if (!pendingMessageQueue) return false;
|
|
945
974
|
var consumed = pendingMessageQueue.consumeOne(session, function (item, complete) {
|
|
946
975
|
var actor = item.actorId && usersModule.findUserById(item.actorId);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
var fs = require('fs'), path = require('path'), crypto = require('crypto');
|
|
2
|
+
var git = require('./git-cli');
|
|
3
|
+
function digest(value) { return crypto.createHash('sha256').update(typeof value === 'string' || Buffer.isBuffer(value) ? value : JSON.stringify(value)).digest('hex'); }
|
|
4
|
+
function inode(file) { var s = fs.statSync(file); return [s.dev, s.ino, s.mode, s.uid, s.gid]; }
|
|
5
|
+
function attachWorkspaceOperations(ctx) {
|
|
6
|
+
var busy = false;
|
|
7
|
+
function authorize(principal, domain) {
|
|
8
|
+
var record = ctx.getAccess(), user = principal.userId && ctx.users.findUserById(principal.userId);
|
|
9
|
+
if (ctx.isMate || !record || record.lifecycleExecutionPaused || ctx.users.isMultiUser() && (!user || record.ownerId !== user.id || !ctx.users.canAccessProject(user.id, record))) throw new Error('Live selected-project ownership or access is unavailable.');
|
|
10
|
+
if (!ctx.users.isMultiUser() && record.ownerId && record.ownerId !== principal.singleUserOwnerId) throw new Error('Selected-project ownership changed.');
|
|
11
|
+
if (user && ctx.users.getEffectivePermissions(user, ctx.osUsers)[domain === 'terminal' ? 'terminal' : 'fileBrowser'] !== true) throw new Error('The account does not have permission for this product operation.');
|
|
12
|
+
var identity = null;
|
|
13
|
+
if (ctx.osUsers) {
|
|
14
|
+
if (!user || !user.linuxUser) throw new Error('A live mapped OS identity is required.');
|
|
15
|
+
identity = require('./os-users').resolveOsUserInfo(user.linuxUser);
|
|
16
|
+
if (!identity || !Number.isInteger(identity.uid) || identity.uid < 0 || !Number.isInteger(identity.gid) || identity.gid < 0 || identity.user !== user.linuxUser) throw new Error('The mapped OS identity is unavailable.');
|
|
17
|
+
}
|
|
18
|
+
return { identity: identity, projectBound: true, ownerId: user ? user.id : null };
|
|
19
|
+
}
|
|
20
|
+
function rootIdentity(principal, domain) {
|
|
21
|
+
var scope = authorize(principal, domain), root = fs.realpathSync(ctx.cwd);
|
|
22
|
+
return digest([root, inode(root), scope.ownerId, scope.identity && [scope.identity.user, scope.identity.uid, scope.identity.gid, scope.identity.home]]);
|
|
23
|
+
}
|
|
24
|
+
function bounded(requested, missing) {
|
|
25
|
+
if (typeof requested !== 'string' || !requested || path.isAbsolute(requested) || /[\u0000-\u001f\u007f]/.test(requested) || requested.split(/[\\/]/).some(function (part) { return part === '..' || ['.git', '.clay', '.claude', '.codex', '.envrc'].indexOf(part) >= 0; })) throw new Error('Use an in-project relative path outside managed settings and Git metadata.');
|
|
26
|
+
var root = fs.realpathSync(ctx.cwd), resolved = root;
|
|
27
|
+
var parts = requested.split('/').filter(function (part) { return part && part !== '.'; });
|
|
28
|
+
for (var i = 0; i < parts.length; i++) {
|
|
29
|
+
resolved = path.join(resolved, parts[i]);
|
|
30
|
+
var stat;
|
|
31
|
+
try { stat = fs.lstatSync(resolved); } catch (error) { if (missing && error.code === 'ENOENT' && i === parts.length - 1) return resolved; throw error; }
|
|
32
|
+
if (stat.isSymbolicLink() || stat.isFile() && stat.nlink !== 1) throw new Error('Symlinks and multiply linked files are unavailable in canonical project controls.');
|
|
33
|
+
}
|
|
34
|
+
if (path.relative(root, resolved).split(path.sep).indexOf('..') >= 0) throw new Error('The canonical project boundary changed.');
|
|
35
|
+
return resolved;
|
|
36
|
+
}
|
|
37
|
+
function repository(scope) {
|
|
38
|
+
var marker = fs.lstatSync(path.join(ctx.cwd, '.git'));
|
|
39
|
+
if (!marker.isDirectory() || marker.isSymbolicLink()) throw new Error('Canonical Git controls require a direct local .git directory; linked metadata and submodules are unavailable.');
|
|
40
|
+
var status = git.getStatus(ctx.cwd, scope.identity);
|
|
41
|
+
if (!status.isRepository || fs.realpathSync(status.root) !== fs.realpathSync(ctx.cwd)) throw new Error('Select the exact registered repository root, rather than a repository subdirectory.');
|
|
42
|
+
if (fs.realpathSync(status.gitDir) !== fs.realpathSync(path.join(ctx.cwd, '.git'))) throw new Error('The Git metadata directory escaped the selected project.');
|
|
43
|
+
return status;
|
|
44
|
+
}
|
|
45
|
+
function gitProjection(status) { return { isRepository: true, branch: status.branch, oid: status.oid, upstream: status.upstream, ahead: status.ahead, behind: status.behind, detached: status.detached, files: status.files.slice(0, 200), truncated: status.files.length > 200, dirty: status.dirty }; }
|
|
46
|
+
function terminal(scope, id) {
|
|
47
|
+
var item = ctx.tm.inspect(id);
|
|
48
|
+
var identity = scope.identity ? JSON.stringify([scope.identity.user, scope.identity.uid, scope.identity.gid, scope.identity.home]) : null;
|
|
49
|
+
if (!item || item.kind !== 'shell' || item.ownerId !== scope.ownerId || item.osIdentity !== identity) throw new Error('Select an exact same-owner shell terminal. Coding-agent, login and foreign terminals are excluded.');
|
|
50
|
+
return item;
|
|
51
|
+
}
|
|
52
|
+
function read(principal, op) {
|
|
53
|
+
var scope = authorize(principal, op.domain), before = rootIdentity(principal, op.domain), result;
|
|
54
|
+
if (op.domain === 'files') {
|
|
55
|
+
bounded(op.path || '.');
|
|
56
|
+
if (op.action === 'list') { var entries = ctx.files().list(scope, op.path || '.'); result = { entries: entries.slice(0, 200), truncated: entries.length > 200 }; }
|
|
57
|
+
else { result = ctx.files().read(scope, op.path); if (result.content && result.content.length > 65536) result = { error: 'Canonical text reads are limited to 64 KB.', size: result.size }; }
|
|
58
|
+
} else if (op.domain === 'git') {
|
|
59
|
+
var status = repository(scope);
|
|
60
|
+
if (op.action === 'status') result = gitProjection(status);
|
|
61
|
+
else { var working = bounded(op.path, true); if (fs.existsSync(working)) ctx.files().read(scope, op.path); result = git.getFileDiff(ctx.cwd, op.path, scope.identity); if ((result.oldContent.length + result.newContent.length) > 65536) throw new Error('Canonical diff reads are limited to 64 KB.'); }
|
|
62
|
+
} else if (op.action === 'list') result = { terminals: ctx.tm.list().filter(function (item) { try { terminal(scope, item.id); return true; } catch (error) { return false; } }).map(function (item) { return ctx.tm.inspect(item.id); }) };
|
|
63
|
+
else { var item = terminal(scope, op.id); result = Object.assign({}, item, ctx.tm.getScrollback(item.id)); }
|
|
64
|
+
if (rootIdentity(principal, op.domain) !== before) throw new Error('Project or OS identity changed during the read.');
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
function snapshot(principal, op) {
|
|
68
|
+
var scope = authorize(principal, op.domain);
|
|
69
|
+
if (op.domain === 'files') { bounded(op.path); return ctx.files().snapshot(scope, op.path); }
|
|
70
|
+
if (op.domain === 'git') {
|
|
71
|
+
var status = repository(scope);
|
|
72
|
+
if (!op.paths.length || !op.paths.every(function (name) { return status.files.some(function (item) { return item.path === name; }); })) throw new Error('Select exact currently changed Git paths.');
|
|
73
|
+
var entries = op.paths.map(function (name) {
|
|
74
|
+
var working = bounded(name, true);
|
|
75
|
+
if (fs.existsSync(working) && !fs.lstatSync(working).isFile()) throw new Error('Canonical staging requires individual regular file paths; recursive directory staging is unavailable.');
|
|
76
|
+
if (fs.existsSync(working)) ctx.files().read(scope, name);
|
|
77
|
+
var item = status.files.find(function (file) { return file.path === name; });
|
|
78
|
+
if (item.originalPath) bounded(item.originalPath, true);
|
|
79
|
+
var diff = git.getFileDiff(ctx.cwd, name, scope.identity);
|
|
80
|
+
if (diff.binary) throw new Error('Canonical staging of binary files is unavailable.');
|
|
81
|
+
return digest(diff);
|
|
82
|
+
});
|
|
83
|
+
var index = path.join(status.gitDir, 'index');
|
|
84
|
+
return { status: gitProjection(status), gitDirectory: [fs.realpathSync(status.gitDir), inode(status.gitDir)], index: fs.existsSync(index) ? digest(fs.readFileSync(index)) : null, files: entries,
|
|
85
|
+
configuration: digest(git.runGitSync(ctx.cwd, ['config', '--null', '--list', '--show-origin'], null, scope.identity)),
|
|
86
|
+
attributes: digest(git.runGitSync(ctx.cwd, ['check-attr', '-z', '--all', '--'].concat(op.paths), null, scope.identity)) };
|
|
87
|
+
}
|
|
88
|
+
return op.action === 'create' ? ctx.tm.list().map(function (item) { return ctx.tm.inspect(item.id); }) : terminal(scope, op.id);
|
|
89
|
+
}
|
|
90
|
+
async function apply(principal, op, expected, revalidate) {
|
|
91
|
+
if (busy) throw new Error('Another project file, Git or terminal change is applying. Inspect again after it settles.');
|
|
92
|
+
busy = true;
|
|
93
|
+
var attempted = false, result;
|
|
94
|
+
try {
|
|
95
|
+
var scope = authorize(principal, op.domain);
|
|
96
|
+
revalidate();
|
|
97
|
+
if (digest(snapshot(principal, op)) !== digest(expected)) throw new Error('The exact product revision changed.');
|
|
98
|
+
if (op.domain === 'files') return ctx.files().write(scope, op.path, op.content, { expected: expected, revalidate: function () { revalidate(); bounded(op.path); } });
|
|
99
|
+
attempted = true;
|
|
100
|
+
if (op.domain === 'git') {
|
|
101
|
+
await git.runAction(ctx.cwd, { action: op.action, paths: op.paths }, scope.identity);
|
|
102
|
+
result = { status: 'completed', runtimeEffect: 'index_updated', persisted: true };
|
|
103
|
+
} else if (op.action === 'create') {
|
|
104
|
+
var created = ctx.tm.create(op.cols, op.rows, scope.identity, null, { kind: 'shell', ownerId: scope.ownerId });
|
|
105
|
+
if (!created) throw new Error('The terminal transport is unavailable or its limit was reached.');
|
|
106
|
+
result = { status: 'completed', runtimeEffect: 'shell_created', terminalId: created.id, terminalIdentity: created.identity, commandSent: false, persisted: false };
|
|
107
|
+
} else if (op.action === 'rename') {
|
|
108
|
+
ctx.tm.rename(op.id, op.title); result = { status: 'completed', runtimeEffect: 'renamed', terminalId: op.id, terminalIdentity: expected.identity, persisted: false };
|
|
109
|
+
} else if (op.action === 'resize') {
|
|
110
|
+
ctx.tm.resizeExact(op.id, op.cols, op.rows, expected.identity, revalidate);
|
|
111
|
+
result = { status: 'completed', runtimeEffect: 'resized', terminalId: op.id, terminalIdentity: expected.identity, persisted: false };
|
|
112
|
+
} else {
|
|
113
|
+
await ctx.tm.closeAndWait(op.id, { identity: expected.identity, revalidate: function () { revalidate(); var live = terminal(authorize(principal, 'terminal'), op.id); if (live.identity !== expected.identity) throw new Error('Terminal identity changed during closure.'); } });
|
|
114
|
+
result = { status: 'completed', runtimeEffect: 'closed', terminalId: op.id, terminalIdentity: expected.identity, exitAcknowledged: true, persisted: false };
|
|
115
|
+
}
|
|
116
|
+
revalidate();
|
|
117
|
+
if (op.domain === 'terminal') ctx.publishTerminals();
|
|
118
|
+
return result;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
error.productControlResult = Object.assign({ persisted: attempted ? 'unknown' : false, runtimeEffect: attempted ? 'unknown' : 'none', retrySafe: false }, result, error.productControlResult || {}, { status: 'failed', error: error.message });
|
|
121
|
+
throw error;
|
|
122
|
+
} finally { busy = false; }
|
|
123
|
+
}
|
|
124
|
+
return { rootIdentity: rootIdentity, read: read, snapshot: snapshot, apply: apply };
|
|
125
|
+
}
|
|
126
|
+
module.exports = { attachWorkspaceOperations: attachWorkspaceOperations };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
var workspaceMcp = require("./workspace-query-mcp-server");
|
|
2
|
+
var productTools = require("./clay-product-control-tools");
|
|
2
3
|
var clayHistoryMcp = require("./clay-history-mcp-server");
|
|
3
4
|
|
|
4
5
|
function attachProjectWorkspaceQuery(ctx) {
|
|
@@ -25,7 +26,12 @@ function attachProjectWorkspaceQuery(ctx) {
|
|
|
25
26
|
function getToolDefs(session) {
|
|
26
27
|
var bound = session ? binding(session) : null;
|
|
27
28
|
if (session && !bound) return [];
|
|
28
|
-
|
|
29
|
+
var tools = workspaceMcp.getToolDefs(bound, !!(bound && bound.isClay));
|
|
30
|
+
if (bound && bound.isClay && ctx.controlService) {
|
|
31
|
+
var controls = ctx.controlService.bind({ projectSlug: projectSlug, projectOwnerId: getProjectOwnerId(), isMate: true, mateId: mateId, session: session });
|
|
32
|
+
if (controls) tools = tools.concat(productTools.getToolDefs(controls));
|
|
33
|
+
}
|
|
34
|
+
return tools;
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
function createMcpServer(adapter, session) {
|
|
@@ -34,7 +40,8 @@ function attachProjectWorkspaceQuery(ctx) {
|
|
|
34
40
|
if (session && !bound) return null;
|
|
35
41
|
var projectBound = binding(null);
|
|
36
42
|
if (!projectBound) return null;
|
|
37
|
-
|
|
43
|
+
if (!adapter || typeof adapter.createToolServer !== "function") return null;
|
|
44
|
+
return adapter.createToolServer({ name: "clay-workspace", version: "2.0.0", tools: session ? getToolDefs(session) : workspaceMcp.getToolDefs(null, projectBound.isClay) });
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
function createHistoryMcpServer(adapter, session) {
|