clay-server 5.1.0-beta.1 → 5.1.0-beta.3

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 (231) hide show
  1. package/LICENSES/README.md +4 -0
  2. package/LICENSES/dicebear-avatars.md +16 -0
  3. package/bin/cli.js +9 -91
  4. package/lib/builtin-mates.js +7 -7
  5. package/lib/daemon-project-access.js +7 -1
  6. package/lib/daemon-settings.js +11 -0
  7. package/lib/daemon.js +10 -37
  8. package/lib/dicebear-avatar.js +62 -0
  9. package/lib/knowledge-db-service.js +175 -0
  10. package/lib/knowledge-db-validation.js +0 -0
  11. package/lib/knowledge-document-index.js +49 -0
  12. package/lib/knowledge-draft-store.js +56 -0
  13. package/lib/knowledge-file-store.js +199 -0
  14. package/lib/knowledge-markdown.js +128 -0
  15. package/lib/linear-store.js +35 -8
  16. package/lib/mate-identity-file.js +39 -0
  17. package/lib/mate-knowledge-mcp-server.js +29 -6
  18. package/lib/mate-knowledge-service.js +23 -2
  19. package/lib/mate-knowledge-sync.js +37 -22
  20. package/lib/mate-ready-creation.js +2 -1
  21. package/lib/mate-workspace-color.js +4 -0
  22. package/lib/mates-identity.js +4 -4
  23. package/lib/mates-prompts.js +4 -3
  24. package/lib/mates.js +30 -11
  25. package/lib/os-users.js +5 -16
  26. package/lib/project-connection.js +36 -9
  27. package/lib/project-debate-proposal.js +6 -0
  28. package/lib/project-debate-workspace.js +43 -0
  29. package/lib/project-debate.js +4 -2
  30. package/lib/project-filesystem.js +21 -39
  31. package/lib/project-knowledge.js +315 -136
  32. package/lib/project-mate-creation-proposal.js +1 -0
  33. package/lib/project-mate-interaction.js +7 -6
  34. package/lib/project-server-settings.js +31 -0
  35. package/lib/project-sessions.js +92 -182
  36. package/lib/project-user-knowledge.js +60 -0
  37. package/lib/project.js +62 -21
  38. package/lib/public/app.js +79 -71
  39. package/lib/public/clay-studio-wordmark-bold.svg +20 -0
  40. package/lib/public/css/clay-workspace.css +105 -0
  41. package/lib/public/css/debates-workbench.css +47 -0
  42. package/lib/public/css/home-debate-live.css +26 -0
  43. package/lib/public/css/home-hub.css +3 -1
  44. package/lib/public/css/home-mate-model-picker.css +15 -131
  45. package/lib/public/css/home-mate-settings.css +168 -16
  46. package/lib/public/css/icon-strip.css +249 -32
  47. package/lib/public/css/input.css +3 -1
  48. package/lib/public/css/mate-conversation.css +176 -0
  49. package/lib/public/css/mate-knowledge-workbench.css +118 -0
  50. package/lib/public/css/mate-workspace-color.css +16 -0
  51. package/lib/public/css/mates.css +28 -28
  52. package/lib/public/css/menus.css +3 -0
  53. package/lib/public/css/messages.css +1 -114
  54. package/lib/public/css/mobile-nav.css +16 -16
  55. package/lib/public/css/overlays.css +15 -0
  56. package/lib/public/css/project-logs.css +7 -0
  57. package/lib/public/css/project-mate-creation.css +105 -0
  58. package/lib/public/css/project-settings-modal.css +56 -0
  59. package/lib/public/css/right-workbench.css +3 -3
  60. package/lib/public/css/server-settings-system.css +125 -0
  61. package/lib/public/css/session-folders.css +80 -21
  62. package/lib/public/css/session-github.css +25 -16
  63. package/lib/public/css/settings-ui.css +247 -22
  64. package/lib/public/css/shared-browser.css +7 -3
  65. package/lib/public/css/sidebar.css +183 -107
  66. package/lib/public/css/title-bar.css +156 -1
  67. package/lib/public/css/worker-delegation.css +47 -0
  68. package/lib/public/css/worker-proposal.css +72 -150
  69. package/lib/public/css/you.css +74 -0
  70. package/lib/public/index.html +206 -548
  71. package/lib/public/modules/account-island.js +232 -0
  72. package/lib/public/modules/admin-settings-email.js +197 -0
  73. package/lib/public/modules/admin-settings-invitations.js +183 -0
  74. package/lib/public/modules/admin-settings-projects.js +198 -0
  75. package/lib/public/modules/admin-settings-ui.js +100 -0
  76. package/lib/public/modules/admin-settings-users.js +331 -0
  77. package/lib/public/modules/admin.js +19 -949
  78. package/lib/public/modules/app-cursors.js +19 -43
  79. package/lib/public/modules/app-debate-ui.js +4 -3
  80. package/lib/public/modules/app-dm.js +0 -2
  81. package/lib/public/modules/app-header.js +106 -0
  82. package/lib/public/modules/app-message-router.js +12 -3
  83. package/lib/public/modules/app-messages.js +47 -61
  84. package/lib/public/modules/app-notifications.js +4 -7
  85. package/lib/public/modules/app-projects.js +5 -38
  86. package/lib/public/modules/app-rate-limit.js +70 -12
  87. package/lib/public/modules/app-rendering.js +12 -13
  88. package/lib/public/modules/avatar.js +30 -9
  89. package/lib/public/modules/chat-render-runtime.js +16 -4
  90. package/lib/public/modules/command-palette.js +2 -15
  91. package/lib/public/modules/context-sources.js +27 -18
  92. package/lib/public/modules/cursor-sharing-preference.js +3 -3
  93. package/lib/public/modules/debate-controls.js +299 -0
  94. package/lib/public/modules/debate.js +2 -0
  95. package/lib/public/modules/debates-workbench.js +339 -0
  96. package/lib/public/modules/default-ai.js +88 -65
  97. package/lib/public/modules/header-tui-font.js +10 -20
  98. package/lib/public/modules/home-debate-controls.js +18 -289
  99. package/lib/public/modules/home-mate-avatar-editor.js +228 -0
  100. package/lib/public/modules/home-mate-color-picker.js +66 -0
  101. package/lib/public/modules/home-mate-creation.js +1 -1
  102. package/lib/public/modules/home-mate-instructions.js +90 -0
  103. package/lib/public/modules/home-mate-model-picker.js +80 -271
  104. package/lib/public/modules/home-mate-profile-editor.js +72 -0
  105. package/lib/public/modules/home-mate-settings.js +118 -125
  106. package/lib/public/modules/home-shell.js +3 -0
  107. package/lib/public/modules/home-surface-boot.js +1 -9
  108. package/lib/public/modules/icon-strip-tooltip.js +95 -0
  109. package/lib/public/modules/knowledge-db-edit-state.js +35 -0
  110. package/lib/public/modules/knowledge-db-workbench.js +234 -0
  111. package/lib/public/modules/knowledge-history-state.js +55 -0
  112. package/lib/public/modules/knowledge-markdown-editor.js +57 -0
  113. package/lib/public/modules/knowledge-preview-links.js +70 -0
  114. package/lib/public/modules/knowledge-workbench-shell.js +21 -0
  115. package/lib/public/modules/linear-settings.js +3 -2
  116. package/lib/public/modules/markdown.js +5 -6
  117. package/lib/public/modules/mate-knowledge-workbench-state.js +174 -0
  118. package/lib/public/modules/mate-knowledge-workbench.js +500 -0
  119. package/lib/public/modules/mate-knowledge.js +11 -3
  120. package/lib/public/modules/mate-management.js +54 -10
  121. package/lib/public/modules/mate-sidebar.js +11 -1
  122. package/lib/public/modules/mate-workspace-color.js +33 -0
  123. package/lib/public/modules/mention.js +5 -13
  124. package/lib/public/modules/notification-settings.js +81 -0
  125. package/lib/public/modules/notifications.js +44 -28
  126. package/lib/public/modules/personal-settings-dialog.js +51 -0
  127. package/lib/public/modules/profile.js +66 -24
  128. package/lib/public/modules/project-chat-identity.js +79 -0
  129. package/lib/public/modules/project-debate-planning-workspace.js +250 -0
  130. package/lib/public/modules/project-debate-workspace.js +185 -0
  131. package/lib/public/modules/project-header-identity.js +127 -0
  132. package/lib/public/modules/project-logs-render.js +9 -4
  133. package/lib/public/modules/project-mate-creation-workspace.js +414 -0
  134. package/lib/public/modules/project-mate-navigation.js +238 -0
  135. package/lib/public/modules/project-session-navigation.js +29 -0
  136. package/lib/public/modules/project-settings-environment.js +106 -0
  137. package/lib/public/modules/project-settings.js +72 -628
  138. package/lib/public/modules/project-switcher.js +6 -8
  139. package/lib/public/modules/projects-hub-view.js +5 -6
  140. package/lib/public/modules/projects-hub.js +2 -6
  141. package/lib/public/modules/qrcode.js +27 -8
  142. package/lib/public/modules/search-clay-chat.js +8 -11
  143. package/lib/public/modules/server-settings-controls.js +316 -0
  144. package/lib/public/modules/server-settings-environment.js +194 -0
  145. package/lib/public/modules/server-settings-navigation.js +39 -0
  146. package/lib/public/modules/server-settings-state.js +54 -0
  147. package/lib/public/modules/server-settings.js +116 -788
  148. package/lib/public/modules/server-storage-settings.js +84 -0
  149. package/lib/public/modules/session-activity-tooltip.js +12 -0
  150. package/lib/public/modules/session-create-form.js +86 -14
  151. package/lib/public/modules/session-create.js +50 -7
  152. package/lib/public/modules/session-github.js +2 -1
  153. package/lib/public/modules/session-list-visibility.js +41 -0
  154. package/lib/public/modules/session-presence-ui.js +3 -0
  155. package/lib/public/modules/session-title-marquee.js +51 -0
  156. package/lib/public/modules/settings-env-values.js +34 -0
  157. package/lib/public/modules/settings-select.js +121 -0
  158. package/lib/public/modules/shared-browser.js +22 -12
  159. package/lib/public/modules/sidebar-mates.js +3 -50
  160. package/lib/public/modules/sidebar-mobile.js +63 -60
  161. package/lib/public/modules/sidebar-projects.js +84 -97
  162. package/lib/public/modules/sidebar-session-folders-ui.js +2 -2
  163. package/lib/public/modules/sidebar-session-hierarchy.js +149 -70
  164. package/lib/public/modules/sidebar-sessions.js +49 -50
  165. package/lib/public/modules/sidebar.js +11 -13
  166. package/lib/public/modules/stt-picker.js +14 -5
  167. package/lib/public/modules/terminal-prefs.js +22 -0
  168. package/lib/public/modules/theme.js +88 -52
  169. package/lib/public/modules/thinking-lifecycle.js +7 -6
  170. package/lib/public/modules/tool-palette-order.js +4 -3
  171. package/lib/public/modules/tool-palette.js +13 -3
  172. package/lib/public/modules/tools.js +22 -22
  173. package/lib/public/modules/user-settings-account.js +318 -0
  174. package/lib/public/modules/user-settings-appearance.js +55 -0
  175. package/lib/public/modules/user-settings-editors.js +24 -0
  176. package/lib/public/modules/user-settings-integrations.js +104 -0
  177. package/lib/public/modules/user-settings-layout.js +80 -0
  178. package/lib/public/modules/user-settings-preferences.js +48 -0
  179. package/lib/public/modules/user-settings-profile.js +53 -0
  180. package/lib/public/modules/user-settings-themes.js +38 -0
  181. package/lib/public/modules/user-settings-voice.js +75 -0
  182. package/lib/public/modules/user-settings.js +53 -480
  183. package/lib/public/modules/worker-proposal.js +42 -8
  184. package/lib/public/modules/worker-runtime-picker.js +115 -0
  185. package/lib/public/modules/you-activity.js +121 -0
  186. package/lib/public/modules/you.js +251 -0
  187. package/lib/public/style.css +20 -8
  188. package/lib/public/vendor/codemirror/LICENSE +21 -0
  189. package/lib/public/vendor/codemirror/codemirror.css +344 -0
  190. package/lib/public/vendor/codemirror/codemirror.js +9884 -0
  191. package/lib/public/vendor/codemirror/continuelist.js +101 -0
  192. package/lib/public/vendor/codemirror/markdown.js +886 -0
  193. package/lib/public/vendor/codemirror/show-hint.css +37 -0
  194. package/lib/public/vendor/codemirror/show-hint.js +523 -0
  195. package/lib/sdk-bridge.js +15 -3
  196. package/lib/sdk-message-processor.js +16 -4
  197. package/lib/sdk-quota-snapshot.js +37 -0
  198. package/lib/server-admin.js +11 -0
  199. package/lib/server-appearance-settings.js +71 -0
  200. package/lib/server-home-chat-events.js +1 -1
  201. package/lib/server-home-chat.js +25 -3
  202. package/lib/server-home-debate-planning.js +4 -1
  203. package/lib/server-home-debates.js +2 -0
  204. package/lib/server-home-mate-creation.js +4 -1
  205. package/lib/server-linear.js +14 -8
  206. package/lib/server-mate-instructions.js +44 -0
  207. package/lib/server-mates.js +14 -4
  208. package/lib/server-notification-settings.js +67 -0
  209. package/lib/server-palette.js +1 -1
  210. package/lib/server-settings.js +69 -1
  211. package/lib/server-storage.js +57 -0
  212. package/lib/server-user-knowledge.js +119 -0
  213. package/lib/server.js +31 -3
  214. package/lib/session-visibility.js +7 -2
  215. package/lib/sessions.js +35 -10
  216. package/lib/startup-projects.js +40 -0
  217. package/lib/storage-native-walk.js +85 -0
  218. package/lib/storage-usage.js +144 -0
  219. package/lib/user-knowledge-service.js +167 -0
  220. package/lib/users-permissions.js +7 -0
  221. package/lib/users-preferences.js +0 -74
  222. package/lib/users-project-mate-filter-preferences.js +39 -0
  223. package/lib/users.js +6 -4
  224. package/lib/workspace-query-access.js +4 -1
  225. package/lib/ws-schema.js +52 -4
  226. package/lib/yoke/acp-process-manager.js +2 -2
  227. package/lib/yoke/adapters/codex.js +12 -52
  228. package/lib/yoke/codex-app-server.js +2 -2
  229. package/lib/yoke/codex-model-catalog.js +52 -0
  230. package/lib/yoke/codex-rate-limits.js +26 -0
  231. package/package.json +12 -3
