clay-server 5.2.0-beta.5 → 5.2.0-beta.7
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/claude-hook-installer.js +1 -267
- package/lib/claude-jsonl-watcher.js +1 -204
- package/lib/clay-control-presentation-binding.js +96 -0
- package/lib/clay-control-presentation.js +128 -0
- package/lib/clay-control-target-projection.js +26 -0
- package/lib/clay-mate-chat.js +1 -0
- package/lib/clay-mate-knowledge.js +1 -1
- package/lib/clay-product-control-tools.js +1 -1
- package/lib/clay-product-control.js +22 -12
- package/lib/clay-project-records.js +2 -1
- package/lib/clay-scheduling.js +1 -1
- package/lib/clay-screen-controls.js +13 -5
- package/lib/clay-skill-install.js +1 -33
- package/lib/clay-workspace-controls.js +2 -1
- package/lib/cli-sessions.js +1 -300
- package/lib/codex-defaults.js +1 -28
- package/lib/context-usage.js +3 -2
- package/lib/contextual-screen-snapshot.js +38 -0
- package/lib/daemon.js +5 -55
- package/lib/default-ai-runtime.js +4 -8
- package/lib/email-mcp-server.js +1 -1
- package/lib/kiro-defaults.js +1 -22
- package/lib/mate-context-service.js +138 -0
- package/lib/mate-context-tools.js +21 -0
- 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/os-user-diagnostics.js +2 -4
- package/lib/os-users.js +4 -65
- package/lib/project-connection.js +14 -10
- package/lib/project-control-git-view.js +12 -0
- package/lib/project-filesystem.js +5 -1
- package/lib/project-http.js +7 -4
- package/lib/project-mcp.js +2 -2
- package/lib/project-notifications.js +5 -4
- package/lib/project-pair-result-delivery.js +2 -2
- package/lib/project-pair-usage.js +12 -13
- package/lib/project-pending-message-queue.js +3 -1
- package/lib/project-product-control.js +3 -1
- package/lib/project-scheduled-messages.js +4 -4
- package/lib/project-scheduled-task-control.js +1 -1
- package/lib/project-session-document.js +20 -5
- package/lib/project-session-history-actions.js +5 -3
- package/lib/project-session-pair.js +1 -1
- package/lib/project-session-runtime-actions.js +2 -1
- package/lib/project-session-spawn.js +2 -3
- package/lib/project-session-vendor.js +2 -1
- package/lib/project-sessions.js +18 -26
- package/lib/project-shared-browser.js +22 -5
- package/lib/project-studio-presentation.js +58 -0
- package/lib/project-user-message.js +37 -0
- package/lib/project-vendor-login.js +6 -36
- package/lib/project-workspace-query.js +11 -1
- package/lib/project.js +24 -7
- package/lib/public/app.js +25 -0
- package/lib/public/appearance-bootstrap.js +2 -0
- package/lib/public/css/contextual-clay.css +240 -0
- package/lib/public/css/messages.css +6 -2
- package/lib/public/modules/app-favicon.js +18 -9
- 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 +5 -1
- package/lib/public/modules/app-messages.js +50 -6
- package/lib/public/modules/app-notifications.js +11 -5
- package/lib/public/modules/app-panels.js +61 -123
- package/lib/public/modules/app-projects.js +3 -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-control-activity.js +214 -0
- package/lib/public/modules/clay-control-copy.js +36 -0
- package/lib/public/modules/clay-control-detail-targets.js +44 -0
- package/lib/public/modules/clay-control-object-targets.js +52 -0
- package/lib/public/modules/clay-control-target-visibility.js +12 -0
- package/lib/public/modules/clay-control-targets.js +44 -0
- package/lib/public/modules/clay-navigation-render.js +52 -0
- package/lib/public/modules/clay-owner-navigation.js +11 -2
- package/lib/public/modules/clay-session-links.js +31 -0
- package/lib/public/modules/clay-studio-bridge.js +20 -0
- package/lib/public/modules/clay-studio-document.js +67 -0
- package/lib/public/modules/clay-studio-render.js +144 -0
- package/lib/public/modules/clay-tool-presentation.js +4 -0
- package/lib/public/modules/command-palette.js +26 -31
- package/lib/public/modules/contextual-ask-session.js +26 -0
- package/lib/public/modules/contextual-clay-geometry.js +155 -0
- package/lib/public/modules/contextual-clay-session.js +130 -0
- package/lib/public/modules/contextual-clay-workspace.js +157 -0
- package/lib/public/modules/contextual-clay.js +237 -0
- package/lib/public/modules/contextual-composer.js +91 -0
- package/lib/public/modules/contextual-mate-context.js +115 -0
- package/lib/public/modules/contextual-mate-hint.js +40 -0
- package/lib/public/modules/contextual-mate-identity.js +23 -0
- package/lib/public/modules/contextual-mate-sessions.js +134 -0
- package/lib/public/modules/contextual-search.js +137 -0
- package/lib/public/modules/contextual-session-citations.js +58 -0
- package/lib/public/modules/filebrowser.js +20 -1
- package/lib/public/modules/generation-stop.js +8 -5
- package/lib/public/modules/git-panel.js +3 -1
- package/lib/public/modules/icon-strip-tooltip.js +2 -1
- package/lib/public/modules/input.js +35 -1
- package/lib/public/modules/issues-render.js +3 -2
- package/lib/public/modules/knowledge-db-workbench.js +4 -4
- package/lib/public/modules/markdown.js +2 -34
- package/lib/public/modules/mate-knowledge-workbench.js +1 -1
- package/lib/public/modules/mcp-connection-view.js +1 -1
- package/lib/public/modules/message-delivery.js +10 -0
- package/lib/public/modules/pane-bridge.js +2 -2
- package/lib/public/modules/pending-message-queue.js +1 -0
- package/lib/public/modules/permission-control.js +5 -4
- package/lib/public/modules/product-review.js +26 -0
- package/lib/public/modules/project-chat-identity.js +13 -4
- package/lib/public/modules/project-logs-render.js +3 -0
- package/lib/public/modules/project-mate-navigation.js +58 -21
- package/lib/public/modules/scheduled-tasks.js +2 -0
- package/lib/public/modules/scheduler.js +2 -0
- package/lib/public/modules/search-clay-chat.js +5 -454
- package/lib/public/modules/session-create-form.js +10 -10
- package/lib/public/modules/settings-defaults.js +27 -28
- package/lib/public/modules/shared-browser.js +2 -0
- package/lib/public/modules/sidebar-mobile.js +4 -3
- package/lib/public/modules/sidebar-projects.js +1 -0
- package/lib/public/modules/sidebar-sessions.js +1 -0
- package/lib/public/modules/split-view.js +7 -16
- package/lib/public/modules/terminal.js +9 -0
- package/lib/public/modules/tools.js +6 -2
- package/lib/public/modules/tui-grab.js +1 -2
- package/lib/public/modules/vendor-login-browser.js +5 -3
- package/lib/public/modules/vendor-login.js +2 -2
- package/lib/public/modules/vendor-priority.js +15 -1
- package/lib/public/modules/wireframe-workbench.js +5 -2
- package/lib/public/muse-avatar.svg +4 -0
- package/lib/public/style.css +2 -0
- package/lib/safe-bash-commands.js +3 -45
- package/lib/sdk-bridge.js +131 -395
- package/lib/sdk-foreground-admission.js +16 -0
- package/lib/sdk-mcp-descriptors.js +2 -37
- package/lib/sdk-message-processor.js +50 -17
- package/lib/sdk-quota-snapshot.js +1 -1
- package/lib/sdk-response-recovery.js +1 -155
- package/lib/sdk-worker.js +2 -677
- package/lib/server-contextual-clay-settings.js +40 -0
- package/lib/server-contextual-mate-sessions.js +70 -0
- package/lib/server-home-chat.js +7 -1
- package/lib/server-home-clay-entry.js +23 -4
- package/lib/server-home-clay-session-links.js +12 -0
- package/lib/server-owner-ui.js +49 -8
- package/lib/server-settings.js +2 -0
- package/lib/server.js +18 -1
- package/lib/session-fork-history.js +21 -0
- 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/sessions.js +24 -89
- package/lib/terminal-manager.js +18 -11
- package/lib/tool-llm.js +1 -1
- package/lib/tui-transcript-index.js +1 -132
- package/lib/users-contextual-clay-preferences.js +31 -0
- package/lib/users.js +3 -0
- package/lib/vendor-login-browser.js +1 -11
- package/lib/workspace-assignment-service.js +25 -0
- package/lib/workspace-query-mcp-server.js +1 -1
- package/lib/ws-request.js +1 -0
- package/lib/ws-schema.js +10 -0
- 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 +7 -42
- package/lib/yoke/adapters/codex-query-contract.js +24 -0
- package/lib/yoke/adapters/codex.js +1 -0
- package/lib/yoke/adapters/kiro.js +3 -1
- package/lib/yoke/adapters/muse-query-contract.js +7 -0
- package/lib/yoke/adapters/muse.js +122 -0
- package/lib/yoke/auth-events.js +17 -0
- package/lib/yoke/claude-hook-installer.js +271 -0
- package/lib/yoke/claude-jsonl-watcher.js +204 -0
- package/lib/yoke/claude-maintenance.js +55 -0
- package/lib/yoke/claude-permission-patterns.js +53 -0
- package/lib/yoke/claude-response-recovery.js +155 -0
- package/lib/yoke/claude-transcript-hygiene.js +34 -0
- package/lib/yoke/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/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 +31 -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,40 @@
|
|
|
1
|
+
// Authenticated personal preferences, using the shared users/config persistence.
|
|
2
|
+
function attachContextualClaySettings(ctx) {
|
|
3
|
+
function reply(res, status, data) {
|
|
4
|
+
res.writeHead(status, { 'Content-Type': 'application/json', 'Cache-Control': 'no-store' });
|
|
5
|
+
res.end(JSON.stringify(data));
|
|
6
|
+
}
|
|
7
|
+
function identity(req) {
|
|
8
|
+
if (!ctx.isRequestAuthed(req)) return null;
|
|
9
|
+
return ctx.users.isMultiUser() ? ctx.getMultiUserFromReq(req) : { id: 'default' };
|
|
10
|
+
}
|
|
11
|
+
function handleRequest(req, res, url) {
|
|
12
|
+
if (url !== '/api/user/contextual-clay-geometry') return false;
|
|
13
|
+
var user = identity(req);
|
|
14
|
+
if (!user) { reply(res, 401, { error: 'Sign in to manage popover preferences.' }); return true; }
|
|
15
|
+
if (req.method === 'GET') {
|
|
16
|
+
try { reply(res, 200, { geometry: ctx.users.getContextualClayGeometry(user.id) }); }
|
|
17
|
+
catch (error) { reply(res, 500, { error: 'Could not read popover preference.' }); }
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
if (req.method !== 'PUT') { reply(res, 405, { error: 'Method not allowed.' }); return true; }
|
|
21
|
+
if (req.headers['x-clay-settings'] !== '1' || (req.headers['sec-fetch-site'] && ['same-origin', 'none'].indexOf(req.headers['sec-fetch-site']) < 0)) {
|
|
22
|
+
reply(res, 403, { error: 'Save preferences from Clay.' }); return true;
|
|
23
|
+
}
|
|
24
|
+
var body = ''; var tooLarge = false;
|
|
25
|
+
req.setEncoding('utf8');
|
|
26
|
+
req.on('data', function (chunk) { body += chunk; if (body.length > 1024 && !tooLarge) { tooLarge = true; reply(res, 413, { error: 'Request too large.' }); } });
|
|
27
|
+
req.on('end', function () {
|
|
28
|
+
if (tooLarge) return;
|
|
29
|
+
var live = identity(req);
|
|
30
|
+
if (!live || live.id !== user.id) { reply(res, 401, { error: 'Sign in again.' }); return; }
|
|
31
|
+
try {
|
|
32
|
+
var result = ctx.users.setContextualClayGeometry(live.id, JSON.parse(body));
|
|
33
|
+
reply(res, result.error ? 400 : 200, result);
|
|
34
|
+
} catch (error) { reply(res, 400, { error: 'Could not save popover preference.' }); }
|
|
35
|
+
});
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return { handleRequest: handleRequest };
|
|
39
|
+
}
|
|
40
|
+
module.exports = { attachContextualClaySettings: attachContextualClaySettings };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// Presentation-only session operations share Home's authoritative permissions,
|
|
2
|
+
// model policy, saved-session projection and session manager. No transcript tap.
|
|
3
|
+
function attachContextualMateSessions(ctx) {
|
|
4
|
+
var receipts = new Map();
|
|
5
|
+
var lifetime = 30 * 60 * 1000;
|
|
6
|
+
function stamp(mate) { return JSON.stringify([mate.id, mate.createdBy || null, mate.createdAt || null, mate.builtinKey || null]); }
|
|
7
|
+
function live(ws, userId, msg, original) {
|
|
8
|
+
if (!ctx.isActorLive(ws, userId)) throw new Error('Your account is no longer available.');
|
|
9
|
+
var found = ctx.findMateProject(userId, msg.mateId, true);
|
|
10
|
+
if (!found || found.mate.archived || found.mate.lifecycleDeletion || found.mate.createdBy && found.mate.createdBy !== userId || stamp(found.mate) !== msg.mateStamp) throw new Error('This Mate is no longer available.');
|
|
11
|
+
var status = found.ctx.getStatus();
|
|
12
|
+
if (!status || !status.isMate || status.mateId !== found.mate.id || userId && status.projectOwnerId !== userId) throw new Error('This private Mate workspace is unavailable.');
|
|
13
|
+
found.contextualManager = found.ctx.getSessionManager();
|
|
14
|
+
found.contextualRevision = JSON.stringify([status.path, status.projectOwnerId, status.mateId, stamp(found.mate)]);
|
|
15
|
+
if (original && (found.ctx !== original.ctx || found.contextualRevision !== original.contextualRevision || found.contextualManager !== original.contextualManager)) throw new Error('This Mate conversation changed.');
|
|
16
|
+
return found;
|
|
17
|
+
}
|
|
18
|
+
function reply(ws, msg, data) { ctx.sendMessage(ws, Object.assign({ type: 'contextual_mate_sessions_result', requestId: msg.requestId, mateId: msg.mateId, mateStamp: msg.mateStamp }, data)); }
|
|
19
|
+
async function perform(ws, userId, msg) {
|
|
20
|
+
try {
|
|
21
|
+
if (typeof msg.requestId !== 'string' || !/^mate-conversations-[a-z0-9-]{36}$/.test(msg.requestId)) throw new Error('Invalid conversation request.');
|
|
22
|
+
var found = live(ws, userId, msg), manager = found.ctx.getSessionManager();
|
|
23
|
+
if (!manager) throw new Error('Conversation history is unavailable.');
|
|
24
|
+
if (msg.type === 'home_mate_sessions_list') {
|
|
25
|
+
var list = ctx.listHomeSessions(found, userId);
|
|
26
|
+
reply(ws, msg, { ok: true, sessions: list.slice(0, 500), truncated: list.length > 500 }); return;
|
|
27
|
+
}
|
|
28
|
+
var session;
|
|
29
|
+
if (msg.type === 'home_mate_session_open') {
|
|
30
|
+
session = ctx.resolveHomeSession(found, userId, msg.sessionId);
|
|
31
|
+
if (!session || typeof msg.sessionOriginId !== 'string' || msg.sessionOriginId !== session.sessionOriginId) throw new Error('This conversation is no longer available.');
|
|
32
|
+
} else {
|
|
33
|
+
var now = Date.now();
|
|
34
|
+
receipts.forEach(function (entry, key) { if (now - entry.at > lifetime) receipts.delete(key); });
|
|
35
|
+
var key = JSON.stringify([userId, msg.mateId, msg.requestId]);
|
|
36
|
+
var receipt = receipts.get(key);
|
|
37
|
+
manager.sessions.forEach(function (candidate) { if (candidate.contextualCreationRequestId === msg.requestId && ctx.ownsSession(candidate, userId)) session = candidate; });
|
|
38
|
+
if (session && session.contextualCreationMateStamp !== msg.mateStamp) throw new Error('This request belongs to a replaced Mate.');
|
|
39
|
+
if (receipt && (!session || receipt.origin !== session.sessionOriginId || receipt.stamp !== msg.mateStamp)) throw new Error('The previously created conversation is no longer available.');
|
|
40
|
+
if (!session && msg.lookupOnly) throw new Error('Creation was not confirmed. Start a new chat explicitly if you still want one.');
|
|
41
|
+
if (!session) {
|
|
42
|
+
if (receipts.size >= 1024) throw new Error('Please try starting a conversation later.');
|
|
43
|
+
var resolved = await ctx.homeModels.resolveMateModel(ws, found, userId);
|
|
44
|
+
found = live(ws, userId, msg, found);
|
|
45
|
+
// Recheck after model resolution: concurrent retries select one exact session.
|
|
46
|
+
manager.sessions.forEach(function (candidate) { if (candidate.contextualCreationRequestId === msg.requestId && ctx.ownsSession(candidate, userId)) session = candidate; });
|
|
47
|
+
receipt = receipts.get(key);
|
|
48
|
+
if (receipt && (!session || receipt.origin !== session.sessionOriginId || receipt.stamp !== msg.mateStamp)) throw new Error('The previously created conversation is no longer available.');
|
|
49
|
+
if (session && session.contextualCreationMateStamp !== msg.mateStamp) throw new Error('This request belongs to a replaced Mate.');
|
|
50
|
+
if (!session) {
|
|
51
|
+
if (receipts.size >= 1024) throw new Error('Please try starting a conversation later.');
|
|
52
|
+
session = manager.createSessionRaw({ ownerId: userId, vendor: resolved.vendor, model: resolved.model, effort: resolved.effort || null });
|
|
53
|
+
session.contextualCreationRequestId = msg.requestId;
|
|
54
|
+
session.contextualCreationMateStamp = msg.mateStamp;
|
|
55
|
+
manager.saveSessionFile(session);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (!ctx.ownsSession(session, userId)) throw new Error('This conversation is no longer available.');
|
|
59
|
+
receipts.set(key, { at: now, origin: session.sessionOriginId, stamp: msg.mateStamp });
|
|
60
|
+
}
|
|
61
|
+
live(ws, userId, msg, found);
|
|
62
|
+
reply(ws, msg, { ok: true, sessionId: session.localId, sessionOriginId: session.sessionOriginId });
|
|
63
|
+
} catch (error) { reply(ws, msg, { ok: false, error: error.message || 'Conversation unavailable.' }); }
|
|
64
|
+
}
|
|
65
|
+
return { handle: function (ws, userId, msg) {
|
|
66
|
+
if (msg.presentation !== 'contextual' || !/^home_mate_(sessions_list|session_open|new_session)$/.test(msg.type)) return false;
|
|
67
|
+
perform(ws, userId, msg); return true;
|
|
68
|
+
} };
|
|
69
|
+
}
|
|
70
|
+
module.exports = { attachContextualMateSessions: attachContextualMateSessions };
|
package/lib/server-home-chat.js
CHANGED
|
@@ -123,6 +123,7 @@ function attachHomeChat(deps) {
|
|
|
123
123
|
model: sessionString(session.model, 240),
|
|
124
124
|
createdAt: createdAt,
|
|
125
125
|
lastActivity: lastActivity,
|
|
126
|
+
sessionOriginId: session.sessionOriginId || null,
|
|
126
127
|
isProcessing: !!session.isProcessing,
|
|
127
128
|
sessionRole: hierarchy[session.localId].role,
|
|
128
129
|
parentSessionId: hierarchy[session.localId].parentSessionId == null ? null : sessionReference(sessionManager.sessions.get(hierarchy[session.localId].parentSessionId)),
|
|
@@ -228,6 +229,7 @@ function attachHomeChat(deps) {
|
|
|
228
229
|
var homeDebates = attachHomeDebates({ mates: mates, findMateProject: findMateProject, ownsSession: ownsSession, sessionReference: sessionReference, sendMessage: sendMessage });
|
|
229
230
|
var homeClaySessionLinks = attachHomeClaySessionLinks({
|
|
230
231
|
projects: projects,
|
|
232
|
+
findMateProject: findMateProject, ownsSession: ownsSession, isActorLive: isActorLive,
|
|
231
233
|
workspaceQueryService: deps.workspaceQueryService,
|
|
232
234
|
projectLogsService: deps.projectLogsService,
|
|
233
235
|
sendMessage: sendMessage,
|
|
@@ -300,7 +302,9 @@ function attachHomeChat(deps) {
|
|
|
300
302
|
});
|
|
301
303
|
var homeMateCreation = attachHomeMateCreation({ findMateProject: findMateProject, resolveHomeSession: resolveHomeSession, sessionReference: sessionReference, setupTap: setupTap, sendHistory: sendHistory, sendSessionList: sendSessionList, sendError: sendError, sendModelError: sendModelError, homeModels: homeModels, isActorLive: isActorLive, persistActiveSession: persistActiveSession });
|
|
302
304
|
var homeMcpConsent = require('./server-home-mcp-consent').attachHomeMcpConsent({ findMateProject: findMateProject, resolveHomeSession: resolveHomeSession, messageTap: messageTap, isActorLive: isActorLive, sendError: sendError });
|
|
303
|
-
var homeClayEntry = attachHomeClayEntry({ findMateProject: findMateProject, ownsSession: ownsSession, sessionReference: sessionReference, setupSearchTap: setupSearchTap, sendHistory: sendHistory, sendSessionList: sendSessionList, sendError: sendError, sendModelError: sendModelError, homeModels: homeModels, isActorLive: isActorLive });
|
|
305
|
+
var homeClayEntry = attachHomeClayEntry({ findMateProject: findMateProject, ownsSession: ownsSession, sessionReference: sessionReference, setupSearchTap: setupSearchTap, sendHistory: sendHistory, sendSessionList: sendSessionList, sendError: sendError, sendModelError: sendModelError, sendMessage: sendMessage, homeModels: homeModels, isActorLive: isActorLive });
|
|
306
|
+
|
|
307
|
+
var contextualSessions = require('./server-contextual-mate-sessions').attachContextualMateSessions({ findMateProject: findMateProject, ownsSession: ownsSession, resolveHomeSession: resolveHomeSession, listHomeSessions: listHomeSessions, isActorLive: isActorLive, homeModels: homeModels, sendMessage: sendMessage });
|
|
304
308
|
|
|
305
309
|
async function openExactSession(ws, found, userId, selected, msg) {
|
|
306
310
|
await ensureSessionModel(ws, found, userId, selected, null);
|
|
@@ -448,6 +452,8 @@ function attachHomeChat(deps) {
|
|
|
448
452
|
return false;
|
|
449
453
|
}
|
|
450
454
|
|
|
455
|
+
if (contextualSessions.handle(ws, ws._clayUser ? ws._clayUser.id : null, msg)) return true;
|
|
456
|
+
|
|
451
457
|
if (msg.type === "home_mate_present") {
|
|
452
458
|
ws._homeChatPresented = msg.visible === true;
|
|
453
459
|
return true;
|
|
@@ -15,10 +15,12 @@ function attachHomeClayEntry(ctx) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
async function start(ws, userId, msg) {
|
|
18
|
+
var contextual = msg.presentation === 'contextual';
|
|
18
19
|
var text = typeof msg.text === "string" ? msg.text.replace(/[\u0000-\u001f\u007f]/g, " ").trim().slice(0, 12000) : "";
|
|
19
20
|
var requestId = typeof msg.requestId === "string" ? msg.requestId.slice(0, 240) : null;
|
|
21
|
+
if (ctx.isActorLive && !ctx.isActorLive(ws, userId)) { ctx.sendError(ws, null, 'Your account is no longer available.', requestId, null, 'actor_unavailable'); return; }
|
|
20
22
|
var found = ctx.findMateProject(userId, null, true);
|
|
21
|
-
if (!found || found.mate.builtinKey !== "clay") {
|
|
23
|
+
if (!found || found.mate.builtinKey !== "clay" || found.mate.archived || ctx.isActorLive && !ctx.isActorLive(ws, userId)) {
|
|
22
24
|
ctx.sendError(ws, null, "Clay is not available.", requestId, null, "mate_unavailable");
|
|
23
25
|
return;
|
|
24
26
|
}
|
|
@@ -27,26 +29,43 @@ function attachHomeClayEntry(ctx) {
|
|
|
27
29
|
return;
|
|
28
30
|
}
|
|
29
31
|
var manager = found.ctx.getSessionManager();
|
|
32
|
+
var mateStamp = JSON.stringify([found.mate.id, found.mate.createdAt || null, found.mate.createdBy || null]);
|
|
30
33
|
if (!manager || typeof manager.sendAndRecord !== "function") {
|
|
31
34
|
ctx.sendError(ws, found.mate.id, "Conversation history is unavailable.", requestId, null, "session_unavailable");
|
|
32
35
|
return;
|
|
33
36
|
}
|
|
34
37
|
var existing = findExisting(manager, userId, requestId);
|
|
35
38
|
if (existing) {
|
|
39
|
+
if (contextual) {
|
|
40
|
+
if (existing.homeClayEntryMode !== 'contextual' || existing.homeClayEntryText !== text || msg.sessionOriginId && msg.sessionOriginId !== existing.sessionOriginId) { ctx.sendError(ws, found.mate.id, 'This request belongs to a different conversation.', requestId, null, 'request_mismatch'); return; }
|
|
41
|
+
ctx.sendMessage(ws, { type: 'home_clay_contextual_session', requestId: requestId, mateId: found.mate.id, sessionId: existing.localId, sessionOriginId: existing.sessionOriginId }); return;
|
|
42
|
+
}
|
|
36
43
|
ctx.setupSearchTap(ws, found, existing.localId, requestId);
|
|
37
44
|
ctx.sendHistory(ws, found, existing, requestId);
|
|
38
45
|
ctx.sendSessionList(ws, found, userId);
|
|
39
46
|
return;
|
|
40
47
|
}
|
|
48
|
+
if (contextual && msg.sessionOriginId) { ctx.sendError(ws, found.mate.id, 'This new conversation is no longer available. Start a new Ask.', requestId, null, 'session_unavailable'); return; }
|
|
41
49
|
var session = null;
|
|
42
50
|
try {
|
|
43
51
|
var resolved = await ctx.homeModels.resolveMateModel(ws, found, userId);
|
|
44
52
|
if (ctx.isActorLive && !ctx.isActorLive(ws, userId)) throw new Error("Your account is no longer available.");
|
|
45
53
|
var liveFound = ctx.findMateProject(userId, null, true);
|
|
46
|
-
if (!liveFound || liveFound.ctx !== found.ctx || !liveFound.mate || liveFound.mate.builtinKey !== "clay") throw new Error("Clay is no longer available.");
|
|
47
|
-
|
|
54
|
+
if (!liveFound || liveFound.ctx !== found.ctx || !liveFound.mate || liveFound.ctx.getSessionManager() !== manager || liveFound.mate.builtinKey !== "clay" || JSON.stringify([liveFound.mate.id, liveFound.mate.createdAt || null, liveFound.mate.createdBy || null]) !== mateStamp || liveFound.mate.archived) throw new Error("Clay is no longer available.");
|
|
55
|
+
// Another request may have completed while model resolution was awaiting.
|
|
56
|
+
if (findExisting(manager, userId, requestId)) return start(ws, userId, msg);
|
|
57
|
+
var options = { ownerId: userId, vendor: resolved.vendor, model: resolved.model, effort: resolved.effort || null };
|
|
58
|
+
// Background creation must not switch any existing pane/native writer.
|
|
59
|
+
session = contextual ? manager.createSessionRaw(options) : manager.createSession(options, null);
|
|
48
60
|
session.homeClayEntryRequestId = requestId;
|
|
49
|
-
session.homeClayEntryMode =
|
|
61
|
+
session.homeClayEntryMode = contextual ? 'contextual' : 'search';
|
|
62
|
+
if (contextual) {
|
|
63
|
+
session.homeClayEntryText = text;
|
|
64
|
+
session.title = text.slice(0, 80);
|
|
65
|
+
manager.saveSessionFile(session);
|
|
66
|
+
ctx.sendMessage(ws, { type: 'home_clay_contextual_session', requestId: requestId, mateId: found.mate.id, sessionId: session.localId, sessionOriginId: session.sessionOriginId });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
50
69
|
manager.sendAndRecord(session, { type: "user_message", text: text });
|
|
51
70
|
session.isProcessing = true;
|
|
52
71
|
session.sentToolResults = {};
|
|
@@ -3,6 +3,18 @@
|
|
|
3
3
|
|
|
4
4
|
function attachHomeClaySessionLinks(ctx) {
|
|
5
5
|
function source(ws, msg, routedProjectSlug) {
|
|
6
|
+
if (Object.prototype.hasOwnProperty.call(msg, "contextualSource")) {
|
|
7
|
+
var hint = msg.contextualSource, userId = ws && ws._clayUser && ws._clayUser.id;
|
|
8
|
+
if (!hint || typeof hint.mateId !== "string" || !Number.isSafeInteger(hint.sessionId) || typeof hint.sessionOriginId !== "string" || typeof hint.mateStamp !== "string" || !ctx.isActorLive || !ctx.isActorLive(ws, userId)) return {};
|
|
9
|
+
var found = ctx.findMateProject && ctx.findMateProject(userId, hint.mateId, false);
|
|
10
|
+
if (!found || found.mate.archived || found.mate.lifecycleDeletion || found.mate.createdBy && found.mate.createdBy !== userId) return {};
|
|
11
|
+
var stamp = JSON.stringify([found.mate.id, found.mate.createdBy || null, found.mate.createdAt || null, found.mate.builtinKey || null]);
|
|
12
|
+
if (stamp !== hint.mateStamp || ctx.projects.get(found.slug) !== found.ctx) return {};
|
|
13
|
+
var manager = found.ctx.getSessionManager && found.ctx.getSessionManager();
|
|
14
|
+
var session = manager && manager.sessions.get(hint.sessionId);
|
|
15
|
+
if (!ctx.ownsSession(session, userId) || session.sessionOriginId !== hint.sessionOriginId) return {};
|
|
16
|
+
return { tap: { mateSlug: found.slug, mateId: found.mate.id, sessionId: hint.sessionId }, project: found.ctx, session: session };
|
|
17
|
+
}
|
|
6
18
|
var tap = msg.surface === "search" ? ws._searchClayTap : ws._homeChatTap;
|
|
7
19
|
if (!tap && routedProjectSlug) {
|
|
8
20
|
var routedProject = ctx.projects.get(routedProjectSlug);
|
package/lib/server-owner-ui.js
CHANGED
|
@@ -2,6 +2,27 @@ var crypto = require('crypto');
|
|
|
2
2
|
// Only authenticated application sockets enumerated by the server are targets.
|
|
3
3
|
function attachOwnerUi(ctx) {
|
|
4
4
|
var identities = new WeakMap(), capabilities = new WeakMap(), deliveries = new Map();
|
|
5
|
+
var activityOrder = 0;
|
|
6
|
+
function activity(owner, slug, sessionId, mateId, selected, source, validateSource, navigationTarget) {
|
|
7
|
+
var order = ++activityOrder;
|
|
8
|
+
var viewers = selected ? [check(owner, selected)] : sockets(owner).filter(function (viewer) {
|
|
9
|
+
var tap = viewer.ws._homeChatTap;
|
|
10
|
+
return viewer.slug === slug && viewer.sessionId === sessionId || tap && tap.mateSlug === slug && tap.sessionId === sessionId;
|
|
11
|
+
});
|
|
12
|
+
function send(message, receiptViewer) {
|
|
13
|
+
viewers.forEach(function (viewer) {
|
|
14
|
+
try {
|
|
15
|
+
if (receiptViewer && viewer.ws !== receiptViewer || !alive(viewer.ws, owner)) return;
|
|
16
|
+
if (message.phase !== 'clear' && validateSource) validateSource();
|
|
17
|
+
var live = inspect(owner, viewer.ref), tap = live.ws._homeChatTap;
|
|
18
|
+
if (message.phase !== 'clear' && (selected ? (live.slug !== viewer.slug || live.sessionId !== viewer.sessionId) && !(message.phase === 'rendered' && navigationTarget && live.slug === navigationTarget.projectSlug && live.sessionId === navigationTarget.sessionId) : !(live.slug === slug && live.sessionId === sessionId || tap && tap.mateSlug === slug && tap.sessionId === sessionId))) return;
|
|
19
|
+
live.ws.send(JSON.stringify(Object.assign({ type: 'clay_control_activity', ownerId: owner, mateId: mateId,
|
|
20
|
+
sourceSlug: slug, sourceSessionId: sessionId, order: order, recipientViewerRef: live.ref }, source, message)));
|
|
21
|
+
} catch (error) { /* Presentation cannot alter the authoritative effect. */ }
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return { send: send, clear: function (id, sequence) { send({ version: 1, id: id, sequence: sequence, phase: 'clear' }); } };
|
|
25
|
+
}
|
|
5
26
|
function alive(ws, owner) {
|
|
6
27
|
return ws && ws.readyState === 1 && (!ws._clayAuthCheck || ws._clayAuthCheck()) &&
|
|
7
28
|
(ctx.isMultiUser() ? !!owner && ws._clayUser && ws._clayUser.id === owner && !!ctx.findUser(owner) : !ws._clayUser);
|
|
@@ -27,7 +48,7 @@ function attachOwnerUi(ctx) {
|
|
|
27
48
|
if (live.ws !== original.ws || live.slug !== original.slug || live.sessionId !== original.sessionId || live.supported !== original.supported) throw new Error('The selected viewer context changed. Inspect again.');
|
|
28
49
|
return live;
|
|
29
50
|
}
|
|
30
|
-
function deliver(owner, original, target, revalidate) {
|
|
51
|
+
function deliver(owner, original, target, revalidate, activity) {
|
|
31
52
|
revalidate(); var viewer = check(owner, original);
|
|
32
53
|
if (!viewer.supported) return { status: 'unsupported', delivered: false, acknowledged: false, visible: 'unknown' };
|
|
33
54
|
if (deliveries.size >= 100) {
|
|
@@ -35,9 +56,15 @@ function attachOwnerUi(ctx) {
|
|
|
35
56
|
if (deliveries.size >= 100) throw new Error('UI delivery capacity is full; wait for earlier attempts to expire.');
|
|
36
57
|
}
|
|
37
58
|
var id = 'navigation:' + crypto.randomUUID();
|
|
38
|
-
var
|
|
59
|
+
var renderToken = crypto.randomUUID();
|
|
60
|
+
var record = { owner: owner, viewer: viewer, target: target, revalidate: revalidate, activity: activity, created: Date.now(), claimed: false, renderToken: renderToken, renderClosed: false,
|
|
39
61
|
result: { status: 'delivered', deliveryId: id, delivered: false, acknowledged: false, visible: 'unknown' } };
|
|
40
62
|
deliveries.set(id, record);
|
|
63
|
+
record.renderTimer = setTimeout(function () {
|
|
64
|
+
if (record.renderClosed) return;
|
|
65
|
+
record.renderClosed = true; record.result.renderStatus = 'unknown';
|
|
66
|
+
if (record.activity) record.activity.navigation('unknown');
|
|
67
|
+
}, ctx.renderTimeoutMs || 5000); record.renderTimer.unref();
|
|
41
68
|
try {
|
|
42
69
|
viewer.ws.send(JSON.stringify({ type: 'clay_navigation_offer', deliveryId: id }));
|
|
43
70
|
if (!alive(viewer.ws, owner)) throw new Error('Viewer authorization or connection ended during delivery.');
|
|
@@ -48,30 +75,44 @@ function attachOwnerUi(ctx) {
|
|
|
48
75
|
function read(owner, id) {
|
|
49
76
|
var record = deliveries.get(id);
|
|
50
77
|
if (!record || record.owner !== owner) throw new Error('No delivery exists for this owner.');
|
|
78
|
+
if (Date.now() - record.created > 300000) { deliveries.delete(id); throw new Error('The five-minute navigation receipt expired. Inspect the current view before any new reviewed request.'); }
|
|
51
79
|
record.revalidate();
|
|
52
80
|
return Object.assign({}, record.result);
|
|
53
81
|
}
|
|
54
82
|
function handleMessage(ws, msg) {
|
|
55
|
-
if (['clay_ui_ready', 'clay_navigation_claim', 'clay_navigation_ack'].indexOf(msg.type) < 0) return false;
|
|
83
|
+
if (['clay_ui_ready', 'clay_navigation_claim', 'clay_navigation_ack', 'clay_navigation_rendered'].indexOf(msg.type) < 0) return false;
|
|
56
84
|
var owner = ctx.isMultiUser() ? ws._clayUser && ws._clayUser.id : null;
|
|
57
85
|
if (!sockets(owner).some(function (item) { return item.ws === ws; })) return true;
|
|
58
|
-
if (msg.type === 'clay_ui_ready') {
|
|
86
|
+
if (msg.type === 'clay_ui_ready') {
|
|
87
|
+
if (msg.version === 1) { capabilities.set(ws, 1); ws.send(JSON.stringify({ type: 'clay_control_viewer', viewerRef: identities.get(ws) })); }
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
59
90
|
var record = deliveries.get(msg.deliveryId);
|
|
60
91
|
if (!record || record.viewer.ws !== ws || record.owner !== owner || Date.now() - record.created > 30000) return true;
|
|
61
92
|
try {
|
|
62
93
|
record.revalidate();
|
|
63
|
-
if (msg.type === '
|
|
94
|
+
if (msg.type === 'clay_navigation_rendered') {
|
|
95
|
+
if (!record.renderClosed && Date.now() > record.created + (ctx.renderTimeoutMs || 5000)) { record.renderClosed = true; clearTimeout(record.renderTimer); record.result.renderStatus = 'unknown'; if (record.activity) record.activity.navigation('unknown'); }
|
|
96
|
+
if (record.renderClosed || !record.claimed || !record.result.acknowledged || record.result.clientStatus !== 'accepted' || msg.renderToken !== record.renderToken) return true;
|
|
97
|
+
var current = inspect(owner, record.viewer.ref);
|
|
98
|
+
if (current.ws !== ws || msg.sessionOriginId !== record.target.sessionOriginId || !record.target.sessionOriginId || msg.projectSlug !== record.target.projectSlug || msg.sessionId !== record.target.sessionId || msg.surface !== 'conversation' || msg.entryIndex !== record.target.entryIndex || current.slug !== record.target.projectSlug || current.sessionId !== record.target.sessionId) throw new Error('Rendered target or authenticated viewer changed.');
|
|
99
|
+
record.renderClosed = true; clearTimeout(record.renderTimer);
|
|
100
|
+
record.result.renderStatus = msg.visible === true ? 'rendered' : 'unknown'; record.result.visible = msg.visible === true ? true : 'unknown';
|
|
101
|
+
if (record.activity) record.activity.navigation(msg.visible === true ? 'rendered' : 'unknown');
|
|
102
|
+
} else if (msg.type === 'clay_navigation_claim') {
|
|
64
103
|
if (record.claimed) return true;
|
|
65
104
|
check(owner, record.viewer); record.claimed = true;
|
|
66
|
-
ws.send(JSON.stringify({ type: 'clay_navigation_apply', deliveryId: msg.deliveryId, target: record.target }));
|
|
105
|
+
ws.send(JSON.stringify({ type: 'clay_navigation_apply', deliveryId: msg.deliveryId, target: record.target, renderToken: record.renderToken, renderDeadline: record.created + (ctx.renderTimeoutMs || 5000) }));
|
|
67
106
|
} else if (record.claimed && !record.result.acknowledged && ['accepted', 'refused', 'failed'].indexOf(msg.status) >= 0) {
|
|
68
107
|
record.result.acknowledged = true;
|
|
69
108
|
// Accepted only confirms invocation of the existing navigator, never rendering.
|
|
70
109
|
record.result.clientStatus = msg.status;
|
|
110
|
+
if (msg.status !== 'accepted') { record.renderClosed = true; clearTimeout(record.renderTimer); }
|
|
111
|
+
if (record.activity) record.activity.navigation(msg.status === 'accepted' ? 'acknowledged' : msg.status === 'refused' ? 'cancelled' : 'failed');
|
|
71
112
|
}
|
|
72
|
-
} catch (error) { record.result.status = 'failed'; record.result.error = error.message; record.result.retrySafe = false; }
|
|
113
|
+
} catch (error) { record.renderClosed = true; clearTimeout(record.renderTimer); record.result.visible = 'unknown'; record.result.status = 'failed'; record.result.error = error.message; record.result.retrySafe = false; if (record.activity) record.activity.cancel(); }
|
|
73
114
|
return true;
|
|
74
115
|
}
|
|
75
|
-
return { list: sockets, inspect: inspect, check: check, deliver: deliver, read: read, handleMessage: handleMessage };
|
|
116
|
+
return { list: sockets, inspect: inspect, check: check, deliver: deliver, read: read, handleMessage: handleMessage, activity: activity };
|
|
76
117
|
}
|
|
77
118
|
module.exports = { attachOwnerUi: attachOwnerUi };
|
package/lib/server-settings.js
CHANGED
|
@@ -12,11 +12,13 @@ function attachSettings(ctx) {
|
|
|
12
12
|
var CONFIG_DIR = ctx.CONFIG_DIR;
|
|
13
13
|
|
|
14
14
|
var profilePath = path.join(CONFIG_DIR, "profile.json");
|
|
15
|
+
var contextualClaySettings = require('./server-contextual-clay-settings').attachContextualClaySettings(ctx);
|
|
15
16
|
var appearanceSettings = require('./server-appearance-settings').attachAppearanceSettings(ctx);
|
|
16
17
|
var notificationSettings = require('./server-notification-settings').attachNotificationSettings(ctx);
|
|
17
18
|
var experimentalSettings = attachExperimentalSettings(ctx);
|
|
18
19
|
|
|
19
20
|
function handleRequest(req, res, fullUrl) {
|
|
21
|
+
if (contextualClaySettings.handleRequest(req, res, fullUrl)) return true;
|
|
20
22
|
if (appearanceSettings.handleRequest(req, res, fullUrl)) return true;
|
|
21
23
|
if (notificationSettings.handleRequest(req, res, fullUrl)) return true;
|
|
22
24
|
if (experimentalSettings.handleRequest(req, res, fullUrl)) return true;
|
package/lib/server.js
CHANGED
|
@@ -10,7 +10,7 @@ var dm = require("./dm");
|
|
|
10
10
|
var mates = require("./mates");
|
|
11
11
|
var serverAuth = require("./server-auth");
|
|
12
12
|
var serverSkills = require("./server-skills");
|
|
13
|
-
var claudeHookInstaller = require("./claude-hook-installer");
|
|
13
|
+
var claudeHookInstaller = require("./yoke/claude-hook-installer");
|
|
14
14
|
var serverDm = require("./server-dm");
|
|
15
15
|
var serverMates = require("./server-mates");
|
|
16
16
|
var serverHomeChat = require("./server-home-chat");
|
|
@@ -357,6 +357,12 @@ function createServer(opts) {
|
|
|
357
357
|
|
|
358
358
|
// --- Project registry ---
|
|
359
359
|
var assignmentService = attachWorkspaceAssignmentService({
|
|
360
|
+
getPresentation: function (principal) {
|
|
361
|
+
var project = projects.get(principal.sourceProjectSlug), manager = project && project.getSessionManager(), status = project && project.getStatus();
|
|
362
|
+
var session = manager && manager.sessions.get(principal.sourceSessionId);
|
|
363
|
+
var bound = clayProductControl && session && clayProductControl.bind({ projectSlug: principal.sourceProjectSlug, projectOwnerId: status.projectOwnerId, isMate: status.isMate, mateId: status.mateId, session: session });
|
|
364
|
+
return bound && bound._presentation;
|
|
365
|
+
},
|
|
360
366
|
storageDir: path.join(CONFIG_DIR, "workspace-assignments"),
|
|
361
367
|
getProjects: function () { return projects; },
|
|
362
368
|
isMultiUser: function () { return users.isMultiUser(); },
|
|
@@ -384,6 +390,13 @@ function createServer(opts) {
|
|
|
384
390
|
projects.forEach(function (project, slug) { project.forEachClient(function (ws) { visit(ws, slug); }); });
|
|
385
391
|
}
|
|
386
392
|
});
|
|
393
|
+
var mateContextService = require('./mate-context-service').attachMateContextService({
|
|
394
|
+
getOwnerUi: function () { return ownerUiService; }, workspace: workspaceQueryService,
|
|
395
|
+
getProjects: function () { return projects; }, isMultiUser: users.isMultiUser,
|
|
396
|
+
findUser: users.findUserById, onGetProjectAccess: onGetProjectAccess,
|
|
397
|
+
canAccessProject: users.canAccessProject,
|
|
398
|
+
resolveMate: function (owner, id) { return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? owner : null), id); }
|
|
399
|
+
});
|
|
387
400
|
var clayProductControl = require("./clay-product-control").attachClayProductControl({
|
|
388
401
|
getOwnerUi: function () { return ownerUiService; },
|
|
389
402
|
getNotifications: function () { return _globalNotifications; },
|
|
@@ -1284,6 +1297,7 @@ function createServer(opts) {
|
|
|
1284
1297
|
if (!require('./account-auth-socket').guardSocket(ws, req, isRequestAuthed, auth.touchSession)) return;
|
|
1285
1298
|
ws._clayPane = wsRequest.pane;
|
|
1286
1299
|
ws._clayPaneSession = wsRequest.paneSession;
|
|
1300
|
+
ws._clayContextual = wsRequest.contextual;
|
|
1287
1301
|
globalWs.handleConnection(ws, globalUser);
|
|
1288
1302
|
});
|
|
1289
1303
|
return;
|
|
@@ -1329,6 +1343,7 @@ function createServer(opts) {
|
|
|
1329
1343
|
ws._clayUser = wsUser; // attach user context
|
|
1330
1344
|
ws._clayPane = wsRequest.pane;
|
|
1331
1345
|
ws._clayPaneSession = wsRequest.paneSession;
|
|
1346
|
+
ws._clayContextual = wsRequest.contextual;
|
|
1332
1347
|
var remoteAddr = req.socket.remoteAddress || "";
|
|
1333
1348
|
ws._clayLocal = (remoteAddr === "127.0.0.1" || remoteAddr === "::1" || remoteAddr === "::ffff:127.0.0.1");
|
|
1334
1349
|
// Clear cross-project unread for this project when client connects
|
|
@@ -1450,6 +1465,7 @@ function createServer(opts) {
|
|
|
1450
1465
|
},
|
|
1451
1466
|
workspaceQueryService: workspaceQueryService,
|
|
1452
1467
|
clayProductControl: clayProductControl,
|
|
1468
|
+
mateContextService: mateContextService,
|
|
1453
1469
|
resumeLifecycleProject: function (userId) { return opts.projectLifecycle ? opts.projectLifecycle.resumeRecovered(slug, userId) : true; },
|
|
1454
1470
|
projectLogsService: projectLogsService,
|
|
1455
1471
|
issuesService: issuesService,
|
|
@@ -1729,6 +1745,7 @@ function createServer(opts) {
|
|
|
1729
1745
|
|
|
1730
1746
|
function handleDmMessage(ws, msg, routedProjectSlug) {
|
|
1731
1747
|
if (ownerUiService.handleMessage(ws, msg)) return true;
|
|
1748
|
+
if (mateContextService.handleMessage(ws, msg)) return true;
|
|
1732
1749
|
if (cursorSharingService && cursorSharingService.handleMessage(ws, msg)) return true;
|
|
1733
1750
|
if (defaultVendorService && defaultVendorService.handleMessage(ws, msg, routedProjectSlug)) return true;
|
|
1734
1751
|
if (contextViewService && contextViewService.handleMessage(ws, msg)) return true;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Product transcript slicing for providers that include a completed turn.
|
|
2
|
+
function endIndex(session, uuid, boundary) {
|
|
3
|
+
if (boundary === undefined) return undefined;
|
|
4
|
+
if (boundary !== "completed-turn-inclusive") throw new Error("Unsupported local fork history boundary");
|
|
5
|
+
var history = session.history || [];
|
|
6
|
+
var markers = (session.messageUUIDs || []).filter(function(marker) { return marker.uuid === uuid && marker.type === "user"; });
|
|
7
|
+
var marker = markers.length === 1 && markers[0];
|
|
8
|
+
var index = marker && marker.historyIndex;
|
|
9
|
+
var entry = Number.isInteger(index) && history[index];
|
|
10
|
+
if (!entry || entry.type !== "message_uuid" || entry.uuid !== uuid) throw new Error("Cannot safely represent this completed-turn fork boundary in local history");
|
|
11
|
+
for (var i = index + 1; i < history.length; i++) {
|
|
12
|
+
entry = history[i];
|
|
13
|
+
if (entry.type === "user_message" || entry.type === "message_uuid" && entry.messageType === "user") break;
|
|
14
|
+
if (entry.type === "done") {
|
|
15
|
+
if (entry.code === 0) return i + 1;
|
|
16
|
+
break;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
throw new Error("Cannot safely represent this completed-turn fork boundary in local history");
|
|
20
|
+
}
|
|
21
|
+
module.exports = { endIndex: endIndex };
|
package/lib/session-hygiene.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
var sessionProvenance = require("./session-provenance");
|
|
6
6
|
|
|
7
7
|
var BLANK_SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
8
|
-
var
|
|
8
|
+
var transcriptHygiene = require("./yoke/claude-transcript-hygiene");
|
|
9
9
|
|
|
10
10
|
// A session is "blank" when nothing has ever happened in it and nothing is
|
|
11
11
|
// attached to it. Adopted external sessions are never blank (their transcript
|
|
@@ -61,43 +61,11 @@ function collectStaleBlankSessions(sessions, activeSessionId, now) {
|
|
|
61
61
|
return stale;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
function extractClaudeUserText(event) {
|
|
65
|
-
var content = event && event.message && event.message.content;
|
|
66
|
-
if (typeof content === "string") return content;
|
|
67
|
-
if (!Array.isArray(content)) return "";
|
|
68
|
-
var parts = [];
|
|
69
|
-
for (var i = 0; i < content.length; i++) {
|
|
70
|
-
if (content[i] && content[i].type === "text" && typeof content[i].text === "string") {
|
|
71
|
-
parts.push(content[i].text);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return parts.join("");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// SDK capability probes create a real Claude transcript with a single "hi"
|
|
78
|
-
// prompt. Newer Claude Code versions append an interruption record when Clay
|
|
79
|
-
// aborts the probe, so that bookkeeping record must not make the transcript
|
|
80
|
-
// look like a real two-turn conversation.
|
|
81
|
-
function isClaudeWarmupTranscript(events) {
|
|
82
|
-
var promptCount = 0;
|
|
83
|
-
for (var i = 0; i < events.length; i++) {
|
|
84
|
-
var event = events[i];
|
|
85
|
-
if (!event || typeof event !== "object") continue;
|
|
86
|
-
if (event.type === "assistant") return false;
|
|
87
|
-
if (event.type !== "user" || !event.message || event.message.role !== "user") continue;
|
|
88
|
-
var text = extractClaudeUserText(event);
|
|
89
|
-
if (text === CLAUDE_INTERRUPTED_TEXT) continue;
|
|
90
|
-
if (text !== "hi") return false;
|
|
91
|
-
promptCount++;
|
|
92
|
-
}
|
|
93
|
-
return promptCount === 1;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
64
|
module.exports = {
|
|
97
65
|
isBlankSession: isBlankSession,
|
|
98
66
|
findReusableBlankSession: findReusableBlankSession,
|
|
99
67
|
collectStaleBlankSessions: collectStaleBlankSessions,
|
|
100
|
-
extractClaudeUserText: extractClaudeUserText,
|
|
101
|
-
isClaudeWarmupTranscript: isClaudeWarmupTranscript,
|
|
68
|
+
extractClaudeUserText: transcriptHygiene.extractClaudeUserText,
|
|
69
|
+
isClaudeWarmupTranscript: transcriptHygiene.isClaudeWarmupTranscript,
|
|
102
70
|
BLANK_SESSION_MAX_AGE_MS: BLANK_SESSION_MAX_AGE_MS,
|
|
103
71
|
};
|
|
@@ -7,11 +7,12 @@ function normalize(mode) {
|
|
|
7
7
|
return null;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
function capabilities(session, defaultVendor) {
|
|
10
|
+
function capabilities(session, defaultVendor, provider) {
|
|
11
11
|
var vendor = session && session.vendor || defaultVendor || "claude";
|
|
12
|
+
var contract = require("./yoke/session-tool-transport").contract(provider || vendor);
|
|
12
13
|
return {
|
|
13
|
-
auto:
|
|
14
|
-
mcpOverride:
|
|
14
|
+
auto: Array.isArray(contract.permissionModes) && contract.permissionModes.indexOf("auto") !== -1,
|
|
15
|
+
mcpOverride: contract.mcpPermissionOverrides === true && !!(session && session.queryInstance &&
|
|
15
16
|
typeof session.queryInstance.setMcpPermissionModeOverride === "function"),
|
|
16
17
|
};
|
|
17
18
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Bind personal instruction reads to the existing live session/query authority.
|
|
2
|
+
function bind(session, sessions, options, valid, multiUser) {
|
|
3
|
+
var owner = session.ownerId || null;
|
|
4
|
+
return Object.assign({}, options, { isAvailable: function() {
|
|
5
|
+
if (!sessions || typeof sessions.get !== "function" || sessions.get(session.localId) !== session || !valid() || (session.ownerId || null) !== owner || session.youCurator || session.sessionVisibility === "shared" || session._mcpForeignInput) return false;
|
|
6
|
+
if (owner) return require("./mcp-session-access").canUsePersonalConnections(session, sessions);
|
|
7
|
+
return !multiUser && !session.delegated && !session._delegatedBy && !session._pairDelegation
|
|
8
|
+
&& !(session.history || []).some(function(entry) { return entry.type === "user_message" && entry.from && entry.from !== "default"; });
|
|
9
|
+
} });
|
|
10
|
+
}
|
|
11
|
+
module.exports = { bind: bind };
|