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

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 (222) 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/daemon-project-access.js +7 -1
  5. package/lib/daemon-settings.js +11 -0
  6. package/lib/daemon.js +10 -37
  7. package/lib/dicebear-avatar.js +62 -0
  8. package/lib/knowledge-db-service.js +175 -0
  9. package/lib/knowledge-db-validation.js +0 -0
  10. package/lib/knowledge-document-index.js +49 -0
  11. package/lib/knowledge-draft-store.js +56 -0
  12. package/lib/knowledge-file-store.js +199 -0
  13. package/lib/knowledge-markdown.js +128 -0
  14. package/lib/linear-store.js +35 -8
  15. package/lib/mate-knowledge-mcp-server.js +29 -6
  16. package/lib/mate-knowledge-service.js +23 -2
  17. package/lib/mate-knowledge-sync.js +36 -21
  18. package/lib/mate-workspace-color.js +4 -0
  19. package/lib/mates.js +18 -0
  20. package/lib/os-users.js +5 -16
  21. package/lib/project-connection.js +36 -9
  22. package/lib/project-debate-proposal.js +6 -0
  23. package/lib/project-debate-workspace.js +43 -0
  24. package/lib/project-debate.js +1 -0
  25. package/lib/project-filesystem.js +21 -39
  26. package/lib/project-knowledge.js +315 -136
  27. package/lib/project-mate-creation-proposal.js +1 -0
  28. package/lib/project-mate-interaction.js +2 -2
  29. package/lib/project-server-settings.js +31 -0
  30. package/lib/project-sessions.js +92 -182
  31. package/lib/project-user-knowledge.js +60 -0
  32. package/lib/project.js +59 -18
  33. package/lib/public/app.js +79 -71
  34. package/lib/public/clay-studio-wordmark-bold.svg +20 -0
  35. package/lib/public/css/clay-workspace.css +105 -0
  36. package/lib/public/css/debates-workbench.css +47 -0
  37. package/lib/public/css/home-debate-live.css +26 -0
  38. package/lib/public/css/home-hub.css +3 -1
  39. package/lib/public/css/home-mate-model-picker.css +15 -131
  40. package/lib/public/css/home-mate-settings.css +168 -16
  41. package/lib/public/css/icon-strip.css +249 -32
  42. package/lib/public/css/input.css +3 -1
  43. package/lib/public/css/mate-conversation.css +176 -0
  44. package/lib/public/css/mate-knowledge-workbench.css +118 -0
  45. package/lib/public/css/mate-workspace-color.css +16 -0
  46. package/lib/public/css/mates.css +28 -28
  47. package/lib/public/css/messages.css +1 -114
  48. package/lib/public/css/mobile-nav.css +16 -16
  49. package/lib/public/css/overlays.css +8 -0
  50. package/lib/public/css/project-logs.css +7 -0
  51. package/lib/public/css/project-mate-creation.css +105 -0
  52. package/lib/public/css/project-settings-modal.css +56 -0
  53. package/lib/public/css/right-workbench.css +3 -3
  54. package/lib/public/css/server-settings-system.css +125 -0
  55. package/lib/public/css/session-folders.css +80 -21
  56. package/lib/public/css/session-github.css +25 -16
  57. package/lib/public/css/settings-ui.css +247 -22
  58. package/lib/public/css/shared-browser.css +2 -0
  59. package/lib/public/css/sidebar.css +183 -107
  60. package/lib/public/css/title-bar.css +156 -1
  61. package/lib/public/css/worker-delegation.css +47 -0
  62. package/lib/public/css/worker-proposal.css +72 -150
  63. package/lib/public/css/you.css +74 -0
  64. package/lib/public/index.html +203 -547
  65. package/lib/public/modules/account-island.js +232 -0
  66. package/lib/public/modules/admin-settings-email.js +197 -0
  67. package/lib/public/modules/admin-settings-invitations.js +183 -0
  68. package/lib/public/modules/admin-settings-projects.js +198 -0
  69. package/lib/public/modules/admin-settings-ui.js +100 -0
  70. package/lib/public/modules/admin-settings-users.js +331 -0
  71. package/lib/public/modules/admin.js +19 -949
  72. package/lib/public/modules/app-cursors.js +19 -43
  73. package/lib/public/modules/app-debate-ui.js +4 -3
  74. package/lib/public/modules/app-dm.js +0 -2
  75. package/lib/public/modules/app-header.js +106 -0
  76. package/lib/public/modules/app-message-router.js +12 -3
  77. package/lib/public/modules/app-messages.js +47 -61
  78. package/lib/public/modules/app-notifications.js +4 -7
  79. package/lib/public/modules/app-projects.js +5 -38
  80. package/lib/public/modules/app-rate-limit.js +70 -12
  81. package/lib/public/modules/app-rendering.js +12 -13
  82. package/lib/public/modules/avatar.js +25 -9
  83. package/lib/public/modules/chat-render-runtime.js +16 -4
  84. package/lib/public/modules/command-palette.js +2 -15
  85. package/lib/public/modules/context-sources.js +27 -18
  86. package/lib/public/modules/cursor-sharing-preference.js +3 -3
  87. package/lib/public/modules/debate-controls.js +299 -0
  88. package/lib/public/modules/debate.js +2 -0
  89. package/lib/public/modules/debates-workbench.js +339 -0
  90. package/lib/public/modules/default-ai.js +88 -65
  91. package/lib/public/modules/header-tui-font.js +10 -20
  92. package/lib/public/modules/home-debate-controls.js +18 -289
  93. package/lib/public/modules/home-mate-avatar-editor.js +228 -0
  94. package/lib/public/modules/home-mate-color-picker.js +66 -0
  95. package/lib/public/modules/home-mate-creation.js +1 -1
  96. package/lib/public/modules/home-mate-instructions.js +90 -0
  97. package/lib/public/modules/home-mate-model-picker.js +80 -271
  98. package/lib/public/modules/home-mate-profile-editor.js +72 -0
  99. package/lib/public/modules/home-mate-settings.js +118 -125
  100. package/lib/public/modules/home-shell.js +3 -0
  101. package/lib/public/modules/home-surface-boot.js +1 -9
  102. package/lib/public/modules/icon-strip-tooltip.js +95 -0
  103. package/lib/public/modules/knowledge-db-edit-state.js +35 -0
  104. package/lib/public/modules/knowledge-db-workbench.js +234 -0
  105. package/lib/public/modules/knowledge-history-state.js +55 -0
  106. package/lib/public/modules/knowledge-markdown-editor.js +57 -0
  107. package/lib/public/modules/knowledge-preview-links.js +70 -0
  108. package/lib/public/modules/knowledge-workbench-shell.js +21 -0
  109. package/lib/public/modules/linear-settings.js +3 -2
  110. package/lib/public/modules/markdown.js +5 -6
  111. package/lib/public/modules/mate-knowledge-workbench-state.js +174 -0
  112. package/lib/public/modules/mate-knowledge-workbench.js +500 -0
  113. package/lib/public/modules/mate-knowledge.js +11 -3
  114. package/lib/public/modules/mate-management.js +54 -10
  115. package/lib/public/modules/mate-sidebar.js +11 -1
  116. package/lib/public/modules/mate-workspace-color.js +33 -0
  117. package/lib/public/modules/mention.js +5 -13
  118. package/lib/public/modules/notification-settings.js +81 -0
  119. package/lib/public/modules/notifications.js +44 -28
  120. package/lib/public/modules/personal-settings-dialog.js +51 -0
  121. package/lib/public/modules/profile.js +66 -24
  122. package/lib/public/modules/project-chat-identity.js +79 -0
  123. package/lib/public/modules/project-debate-planning-workspace.js +250 -0
  124. package/lib/public/modules/project-debate-workspace.js +185 -0
  125. package/lib/public/modules/project-header-identity.js +127 -0
  126. package/lib/public/modules/project-logs-render.js +9 -4
  127. package/lib/public/modules/project-mate-creation-workspace.js +414 -0
  128. package/lib/public/modules/project-mate-navigation.js +238 -0
  129. package/lib/public/modules/project-session-navigation.js +29 -0
  130. package/lib/public/modules/project-settings-environment.js +106 -0
  131. package/lib/public/modules/project-settings.js +72 -628
  132. package/lib/public/modules/project-switcher.js +6 -8
  133. package/lib/public/modules/projects-hub-view.js +5 -6
  134. package/lib/public/modules/projects-hub.js +2 -6
  135. package/lib/public/modules/qrcode.js +27 -8
  136. package/lib/public/modules/search-clay-chat.js +8 -11
  137. package/lib/public/modules/server-settings-controls.js +316 -0
  138. package/lib/public/modules/server-settings-environment.js +194 -0
  139. package/lib/public/modules/server-settings-navigation.js +39 -0
  140. package/lib/public/modules/server-settings-state.js +54 -0
  141. package/lib/public/modules/server-settings.js +116 -788
  142. package/lib/public/modules/server-storage-settings.js +84 -0
  143. package/lib/public/modules/session-activity-tooltip.js +12 -0
  144. package/lib/public/modules/session-create-form.js +86 -14
  145. package/lib/public/modules/session-create.js +50 -7
  146. package/lib/public/modules/session-github.js +2 -1
  147. package/lib/public/modules/session-list-visibility.js +41 -0
  148. package/lib/public/modules/session-presence-ui.js +3 -0
  149. package/lib/public/modules/session-title-marquee.js +51 -0
  150. package/lib/public/modules/settings-env-values.js +34 -0
  151. package/lib/public/modules/settings-select.js +121 -0
  152. package/lib/public/modules/shared-browser.js +10 -7
  153. package/lib/public/modules/sidebar-mates.js +3 -50
  154. package/lib/public/modules/sidebar-mobile.js +63 -60
  155. package/lib/public/modules/sidebar-projects.js +84 -97
  156. package/lib/public/modules/sidebar-session-folders-ui.js +2 -2
  157. package/lib/public/modules/sidebar-session-hierarchy.js +149 -70
  158. package/lib/public/modules/sidebar-sessions.js +49 -50
  159. package/lib/public/modules/sidebar.js +11 -13
  160. package/lib/public/modules/stt-picker.js +14 -5
  161. package/lib/public/modules/terminal-prefs.js +22 -0
  162. package/lib/public/modules/theme.js +88 -52
  163. package/lib/public/modules/thinking-lifecycle.js +7 -6
  164. package/lib/public/modules/tool-palette-order.js +4 -3
  165. package/lib/public/modules/tool-palette.js +13 -3
  166. package/lib/public/modules/tools.js +22 -22
  167. package/lib/public/modules/user-settings-account.js +318 -0
  168. package/lib/public/modules/user-settings-appearance.js +55 -0
  169. package/lib/public/modules/user-settings-editors.js +24 -0
  170. package/lib/public/modules/user-settings-integrations.js +104 -0
  171. package/lib/public/modules/user-settings-layout.js +80 -0
  172. package/lib/public/modules/user-settings-preferences.js +48 -0
  173. package/lib/public/modules/user-settings-profile.js +53 -0
  174. package/lib/public/modules/user-settings-themes.js +38 -0
  175. package/lib/public/modules/user-settings-voice.js +75 -0
  176. package/lib/public/modules/user-settings.js +53 -480
  177. package/lib/public/modules/worker-proposal.js +42 -8
  178. package/lib/public/modules/worker-runtime-picker.js +115 -0
  179. package/lib/public/modules/you-activity.js +121 -0
  180. package/lib/public/modules/you.js +251 -0
  181. package/lib/public/style.css +20 -8
  182. package/lib/public/vendor/codemirror/LICENSE +21 -0
  183. package/lib/public/vendor/codemirror/codemirror.css +344 -0
  184. package/lib/public/vendor/codemirror/codemirror.js +9884 -0
  185. package/lib/public/vendor/codemirror/continuelist.js +101 -0
  186. package/lib/public/vendor/codemirror/markdown.js +886 -0
  187. package/lib/public/vendor/codemirror/show-hint.css +37 -0
  188. package/lib/public/vendor/codemirror/show-hint.js +523 -0
  189. package/lib/sdk-bridge.js +15 -3
  190. package/lib/sdk-message-processor.js +16 -4
  191. package/lib/sdk-quota-snapshot.js +37 -0
  192. package/lib/server-admin.js +11 -0
  193. package/lib/server-appearance-settings.js +71 -0
  194. package/lib/server-home-chat-events.js +1 -1
  195. package/lib/server-home-chat.js +25 -3
  196. package/lib/server-home-debate-planning.js +4 -1
  197. package/lib/server-home-debates.js +2 -0
  198. package/lib/server-home-mate-creation.js +4 -1
  199. package/lib/server-linear.js +14 -8
  200. package/lib/server-mate-instructions.js +44 -0
  201. package/lib/server-mates.js +12 -3
  202. package/lib/server-notification-settings.js +67 -0
  203. package/lib/server-palette.js +1 -1
  204. package/lib/server-settings.js +69 -1
  205. package/lib/server-storage.js +56 -0
  206. package/lib/server-user-knowledge.js +119 -0
  207. package/lib/server.js +31 -3
  208. package/lib/session-visibility.js +7 -2
  209. package/lib/sessions.js +34 -9
  210. package/lib/startup-projects.js +40 -0
  211. package/lib/storage-usage.js +127 -0
  212. package/lib/user-knowledge-service.js +167 -0
  213. package/lib/users-permissions.js +7 -0
  214. package/lib/users-preferences.js +0 -74
  215. package/lib/users-project-mate-filter-preferences.js +39 -0
  216. package/lib/users.js +6 -4
  217. package/lib/workspace-query-access.js +4 -1
  218. package/lib/ws-schema.js +52 -4
  219. package/lib/yoke/adapters/codex.js +8 -45
  220. package/lib/yoke/codex-model-catalog.js +40 -0
  221. package/lib/yoke/codex-rate-limits.js +26 -0
  222. package/package.json +12 -3
