clay-server 5.2.0-beta.3 → 5.2.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/lib/account-auth-socket.js +1 -0
  2. package/lib/builtin-mates.js +19 -16
  3. package/lib/bundled-skills/clay-agent-browser/SKILL.md +6 -2
  4. package/lib/canonical-clay-record-authority.js +15 -0
  5. package/lib/canonical-clay-scheduling-authority.js +19 -0
  6. package/lib/claude-hook-installer.js +28 -0
  7. package/lib/clay-mate-chat-receipts.js +26 -0
  8. package/lib/clay-mate-chat.js +87 -0
  9. package/lib/clay-mate-knowledge-input.js +26 -0
  10. package/lib/clay-mate-knowledge.js +95 -0
  11. package/lib/clay-mate-lifecycle.js +95 -0
  12. package/lib/clay-product-control-input.js +33 -0
  13. package/lib/clay-product-control-tools.js +51 -0
  14. package/lib/clay-product-control.js +237 -0
  15. package/lib/clay-project-lifecycle.js +154 -0
  16. package/lib/clay-project-record-input.js +44 -0
  17. package/lib/clay-project-records.js +83 -0
  18. package/lib/clay-scheduling-input.js +21 -0
  19. package/lib/clay-scheduling-receipts.js +26 -0
  20. package/lib/clay-scheduling.js +119 -0
  21. package/lib/clay-screen-controls.js +96 -0
  22. package/lib/clay-workspace-controls.js +83 -0
  23. package/lib/clay-workspace-input.js +17 -0
  24. package/lib/clay-workspace-receipts.js +26 -0
  25. package/lib/daemon-project-access.js +11 -0
  26. package/lib/daemon-project-lifecycle-callbacks.js +15 -0
  27. package/lib/daemon-project-lifecycle.js +262 -0
  28. package/lib/daemon-projects.js +1 -1
  29. package/lib/daemon.js +73 -221
  30. package/lib/issues-service.js +29 -15
  31. package/lib/knowledge-db-import.js +5 -4
  32. package/lib/knowledge-db-service.js +9 -6
  33. package/lib/knowledge-document-index.js +2 -1
  34. package/lib/knowledge-file-store.js +7 -1
  35. package/lib/knowledge-product-operations.js +46 -0
  36. package/lib/mate-avatar-catalog.js +12 -0
  37. package/lib/mate-avatar-migration.js +41 -0
  38. package/lib/mate-creation-mcp-server.js +2 -0
  39. package/lib/mate-knowledge-canonical-access.js +10 -0
  40. package/lib/mate-knowledge-service.js +7 -4
  41. package/lib/mate-lifecycle-service.js +94 -0
  42. package/lib/mate-message-delivery.js +43 -0
  43. package/lib/mate-ready-creation.js +9 -1
  44. package/lib/mate-schedule-input.js +29 -0
  45. package/lib/mate-schedule-lifecycle.js +151 -0
  46. package/lib/mate-scheduling.js +103 -0
  47. package/lib/mates.js +18 -6
  48. package/lib/mcp-agent-tools.js +117 -0
  49. package/lib/mcp-connection-store.js +119 -15
  50. package/lib/mcp-import-credentials.js +31 -0
  51. package/lib/mcp-local-transport.js +13 -0
  52. package/lib/mcp-oauth.js +8 -6
  53. package/lib/mcp-query-bridge.js +30 -0
  54. package/lib/mcp-remote-connections.js +74 -20
  55. package/lib/mcp-source-adapters.js +91 -0
  56. package/lib/mcp-source-context.js +42 -0
  57. package/lib/mcp-source-discovery.js +169 -0
  58. package/lib/mcp-source-plugins.js +68 -0
  59. package/lib/mcp-source-restrictions.js +47 -0
  60. package/lib/mcp-tool-policy.js +34 -0
  61. package/lib/notes-checklist.js +50 -0
  62. package/lib/notes.js +49 -3
  63. package/lib/os-users.js +20 -5
  64. package/lib/project-admin-overview.js +51 -0
  65. package/lib/project-ask-user.js +8 -4
  66. package/lib/project-debate.js +1 -0
  67. package/lib/project-delegated-follow-up.js +2 -1
  68. package/lib/project-directory-claim.js +65 -0
  69. package/lib/project-file-operations.js +61 -0
  70. package/lib/project-filesystem.js +13 -87
  71. package/lib/project-full-access.js +23 -2
  72. package/lib/project-knowledge.js +32 -18
  73. package/lib/project-lifecycle-operations.js +98 -0
  74. package/lib/project-lifecycle-process-witness.js +32 -0
  75. package/lib/project-lifecycle-runtime.js +50 -0
  76. package/lib/project-linux-access.js +22 -0
  77. package/lib/project-logs-canonical-clay.js +27 -0
  78. package/lib/project-logs-clay-query.js +16 -0
  79. package/lib/project-logs-schema.js +4 -2
  80. package/lib/project-logs-service.js +8 -7
  81. package/lib/project-logs.js +1 -0
  82. package/lib/project-loop.js +1 -0
  83. package/lib/project-mate-chat-control.js +55 -0
  84. package/lib/project-mate-creation-proposal.js +18 -3
  85. package/lib/project-mate-interaction.js +8 -0
  86. package/lib/project-mcp.js +54 -16
  87. package/lib/project-models.js +14 -31
  88. package/lib/project-notifications.js +74 -16
  89. package/lib/project-ownership.js +77 -0
  90. package/lib/project-pair-result-delivery.js +14 -3
  91. package/lib/project-pending-message-queue.js +1 -0
  92. package/lib/project-product-control.js +87 -0
  93. package/lib/project-product-session-queue.js +46 -0
  94. package/lib/project-push-access.js +28 -0
  95. package/lib/project-scheduled-message-queue.js +30 -0
  96. package/lib/project-scheduled-messages.js +63 -64
  97. package/lib/project-scheduled-task-control.js +83 -0
  98. package/lib/project-scheduled-tasks.js +13 -2
  99. package/lib/project-scheduling-access.js +10 -0
  100. package/lib/project-session-actions.js +65 -0
  101. package/lib/project-session-delete.js +8 -1
  102. package/lib/project-session-document.js +8 -3
  103. package/lib/project-session-github.js +63 -19
  104. package/lib/project-session-history-actions.js +165 -0
  105. package/lib/project-session-linear.js +14 -6
  106. package/lib/project-session-notes.js +7 -3
  107. package/lib/project-session-runtime-actions.js +63 -0
  108. package/lib/project-session-vendor.js +54 -0
  109. package/lib/project-sessions.js +68 -241
  110. package/lib/project-shared-browser.js +56 -12
  111. package/lib/project-user-message.js +30 -1
  112. package/lib/project-workspace-operations.js +126 -0
  113. package/lib/project-workspace-query.js +9 -2
  114. package/lib/project.js +144 -37
  115. package/lib/public/app.js +5 -0
  116. package/lib/public/appearance-bootstrap.js +14 -0
  117. package/lib/public/css/clay-tool-activity.css +51 -0
  118. package/lib/public/css/clay-workspace.css +29 -0
  119. package/lib/public/css/home-debate-planning.css +2 -0
  120. package/lib/public/css/home-mate-creation.css +5 -0
  121. package/lib/public/css/mate-avatar-gallery.css +12 -0
  122. package/lib/public/css/mate-conversation.css +10 -5
  123. package/lib/public/css/overlays.css +4 -2
  124. package/lib/public/css/project-admin-overview.css +13 -0
  125. package/lib/public/css/session-folders.css +11 -0
  126. package/lib/public/css/session-github.css +46 -1
  127. package/lib/public/css/shared-browser.css +35 -1
  128. package/lib/public/css/sticky-notes.css +61 -1
  129. package/lib/public/index.html +11 -5
  130. package/lib/public/mate-avatars/B01.png +0 -0
  131. package/lib/public/mate-avatars/B02.png +0 -0
  132. package/lib/public/mate-avatars/B03.png +0 -0
  133. package/lib/public/mate-avatars/B04.png +0 -0
  134. package/lib/public/mate-avatars/B05.png +0 -0
  135. package/lib/public/mate-avatars/B06.png +0 -0
  136. package/lib/public/mate-avatars/B07.png +0 -0
  137. package/lib/public/mate-avatars/B08.png +0 -0
  138. package/lib/public/mate-avatars/B09.png +0 -0
  139. package/lib/public/mate-avatars/B10.png +0 -0
  140. package/lib/public/mate-avatars/B11.png +0 -0
  141. package/lib/public/mate-avatars/B12.png +0 -0
  142. package/lib/public/mate-avatars/B13.png +0 -0
  143. package/lib/public/mate-avatars/B14.png +0 -0
  144. package/lib/public/mate-avatars/B15.png +0 -0
  145. package/lib/public/mate-avatars/B16.png +0 -0
  146. package/lib/public/mate-avatars/B17.png +0 -0
  147. package/lib/public/mate-avatars/B18.png +0 -0
  148. package/lib/public/mate-avatars/B19.png +0 -0
  149. package/lib/public/mate-avatars/B20.png +0 -0
  150. package/lib/public/mate-avatars/B21.png +0 -0
  151. package/lib/public/mate-avatars/B22.png +0 -0
  152. package/lib/public/mate-avatars/B23.png +0 -0
  153. package/lib/public/mate-avatars/B24.png +0 -0
  154. package/lib/public/mate-avatars/B25.png +0 -0
  155. package/lib/public/mate-avatars/B26.png +0 -0
  156. package/lib/public/mate-avatars/B27.png +0 -0
  157. package/lib/public/mate-avatars/B28.png +0 -0
  158. package/lib/public/mate-avatars/B29.png +0 -0
  159. package/lib/public/mate-avatars/B30.png +0 -0
  160. package/lib/public/mate-avatars/B31.png +0 -0
  161. package/lib/public/mate-avatars/B32.png +0 -0
  162. package/lib/public/mate-avatars/B33.png +0 -0
  163. package/lib/public/mate-avatars/B34.png +0 -0
  164. package/lib/public/mate-avatars/B35.png +0 -0
  165. package/lib/public/mate-avatars/B36.png +0 -0
  166. package/lib/public/mate-avatars/B37.png +0 -0
  167. package/lib/public/mate-avatars/B38.png +0 -0
  168. package/lib/public/mate-avatars/B39.png +0 -0
  169. package/lib/public/mate-avatars/B40.png +0 -0
  170. package/lib/public/mate-avatars/B41.png +0 -0
  171. package/lib/public/mate-avatars/B42.png +0 -0
  172. package/lib/public/mate-avatars/B43.png +0 -0
  173. package/lib/public/mate-avatars/B44.png +0 -0
  174. package/lib/public/mate-avatars/B45.png +0 -0
  175. package/lib/public/mate-avatars/B46.png +0 -0
  176. package/lib/public/mate-avatars/B47.png +0 -0
  177. package/lib/public/mate-avatars/B48.png +0 -0
  178. package/lib/public/mate-avatars/C01.png +0 -0
  179. package/lib/public/mate-avatars/C02.png +0 -0
  180. package/lib/public/mate-avatars/C03.png +0 -0
  181. package/lib/public/mate-avatars/C04.png +0 -0
  182. package/lib/public/mate-avatars/C05.png +0 -0
  183. package/lib/public/mate-avatars/C06.png +0 -0
  184. package/lib/public/mate-avatars/C07.png +0 -0
  185. package/lib/public/mate-avatars/C08.png +0 -0
  186. package/lib/public/mate-avatars/C09.png +0 -0
  187. package/lib/public/mate-avatars/C10.png +0 -0
  188. package/lib/public/mate-avatars/C11.png +0 -0
  189. package/lib/public/mate-avatars/C12.png +0 -0
  190. package/lib/public/mate-avatars/C13.png +0 -0
  191. package/lib/public/mate-avatars/C14.png +0 -0
  192. package/lib/public/mate-avatars/C15.png +0 -0
  193. package/lib/public/mate-avatars/C16.png +0 -0
  194. package/lib/public/mate-avatars/C17.png +0 -0
  195. package/lib/public/mate-avatars/C18.png +0 -0
  196. package/lib/public/mate-avatars/C19.png +0 -0
  197. package/lib/public/mate-avatars/C20.png +0 -0
  198. package/lib/public/mate-avatars/C21.png +0 -0
  199. package/lib/public/mate-avatars/C22.png +0 -0
  200. package/lib/public/mate-avatars/C23.png +0 -0
  201. package/lib/public/mate-avatars/C24.png +0 -0
  202. package/lib/public/mate-avatars/C25.png +0 -0
  203. package/lib/public/mate-avatars/C26.png +0 -0
  204. package/lib/public/mate-avatars/C27.png +0 -0
  205. package/lib/public/mate-avatars/C28.png +0 -0
  206. package/lib/public/mate-avatars/C29.png +0 -0
  207. package/lib/public/mate-avatars/C30.png +0 -0
  208. package/lib/public/mate-avatars/C31.png +0 -0
  209. package/lib/public/mate-avatars/C32.png +0 -0
  210. package/lib/public/mate-avatars/C33.png +0 -0
  211. package/lib/public/mate-avatars/C34.png +0 -0
  212. package/lib/public/mate-avatars/C35.png +0 -0
  213. package/lib/public/mate-avatars/C36.png +0 -0
  214. package/lib/public/mate-avatars/C37.png +0 -0
  215. package/lib/public/mate-avatars/C38.png +0 -0
  216. package/lib/public/mate-avatars/C39.png +0 -0
  217. package/lib/public/mate-avatars/C40.png +0 -0
  218. package/lib/public/mate-avatars/C41.png +0 -0
  219. package/lib/public/mate-avatars/C42.png +0 -0
  220. package/lib/public/mate-avatars/C43.png +0 -0
  221. package/lib/public/mate-avatars/C44.png +0 -0
  222. package/lib/public/mate-avatars/C45.png +0 -0
  223. package/lib/public/mate-avatars/C46.png +0 -0
  224. package/lib/public/mate-avatars/C47.png +0 -0
  225. package/lib/public/mate-avatars/C48.png +0 -0
  226. package/lib/public/mate-avatars/catalog.json +482 -0
  227. package/lib/public/mate-avatars/lead-clay.png +0 -0
  228. package/lib/public/modules/admin-settings-projects.js +6 -2
  229. package/lib/public/modules/app-connection.js +8 -1
  230. package/lib/public/modules/app-message-router.js +4 -0
  231. package/lib/public/modules/app-messages.js +31 -5
  232. package/lib/public/modules/app-projects.js +5 -0
  233. package/lib/public/modules/app-rendering.js +1 -1
  234. package/lib/public/modules/avatar.js +15 -41
  235. package/lib/public/modules/chat-render-runtime.js +10 -1
  236. package/lib/public/modules/clay-browser-links.js +47 -0
  237. package/lib/public/modules/clay-log-links.js +1 -1
  238. package/lib/public/modules/clay-owner-navigation.js +27 -0
  239. package/lib/public/modules/clay-tool-activity.js +140 -0
  240. package/lib/public/modules/clay-tool-presentation.js +338 -0
  241. package/lib/public/modules/command-palette.js +2 -2
  242. package/lib/public/modules/debate.js +1 -0
  243. package/lib/public/modules/default-ai.js +11 -3
  244. package/lib/public/modules/history-replay-batch.js +5 -1
  245. package/lib/public/modules/home-debate-live.js +2 -0
  246. package/lib/public/modules/home-debate-planning.js +7 -7
  247. package/lib/public/modules/home-mate-avatar-editor.js +127 -208
  248. package/lib/public/modules/home-mate-chat.js +8 -6
  249. package/lib/public/modules/home-mate-creation.js +73 -1
  250. package/lib/public/modules/home-mate-settings.js +19 -12
  251. package/lib/public/modules/markdown.js +4 -0
  252. package/lib/public/modules/mate-avatar-catalog.js +97 -0
  253. package/lib/public/modules/mate-management.js +40 -7
  254. package/lib/public/modules/mcp-connection-view.js +31 -13
  255. package/lib/public/modules/mcp-discovery-view.js +31 -0
  256. package/lib/public/modules/mcp-ui.js +23 -4
  257. package/lib/public/modules/mention.js +3 -0
  258. package/lib/public/modules/pane-browser-bridge.js +20 -0
  259. package/lib/public/modules/personal-settings-dialog.js +7 -1
  260. package/lib/public/modules/profile.js +1 -238
  261. package/lib/public/modules/project-access-recovery.js +17 -0
  262. package/lib/public/modules/project-admin-overview.js +21 -0
  263. package/lib/public/modules/project-mate-navigation.js +99 -14
  264. package/lib/public/modules/search-clay-chat.js +16 -2
  265. package/lib/public/modules/session-folder-layout.js +1 -1
  266. package/lib/public/modules/session-folder-view-menu.js +9 -0
  267. package/lib/public/modules/session-github.js +85 -14
  268. package/lib/public/modules/session-work-primary.js +51 -0
  269. package/lib/public/modules/shared-browser-settings.js +112 -0
  270. package/lib/public/modules/shared-browser-tabs.js +13 -3
  271. package/lib/public/modules/shared-browser.js +27 -6
  272. package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
  273. package/lib/public/modules/split-view.js +2 -0
  274. package/lib/public/modules/sticky-note-markdown.js +72 -6
  275. package/lib/public/modules/sticky-notes-actions.js +30 -0
  276. package/lib/public/modules/sticky-notes-browser.js +9 -2
  277. package/lib/public/modules/sticky-notes-card.js +23 -7
  278. package/lib/public/modules/sticky-notes-checklist.js +71 -0
  279. package/lib/public/modules/sticky-notes-editor.js +108 -44
  280. package/lib/public/modules/sticky-notes-shared.js +50 -2
  281. package/lib/public/modules/sticky-notes-state.js +66 -0
  282. package/lib/public/modules/sticky-notes.js +34 -51
  283. package/lib/public/modules/theme.js +8 -1
  284. package/lib/public/modules/thinking-lifecycle.js +33 -3
  285. package/lib/public/modules/thinking-view.js +23 -1
  286. package/lib/public/modules/tools.js +36 -7
  287. package/lib/public/modules/user-settings-appearance.js +10 -3
  288. package/lib/public/modules/user-settings-editors.js +1 -1
  289. package/lib/public/modules/user-settings-layout.js +11 -2
  290. package/lib/public/modules/user-settings-preferences.js +6 -2
  291. package/lib/public/modules/user-settings-profile.js +12 -2
  292. package/lib/public/modules/user-settings.js +3 -2
  293. package/lib/public/style.css +3 -0
  294. package/lib/public/sw.js +3 -1
  295. package/lib/scheduled-task-records.js +3 -1
  296. package/lib/scheduler.js +2 -0
  297. package/lib/sdk-bridge.js +228 -23
  298. package/lib/sdk-message-processor.js +2 -0
  299. package/lib/sdk-response-recovery.js +155 -0
  300. package/lib/server-admin.js +63 -16
  301. package/lib/server-appearance-settings.js +13 -1
  302. package/lib/server-dm.js +2 -1
  303. package/lib/server-global-ws.js +10 -20
  304. package/lib/server-home-chat-events.js +18 -4
  305. package/lib/server-home-chat.js +12 -16
  306. package/lib/server-home-mate-creation.js +1 -0
  307. package/lib/server-home-mcp-consent.js +14 -0
  308. package/lib/server-mates.js +25 -62
  309. package/lib/server-owner-ui.js +77 -0
  310. package/lib/server-path-access.js +38 -0
  311. package/lib/server-project-removal.js +22 -0
  312. package/lib/server-settings.js +4 -1
  313. package/lib/server.js +144 -41
  314. package/lib/session-config-transition.js +45 -0
  315. package/lib/session-folders.js +7 -2
  316. package/lib/session-github.js +9 -2
  317. package/lib/session-visibility.js +5 -2
  318. package/lib/session-work-primary.js +50 -0
  319. package/lib/sessions.js +13 -0
  320. package/lib/shared-browser-cookie-sync.js +32 -0
  321. package/lib/shared-browser-cookies.js +79 -0
  322. package/lib/shared-browser-runtime.js +25 -9
  323. package/lib/shared-browser-storage-sync.js +43 -0
  324. package/lib/shared-browser-storage.js +101 -0
  325. package/lib/shared-browser-tools.js +2 -1
  326. package/lib/shared-browser-worker.js +46 -10
  327. package/lib/terminal-manager.js +52 -0
  328. package/lib/users-permissions.js +2 -8
  329. package/lib/users.js +4 -0
  330. package/lib/workspace-query-access.js +4 -1
  331. package/lib/ws-schema.js +8 -2
  332. package/lib/yoke/acp-agent-profiles.js +2 -2
  333. package/lib/yoke/adapters/claude.js +47 -8
  334. package/lib/yoke/adapters/codex.js +52 -111
  335. package/lib/yoke/claude-worker-startup.js +28 -0
  336. package/lib/yoke/codex-idle-recovery.js +81 -1
  337. package/lib/yoke/codex-recovery-admission.js +164 -0
  338. package/lib/yoke/session-tool-transport.js +3 -0
  339. package/package.json +5 -1
