clay-server 5.2.0-beta.4 → 5.2.0-beta.6

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 (301) hide show
  1. package/lib/account-auth-socket.js +1 -0
  2. package/lib/builtin-mates.js +17 -14
  3. package/lib/canonical-clay-record-authority.js +15 -0
  4. package/lib/canonical-clay-scheduling-authority.js +19 -0
  5. package/lib/claude-hook-installer.js +1 -239
  6. package/lib/claude-jsonl-watcher.js +1 -204
  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-skill-install.js +1 -33
  23. package/lib/clay-workspace-controls.js +83 -0
  24. package/lib/clay-workspace-input.js +17 -0
  25. package/lib/clay-workspace-receipts.js +26 -0
  26. package/lib/cli-sessions.js +1 -300
  27. package/lib/codex-defaults.js +1 -28
  28. package/lib/context-usage.js +3 -2
  29. package/lib/daemon-project-access.js +11 -0
  30. package/lib/daemon-project-lifecycle-callbacks.js +15 -0
  31. package/lib/daemon-project-lifecycle.js +262 -0
  32. package/lib/daemon-projects.js +1 -1
  33. package/lib/daemon.js +78 -276
  34. package/lib/default-ai-runtime.js +4 -8
  35. package/lib/email-mcp-server.js +1 -1
  36. package/lib/issues-service.js +29 -15
  37. package/lib/kiro-defaults.js +1 -22
  38. package/lib/knowledge-db-import.js +5 -4
  39. package/lib/knowledge-db-service.js +9 -6
  40. package/lib/knowledge-document-index.js +2 -1
  41. package/lib/knowledge-product-operations.js +46 -0
  42. package/lib/mate-creation-mcp-server.js +2 -0
  43. package/lib/mate-knowledge-canonical-access.js +10 -0
  44. package/lib/mate-knowledge-service.js +7 -4
  45. package/lib/mate-lifecycle-service.js +94 -0
  46. package/lib/mate-message-delivery.js +43 -0
  47. package/lib/mate-ready-creation.js +9 -1
  48. package/lib/mate-schedule-input.js +29 -0
  49. package/lib/mate-schedule-lifecycle.js +151 -0
  50. package/lib/mate-scheduling.js +103 -0
  51. package/lib/mates.js +4 -3
  52. package/lib/mcp-proxy.js +2 -32
  53. package/lib/mcp-query-bridge.js +1 -1
  54. package/lib/mcp-source-adapters.js +1 -91
  55. package/lib/mcp-source-context.js +1 -42
  56. package/lib/mcp-source-discovery.js +1 -169
  57. package/lib/mcp-source-plugins.js +1 -68
  58. package/lib/mcp-source-restrictions.js +1 -47
  59. package/lib/mcp-tool-policy.js +1 -26
  60. package/lib/notes-checklist.js +50 -0
  61. package/lib/notes.js +49 -3
  62. package/lib/os-user-diagnostics.js +2 -4
  63. package/lib/os-users.js +24 -70
  64. package/lib/project-admin-overview.js +51 -0
  65. package/lib/project-ask-user.js +4 -2
  66. package/lib/project-connection.js +8 -7
  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-http.js +4 -4
  73. package/lib/project-knowledge.js +32 -18
  74. package/lib/project-lifecycle-operations.js +98 -0
  75. package/lib/project-lifecycle-process-witness.js +32 -0
  76. package/lib/project-lifecycle-runtime.js +50 -0
  77. package/lib/project-linux-access.js +22 -0
  78. package/lib/project-logs-canonical-clay.js +27 -0
  79. package/lib/project-logs-clay-query.js +16 -0
  80. package/lib/project-logs-schema.js +4 -2
  81. package/lib/project-logs-service.js +8 -7
  82. package/lib/project-logs.js +1 -0
  83. package/lib/project-loop.js +1 -0
  84. package/lib/project-mate-chat-control.js +55 -0
  85. package/lib/project-mate-creation-proposal.js +18 -3
  86. package/lib/project-mcp.js +2 -2
  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-pair-usage.js +12 -13
  92. package/lib/project-pending-message-queue.js +4 -1
  93. package/lib/project-product-control.js +87 -0
  94. package/lib/project-product-session-queue.js +46 -0
  95. package/lib/project-push-access.js +28 -0
  96. package/lib/project-scheduled-message-queue.js +30 -0
  97. package/lib/project-scheduled-messages.js +63 -64
  98. package/lib/project-scheduled-task-control.js +83 -0
  99. package/lib/project-scheduled-tasks.js +13 -2
  100. package/lib/project-scheduling-access.js +10 -0
  101. package/lib/project-session-actions.js +65 -0
  102. package/lib/project-session-delete.js +8 -1
  103. package/lib/project-session-github.js +47 -12
  104. package/lib/project-session-history-actions.js +167 -0
  105. package/lib/project-session-pair.js +1 -1
  106. package/lib/project-session-runtime-actions.js +64 -0
  107. package/lib/project-session-spawn.js +2 -3
  108. package/lib/project-session-vendor.js +55 -0
  109. package/lib/project-sessions.js +82 -267
  110. package/lib/project-user-message.js +31 -1
  111. package/lib/project-vendor-login.js +6 -36
  112. package/lib/project-workspace-operations.js +126 -0
  113. package/lib/project-workspace-query.js +9 -2
  114. package/lib/project.js +95 -3
  115. package/lib/public/app.js +1 -0
  116. package/lib/public/css/clay-tool-activity.css +51 -0
  117. package/lib/public/css/home-mate-creation.css +5 -0
  118. package/lib/public/css/messages.css +6 -2
  119. package/lib/public/css/overlays.css +4 -2
  120. package/lib/public/css/project-admin-overview.css +13 -0
  121. package/lib/public/css/session-folders.css +11 -0
  122. package/lib/public/css/session-github.css +3 -0
  123. package/lib/public/css/sticky-notes.css +61 -1
  124. package/lib/public/index.html +1 -1
  125. package/lib/public/modules/admin-settings-projects.js +6 -2
  126. package/lib/public/modules/app-connection.js +8 -1
  127. package/lib/public/modules/app-favicon.js +11 -2
  128. package/lib/public/modules/app-header.js +4 -2
  129. package/lib/public/modules/app-loop-ui.js +2 -0
  130. package/lib/public/modules/app-message-router.js +1 -1
  131. package/lib/public/modules/app-messages.js +54 -8
  132. package/lib/public/modules/app-notifications.js +1 -2
  133. package/lib/public/modules/app-panels.js +61 -123
  134. package/lib/public/modules/app-projects.js +5 -0
  135. package/lib/public/modules/app-rate-limit.js +7 -27
  136. package/lib/public/modules/chat-render-runtime.js +3 -0
  137. package/lib/public/modules/clay-owner-navigation.js +27 -0
  138. package/lib/public/modules/clay-tool-activity.js +140 -0
  139. package/lib/public/modules/clay-tool-presentation.js +338 -0
  140. package/lib/public/modules/command-palette.js +1 -1
  141. package/lib/public/modules/default-ai.js +11 -3
  142. package/lib/public/modules/home-debate-planning.js +5 -5
  143. package/lib/public/modules/home-mate-chat.js +8 -6
  144. package/lib/public/modules/home-mate-creation.js +73 -1
  145. package/lib/public/modules/mcp-connection-view.js +1 -1
  146. package/lib/public/modules/permission-control.js +5 -4
  147. package/lib/public/modules/personal-settings-dialog.js +7 -1
  148. package/lib/public/modules/project-access-recovery.js +17 -0
  149. package/lib/public/modules/project-admin-overview.js +21 -0
  150. package/lib/public/modules/scheduler.js +2 -0
  151. package/lib/public/modules/session-create-form.js +10 -10
  152. package/lib/public/modules/session-folder-layout.js +1 -1
  153. package/lib/public/modules/session-folder-view-menu.js +9 -0
  154. package/lib/public/modules/session-github.js +65 -8
  155. package/lib/public/modules/settings-defaults.js +27 -28
  156. package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
  157. package/lib/public/modules/sticky-note-markdown.js +72 -6
  158. package/lib/public/modules/sticky-notes-actions.js +30 -0
  159. package/lib/public/modules/sticky-notes-browser.js +9 -2
  160. package/lib/public/modules/sticky-notes-card.js +23 -7
  161. package/lib/public/modules/sticky-notes-checklist.js +71 -0
  162. package/lib/public/modules/sticky-notes-editor.js +108 -44
  163. package/lib/public/modules/sticky-notes-shared.js +50 -2
  164. package/lib/public/modules/sticky-notes-state.js +66 -0
  165. package/lib/public/modules/sticky-notes.js +34 -51
  166. package/lib/public/modules/thinking-lifecycle.js +33 -3
  167. package/lib/public/modules/thinking-view.js +23 -1
  168. package/lib/public/modules/tools.js +36 -7
  169. package/lib/public/modules/tui-grab.js +1 -2
  170. package/lib/public/modules/user-settings-appearance.js +10 -3
  171. package/lib/public/modules/user-settings-editors.js +1 -1
  172. package/lib/public/modules/user-settings-layout.js +11 -2
  173. package/lib/public/modules/user-settings-preferences.js +6 -2
  174. package/lib/public/modules/user-settings-profile.js +12 -2
  175. package/lib/public/modules/user-settings.js +3 -2
  176. package/lib/public/modules/vendor-login-browser.js +5 -3
  177. package/lib/public/modules/vendor-login.js +2 -2
  178. package/lib/public/modules/vendor-priority.js +15 -1
  179. package/lib/public/muse-avatar.svg +4 -0
  180. package/lib/public/style.css +2 -0
  181. package/lib/safe-bash-commands.js +3 -45
  182. package/lib/scheduled-task-records.js +3 -1
  183. package/lib/scheduler.js +2 -0
  184. package/lib/sdk-bridge.js +334 -403
  185. package/lib/sdk-foreground-admission.js +16 -0
  186. package/lib/sdk-mcp-descriptors.js +2 -37
  187. package/lib/sdk-message-processor.js +52 -17
  188. package/lib/sdk-quota-snapshot.js +1 -1
  189. package/lib/sdk-response-recovery.js +1 -0
  190. package/lib/sdk-worker.js +2 -677
  191. package/lib/server-admin.js +63 -16
  192. package/lib/server-dm.js +1 -1
  193. package/lib/server-global-ws.js +10 -20
  194. package/lib/server-home-chat-events.js +7 -3
  195. package/lib/server-home-chat.js +4 -16
  196. package/lib/server-home-mate-creation.js +1 -0
  197. package/lib/server-home-mcp-consent.js +3 -3
  198. package/lib/server-mates.js +25 -67
  199. package/lib/server-owner-ui.js +77 -0
  200. package/lib/server-path-access.js +38 -0
  201. package/lib/server-project-removal.js +22 -0
  202. package/lib/server.js +100 -30
  203. package/lib/session-config-transition.js +45 -0
  204. package/lib/session-folders.js +7 -2
  205. package/lib/session-fork-history.js +21 -0
  206. package/lib/session-github.js +9 -2
  207. package/lib/session-hygiene.js +3 -35
  208. package/lib/session-permission-mode.js +4 -3
  209. package/lib/session-skill-scope.js +11 -0
  210. package/lib/session-visibility.js +5 -2
  211. package/lib/sessions.js +27 -89
  212. package/lib/terminal-manager.js +52 -0
  213. package/lib/tool-llm.js +1 -1
  214. package/lib/tui-transcript-index.js +1 -132
  215. package/lib/users-permissions.js +2 -8
  216. package/lib/users.js +4 -0
  217. package/lib/vendor-login-browser.js +1 -11
  218. package/lib/workspace-query-access.js +4 -1
  219. package/lib/ws-schema.js +8 -2
  220. package/lib/yoke/acp-agent-profiles.js +5 -3
  221. package/lib/yoke/adapters/antigravity.js +3 -14
  222. package/lib/yoke/adapters/claude-query-contract.js +9 -0
  223. package/lib/yoke/adapters/claude.js +54 -50
  224. package/lib/yoke/adapters/codex-query-contract.js +24 -0
  225. package/lib/yoke/adapters/codex.js +53 -111
  226. package/lib/yoke/adapters/kiro.js +3 -1
  227. package/lib/yoke/adapters/muse-query-contract.js +7 -0
  228. package/lib/yoke/adapters/muse.js +122 -0
  229. package/lib/yoke/auth-events.js +17 -0
  230. package/lib/yoke/claude-hook-installer.js +267 -0
  231. package/lib/yoke/claude-jsonl-watcher.js +204 -0
  232. package/lib/yoke/claude-maintenance.js +55 -0
  233. package/lib/yoke/claude-permission-patterns.js +53 -0
  234. package/lib/yoke/claude-response-recovery.js +155 -0
  235. package/lib/yoke/claude-transcript-hygiene.js +34 -0
  236. package/lib/yoke/claude-worker-startup.js +28 -0
  237. package/lib/yoke/clay-skill-install.js +33 -0
  238. package/lib/yoke/cli-sessions.js +300 -0
  239. package/lib/yoke/codex-app-server.js +7 -12
  240. package/lib/yoke/codex-idle-recovery.js +81 -1
  241. package/lib/yoke/codex-recovery-admission.js +164 -0
  242. package/lib/yoke/configuration.js +13 -0
  243. package/lib/yoke/discovery.js +70 -0
  244. package/lib/yoke/errors.js +8 -0
  245. package/lib/yoke/host-environment.js +7 -0
  246. package/lib/yoke/index.js +9 -51
  247. package/lib/yoke/interface.js +116 -0
  248. package/lib/yoke/kiro-acp-server.js +5 -5
  249. package/lib/yoke/legacy-sessions.js +89 -0
  250. package/lib/yoke/login-protocol.js +25 -0
  251. package/lib/yoke/maintenance.js +12 -0
  252. package/lib/yoke/mcp-proxy.js +32 -0
  253. package/lib/yoke/mcp-source-adapters.js +91 -0
  254. package/lib/yoke/mcp-source-context.js +42 -0
  255. package/lib/yoke/mcp-source-discovery.js +169 -0
  256. package/lib/yoke/mcp-source-plugins.js +68 -0
  257. package/lib/yoke/mcp-source-restrictions.js +47 -0
  258. package/lib/yoke/mcp-tool-policy-normalize.js +28 -0
  259. package/lib/yoke/model-preferences.js +6 -0
  260. package/lib/yoke/muse-artifacts.js +36 -0
  261. package/lib/yoke/muse-controls.js +50 -0
  262. package/lib/yoke/muse-input.js +64 -0
  263. package/lib/yoke/muse-items.js +157 -0
  264. package/lib/yoke/muse-policy.js +52 -0
  265. package/lib/yoke/muse-query.js +428 -0
  266. package/lib/yoke/muse-runtime.js +211 -0
  267. package/lib/yoke/muse-services.js +79 -0
  268. package/lib/yoke/muse-skills.js +79 -0
  269. package/lib/yoke/muse-turns.js +71 -0
  270. package/lib/yoke/options/acp.js +2 -0
  271. package/lib/yoke/options/antigravity.js +2 -0
  272. package/lib/yoke/options/claude.js +21 -0
  273. package/lib/yoke/options/codex-defaults.js +29 -0
  274. package/lib/yoke/options/codex-settings.js +21 -0
  275. package/lib/yoke/options/codex.js +6 -0
  276. package/lib/yoke/options/kiro-defaults.js +22 -0
  277. package/lib/yoke/options/kiro.js +5 -0
  278. package/lib/yoke/options/muse.js +5 -0
  279. package/lib/yoke/provider-contract.js +5 -0
  280. package/lib/yoke/providers/antigravity-executable.js +8 -0
  281. package/lib/yoke/providers/claude-executable.js +40 -0
  282. package/lib/yoke/providers/claude-model-descriptions.js +14 -0
  283. package/lib/yoke/providers/claude-process.js +97 -0
  284. package/lib/yoke/providers/claude-provision.js +75 -0
  285. package/lib/yoke/providers/codex-errors.js +8 -0
  286. package/lib/yoke/providers/muse-executable.js +17 -0
  287. package/lib/yoke/provisioning.js +6 -0
  288. package/lib/yoke/query-options.js +10 -0
  289. package/lib/yoke/response-recovery.js +41 -0
  290. package/lib/yoke/sdk-mcp-descriptors.js +37 -0
  291. package/lib/yoke/sdk-server-factory.js +7 -0
  292. package/lib/yoke/session-history.js +8 -0
  293. package/lib/yoke/session-tool-transport.js +34 -13
  294. package/lib/yoke/settings.js +23 -0
  295. package/lib/yoke/source-locations.js +4 -0
  296. package/lib/yoke/title-query.js +18 -7
  297. package/lib/yoke/tui-transcript-index.js +132 -0
  298. package/lib/yoke/usage.js +48 -0
  299. package/lib/yoke/user-input.js +13 -0
  300. package/lib/yoke/vendor-registry.js +81 -1
  301. package/package.json +3 -2