@@ -23,7 +23,7 @@ var config = require("./config");
23
23
  var knowledgeImport = require("./knowledge-import");
24
24
 
25
25
  var SOURCE_NAMESPACE = "mate-knowledge";
26
- var SOURCE_VERSION = 2;
26
+ var SOURCE_VERSION = 3;
27
27
  var MAX_SOURCE_BYTES = 64 * 1024 * 1024;
28
28
 
29
29
  // Identity and template artifacts. Never mirrored, at any version.
@@ -84,6 +84,13 @@ function sourceKindFor(name) {
84
84
  return null;
85
85
  }
86
86
 
87
+ function safeSourceName(value) {
88
+ var name = String(value || "").replace(/\\/g, "/").replace(/^\.\//, ""); var parts = name.split("/");
89
+ if (!name || name[0] === "/" || name.length > 500 || parts.length > 20) return null;
90
+ if (parts.some(function (part) { return !part || part === "." || part === ".." || part.length > 120; })) return null;
91
+ return parts.join("/");
92
+ }
93
+
87
94
  function isJournal(name) {
88
95
  return !!JOURNAL_KINDS[name] || /\.jsonl$/.test(name);
89
96
  }
@@ -133,9 +140,8 @@ function isMateSourceEntry(entry) {
133
140
  if (typeof source.sourceVersion !== "number" || source.sourceVersion > SOURCE_VERSION) return false;
134
141
  var name = source.fileName;
135
142
  if (!name || typeof name !== "string") return false;
136
- // The file name and the relative path must agree, so neither field alone is
137
- // trusted, and the name must be a plain basename.
138
- if (path.basename(name) !== name) return false;
143
+ // The source name and relative path must agree, so neither field alone is trusted.
144
+ if (safeSourceName(name) !== name) return false;
139
145
  return source.relPath === relativeSource(name);
140
146
  }
141
147
 
@@ -180,6 +186,18 @@ function readStat(filePath) {
180
186
  }
181
187
  }
