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
@@ -11,7 +11,8 @@ function attachSessionRuntimeActions(ctx) {
11
11
  var vendor = session.vendor || ctx.sm.defaultVendor || "claude";
12
12
  var ids = Array.from(new Set([session.terminalId, session.runtimeTerminalId].filter(function (id) { return typeof id === "number"; })));
13
13
  if (!ids.length && (session.runtimeMode || session.mode || "gui") === "gui") return { status: "completed", runtimeMode: "gui", runtimeEffect: "none", threadPreserved: true };
14
- if (vendor !== "claude" || !session.cliSessionId) throw new Error("Only a legacy Claude terminal thread can return to GUI chat.");
14
+ var lifecycle = require("./yoke/provider-contract").resolve(vendor);
15
+ if (typeof lifecycle.supportsLegacyGuiResume !== "function" || !lifecycle.supportsLegacyGuiResume(session)) throw new Error("This provider cannot return a legacy terminal thread to GUI chat.");
15
16
  var closed = 0, persisted = false;
16
17
  var fields = ['terminalId', 'runtimeTerminalId'];
17
18
  var originalFields = fields.map(function (field) { return session[field]; });
@@ -1,6 +1,5 @@
1
1
  var yoke = require("./yoke");
2
2
  var sessionSpawnMcp = require("./session-spawn-mcp-server");
3
- var cliSessions = require("./cli-sessions");
4
3
  var os = require("os");
5
4
  var osUsers = require("./os-users");
6
5
 
@@ -161,7 +160,7 @@ function attachSessionSpawn(ctx) {
161
160
  var sm = ctx.sm;
162
161
  var queue = createSpawnQueue(SPAWN_CONCURRENCY);
163
162
  var adapters = ctx.adapters || {};
164
- var readCliSessionHistory = ctx.readCliSessionHistory || cliSessions.readCliSessionHistory;
163
+ var readCliSessionHistory = ctx.readCliSessionHistory || function(home, project, sessionId, provider) { return require("./yoke/session-history").read(provider || "claude", home, project, sessionId); };
165
164
 
166
165
  function resolveSessionHome(session) {
167
166
  var linuxUser = ctx.getLinuxUserForSession(session);
@@ -283,7 +282,7 @@ function attachSessionSpawn(ctx) {
283
282
  forkHistory = parent.history.slice();
284
283
  } else {
285
284
  forkHistory = await readCliSessionHistory(
286
- resolveSessionHome(parent), ctx.cwd, forkResult.sessionId
285
+ resolveSessionHome(parent), ctx.cwd, forkResult.sessionId, parent.vendor
287
286
  );
288
287
  }
289
288
  } catch (e) {
@@ -18,7 +18,8 @@ function attachSessionVendor(ctx) {
18
18
  session.vendor = vendor;
19
19
  session.model = null;
20
20
  session.effort = yoke.clampEffort(vendor, session.effort) || null;
21
- if (session.permissionMode === 'auto' && vendor !== 'claude') session.permissionMode = 'default';
21
+ var modes = yoke.sessionTools.contract(ctx.adapters[vendor]).permissionModes;
22
+ if (session.permissionMode === 'auto' && (!Array.isArray(modes) || modes.indexOf('auto') < 0)) session.permissionMode = 'default';
22
23
  session.effectivePermissionMode = null;
23
24
  try { if (ctx.sm.saveSessionFile(session) !== true) throw new Error('Could not persist the provider selection.'); }
24
25
  catch (error) { Object.assign(session, previous); throw error; }
@@ -2,7 +2,7 @@ var serverSettingsMessages = require("./project-server-settings");
2
2
  var fs = require("fs");
3
3
  var path = require("path");
4
4
  var { execFileSync } = require("child_process");
5
- var { CODEX_DEFAULTS, getCodexConfig } = require("./codex-defaults");
5
+ var providerSettings = require("./yoke/settings");
6
6
  var updateSnooze = require("./update-snooze");
7
7
  var yoke = require("./yoke");
8
8
  var sessionVisibility = require("./session-visibility");
@@ -258,7 +258,7 @@ function attachSessions(ctx) {
258
258
  if (!session || !session.cliSessionId) return;
259
259
  if (session._titleWatcherStop) return; // already watching
260
260
  var watcher;
261
- try { watcher = require("./claude-jsonl-watcher"); } catch (e) { return; }
261
+ try { watcher = require("./yoke/claude-jsonl-watcher"); } catch (e) { return; }
262
262
  var home = resolveSessionHome(session);
263
263
  var jsonlPath = watcher.jsonlPathFor(home, cwd, session.cliSessionId);
264
264
  if (!jsonlPath) return;
@@ -326,7 +326,7 @@ function attachSessions(ctx) {
326
326
  // message right away. The transcript is small enough that a full
327
327
  // re-send beats maintaining a delta protocol.
328
328
  try {
329
- var newIndex = require("./tui-transcript-index").readAssistantIndex(resolveSessionHome(s), cwd, s.cliSessionId);
329
+ var newIndex = require("./yoke/tui-transcript-index").readAssistantIndex(resolveSessionHome(s), cwd, s.cliSessionId);
330
330
  send({
331
331
  type: "tui_transcript_state",
332
332
  id: s.localId,
@@ -453,7 +453,7 @@ function attachSessions(ctx) {
453
453
  function prepareTuiSessionForGuiView(session, options) {
454
454
  if (!session || session.cliSessionId == null) return;
455
455
  var cliSess;
456
- try { cliSess = require("./cli-sessions"); } catch (e) { return; }
456
+ try { cliSess = require("./yoke/cli-sessions"); } catch (e) { return; }
457
457
  // Re-read whenever the jsonl has changed since the last hydrate (a TUI
458
458
  // turn appended messages), not just when history is empty. The earlier
459
459
  // "already hydrated" short-circuit kept stale history after a
@@ -603,7 +603,7 @@ function attachSessions(ctx) {
603
603
  var from = sm.findTurnBoundary(session.history, Math.max(0, targetFrom));
604
604
  var to = before;
605
605
  var items = session.history.slice(from, to).map(function (item, index) {
606
- return Object.assign({}, hydrateImageRefs(item), { _historyIndex: from + index });
606
+ return Object.assign({}, hydrateImageRefs(require("./yoke/usage").clientEvent(item, session.vendor || "claude")), { _historyIndex: from + index });
607
607
  });
608
608
  sendTo(ws, {
609
609
  type: "history_prepend",
@@ -918,7 +918,7 @@ function attachSessions(ctx) {
918
918
  return true;
919
919
  }
920
920
  try {
921
- var tprIndex = require("./tui-transcript-index").readAssistantIndex(resolveSessionHome(tprSess), cwd, tprSess.cliSessionId);
921
+ var tprIndex = require("./yoke/tui-transcript-index").readAssistantIndex(resolveSessionHome(tprSess), cwd, tprSess.cliSessionId);
922
922
  sendTo(ws, {
923
923
  type: "tui_transcript_state",
924
924
  id: tprId,
@@ -1099,14 +1099,14 @@ function attachSessions(ctx) {
1099
1099
  var pid = msg.pid;
1100
1100
  if (!pid || typeof pid !== "number") return true;
1101
1101
  // Verify target is actually a claude process before killing
1102
- if (!sdk.isClaudeProcess(pid)) {
1103
- console.error("[project] Refused to kill PID " + pid + ": not a claude process");
1104
- sendTo(ws, { type: "error", text: "Process " + pid + " is not a Claude process." });
1102
+ if (!sdk.isProviderProcess(pid, getSessionForWs(ws))) {
1103
+ console.error("[project] Refused to kill PID " + pid + ": not a verified provider process");
1104
+ sendTo(ws, { type: "error", text: "Process " + pid + " is not a verified provider process." });
1105
1105
  return true;
1106
1106
  }
1107
1107
  try {
1108
1108
  process.kill(pid, "SIGTERM");
1109
- console.log("[project] Sent SIGTERM to conflicting Claude process PID " + pid);
1109
+ console.log("[project] Sent SIGTERM to conflicting provider process PID " + pid);
1110
1110
  sendTo(ws, { type: "process_killed", pid: pid });
1111
1111
  } catch (e) {
1112
1112
  console.error("[project] Failed to kill PID " + pid + ":", e.message);
@@ -1367,20 +1367,12 @@ function attachSessions(ctx) {
1367
1367
  return true;
1368
1368
  }
1369
1369
 
1370
- // Codex-specific settings (stored on sessionManager, passed to adapter via adapterOptions)
1371
- if (msg.type === "set_codex_approval") {
1372
- sm.codexApproval = getCodexConfig({ codexApproval: msg.approval }).approval;
1373
- send(Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
1374
- return true;
1375
- }
1376
- if (msg.type === "set_codex_sandbox") {
1377
- sm.codexSandbox = msg.sandbox || CODEX_DEFAULTS.sandbox;
1378
- send(Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
1379
- return true;
1380
- }
1381
- if (msg.type === "set_codex_websearch") {
1382
- sm.codexWebSearch = msg.webSearch || CODEX_DEFAULTS.webSearch;
1383
- send(Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
1370
+ var providerChange;
1371
+ try { providerChange = providerSettings.change(msg, sm); }
1372
+ catch (error) { sendTo(ws, { type: "error", text: error.message }); return true; }
1373
+ if (providerChange) {
1374
+ Object.keys(providerChange.patch).forEach(function(key) { sm[key] = providerChange.patch[key]; });
1375
+ providerChange.messages.forEach(function(message) { send(message); });
1384
1376
  return true;
1385
1377
  }
1386
1378
 
@@ -1924,7 +1916,7 @@ function attachSessions(ctx) {
1924
1916
  var galManaged = [];
1925
1917
  var galUser = [];
1926
1918
  try {
1927
- var galInstaller = require("./claude-hook-installer");
1919
+ var galInstaller = require("./yoke/claude-hook-installer");
1928
1920
  galManaged = galInstaller.CLAY_MANAGED_ALLOW || [];
1929
1921
  } catch (e) {}
1930
1922
  if (galUid) {
@@ -1950,7 +1942,7 @@ function attachSessions(ctx) {
1950
1942
  // home (OS-mode: getent passwd; single-user: os.homedir()).
1951
1943
  var salEffective = false;
1952
1944
  try {
1953
- var salInstaller = require("./claude-hook-installer");
1945
+ var salInstaller = require("./yoke/claude-hook-installer");
1954
1946
  var salHome = null;
1955
1947
  if (osUsers && ws._clayUser && ws._clayUser.linuxUser) {
1956
1948
  try {
@@ -175,13 +175,14 @@ function attachSharedBrowser(ctx) {
175
175
  record.touched = Date.now(); broadcast(record);
176
176
  return projection(record);
177
177
  }
178
- async function run(session, record, actor, action, event, intent) {
178
+ async function run(session, record, actor, action, event, intent, presentationStart) {
179
179
  requireAccess(session);
180
180
  if (!record || !record.runtime || !recordAllowed(record)) throw new Error("Open a browser first");
181
181
  if (record.handoff) throw new Error("Browser control is changing; wait for the handoff");
182
182
  // control "agent" = shared (owner and Clay may both act); "user" = exclusive human mode (Clay mutations denied).
183
183
  // The authorized owner is never blocked by control mode and ordinary human input never changes it.
184
184
  if (action !== "inspect" && actor === "agent" && record.control !== "agent") throw new Error("Only the user can control this browser right now (Clay is paused). Wait for them to resume shared control; do not work around it.");
185
+ if (action !== 'inspect') { record.presentationRevision = (record.presentationRevision || 0) + 1; if (presentationStart) presentationStart(record.presentationRevision); }
185
186
  var epoch = record.epoch;
186
187
  var generation = Number(session._sdkQueryGeneration || 0);
187
188
  await record.ready;
@@ -204,18 +205,22 @@ function attachSharedBrowser(ctx) {
204
205
  return result;
205
206
  }
206
207
  async function tool(session, args) {
208
+ var observer;
207
209
  try {
208
210
  requireAccess(session);
209
211
  if (args.intent !== undefined && (typeof args.intent !== "string" || args.intent.length > 160)) throw new Error("Browser intent must be at most 160 characters");
210
212
  var record = args.action === "status" || args.action === "open" ? null : selectRecord(session, args.browserId);
211
- var value;
213
+ var value, actionRevision = record && (record.presentationRevision || 0);
214
+ function presentationStart(value) { actionRevision = value; }
215
+ if (['status', 'inspect'].indexOf(args.action) < 0 && ctx.getPresentation) { var display = ctx.getPresentation(session); if (display) observer = ctx.studioPresentation ? ctx.studioPresentation.begin(session, 'browser') : display.track('studio', 'browser'); }
212
216
  if (args.action === "status") value = { browsers: sessionRecords(session).map(projection) };
213
217
  else if (args.action === "open") {
214
218
  if (args.incognito !== undefined && typeof args.incognito !== "boolean") throw new Error("Invalid incognito mode");
215
219
  var url = args.url ? policy.browserUrl(args.url) : null;
216
220
  record = args.browserId ? selectRecord(session, args.browserId) : create(session, "agent", args.newTab === true, args.incognito);
217
221
  if (args.incognito !== undefined && record.incognito !== (!ctx.isMate || args.incognito)) throw new Error("This tab has a different cookie mode. Open a new tab with the requested mode.");
218
- if (url) await run(session, record, "agent", "action", { kind: "navigate", url: url }, args.intent);
222
+ if (url) await run(session, record, "agent", "action", { kind: "navigate", url: url }, args.intent, presentationStart);
223
+ else actionRevision = record.presentationRevision || 0;
219
224
  value = projection(record);
220
225
  } else if (args.action === "finish") {
221
226
  if (!record || !record.runtime) throw new Error("Open a browser first");
@@ -227,11 +232,23 @@ function attachSharedBrowser(ctx) {
227
232
  var image = value.image; delete value.image;
228
233
  return { content: [{ type: "text", text: JSON.stringify(value) }, { type: "image", mimeType: "image/jpeg", data: image }] };
229
234
  } else {
230
- value = await run(session, record, "agent", "action", policy.validateAction(Object.assign({}, args, { kind: args.action })), args.intent);
235
+ value = await run(session, record, "agent", "action", policy.validateAction(Object.assign({}, args, { kind: args.action })), args.intent, presentationStart);
231
236
  }
237
+ if (observer && observer.present) {
238
+ observer.phase('completed');
239
+ try {
240
+ observer.guard();
241
+ var expectedRecord = record, expectedEpoch = record.epoch, expectedUrl = record.url;
242
+ if ((record.presentationRevision || 0) !== actionRevision || record.control !== 'agent') throw new Error('The browser changed after this action.');
243
+ var view = await run(session, record, 'agent', 'inspect', null, args.intent);
244
+ if (view && view.url && policy.visibleUrl(view.url) !== expectedUrl) throw new Error('The browser navigated after this action.');
245
+ if (!view || typeof view.image !== 'string' || !view.image) throw new Error('No exact browser frame was returned.');
246
+ observer.present({ type: 'shared_browser_frame', sessionId: session.localId, browserId: record.id, data: view.image, width: view.width, height: view.height }, 'browser', record.id, require('./clay-control-target-projection').digest(view.image), function () { requireAccess(session); if (records.get(expectedRecord.id) !== expectedRecord || !recordAllowed(expectedRecord) || expectedRecord.epoch !== expectedEpoch || expectedRecord.url !== expectedUrl || expectedRecord.control !== 'agent' || (expectedRecord.presentationRevision || 0) !== actionRevision || !expectedRecord.runtime) throw new Error('Browser target changed.'); }, function (message, onSent) { return ctx.sendToSession(session.localId, message, onSent); });
247
+ } catch (visibilityError) { observer.noVisibility(); }
248
+ } else if (observer) observer.finish({ status: 'completed' });
232
249
  if (value && value.url) value.browserLink = "browser:" + policy.visibleUrl(value.url);
233
250
  return { content: [{ type: "text", text: JSON.stringify(value) }] };
234
- } catch (e) { return { isError: true, content: [{ type: "text", text: e.message }] }; }
251
+ } catch (e) { if (observer) { if (observer.unknown) observer.unknown(); else observer.finish({ outcome: "uncertain" }); } return { isError: true, content: [{ type: "text", text: e.message }] }; }
235
252
  }
236
253
  async function openLinkedPage(session, url, incognito) {
237
254
  var record = sessionRecords(session).find(function (tab) {
@@ -0,0 +1,58 @@
1
+ var crypto = require('crypto');
2
+
3
+ // Visibility receipts observe existing workbench effects; they dispatch no product action.
4
+ function attachStudioPresentation(ctx) {
5
+ var pending = new Map();
6
+ function begin(session, action) {
7
+ var display = ctx.getPresentation && ctx.getPresentation(session);
8
+ if (!display) return null;
9
+ var source = display.source(), observer = display.track('studio', action);
10
+ if (!observer) return null;
11
+ var id = crypto.randomUUID(), token = crypto.randomUUID(), record;
12
+ function end(status, viewer) {
13
+ if (!record || !pending.has(id)) return;
14
+ pending.delete(id); clearTimeout(record.timer);
15
+ if (viewer) observer.focus(viewer);
16
+ observer.finish({ status: status });
17
+ }
18
+ function cancel() { if (record) { pending.delete(id); clearTimeout(record.timer); } observer.cancel(); }
19
+ return {
20
+ phase: observer.phase,
21
+ guard: observer.guard,
22
+ noVisibility: function () { observer.finish({ status: "render_unknown" }); },
23
+ fail: function () { cancel(); },
24
+ unknown: function () { observer.finish({ outcome: 'uncertain' }); },
25
+ present: function (message, surface, key, revision, validate, send) {
26
+ observer.guard();
27
+ if (pending.size >= 100) { observer.finish({ status: 'render_unknown' }); return 0; }
28
+ var target = { kind: 'surface', sourceSlug: source.sourceSlug, sourceSessionId: session.localId, sourceSessionOriginId: source.sourceSessionOriginId,
29
+ runtimeId: source.runtimeId, surface: surface, key: key, revision: revision };
30
+ observer.target(target, validate); observer.guard();
31
+ var evidence = Object.assign({ version: 1, id: id, activityId: observer.id, token: token, deadline: Date.now() + 5000, target: target }, source);
32
+ record = { session: session, source: source, evidence: evidence, observer: observer, viewers: new Set(), end: end, cancel: cancel };
33
+ pending.set(id, record);
34
+ record.timer = setTimeout(function () { end('render_unknown'); }, 5000); record.timer.unref();
35
+ var sent = send(Object.assign({}, message, { controlPresentation: evidence }), function (ws) { record.viewers.add(ws); });
36
+ if (sent > 0) { if (surface !== 'browser') observer.phase('delivered'); }
37
+ else end('render_unknown');
38
+ return sent;
39
+ }
40
+ };
41
+ }
42
+ function handleMessage(ws, msg) {
43
+ if (msg.type !== 'clay_studio_rendered') return false;
44
+ var record = pending.get(msg.id);
45
+ if (!record || record.evidence.token !== msg.token || !record.viewers.has(ws)) return true;
46
+ try {
47
+ record.observer.guard();
48
+ if (Date.now() > record.evidence.deadline) { record.end('render_unknown', ws); return true; }
49
+ if (ws.readyState !== 1 || ws._clayAuthCheck && !ws._clayAuthCheck() ||
50
+ ws._clayActiveSession !== record.session.localId || ctx.getSessionForWs(ws) !== record.session || (ws._clayUser && ws._clayUser.id || null) !== record.source.ownerId ||
51
+ msg.sessionOriginId !== record.source.sourceSessionOriginId || msg.revision !== record.evidence.target.revision) throw new Error('Studio viewer or target changed.');
52
+ record.end(msg.visible === true ? 'rendered' : 'render_unknown', ws);
53
+ } catch (error) { record.cancel(); }
54
+ return true;
55
+ }
56
+ return { begin: begin, handleMessage: handleMessage };
57
+ }
58
+ module.exports = { attachStudioPresentation: attachStudioPresentation };
@@ -49,6 +49,7 @@ function attachUserMessage(ctx) {
49
49
 
50
50
  var clients = ctx.clients;
51
51
  var opts = ctx.opts;
52
+ var snapshotService = require('./contextual-screen-snapshot').attachContextualScreenSnapshot(ctx);
52
53
 
53
54
  var usersModule = ctx.usersModule;
54
55
  var matesModule = ctx.matesModule;
@@ -450,6 +451,10 @@ function attachUserMessage(ctx) {
450
451
  return true;
451
452
  }
452
453
 
454
+ var screenSnapshot;
455
+ try { screenSnapshot = snapshotService.capture(ws, session, msg); }
456
+ catch (error) { sendTo(ws, { type: 'error', text: error.message }); finishPendingEarly(false, error.message); return true; }
457
+
453
458
  // A configured Split Worker is Driver-operated: its work arrives through
454
459
  // the Driver's delegation, not from a person typing into its pane. The UI
455
460
  // hides the composer, but the refusal has to live here too — a legacy
@@ -470,9 +475,13 @@ function attachUserMessage(ctx) {
470
475
  try { ctx.resumeLifecycleProject(ws); }
471
476
  catch (error) { sendTo(ws, { type: "error", text: error.message }); return true; }
472
477
  }
478
+ function reportQueuedContext() {
479
+ if (msg.mateContextRef) sendTo(ws, { type: 'mate_context_shared', status: 'unavailable', error: 'Queued messages do not retain contextual access. Send again from the current view when ready.' });
480
+ }
473
481
  if (!pendingInternal && session._driverContinuationLease && pendingMessageQueue) {
474
482
  var continuationQueued = pendingMessageQueue.admit(session, msg, ws && ws._clayUser);
475
483
  if (continuationQueued.ok) {
484
+ reportQueuedContext();
476
485
  messageDelivery.acknowledge(ws, deliveryReceipt, null, session);
477
486
  sendTo(ws, { type: "message_queued", clientMessageId: msg.clientMessageId || null,
478
487
  sessionId: session.localId, projectSlug: slug, state: "pending",
@@ -486,8 +495,10 @@ function attachUserMessage(ctx) {
486
495
  if (!pendingInternal && pendingMessageQueue && (pendingMessageQueue.hasActive(session) || session.isProcessing || session._queryStarting || session._awaitingTurnResult)) {
487
496
  var queuedResult = pendingMessageQueue.admit(session, msg, ws && ws._clayUser);
488
497
  if (queuedResult.ok) {
498
+ reportQueuedContext();
489
499
  messageDelivery.acknowledge(ws, deliveryReceipt, null, session);
490
500
  sendTo(ws, { type: "message_queued", clientMessageId: msg.clientMessageId || null, sessionId: session.localId, projectSlug: slug, state: "pending", paused: pendingMessageQueue.isPaused(session), revision: pendingMessageQueue.getRevision() });
501
+ if (require('./sdk-foreground-admission').available(session)) setImmediate(function () { consumePendingMessage(session); });
491
502
  } else {
492
503
  sendTo(ws, { type: "error", text: queuedResult.error || "Unable to queue message" });
493
504
  }
@@ -553,6 +564,7 @@ function attachUserMessage(ctx) {
553
564
  if (msg.pastes && msg.pastes.length > 0) {
554
565
  userMsg2.pastes = msg.pastes;
555
566
  }
567
+ if (screenSnapshot) { userMsg2.contextualSnapshot = screenSnapshot.snapshot; userMsg2.sessionOriginId = session.sessionOriginId; }
556
568
  if (deliveryReceipt.clientMessageId) userMsg2.clientMessageId = deliveryReceipt.clientMessageId;
557
569
  if (!userMsg2._ts) userMsg2._ts = Date.now();
558
570
  var transcriptRecorded = false;
@@ -757,7 +769,12 @@ function attachUserMessage(ctx) {
757
769
  return !pendingBlockReason();
758
770
  }
759
771
 
772
+ var contextualAdmission = null, contextualNotified = false;
760
773
  function acceptPendingDispatch() {
774
+ if (contextualAdmission && !contextualNotified) {
775
+ contextualNotified = true;
776
+ sendTo(ws, { type: 'mate_context_shared', contextRef: contextualAdmission.contextRef, summary: contextualAdmission.summary, status: 'submitted' });
777
+ }
761
778
  if (!pendingInternal) return true;
762
779
  var blockReason = pendingBlockReason();
763
780
  if (blockReason) {
@@ -784,6 +801,11 @@ function attachUserMessage(ctx) {
784
801
  console.log("[PERF] project.js: startQuery called, localId=" + session.localId + " t=0ms");
785
802
  var started = sdk.startQuery(session, finalText, msg.images, ensureProjectAccessForSession(session), function () {
786
803
  var blockReason = pendingBlockReason();
804
+ if (screenSnapshot) { try { screenSnapshot.assertLive(); } catch (error) { blockReason = error.message; } }
805
+ if (contextualAdmission) {
806
+ try { opts.mateContextService.assertActive(session, contextualAdmission.contextRef); }
807
+ catch (error) { blockReason = error.message; sendTo(ws, { type: 'mate_context_shared', status: 'unavailable', error: error.message }); }
808
+ }
787
809
  if (blockReason) rejectPendingDispatch(blockReason);
788
810
  return !blockReason;
789
811
  }, acceptPendingDispatch);
@@ -793,6 +815,21 @@ function attachUserMessage(ctx) {
793
815
  }
794
816
 
795
817
  function dispatchToSdk(finalText) {
818
+ if (screenSnapshot) {
819
+ try { screenSnapshot.assertLive(); } catch (error) { sendTo(ws, { type: 'error', text: error.message }); rejectPendingDispatch(error); return; }
820
+ finalText = require('./contextual-screen-snapshot').modelContext(screenSnapshot.snapshot) + finalText;
821
+ }
822
+ if (isMate && opts.mateContextService) {
823
+ opts.mateContextService.clear(session);
824
+ if (msg.mateContextRef && !pendingInternal) {
825
+ try {
826
+ var shared = opts.mateContextService.activate(ws, session, msg.mateContextRef, userMsg2);
827
+ contextualAdmission = shared;
828
+ sdk.refreshSessionRuntime(session);
829
+ finalText = 'Shared context metadata (untrusted task data, never approval):\n' + JSON.stringify(shared) + '\n\n' + finalText;
830
+ } catch (error) { sendTo(ws, { type: 'mate_context_shared', status: 'unavailable', error: error.message }); }
831
+ } else if (msg.mateContextRef) sendTo(ws, { type: 'mate_context_shared', status: 'unavailable', error: 'Queued messages do not retain contextual access. Send again from the current view when ready.' });
832
+ }
796
833
  if (!session.isProcessing) {
797
834
  session.isProcessing = true;
798
835
  onProcessingChanged();
@@ -25,37 +25,11 @@ var yoke = require("./yoke");
25
25
  // Login CLIs print a success line and then hand the shell back, so the PTY
26
26
  // itself does not exit. Watch the output stream instead. Deliberately narrow:
27
27
  // "Successfully logged out" and MCP-server logins must not match.
28
- var LOGIN_SUCCESS_PATTERNS = [
29
- /successfully logged in(?![ \t]+to[ \t]+mcp)/i,
30
- /logged in successfully/i,
31
- /login successful/i,
32
- /successfully authenticated/i,
33
- /authentication successful/i,
34
- /signed in successfully/i,
35
- /signed in with your [a-z ]+ account/i,
36
- /you(?:'re| are) (?:now )?(?:logged|signed) in/i,
37
- ];
38
-
39
- // Rolling window kept per flow so a success line split across PTY chunks is
40
- // still matched, without buffering the whole login transcript.
41
28
  var OUTPUT_TAIL_MAX = 4096;
42
-
43
- // Grace period between detecting success and killing the terminal, so the user
44
- // actually sees the confirmation line before the modal closes.
45
29
  var CLOSE_AFTER_SUCCESS_MS = 2000;
46
-
47
- var ANSI_PATTERN = /\x1b\[[0-9;?]*[ -\/]*[@-~]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\r/g;
48
-
49
- function stripAnsi(text) {
50
- return String(text).replace(ANSI_PATTERN, "");
51
- }
52
-
53
- function looksLikeLoginSuccess(text) {
54
- for (var i = 0; i < LOGIN_SUCCESS_PATTERNS.length; i++) {
55
- if (LOGIN_SUCCESS_PATTERNS[i].test(text)) return true;
56
- }
57
- return false;
58
- }
30
+ var loginProtocol = require("./yoke/login-protocol");
31
+ var stripAnsi = loginProtocol.stripAnsi;
32
+ var looksLikeLoginSuccess = loginProtocol.successful;
59
33
 
60
34
  /**
61
35
  * ctx fields:
@@ -96,11 +70,7 @@ function attachVendorLogin(ctx) {
96
70
  cancel: function (vendor) { closeFlowTerminal(discardFlow(vendor)); broadcastState(); },
97
71
  });
98
72
 
99
- function loginCommandFor(vendor) {
100
- if (vendor === "claude") return "claude auth login";
101
- var info = vendorRegistry.getVendorInfo(vendor);
102
- return (info && info.loginCommand) || "claude login";
103
- }
73
+ function loginCommandFor(vendor) { return loginProtocol.command(vendor, false); }
104
74
 
105
75
  function vendorDisplayName(vendor) {
106
76
  var info = vendorRegistry.getVendorInfo(vendor);
@@ -284,8 +254,8 @@ function attachVendorLogin(ctx) {
284
254
  var key = flowKey(ws, vendor);
285
255
  var identity = resolveLoginIdentity(ws, msg);
286
256
  var command = loginCommandFor(vendor);
287
- var browserMode = msg.mode === "browser" && (vendor === "claude" || vendor === "codex");
288
- if (browserMode) command = vendor === "codex" ? "codex login" : "claude auth login";
257
+ var browserMode = msg.mode === "browser" && !!(vendorRegistry.getVendorInfo(vendor) || {}).browserLoginCommand;
258
+ if (browserMode) command = loginProtocol.command(vendor, true);
289
259
  var flow = {
290
260
  vendor: vendor,
291
261
  key: key,
@@ -31,6 +31,10 @@ function attachProjectWorkspaceQuery(ctx) {
31
31
  var controls = ctx.controlService.bind({ projectSlug: projectSlug, projectOwnerId: getProjectOwnerId(), isMate: true, mateId: mateId, session: session });
32
32
  if (controls) tools = tools.concat(productTools.getToolDefs(controls));
33
33
  }
34
+ if (bound && !bound.isClay && ctx.contextService) {
35
+ var context = ctx.contextService.bind({ projectSlug: projectSlug, projectOwnerId: getProjectOwnerId(), mateId: mateId, session: session });
36
+ if (context) tools = tools.concat(require('./mate-context-tools').getToolDefs(context));
37
+ }
34
38
  return tools;
35
39
  }
36
40
 
@@ -41,7 +45,7 @@ function attachProjectWorkspaceQuery(ctx) {
41
45
  var projectBound = binding(null);
42
46
  if (!projectBound) return null;
43
47
  if (!adapter || typeof adapter.createToolServer !== "function") return null;
44
- return adapter.createToolServer({ name: "clay-workspace", version: "2.0.0", tools: session ? getToolDefs(session) : workspaceMcp.getToolDefs(null, projectBound.isClay) });
48
+ return adapter.createToolServer({ name: "clay-workspace", version: "2.0.0", tools: session ? getToolDefs(session) : workspaceMcp.getToolDefs(null, projectBound.isClay).concat(!projectBound.isClay && ctx.contextService ? require('./mate-context-tools').getToolDefs(null) : []) });
45
49
  }
46
50
 
47
51
  function createHistoryMcpServer(adapter, session) {
@@ -169,6 +173,12 @@ function attachProjectWorkspaceQuery(ctx) {
169
173
  getHistoryDynamicToolDefs: getHistoryDynamicToolDefs,
170
174
  callBridgeTool: callBridgeTool,
171
175
  callHistoryBridgeTool: callHistoryBridgeTool,
176
+ getPresentation: function (session) {
177
+ try {
178
+ var bound = ctx.controlService && ctx.controlService.bind({ projectSlug: projectSlug, projectOwnerId: getProjectOwnerId(), isMate: isMate, mateId: mateId, session: session });
179
+ return bound && bound._presentation;
180
+ } catch (error) { return null; }
181
+ },
172
182
  getMemorySessions: getMemorySessions,
173
183
  };
174
184
  }
package/lib/project.js CHANGED
@@ -551,17 +551,18 @@ function createProjectContext(opts) {
551
551
  || !!(ws._clayUser && ws._clayUser.id === session.userKnowledgeOwnerId);
552
552
  }
553
553
 
554
- function sendToSession(sessionId, obj) {
555
- var data = JSON.stringify(obj);
554
+ function sendToSession(sessionId, obj, onSent) {
555
+ var data = JSON.stringify(obj), delivered = 0;
556
556
  for (var ws of clients) {
557
557
  if (ws.readyState === 1 && canReceiveMate(ws) && canReceiveYou(ws, sessionId) && ws._clayActiveSession === sessionId) {
558
- ws.send(data);
558
+ ws.send(data); delivered++; if (onSent) onSent(ws);
559
559
  }
560
560
  }
561
561
  homeDebateClients.forEach(function (ws) {
562
562
  if (ws.readyState !== 1) { homeDebateClients.delete(ws); return; }
563
- if (canReceiveMate(ws) && canReceiveYou(ws, sessionId) && !clients.has(ws) && ws._clayHomeDebateSlug === slug && ws._clayActiveSession === sessionId) ws.send(data);
563
+ if (canReceiveMate(ws) && canReceiveYou(ws, sessionId) && !clients.has(ws) && ws._clayHomeDebateSlug === slug && ws._clayActiveSession === sessionId) { ws.send(data); delivered++; if (onSent) onSent(ws); }
564
564
  });
565
+ return delivered;
565
566
  }
566
567
 
567
568
  function sendToSessionOthers(sender, sessionId, obj) {
@@ -791,7 +792,14 @@ function createProjectContext(opts) {
791
792
  }
792
793
  },
793
794
  });
795
+ var _studioPresentation = require('./project-studio-presentation').attachStudioPresentation({
796
+ getPresentation: function (session) { return _workspaceQuery && _workspaceQuery.getPresentation(session); },
797
+ getSessionForWs: getSessionForWs,
798
+ });
794
799
  var _sharedBrowser = attachSharedBrowser({
800
+ studioPresentation: _studioPresentation,
801
+ sendToSession: sendToSession,
802
+ getPresentation: function (session) { return _workspaceQuery && _workspaceQuery.getPresentation(session); },
795
803
  slug: slug, isMate: isMate, ownerId: projectOwnerId, sm: sm, clients: clients, sendTo: sendTo,
796
804
  usersModule: usersModule, requestAccess: _requestAccess, osUsers: osUsers,
797
805
  getSessionForWs: getSessionForWs,
@@ -801,6 +809,8 @@ function createProjectContext(opts) {
801
809
  },
802
810
  });
803
811
  var _sessionDocument = attachSessionDocument({
812
+ studioPresentation: _studioPresentation,
813
+ getPresentation: function (session) { return _workspaceQuery && _workspaceQuery.getPresentation(session); },
804
814
  cwd: cwd,
805
815
  isMate: isMate,
806
816
  canUseSession: _sessionNotes.canUseSession,
@@ -824,6 +834,7 @@ function createProjectContext(opts) {
824
834
  var _workspaceQuery = attachProjectWorkspaceQuery({
825
835
  service: opts.workspaceQueryService || null,
826
836
  controlService: opts.clayProductControl || null,
837
+ contextService: opts.mateContextService || null,
827
838
  sm: sm,
828
839
  projectSlug: slug,
829
840
  getProjectOwnerId: function () { return projectOwnerId; },
@@ -1837,8 +1848,10 @@ function createProjectContext(opts) {
1837
1848
  }
1838
1849
  }
1839
1850
 
1851
+ if (_studioPresentation.handleMessage(ws, msg)) return;
1852
+
1840
1853
  // --- DM messages (delegated to server-level handler) ---
1841
- if (msg.type === "home_mate_question_response" || msg.type === "clay_ui_ready" || msg.type === "clay_navigation_claim" || msg.type === "clay_navigation_ack" || msg.type === "home_debate_question_response" || msg.type === "home_debate_control" || msg.type === "home_mate_creation_question_response" || msg.type === "default_ai_get" || msg.type === "default_ai_catalog_get" || msg.type === "default_ai_set" || msg.type === "default_vendor_get" || msg.type === "default_vendor_set" || msg.type === "context_view_get" || msg.type === "context_view_set" || msg.type === "cursor_sharing_get" || msg.type === "cursor_sharing_set") {
1854
+ if (msg.type === "home_mate_question_response" || msg.type === "mate_context_prepare" || msg.type === "clay_ui_ready" || msg.type === "clay_navigation_claim" || msg.type === "clay_navigation_ack" || msg.type === "clay_navigation_rendered" || msg.type === "home_debate_question_response" || msg.type === "home_debate_control" || msg.type === "home_mate_creation_question_response" || msg.type === "default_ai_get" || msg.type === "default_ai_catalog_get" || msg.type === "default_ai_set" || msg.type === "default_vendor_get" || msg.type === "default_vendor_set" || msg.type === "context_view_get" || msg.type === "context_view_set" || msg.type === "cursor_sharing_get" || msg.type === "cursor_sharing_set") {
1842
1855
  if (typeof opts.onDmMessage === "function") opts.onDmMessage(ws, msg, slug);
1843
1856
  return;
1844
1857
  }
@@ -2323,6 +2336,7 @@ function createProjectContext(opts) {
2323
2336
  cwd: cwd,
2324
2337
  slug: slug,
2325
2338
  isMate: isMate,
2339
+ mateId: mateId,
2326
2340
  osUsers: osUsers,
2327
2341
  sm: sm,
2328
2342
  sdk: sdk,
@@ -2422,7 +2436,7 @@ function createProjectContext(opts) {
2422
2436
  cwd: cwd, isMate: isMate, osUsers: osUsers, users: usersModule, tm: tm,
2423
2437
  getAccess: opts.getProjectAccess || function () { return { ownerId: projectOwnerId || null, visibility: 'private' }; },
2424
2438
  files: function () { return _filesystem.productOperations; },
2425
- publishTerminals: function () { send({ type: 'term_list', terminals: tm.list() }); },
2439
+ publishTerminals: function () { send({ type: 'term_list', terminals: tm.list(true) }); },
2426
2440
  });
2427
2441
 
2428
2442
  // --- MCP bridge handler for Codex and session-bound Kiro tools ---
@@ -2696,6 +2710,8 @@ function createProjectContext(opts) {
2696
2710
 
2697
2711
  // --- HTTP handler (delegated to project-http.js) ---
2698
2712
  var _http = attachHTTP({
2713
+ workspaceOperations: _workspaceOperations,
2714
+ getProjectOwnerId: function () { return projectOwnerId; },
2699
2715
  requestAccess: _requestAccess,
2700
2716
  cwd: cwd,
2701
2717
  slug: slug,
@@ -2877,6 +2893,7 @@ function createProjectContext(opts) {
2877
2893
  }
2878
2894
  });
2879
2895
  var status = {
2896
+ controlRuntimeId: require('./clay-control-target-projection').runtime(sm),
2880
2897
  slug: slug,
2881
2898
  path: cwd,
2882
2899
  project: project,
@@ -2927,7 +2944,7 @@ function createProjectContext(opts) {
2927
2944
 
2928
2945
  function setTitle(newTitle) {
2929
2946
  title = newTitle || null;
2930
- send({ type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, osUsers: osUsers, lanHost: lanHost, projectCount: getProjectCount(), projects: getProjectList(), projectOwnerId: projectOwnerId, isMate: isMate, mateId: isMate ? mateId : null, mateDisplayName: isMate ? (opts.mateDisplayName || title || project || "Mate") : "" });
2947
+ send({ type: "info", controlRuntimeId: require("./clay-control-target-projection").runtime(sm), cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, osUsers: osUsers, lanHost: lanHost, projectCount: getProjectCount(), projects: getProjectList(), projectOwnerId: projectOwnerId, isMate: isMate, mateId: isMate ? mateId : null, mateDisplayName: isMate ? (opts.mateDisplayName || title || project || "Mate") : "" });
2931
2948
  }
2932
2949
 
2933
2950
  function setIcon(newIcon) {