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.
Files changed (246) hide show
  1. package/lib/claude-hook-installer.js +1 -267
  2. package/lib/claude-jsonl-watcher.js +1 -204
  3. package/lib/clay-control-presentation-binding.js +96 -0
  4. package/lib/clay-control-presentation.js +128 -0
  5. package/lib/clay-control-target-projection.js +26 -0
  6. package/lib/clay-mate-chat.js +1 -0
  7. package/lib/clay-mate-knowledge.js +1 -1
  8. package/lib/clay-product-control-tools.js +1 -1
  9. package/lib/clay-product-control.js +22 -12
  10. package/lib/clay-project-records.js +2 -1
  11. package/lib/clay-scheduling.js +1 -1
  12. package/lib/clay-screen-controls.js +13 -5
  13. package/lib/clay-skill-install.js +1 -33
  14. package/lib/clay-workspace-controls.js +2 -1
  15. package/lib/cli-sessions.js +1 -300
  16. package/lib/codex-defaults.js +1 -28
  17. package/lib/context-usage.js +3 -2
  18. package/lib/contextual-screen-snapshot.js +38 -0
  19. package/lib/daemon.js +5 -55
  20. package/lib/default-ai-runtime.js +4 -8
  21. package/lib/email-mcp-server.js +1 -1
  22. package/lib/kiro-defaults.js +1 -22
  23. package/lib/mate-context-service.js +138 -0
  24. package/lib/mate-context-tools.js +21 -0
  25. package/lib/mcp-proxy.js +2 -32
  26. package/lib/mcp-query-bridge.js +1 -1
  27. package/lib/mcp-source-adapters.js +1 -91
  28. package/lib/mcp-source-context.js +1 -42
  29. package/lib/mcp-source-discovery.js +1 -169
  30. package/lib/mcp-source-plugins.js +1 -68
  31. package/lib/mcp-source-restrictions.js +1 -47
  32. package/lib/mcp-tool-policy.js +1 -26
  33. package/lib/os-user-diagnostics.js +2 -4
  34. package/lib/os-users.js +4 -65
  35. package/lib/project-connection.js +14 -10
  36. package/lib/project-control-git-view.js +12 -0
  37. package/lib/project-filesystem.js +5 -1
  38. package/lib/project-http.js +7 -4
  39. package/lib/project-mcp.js +2 -2
  40. package/lib/project-notifications.js +5 -4
  41. package/lib/project-pair-result-delivery.js +2 -2
  42. package/lib/project-pair-usage.js +12 -13
  43. package/lib/project-pending-message-queue.js +3 -1
  44. package/lib/project-product-control.js +3 -1
  45. package/lib/project-scheduled-messages.js +4 -4
  46. package/lib/project-scheduled-task-control.js +1 -1
  47. package/lib/project-session-document.js +20 -5
  48. package/lib/project-session-history-actions.js +5 -3
  49. package/lib/project-session-pair.js +1 -1
  50. package/lib/project-session-runtime-actions.js +2 -1
  51. package/lib/project-session-spawn.js +2 -3
  52. package/lib/project-session-vendor.js +2 -1
  53. package/lib/project-sessions.js +18 -26
  54. package/lib/project-shared-browser.js +22 -5
  55. package/lib/project-studio-presentation.js +58 -0
  56. package/lib/project-user-message.js +37 -0
  57. package/lib/project-vendor-login.js +6 -36
  58. package/lib/project-workspace-query.js +11 -1
  59. package/lib/project.js +24 -7
  60. package/lib/public/app.js +25 -0
  61. package/lib/public/appearance-bootstrap.js +2 -0
  62. package/lib/public/css/contextual-clay.css +240 -0
  63. package/lib/public/css/messages.css +6 -2
  64. package/lib/public/modules/app-favicon.js +18 -9
  65. package/lib/public/modules/app-header.js +4 -2
  66. package/lib/public/modules/app-loop-ui.js +2 -0
  67. package/lib/public/modules/app-message-router.js +5 -1
  68. package/lib/public/modules/app-messages.js +50 -6
  69. package/lib/public/modules/app-notifications.js +11 -5
  70. package/lib/public/modules/app-panels.js +61 -123
  71. package/lib/public/modules/app-projects.js +3 -0
  72. package/lib/public/modules/app-rate-limit.js +7 -27
  73. package/lib/public/modules/chat-render-runtime.js +3 -0
  74. package/lib/public/modules/clay-control-activity.js +214 -0
  75. package/lib/public/modules/clay-control-copy.js +36 -0
  76. package/lib/public/modules/clay-control-detail-targets.js +44 -0
  77. package/lib/public/modules/clay-control-object-targets.js +52 -0
  78. package/lib/public/modules/clay-control-target-visibility.js +12 -0
  79. package/lib/public/modules/clay-control-targets.js +44 -0
  80. package/lib/public/modules/clay-navigation-render.js +52 -0
  81. package/lib/public/modules/clay-owner-navigation.js +11 -2
  82. package/lib/public/modules/clay-session-links.js +31 -0
  83. package/lib/public/modules/clay-studio-bridge.js +20 -0
  84. package/lib/public/modules/clay-studio-document.js +67 -0
  85. package/lib/public/modules/clay-studio-render.js +144 -0
  86. package/lib/public/modules/clay-tool-presentation.js +4 -0
  87. package/lib/public/modules/command-palette.js +26 -31
  88. package/lib/public/modules/contextual-ask-session.js +26 -0
  89. package/lib/public/modules/contextual-clay-geometry.js +155 -0
  90. package/lib/public/modules/contextual-clay-session.js +130 -0
  91. package/lib/public/modules/contextual-clay-workspace.js +157 -0
  92. package/lib/public/modules/contextual-clay.js +237 -0
  93. package/lib/public/modules/contextual-composer.js +91 -0
  94. package/lib/public/modules/contextual-mate-context.js +115 -0
  95. package/lib/public/modules/contextual-mate-hint.js +40 -0
  96. package/lib/public/modules/contextual-mate-identity.js +23 -0
  97. package/lib/public/modules/contextual-mate-sessions.js +134 -0
  98. package/lib/public/modules/contextual-search.js +137 -0
  99. package/lib/public/modules/contextual-session-citations.js +58 -0
  100. package/lib/public/modules/filebrowser.js +20 -1
  101. package/lib/public/modules/generation-stop.js +8 -5
  102. package/lib/public/modules/git-panel.js +3 -1
  103. package/lib/public/modules/icon-strip-tooltip.js +2 -1
  104. package/lib/public/modules/input.js +35 -1
  105. package/lib/public/modules/issues-render.js +3 -2
  106. package/lib/public/modules/knowledge-db-workbench.js +4 -4
  107. package/lib/public/modules/markdown.js +2 -34
  108. package/lib/public/modules/mate-knowledge-workbench.js +1 -1
  109. package/lib/public/modules/mcp-connection-view.js +1 -1
  110. package/lib/public/modules/message-delivery.js +10 -0
  111. package/lib/public/modules/pane-bridge.js +2 -2
  112. package/lib/public/modules/pending-message-queue.js +1 -0
  113. package/lib/public/modules/permission-control.js +5 -4
  114. package/lib/public/modules/product-review.js +26 -0
  115. package/lib/public/modules/project-chat-identity.js +13 -4
  116. package/lib/public/modules/project-logs-render.js +3 -0
  117. package/lib/public/modules/project-mate-navigation.js +58 -21
  118. package/lib/public/modules/scheduled-tasks.js +2 -0
  119. package/lib/public/modules/scheduler.js +2 -0
  120. package/lib/public/modules/search-clay-chat.js +5 -454
  121. package/lib/public/modules/session-create-form.js +10 -10
  122. package/lib/public/modules/settings-defaults.js +27 -28
  123. package/lib/public/modules/shared-browser.js +2 -0
  124. package/lib/public/modules/sidebar-mobile.js +4 -3
  125. package/lib/public/modules/sidebar-projects.js +1 -0
  126. package/lib/public/modules/sidebar-sessions.js +1 -0
  127. package/lib/public/modules/split-view.js +7 -16
  128. package/lib/public/modules/terminal.js +9 -0
  129. package/lib/public/modules/tools.js +6 -2
  130. package/lib/public/modules/tui-grab.js +1 -2
  131. package/lib/public/modules/vendor-login-browser.js +5 -3
  132. package/lib/public/modules/vendor-login.js +2 -2
  133. package/lib/public/modules/vendor-priority.js +15 -1
  134. package/lib/public/modules/wireframe-workbench.js +5 -2
  135. package/lib/public/muse-avatar.svg +4 -0
  136. package/lib/public/style.css +2 -0
  137. package/lib/safe-bash-commands.js +3 -45
  138. package/lib/sdk-bridge.js +131 -395
  139. package/lib/sdk-foreground-admission.js +16 -0
  140. package/lib/sdk-mcp-descriptors.js +2 -37
  141. package/lib/sdk-message-processor.js +50 -17
  142. package/lib/sdk-quota-snapshot.js +1 -1
  143. package/lib/sdk-response-recovery.js +1 -155
  144. package/lib/sdk-worker.js +2 -677
  145. package/lib/server-contextual-clay-settings.js +40 -0
  146. package/lib/server-contextual-mate-sessions.js +70 -0
  147. package/lib/server-home-chat.js +7 -1
  148. package/lib/server-home-clay-entry.js +23 -4
  149. package/lib/server-home-clay-session-links.js +12 -0
  150. package/lib/server-owner-ui.js +49 -8
  151. package/lib/server-settings.js +2 -0
  152. package/lib/server.js +18 -1
  153. package/lib/session-fork-history.js +21 -0
  154. package/lib/session-hygiene.js +3 -35
  155. package/lib/session-permission-mode.js +4 -3
  156. package/lib/session-skill-scope.js +11 -0
  157. package/lib/sessions.js +24 -89
  158. package/lib/terminal-manager.js +18 -11
  159. package/lib/tool-llm.js +1 -1
  160. package/lib/tui-transcript-index.js +1 -132
  161. package/lib/users-contextual-clay-preferences.js +31 -0
  162. package/lib/users.js +3 -0
  163. package/lib/vendor-login-browser.js +1 -11
  164. package/lib/workspace-assignment-service.js +25 -0
  165. package/lib/workspace-query-mcp-server.js +1 -1
  166. package/lib/ws-request.js +1 -0
  167. package/lib/ws-schema.js +10 -0
  168. package/lib/yoke/acp-agent-profiles.js +5 -3
  169. package/lib/yoke/adapters/antigravity.js +3 -14
  170. package/lib/yoke/adapters/claude-query-contract.js +9 -0
  171. package/lib/yoke/adapters/claude.js +7 -42
  172. package/lib/yoke/adapters/codex-query-contract.js +24 -0
  173. package/lib/yoke/adapters/codex.js +1 -0
  174. package/lib/yoke/adapters/kiro.js +3 -1
  175. package/lib/yoke/adapters/muse-query-contract.js +7 -0
  176. package/lib/yoke/adapters/muse.js +122 -0
  177. package/lib/yoke/auth-events.js +17 -0
  178. package/lib/yoke/claude-hook-installer.js +271 -0
  179. package/lib/yoke/claude-jsonl-watcher.js +204 -0
  180. package/lib/yoke/claude-maintenance.js +55 -0
  181. package/lib/yoke/claude-permission-patterns.js +53 -0
  182. package/lib/yoke/claude-response-recovery.js +155 -0
  183. package/lib/yoke/claude-transcript-hygiene.js +34 -0
  184. package/lib/yoke/clay-skill-install.js +33 -0
  185. package/lib/yoke/cli-sessions.js +300 -0
  186. package/lib/yoke/codex-app-server.js +7 -12
  187. package/lib/yoke/configuration.js +13 -0
  188. package/lib/yoke/discovery.js +70 -0
  189. package/lib/yoke/errors.js +8 -0
  190. package/lib/yoke/host-environment.js +7 -0
  191. package/lib/yoke/index.js +9 -51
  192. package/lib/yoke/interface.js +116 -0
  193. package/lib/yoke/kiro-acp-server.js +5 -5
  194. package/lib/yoke/legacy-sessions.js +89 -0
  195. package/lib/yoke/login-protocol.js +25 -0
  196. package/lib/yoke/maintenance.js +12 -0
  197. package/lib/yoke/mcp-proxy.js +32 -0
  198. package/lib/yoke/mcp-source-adapters.js +91 -0
  199. package/lib/yoke/mcp-source-context.js +42 -0
  200. package/lib/yoke/mcp-source-discovery.js +169 -0
  201. package/lib/yoke/mcp-source-plugins.js +68 -0
  202. package/lib/yoke/mcp-source-restrictions.js +47 -0
  203. package/lib/yoke/mcp-tool-policy-normalize.js +28 -0
  204. package/lib/yoke/model-preferences.js +6 -0
  205. package/lib/yoke/muse-artifacts.js +36 -0
  206. package/lib/yoke/muse-controls.js +50 -0
  207. package/lib/yoke/muse-input.js +64 -0
  208. package/lib/yoke/muse-items.js +157 -0
  209. package/lib/yoke/muse-policy.js +52 -0
  210. package/lib/yoke/muse-query.js +428 -0
  211. package/lib/yoke/muse-runtime.js +211 -0
  212. package/lib/yoke/muse-services.js +79 -0
  213. package/lib/yoke/muse-skills.js +79 -0
  214. package/lib/yoke/muse-turns.js +71 -0
  215. package/lib/yoke/options/acp.js +2 -0
  216. package/lib/yoke/options/antigravity.js +2 -0
  217. package/lib/yoke/options/claude.js +21 -0
  218. package/lib/yoke/options/codex-defaults.js +29 -0
  219. package/lib/yoke/options/codex-settings.js +21 -0
  220. package/lib/yoke/options/codex.js +6 -0
  221. package/lib/yoke/options/kiro-defaults.js +22 -0
  222. package/lib/yoke/options/kiro.js +5 -0
  223. package/lib/yoke/options/muse.js +5 -0
  224. package/lib/yoke/provider-contract.js +5 -0
  225. package/lib/yoke/providers/antigravity-executable.js +8 -0
  226. package/lib/yoke/providers/claude-executable.js +40 -0
  227. package/lib/yoke/providers/claude-model-descriptions.js +14 -0
  228. package/lib/yoke/providers/claude-process.js +97 -0
  229. package/lib/yoke/providers/claude-provision.js +75 -0
  230. package/lib/yoke/providers/codex-errors.js +8 -0
  231. package/lib/yoke/providers/muse-executable.js +17 -0
  232. package/lib/yoke/provisioning.js +6 -0
  233. package/lib/yoke/query-options.js +10 -0
  234. package/lib/yoke/response-recovery.js +41 -0
  235. package/lib/yoke/sdk-mcp-descriptors.js +37 -0
  236. package/lib/yoke/sdk-server-factory.js +7 -0
  237. package/lib/yoke/session-history.js +8 -0
  238. package/lib/yoke/session-tool-transport.js +31 -13
  239. package/lib/yoke/settings.js +23 -0
  240. package/lib/yoke/source-locations.js +4 -0
  241. package/lib/yoke/title-query.js +18 -7
  242. package/lib/yoke/tui-transcript-index.js +132 -0
  243. package/lib/yoke/usage.js +48 -0
  244. package/lib/yoke/user-input.js +13 -0
  245. package/lib/yoke/vendor-registry.js +81 -1
  246. 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 };
@@ -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
- session = manager.createSession({ ownerId: userId, vendor: resolved.vendor, model: resolved.model, effort: resolved.effort || null }, null);
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 = "search";
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);
@@ -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 record = { owner: owner, viewer: viewer, target: target, revalidate: revalidate, created: Date.now(), claimed: false,
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') { if (msg.version === 1) capabilities.set(ws, 1); return true; }
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 === 'clay_navigation_claim') {
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 };
@@ -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 };
@@ -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 CLAUDE_INTERRUPTED_TEXT = "[Request interrupted by user]";
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: vendor === "claude",
14
- mcpOverride: vendor === "claude" && !!(session && session.queryInstance &&
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 };