@@ -0,0 +1,22 @@
1
+ // Shared runtime removal used by the server callback and lifecycle controls.
2
+ function removeProjectRuntime(projects, slug, awaitCompletion, registryRefresh) {
3
+ var project = projects.get(slug);
4
+ if (!project) return false;
5
+ var wasMate = project.getStatus().isMate;
6
+ var shutdownResult = project.destroy();
7
+ if (!awaitCompletion) projects.delete(slug);
8
+ if (shutdownResult && typeof shutdownResult.catch === 'function') {
9
+ shutdownResult.catch(function (error) {
10
+ console.error('[server] Project destroy failed for ' + slug + ':', error && error.message ? error.message : error);
11
+ });
12
+ }
13
+ if (!wasMate && registryRefresh) registryRefresh();
14
+ if (awaitCompletion) return Promise.resolve(shutdownResult).then(function (result) {
15
+ if (result !== true) throw new Error('Project runtime shutdown did not complete successfully: ' + slug);
16
+ if (projects.get(slug) !== project) throw new Error('Project runtime was replaced during shutdown: ' + slug);
17
+ projects.delete(slug);
18
+ return true;
19
+ });
20
+ return true;
21
+ }
22
+ module.exports = { removeProjectRuntime: removeProjectRuntime };
package/lib/server.js CHANGED
@@ -10,7 +10,7 @@ var dm = require("./dm");
10
10
  var mates = require("./mates");
