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
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
var fs = require('fs');
|
|
2
|
+
var path = require('path');
|
|
3
|
+
var os = require('os');
|
|
4
|
+
var crypto = require('crypto');
|
|
5
|
+
var adapters = require('./mcp-source-adapters');
|
|
6
|
+
var sourceContext = require('./mcp-source-context');
|
|
7
|
+
var restrictions = require('./mcp-source-restrictions');
|
|
8
|
+
var fsAsUser = require('./os-users').fsAsUser;
|
|
9
|
+
function createDiscovery(ctx) {
|
|
10
|
+
var scans = new Map(), publicScans = new Map();
|
|
11
|
+
function read(file, identity) {
|
|
12
|
+
if (identity) {
|
|
13
|
+
var stat = fsAsUser('stat', { file: file }, identity);
|
|
14
|
+
if (stat.size > 2 * 1024 * 1024 || stat.isDir) throw new Error('Source exceeds size limit.');
|
|
15
|
+
return fsAsUser('read', { file: file, readContent: true }, identity).content;
|
|
16
|
+
}
|
|
17
|
+
var info = fs.statSync(file);
|
|
18
|
+
if (!info.isFile() || info.size > 2 * 1024 * 1024) throw new Error('Source exceeds size limit.');
|
|
19
|
+
return fs.readFileSync(file, 'utf8');
|
|
20
|
+
}
|
|
21
|
+
function list(directory, identity) {
|
|
22
|
+
try { return identity ? fsAsUser('list', { dir: directory }, identity) : fs.readdirSync(directory, { withFileTypes: true }).map(function (entry) { return { name: entry.name, isDir: entry.isDirectory() }; }); }
|
|
23
|
+
catch (error) { return []; }
|
|
24
|
+
}
|
|
25
|
+
function scan(owner, ws, explicit) {
|
|
26
|
+
ctx.authorize(owner, ws);
|
|
27
|
+
var identity = ctx.identity(ws), home = identity ? identity.home : ctx.home || os.homedir();
|
|
28
|
+
var context = sourceContext.resolve(ctx, owner, ws, home, identity);
|
|
29
|
+
var rows = [], diagnostics = [], sources = context.sources, files = new Map(), directories = new Map();
|
|
30
|
+
function listing(directory) { var entries = list(directory, identity); directories.set(directory, JSON.stringify(entries.slice().sort(function (left, right) { return left.name.localeCompare(right.name); }))); return entries; }
|
|
31
|
+
function source(vendor, file, scope, pluginRoot, extra) { sources.push(Object.assign({ vendor: vendor, file: file, scope: scope, cwd: ctx.cwd, pluginRoot: pluginRoot }, extra)); }
|
|
32
|
+
Object.keys(adapters.adapters).forEach(function (vendor) {
|
|
33
|
+
var adapter = adapters.adapters[vendor];
|
|
34
|
+
if (adapter.agents) [home, ctx.cwd].forEach(function (base) {
|
|
35
|
+
var directory = path.join(base, adapter.agents);
|
|
36
|
+
listing(directory).slice(0, 100).forEach(function (entry) { if (!entry.isDir && /\.(json|md)$/.test(entry.name)) source(vendor, path.join(directory, entry.name), 'agent'); });
|
|
37
|
+
});
|
|
38
|
+
if (adapter.plugins) {
|
|
39
|
+
var directory = path.join(home, adapter.plugins);
|
|
40
|
+
listing(directory).slice(0, 100).forEach(function (entry) { if (entry.isDir) source(vendor, path.join(directory, entry.name, 'mcp_config.json'), 'plugin', path.join(directory, entry.name), { blocked: 'This plugin has no locally resolved activation registry. Review its active configuration before importing.' }); });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
// Copilot reads nearer project definitions before ancestors up to the Git root.
|
|
44
|
+
var parents = [], repository = false;
|
|
45
|
+
var currentDirectory = ctx.cwd;
|
|
46
|
+
while (true) {
|
|
47
|
+
try { if (identity) fsAsUser('stat', { file: path.join(currentDirectory, '.git') }, identity); else fs.statSync(path.join(currentDirectory, '.git')); repository = true; break; } catch (error) {}
|
|
48
|
+
var parent = path.dirname(currentDirectory);
|
|
49
|
+
if (parent === currentDirectory) break;
|
|
50
|
+
parents.push(parent); currentDirectory = parent;
|
|
51
|
+
}
|
|
52
|
+
if (repository) parents.forEach(function (directory) {
|
|
53
|
+
source('copilot', path.join(directory, '.mcp.json'), 'project-ancestor');
|
|
54
|
+
source('copilot', path.join(directory, '.github/mcp.json'), 'project-ancestor');
|
|
55
|
+
});
|
|
56
|
+
diagnostics.push({ vendor: 'all', scope: 'plugins', message: 'Remote bundles are not downloaded. Unresolved installed plugin activation and local managed policies block import; credentials and vendor OAuth stores are never inspected.' });
|
|
57
|
+
if (explicit) {
|
|
58
|
+
if (!adapters.adapters[explicit.vendor] || typeof explicit.file !== 'string' || !path.isAbsolute(explicit.file)) throw new Error('Choose a supported format and an absolute source file path.');
|
|
59
|
+
source(explicit.vendor, explicit.file, 'configured');
|
|
60
|
+
}
|
|
61
|
+
function load(file) {
|
|
62
|
+
if (files.has(file)) { var cached = files.get(file); if (cached.error) throw cached.error; return cached.value; }
|
|
63
|
+
try {
|
|
64
|
+
var content = read(file, identity), value = adapters.parse(file, content);
|
|
65
|
+
files.set(file, { value: value, digest: crypto.createHash('sha256').update(content).digest('hex') }); return value;
|
|
66
|
+
} catch (error) { files.set(file, { error: error, digest: null }); throw error; }
|
|
67
|
+
}
|
|
68
|
+
require('./mcp-source-plugins').discover({ load: load, source: source, list: listing, roots: context.roots, cwd: ctx.cwd, diagnostics: diagnostics });
|
|
69
|
+
var blockedVendors = restrictions.inspect({ load: load, roots: context.roots, policyPaths: context.policyPaths, cwd: ctx.cwd, home: home, username: identity ? identity.username : process.env.USER });
|
|
70
|
+
var openCodeTools = {}, openCodeDefinitions = {};
|
|
71
|
+
function collect(source, definitions, root) {
|
|
72
|
+
if (!definitions || typeof definitions !== 'object') return;
|
|
73
|
+
var names = Array.isArray(definitions) ? definitions.map(function (definition) { return definition.name; }) : Object.keys(definitions);
|
|
74
|
+
names.slice(0, 100).forEach(function (name, index) {
|
|
75
|
+
if (typeof name !== 'string' || !name || name.length > 100) return;
|
|
76
|
+
try {
|
|
77
|
+
var definition = Array.isArray(definitions) ? definitions[index] : definitions[name];
|
|
78
|
+
if (source.vendor === 'opencode') {
|
|
79
|
+
definition = Object.assign({}, openCodeDefinitions[name] || {}, definition);
|
|
80
|
+
openCodeDefinitions[name] = definition;
|
|
81
|
+
}
|
|
82
|
+
if (source.overrides && source.overrides[name]) definition = Object.assign({}, definition, source.overrides[name]);
|
|
83
|
+
var provenance = Object.assign({}, source); delete provenance.inline; delete provenance.overrides;
|
|
84
|
+
var config = adapters.normalize(source.vendor, name, definition, provenance);
|
|
85
|
+
var blocked = source.blocked || blockedVendors[source.vendor];
|
|
86
|
+
if (root && Object.keys(root).some(function (key) { return restrictions.securityKey(key); })) blocked = 'This source contains runtime or agent restrictions Clay cannot resolve. Review its effective policy before importing.';
|
|
87
|
+
if (root && root.tools !== undefined && source.vendor !== 'opencode') blocked = 'This source contains runtime tool restrictions Clay cannot resolve. Review its effective policy before importing.';
|
|
88
|
+
var known = ['type', 'transport', 'command', 'args', 'env', 'environment', 'cwd', 'url', 'httpUrl', 'serverUrl', 'headers', 'http_headers', 'env_http_headers', 'bearer_token_env_var', 'bearerTokenEnvVar', 'includeTools', 'excludeTools', 'env_vars', 'enabled', 'disabled', 'autoApprove', 'disabledTools', 'disabled_tools', 'enabledTools', 'enabled_tools', 'timeout', 'startup_timeout_sec', 'tool_timeout_sec', 'oauth', 'oauthScopes', 'trust', 'name', 'description', 'tools', 'startupTimeoutMs'];
|
|
89
|
+
var unknown = Object.keys(definition).filter(function (key) { return known.indexOf(key) < 0; });
|
|
90
|
+
if (unknown.length) blocked = 'Unrecognized server fields require deliberate review in a direct Clay connection: ' + unknown.slice(0, 10).join(', ');
|
|
91
|
+
if (unknown.length) diagnostics.push({ vendor: source.vendor, scope: source.scope, name: name, message: 'Unrecognized fields are not imported: ' + unknown.slice(0, 10).join(', ') });
|
|
92
|
+
var hash = adapters.signature(config);
|
|
93
|
+
rows.push({ id: crypto.randomUUID(), name: name, config: config, signature: hash, blocked: blocked });
|
|
94
|
+
} catch (error) { diagnostics.push({ vendor: source.vendor, scope: source.scope, name: name, message: error.message }); }
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (sources.length > 500) diagnostics.push({ vendor: 'all', scope: 'discovery', message: 'Source limit reached; inspect additional files individually.' });
|
|
98
|
+
sources.sort(function (left, right) { return sourceContext.priority(left) - sourceContext.priority(right); });
|
|
99
|
+
sources.slice(0, 500).forEach(function (source) {
|
|
100
|
+
try {
|
|
101
|
+
var value = source.inline ? adapters.parse('inline.json', source.inline) : load(source.file), adapter = adapters.adapters[source.vendor];
|
|
102
|
+
if (Object.keys(value).some(function (key) { return restrictions.securityKey(key); })) blockedVendors[source.vendor] = 'A configuration layer contains unresolved security restrictions. Review the effective vendor policy before importing.';
|
|
103
|
+
if (source.vendor === 'opencode' && value.agent && Object.keys(value.agent).some(function (name) { return Object.keys(value.agent[name] || {}).some(function (key) { return restrictions.securityKey(key) || key === 'tools'; }); })) blockedVendors.opencode = 'OpenCode agent-specific restrictions require review. Configure a direct Clay connection with the intended restrictions.';
|
|
104
|
+
collect(source, source.scope === 'plugin-inline' ? value.mcpServers : value[adapter.key] || (source.vendor === 'codex' && !source.file.endsWith('.toml') ? value.mcpServers || value : source.vendor === 'copilot' || source.scope === 'plugin' ? value : null), value);
|
|
105
|
+
if (source.vendor === 'opencode' && value.tools !== undefined) {
|
|
106
|
+
if (!value.tools || typeof value.tools !== 'object' || Array.isArray(value.tools) || Object.values(value.tools).some(function (enabled) { return typeof enabled !== 'boolean'; })) blockedVendors.opencode = 'Invalid OpenCode tool policy. Review the runtime configuration before importing.';
|
|
107
|
+
else Object.assign(openCodeTools, value.tools);
|
|
108
|
+
}
|
|
109
|
+
if (source.vendor === 'claude' && source.scope === 'user' && value.projects && value.projects[ctx.cwd]) collect(Object.assign({}, source, { scope: 'local' }), value.projects[ctx.cwd].mcpServers, value.projects[ctx.cwd]);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
if (error.code !== 'ENOENT' && !(identity && /ENOENT|no such file/i.test(error.message))) diagnostics.push({ vendor: source.vendor, scope: source.scope, message: 'Could not read or parse source; check permissions and format.' });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
rows.forEach(function (row) {
|
|
115
|
+
var source = row.config.provenance;
|
|
116
|
+
row.blocked = row.blocked || blockedVendors[source.vendor];
|
|
117
|
+
if (source.vendor === 'opencode') {
|
|
118
|
+
row.config.toolPrefix = row.name + '_';
|
|
119
|
+
row.config.toolPatterns = Object.keys(openCodeTools).filter(function (pattern) { return openCodeTools[pattern] === false; });
|
|
120
|
+
row.signature = adapters.signature(row.config);
|
|
121
|
+
}
|
|
122
|
+
var effective = rows.filter(function (other) { return other.name === row.name && other.config.provenance.vendor === source.vendor && other.config.provenance.scope !== 'agent' && other.config.provenance.scope.indexOf('plugin') !== 0; }).pop();
|
|
123
|
+
if (source.scope !== 'agent' && source.scope.indexOf('plugin') !== 0 && effective && effective !== row) row.blocked = 'Shadowed by the effective ' + effective.config.provenance.scope + ' source. Import that definition instead.';
|
|
124
|
+
});
|
|
125
|
+
var current = ctx.snapshot(owner);
|
|
126
|
+
rows.forEach(function (row) {
|
|
127
|
+
var same = rows.filter(function (other) { return other !== row && (other.name === row.name || row.config.url && other.config.url === row.config.url); });
|
|
128
|
+
row.duplicate = rows.some(function (other) { return other !== row && other.signature === row.signature; });
|
|
129
|
+
row.conflict = same.some(function (other) { return other.signature !== row.signature; });
|
|
130
|
+
row.imported = current.records.some(function (saved) { return saved.importSignature === row.signature || row.config.url && saved.url === row.config.url; });
|
|
131
|
+
});
|
|
132
|
+
var scanId = crypto.randomUUID();
|
|
133
|
+
scans.set(owner, { id: scanId, ws: ws, rows: rows, files: files, directories: directories, identity: identity, revision: current.revision, context: context.fingerprint, home: home, created: Date.now() });
|
|
134
|
+
var publicScan = { scanId: scanId, candidates: rows.map(function (row) {
|
|
135
|
+
return { id: row.id, name: row.name, vendor: row.config.provenance.vendor, scope: row.config.provenance.scope, file: row.config.provenance.file,
|
|
136
|
+
blocked: row.blocked || null, pluginName: row.config.provenance.pluginName, pluginVersion: row.config.provenance.pluginVersion, transport: row.config.transport, dependency: row.config.command ? (/^[a-zA-Z0-9_./\\-]+$/.test(row.config.command) ? path.basename(row.config.command) : 'Configured executable') : new URL(row.config.url).hostname,
|
|
137
|
+
references: require('./mcp-import-credentials').requiredNames(row.config), environmentNames: row.config.environmentNames, sourceEnabled: row.config.sourceEnabled,
|
|
138
|
+
duplicate: row.duplicate, conflict: row.conflict, imported: row.imported };
|
|
139
|
+
}), diagnostics: diagnostics };
|
|
140
|
+
publicScans.set(owner, publicScan); return publicScan;
|
|
141
|
+
}
|
|
142
|
+
function selected(owner, ws, msg) {
|
|
143
|
+
ctx.authorize(owner, ws);
|
|
144
|
+
var scan = scans.get(owner);
|
|
145
|
+
if (!scan || scan.ws !== ws || scan.id !== msg.scanId || Date.now() - scan.created > 10 * 60 * 1000) throw new Error('Discovery expired. Scan again.');
|
|
146
|
+
if (JSON.stringify(ctx.identity(ws)) !== JSON.stringify(scan.identity)) throw new Error('OS identity changed. Scan again.');
|
|
147
|
+
if (sourceContext.resolve(ctx, owner, ws, scan.home, scan.identity).fingerprint !== scan.context) throw new Error('Runtime configuration changed. Scan again.');
|
|
148
|
+
scan.directories.forEach(function (state, directory) {
|
|
149
|
+
var entries = list(directory, scan.identity).slice().sort(function (left, right) { return left.name.localeCompare(right.name); });
|
|
150
|
+
if (JSON.stringify(entries) !== state) throw new Error('Installed source selection changed. Scan again.');
|
|
151
|
+
});
|
|
152
|
+
scan.files.forEach(function (state, file) {
|
|
153
|
+
var hash = null;
|
|
154
|
+
try { hash = crypto.createHash('sha256').update(read(file, scan.identity)).digest('hex'); } catch (error) {
|
|
155
|
+
if (!state.error || (error.code || error.message) !== (state.error.code || state.error.message)) throw new Error('Vendor configuration changed. Scan again.');
|
|
156
|
+
}
|
|
157
|
+
if (hash !== state.digest) throw new Error('Vendor configuration changed. Scan again.');
|
|
158
|
+
});
|
|
159
|
+
var row = scan.rows.find(function (candidate) { return candidate.id === msg.candidateId; });
|
|
160
|
+
if (row && row.blocked) throw new Error(row.blocked);
|
|
161
|
+
if (!row || row.imported) throw new Error('Source is unavailable or already imported.');
|
|
162
|
+
return { config: JSON.parse(JSON.stringify(row.config)), signature: row.signature, revision: scan.revision };
|
|
163
|
+
}
|
|
164
|
+
return { scan: scan, selected: selected, candidate: function (owner, subject, args) {
|
|
165
|
+
selected(owner, subject, args);
|
|
166
|
+
return publicScans.get(owner).candidates.find(function (row) { return row.id === args.candidateId; });
|
|
167
|
+
}, destroy: function () { scans.clear(); publicScans.clear(); } };
|
|
168
|
+
}
|
|
169
|
+
module.exports = { createDiscovery: createDiscovery };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
var path = require('path');
|
|
2
|
+
function discover(ctx) {
|
|
3
|
+
var load = ctx.load, source = ctx.source, list = ctx.list, roots = ctx.roots;
|
|
4
|
+
function optional(file) { try { return load(file); } catch (error) { return {}; } }
|
|
5
|
+
var enabled = {};
|
|
6
|
+
var settingsFiles = [path.join(roots.claude, 'settings.json'), path.join(ctx.cwd, '.claude/settings.json'), path.join(ctx.cwd, '.claude/settings.local.json')];
|
|
7
|
+
settingsFiles.forEach(function (file) { Object.assign(enabled, optional(file).enabledPlugins || {}); });
|
|
8
|
+
var installed = optional(path.join(roots.claude, 'plugins/installed_plugins.json'));
|
|
9
|
+
Object.keys(installed.plugins || {}).slice(0, 100).forEach(function (name) {
|
|
10
|
+
var entries = (installed.plugins[name] || []).filter(function (entry) { return entry.installPath && (!entry.projectPath || path.resolve(entry.projectPath) === path.resolve(ctx.cwd)); });
|
|
11
|
+
var scoped = entries.filter(function (entry) { return entry.scope === 'local' || entry.scope === 'project'; });
|
|
12
|
+
if (scoped.length) entries = scoped;
|
|
13
|
+
var local = entries.filter(function (entry) { return entry.scope === 'local'; }); if (local.length) entries = local;
|
|
14
|
+
// The registry is authoritative only when it selects one installation.
|
|
15
|
+
entries.forEach(function (entry) {
|
|
16
|
+
var extra = { pluginName: name, pluginVersion: entry.version || null, sourceEnabled: enabled[name] === true ? true : enabled[name] === false ? false : null,
|
|
17
|
+
blocked: entries.length > 1 ? 'Claude registry selects multiple installations. Resolve the active plugin installation before importing.' : typeof enabled[name] !== 'boolean' ? 'Claude plugin enablement is unknown. Enable or disable it explicitly in vendor settings and scan again.' : null };
|
|
18
|
+
var root = entry.installPath;
|
|
19
|
+
source('claude', path.join(root, '.mcp.json'), 'plugin', root, extra);
|
|
20
|
+
var manifest = optional(path.join(root, '.claude-plugin/plugin.json'));
|
|
21
|
+
var definitions = Array.isArray(manifest.mcpServers) ? manifest.mcpServers : [manifest.mcpServers];
|
|
22
|
+
definitions.forEach(function (definition) {
|
|
23
|
+
if (typeof definition === 'string' && definition.endsWith('.json') && !definition.includes('://')) source('claude', path.resolve(root, definition), 'plugin', root, extra);
|
|
24
|
+
else if (definition && typeof definition === 'object') source('claude', path.join(root, '.claude-plugin/plugin.json'), 'plugin-inline', root, extra);
|
|
25
|
+
else if (definition) ctx.diagnostics.push({ vendor: 'claude', scope: 'plugin', message: 'Remote plugin bundles are not downloaded. Install and resolve the local plugin before scanning.' });
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
var codex = {};
|
|
30
|
+
[path.join(roots.codex, 'config.toml'), path.join(ctx.cwd, '.codex/config.toml')].forEach(function (file) {
|
|
31
|
+
var plugins = optional(file).plugins || {};
|
|
32
|
+
Object.keys(plugins).forEach(function (name) {
|
|
33
|
+
var previous = codex[name] || {}, next = plugins[name] || {};
|
|
34
|
+
codex[name] = Object.assign({}, previous, next, { mcp_servers: Object.assign({}, previous.mcp_servers || {}, next.mcp_servers || {}) });
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
var cache = path.join(roots.codex, 'plugins/cache');
|
|
38
|
+
list(cache).slice(0, 50).forEach(function (market) {
|
|
39
|
+
if (!market.isDir) return;
|
|
40
|
+
list(path.join(cache, market.name)).slice(0, 50).forEach(function (plugin) {
|
|
41
|
+
if (!plugin.isDir) return;
|
|
42
|
+
var name = plugin.name + '@' + market.name, configured = codex[name] || {};
|
|
43
|
+
var versions = list(path.join(cache, market.name, plugin.name)).filter(function (entry) { return entry.isDir; });
|
|
44
|
+
// Codex core-plugin-common/installed.rs selects local first, otherwise the greatest installed version.
|
|
45
|
+
versions = versions.filter(function (entry) { return /^[A-Za-z0-9._+-]+$/.test(entry.name); }).sort(function (left, right) {
|
|
46
|
+
var semver = require('semver');
|
|
47
|
+
return semver.valid(left.name) && semver.valid(right.name) ? semver.compareBuild(left.name, right.name) : left.name < right.name ? -1 : left.name > right.name ? 1 : 0;
|
|
48
|
+
});
|
|
49
|
+
var active = versions.find(function (entry) { return entry.name === 'local'; }) || versions[versions.length - 1];
|
|
50
|
+
versions = active ? [active] : [];
|
|
51
|
+
versions.slice(0, 50).forEach(function (version) {
|
|
52
|
+
var root = path.join(cache, market.name, plugin.name, version.name);
|
|
53
|
+
var extra = { pluginName: name, pluginVersion: version.name, overrides: configured.mcp_servers,
|
|
54
|
+
sourceEnabled: Object.prototype.hasOwnProperty.call(codex, name) ? configured.enabled !== false : false,
|
|
55
|
+
blocked: configured.enabled !== undefined && typeof configured.enabled !== 'boolean' ? 'Invalid Codex plugin enabled state. Review its vendor configuration before importing.' : null };
|
|
56
|
+
var portable = optional(path.join(root, 'plugin.json'));
|
|
57
|
+
var manifest = portable.extensions && portable.extensions['com.openai'] || optional(path.join(root, '.codex-plugin/plugin.json'));
|
|
58
|
+
if (portable.name) source('codex', path.join(root, 'mcp.json'), 'plugin', root, extra);
|
|
59
|
+
else {
|
|
60
|
+
source('codex', path.join(root, '.mcp.json'), 'plugin', root, extra);
|
|
61
|
+
source('codex', path.join(root, 'mcp.json'), 'plugin', root, extra);
|
|
62
|
+
}
|
|
63
|
+
if (typeof manifest.mcpServers === 'string' && !manifest.mcpServers.includes('://')) source('codex', path.resolve(root, manifest.mcpServers), 'plugin', root, extra);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
module.exports = { discover: discover };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
var path = require('path');
|
|
2
|
+
var SECURITY = /permission|policy|security|sandbox|registry|disabledMcp|restrict|approval|allowed|denied|managedMcp|strictPlugin|includeTools|excludeTools/i;
|
|
3
|
+
function inspect(ctx) {
|
|
4
|
+
var blocked = {}, files = [];
|
|
5
|
+
function add(vendor, file, exclusive) { files.push({ vendor: vendor, file: file, exclusive: exclusive }); }
|
|
6
|
+
var claudeManaged = process.platform === 'darwin' ? '/Library/Application Support/ClaudeCode' : process.platform === 'win32' ? path.join(process.env.ProgramFiles || 'C:\\Program Files', 'ClaudeCode') : '/etc/claude-code';
|
|
7
|
+
['managed-mcp.json', 'managed-settings.json'].forEach(function (file) { add('claude', path.join(claudeManaged, file), true); });
|
|
8
|
+
add('claude', path.join(claudeManaged, 'managed-settings.d'), true);
|
|
9
|
+
if (process.platform === 'darwin') {
|
|
10
|
+
add('claude', '/Library/Managed Preferences/com.anthropic.claudecode.plist', true);
|
|
11
|
+
if (ctx.username) add('claude', path.join('/Library/Managed Preferences', ctx.username, 'com.anthropic.claudecode.plist'), true);
|
|
12
|
+
}
|
|
13
|
+
[path.join(ctx.roots.claude, 'settings.json'), path.join(ctx.cwd, '.claude/settings.json'), path.join(ctx.cwd, '.claude/settings.local.json')].forEach(function (file) { add('claude', file); });
|
|
14
|
+
[path.join(ctx.roots.codex, 'requirements.toml'), '/etc/codex/requirements.toml', '/etc/codex/managed_config.toml', path.join(ctx.roots.codex, 'managed_config.toml')].forEach(function (file) { add('codex', file, true); });
|
|
15
|
+
var openCodeManaged = process.platform === 'darwin' ? '/Library/Application Support/opencode' : process.platform === 'win32' ? path.join(process.env.ProgramData || 'C:\\ProgramData', 'opencode') : '/etc/opencode';
|
|
16
|
+
['opencode.json', 'opencode.jsonc'].forEach(function (file) { add('opencode', path.join(openCodeManaged, file), true); });
|
|
17
|
+
if (process.platform === 'darwin') {
|
|
18
|
+
add('opencode', '/Library/Managed Preferences/ai.opencode.managed.plist', true);
|
|
19
|
+
if (ctx.username) add('opencode', path.join('/Library/Managed Preferences', ctx.username, 'ai.opencode.managed.plist'), true);
|
|
20
|
+
}
|
|
21
|
+
['settings.json', 'permissions-config.json'].forEach(function (file) { add('copilot', path.join(ctx.roots.copilot, file)); });
|
|
22
|
+
['settings.json', 'settings.local.json'].forEach(function (file) { add('copilot', path.join(ctx.cwd, '.github/copilot', file)); });
|
|
23
|
+
var copilotManaged = process.platform === 'darwin' ? '/Library/Application Support/GitHubCopilot/managed-settings.json' : process.platform === 'win32' ? path.join(process.env.ProgramFiles || 'C:\\Program Files', 'GitHubCopilot/managed-settings.json') : '/etc/github-copilot/managed-settings.json';
|
|
24
|
+
add('copilot', copilotManaged, true);
|
|
25
|
+
if (process.platform === 'darwin') {
|
|
26
|
+
add('copilot', '/Library/Managed Preferences/com.github.copilot.plist', true);
|
|
27
|
+
if (ctx.username) add('copilot', path.join('/Library/Managed Preferences', ctx.username, 'com.github.copilot.plist'), true);
|
|
28
|
+
}
|
|
29
|
+
add('kimi', path.join(ctx.roots.kimi, 'config.toml'));
|
|
30
|
+
var qwenSystem = process.platform === 'darwin' ? '/Library/Application Support/QwenCode' : process.platform === 'win32' ? 'C:\\ProgramData\\qwen-code' : '/etc/qwen-code';
|
|
31
|
+
add('qwen', ctx.policyPaths.qwenSettings || path.join(qwenSystem, 'settings.json'), true);
|
|
32
|
+
add('qwen', ctx.policyPaths.qwenDefaults || path.join(qwenSystem, 'system-defaults.json'), true);
|
|
33
|
+
add('grok', '/etc/grok/requirements.toml', true);
|
|
34
|
+
add('grok', '/etc/grok/managed_config.toml', true);
|
|
35
|
+
add('grok', path.join(ctx.roots.grok, 'requirements.toml'), true);
|
|
36
|
+
add('grok', path.join(ctx.roots.grok, 'managed_config.toml'), true);
|
|
37
|
+
files.forEach(function (entry) {
|
|
38
|
+
try {
|
|
39
|
+
var value = ctx.load(entry.file);
|
|
40
|
+
if (entry.exclusive || path.basename(entry.file) === 'permissions-config.json' && Object.keys(value).length || Object.keys(value).some(function (key) { return SECURITY.test(key); })) blocked[entry.vendor] = 'Local vendor security or managed policy is present. Clay cannot resolve its effective restrictions. Review that policy and configure an explicitly authorized Clay connection; vendor import is blocked.';
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (error.code !== 'ENOENT' && !/ENOENT|no such file/i.test(error.message)) blocked[entry.vendor] = 'Could not resolve a local vendor policy source. Check permissions and format before importing.';
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
return blocked;
|
|
46
|
+
}
|
|
47
|
+
module.exports = { inspect: inspect, securityKey: function (key) { return SECURITY.test(key); } };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Restrictive policies are intersected; an empty allowlist means no tools.
|
|
2
|
+
var ALLOW = ['enabled_tools', 'enabledTools', 'includeTools'];
|
|
3
|
+
var DENY = ['disabled_tools', 'disabledTools', 'excludeTools'];
|
|
4
|
+
function list(value, key) {
|
|
5
|
+
if (!Array.isArray(value) || value.some(function (item) { return typeof item !== 'string' || !item || item.indexOf('\0') >= 0; })) throw new Error('Invalid tool policy: ' + key + '. Review the vendor configuration before importing.');
|
|
6
|
+
return Array.from(new Set(value));
|
|
7
|
+
}
|
|
8
|
+
function normalize(vendor, value) {
|
|
9
|
+
var allow = null, deny = [], keys = ALLOW.slice();
|
|
10
|
+
if (vendor === 'copilot') keys.push('tools');
|
|
11
|
+
keys.forEach(function (key) {
|
|
12
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) return;
|
|
13
|
+
var next = list(value[key], key);
|
|
14
|
+
if (vendor === 'copilot' && key === 'tools' && next.indexOf('*') >= 0) next = null;
|
|
15
|
+
if (next !== null) allow = allow === null ? next : allow.filter(function (name) { return next.indexOf(name) >= 0; });
|
|
16
|
+
});
|
|
17
|
+
DENY.forEach(function (key) { if (Object.prototype.hasOwnProperty.call(value, key)) deny = deny.concat(list(value[key], key)); });
|
|
18
|
+
var unsupported = Object.keys(value).filter(function (key) {
|
|
19
|
+
if (ALLOW.concat(DENY, vendor === 'copilot' ? ['tools'] : [], ['autoApprove', 'trust', 'oauth', 'oauthScopes']).indexOf(key) >= 0) return false;
|
|
20
|
+
return /tool|permission|policy|security|authProvider|allowed|denied|restrict|approval/i.test(key) && ['tool_timeout_sec'].indexOf(key) < 0;
|
|
21
|
+
});
|
|
22
|
+
if (unsupported.length) throw new Error('Unsupported restriction or security configuration: ' + unsupported.join(', ') + '. Configure and review a direct Clay connection instead.');
|
|
23
|
+
if (value.autoApprove !== undefined && (!Array.isArray(value.autoApprove) || value.autoApprove.some(function (item) { return typeof item !== 'string'; }))) throw new Error('Invalid autoApprove policy.');
|
|
24
|
+
if (value.trust !== undefined && typeof value.trust !== 'boolean') throw new Error('Invalid trust policy.');
|
|
25
|
+
return { enabledTools: allow, disabledTools: Array.from(new Set(deny)) };
|
|
26
|
+
}
|
|
27
|
+
function matches(pattern, name) {
|
|
28
|
+
return new RegExp('^' + pattern.split('').map(function (letter) { return letter === '*' ? '.*' : letter === '?' ? '.' : letter.replace(/[\\^$+.[\]{}()|]/g, '\\$&'); }).join('') + '$').test(name);
|
|
29
|
+
}
|
|
30
|
+
function permits(row, name) {
|
|
31
|
+
if ((row.disabledTools || []).indexOf(name) >= 0 || row.enabledTools && row.enabledTools.indexOf(name) < 0) return false;
|
|
32
|
+
return !(row.toolPatterns || []).some(function (pattern) { return matches(pattern, row.toolPrefix ? row.toolPrefix + name : name); });
|
|
33
|
+
}
|
|
34
|
+
module.exports = { normalize: normalize, permits: permits, matches: matches };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Bounded source identities shared by checklist validation and reconciliation.
|
|
2
|
+
function items(text) {
|
|
3
|
+
var lines = (text || '').split('\n').slice(1);
|
|
4
|
+
var occurrences = Object.create(null);
|
|
5
|
+
var out = [];
|
|
6
|
+
var fence = null;
|
|
7
|
+
for (var i = 0; i < lines.length; i++) {
|
|
8
|
+
var line = lines[i];
|
|
9
|
+
var match = line.match(/^\s*(\x60{3,}|~{3,})/);
|
|
10
|
+
if (match) { fence = fence === match[1].charAt(0) ? null : (fence || match[1].charAt(0)); continue; }
|
|
11
|
+
if (fence || !line.trim() || /^\s*#{1,6}\s/.test(line)) continue;
|
|
12
|
+
var occurrence = occurrences[line] || 0;
|
|
13
|
+
occurrences[line] = occurrence + 1;
|
|
14
|
+
var key = line + '\u0000' + occurrence;
|
|
15
|
+
if (i <= 1000 && key.length <= 2000 && encodeURIComponent(key).length <= 2000) out.push({ index: i, key: key });
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function validState(value) {
|
|
21
|
+
if (!Array.isArray(value) && (!value || typeof value !== 'object')) return false;
|
|
22
|
+
var keys = Object.keys(value);
|
|
23
|
+
if (keys.length > 1000) return false;
|
|
24
|
+
for (var i = 0; i < keys.length; i++) {
|
|
25
|
+
if (keys[i].length > 2000 || typeof value[keys[i]] !== 'boolean') return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeState(value, text) {
|
|
31
|
+
var state = {};
|
|
32
|
+
items(text).forEach(function (item) {
|
|
33
|
+
var checked = value && value[Array.isArray(value) ? item.index : item.key];
|
|
34
|
+
if (typeof checked === 'boolean' && Object.keys(state).length < 1000) state[item.key] = checked;
|
|
35
|
+
});
|
|
36
|
+
return state;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function validateChange(note, changes) {
|
|
40
|
+
if (changes.checklistIndex === undefined) return null;
|
|
41
|
+
if (!Number.isInteger(changes.checklistIndex) || changes.checklistIndex < 0 || changes.checklistIndex > 1000) return null;
|
|
42
|
+
if (typeof changes.checklistChecked !== 'boolean' || typeof changes.checklistSource !== 'string' || typeof changes.checklistKey !== 'string') return null;
|
|
43
|
+
if (changes.checklistSource !== note.text || changes.checklistKey.length > 2000 || (changes.text !== undefined && changes.text !== note.text)) return null;
|
|
44
|
+
var item = items(note.text).find(function (item) { return item.index === changes.checklistIndex; });
|
|
45
|
+
var state = normalizeState(note.checklistState, note.text);
|
|
46
|
+
if (item && state[item.key] === undefined && Object.keys(state).length >= 1000) return null;
|
|
47
|
+
try { return item && item.key === decodeURIComponent(changes.checklistKey) ? item.key : null; } catch (error) { return null; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { validState: validState, normalizeState: normalizeState, validateChange: validateChange };
|
package/lib/notes.js
CHANGED
|
@@ -4,6 +4,7 @@ var crypto = require("crypto");
|
|
|
4
4
|
var config = require("./config");
|
|
5
5
|
var utils = require("./utils");
|
|
6
6
|
var lifecycle = require("./notes-lifecycle");
|
|
7
|
+
var checklist = require("./notes-checklist");
|
|
7
8
|
|
|
8
9
|
function createNotesManager(opts) {
|
|
9
10
|
var cwd = opts.cwd;
|
|
@@ -61,6 +62,8 @@ function createNotesManager(opts) {
|
|
|
61
62
|
h: typeof data.h === "number" ? data.h : 180,
|
|
62
63
|
color: data.color || "purple",
|
|
63
64
|
opacity: typeof data.opacity === "number" ? data.opacity : 0.64,
|
|
65
|
+
checklistMode: data.checklistMode === true,
|
|
66
|
+
checklistState: checklist.validState(data.checklistState) ? checklist.normalizeState(data.checklistState, data.text || "") : [],
|
|
64
67
|
minimized: false,
|
|
65
68
|
// A new note is always an open attention item.
|
|
66
69
|
state: lifecycle.STATE_OPEN,
|
|
@@ -84,13 +87,33 @@ function createNotesManager(opts) {
|
|
|
84
87
|
function update(id, changes) {
|
|
85
88
|
for (var i = 0; i < notes.length; i++) {
|
|
86
89
|
if (notes[i].id === id) {
|
|
87
|
-
|
|
90
|
+
if (changes.checklistConvert === true) {
|
|
91
|
+
if (changes.checklistSource !== notes[i].text || changes.checklistVersion !== notes[i].updatedAt || notes[i].checklistMode === true) return null;
|
|
92
|
+
changes = Object.assign({}, changes, { text: "", checklistState: {}, checklistMode: true });
|
|
93
|
+
}
|
|
94
|
+
if (changes.checklistEdit === true && (notes[i].checklistMode !== true || changes.checklistSource !== notes[i].text || changes.checklistVersion !== notes[i].updatedAt || typeof changes.text !== "string")) return null;
|
|
95
|
+
if (changes.checklistMode !== undefined && typeof changes.checklistMode !== "boolean") return null;
|
|
96
|
+
if (changes.checklistState !== undefined && !checklist.validState(changes.checklistState)) return null;
|
|
97
|
+
var checklistKey = checklist.validateChange(notes[i], changes);
|
|
98
|
+
if (changes.checklistIndex !== undefined && !checklistKey) return null;
|
|
99
|
+
var previous = {};
|
|
100
|
+
var snapshotKeys = ["text", "x", "y", "w", "h", "color", "minimized", "zIndex", "opacity", "checklistMode", "checklistState", "hidden", "state", "closedAt", "closedBy", "updatedAt"];
|
|
101
|
+
for (var s = 0; s < snapshotKeys.length; s++) previous[snapshotKeys[s]] = notes[i][snapshotKeys[s]];
|
|
102
|
+
var allowed = ["text", "x", "y", "w", "h", "color", "minimized", "zIndex", "opacity", "checklistMode", "checklistState"];
|
|
88
103
|
for (var j = 0; j < allowed.length; j++) {
|
|
89
104
|
var key = allowed[j];
|
|
90
105
|
if (changes[key] !== undefined) {
|
|
91
106
|
notes[i][key] = changes[key];
|
|
92
107
|
}
|
|
93
108
|
}
|
|
109
|
+
if (checklistKey) {
|
|
110
|
+
var state = checklist.normalizeState(notes[i].checklistState, notes[i].text);
|
|
111
|
+
state[checklistKey] = changes.checklistChecked === true;
|
|
112
|
+
notes[i].checklistState = state;
|
|
113
|
+
}
|
|
114
|
+
if (changes.text !== undefined || changes.checklistState !== undefined || checklistKey) {
|
|
115
|
+
notes[i].checklistState = checklist.normalizeState(notes[i].checklistState, notes[i].text);
|
|
116
|
+
}
|
|
94
117
|
// `hidden` is the legacy spelling of the lifecycle. Route it through the
|
|
95
118
|
// transitions so state, hidden, and the close provenance can never
|
|
96
119
|
// disagree, and so an old client cannot half-apply a close.
|
|
@@ -98,8 +121,11 @@ function createNotesManager(opts) {
|
|
|
98
121
|
if (changes.hidden === true) lifecycle.applyClose(notes[i], changes.actor || null, Date.now());
|
|
99
122
|
else lifecycle.applyReopen(notes[i]);
|
|
100
123
|
}
|
|
101
|
-
notes[i].updatedAt = Date.now();
|
|
102
|
-
saveToDisk()
|
|
124
|
+
notes[i].updatedAt = Math.max(Date.now(), (previous.updatedAt || 0) + 1);
|
|
125
|
+
if (!saveToDisk()) {
|
|
126
|
+
for (var p = 0; p < snapshotKeys.length; p++) notes[i][snapshotKeys[p]] = previous[snapshotKeys[p]];
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
103
129
|
return notes[i];
|
|
104
130
|
}
|
|
105
131
|
}
|
|
@@ -120,6 +146,25 @@ function createNotesManager(opts) {
|
|
|
120
146
|
return null;
|
|
121
147
|
}
|
|
122
148
|
|
|
149
|
+
// Human close includes the visible draft and an exact canonical snapshot.
|
|
150
|
+
// Recheck it here, then persist draft + lifecycle/removal in one transaction.
|
|
151
|
+
function closeHuman(id, changes, actor) {
|
|
152
|
+
for (var i = 0; i < notes.length; i++) {
|
|
153
|
+
var note = notes[i];
|
|
154
|
+
if (note.id !== id) continue;
|
|
155
|
+
if (note.text !== changes.expectedText || note.updatedAt !== changes.expectedVersion || typeof changes.draftText !== "string") return null;
|
|
156
|
+
if (!changes.draftText.trim()) {
|
|
157
|
+
notes.splice(i, 1);
|
|
158
|
+
if (saveToDisk()) return { removed: note };
|
|
159
|
+
notes.splice(i, 0, note);
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
var updated = update(id, { text: changes.draftText, hidden: true, actor: actor });
|
|
163
|
+
return updated ? { note: updated } : null;
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
123
168
|
// Reopen a closed note. Idempotent.
|
|
124
169
|
function reopen(id) {
|
|
125
170
|
for (var i = 0; i < notes.length; i++) {
|
|
@@ -211,6 +256,7 @@ function createNotesManager(opts) {
|
|
|
211
256
|
create: create,
|
|
212
257
|
update: update,
|
|
213
258
|
close: close,
|
|
259
|
+
closeHuman: closeHuman,
|
|
214
260
|
reopen: reopen,
|
|
215
261
|
remove: remove,
|
|
216
262
|
removeClosed: removeClosed,
|
package/lib/os-users.js
CHANGED
|
@@ -153,6 +153,19 @@ function fsAsUser(op, args, osUserInfo) {
|
|
|
153
153
|
});
|
|
154
154
|
var binOutput = execFileSync(binSpawn.command, binSpawn.args, binSpawn.options);
|
|
155
155
|
return { buffer: Buffer.from(binOutput.trim(), "base64") };
|
|
156
|
+
} else if (op === 'write_exact') {
|
|
157
|
+
script = [
|
|
158
|
+
"var fs = require('fs'), crypto = require('crypto'), path = require('path');",
|
|
159
|
+
"var f = " + JSON.stringify(args.file) + "; var expected = " + JSON.stringify(args.expected) + ";",
|
|
160
|
+
"var root = " + JSON.stringify(args.root) + "; var relative = path.relative(root, f);",
|
|
161
|
+
"function checkPath() { if (fs.realpathSync(root) !== root || path.isAbsolute(relative) || relative.split(path.sep).indexOf('..') >= 0) throw new Error('The canonical file boundary changed.'); var at = root; relative.split(path.sep).forEach(function (part) { at = path.join(at, part); if (fs.lstatSync(at).isSymbolicLink()) throw new Error('File parent symlinks are unavailable.'); }); }",
|
|
162
|
+
"checkPath();",
|
|
163
|
+
"var fd = fs.openSync(f, fs.constants.O_RDWR | fs.constants.O_NOFOLLOW);",
|
|
164
|
+
"try { var s = fs.fstatSync(fd);",
|
|
165
|
+
"if (!s.isFile() || s.nlink !== 1 || s.dev !== expected.dev || s.ino !== expected.ino || s.mode !== expected.mode || s.uid !== expected.uid || s.gid !== expected.gid || s.size !== expected.size || s.mtimeMs !== expected.mtime || crypto.createHash('sha256').update(fs.readFileSync(fd, 'utf8')).digest('hex') !== expected.hash) throw new Error('The exact file revision changed.');",
|
|
166
|
+
"checkPath(); fs.ftruncateSync(fd, 0); var bytes = Buffer.from(" + JSON.stringify(args.content) + ", 'utf8'), offset = 0; while (offset < bytes.length) { var written = fs.writeSync(fd, bytes, offset, bytes.length - offset, offset); if (!written) throw new Error('File write made no progress.'); offset += written; } fs.fsyncSync(fd); } finally { fs.closeSync(fd); }",
|
|
167
|
+
"process.stdout.write(JSON.stringify({ ok: true }));",
|
|
168
|
+
].join(' ');
|
|
156
169
|
} else if (op === "write") {
|
|
157
170
|
script = [
|
|
158
171
|
"var fs = require('fs');",
|
|
@@ -267,18 +280,18 @@ function isHomeDirectory(dirPath) {
|
|
|
267
280
|
* Uses setfacl to add recursive + default ACL entries.
|
|
268
281
|
* Skips home directories to avoid slow recursive ACL on large trees.
|
|
269
282
|
*/
|
|
270
|
-
function grantProjectAccess(projectPath, linuxUser) {
|
|
283
|
+
function grantProjectAccess(projectPath, linuxUser, options) {
|
|
271
284
|
if (isHomeDirectory(projectPath)) {
|
|
272
285
|
console.log("[os-users] Skipping ACL for home directory: " + projectPath);
|
|
273
|
-
return;
|
|
286
|
+
return true;
|
|
274
287
|
}
|
|
275
288
|
if (!isSafeLinuxUsername(linuxUser)) {
|
|
276
289
|
console.error("[os-users] Invalid Linux username for ACL grant: " + linuxUser);
|
|
277
|
-
return;
|
|
290
|
+
return false;
|
|
278
291
|
}
|
|
279
292
|
var cacheKey = aclCacheKey(projectPath, linuxUser);
|
|
280
|
-
if (_aclGrantCache[cacheKey]) {
|
|
281
|
-
return;
|
|
293
|
+
if (_aclGrantCache[cacheKey] && !(options && options.force)) {
|
|
294
|
+
return true;
|
|
282
295
|
}
|
|
283
296
|
try {
|
|
284
297
|
// Recursive ACL for existing files
|
|
@@ -295,6 +308,7 @@ function grantProjectAccess(projectPath, linuxUser) {
|
|
|
295
308
|
});
|
|
296
309
|
_aclGrantCache[cacheKey] = true;
|
|
297
310
|
console.log("[os-users] Granted ACL access for " + linuxUser + " on " + projectPath);
|
|
311
|
+
return true;
|
|
298
312
|
} catch (e) {
|
|
299
313
|
delete _aclGrantCache[cacheKey];
|
|
300
314
|
var errMsg = (e.stderr || e.message || "").toString();
|
|
@@ -305,6 +319,7 @@ function grantProjectAccess(projectPath, linuxUser) {
|
|
|
305
319
|
} else {
|
|
306
320
|
console.error("[os-users] Failed to grant ACL access for " + linuxUser + " on " + projectPath + ": " + errMsg);
|
|
307
321
|
}
|
|
322
|
+
return false;
|
|
308
323
|
}
|
|
309
324
|
}
|
|
310
325
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// This projection is an allowlist. Never add runtime/session/path objects here.
|
|
2
|
+
function projectMetadata(status, access, users) {
|
|
3
|
+
if (!status || status.isMate || !access || access.error) return null;
|
|
4
|
+
var owner = access.ownerId && users.findUserById(access.ownerId);
|
|
5
|
+
return {
|
|
6
|
+
slug: status.slug,
|
|
7
|
+
project: status.title || status.project || status.slug,
|
|
8
|
+
title: status.title || status.project || status.slug,
|
|
9
|
+
projectOwnerId: owner ? owner.id : null,
|
|
10
|
+
ownerName: owner ? (owner.displayName || owner.username) : "Ownership needs recovery",
|
|
11
|
+
visibility: access.visibility,
|
|
12
|
+
contentAccess: false,
|
|
13
|
+
administrativeAccess: true,
|
|
14
|
+
ownershipRecovery: access.ownershipRecovery === "required" || !owner,
|
|
15
|
+
runtimeHealth: "Registered",
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function metadataFor(user, status, access, users) {
|
|
20
|
+
if (!user || !users.findUserById(user.id) || user.role !== "admin" || !status || status.isMate || /^mate-/.test(status.slug)) return null;
|
|
21
|
+
return projectMetadata(status, access, users);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function escape(value) {
|
|
25
|
+
return String(value || "").replace(/[&<>"']/g, function (character) {
|
|
26
|
+
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[character];
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function page(metadata, projects) {
|
|
31
|
+
var privacyText = metadata.ownershipRecovery
|
|
32
|
+
? "Content remains restricted because no verified owner is available. Ask the server operator to verify the account’s OS UID and real home/project mapping, then run the documented ownership repair."
|
|
33
|
+
: "Conversations, files and terminals are private. Ask the owner to share project access.";
|
|
34
|
+
var accessText = metadata.ownershipRecovery
|
|
35
|
+
? "Ownership needs recovery. The server operator must independently verify or repair the authoritative account-to-OS mapping before ownership repair can succeed. Ambiguous records stay restricted; administrator status cannot claim this project. See the Project ownership and administrator access guide."
|
|
36
|
+
: "Only the project owner can change ownership, visibility and sharing. Administrator status does not grant access to private content.";
|
|
37
|
+
var strip = projects.filter(function (project) { return !project.isMate; }).map(function (project) {
|
|
38
|
+
return '<a class="icon-strip-item' + (project.slug === metadata.slug ? ' active' : '') + '" href="/p/' + encodeURIComponent(project.slug) + '/" title="' + escape(project.title || project.project) + '"><span class="icon-strip-name">' + escape((project.title || project.project || project.slug).substring(0, 2)) + '</span></a>';
|
|
39
|
+
}).join("");
|
|
40
|
+
return '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>' + escape(metadata.title) + ' · Clay</title>' +
|
|
41
|
+
'<script src="/appearance-bootstrap.js"></script><link rel="stylesheet" href="/style.css"><link rel="stylesheet" href="/css/project-admin-overview.css"></head><body>' +
|
|
42
|
+
'<div id="layout"><header id="top-bar"><a href="/" class="top-bar-brand-link" aria-label="Clay home"><span class="top-bar-brand-wordmark"></span></a></header>' +
|
|
43
|
+
'<div id="layout-body"><nav id="icon-strip" aria-label="Projects"><div class="icon-strip-projects">' + strip + '</div></nav>' +
|
|
44
|
+
'<main id="main-area" class="project-admin-overview"><div class="project-admin-canvas"><h1>' + escape(metadata.title) + '</h1>' +
|
|
45
|
+
'<div role="tablist" aria-label="Project information"><button type="button" role="tab" aria-selected="true" id="overview-tab" aria-controls="overview-panel">Overview</button><button type="button" role="tab" aria-selected="false" id="access-tab" aria-controls="access-panel">Access</button></div>' +
|
|
46
|
+
'<section id="overview-panel" role="tabpanel" aria-labelledby="overview-tab"><dl><dt>Owner</dt><dd>' + escape(metadata.ownerName) + '</dd><dt>Visibility</dt><dd>' + escape(metadata.visibility) + '</dd><dt>Status</dt><dd>' + (metadata.ownershipRecovery ? 'Ownership needs recovery' : escape(metadata.runtimeHealth)) + '</dd><dt>Your access</dt><dd>Administrative metadata only</dd></dl><p>' + escape(privacyText) + '</p><p class="project-admin-secondary">Resource usage is unavailable for this project.</p></section>' +
|
|
47
|
+
'<section id="access-panel" role="tabpanel" aria-labelledby="access-tab" hidden><h2>Administrative metadata only</h2><p>' + escape(accessText) + '</p></section></div></main></div></div>' +
|
|
48
|
+
'<script type="module" src="/modules/project-admin-overview.js"></script></body></html>';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { metadataFor: metadataFor, page: page };
|
package/lib/project-ask-user.js
CHANGED
|
@@ -25,8 +25,9 @@ function attachAskUser(ctx) {
|
|
|
25
25
|
respond.cancel("Autonomous mode. Make your own decision.");
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
|
-
var questions = planningQuestions(boundSession, request.questions);
|
|
28
|
+
var questions = ["mcp-import", "product-control"].indexOf(request.source) >= 0 ? request.questions : planningQuestions(boundSession, request.questions);
|
|
29
29
|
var input = { questions: questions };
|
|
30
|
+
if (request.source === 'mcp-import') input.source = 'mcp-import';
|
|
30
31
|
if (!boundSession.pendingAskUser) boundSession.pendingAskUser = {};
|
|
31
32
|
var pending = {
|
|
32
33
|
input: input,
|
|
@@ -38,15 +39,18 @@ function attachAskUser(ctx) {
|
|
|
38
39
|
};
|
|
39
40
|
boundSession.pendingAskUser[request.id] = pending;
|
|
40
41
|
if (respond && typeof respond.onSettle === "function") {
|
|
41
|
-
respond.onSettle(function () {
|
|
42
|
+
respond.onSettle(function (result) {
|
|
43
|
+
if (request.source === 'product-control' && result.status !== 'submitted') ctx.record(boundSession, { type: 'product_control_question_expired', toolId: request.id, error: 'This exact change review expired or was cancelled. No change was authorized.' });
|
|
44
|
+
if (request.source === 'mate-schedule' && result.status !== 'submitted') ctx.record(boundSession, { type: 'mate_schedule_question_expired', toolId: request.id, error: 'This schedule review expired. Request a new review.' });
|
|
45
|
+
if (request.source === 'mcp-import' && result.status !== 'submitted') ctx.record(boundSession, { type: 'mcp_import_consent_expired', toolId: request.id, source: 'mcp-import', error: 'Import consent expired or was cancelled. No connection was imported.' });
|
|
42
46
|
if (boundSession.pendingAskUser && boundSession.pendingAskUser[request.id] === pending) delete boundSession.pendingAskUser[request.id];
|
|
43
47
|
});
|
|
44
48
|
}
|
|
45
49
|
if (!askUserIdentity.hasRecordedAskUserRequest(boundSession.history, request.id, input)) {
|
|
46
|
-
ctx.record(boundSession, { type: "tool_executing", id: request.id, name: "AskUserQuestion", input: input });
|
|
50
|
+
ctx.record(boundSession, { type: "tool_executing", id: request.id, name: "AskUserQuestion", input: input, source: request.source });
|
|
47
51
|
}
|
|
48
52
|
var router = ctx.getWorkerQuestionRouter && ctx.getWorkerQuestionRouter();
|
|
49
|
-
if (router) router.routeQuestion(boundSession, request, respond, function (answers, driverId) {
|
|
53
|
+
if (router && request.source !== "mcp-import" && request.source !== "mate-schedule" && request.source !== "product-control") router.routeQuestion(boundSession, request, respond, function (answers, driverId) {
|
|
50
54
|
var displayAnswers = {};
|
|
51
55
|
request.questions.forEach(function (question, index) { displayAnswers[index] = answers[question.id].join(", "); });
|
|
52
56
|
ctx.record(boundSession, { type: "ask_user_answered", toolId: request.id, answers: displayAnswers, answeredByDriver: driverId });
|