@@ -5,12 +5,14 @@ var browserTools = require("./shared-browser-tools");
5
5
  var createRuntime = require("./shared-browser-runtime").createRuntime;
6
6
 
7
7
  function attachSharedBrowser(ctx) {
8
+ var storageStore = ctx.storageStore || require("./shared-browser-storage").attachStorageStore(require("./config").CONFIG_DIR);
8
9
  var records = new Map();
9
10
  var viewers = new Map();
10
11
  var destroyed = false;
11
12
  function allowed(session, ws) {
12
13
  try {
13
- if (ctx.isMate || !session || ctx.sm.sessions.get(session.localId) !== session) return false;
14
+ if (!session || ctx.sm.sessions.get(session.localId) !== session) return false;
15
+ if (ctx.isMate && ctx.usersModule.isMultiUser() && (!ctx.ownerId || session.ownerId !== ctx.ownerId)) return false;
14
16
  if (ctx.usersModule.isMultiUser()) {
15
17
  var user = ctx.usersModule.findUserById(session.ownerId);
16
18
  if (!user || (ws && (!ws._clayUser || String(ws._clayUser.id) !== String(user.id)))) return false;
@@ -35,7 +37,7 @@ function attachSharedBrowser(ctx) {
35
37
  function projection(record) {
36
38
  if (!record) return null;
37
39
  return { id: record.id, phase: record.phase, control: record.control, epoch: record.epoch, handoff: !!record.handoff,
38
- url: record.url, width: record.width, height: record.height, activity: record.activity || null, activityHistory: record.activityHistory || [], pointer: record.pointer || null, error: record.error || "" };
40
+ browserLink: record.url ? "browser:" + record.url : null, incognito: record.incognito, personalCookiesAvailable: !!ctx.isMate, url: record.url, width: record.width, height: record.height, activity: record.activity || null, activityHistory: record.activityHistory || [], pointer: record.pointer || null, error: record.error || "" };
39
41
  }
40
42
  function sessionRecords(session) {
41
43
  return Array.from(records.values()).filter(function (record) { return record.session === session && recordAllowed(record); });
@@ -93,6 +95,13 @@ function attachSharedBrowser(ctx) {
93
95
  if ((record.activity || record.pointer) && record.activityGeneration !== Number(record.session._sdkQueryGeneration || 0)) {
94
96
  activityHistory.interruptActivity(record); record.pointer = null; broadcast(record);
95
97
  }
98
+ if (message.type === "navigation") {
99
+ try { record.navigationUrl = policy.browserUrl(message.navigationUrl || message.url); } catch (error) { record.navigationUrl = null; }
100
+ }
101
+ if (message.type === "navigation" && !record.incognito) {
102
+ try { storageStore.visit(record.cookieOwner, message.url); }
103
+ catch (error) { end(record, "Could not save personal browsing history"); return; }
104
+ }
96
105
  if (message.type === "ready") { record.phase = "live"; record.resolveReady(); }
97
106
  if (message.type === "error") { end(record, message.error); return; }
98
107
  if (message.url !== undefined) record.url = policy.visibleUrl(message.url);
@@ -108,10 +117,12 @@ function attachSharedBrowser(ctx) {
108
117
  }
109
118
  // Every new tab starts shared. Exclusive human mode is explicit, per tab, and only the human clears it,
110
119
  // so an agent open never reuses (or bypasses) an exclusive tab.
111
- function create(session, actor, newTab) {
120
+ function create(session, actor, newTab, incognito) {
112
121
  requireAccess(session);
122
+ if (incognito !== undefined && typeof incognito !== "boolean") throw new Error("Invalid incognito mode");
123
+ var temporary = !ctx.isMate || incognito === true;
113
124
  var tabs = sessionRecords(session);
114
- var previous = !newTab && tabs.find(function (record) { return record.runtime && (actor === "user" || record.control === "agent"); });
125
+ var previous = !newTab && tabs.find(function (record) { return record.runtime && (incognito === undefined || record.incognito === temporary) && (actor === "user" || record.control === "agent"); });
115
126
  if (previous) return previous;
116
127
  if (tabs.length >= 12) {
117
128
  var expired = tabs.find(function (record) { return !record.runtime; });
@@ -122,12 +133,15 @@ function attachSharedBrowser(ctx) {
122
133
  records.forEach(function (record) { if (record.runtime && record.ownerId === session.ownerId) active++; });
123
134
  if (active >= 4) throw new Error("End another browser before opening more than four active tabs");
124
135
  var record = { session: session, ownerId: session.ownerId, identity: identityKey(session), id: crypto.randomUUID(), phase: "starting", control: "agent", epoch: 0,
125
- url: "", width: 1280, height: 800, touched: Date.now(), runtime: null, frame: null };
136
+ cookieOwner: ctx.isMate ? (ctx.ownerId || session.ownerId || "default") : null, incognito: temporary, url: "", width: 1280, height: 800, touched: Date.now(), runtime: null, frame: null };
126
137
  record.ready = new Promise(function (resolve, reject) { record.resolveReady = resolve; record.rejectReady = reject; });
127
138
  record.ready.catch(function () {});
128
139
  records.set(record.id, record);
129
140
  try {
130
- record.runtime = (ctx.createRuntime || createRuntime)({ identity: ctx.getIdentity(session), onEvent: function (message) { receive(record, message); } });
141
+ record.runtime = (ctx.createRuntime || createRuntime)({ identity: ctx.getIdentity(session), storage: temporary ? null : {
142
+ read: function () { if (!recordAllowed(record)) throw new Error("Browser access was revoked"); return storageStore.read(record.cookieOwner); },
143
+ exchange: function (previous, current) { if (!recordAllowed(record)) throw new Error("Browser access was revoked"); return storageStore.exchange(record.cookieOwner, previous, current); },
144
+ }, onEvent: function (message) { receive(record, message); } });
131
145
  } catch (e) { end(record, e.message); }
132
146
  broadcast(record, true);
133
147
  return record;
@@ -186,6 +200,7 @@ function attachSharedBrowser(ctx) {
186
200
  requireAccess(session);
187
201
  if (actor === "agent" && Number(session._sdkQueryGeneration || 0) !== generation) throw new Error("Browser result belongs to an older query");
188
202
  if (!recordAllowed(record) || records.get(record.id) !== record || record.epoch !== epoch) throw new Error("Browser control changed during the action");
203
+ if (result && result.url !== undefined) record.url = policy.visibleUrl(result.url);
189
204
  return result;
190
205
  }
191
206
  async function tool(session, args) {
@@ -196,8 +211,10 @@ function attachSharedBrowser(ctx) {
196
211
  var value;
197
212
  if (args.action === "status") value = { browsers: sessionRecords(session).map(projection) };
198
213
  else if (args.action === "open") {
214
+ if (args.incognito !== undefined && typeof args.incognito !== "boolean") throw new Error("Invalid incognito mode");
199
215
  var url = args.url ? policy.browserUrl(args.url) : null;
200
- record = args.browserId ? selectRecord(session, args.browserId) : create(session, "agent", args.newTab === true);
216
+ record = args.browserId ? selectRecord(session, args.browserId) : create(session, "agent", args.newTab === true, args.incognito);
217
+ 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.");
201
218
  if (url) await run(session, record, "agent", "action", { kind: "navigate", url: url }, args.intent);
202
219
  value = projection(record);
203
220
  } else if (args.action === "finish") {
@@ -206,14 +223,27 @@ function attachSharedBrowser(ctx) {
206
223
  } else if (args.action === "inspect") {
207
224
  value = await run(session, record, "agent", "inspect", null, args.intent);
208
225
  value.browserId = record.id;
226
+ value.browserLink = record.url ? "browser:" + record.url : null;
209
227
  var image = value.image; delete value.image;
210
228
  return { content: [{ type: "text", text: JSON.stringify(value) }, { type: "image", mimeType: "image/jpeg", data: image }] };
211
229
  } else {
212
230
  value = await run(session, record, "agent", "action", policy.validateAction(Object.assign({}, args, { kind: args.action })), args.intent);
213
231
  }
232
+ if (value && value.url) value.browserLink = "browser:" + policy.visibleUrl(value.url);
214
233
  return { content: [{ type: "text", text: JSON.stringify(value) }] };
215
234
  } catch (e) { return { isError: true, content: [{ type: "text", text: e.message }] }; }
216
235
  }
236
+ async function openLinkedPage(session, url, incognito) {
237
+ var record = sessionRecords(session).find(function (tab) {
238
+ return tab.runtime && ((tab.phase === "live" && (tab.navigationUrl || tab.url) === url) || (tab.linkOpening && tab.linkOpening.url === url));
239
+ });
240
+ if (record) { if (record.linkOpening) await record.linkOpening.promise; return record; }
241
+ record = create(session, "user", true, incognito);
242
+ record.navigationUrl = url;
243
+ record.linkOpening = { url: url, promise: run(session, record, "user", "action", { kind: "navigate", url: url }) };
244
+ try { await record.linkOpening.promise; } finally { record.linkOpening = null; }
245
+ return record;
246
+ }
217
247
  function handleMessage(ws, msg) {
218
248
  if (!msg.type || msg.type.indexOf("shared_browser_") !== 0) return false;
219
249
  var session = ctx.getSessionForWs(ws);
@@ -221,8 +251,22 @@ function attachSharedBrowser(ctx) {
221
251
  requireAccess(session, ws);
222
252
  if (String(msg.sessionId) !== String(session.localId)) throw new Error("Browser session changed");
223
253
  var record;
254
+ if (msg.type === "shared_browser_settings") {
255
+ if (!ctx.isMate) throw new Error("Personal browser settings are available only in Mates");
256
+ if (typeof msg.requestId !== "string" || msg.requestId.length > 100) throw new Error("Invalid browser settings request");
257
+ var owner = ctx.ownerId || session.ownerId || "default";
258
+ var settings = storageStore.manage(owner, msg.operation, msg.target);
259
+ requireAccess(session, ws);
260
+ ctx.sendTo(ws, { type: "shared_browser_settings", sessionId: session.localId, requestId: msg.requestId, settings: settings });
261
+ return;
262
+ }
224
263
  if (msg.type === "shared_browser_state_request") { sendState(ws, session, sessionRecords(session)[0]); return; }
225
- if (msg.type === "shared_browser_open") { record = create(session, "user", msg.newTab === true); sendState(ws, session, record, true, true); return; }
264
+ if (msg.type === "shared_browser_open") {
265
+ var targetUrl = msg.url === undefined ? null : policy.browserUrl(msg.url);
266
+ if (targetUrl) record = await openLinkedPage(session, targetUrl, msg.incognito);
267
+ else record = create(session, "user", msg.newTab === true, msg.incognito);
268
+ sendState(ws, session, record, true, true); return;
269
+ }
226
270
  record = selectRecord(session, msg.browserId);
227
271
  if (!record || !recordAllowed(record, ws) || msg.browserId !== record.id) throw new Error("Browser session expired");
228
272
  if (msg.type === "shared_browser_view") {
@@ -242,7 +286,7 @@ function attachSharedBrowser(ctx) {
242
286
  await run(session, record, "user", "action", policy.validateAction(msg.event));
243
287
  }
244
288
  }).catch(function (e) {
245
- if (ws.readyState === 1) ctx.sendTo(ws, { type: "shared_browser_error", sessionId: msg.sessionId, error: e.message });
289
+ if (ws.readyState === 1) ctx.sendTo(ws, { type: "shared_browser_error", sessionId: msg.sessionId, error: e.message, requestId: msg.requestId });
246
290
  });
247
291
  return true;
248
292
  }
@@ -257,7 +301,7 @@ function attachSharedBrowser(ctx) {
257
301
  sweep.unref();
258
302
  function getToolDefs(session) {
259
303
  var generation = Number(session && session._sdkQueryGeneration || 0);
260
- return ctx.isMate ? [] : browserTools.getToolDefs(function (args) {
304
+ return browserTools.getToolDefs(function (args) {
261
305
  if (session && Number(session._sdkQueryGeneration || 0) !== generation) {
262
306
  return Promise.resolve({ isError: true, content: [{ type: "text", text: "This browser tool belongs to an older query" }] });
263
307
  }
@@ -267,8 +311,8 @@ function attachSharedBrowser(ctx) {
267
311
  return {
268
312
  handleMessage: handleMessage,
269
313
  getToolDefs: getToolDefs,
270
- getSystemPrompt: function () { return ctx.isMate ? "" : browserTools.PROMPT; },
271
- createMcpServer: function (adapter, session) { return ctx.isMate || !adapter || typeof adapter.createToolServer !== "function" ? null : adapter.createToolServer({ name: "clay-shared-browser", version: "1.0.0", tools: getToolDefs(session) }); },
314
+ getSystemPrompt: function () { return browserTools.PROMPT; },
315
+ createMcpServer: function (adapter, session) { return !adapter || typeof adapter.createToolServer !== "function" ? null : adapter.createToolServer({ name: "clay-shared-browser", version: "1.0.0", tools: getToolDefs(session) }); },
272
316
  destroy: function () { destroyed = true; clearInterval(sweep); records.forEach(function (record) { end(record); }); records.clear(); viewers.clear(); },
273
317
  };
274
318
  }
@@ -118,6 +118,12 @@ function attachUserMessage(ctx) {
118
118
  requestId: msg.requestId || null, ok: !!ok, error: error || null });
119
119
  }
120
120
 
121
+ function noteUpdateResult(ws, msg, ok, note, error) {
122
+ if (!msg.requestId) return;
123
+ sendTo(ws, { type: "note_update_result", projectSlug: slug, noteId: msg.id || null,
124
+ requestId: msg.requestId, ok: !!ok, note: note || null, error: error || null });
125
+ }
126
+
121
127
  // --------------- Main handler ---------------
122
128
 
123
129
  function handleUserMessage(ws, msg, forcedSession, internal) {
@@ -155,7 +161,10 @@ function attachUserMessage(ctx) {
155
161
  }
156
162
 
157
163
  if (msg.type === "note_update") {
158
- if (!msg.id) return true;
164
+ if (!msg.id) {
165
+ noteUpdateResult(ws, msg, false, null, "Note id is required");
166
+ return true;
167
+ }
159
168
  // A legacy `hidden` update is a lifecycle transition, so the actor is
160
169
  // derived from the bound socket rather than taken from the message.
161
170
  var noteChanges = msg;
@@ -166,10 +175,24 @@ function attachUserMessage(ctx) {
166
175
  if (updated) {
167
176
  send({ type: "note_updated", note: updated });
168
177
  if (msg.text !== undefined || msg.hidden !== undefined) syncNotesKnowledge();
178
+ noteUpdateResult(ws, msg, true, updated, null);
179
+ } else {
180
+ noteUpdateResult(ws, msg, false, null, "Note update could not be saved");
169
181
  }
170
182
  return true;
171
183
  }
172
184
 
185
+ if (msg.type === "note_close" && msg.humanClose === true) {
186
+ if (!canHumanManageNotes(ws)) { noteUpdateResult(ws, msg, false, null, "You are not authorized to close notes in this project."); return true; }
187
+ var result = nm.closeHuman(msg.id, msg, notesLifecycle.userActor(ws && ws._clayUser));
188
+ if (!result) { noteUpdateResult(ws, msg, false, null, "The note changed or could not be saved. Review it and retry."); return true; }
189
+ if (result.removed) send({ type: "note_deleted", id: result.removed.id });
190
+ else send({ type: "note_updated", note: result.note });
191
+ noteUpdateResult(ws, msg, true, result.note || null, null);
192
+ syncNotesKnowledge();
193
+ return true;
194
+ }
195
+
173
196
  // Completing a note closes it. `note_delete` is the retired spelling and is
174
197
  // deliberately routed to the same non-destructive close, so an older client
175
198
  // still on the wire cannot erase a record.
@@ -443,6 +466,10 @@ function attachUserMessage(ctx) {
443
466
  finishPendingEarly(false, "Pending messages cannot be delivered to a Driver-operated Split Worker");
444
467
  return true;
445
468
  }
469
+ if (!pendingInternal && ctx.resumeLifecycleProject) {
470
+ try { ctx.resumeLifecycleProject(ws); }
471
+ catch (error) { sendTo(ws, { type: "error", text: error.message }); return true; }
472
+ }
446
473
  if (!pendingInternal && session._driverContinuationLease && pendingMessageQueue) {
447
474
  var continuationQueued = pendingMessageQueue.admit(session, msg, ws && ws._clayUser);
448
475
  if (continuationQueued.ok) {
@@ -719,6 +746,7 @@ function attachUserMessage(ctx) {
719
746
 
720
747
  function pendingBlockReason() {
721
748
  if (!pendingInternal) return null;
749
+ if (ctx.isLifecycleExecutionPaused && ctx.isLifecycleExecutionPaused()) return "Pending message dispatch paused";
722
750
  if (!pendingMessageQueue.isClaimed(session, pendingInternal.item.id)) return "Pending message was cancelled";
723
751
  if (pendingMessageQueue.isPaused(session) || session._pendingMessageDrainPaused || session.taskStopRequested) return "Pending message dispatch paused";
724
752
  if (typeof ctx.authorizePendingDispatch !== "function" || !ctx.authorizePendingDispatch(session, ws && ws._clayUser, pendingInternal.item)) return "Pending message authorization changed";
@@ -941,6 +969,7 @@ function attachUserMessage(ctx) {
941
969
  }
942
970
 
943
971
  function consumePendingMessage(session) {
972
+ if (ctx.isLifecycleExecutionPaused && ctx.isLifecycleExecutionPaused()) return false;
944
973
  if (!pendingMessageQueue) return false;
945
974
  var consumed = pendingMessageQueue.consumeOne(session, function (item, complete) {
946
975
  var actor = item.actorId && usersModule.findUserById(item.actorId);
@@ -0,0 +1,126 @@
1
+ var fs = require('fs'), path = require('path'), crypto = require('crypto');
2
+ var git = require('./git-cli');
3
+ function digest(value) { return crypto.createHash('sha256').update(typeof value === 'string' || Buffer.isBuffer(value) ? value : JSON.stringify(value)).digest('hex'); }
4
+ function inode(file) { var s = fs.statSync(file); return [s.dev, s.ino, s.mode, s.uid, s.gid]; }
5
+ function attachWorkspaceOperations(ctx) {
6
+ var busy = false;
7
+ function authorize(principal, domain) {
8
+ var record = ctx.getAccess(), user = principal.userId && ctx.users.findUserById(principal.userId);
9
+ if (ctx.isMate || !record || record.lifecycleExecutionPaused || ctx.users.isMultiUser() && (!user || record.ownerId !== user.id || !ctx.users.canAccessProject(user.id, record))) throw new Error('Live selected-project ownership or access is unavailable.');
10
+ if (!ctx.users.isMultiUser() && record.ownerId && record.ownerId !== principal.singleUserOwnerId) throw new Error('Selected-project ownership changed.');
11
+ if (user && ctx.users.getEffectivePermissions(user, ctx.osUsers)[domain === 'terminal' ? 'terminal' : 'fileBrowser'] !== true) throw new Error('The account does not have permission for this product operation.');
12
+ var identity = null;
13
+ if (ctx.osUsers) {
14
+ if (!user || !user.linuxUser) throw new Error('A live mapped OS identity is required.');
15
+ identity = require('./os-users').resolveOsUserInfo(user.linuxUser);
16
+ if (!identity || !Number.isInteger(identity.uid) || identity.uid < 0 || !Number.isInteger(identity.gid) || identity.gid < 0 || identity.user !== user.linuxUser) throw new Error('The mapped OS identity is unavailable.');
17
+ }
18
+ return { identity: identity, projectBound: true, ownerId: user ? user.id : null };
19
+ }
20
+ function rootIdentity(principal, domain) {
21
+ var scope = authorize(principal, domain), root = fs.realpathSync(ctx.cwd);
22
+ return digest([root, inode(root), scope.ownerId, scope.identity && [scope.identity.user, scope.identity.uid, scope.identity.gid, scope.identity.home]]);
23
+ }
24
+ function bounded(requested, missing) {
25
+ if (typeof requested !== 'string' || !requested || path.isAbsolute(requested) || /[\u0000-\u001f\u007f]/.test(requested) || requested.split(/[\\/]/).some(function (part) { return part === '..' || ['.git', '.clay', '.claude', '.codex', '.envrc'].indexOf(part) >= 0; })) throw new Error('Use an in-project relative path outside managed settings and Git metadata.');
26
+ var root = fs.realpathSync(ctx.cwd), resolved = root;
27
+ var parts = requested.split('/').filter(function (part) { return part && part !== '.'; });
28
+ for (var i = 0; i < parts.length; i++) {
29
+ resolved = path.join(resolved, parts[i]);
30
+ var stat;
31
+ try { stat = fs.lstatSync(resolved); } catch (error) { if (missing && error.code === 'ENOENT' && i === parts.length - 1) return resolved; throw error; }
32
+ if (stat.isSymbolicLink() || stat.isFile() && stat.nlink !== 1) throw new Error('Symlinks and multiply linked files are unavailable in canonical project controls.');
33
+ }
34
+ if (path.relative(root, resolved).split(path.sep).indexOf('..') >= 0) throw new Error('The canonical project boundary changed.');
35
+ return resolved;
36
+ }
37
+ function repository(scope) {
38
+ var marker = fs.lstatSync(path.join(ctx.cwd, '.git'));
39
+ if (!marker.isDirectory() || marker.isSymbolicLink()) throw new Error('Canonical Git controls require a direct local .git directory; linked metadata and submodules are unavailable.');
40
+ var status = git.getStatus(ctx.cwd, scope.identity);
41
+ if (!status.isRepository || fs.realpathSync(status.root) !== fs.realpathSync(ctx.cwd)) throw new Error('Select the exact registered repository root, rather than a repository subdirectory.');
42
+ if (fs.realpathSync(status.gitDir) !== fs.realpathSync(path.join(ctx.cwd, '.git'))) throw new Error('The Git metadata directory escaped the selected project.');
43
+ return status;
44
+ }
45
+ function gitProjection(status) { return { isRepository: true, branch: status.branch, oid: status.oid, upstream: status.upstream, ahead: status.ahead, behind: status.behind, detached: status.detached, files: status.files.slice(0, 200), truncated: status.files.length > 200, dirty: status.dirty }; }
46
+ function terminal(scope, id) {
47
+ var item = ctx.tm.inspect(id);
48
+ var identity = scope.identity ? JSON.stringify([scope.identity.user, scope.identity.uid, scope.identity.gid, scope.identity.home]) : null;
49
+ if (!item || item.kind !== 'shell' || item.ownerId !== scope.ownerId || item.osIdentity !== identity) throw new Error('Select an exact same-owner shell terminal. Coding-agent, login and foreign terminals are excluded.');
50
+ return item;
51
+ }
52
+ function read(principal, op) {
53
+ var scope = authorize(principal, op.domain), before = rootIdentity(principal, op.domain), result;
54
+ if (op.domain === 'files') {
55
+ bounded(op.path || '.');
56
+ if (op.action === 'list') { var entries = ctx.files().list(scope, op.path || '.'); result = { entries: entries.slice(0, 200), truncated: entries.length > 200 }; }
57
+ else { result = ctx.files().read(scope, op.path); if (result.content && result.content.length > 65536) result = { error: 'Canonical text reads are limited to 64 KB.', size: result.size }; }
58
+ } else if (op.domain === 'git') {
59
+ var status = repository(scope);
60
+ if (op.action === 'status') result = gitProjection(status);
61
+ else { var working = bounded(op.path, true); if (fs.existsSync(working)) ctx.files().read(scope, op.path); result = git.getFileDiff(ctx.cwd, op.path, scope.identity); if ((result.oldContent.length + result.newContent.length) > 65536) throw new Error('Canonical diff reads are limited to 64 KB.'); }
62
+ } else if (op.action === 'list') result = { terminals: ctx.tm.list().filter(function (item) { try { terminal(scope, item.id); return true; } catch (error) { return false; } }).map(function (item) { return ctx.tm.inspect(item.id); }) };
63
+ else { var item = terminal(scope, op.id); result = Object.assign({}, item, ctx.tm.getScrollback(item.id)); }
64
+ if (rootIdentity(principal, op.domain) !== before) throw new Error('Project or OS identity changed during the read.');
65
+ return result;
66
+ }
67
+ function snapshot(principal, op) {
68
+ var scope = authorize(principal, op.domain);
69
+ if (op.domain === 'files') { bounded(op.path); return ctx.files().snapshot(scope, op.path); }
70
+ if (op.domain === 'git') {
71
+ var status = repository(scope);
72
+ if (!op.paths.length || !op.paths.every(function (name) { return status.files.some(function (item) { return item.path === name; }); })) throw new Error('Select exact currently changed Git paths.');
73
+ var entries = op.paths.map(function (name) {
74
+ var working = bounded(name, true);
75
+ if (fs.existsSync(working) && !fs.lstatSync(working).isFile()) throw new Error('Canonical staging requires individual regular file paths; recursive directory staging is unavailable.');
76
+ if (fs.existsSync(working)) ctx.files().read(scope, name);
77
+ var item = status.files.find(function (file) { return file.path === name; });
78
+ if (item.originalPath) bounded(item.originalPath, true);
79
+ var diff = git.getFileDiff(ctx.cwd, name, scope.identity);
80
+ if (diff.binary) throw new Error('Canonical staging of binary files is unavailable.');
81
+ return digest(diff);
82
+ });
83
+ var index = path.join(status.gitDir, 'index');
84
+ return { status: gitProjection(status), gitDirectory: [fs.realpathSync(status.gitDir), inode(status.gitDir)], index: fs.existsSync(index) ? digest(fs.readFileSync(index)) : null, files: entries,
85
+ configuration: digest(git.runGitSync(ctx.cwd, ['config', '--null', '--list', '--show-origin'], null, scope.identity)),
86
+ attributes: digest(git.runGitSync(ctx.cwd, ['check-attr', '-z', '--all', '--'].concat(op.paths), null, scope.identity)) };
87
+ }
88
+ return op.action === 'create' ? ctx.tm.list().map(function (item) { return ctx.tm.inspect(item.id); }) : terminal(scope, op.id);
89
+ }
90
+ async function apply(principal, op, expected, revalidate) {
91
+ if (busy) throw new Error('Another project file, Git or terminal change is applying. Inspect again after it settles.');
92
+ busy = true;
93
+ var attempted = false, result;
94
+ try {
95
+ var scope = authorize(principal, op.domain);
96
+ revalidate();
97
+ if (digest(snapshot(principal, op)) !== digest(expected)) throw new Error('The exact product revision changed.');
98
+ if (op.domain === 'files') return ctx.files().write(scope, op.path, op.content, { expected: expected, revalidate: function () { revalidate(); bounded(op.path); } });
99
+ attempted = true;
100
+ if (op.domain === 'git') {
101
+ await git.runAction(ctx.cwd, { action: op.action, paths: op.paths }, scope.identity);
102
+ result = { status: 'completed', runtimeEffect: 'index_updated', persisted: true };
103
+ } else if (op.action === 'create') {
104
+ var created = ctx.tm.create(op.cols, op.rows, scope.identity, null, { kind: 'shell', ownerId: scope.ownerId });
105
+ if (!created) throw new Error('The terminal transport is unavailable or its limit was reached.');
106
+ result = { status: 'completed', runtimeEffect: 'shell_created', terminalId: created.id, terminalIdentity: created.identity, commandSent: false, persisted: false };
107
+ } else if (op.action === 'rename') {
108
+ ctx.tm.rename(op.id, op.title); result = { status: 'completed', runtimeEffect: 'renamed', terminalId: op.id, terminalIdentity: expected.identity, persisted: false };
109
+ } else if (op.action === 'resize') {
110
+ ctx.tm.resizeExact(op.id, op.cols, op.rows, expected.identity, revalidate);
111
+ result = { status: 'completed', runtimeEffect: 'resized', terminalId: op.id, terminalIdentity: expected.identity, persisted: false };
112
+ } else {
113
+ await ctx.tm.closeAndWait(op.id, { identity: expected.identity, revalidate: function () { revalidate(); var live = terminal(authorize(principal, 'terminal'), op.id); if (live.identity !== expected.identity) throw new Error('Terminal identity changed during closure.'); } });
114
+ result = { status: 'completed', runtimeEffect: 'closed', terminalId: op.id, terminalIdentity: expected.identity, exitAcknowledged: true, persisted: false };
115
+ }
116
+ revalidate();
117
+ if (op.domain === 'terminal') ctx.publishTerminals();
118
+ return result;
119
+ } catch (error) {
120
+ error.productControlResult = Object.assign({ persisted: attempted ? 'unknown' : false, runtimeEffect: attempted ? 'unknown' : 'none', retrySafe: false }, result, error.productControlResult || {}, { status: 'failed', error: error.message });
121
+ throw error;
122
+ } finally { busy = false; }
123
+ }
124
+ return { rootIdentity: rootIdentity, read: read, snapshot: snapshot, apply: apply };
125
+ }
126
+ module.exports = { attachWorkspaceOperations: attachWorkspaceOperations };
@@ -1,4 +1,5 @@
1
1
  var workspaceMcp = require("./workspace-query-mcp-server");
2
+ var productTools = require("./clay-product-control-tools");
2
3
  var clayHistoryMcp = require("./clay-history-mcp-server");
3
4
 
4
5
  function attachProjectWorkspaceQuery(ctx) {
@@ -25,7 +26,12 @@ function attachProjectWorkspaceQuery(ctx) {
25
26
  function getToolDefs(session) {
26
27
  var bound = session ? binding(session) : null;
27
28
  if (session && !bound) return [];
28
- return workspaceMcp.getToolDefs(bound, !!(bound && bound.isClay));
29
+ var tools = workspaceMcp.getToolDefs(bound, !!(bound && bound.isClay));
30
+ if (bound && bound.isClay && ctx.controlService) {
31
+ var controls = ctx.controlService.bind({ projectSlug: projectSlug, projectOwnerId: getProjectOwnerId(), isMate: true, mateId: mateId, session: session });
32
+ if (controls) tools = tools.concat(productTools.getToolDefs(controls));
33
+ }
34
+ return tools;
29
35
  }
30
36
 
31
37
  function createMcpServer(adapter, session) {
@@ -34,7 +40,8 @@ function attachProjectWorkspaceQuery(ctx) {
34
40
  if (session && !bound) return null;
35
41
  var projectBound = binding(null);
36
42
  if (!projectBound) return null;
37
- return workspaceMcp.createMcpServer(adapter, bound, projectBound.isClay);
43
+ 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) });
38
45
  }
39
46
 
40
47
  function createHistoryMcpServer(adapter, session) {