11
11
  var serverAuth = require("./server-auth");
12
12
  var serverSkills = require("./server-skills");
13
- var claudeHookInstaller = require("./claude-hook-installer");
13
+ var claudeHookInstaller = require("./yoke/claude-hook-installer");
14
14
  var serverDm = require("./server-dm");
15
15
  var serverMates = require("./server-mates");
16
16
  var serverHomeChat = require("./server-home-chat");
@@ -219,6 +219,14 @@ function createServer(opts) {
219
219
  var isRequestAuthed = auth.isRequestAuthed;
220
220
  var parseCookies = auth.parseCookies;
221
221
 
222
+ var projectAdminOverview = require("./project-admin-overview");
223
+
224
+ function administrativeMetadata(userId, slug) {
225
+ var user = userId && users.findUserById(userId);
226
+ var context = projects.get(slug);
227
+ return projectAdminOverview.metadataFor(user, context && context.getStatus(), onGetProjectAccess && onGetProjectAccess(slug), users);
228
+ }
229
+
222
230
  function canUserAccessSlug(userId, slug) {
223
231
  if (!userId || !slug) return false;
224
232
  var project = projects && projects.get(slug);
@@ -237,6 +245,13 @@ function createServer(opts) {
237
245
  return users.canAccessProject(userId, access) === true;
238
246
  }
239
247
 
248
+ var projects = new Map(); // slug → projectContext
249
+ var lifecycleRuntime = require("./project-lifecycle-runtime").attachLifecycleRuntime({ projects: projects });
250
+ var canUsePath = require("./server-path-access").attachPathAccess({
251
+ users: users, projects: projects, canAccess: canUserAccessSlug, osUsers: osUsers,
252
+ resolveIdentity: require("./os-users").resolveOsUserInfo,
253
+ });
254
+
240
255
  var realVersion = require("../package.json").version;
241
256
  var currentVersion = debug ? "0.0.9" : realVersion;
242
257
 
@@ -341,7 +356,6 @@ function createServer(opts) {
341
356
  var caContent = caPath ? (function () { try { return fs.readFileSync(caPath); } catch (e) { return null; } })() : null;
342
357
 
343
358
  // --- Project registry ---
344
- var projects = new Map(); // slug → projectContext
345
359
  var assignmentService = attachWorkspaceAssignmentService({
346
360
  storageDir: path.join(CONFIG_DIR, "workspace-assignments"),
347
361
  getProjects: function () { return projects; },
@@ -363,6 +377,29 @@ function createServer(opts) {
363
377
  },
364
378
  assignmentService: assignmentService,
365
379
  });
380
+ var ownerUiService = require('./server-owner-ui').attachOwnerUi({
381
+ isMultiUser: users.isMultiUser, findUser: users.findUserById,
382
+ forEachClient: function (visit) {
383
+ if (globalWs) globalWs.forEachClient(function (ws) { visit(ws, null); });
384
+ projects.forEach(function (project, slug) { project.forEachClient(function (ws) { visit(ws, slug); }); });
385
+ }
386
+ });
387
+ var clayProductControl = require("./clay-product-control").attachClayProductControl({
388
+ getOwnerUi: function () { return ownerUiService; },
389
+ getNotifications: function () { return _globalNotifications; },
390
+ getRecordServices: function () { return { issues: issuesService, logs: projectLogsService }; },
391
+ projectLifecycle: opts.projectLifecycle || null,
392
+ getMateLifecycle: function () { return mateHandler && mateHandler.lifecycle; },
393
+ getMateDir: function (ownerId, mateId) { return mates.getMateDir(mates.buildMateCtx(users.isMultiUser() ? ownerId : null), mateId); },
394
+ lifecyclePermissions: function (user) { return users.getEffectivePermissions(user, osUsers); },
395
+ canUsePath: canUsePath,
396
+ getProjects: function () { return projects; },
397
+ isMultiUser: function () { return users.isMultiUser(); },
398
+ onGetProjectAccess: onGetProjectAccess,
399
+ canAccessProject: function (userId, access) { return users.canAccessProject(userId, access) === true; },
400
+ findUser: function (userId) { return users.findUserById(userId); },
401
+ resolveMate: function (userId, mateId) { return mates.getMate(mates.buildMateCtx(users.isMultiUser() ? userId : null), mateId); },
402
+ });
366
403
  // Project access is resolved from the authoritative project record, not from
367
404
  // getStatus(), which carries no visibility or allowedUsers fields.
368
405
  var projectLogsService = attachProjectLogsService({
@@ -409,6 +446,7 @@ function createServer(opts) {
409
446
  // Mate Knowledge reads the synchronized Knowledge records. The Mate list is
410
447
  // derived from the owning user alone, so a binding can never span users.
411
448
  var mateKnowledgeService = attachMateKnowledgeService({
449
+ findUser: function (userId) { return users.findUserById(userId); },
412
450
  getProjects: function () { return projects; },
413
451
  isMultiUser: function () { return users.isMultiUser(); },
414
452
  resolveMate: function (userId, mateId) {
@@ -547,7 +585,28 @@ function createServer(opts) {
547
585
  broadcastAll: function (msg) { broadcastAll(msg); },
548
586
  sendToUser: function (userId, msg) { sendToUser(userId, msg); },
549
587
  pushModule: pushModule,
588
+ forEachClient: forEachAppClient,
589
+ getUsers: function () { return users.getAllUsers(); },
590
+ canView: function (ws, notification) { return canViewNotification(ws && ws._clayUser && ws._clayUser.id, notification); },
591
+ canViewPrincipal: function (principal, notification) { return canViewNotification(principal.userId, notification); },
550
592
  });
593
+ function canViewNotification(userId, notification) {
594
+ if (!users.isMultiUser()) return true;
595
+ if (!userId || !users.findUserById(userId)) return false;
596
+ if (notification.targetUserId && notification.targetUserId !== userId) return false;
597
+ if (notification.slug) {
598
+ if (!canUserAccessSlug(userId, notification.slug)) return false;
599
+ if (notification.sessionId) {
600
+ var project = projects.get(notification.slug);
601
+ var session = project && project.getSessionManager().sessions.get(notification.sessionId);
602
+ var status = project && project.getStatus();
603
+ var access = status && status.isMate ? { isMate: true, visibility: "private", ownerId: status.projectOwnerId } : onGetProjectAccess(notification.slug);
604
+ if (!session || !users.canAccessSession(userId, session, access)) return false;
605
+ }
606
+ return !notification.ownerId || notification.ownerId === userId || !!notification.sessionId;
607
+ }
608
+ return notification.targetUserId === userId || notification.ownerId === userId;
609
+ }
551
610
 
552
611
  // --- Security headers ---
553
612
  var securityHeaders = {
@@ -980,8 +1039,8 @@ function createServer(opts) {
980
1039
  var projectList = [];
981
1040
  var infoUser = users.isMultiUser() ? getMultiUserFromReq(req) : null;
982
1041
  projects.forEach(function (ctx, slug) {
983
- if (users.isMultiUser() && (!infoUser || !canUserAccessSlug(infoUser.id, slug))) return;
984
- projectList.push({ slug: slug, project: ctx.project });
1042
+ if (users.isMultiUser() && (!infoUser || (!canUserAccessSlug(infoUser.id, slug) && !administrativeMetadata(infoUser.id, slug)))) return;
1043
+ projectList.push({ slug: slug, project: ctx.project, contentAccess: !users.isMultiUser() || canUserAccessSlug(infoUser.id, slug) });
985
1044
  });
986
1045
  res.writeHead(200, { "Content-Type": "application/json" });
987
1046
  res.end(JSON.stringify({ projects: projectList, version: currentVersion, authenticated: true }));
@@ -1057,8 +1116,14 @@ function createServer(opts) {
1057
1116
  if (users.isMultiUser() && !isMcpBridgeAuthenticated) {
1058
1117
  var httpUser = getMultiUserFromReq(req);
1059
1118
  if (!httpUser || !canUserAccessSlug(httpUser.id, slug)) {
1060
- res.writeHead(302, { "Location": "/" });
1061
- res.end();
1119
+ var overview = httpUser && administrativeMetadata(httpUser.id, slug);
1120
+ if (overview && req.method === "GET" && (projectUrlForAuth === "/" || projectUrlForAuth === "/index.html")) {
1121
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
1122
+ res.end(projectAdminOverview.page(overview, getVisibleProjects(httpUser.id)));
1123
+ return;
1124
+ }
1125
+ res.writeHead(403, { "Content-Type": "application/json", "Cache-Control": "no-store" });
1126
+ res.end(JSON.stringify({ error: "Project content access is not permitted" }));
1062
1127
  return;
1063
1128
  }
1064
1129
  }
@@ -1167,6 +1232,7 @@ function createServer(opts) {
1167
1232
  var globalWs = serverGlobalWs.attachGlobalWs({
1168
1233
  osUsers: osUsers,
1169
1234
  usersModule: users,
1235
+ canUsePath: canUsePath,
1170
1236
  onAddProject: onAddProject,
1171
1237
  onCreateProject: onCreateProject,
1172
1238
  onCloneProject: onCloneProject,
@@ -1307,17 +1373,10 @@ function createServer(opts) {
1307
1373
  if (processingUpdateTimer) clearTimeout(processingUpdateTimer);
1308
1374
  processingUpdateTimer = setTimeout(function () {
1309
1375
  processingUpdateTimer = null;
1310
- var allProjectsList = getProjects();
1311
1376
  // Always send per-client to include cross-project unread counts
1312
1377
  projects.forEach(function (ctx, projSlug) {
1313
1378
  ctx.forEachClient(function (ws) {
1314
- var filtered = allProjectsList;
1315
- if (users.isMultiUser() && onGetProjectAccess) {
1316
- var wsUser = ws._clayUser;
1317
- filtered = wsUser ? allProjectsList.filter(function (p) {
1318
- return canUserAccessSlug(wsUser.id, p.slug);
1319
- }) : [];
1320
- }
1379
+ var filtered = getVisibleProjects(ws._clayUser ? ws._clayUser.id : null);
1321
1380
  // Attach per-project unread counts for this client
1322
1381
  var unreadMap = getCrossProjectUnread(ws);
1323
1382
  var projectsWithUnread = filtered.map(function (p) {
@@ -1367,6 +1426,10 @@ function createServer(opts) {
1367
1426
  function addProject(cwd, slug, title, icon, projectOwnerId, worktreeMeta, extraOpts) {
1368
1427
  if (projects.has(slug)) return false;
1369
1428
  var extra = extraOpts || {};
1429
+ if (extra.isMate) {
1430
+ var registeredMate = mates.getMate(mates.buildMateCtx(users.isMultiUser() ? projectOwnerId : null), extra.mateId || path.basename(cwd));
1431
+ if (registeredMate && registeredMate.lifecycleDeletion) return false;
1432
+ }
1370
1433
  var ctx = createProjectContext({
1371
1434
  cwd: cwd,
1372
1435
  slug: slug,
@@ -1386,6 +1449,8 @@ function createServer(opts) {
1386
1449
  return mateHandler.createReadyMateFromInterview(ws, userId, definition);
1387
1450
  },
1388
1451
  workspaceQueryService: workspaceQueryService,
1452
+ clayProductControl: clayProductControl,
1453
+ resumeLifecycleProject: function (userId) { return opts.projectLifecycle ? opts.projectLifecycle.resumeRecovered(slug, userId) : true; },
1389
1454
  projectLogsService: projectLogsService,
1390
1455
  issuesService: issuesService,
1391
1456
  linearStorage: linear.storage,
@@ -1407,13 +1472,19 @@ function createServer(opts) {
1407
1472
  return !!(access && !access.error && users.canAccessProject(userId, access));
1408
1473
  },
1409
1474
  getProjectAccess: function () {
1410
- if (extra.isMate) return { isMate: true, visibility: "private", ownerId: projectOwnerId || null, allowedUsers: [] };
1475
+ if (extra.isMate) {
1476
+ var mate = mates.getMate(mates.buildMateCtx(users.isMultiUser() ? projectOwnerId : null), extra.mateId || path.basename(cwd));
1477
+ return { isMate: true, visibility: "private", ownerId: projectOwnerId || null, allowedUsers: [], lifecycleExecutionPaused: !mate || !!mate.lifecycleDeletion };
1478
+ }
1411
1479
  return onGetProjectAccess ? onGetProjectAccess(slug) : { visibility: "public", ownerId: projectOwnerId || null };
1412
1480
  },
1413
1481
  shouldSuppressResponseNotification: function (session) {
1414
1482
  return shouldSuppressHomeResponse(projects, slug, session);
1415
1483
  },
1416
- pushModule: pushModule,
1484
+ pushModule: require("./project-push-access").scopedPush(pushModule, {
1485
+ slug: slug, users: users, getProject: function (target) { return projects.get(target); },
1486
+ canAccessProject: canUserAccessSlug, getAccess: onGetProjectAccess,
1487
+ }),
1417
1488
  debug: debug,
1418
1489
  dangerouslySkipPermissions: dangerouslySkipPermissions,
1419
1490
  osUsers: osUsers,
@@ -1445,6 +1516,7 @@ function createServer(opts) {
1445
1516
  var status = ctx.getStatus();
1446
1517
  var recs = ctx.getSchedules();
1447
1518
  for (var i = 0; i < recs.length; i++) {
1519
+ if (users.isMultiUser() && recs[i].ownerId !== userId) continue;
1448
1520
  // Shallow-copy full record and augment with project metadata
1449
1521
  var copy = {};
1450
1522
  var keys = Object.keys(recs[i]);
@@ -1502,6 +1574,7 @@ function createServer(opts) {
1502
1574
  return ctx.getSchedules().length;
1503
1575
  },
1504
1576
  canAccessProjectSlug: canUserAccessSlug,
1577
+ canUsePath: canUsePath,
1505
1578
  onPresenceChange: broadcastPresenceChange,
1506
1579
  onProcessingChanged: broadcastProcessingChange,
1507
1580
  onSessionDone: function () { onSessionDone(slug); },
@@ -1655,6 +1728,7 @@ function createServer(opts) {
1655
1728
  }
1656
1729
 
1657
1730
  function handleDmMessage(ws, msg, routedProjectSlug) {
1731
+ if (ownerUiService.handleMessage(ws, msg)) return true;
1658
1732
  if (cursorSharingService && cursorSharingService.handleMessage(ws, msg)) return true;
1659
1733
  if (defaultVendorService && defaultVendorService.handleMessage(ws, msg, routedProjectSlug)) return true;
1660
1734
  if (contextViewService && contextViewService.handleMessage(ws, msg)) return true;
@@ -1669,19 +1743,8 @@ function createServer(opts) {
1669
1743
  return false;
1670
1744
  }
1671
1745
 
1672
- function removeProject(slug) {
1673
- var ctx = projects.get(slug);
1674
- if (!ctx) return false;
1675
- var wasMate = ctx.getStatus().isMate;
1676
- var shutdownResult = ctx.destroy();
1677
- projects.delete(slug);
1678
- if (shutdownResult && typeof shutdownResult.catch === "function") {
1679
- shutdownResult.catch(function(err) {
1680
- console.error("[server] Project destroy failed for " + slug + ":", err && err.message ? err.message : err);
1681
- });
1682
- }
1683
- if (!wasMate) scheduleRegistryRefresh();
1684
- return true;
1746
+ function removeProject(slug, awaitCompletion) {
1747
+ return require('./server-project-removal').removeProjectRuntime(projects, slug, awaitCompletion, scheduleRegistryRefresh);
1685
1748
  }
1686
1749
 
1687
1750
  // Now that addProject and removeProject are defined, initialize mateHandler
@@ -1767,7 +1830,11 @@ function createServer(opts) {
1767
1830
  function getVisibleProjects(userId) {
1768
1831
  var visible = [];
1769
1832
  projects.forEach(function (ctx, slug) {
1770
- if (users.isMultiUser() && (!userId || !canUserAccessSlug(userId, slug))) return;
1833
+ if (users.isMultiUser() && (!userId || !canUserAccessSlug(userId, slug))) {
1834
+ var metadata = administrativeMetadata(userId, slug);
1835
+ if (metadata) visible.push(metadata);
1836
+ return;
1837
+ }
1771
1838
  var status = ctx.getStatus();
1772
1839
  if (!status.isMate && onGetProjectAccess) {
1773
1840
  var access = onGetProjectAccess(slug);
@@ -1966,6 +2033,7 @@ function createServer(opts) {
1966
2033
  speech.destroy();
1967
2034
  var shutdowns = [];
1968
2035
  shutdowns.push(durableSchedulerLifecycle.shutdown());
2036
+ shutdowns.push(lifecycleRuntime.shutdown());
1969
2037
  projects.forEach(function (ctx, slug) {
1970
2038
  console.log("[server] Destroying project:", slug);
1971
2039
  var result = ctx.destroy();
@@ -2033,6 +2101,8 @@ function createServer(opts) {
2033
2101
  isTLS: !!tlsOptions,
2034
2102
  addProject: addProject,
2035
2103
  removeProject: removeProject,
2104
+ getProjectLifecycleTargets: lifecycleRuntime.targets,
2105
+ closeProjectLifecycle: lifecycleRuntime.close,
2036
2106
  getProjects: getProjects,
2037
2107
  reorderProjects: reorderProjects,
2038
2108
  setProjectTitle: setProjectTitle,
@@ -0,0 +1,45 @@
1
+ // Guarded configuration commit shared by SDK setters and permission transitions.
2
+ // Provider acknowledgement is distinct from local persistence. Once a request
3
+ // has crossed that boundary, failures never imply the provider was rolled back.
4
+ async function applySessionConfig(ctx) {
5
+ var session = ctx.session, sm = ctx.sm, handle = session.queryInstance;
6
+ var generation = Number(session._sdkQueryGeneration || 0), owner = session.ownerId;
7
+ var paused = !!session._pendingMessageDrainPaused, signal = session.abortController && session.abortController.signal;
8
+ var attempted = false, acknowledged = false, persisted = false, previous = {};
9
+ function current() {
10
+ ctx.revalidate();
11
+ if (sm.sessions.get(session.localId) !== session || session.ownerId !== owner || session.queryInstance !== handle ||
12
+ Number(session._sdkQueryGeneration || 0) !== generation || session.taskStopRequested ||
13
+ !!session._pendingMessageDrainPaused !== paused || signal && signal.aborted) throw new Error("Target ownership, runtime, or Stop state changed during the configuration request.");
14
+ }
15
+ try {
16
+ current();
17
+ if (handle) {
18
+ if (typeof handle[ctx.method] !== "function") throw new Error("This provider cannot apply this configuration to the active runtime.");
19
+ attempted = true;
20
+ var reply = await handle[ctx.method](ctx.nativeValue);
21
+ if (reply === false || reply && reply.ok === false) throw new Error(reply && reply.error || "The provider refused the configuration change.");
22
+ acknowledged = true;
23
+ current();
24
+ }
25
+ var changes = typeof ctx.changes === "function" ? ctx.changes() : ctx.changes;
26
+ Object.keys(changes).forEach(function (key) { previous[key] = { present: Object.prototype.hasOwnProperty.call(session, key), value: session[key] }; });
27
+ Object.assign(session, changes);
28
+ try {
29
+ if (sm.saveSessionFile(session) !== true) throw new Error("Could not persist the configuration change.");
30
+ persisted = true;
31
+ } catch (error) {
32
+ Object.keys(previous).forEach(function (key) { if (previous[key].present) session[key] = previous[key].value; else delete session[key]; });
33
+ throw error;
34
+ }
35
+ // There is no await between the last authorization check, disk commit and
36
+ // publication. Never broadcast a desired value before its save succeeds.
37
+ ctx.publish();
38
+ return Object.assign({ ok: true, status: "completed", persisted: true, runtimeEffect: acknowledged ? "applied" : "none" }, changes);
39
+ } catch (error) {
40
+ return { ok: false, status: "failed", outcome: persisted ? "partial" : attempted ? "uncertain" : "failed", persisted: persisted,
41
+ runtimeEffect: acknowledged ? "applied" : attempted ? "unknown" : "none", retrySafe: !attempted && !persisted,
42
+ error: (persisted ? "The configuration was saved, but publication did not complete. " : "") + (attempted ? "The provider may retain the requested change; no external rollback was attempted. " : "") + (error.message || String(error)) };
43
+ }
44
+ }
45
+ module.exports = { applySessionConfig: applySessionConfig };
@@ -9,7 +9,7 @@
9
9
  // also keeps its folder (or Unfiled) assignment
10
10
  // orders: { containerKey: [keys] } manual order per container ("unfiled", custom id, "all")
11
11
  // collapsed: { containerKey: true }
12
- // view: { group, sort, direction }
12
+ // view: { group, sort, direction, showDateHeadings }
13
13
  // sessionKey is the session's opaque, durable sessionOriginId.
14
14
 
15
15
  var crypto = require("crypto");
@@ -35,7 +35,7 @@ function defaultState() {
35
35
  favorites: [],
36
36
  orders: {},
37
37
  collapsed: {},
38
- view: { group: "folders", sort: "activity", direction: "desc" },
38
+ view: { group: "folders", sort: "activity", direction: "desc", showDateHeadings: true },
39
39
  legacyFavoritesMigrated: false,
40
40
  };
41
41
  }
@@ -147,6 +147,7 @@ function normalizeState(raw) {
147
147
  var view = isPlainObject(source.view) ? source.view : {};
148
148
  if (SORTS.indexOf(view.sort) !== -1) state.view.sort = view.sort;
149
149
  if (DIRECTIONS.indexOf(view.direction) !== -1) state.view.direction = view.direction;
150
+ if (typeof view.showDateHeadings === "boolean") state.view.showDateHeadings = view.showDateHeadings;
150
151
  state.legacyFavoritesMigrated = source.legacyFavoritesMigrated === true;
151
152
  return state;
152
153
  }
@@ -334,6 +335,10 @@ function applyRaw(input, op, env) {
334
335
  if (DIRECTIONS.indexOf(op.direction) === -1) return { error: "Invalid direction" };
335
336
  state.view.direction = op.direction;
336
337
  }
338
+ if (op.showDateHeadings !== undefined) {
339
+ if (typeof op.showDateHeadings !== "boolean") return { error: "Invalid date heading setting" };
340
+ state.view.showDateHeadings = op.showDateHeadings;
341
+ }
337
342
  return { state: state };
338
343
  }
339
344
 
@@ -0,0 +1,21 @@
1
+ // Product transcript slicing for providers that include a completed turn.
2
+ function endIndex(session, uuid, boundary) {
3
+ if (boundary === undefined) return undefined;
4
+ if (boundary !== "completed-turn-inclusive") throw new Error("Unsupported local fork history boundary");
5
+ var history = session.history || [];
6
+ var markers = (session.messageUUIDs || []).filter(function(marker) { return marker.uuid === uuid && marker.type === "user"; });
7
+ var marker = markers.length === 1 && markers[0];
8
+ var index = marker && marker.historyIndex;
9
+ var entry = Number.isInteger(index) && history[index];
10
+ if (!entry || entry.type !== "message_uuid" || entry.uuid !== uuid) throw new Error("Cannot safely represent this completed-turn fork boundary in local history");
11
+ for (var i = index + 1; i < history.length; i++) {
12
+ entry = history[i];
13
+ if (entry.type === "user_message" || entry.type === "message_uuid" && entry.messageType === "user") break;
14
+ if (entry.type === "done") {
15
+ if (entry.code === 0) return i + 1;
16
+ break;
17
+ }
18
+ }
19
+ throw new Error("Cannot safely represent this completed-turn fork boundary in local history");
20
+ }
21
+ module.exports = { endIndex: endIndex };
@@ -33,22 +33,29 @@ function runGh(cwd, args, identity, input) {
33
33
  }
34
34
  async function readReference(cwd, url, identity, run, statusOnly) {
35
35
  var ref = parseReference(url);
36
- var fields = "title,state,url" + (ref.kind === "pr" ? (statusOnly ? ",isDraft" : ",isDraft,headRefName,reviewDecision,statusCheckRollup") : "");
36
+ var fields = "title,state,url" + (ref.kind === "pr" ? (statusOnly ? ",isDraft,reviewDecision,reviewRequests" : ",isDraft,headRefName,reviewDecision,statusCheckRollup") : "");
37
37
  var data = await (run || runGh)(cwd, [ref.kind === "pr" ? "pr" : "issue", "view", String(ref.number), "--repo", ref.repository, "--json", fields], identity);
38
38
  var verified = parseReference(data.url);
39
39
  if (verified.url.toLowerCase() !== ref.url.toLowerCase()) throw new Error("GitHub returned a different reference.");
40
40
  ref.title = String(data.title || "").slice(0, 200);
41
41
  ref.state = data.state === "MERGED" ? "merged" : data.state === "CLOSED" ? "closed" : data.state === "OPEN" ? (data.isDraft ? "draft" : "open") : "unknown";
42
42
  ref.checkedAt = Date.now();
43
+ if (ref.kind === "pr") {
44
+ ref.review = ["APPROVED", "CHANGES_REQUESTED", "REVIEW_REQUIRED"].indexOf(data.reviewDecision) >= 0 ? data.reviewDecision : "";
45
+ }
43
46
  if (ref.kind === "pr" && !statusOnly) {
44
47
  ref.branch = String(data.headRefName || "").slice(0, 200);
45
- ref.review = ["APPROVED", "CHANGES_REQUESTED", "REVIEW_REQUIRED"].indexOf(data.reviewDecision) >= 0 ? data.reviewDecision : "";
46
48
  ref.checks = (data.statusCheckRollup || []).slice(0, 30).map(function (check) {
47
49
  var target = check.detailsUrl || check.targetUrl || "";
48
50
  if (!/^https:\/\//i.test(target)) target = "";
49
51
  return { name: String(check.name || check.context || "Check").slice(0, 100), state: String(check.conclusion || check.state || check.status || "PENDING").slice(0, 40), url: target };
50
52
  });
51
53
  }
54
+ if (ref.kind === "pr" && statusOnly) {
55
+ ref.reviewRequests = Array.isArray(data.reviewRequests) ? data.reviewRequests.slice(0, 30).map(function (request) {
56
+ return String(request.login || "").trim().toLowerCase().slice(0, 100);
57
+ }).filter(Boolean) : [];
58
+ }
52
59
  return ref;
53
60
  }
54
61
  module.exports = { parseReference: parseReference, readReference: readReference, runGh: runGh };
@@ -5,7 +5,7 @@
5
5
  var sessionProvenance = require("./session-provenance");
6
6
 
7
7
  var BLANK_SESSION_MAX_AGE_MS = 24 * 60 * 60 * 1000;
8
- var CLAUDE_INTERRUPTED_TEXT = "[Request interrupted by user]";
8
+ var transcriptHygiene = require("./yoke/claude-transcript-hygiene");
9
9
 
10
10
  // A session is "blank" when nothing has ever happened in it and nothing is
11
11
  // attached to it. Adopted external sessions are never blank (their transcript
@@ -61,43 +61,11 @@ function collectStaleBlankSessions(sessions, activeSessionId, now) {
61
61
  return stale;
62
62
  }
63
63
 
64
- function extractClaudeUserText(event) {
65
- var content = event && event.message && event.message.content;
66
- if (typeof content === "string") return content;
67
- if (!Array.isArray(content)) return "";
68
- var parts = [];
69
- for (var i = 0; i < content.length; i++) {
70
- if (content[i] && content[i].type === "text" && typeof content[i].text === "string") {
71
- parts.push(content[i].text);
72
- }
73
- }
74
- return parts.join("");
75
- }
76
-
77
- // SDK capability probes create a real Claude transcript with a single "hi"
78
- // prompt. Newer Claude Code versions append an interruption record when Clay
79
- // aborts the probe, so that bookkeeping record must not make the transcript
80
- // look like a real two-turn conversation.
81
- function isClaudeWarmupTranscript(events) {
82
- var promptCount = 0;
83
- for (var i = 0; i < events.length; i++) {
84
- var event = events[i];
85
- if (!event || typeof event !== "object") continue;
86
- if (event.type === "assistant") return false;
87
- if (event.type !== "user" || !event.message || event.message.role !== "user") continue;
88
- var text = extractClaudeUserText(event);
89
- if (text === CLAUDE_INTERRUPTED_TEXT) continue;
90
- if (text !== "hi") return false;
91
- promptCount++;
92
- }
93
- return promptCount === 1;
94
- }
95
-
96
64
  module.exports = {
97
65
  isBlankSession: isBlankSession,
98
66
  findReusableBlankSession: findReusableBlankSession,
99
67
  collectStaleBlankSessions: collectStaleBlankSessions,
100
- extractClaudeUserText: extractClaudeUserText,
101
- isClaudeWarmupTranscript: isClaudeWarmupTranscript,
68
+ extractClaudeUserText: transcriptHygiene.extractClaudeUserText,
69
+ isClaudeWarmupTranscript: transcriptHygiene.isClaudeWarmupTranscript,
102
70
  BLANK_SESSION_MAX_AGE_MS: BLANK_SESSION_MAX_AGE_MS,
103
71
  };
@@ -7,11 +7,12 @@ function normalize(mode) {
7
7
  return null;
8
8
  }
9
9
 
10
- function capabilities(session, defaultVendor) {
10
+ function capabilities(session, defaultVendor, provider) {
11
11
  var vendor = session && session.vendor || defaultVendor || "claude";
12
+ var contract = require("./yoke/session-tool-transport").contract(provider || vendor);
12
13
  return {
13
- auto: vendor === "claude",
14
- mcpOverride: vendor === "claude" && !!(session && session.queryInstance &&
14
+ auto: Array.isArray(contract.permissionModes) && contract.permissionModes.indexOf("auto") !== -1,
15
+ mcpOverride: contract.mcpPermissionOverrides === true && !!(session && session.queryInstance &&
15
16
  typeof session.queryInstance.setMcpPermissionModeOverride === "function"),
16
17
  };
17
18
  }
@@ -0,0 +1,11 @@
1
+ // Bind personal instruction reads to the existing live session/query authority.
2
+ function bind(session, sessions, options, valid, multiUser) {
3
+ var owner = session.ownerId || null;
4
+ return Object.assign({}, options, { isAvailable: function() {
5
+ if (!sessions || typeof sessions.get !== "function" || sessions.get(session.localId) !== session || !valid() || (session.ownerId || null) !== owner || session.youCurator || session.sessionVisibility === "shared" || session._mcpForeignInput) return false;
6
+ if (owner) return require("./mcp-session-access").canUsePersonalConnections(session, sessions);
7
+ return !multiUser && !session.delegated && !session._delegatedBy && !session._pairDelegation
8
+ && !(session.history || []).some(function(entry) { return entry.type === "user_message" && entry.from && entry.from !== "default"; });
9
+ } });
10
+ }
11
+ module.exports = { bind: bind };
@@ -54,7 +54,7 @@ function handleDefaultChange(ctx, ws, msg) {
54
54
  ? ctx.usersModule.getEffectivePermissions(user, ctx.osUsers) : {};
55
55
  var isOwner = !!(user && access && access.ownerId && access.ownerId === user.id);
56
56
  var isAdmin = !!(user && user.role === "admin");
57
- if (!user || !permissions.projectSettings || (!isOwner && !isAdmin)) {
57
+ if (!user || !permissions.projectSettings || !isOwner) {
58
58
  defaultChangeResult(ctx, ws, msg, { ok: false, error: "Project settings access is not permitted" });
59
59
  return true;
60
60
  }
@@ -98,7 +98,10 @@ function revokeViewers(ctx, session) {
98
98
  // Clear the server-side selection before reconnecting so no further
99
99
  // session events or input can cross the revoked access boundary.
100
100
  client._clayActiveSession = null;
101
- if (client.readyState === 1) client.close(1008, "Session access changed");
101
+ if (client.readyState === 1) {
102
+ if (ctx.sendTo) ctx.sendTo(client, { type: "access_revoked", slug: ctx.slug });
103
+ client.close(1008, "Session access changed");
104
+ }
102
105
  });
103
106
  }
104
107
  }