clay-server 5.2.0-beta.4 → 5.2.0-beta.6
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 +17 -14
- 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-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-skill-install.js +1 -33
- 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/cli-sessions.js +1 -300
- package/lib/codex-defaults.js +1 -28
- package/lib/context-usage.js +3 -2
- 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/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.js +5 -4
- package/lib/knowledge-db-service.js +9 -6
- package/lib/knowledge-document-index.js +2 -1
- package/lib/knowledge-product-operations.js +46 -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 +4 -3
- package/lib/mcp-proxy.js +2 -32
- package/lib/mcp-query-bridge.js +1 -1
- package/lib/mcp-source-adapters.js +1 -91
- package/lib/mcp-source-context.js +1 -42
- package/lib/mcp-source-discovery.js +1 -169
- package/lib/mcp-source-plugins.js +1 -68
- package/lib/mcp-source-restrictions.js +1 -47
- package/lib/mcp-tool-policy.js +1 -26
- 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/project-admin-overview.js +51 -0
- package/lib/project-ask-user.js +4 -2
- package/lib/project-connection.js +8 -7
- 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-http.js +4 -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-mcp.js +2 -2
- 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-pair-usage.js +12 -13
- package/lib/project-pending-message-queue.js +4 -1
- 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-github.js +47 -12
- package/lib/project-session-history-actions.js +167 -0
- 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 +82 -267
- package/lib/project-user-message.js +31 -1
- package/lib/project-vendor-login.js +6 -36
- package/lib/project-workspace-operations.js +126 -0
- package/lib/project-workspace-query.js +9 -2
- package/lib/project.js +95 -3
- package/lib/public/app.js +1 -0
- package/lib/public/css/clay-tool-activity.css +51 -0
- package/lib/public/css/home-mate-creation.css +5 -0
- 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 +3 -0
- package/lib/public/css/sticky-notes.css +61 -1
- package/lib/public/index.html +1 -1
- package/lib/public/modules/admin-settings-projects.js +6 -2
- package/lib/public/modules/app-connection.js +8 -1
- package/lib/public/modules/app-favicon.js +11 -2
- package/lib/public/modules/app-header.js +4 -2
- package/lib/public/modules/app-loop-ui.js +2 -0
- package/lib/public/modules/app-message-router.js +1 -1
- package/lib/public/modules/app-messages.js +54 -8
- package/lib/public/modules/app-notifications.js +1 -2
- package/lib/public/modules/app-panels.js +61 -123
- package/lib/public/modules/app-projects.js +5 -0
- package/lib/public/modules/app-rate-limit.js +7 -27
- package/lib/public/modules/chat-render-runtime.js +3 -0
- 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 +1 -1
- package/lib/public/modules/default-ai.js +11 -3
- package/lib/public/modules/home-debate-planning.js +5 -5
- package/lib/public/modules/home-mate-chat.js +8 -6
- package/lib/public/modules/home-mate-creation.js +73 -1
- package/lib/public/modules/mcp-connection-view.js +1 -1
- package/lib/public/modules/permission-control.js +5 -4
- package/lib/public/modules/personal-settings-dialog.js +7 -1
- package/lib/public/modules/project-access-recovery.js +17 -0
- package/lib/public/modules/project-admin-overview.js +21 -0
- package/lib/public/modules/scheduler.js +2 -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 +65 -8
- package/lib/public/modules/settings-defaults.js +27 -28
- package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
- 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/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/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/muse-avatar.svg +4 -0
- package/lib/public/style.css +2 -0
- 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 +334 -403
- 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-dm.js +1 -1
- package/lib/server-global-ws.js +10 -20
- package/lib/server-home-chat-events.js +7 -3
- package/lib/server-home-chat.js +4 -16
- package/lib/server-home-mate-creation.js +1 -0
- package/lib/server-home-mcp-consent.js +3 -3
- package/lib/server-mates.js +25 -67
- 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.js +100 -30
- 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/sessions.js +27 -89
- package/lib/terminal-manager.js +52 -0
- package/lib/tool-llm.js +1 -1
- package/lib/tui-transcript-index.js +1 -132
- package/lib/users-permissions.js +2 -8
- package/lib/users.js +4 -0
- package/lib/vendor-login-browser.js +1 -11
- package/lib/workspace-query-access.js +4 -1
- package/lib/ws-schema.js +8 -2
- package/lib/yoke/acp-agent-profiles.js +5 -3
- 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 +267 -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 +3 -2
|
@@ -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,7 +25,7 @@ function attachAskUser(ctx) {
|
|
|
25
25
|
respond.cancel("Autonomous mode. Make your own decision.");
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
|
-
var 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
30
|
if (request.source === 'mcp-import') input.source = 'mcp-import';
|
|
31
31
|
if (!boundSession.pendingAskUser) boundSession.pendingAskUser = {};
|
|
@@ -40,6 +40,8 @@ function attachAskUser(ctx) {
|
|
|
40
40
|
boundSession.pendingAskUser[request.id] = pending;
|
|
41
41
|
if (respond && typeof respond.onSettle === "function") {
|
|
42
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.' });
|
|
43
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.' });
|
|
44
46
|
if (boundSession.pendingAskUser && boundSession.pendingAskUser[request.id] === pending) delete boundSession.pendingAskUser[request.id];
|
|
45
47
|
});
|
|
@@ -48,7 +50,7 @@ function attachAskUser(ctx) {
|
|
|
48
50
|
ctx.record(boundSession, { type: "tool_executing", id: request.id, name: "AskUserQuestion", input: input, source: request.source });
|
|
49
51
|
}
|
|
50
52
|
var router = ctx.getWorkerQuestionRouter && ctx.getWorkerQuestionRouter();
|
|
51
|
-
if (router && request.source !== "mcp-import") 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) {
|
|
52
54
|
var displayAnswers = {};
|
|
53
55
|
request.questions.forEach(function (question, index) { displayAnswers[index] = answers[question.id].join(", "); });
|
|
54
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");
|
|
@@ -217,7 +217,7 @@ function attachConnection(ctx) {
|
|
|
217
217
|
sm.saveSessionFile(restoredActive);
|
|
218
218
|
}
|
|
219
219
|
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.
|
|
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: require("./yoke/vendor-registry").publicInfo(), isMate: isMate, mateId: isMate ? mateId : null, mateDisplayName: isMate ? (mateDisplayName || title || project || "Mate") : "" });
|
|
221
221
|
if (typeof sendCursorSharingState === "function") sendCursorSharingState(ws);
|
|
222
222
|
// Update notifications are pushed on a scheduled interval (see
|
|
223
223
|
// scheduleUpdateBroadcast). We no longer push on connect to avoid
|
|
@@ -234,7 +234,7 @@ function attachConnection(ctx) {
|
|
|
234
234
|
var initialPermissionState = permissionModes.clientState(restoredActive, sm.currentPermissionMode, sm.defaultVendor);
|
|
235
235
|
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
236
|
sendTo(ws, { type: "last_vendor", vendor: sm.lastVendor || "" });
|
|
237
|
-
|
|
237
|
+
providerSettings.messages(sm).forEach(function(message) { sendTo(ws, message); });
|
|
238
238
|
sendTo(ws, { type: "term_list", terminals: tm.list() });
|
|
239
239
|
// Any login flow already running in this project, so a reconnecting client
|
|
240
240
|
// re-attaches to the existing login terminal instead of starting another.
|
|
@@ -301,12 +301,13 @@ function attachConnection(ctx) {
|
|
|
301
301
|
}
|
|
302
302
|
sendTo(ws, { type: "history_meta", total: total, from: fromIndex });
|
|
303
303
|
for (var i = fromIndex; i < total; i++) {
|
|
304
|
-
sendTo(ws, Object.assign({}, hydrateImageRefs(active.history[i]), { _historyIndex: i }));
|
|
304
|
+
sendTo(ws, Object.assign({}, hydrateImageRefs(require("./yoke/usage").clientEvent(active.history[i], active.vendor || "claude")), { _historyIndex: i }));
|
|
305
305
|
}
|
|
306
|
-
var _lastUsage = null, _lastModelUsage = null, _lastCost = null, _lastStreamInputTokens = null;
|
|
306
|
+
var _lastContext = null, _lastUsage = null, _lastModelUsage = null, _lastCost = null, _lastStreamInputTokens = null;
|
|
307
307
|
for (var _ri = total - 1; _ri >= 0; _ri--) {
|
|
308
308
|
if (active.history[_ri].type === "result") {
|
|
309
|
-
var _r = active.history[_ri];
|
|
309
|
+
var _r = require("./yoke/usage").clientEvent(active.history[_ri], active.vendor || "claude");
|
|
310
|
+
_lastContext = _r.context;
|
|
310
311
|
_lastUsage = _r.usage || null;
|
|
311
312
|
_lastModelUsage = _r.modelUsage || null;
|
|
312
313
|
_lastCost = _r.cost != null ? _r.cost : null;
|
|
@@ -314,7 +315,7 @@ function attachConnection(ctx) {
|
|
|
314
315
|
break;
|
|
315
316
|
}
|
|
316
317
|
}
|
|
317
|
-
sendTo(ws, { type: "history_done", lastUsage: _lastUsage, lastModelUsage: _lastModelUsage, lastCost: _lastCost, lastStreamInputTokens: _lastStreamInputTokens, contextUsage: active.lastContextUsage ||
|
|
318
|
+
sendTo(ws, { type: "history_done", lastUsage: _lastUsage, lastModelUsage: _lastModelUsage, lastCost: _lastCost, lastStreamInputTokens: _lastStreamInputTokens, context: _lastContext, contextUsage: require("./yoke/usage").context(active.lastContextUsage, active.vendor || "claude") });
|
|
318
319
|
if (scheduledMessages) scheduledMessages.sendState(active, ws);
|
|
319
320
|
if (pairResultRecovery) pairResultRecovery.sendState(active, ws);
|
|
320
321
|
|
|
@@ -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 };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Shared file-viewer effects. Authority and scope are derived by each caller.
|
|
2
|
+
var fs = require('fs'), path = require('path'), crypto = require('crypto');
|
|
3
|
+
var resolve = require('./project-file-path').resolveFilePath;
|
|
4
|
+
function hash(content) { return crypto.createHash('sha256').update(content).digest('hex'); }
|
|
5
|
+
function attachFileOperations(ctx) {
|
|
6
|
+
function list(scope, requested) {
|
|
7
|
+
var dir = resolve(ctx.cwd, requested || '.', scope);
|
|
8
|
+
var items = scope.identity ? ctx.fsAsUser('list', { dir: dir }, scope.identity) : fs.readdirSync(dir, { withFileTypes: true }).map(function (item) { return { name: item.name, isDir: item.isDirectory() }; });
|
|
9
|
+
return items.filter(function (item) { return !item.isDir || !ctx.IGNORED_DIRS.has(item.name); }).map(function (item) { return { name: item.name, type: item.isDir ? 'dir' : 'file', path: path.relative(ctx.cwd, path.join(dir, item.name)).split(path.sep).join('/') }; });
|
|
10
|
+
}
|
|
11
|
+
function read(scope, requested) {
|
|
12
|
+
var file = resolve(ctx.cwd, requested, scope), identity = scope.identity;
|
|
13
|
+
var stat = identity ? ctx.fsAsUser('stat', { file: file }, identity) : fs.statSync(file);
|
|
14
|
+
if (stat.size > ctx.FS_MAX_SIZE) return { binary: true, size: stat.size, error: 'File too large (' + (stat.size / 1024 / 1024).toFixed(1) + ' MB)' };
|
|
15
|
+
var ext = path.extname(file).toLowerCase();
|
|
16
|
+
if (ctx.BINARY_EXTS.has(ext)) {
|
|
17
|
+
var binary = { binary: true, size: stat.size };
|
|
18
|
+
if (ctx.IMAGE_EXTS.has(ext)) binary.imageUrl = 'api/file?path=' + encodeURIComponent(requested);
|
|
19
|
+
return binary;
|
|
20
|
+
}
|
|
21
|
+
return { content: identity ? ctx.fsAsUser('read', { file: file, readContent: true }, identity).content : fs.readFileSync(file, 'utf8'), size: stat.size };
|
|
22
|
+
}
|
|
23
|
+
function snapshot(scope, requested) {
|
|
24
|
+
var file = resolve(ctx.cwd, requested, scope), stat = fs.lstatSync(file);
|
|
25
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > 65536) throw new Error('Select a regular, singly linked text file of at most 64 KB.');
|
|
26
|
+
var result = read(scope, requested);
|
|
27
|
+
if (result.binary || typeof result.content !== 'string' || result.content.indexOf('\0') >= 0) throw new Error('Binary files are unavailable for canonical editing.');
|
|
28
|
+
return { dev: stat.dev, ino: stat.ino, mode: stat.mode, uid: stat.uid, gid: stat.gid, size: stat.size, mtime: stat.mtimeMs, hash: hash(result.content) };
|
|
29
|
+
}
|
|
30
|
+
function write(scope, requested, content, control) {
|
|
31
|
+
var file = resolve(ctx.cwd, requested, scope), attempted = false;
|
|
32
|
+
if (!control) {
|
|
33
|
+
if (scope.identity) ctx.fsAsUser('write', { file: file, content: content }, scope.identity);
|
|
34
|
+
else fs.writeFileSync(file, content, 'utf8');
|
|
35
|
+
return { ok: true };
|
|
36
|
+
}
|
|
37
|
+
if (typeof fs.constants.O_NOFOLLOW !== 'number') throw new Error('Canonical editing requires no-follow file descriptors on this platform.');
|
|
38
|
+
control.revalidate();
|
|
39
|
+
if (JSON.stringify(snapshot(scope, requested)) !== JSON.stringify(control.expected)) throw new Error('The exact file revision changed.');
|
|
40
|
+
try {
|
|
41
|
+
attempted = true;
|
|
42
|
+
if (scope.identity) ctx.fsAsUser('write_exact', { file: file, content: content, expected: control.expected, root: fs.realpathSync(ctx.cwd) }, scope.identity);
|
|
43
|
+
else {
|
|
44
|
+
var fd = fs.openSync(file, fs.constants.O_RDWR | fs.constants.O_NOFOLLOW);
|
|
45
|
+
try {
|
|
46
|
+
var stat = fs.fstatSync(fd), expected = control.expected;
|
|
47
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.dev !== expected.dev || stat.ino !== expected.ino || stat.mode !== expected.mode || stat.uid !== expected.uid || stat.gid !== expected.gid || stat.size !== expected.size || stat.mtimeMs !== expected.mtime || hash(fs.readFileSync(fd, 'utf8')) !== expected.hash) throw new Error('The open file revision changed.');
|
|
48
|
+
control.revalidate();
|
|
49
|
+
fs.ftruncateSync(fd, 0);
|
|
50
|
+
var bytes = Buffer.from(content, 'utf8'), offset = 0;
|
|
51
|
+
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; }
|
|
52
|
+
fs.fsyncSync(fd);
|
|
53
|
+
} finally { fs.closeSync(fd); }
|
|
54
|
+
}
|
|
55
|
+
control.revalidate();
|
|
56
|
+
return { status: 'completed', persisted: true, path: requested };
|
|
57
|
+
} catch (error) { error.productControlResult = { persisted: attempted ? 'unknown' : false, retrySafe: false, error: error.message }; throw error; }
|
|
58
|
+
}
|
|
59
|
+
return { list: list, read: read, write: write, snapshot: snapshot };
|
|
60
|
+
}
|
|
61
|
+
module.exports = { attachFileOperations: attachFileOperations };
|
|
@@ -37,6 +37,7 @@ function attachFilesystem(ctx) {
|
|
|
37
37
|
|
|
38
38
|
var access = ctx.requestAccess || attachRequestAccess(ctx);
|
|
39
39
|
var fileHistory = attachFileHistory(ctx, access);
|
|
40
|
+
var operations = require("./project-file-operations").attachFileOperations(ctx);
|
|
40
41
|
|
|
41
42
|
function handleFilesystemMessage(ws, msg) {
|
|
42
43
|
var fileScope;
|
|
@@ -57,40 +58,11 @@ function attachFilesystem(ctx) {
|
|
|
57
58
|
|
|
58
59
|
// --- fs_list ---
|
|
59
60
|
if (msg.type === "fs_list") {
|
|
60
|
-
var fsListIdentity = fileScope.identity;
|
|
61
|
-
var fsDir = fileTarget;
|
|
62
61
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (fsListUserInfo) {
|
|
66
|
-
// Run as target OS user to respect Linux file permissions
|
|
67
|
-
var rawEntries = fsAsUser("list", { dir: fsDir }, fsListUserInfo);
|
|
68
|
-
for (var fi = 0; fi < rawEntries.length; fi++) {
|
|
69
|
-
var re = rawEntries[fi];
|
|
70
|
-
if (re.isDir && IGNORED_DIRS.has(re.name)) continue;
|
|
71
|
-
entries.push({
|
|
72
|
-
name: re.name,
|
|
73
|
-
type: re.isDir ? "dir" : "file",
|
|
74
|
-
path: path.relative(cwd, path.join(fsDir, re.name)).split(path.sep).join("/"),
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
} else {
|
|
78
|
-
var items = fs.readdirSync(fsDir, { withFileTypes: true });
|
|
79
|
-
for (var fi = 0; fi < items.length; fi++) {
|
|
80
|
-
var item = items[fi];
|
|
81
|
-
if (item.isDirectory() && IGNORED_DIRS.has(item.name)) continue;
|
|
82
|
-
entries.push({
|
|
83
|
-
name: item.name,
|
|
84
|
-
type: item.isDirectory() ? "dir" : "file",
|
|
85
|
-
path: path.relative(cwd, path.join(fsDir, item.name)).split(path.sep).join("/"),
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
sendTo(ws, { type: "fs_list_result", path: msg.path || ".", entries: entries });
|
|
90
|
-
// Auto-watch the directory for changes
|
|
91
|
-
startDirWatch(ws, msg.path || ".");
|
|
62
|
+
sendTo(ws, { type: 'fs_list_result', path: msg.path || '.', entries: operations.list(fileScope, msg.path || '.') });
|
|
63
|
+
startDirWatch(ws, msg.path || '.');
|
|
92
64
|
} catch (e) {
|
|
93
|
-
sendTo(ws, { type:
|
|
65
|
+
sendTo(ws, { type: 'fs_list_result', path: msg.path, entries: [], error: fileError(e).message, errorCode: fileError(e).code });
|
|
94
66
|
}
|
|
95
67
|
return true;
|
|
96
68
|
}
|
|
@@ -143,62 +115,15 @@ function attachFilesystem(ctx) {
|
|
|
143
115
|
|
|
144
116
|
// --- fs_read ---
|
|
145
117
|
if (msg.type === "fs_read") {
|
|
146
|
-
var
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
var fsReadUserInfo = fsReadIdentity;
|
|
150
|
-
var ext = path.extname(fsFile).toLowerCase();
|
|
151
|
-
if (fsReadUserInfo) {
|
|
152
|
-
// Run stat and read as target OS user
|
|
153
|
-
var statResult = fsAsUser("stat", { file: fsFile }, fsReadUserInfo);
|
|
154
|
-
if (statResult.size > FS_MAX_SIZE) {
|
|
155
|
-
sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, binary: true, size: statResult.size, error: "File too large (" + (statResult.size / 1024 / 1024).toFixed(1) + " MB)" });
|
|
156
|
-
return true;
|
|
157
|
-
}
|
|
158
|
-
if (BINARY_EXTS.has(ext)) {
|
|
159
|
-
var result = { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, binary: true, size: statResult.size };
|
|
160
|
-
if (IMAGE_EXTS.has(ext)) result.imageUrl = "api/file?path=" + encodeURIComponent(msg.path);
|
|
161
|
-
sendTo(ws, result);
|
|
162
|
-
return true;
|
|
163
|
-
}
|
|
164
|
-
var readResult = fsAsUser("read", { file: fsFile, readContent: true }, fsReadUserInfo);
|
|
165
|
-
sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, content: readResult.content, size: statResult.size });
|
|
166
|
-
} else {
|
|
167
|
-
var stat = fs.statSync(fsFile);
|
|
168
|
-
if (stat.size > FS_MAX_SIZE) {
|
|
169
|
-
sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, binary: true, size: stat.size, error: "File too large (" + (stat.size / 1024 / 1024).toFixed(1) + " MB)" });
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
if (BINARY_EXTS.has(ext)) {
|
|
173
|
-
var result = { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, binary: true, size: stat.size };
|
|
174
|
-
if (IMAGE_EXTS.has(ext)) result.imageUrl = "api/file?path=" + encodeURIComponent(msg.path);
|
|
175
|
-
sendTo(ws, result);
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
var content = fs.readFileSync(fsFile, "utf8");
|
|
179
|
-
sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, content: content, size: stat.size });
|
|
180
|
-
}
|
|
181
|
-
} catch (e) {
|
|
182
|
-
sendTo(ws, { type: "fs_read_result", path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId, error: fileError(e).message, errorCode: fileError(e).code });
|
|
183
|
-
}
|
|
118
|
+
var metadata = { type: 'fs_read_result', path: msg.path, requestId: msg.requestId, projectSlug: msg.projectSlug, sessionId: msg.sessionId, accountId: msg.accountId };
|
|
119
|
+
try { sendTo(ws, Object.assign(metadata, operations.read(fileScope, msg.path))); }
|
|
120
|
+
catch (e) { sendTo(ws, Object.assign(metadata, { error: fileError(e).message, errorCode: fileError(e).code })); }
|
|
184
121
|
return true;
|
|
185
122
|
}
|
|
186
123
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
var fsWriteFile = fileTarget;
|
|
191
|
-
try {
|
|
192
|
-
var fsWriteUserInfo = fsWriteIdentity;
|
|
193
|
-
if (fsWriteUserInfo) {
|
|
194
|
-
fsAsUser("write", { file: fsWriteFile, content: msg.content || "" }, fsWriteUserInfo);
|
|
195
|
-
} else {
|
|
196
|
-
fs.writeFileSync(fsWriteFile, msg.content || "", "utf8");
|
|
197
|
-
}
|
|
198
|
-
sendTo(ws, { type: "fs_write_result", path: msg.path, ok: true });
|
|
199
|
-
} catch (e) {
|
|
200
|
-
sendTo(ws, { type: "fs_write_result", path: msg.path, ok: false, error: fileError(e).message, errorCode: fileError(e).code });
|
|
201
|
-
}
|
|
124
|
+
if (msg.type === 'fs_write') {
|
|
125
|
+
try { sendTo(ws, Object.assign({ type: 'fs_write_result', path: msg.path }, operations.write(fileScope, msg.path, msg.content || ''))); }
|
|
126
|
+
catch (e) { sendTo(ws, { type: 'fs_write_result', path: msg.path, ok: false, error: fileError(e).message, errorCode: fileError(e).code }); }
|
|
202
127
|
return true;
|
|
203
128
|
}
|
|
204
129
|
|
|
@@ -239,8 +164,8 @@ function attachFilesystem(ctx) {
|
|
|
239
164
|
var setResult;
|
|
240
165
|
try { setResult = opts.onSetProjectEnv(targetSlug, msg.envrc || ""); }
|
|
241
166
|
catch (error) { sendTo(ws, { type: "set_project_env_result", slug: targetSlug, requestId: msg.requestId, ok: false, error: error.message }); return true; }
|
|
242
|
-
if (setResult.ok) onEnvironmentChanged();
|
|
243
|
-
sendTo(ws, { type: "set_project_env_result",
|
|
167
|
+
if (setResult.ok && !setResult.runtimeRefreshed) onEnvironmentChanged();
|
|
168
|
+
sendTo(ws, Object.assign({}, setResult, { type: "set_project_env_result", slug: targetSlug, requestId: msg.requestId, timing: setResult.ok ? "Applies to newly created coding-agent processes. Active processes keep their current environment." : undefined }));
|
|
244
169
|
} else {
|
|
245
170
|
sendTo(ws, { type: "set_project_env_result", slug: targetSlug, requestId: msg.requestId, ok: false, error: "Not supported" });
|
|
246
171
|
}
|
|
@@ -297,6 +222,7 @@ function attachFilesystem(ctx) {
|
|
|
297
222
|
|
|
298
223
|
return {
|
|
299
224
|
handleFilesystemMessage: handleFilesystemMessage,
|
|
225
|
+
productOperations: operations,
|
|
300
226
|
};
|
|
301
227
|
}
|
|
302
228
|
|
|
@@ -16,7 +16,7 @@ function createFullAccessService(ctx) {
|
|
|
16
16
|
enabled: session.permissionMode === "bypassPermissions" };
|
|
17
17
|
}
|
|
18
18
|
function persist(session) {
|
|
19
|
-
sm.saveSessionFile(session);
|
|
19
|
+
if (sm.saveSessionFile(session) === false) throw new Error("Could not persist permission mode.");
|
|
20
20
|
sm.broadcastSessionList();
|
|
21
21
|
if (typeof sm.sendToSession === "function") sm.sendToSession(session, { type: "session_full_access_changed", id: session.localId,
|
|
22
22
|
enabled: session.permissionMode === "bypassPermissions", permissionMode: session.permissionMode });
|
|
@@ -55,7 +55,28 @@ function createFullAccessService(ctx) {
|
|
|
55
55
|
throw error;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
|
-
function setMode(session, mode
|
|
58
|
+
function setMode(session, mode, options) {
|
|
59
|
+
return serialize(session, function () {
|
|
60
|
+
if (!options || typeof options.revalidate !== "function") return applyMode(session, mode);
|
|
61
|
+
options.revalidate();
|
|
62
|
+
var observed = { revision: Number(session._permissionObservationRevision || 0), mode: session.effectivePermissionMode, reason: session.permissionModeFallbackReason };
|
|
63
|
+
return require("./session-config-transition").applySessionConfig({
|
|
64
|
+
sm: sm, session: session, revalidate: options.revalidate, method: "setPermissionMode", nativeValue: mode === "bypassPermissions" ? "default" : mode,
|
|
65
|
+
changes: function () {
|
|
66
|
+
var newer = Number(session._permissionObservationRevision || 0) !== observed.revision || session.effectivePermissionMode !== observed.mode || session.permissionModeFallbackReason !== observed.reason;
|
|
67
|
+
return { permissionMode: mode, permissionModeBeforeFullAccess: mode === "bypassPermissions" ? (session.permissionMode || sm.currentPermissionMode || "default") : null,
|
|
68
|
+
effectivePermissionMode: newer ? session.effectivePermissionMode : null, permissionModeFallbackReason: newer ? session.permissionModeFallbackReason : null };
|
|
69
|
+
},
|
|
70
|
+
publish: function () {
|
|
71
|
+
sm.broadcastSessionList();
|
|
72
|
+
if (typeof sm.sendToSession === "function") {
|
|
73
|
+
sm.sendToSession(session, { type: "session_full_access_changed", id: session.localId, enabled: mode === "bypassPermissions", permissionMode: mode });
|
|
74
|
+
sm.sendToSession(session, { type: "config_state", sessionId: session.localId, mode: mode, effectivePermissionMode: session.effectivePermissionMode || null, vendor: session.vendor || sm.defaultVendor || "claude" });
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
59
80
|
function setEnabled(session, enabled) {
|
|
60
81
|
return serialize(session, function () {
|
|
61
82
|
return applyMode(session, enabled ? "bypassPermissions" : (session.permissionModeBeforeFullAccess || "default"));
|
package/lib/project-http.js
CHANGED
|
@@ -419,9 +419,9 @@ function attachHTTP(ctx) {
|
|
|
419
419
|
var uninstallUserInfo = getOsUserInfoForReq(req);
|
|
420
420
|
var baseDir = scope === "global" ? (uninstallUserInfo ? uninstallUserInfo.home : require("./config").REAL_HOME) : cwd;
|
|
421
421
|
if (osUsers && !uninstallUserInfo) throw new Error("A mapped OS user is required to uninstall skills");
|
|
422
|
-
var skillDir = claySkillStorage.isClaySkill(skill) ? path.join(claySkillStorage.privateRoot(baseDir), skill) : path.join(
|
|
422
|
+
var skillDir = claySkillStorage.isClaySkill(skill) ? path.join(claySkillStorage.privateRoot(baseDir), skill) : path.join(require("./yoke/source-locations").legacySkillRoot(baseDir), skill);
|
|
423
423
|
// Safety: keep removal inside the selected skill storage root.
|
|
424
|
-
var expectedParent = claySkillStorage.isClaySkill(skill) ? claySkillStorage.privateRoot(baseDir) :
|
|
424
|
+
var expectedParent = claySkillStorage.isClaySkill(skill) ? claySkillStorage.privateRoot(baseDir) : require("./yoke/source-locations").legacySkillRoot(baseDir);
|
|
425
425
|
var resolved = path.resolve(skillDir);
|
|
426
426
|
if (!resolved.startsWith(expectedParent + path.sep)) {
|
|
427
427
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
@@ -528,8 +528,8 @@ function attachHTTP(ctx) {
|
|
|
528
528
|
var sui = getOsUserInfoForReq(req);
|
|
529
529
|
return sui ? sui.home : require("./config").REAL_HOME;
|
|
530
530
|
})();
|
|
531
|
-
var globalSkillsDir =
|
|
532
|
-
var projectSkillsDir =
|
|
531
|
+
var globalSkillsDir = require("./yoke/source-locations").legacySkillRoot(skillUserHome);
|
|
532
|
+
var projectSkillsDir = require("./yoke/source-locations").legacySkillRoot(cwd);
|
|
533
533
|
var results = [];
|
|
534
534
|
var pending = skills.length;
|
|
535
535
|
|
package/lib/project-knowledge.js
CHANGED
|
@@ -44,21 +44,30 @@ function attachKnowledge(ctx) {
|
|
|
44
44
|
return { type: user ? "user" : "system", userId: user ? user.id : null, displayName: user ? (user.displayName || user.username) : null };
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function
|
|
47
|
+
function mirrorActor(who, name) {
|
|
48
48
|
if (!isMate) return { ok: true, names: [] };
|
|
49
|
-
var summary = mateSync.syncMateSource({ mateDir: cwd, fileName: name, baseDir: ctx.knowledgeBaseDir, actor:
|
|
49
|
+
var summary = mateSync.syncMateSource({ mateDir: cwd, fileName: name, baseDir: ctx.knowledgeBaseDir, actor: who });
|
|
50
50
|
return { ok: !!summary && summary.failed === 0, names: [name], error: summary && summary.errors[0] ? summary.errors[0].message : "Knowledge index synchronization failed." };
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
function
|
|
53
|
+
function mirror(ws, name) { return mirrorActor(actor(ws), name); }
|
|
54
|
+
|
|
55
|
+
function mirrorManyActor(who, names) {
|
|
54
56
|
var failures = [];
|
|
55
57
|
for (var i = 0; i < names.length; i++) {
|
|
56
|
-
var mirrored =
|
|
58
|
+
var mirrored = mirrorActor(who, names[i]);
|
|
57
59
|
if (!mirrored.ok) failures.push(mirrored.error);
|
|
58
60
|
}
|
|
59
61
|
return { ok: failures.length === 0, names: names.slice(), error: failures[0] || null };
|
|
60
62
|
}
|
|
61
63
|
|
|
64
|
+
function mirrorMany(ws, names) { return mirrorManyActor(actor(ws), names); }
|
|
65
|
+
|
|
66
|
+
function controlMirror(who, names) {
|
|
67
|
+
try { var state = mirrorManyActor(who, names); return { indexSyncPending: !state.ok, syncNames: names, syncError: state.ok ? null : state.error }; }
|
|
68
|
+
catch (error) { return { indexSyncPending: true, syncNames: names, syncError: error.message }; }
|
|
69
|
+
}
|
|
70
|
+
|
|
62
71
|
function addSyncState(payload, sync) {
|
|
63
72
|
if (sync.ok) return payload;
|
|
64
73
|
return Object.assign(payload, { indexSyncPending: true, syncNames: sync.names, syncError: sync.error });
|
|
@@ -188,6 +197,22 @@ function attachKnowledge(ctx) {
|
|
|
188
197
|
return databaseReady.then(function () { return combineSearch(databases.listCached({})); });
|
|
189
198
|
}
|
|
190
199
|
|
|
200
|
+
function renameDocument(msg, who) {
|
|
201
|
+
var beforeMoveState = documentIndex(); var beforeMove = beforeMoveState.documents; var moveTarget = findDocument(beforeMove, msg.from, msg.documentId); var linkChanges = moveTarget ? rewriteMoveLinks(beforeMove, moveTarget, msg.name) : [];
|
|
202
|
+
var renamed = files.rename(msg.from, msg.name, msg.expectedRevision || null); var linkUpdateFailures = []; var updatedLinks = 0;
|
|
203
|
+
var updatedDocuments = []; var mirrorNames = [msg.from, renamed.name];
|
|
204
|
+
for (var u = 0; u < linkChanges.length; u++) {
|
|
205
|
+
var change = linkChanges[u]; var writeName = change.id === renamed.id ? renamed.name : change.name; var expected = change.id === renamed.id ? renamed.revision : change.revision;
|
|
206
|
+
try { var rewritten = files.save(writeName, change.content, expected); updatedLinks += change.count; updatedDocuments.push(rewritten); mirrorNames.push(rewritten.name); }
|
|
207
|
+
catch (linkError) { linkUpdateFailures.push({ name: writeName, error: linkError.message }); }
|
|
208
|
+
}
|
|
209
|
+
renamed = files.read(renamed.name);
|
|
210
|
+
var uniqueMirrorNames = mirrorNames.filter(function (name, at) { return mirrorNames.indexOf(name) === at; }); var renameSync = mirrorManyActor(who, uniqueMirrorNames);
|
|
211
|
+
return addSyncState({ type: "knowledge_renamed", id: renamed.id, from: msg.from, name: renamed.name, revision: renamed.revision,
|
|
212
|
+
content: renamed.content, updatedLinks: updatedLinks, linkUpdateFailures: linkUpdateFailures,
|
|
213
|
+
updatedDocuments: updatedDocuments.map(function (item) { return { id: item.id, name: item.name, content: item.content, revision: item.revision }; }) }, renameSync);
|
|
214
|
+
}
|
|
215
|
+
|
|
191
216
|
function handleKnowledgeMessage(ws, msg) {
|
|
192
217
|
if (msg.type === "knowledge_list") {
|
|
193
218
|
try { var listed = listKnowledgeItems(ws); if (listed && typeof listed.then === "function") listed.then(function (items) { response(ws, msg, { type: "knowledge_list", files: items, drafts: drafts.list(ws && ws._clayUser && ws._clayUser.id || "system") }); }).catch(function (error) { failure(ws, msg, error); }); else response(ws, msg, { type: "knowledge_list", files: listed, drafts: drafts.list(ws && ws._clayUser && ws._clayUser.id || "system") }); } catch (error) { failure(ws, msg, error); }
|
|
@@ -236,19 +261,7 @@ function attachKnowledge(ctx) {
|
|
|
236
261
|
if (msg.type === "knowledge_rename") {
|
|
237
262
|
if (!requireWrite(ws, msg)) return true;
|
|
238
263
|
try {
|
|
239
|
-
|
|
240
|
-
var renamed = files.rename(msg.from, msg.name, msg.expectedRevision || null); var linkUpdateFailures = []; var updatedLinks = 0;
|
|
241
|
-
var updatedDocuments = []; var mirrorNames = [msg.from, renamed.name];
|
|
242
|
-
for (var u = 0; u < linkChanges.length; u++) {
|
|
243
|
-
var change = linkChanges[u]; var writeName = change.id === renamed.id ? renamed.name : change.name; var expected = change.id === renamed.id ? renamed.revision : change.revision;
|
|
244
|
-
try { var rewritten = files.save(writeName, change.content, expected); updatedLinks += change.count; updatedDocuments.push(rewritten); mirrorNames.push(rewritten.name); }
|
|
245
|
-
catch (linkError) { linkUpdateFailures.push({ name: writeName, error: linkError.message }); }
|
|
246
|
-
}
|
|
247
|
-
renamed = files.read(renamed.name);
|
|
248
|
-
var uniqueMirrorNames = mirrorNames.filter(function (name, at) { return mirrorNames.indexOf(name) === at; }); var renameSync = mirrorMany(ws, uniqueMirrorNames);
|
|
249
|
-
response(ws, msg, addSyncState({ type: "knowledge_renamed", id: renamed.id, from: msg.from, name: renamed.name, revision: renamed.revision,
|
|
250
|
-
content: renamed.content, updatedLinks: updatedLinks, linkUpdateFailures: linkUpdateFailures,
|
|
251
|
-
updatedDocuments: updatedDocuments.map(function (item) { return { id: item.id, name: item.name, content: item.content, revision: item.revision }; }) }, renameSync));
|
|
264
|
+
response(ws, msg, renameDocument(msg, actor(ws)));
|
|
252
265
|
sendTo(ws, { type: "knowledge_list", files: listKnowledgeFiles() });
|
|
253
266
|
} catch (error) { failure(ws, msg, error); }
|
|
254
267
|
return true;
|
|
@@ -357,7 +370,8 @@ function attachKnowledge(ctx) {
|
|
|
357
370
|
return false;
|
|
358
371
|
}
|
|
359
372
|
|
|
360
|
-
|
|
373
|
+
var productOperations = require('./knowledge-product-operations').createKnowledgeProductOperations({ files: files, mirror: controlMirror, rename: renameDocument });
|
|
374
|
+
return { productOperations: productOperations, handleKnowledgeMessage: handleKnowledgeMessage, listKnowledgeFiles: listKnowledgeFiles, databases: databases };
|
|
361
375
|
}
|
|
362
376
|
|
|
363
377
|
module.exports = { attachKnowledge: attachKnowledge };
|