182
188
 
189
+ function safeKnowledgeFileStat(root, name) {
190
+ var parts = name.split("/"); var cursor = root;
191
+ for (var i = 0; i < parts.length; i++) {
192
+ cursor = path.join(cursor, parts[i]); var stat;
193
+ try { stat = fs.lstatSync(cursor); } catch (error) { if (error.code === "ENOENT") return null; throw error; }
194
+ if (stat.isSymbolicLink()) throw new Error("Knowledge source contains a symbolic link.");
195
+ if (i < parts.length - 1 && !stat.isDirectory()) throw new Error("Knowledge source path is not a directory.");
196
+ if (i === parts.length - 1 && !stat.isFile()) return null;
197
+ }
198
+ return fs.statSync(cursor);
199
+ }
200
+
183
201
  // --- File-addressed sources -------------------------------------------
184
202
 
185
203
  function syncFile(importer, scope, name, kind, filePath, stat, actor, summary) {
@@ -361,8 +379,8 @@ function syncMateSource(opts) {
361
379
  var scope = options.scope || scopeForMateDir(options.mateDir);
362
380
  if (!scope) return summary;
363
381
  if (options.mateName) scope.mateName = options.mateName;
364
- var name = path.basename(String(options.fileName || ""));
365
- if (!name) return summary;
382
+ var rawName = String(options.fileName || ""); var name = safeSourceName(rawName);
383
+ if (!name) { if (rawName) summary.unsupported++; return summary; }
366
384
  if (IDENTITY_FILES.indexOf(name) !== -1) { summary.identitySkipped++; return summary; }
367
385
  if (EXCLUDED_FILES.indexOf(name) !== -1) { summary.excluded++; return summary; }
368
386
  var kind = sourceKindFor(name);
@@ -370,8 +388,8 @@ function syncMateSource(opts) {
370
388
 
371
389
  var actor = normalizeActor(options.actor);
372
390
  var importer = options.importer || importerFor(scope, options.baseDir);
373
- var filePath = path.join(scope.mateDir, "knowledge", name);
374
- var stat = readStat(filePath);
391
+ var knowledgeRoot = path.join(scope.mateDir, "knowledge"); var filePath = path.join(knowledgeRoot, name);
392
+ var stat = safeKnowledgeFileStat(knowledgeRoot, name);
375
393
 
376
394
  if (isJournal(name)) syncJournal(importer, scope, name, kind, filePath, stat, actor, summary);
377
395
  else syncFile(importer, scope, name, kind, filePath, stat, actor, summary);
@@ -382,19 +400,11 @@ function syncMateSource(opts) {
382
400
  return summary;
383
401
  }
384
402
 
385
- // Alias that reads better at a call site reacting to a removal or a rewrite.
386
403
  function reconcileSource(opts) {
387
404
  return syncMateSource(opts);
388
405
  }
389
406
 
390
407
  // Reconcile every durable source in one Mate directory, in both directions.
391
- //
392
- // Enumerating the directory alone is not enough. A knowledge file or a whole
393
- // journal can be deleted while the daemon is stopped, or by direct filesystem
394
- // tooling, and nothing would ever call syncMateSource for it again. So the
395
- // scope's own live records are also consulted: any source this module
396
- // previously imported that is no longer on disk is reconciled, which tombstones
397
- // a missing file record or every line of a missing journal.
398
408
  function reconcileMate(opts) {
399
409
  var options = opts || {};
400
410
  var summary = options.summary || newSummary();
@@ -404,11 +414,17 @@ function reconcileMate(opts) {
404
414
  var knowledgeDir = path.join(scope.mateDir, "knowledge");
405
415
  var present = {};
406
416
  var entries = [];
407
- try {
408
- entries = fs.readdirSync(knowledgeDir, { withFileTypes: true });
409
- } catch (e) {
410
- entries = [];
417
+ function discover(relative) {
418
+ var folder = relative ? path.join(knowledgeDir, relative) : knowledgeDir; var children;
419
+ try { children = fs.readdirSync(folder, { withFileTypes: true }); } catch (error) { return; }
420
+ for (var d = 0; d < children.length; d++) {
421
+ var name = relative ? relative + "/" + children[d].name : children[d].name; var stat;
422
+ try { stat = fs.lstatSync(path.join(knowledgeDir, name)); } catch (error) { continue; }
423
+ if (stat.isSymbolicLink()) continue;
424
+ if (stat.isDirectory()) discover(name); else if (stat.isFile()) entries.push({ name: name });
425
+ }
411
426
  }
427
+ discover("");
412
428
  var importer = options.importer || importerFor(scope, options.baseDir);
413
429
 
414
430
  function reconcileOne(fileName) {
@@ -423,7 +439,6 @@ function reconcileMate(opts) {
423
439
  }
424
440
 
425
441
  for (var i = 0; i < entries.length; i++) {
426
- if (!entries[i].isFile()) continue;
427
442
  // Recorded as present whatever its kind, so an excluded or unsupported file
428
443
  // that still exists is never mistaken for a deleted source.
429
444
  present[entries[i].name] = true;
@@ -0,0 +1,4 @@
1
+ // Only curated pigments may affect a Mate's workspace surface.
2
+ var COLORS = ['mint', 'rose', 'plum', 'coral', 'peach', 'gold', 'sky', 'indigo', 'stone'];
3
+ function valid(value) { return typeof value === 'string' && COLORS.indexOf(value) !== -1; }
4
+ module.exports = { colors: COLORS, valid: valid };
package/lib/mates.js CHANGED
@@ -2,6 +2,7 @@ var fs = require("fs");
2
2
  var path = require("path");
3
3
  var crypto = require("crypto");
4
4
  var config = require("./config");
5
+ var workspaceColor = require("./mate-workspace-color");
5
6
 
6
7
  var crisisSafety = require("./crisis-safety");
7
8
  var matesPrompts = require("./mates-prompts");
@@ -192,9 +193,17 @@ function getMate(ctx, id) {
192
193
  }
193
194
 
194
195
  function updateMate(ctx, id, updates) {
196
+ updates = Object.assign({}, updates);
197
+ delete updates.id;
198
+ delete updates.createdBy;
199
+ delete updates.builtinKey;
195
200
  var data = loadMates(ctx);
196
201
  for (var i = 0; i < data.mates.length; i++) {
197
202
  if (data.mates[i].id === id) {
203
+ if (Object.prototype.hasOwnProperty.call(updates, "workspaceColor")) {
204
+ if (data.mates[i].primary || data.mates[i].builtinKey === "clay") delete updates.workspaceColor;
205
+ else if (!workspaceColor.valid(updates.workspaceColor)) return { error: "Choose a supported workspace color." };
206
+ }
198
207
  // Primary mates: protect name and core identity fields
199
208
  if (data.mates[i].primary) {
200
209
  delete updates.name;
@@ -206,6 +215,15 @@ function updateMate(ctx, id, updates) {
206
215
  delete updates.profile.displayName;
207
216
  }
208
217
  }
218
+ // Locked built-in artwork is server-managed. Preserve it even when a
219
+ // stale or crafted client submits a replacement profile object.
220
+ if (data.mates[i].profile && data.mates[i].profile.avatarLocked && updates.profile) {
221
+ updates.profile.avatarStyle = data.mates[i].profile.avatarStyle;
222
+ updates.profile.avatarSeed = data.mates[i].profile.avatarSeed;
223
+ updates.profile.avatarColor = data.mates[i].profile.avatarColor;
224
+ updates.profile.avatarCustom = data.mates[i].profile.avatarCustom;
225
+ updates.profile.avatarLocked = true;
226
+ }
209
227
  var keys = Object.keys(updates);
210
228
  for (var j = 0; j < keys.length; j++) {
211
229
  data.mates[i][keys[j]] = updates[keys[j]];
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 };
@@ -1859,6 +1859,7 @@ function attachDebate(ctx) {
1859
1859
  handleDebateHandRaise: handleDebateHandRaise,
1860
1860
  handleDebateComment: handleDebateComment,
1861
1861
  handleDebateStop: handleDebateStop,
1862
+ handleDebateCancelStop: handleDebateCancelStop,
1862
1863
  handleDebateConcludeResponse: handleDebateConcludeResponse,
1863
1864
  handleDebateConfirmBrief: handleDebateConfirmBrief,
1864
1865
  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 {