package/lib/mates.js CHANGED
@@ -1,7 +1,9 @@
1
+ var resolveMateIdentityFile = require('./mate-identity-file').resolveMateIdentityFile;
1
2
  var fs = require("fs");
2
3
  var path = require("path");
3
4
  var crypto = require("crypto");
4
5
  var config = require("./config");
6
+ var workspaceColor = require("./mate-workspace-color");
5
7
 
6
8
  var crisisSafety = require("./crisis-safety");
7
9
  var matesPrompts = require("./mates-prompts");
@@ -160,7 +162,7 @@ function createMate(ctx, seedData) {
160
162
  yaml += "model: " + JSON.stringify(seedData.model || null) + "\n";
161
163
  fs.writeFileSync(path.join(mateDir, "mate.yaml"), yaml);
162
164
 
163
- // Write initial CLAUDE.md (will be replaced by interview)
165
+ // Write initial AGENTS.md (will be replaced by interview)
164
166
  var claudeMd = "# Mate Identity\n\n";
165
167
  claudeMd += "This mate is currently being interviewed. Identity will be generated after the interview.\n\n";
166
168
  claudeMd += "## Seed Data\n\n";
@@ -175,7 +177,7 @@ function createMate(ctx, seedData) {
175
177
  claudeMd += TEAM_SECTION;
176
178
  claudeMd += SESSION_MEMORY_SECTION;
177
179
  claudeMd += crisisSafety.getSection();
178
- fs.writeFileSync(path.join(mateDir, "CLAUDE.md"), claudeMd);
180
+ fs.writeFileSync(resolveMateIdentityFile(mateDir), claudeMd);
179
181
 
180
182
  // Log creation (identity is placeholder, will be replaced by interview)
181
183
  logIdentityChange(mateDir, "create_custom", initialIdentity, "");
@@ -192,9 +194,17 @@ function getMate(ctx, id) {
192
194
  }
193
195
 
194
196
  function updateMate(ctx, id, updates) {
197
+ updates = Object.assign({}, updates);
198
+ delete updates.id;
199
+ delete updates.createdBy;
200
+ delete updates.builtinKey;
195
201
  var data = loadMates(ctx);
196
202
  for (var i = 0; i < data.mates.length; i++) {
197
203
  if (data.mates[i].id === id) {
204
+ if (Object.prototype.hasOwnProperty.call(updates, "workspaceColor")) {
205
+ if (data.mates[i].primary || data.mates[i].builtinKey === "clay") delete updates.workspaceColor;
206
+ else if (!workspaceColor.valid(updates.workspaceColor)) return { error: "Choose a supported workspace color." };
207
+ }
198
208
  // Primary mates: protect name and core identity fields
199
209
  if (data.mates[i].primary) {
200
210
  delete updates.name;
@@ -206,6 +216,15 @@ function updateMate(ctx, id, updates) {
206
216
  delete updates.profile.displayName;
207
217
  }
208
218
  }
219
+ // Locked built-in artwork is server-managed. Preserve it even when a
220
+ // stale or crafted client submits a replacement profile object.
221
+ if (data.mates[i].profile && data.mates[i].profile.avatarLocked && updates.profile) {
222
+ updates.profile.avatarStyle = data.mates[i].profile.avatarStyle;
223
+ updates.profile.avatarSeed = data.mates[i].profile.avatarSeed;
224
+ updates.profile.avatarColor = data.mates[i].profile.avatarColor;
225
+ updates.profile.avatarCustom = data.mates[i].profile.avatarCustom;
226
+ updates.profile.avatarLocked = true;
227
+ }
209
228
  var keys = Object.keys(updates);
210
229
  for (var j = 0; j < keys.length; j++) {
211
230
  data.mates[i][keys[j]] = updates[keys[j]];
@@ -326,7 +345,7 @@ function migrateLegacyMates() {
326
345
  }
327
346
 
328
347
  /**
329
- * Strip all system sections from CLAUDE.md content, returning only identity.
348
+ * Strip all system sections from AGENTS.md content, returning only identity.
330
349
  */
331
350
  function stripAllSystemSections(content) {
332
351
  return extractIdentity(content);
@@ -336,10 +355,10 @@ function stripAllSystemSections(content) {
336
355
  var _warnedPaths = {};
337
356
 
338
357
  /**
339
- * Atomic enforce: read CLAUDE.md once, enforce all system sections, write once.
358
+ * Atomic enforce: read AGENTS.md once, enforce all system sections, write once.
340
359
  * Includes identity backup, validation, and change tracking.
341
360
  * Returns true if the file was modified, false if already correct.
342
- * @param {string} filePath - path to CLAUDE.md
361
+ * @param {string} filePath - path to AGENTS.md
343
362
  * @param {object} opts - optional { ctx, mateId } for dynamic team section
344
363
  */
345
364
  function enforceAllSections(filePath, opts) {
@@ -511,7 +530,7 @@ function createBuiltinMate(ctx, builtinKey) {
511
530
  yaml += "autonomy: " + (seedData.autonomy || "always_ask") + "\n";
512
531
  fs.writeFileSync(path.join(mateDir, "mate.yaml"), yaml);
513
532
 
514
- // Write CLAUDE.md with full template + system sections
533
+ // Write AGENTS.md with full template + system sections
515
534
  var claudeMd = def.getClaudeMd();
516
535
  var builtinIdentity = claudeMd.trimEnd();
517
536
  claudeMd += TEAM_SECTION;
@@ -519,7 +538,7 @@ function createBuiltinMate(ctx, builtinKey) {
519
538
  claudeMd += STICKY_NOTES_SECTION;
520
539
  claudeMd += DEBATE_AWARENESS_SECTION;
521
540
  claudeMd += crisisSafety.getSection();
522
- fs.writeFileSync(path.join(mateDir, "CLAUDE.md"), claudeMd);
541
+ fs.writeFileSync(resolveMateIdentityFile(mateDir), claudeMd);
523
542
 
524
543
  // Backup identity and log creation
525
544
  backupIdentity(mateDir, builtinIdentity);
@@ -603,7 +622,7 @@ function syncArchivedBuiltinMates(ctx) {
603
622
  *
604
623
  * What gets synced:
605
624
  * - Metadata: bio, name, profile, globalSearch, primary flag
606
- * - CLAUDE.md identity: re-applied when templateVersion changes
625
+ * - AGENTS.md identity: re-applied when templateVersion changes
607
626
  * - templateVersion is stored on the mate object to track updates
608
627
  *
609
628
  * What is NOT synced (regular builtin mates):
@@ -653,10 +672,10 @@ function syncPrimaryMates(ctx) {
653
672
  }
654
673
  }
655
674
 
656
- // --- Sync CLAUDE.md identity (only when templateVersion changes) ---
675
+ // --- Sync AGENTS.md identity (only when templateVersion changes) ---
657
676
  // Uses 3-way comparison to preserve user/Ally modifications:
658
677
  // - base-template.md = the template that was last synced (what code wrote)
659
- // - current identity = what's in CLAUDE.md now (may have been modified)
678
+ // - current identity = what's in AGENTS.md now (may have been modified)
660
679
  // - new template = latest code definition
661
680
  //
662
681
  // If current identity === old base → user didn't touch it → safe to replace
@@ -666,7 +685,7 @@ function syncPrimaryMates(ctx) {
666
685
 
667
686
  if (latestVersion > currentVersion) {
668
687
  var mateDir = getMateDir(ctx, mate.id);
669
- var claudePath = path.join(mateDir, "CLAUDE.md");
688
+ var claudePath = resolveMateIdentityFile(mateDir);
670
689
  var basePath = path.join(mateDir, "knowledge", "base-template.md");
671
690
  var latestIdentity = def.getClaudeMd().trimEnd();
672
691
 
package/lib/os-users.js CHANGED
@@ -17,20 +17,11 @@ function isSafeLinuxUsername(username) {
17
17
  // --- Supplementary group inheritance (issue #367) ---
18
18
  // Node's child_process { uid, gid } options call setuid/setgid but never
19
19
  // initgroups(), so spawned sessions drop all supplementary groups (docker,
20
- // video, wheel, ...). When inheritGroups is enabled we route the spawn through
20
+ // video, wheel, ...). Route user-switched spawns through
21
21
  // `setpriv --init-groups` so the child gets the same groups as a normal login.
22
- var _inheritGroups = true; // default: behave like a normal login shell
23
22
  var _setprivAvail = null;
24
23
  var _warnedNoSetpriv = false;
25
24
 
26
- function setInheritGroups(value) {
27
- _inheritGroups = !!value;
28
- }
29
-
30
- function getInheritGroups() {
31
- return _inheritGroups;
32
- }
33
-
34
25
  function setprivAvailable() {
35
26
  if (_setprivAvail !== null) return _setprivAvail;
36
27
  try {
@@ -50,7 +41,7 @@ function setprivAvailable() {
50
41
  * spawn()/execFileSync()/pty.spawn().
51
42
  *
52
43
  * - Same-user spawns (no uid/gid in options) are returned unchanged.
53
- * - When inheritGroups is disabled, or setpriv is unavailable, falls back to
44
+ * - When setpriv is unavailable, falls back to
54
45
  * Node's uid/gid options (primary group only) so behavior degrades safely.
55
46
  * Requires the parent process to be root, which is the case in os-users mode.
56
47
  */
@@ -62,7 +53,7 @@ function wrapSpawnAsUser(command, args, options, setprivCheck) {
62
53
  if (uid == null || gid == null) return { command: command, args: args, options: options };
63
54
 
64
55
  var hasSetpriv = typeof setprivCheck === "function" ? setprivCheck() : setprivAvailable();
65
- if (_inheritGroups && hasSetpriv) {
56
+ if (hasSetpriv) {
66
57
  var wrappedArgs = ["--reuid", String(uid), "--regid", String(gid), "--init-groups", "--", command].concat(args);
67
58
  var newOpts = Object.assign({}, options);
68
59
  delete newOpts.uid;
@@ -70,9 +61,9 @@ function wrapSpawnAsUser(command, args, options, setprivCheck) {
70
61
  return { command: "setpriv", args: wrappedArgs, options: newOpts };
71
62
  }
72
63
 
73
- if (_inheritGroups && !_warnedNoSetpriv) {
64
+ if (!_warnedNoSetpriv) {
74
65
  _warnedNoSetpriv = true;
75
- console.warn("[os-users] inheritGroups enabled but `setpriv` not found; supplementary groups will be dropped. Install util-linux to restore them.");
66
+ console.warn("[os-users] `setpriv` not found; supplementary groups will be dropped. Install util-linux to restore them.");
76
67
  }
77
68
  return { command: command, args: args, options: options };
78
69
  }
@@ -673,8 +664,6 @@ module.exports = {
673
664
  grantAllUsersAccess: grantAllUsersAccess,
674
665
  installClaudeCli: installClaudeCli,
675
666
  claudeAvailableForUser: claudeAvailableForUser,
676
- setInheritGroups: setInheritGroups,
677
- getInheritGroups: getInheritGroups,
678
667
  wrapSpawnAsUser: wrapSpawnAsUser,
679
668
  deactivateLinuxUser: deactivateLinuxUser,
680
669
  ensureProjectsDir: ensureProjectsDir,
@@ -6,6 +6,7 @@ var emailAccounts = require("./email-accounts");
6
6
  var { getCodexConfig } = require("./codex-defaults");
7
7
  var yoke = require("./yoke");
8
8
  var permissionModes = require("./session-permission-mode");
9
+ var sessionProvenance = require("./session-provenance");
9
10
 
10
11
  function dispatchMessageSafely(handleMessage, sendTo, slug, ws, msg) {
11
12
  try {
@@ -45,6 +46,36 @@ function buildInitialModelInfo(sm, initialVendor, initialModel, initialModels, s
45
46
  };
46
47
  }
47
48
 
49
+ function buildRestoredSessionMessage(active, initialEffort, capabilities, permissionState) {
50
+ return {
51
+ type: "session_switched",
52
+ id: active.localId,
53
+ sessionOriginId: active.sessionOriginId || null,
54
+ sessionRole: sessionProvenance.isWorker(active) ? "worker" : "driver",
55
+ cliSessionId: active.cliSessionId || null,
56
+ loop: active.loop || null,
57
+ vendor: active.vendor || null,
58
+ model: active.model || null,
59
+ effort: active.effort || initialEffort || null,
60
+ hasHistory: !!(active.history && active.history.length > 0),
61
+ capabilities: capabilities,
62
+ isProcessing: !!active.isProcessing,
63
+ mateCreation: active.mateCreationMode === true,
64
+ mateCreationPhase: active.homeMateCreationPhase || null,
65
+ debatePlanning: active.debateSetupMode === true,
66
+ debatePhase: active.homeDebatePhase || null,
67
+ mode: active.mode || "gui",
68
+ terminalId: typeof active.terminalId === "number" ? active.terminalId : null,
69
+ runtimeMode: active.runtimeMode || null,
70
+ runtimeTerminalId: typeof active.runtimeTerminalId === "number" ? active.runtimeTerminalId : null,
71
+ tuiSuspended: !!active.tuiSuspended,
72
+ permissionMode: permissionState.requestedPermissionMode,
73
+ effectivePermissionMode: permissionState.effectivePermissionMode,
74
+ permissionCapabilities: permissionState.permissionCapabilities,
75
+ mcpPermissionModeOverrides: permissionState.mcpPermissionModeOverrides,
76
+ };
77
+ }
78
+
48
79
  /**
49
80
  * Attach connection/disconnection handlers to a project context.
50
81
  *
@@ -63,6 +94,8 @@ function attachConnection(ctx) {
63
94
  var cwd = ctx.cwd;
64
95
  var slug = ctx.slug;
65
96
  var isMate = ctx.isMate;
97
+ var mateId = ctx.mateId || null;
98
+ var mateDisplayName = ctx.mateDisplayName || "";
66
99
  var osUsers = ctx.osUsers;
67
100
  var debug = ctx.debug;
68
101
  var dangerouslySkipPermissions = ctx.dangerouslySkipPermissions;
@@ -184,7 +217,7 @@ function attachConnection(ctx) {
184
217
  sm.saveSessionFile(restoredActive);
185
218
  }
186
219
  var initialModels = (sm.modelsByVendor && sm.modelsByVendor[initialVendor]) || sm.availableModels || [];
187
- sendTo(ws, { type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, dangerouslySkipPermissions: dangerouslySkipPermissions, osUsers: osUsers, lanHost: lanHost, projectCount: _filteredProjects.length, projects: _filteredProjects, projectOwnerId: projectOwnerId, ownerLocked: ownerLocked, vendors: yoke.VENDOR_REGISTRY });
220
+ sendTo(ws, { type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, dangerouslySkipPermissions: dangerouslySkipPermissions, osUsers: osUsers, lanHost: lanHost, projectCount: _filteredProjects.length, projects: _filteredProjects, projectOwnerId: projectOwnerId, ownerLocked: ownerLocked, vendors: yoke.VENDOR_REGISTRY, isMate: isMate, mateId: isMate ? mateId : null, mateDisplayName: isMate ? (mateDisplayName || title || project || "Mate") : "" });
188
221
  if (typeof sendCursorSharingState === "function") sendCursorSharingState(ws);
189
222
  // Update notifications are pushed on a scheduled interval (see
190
223
  // scheduleUpdateBroadcast). We no longer push on connect to avoid
@@ -213,13 +246,6 @@ function attachConnection(ctx) {
213
246
  sendTo(ws, { type: "email_accounts_list", accounts: emailAccountsList, providers: emailAccounts.PROVIDER_PRESETS });
214
247
  sendTo(ws, { type: "notes_list", notes: nm.list() });
215
248
  sendTo(ws, { type: "loop_registry_updated", records: getHubSchedules() });
216
- // Initial per-user preference: how to render Claude sessions.
217
- if (usersModule && typeof usersModule.getClaudeOpenMode === "function") {
218
- var _comUid = (wsUser && wsUser.id) || null;
219
- var _comVal = _comUid ? usersModule.getClaudeOpenMode(_comUid) : "gui";
220
- sendTo(ws, { type: "claude_open_mode_changed", claudeOpenMode: _comVal || "gui" });
221
- }
222
-
223
249
  _loop.sendConnectionState(ws);
224
250
  if (_mcp) _mcp.sendConnectionState(ws);
225
251
  if (_notifications) _notifications.sendConnectionState(ws, sendTo);
@@ -262,7 +288,7 @@ function attachConnection(ctx) {
262
288
  }
263
289
  var _vendorCaps = (sm.capabilitiesByVendor && sm.capabilitiesByVendor[active.vendor || sm.defaultVendor || "claude"]) || {};
264
290
  var activePermissionState = permissionModes.clientState(active, sm.currentPermissionMode, sm.defaultVendor);
265
- sendTo(ws, { type: "session_switched", id: active.localId, sessionOriginId: active.sessionOriginId || null, cliSessionId: active.cliSessionId || null, loop: active.loop || null, vendor: active.vendor || null, model: active.model || null, effort: active.effort || initialEffort || null, hasHistory: (active.history && active.history.length > 0), capabilities: _vendorCaps, isProcessing: !!active.isProcessing, mode: active.mode || "gui", terminalId: typeof active.terminalId === "number" ? active.terminalId : null, runtimeMode: active.runtimeMode || null, runtimeTerminalId: typeof active.runtimeTerminalId === "number" ? active.runtimeTerminalId : null, tuiSuspended: !!active.tuiSuspended, permissionMode: activePermissionState.requestedPermissionMode, effectivePermissionMode: activePermissionState.effectivePermissionMode, permissionCapabilities: activePermissionState.permissionCapabilities, mcpPermissionModeOverrides: activePermissionState.mcpPermissionModeOverrides });
291
+ sendTo(ws, buildRestoredSessionMessage(active, initialEffort, _vendorCaps, activePermissionState));
266
292
  if (ctx.autonomousRun) sendTo(ws, { type: "autonomous_run_state", sessionId: active.localId, run: ctx.autonomousRun.project(active.autonomousRun) });
267
293
  // Send per-session context sources
268
294
  var sessionSources = loadContextSources(slug, active.localId);
@@ -382,5 +408,6 @@ function attachConnection(ctx) {
382
408
  module.exports = {
383
409
  attachConnection: attachConnection,
384
410
  buildInitialModelInfo: buildInitialModelInfo,
411
+ buildRestoredSessionMessage: buildRestoredSessionMessage,
385
412
  dispatchMessageSafely: dispatchMessageSafely,
386
413
  };
@@ -227,6 +227,12 @@ function attachDebateProposal(ctx) {
227
227
 
228
228
  var found = findPendingProposal(ws, msg);
229
229
  if (!found) return true;
230
+ if (msg.sessionId != null && (String(found.pending.session.localId) !== String(msg.sessionId)
231
+ || String(ws._clayActiveSession) !== String(msg.sessionId)
232
+ || (found.pending.session.ownerId && (!ws._clayUser || ws._clayUser.id !== found.pending.session.ownerId)))) {
233
+ ctx.sendTo(ws, { type: "debate_error", error: "This proposal belongs to a different session." });
234
+ return true;
235
+ }
230
236
  startApprovedProposal(ws, msg, found).catch(function (error) {
231
237
  delete pendingProposals[found.key];
232
238
  rejectProposal(ws, found.pending, error && error.message ? error.message : "The selected debate models could not be verified.");
@@ -0,0 +1,43 @@
1
+ // Project-bound access to the same participation actions used by Home debates.
2
+ function handleProjectDebateControl(ws, msg, session, engine, sendTo) {
3
+ if (msg.type !== "project_debate_control" && msg.type !== "project_debate_state") return false;
4
+ var userId = ws && ws._clayUser && ws._clayUser.id;
5
+ var valid = session && msg.sessionId != null && String(session.localId) === String(msg.sessionId)
6
+ && (!session.ownerId || session.ownerId === userId);
7
+ if (msg.type === "project_debate_state") {
8
+ sendTo(ws, { type: "project_debate_state", sessionId: msg.sessionId, header: valid ? debateHeader(session) : null });
9
+ return true;
10
+ }
11
+ var ok = false;
12
+ if (valid) {
13
+ if (session.homeDebatePlanning) ok = engine.handleHomeControl(ws, msg, session);
14
+ else if (msg.action === "hand_raise") { engine.handleDebateHandRaise(ws, session); ok = true; }
15
+ else if (msg.action === "stop") ok = engine.handleDebateStop(ws, session);
16
+ else if (msg.action === "cancel_stop") ok = engine.handleDebateCancelStop(ws, session);
17
+ else if (msg.action === "user_floor") { engine.handleDebateUserFloorResponse(ws, msg, session); ok = true; }
18
+ else if (msg.action === "conclude" || msg.action === "resume") {
19
+ engine.handleDebateConcludeResponse(ws, { action: msg.action === "resume" ? "continue" : msg.response, text: msg.text || "" }, session);
20
+ ok = true;
21
+ }
22
+ }
23
+ sendTo(ws, { type: "project_debate_control_result", sessionId: msg.sessionId, ok: !!ok, error: ok ? null : "This debate action is no longer available. Reopen the debate and try again." });
24
+ return true;
25
+ }
26
+ function debateHeader(session) {
27
+ var header = null;
28
+ (session.history || []).forEach(function (event) {
29
+ if (event.type === "debate_started") header = Object.assign({}, event, { role: "debate_header", phase: "live", round: 1, interaction: null, handRaised: false });
30
+ if (!header) return;
31
+ if (event.round) header.round = event.round;
32
+ if (event.type === "debate_conclude_confirm") header.interaction = "conclude";
33
+ if (event.type === "debate_user_floor") header.interaction = "user_floor";
34
+ if (event.type === "debate_user_floor_done") { header.interaction = null; header.handRaised = false; }
35
+ if (event.type === "debate_hand_raised") header.handRaised = true;
36
+ if (event.type === "debate_stop_requested") header.stopping = true;
37
+ if (event.type === "debate_stop_cancelled") header.stopping = false;
38
+ if (event.type === "debate_turn" || event.type === "debate_resumed") { header.interaction = null; header.phase = "live"; }
39
+ if (event.type === "debate_ended") { header.phase = event.reason === "interrupted" ? "interrupted" : "ended"; header.reason = event.reason; header.interaction = null; header.stopping = false; }
40
+ });
41
+ return header;
42
+ }
43
+ module.exports = { handleProjectDebateControl: handleProjectDebateControl, debateHeader: debateHeader };
@@ -1,3 +1,4 @@
1
+ var resolveMateIdentityFile = require('./mate-identity-file').resolveMateIdentityFile;
1
2
  var fs = require("fs");
2
3
  var path = require("path");
3
4
  var crypto = require("crypto");
@@ -15,10 +16,10 @@ var debateToolPolicy = require("./home-debate-tool-policy");
15
16
  */
16
17
  function attachDebate(ctx) {
17
18
 
18
- // For mate projects, enforce latest debate awareness prompt in CLAUDE.md
19
+ // For mate projects, enforce latest debate awareness prompt in AGENTS.md
19
20
  // so mates use the propose_debate MCP tool instead of writing files.
20
21
  if (ctx.isMate) {
21
- var _debateClaudeMdPath = path.join(ctx.cwd, "CLAUDE.md");
22
+ var _debateClaudeMdPath = resolveMateIdentityFile(ctx.cwd);
22
23
  try { matesModule.enforceDebateAwareness(_debateClaudeMdPath); } catch (e) {}
23
24
  }
24
25
 
@@ -1859,6 +1860,7 @@ function attachDebate(ctx) {
1859
1860
  handleDebateHandRaise: handleDebateHandRaise,
1860
1861
  handleDebateComment: handleDebateComment,
1861
1862
  handleDebateStop: handleDebateStop,
1863
+ handleDebateCancelStop: handleDebateCancelStop,
1862
1864
  handleDebateConcludeResponse: handleDebateConcludeResponse,
1863
1865
  handleDebateConfirmBrief: handleDebateConfirmBrief,
1864
1866
  handleDebateUserFloorResponse: handleDebateUserFloorResponse,
@@ -204,26 +204,28 @@ function attachFilesystem(ctx) {
204
204
 
205
205
  // --- Project settings permission gate ---
206
206
  if (msg.type === "get_project_env" || msg.type === "set_project_env" ||
207
- msg.type === "read_global_claude_md" || msg.type === "write_global_claude_md" ||
208
207
  msg.type === "get_shared_env" || msg.type === "set_shared_env" ||
209
208
  msg.type === "transfer_project_owner") {
210
- var globalSetting = /^(read_global_claude_md|write_global_claude_md|get_shared_env|set_shared_env)$/.test(msg.type);
209
+ var globalSetting = /^(get_shared_env|set_shared_env)$/.test(msg.type);
211
210
  var targetSlug = msg.slug || slug;
212
211
  if (!access.hasPermission(ws, "projectSettings") ||
213
212
  (globalSetting ? !access.isAdmin(ws) : !access.canAccessProject(ws, targetSlug))) {
214
- sendTo(ws, { type: "error", text: "Project settings access is not permitted" });
213
+ sendTo(ws, { type: globalSetting ? (msg.type === 'get_shared_env' ? 'shared_env_result' : 'set_shared_env_result') : (msg.type === 'get_project_env' ? 'project_env_result' : msg.type === 'set_project_env' ? 'set_project_env_result' : 'error'), slug: targetSlug, requestId: msg.requestId, ok: false, error: 'Project settings access is not permitted', text: "Project settings access is not permitted" });
215
214
  return true;
216
215
  }
217
216
  }
218
217
 
219
218
  // --- Project environment variables ---
220
219
  if (msg.type === "get_project_env") {
220
+ if (typeof opts.onGetProjectEnv !== "function") { sendTo(ws, { type: "project_env_result", slug: targetSlug, requestId: msg.requestId, error: "Project environment is unavailable in this runtime." }); return true; }
221
221
  var envrc = "";
222
222
  if (typeof opts.onGetProjectEnv === "function") {
223
- var envResult = opts.onGetProjectEnv(targetSlug);
223
+ var envResult;
224
+ try { envResult = opts.onGetProjectEnv(targetSlug); }
225
+ catch (error) { sendTo(ws, { type: "project_env_result", slug: targetSlug, requestId: msg.requestId, error: error.message }); return true; }
224
226
  envrc = envResult.envrc || "";
225
227
  }
226
- sendTo(ws, { type: "project_env_result", slug: msg.slug, envrc: envrc });
228
+ sendTo(ws, { type: "project_env_result", slug: targetSlug, requestId: msg.requestId, envrc: envrc });
227
229
  return true;
228
230
  }
229
231
 
@@ -231,41 +233,16 @@ function attachFilesystem(ctx) {
231
233
  if (typeof opts.onSetProjectEnv === "function") {
232
234
  var envError = validateEnvString(msg.envrc || "");
233
235
  if (envError) {
234
- sendTo(ws, { type: "set_project_env_result", ok: false, slug: msg.slug, error: envError });
236
+ sendTo(ws, { type: "set_project_env_result", ok: false, slug: targetSlug, requestId: msg.requestId, error: envError });
235
237
  return true;
236
238
  }
237
- var setResult = opts.onSetProjectEnv(targetSlug, msg.envrc || "");
239
+ var setResult;
240
+ try { setResult = opts.onSetProjectEnv(targetSlug, msg.envrc || ""); }
241
+ catch (error) { sendTo(ws, { type: "set_project_env_result", slug: targetSlug, requestId: msg.requestId, ok: false, error: error.message }); return true; }
238
242
  if (setResult.ok) onEnvironmentChanged();
239
- sendTo(ws, { type: "set_project_env_result", ok: setResult.ok, slug: msg.slug, error: setResult.error, timing: setResult.ok ? "Applies to newly created coding-agent processes. Active processes keep their current environment." : undefined });
243
+ sendTo(ws, { type: "set_project_env_result", ok: setResult.ok, slug: targetSlug, requestId: msg.requestId, error: setResult.error, timing: setResult.ok ? "Applies to newly created coding-agent processes. Active processes keep their current environment." : undefined });
240
244
  } else {
241
- sendTo(ws, { type: "set_project_env_result", ok: false, error: "Not supported" });
242
- }
243
- return true;
244
- }
245
-
246
- // --- Global CLAUDE.md ---
247
- if (msg.type === "read_global_claude_md") {
248
- var globalMdPath = path.join(require("./config").REAL_HOME, ".claude", "CLAUDE.md");
249
- try {
250
- var globalMdContent = fs.readFileSync(globalMdPath, "utf8");
251
- sendTo(ws, { type: "global_claude_md_result", content: globalMdContent });
252
- } catch (e) {
253
- sendTo(ws, { type: "global_claude_md_result", error: e.message });
254
- }
255
- return true;
256
- }
257
-
258
- if (msg.type === "write_global_claude_md") {
259
- var globalMdDir = path.join(require("./config").REAL_HOME, ".claude");
260
- var globalMdWritePath = path.join(globalMdDir, "CLAUDE.md");
261
- try {
262
- if (!fs.existsSync(globalMdDir)) {
263
- fs.mkdirSync(globalMdDir, { recursive: true });
264
- }
265
- fs.writeFileSync(globalMdWritePath, msg.content || "", "utf8");
266
- sendTo(ws, { type: "write_global_claude_md_result", ok: true });
267
- } catch (e) {
268
- sendTo(ws, { type: "write_global_claude_md_result", ok: false, error: e.message });
245
+ sendTo(ws, { type: "set_project_env_result", slug: targetSlug, requestId: msg.requestId, ok: false, error: "Not supported" });
269
246
  }
270
247
  return true;
271
248
  }
@@ -274,9 +251,12 @@ function attachFilesystem(ctx) {
274
251
  if (msg.type === "get_shared_env") {
275
252
  var sharedEnvrc = "";
276
253
  if (typeof opts.onGetSharedEnv === "function") {
277
- var sharedResult = opts.onGetSharedEnv();
278
- sharedEnvrc = sharedResult.envrc || "";
254
+ try {
255
+ var sharedResult = opts.onGetSharedEnv();
256
+ sharedEnvrc = sharedResult.envrc || "";
257
+ } catch (error) { sendTo(ws, { type: "shared_env_result", error: error.message }); return true; }
279
258
  }
259
+ if (typeof opts.onGetSharedEnv !== "function") { sendTo(ws, { type: "shared_env_result", error: "Shared environment is unavailable in this runtime." }); return true; }
280
260
  sendTo(ws, { type: "shared_env_result", envrc: sharedEnvrc });
281
261
  return true;
282
262
  }
@@ -288,7 +268,9 @@ function attachFilesystem(ctx) {
288
268
  sendTo(ws, { type: "set_shared_env_result", ok: false, error: sharedEnvError });
289
269
  return true;
290
270
  }
291
- var sharedSetResult = opts.onSetSharedEnv(msg.envrc || "");
271
+ var sharedSetResult;
272
+ try { sharedSetResult = opts.onSetSharedEnv(msg.envrc || ""); }
273
+ catch (error) { sendTo(ws, { type: "set_shared_env_result", ok: false, error: error.message }); return true; }
292
274
  if (sharedSetResult.ok) onEnvironmentChanged();
293
275
  sendTo(ws, { type: "set_shared_env_result", ok: sharedSetResult.ok, error: sharedSetResult.error, timing: sharedSetResult.ok ? "Applies to newly created coding-agent processes. Active processes keep their current environment." : undefined });
294
276
  } else {