clay-server 3.4.0-beta.2 → 3.4.0-beta.21

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 (82) hide show
  1. package/lib/daemon-projects.js +3 -3
  2. package/lib/daemon.js +59 -59
  3. package/lib/project-connection.js +35 -7
  4. package/lib/project-debate.js +4 -0
  5. package/lib/project-mate-interaction.js +21 -1
  6. package/lib/project-memory.js +3 -0
  7. package/lib/project-models.js +220 -0
  8. package/lib/project-session-handoff.js +162 -0
  9. package/lib/project-session-pair.js +14 -7
  10. package/lib/project-session-spawn.js +1 -1
  11. package/lib/project-sessions.js +13 -45
  12. package/lib/project-worker-proposal.js +51 -14
  13. package/lib/project.js +38 -80
  14. package/lib/public/antigravity-avatar.png +0 -0
  15. package/lib/public/app.js +29 -0
  16. package/lib/public/copilot-avatar.svg +5 -0
  17. package/lib/public/css/command-palette.css +22 -2
  18. package/lib/public/css/icon-strip.css +29 -13
  19. package/lib/public/css/input.css +56 -0
  20. package/lib/public/css/mates.css +6 -0
  21. package/lib/public/css/menus.css +38 -25
  22. package/lib/public/css/messages.css +9 -0
  23. package/lib/public/css/pane.css +16 -3
  24. package/lib/public/css/pwa-mobile.css +17 -0
  25. package/lib/public/css/session-actions.css +98 -0
  26. package/lib/public/css/title-bar.css +19 -0
  27. package/lib/public/grok-avatar.svg +4 -0
  28. package/lib/public/index.html +29 -7
  29. package/lib/public/junie-avatar.svg +11 -0
  30. package/lib/public/kimi-avatar.svg +4 -0
  31. package/lib/public/modules/agent-config-selects.js +69 -0
  32. package/lib/public/modules/app-header.js +4 -22
  33. package/lib/public/modules/app-messages.js +54 -11
  34. package/lib/public/modules/app-panels.js +36 -83
  35. package/lib/public/modules/app-projects.js +3 -1
  36. package/lib/public/modules/app-rendering.js +19 -4
  37. package/lib/public/modules/background-tasks-ui.js +55 -0
  38. package/lib/public/modules/mate-sidebar.js +11 -3
  39. package/lib/public/modules/model-picker.js +263 -0
  40. package/lib/public/modules/notifications.js +7 -1
  41. package/lib/public/modules/pane-bridge.js +11 -0
  42. package/lib/public/modules/pane-links.js +23 -0
  43. package/lib/public/modules/project-switcher.js +7 -6
  44. package/lib/public/modules/session-actions.js +294 -0
  45. package/lib/public/modules/sidebar-mates.js +1 -1
  46. package/lib/public/modules/sidebar-mobile.js +2 -1
  47. package/lib/public/modules/sidebar-projects.js +34 -43
  48. package/lib/public/modules/sidebar-sessions.js +20 -6
  49. package/lib/public/modules/split-pair-ui.js +16 -77
  50. package/lib/public/modules/split-view.js +3 -0
  51. package/lib/public/modules/tools.js +6 -1
  52. package/lib/public/modules/vendor-priority.js +14 -0
  53. package/lib/public/modules/vendor-selection.js +20 -0
  54. package/lib/public/modules/worker-proposal.js +6 -1
  55. package/lib/public/modules/worktree-location.js +17 -0
  56. package/lib/public/qwen-avatar.svg +4 -0
  57. package/lib/public/style.css +4 -2
  58. package/lib/sdk-bridge.js +103 -56
  59. package/lib/sdk-message-processor.js +26 -4
  60. package/lib/session-handoff-context.js +110 -0
  61. package/lib/session-notes-mcp-server.js +8 -1
  62. package/lib/sessions.js +4 -0
  63. package/lib/worktree.js +9 -4
  64. package/lib/ws-schema.js +9 -1
  65. package/lib/yoke/acp-agent-profiles.js +64 -14
  66. package/lib/yoke/adapters/antigravity.js +417 -0
  67. package/lib/yoke/adapters/claude.js +34 -0
  68. package/lib/yoke/adapters/codex.js +101 -5
  69. package/lib/yoke/adapters/copilot.js +7 -0
  70. package/lib/yoke/adapters/grok.js +7 -0
  71. package/lib/yoke/adapters/junie.js +7 -0
  72. package/lib/yoke/adapters/kimi.js +7 -0
  73. package/lib/yoke/adapters/kiro.js +2 -2
  74. package/lib/yoke/adapters/qwen.js +7 -0
  75. package/lib/yoke/codex-app-server.js +25 -8
  76. package/lib/yoke/index.js +67 -28
  77. package/lib/yoke/instructions.js +3 -0
  78. package/lib/yoke/interface.js +12 -0
  79. package/lib/yoke/vendor-registry.js +61 -6
  80. package/package.json +1 -1
  81. package/lib/public/gemini-avatar.svg +0 -11
  82. package/lib/yoke/adapters/gemini.js +0 -7
@@ -41,10 +41,10 @@ function scanAndRegisterWorktrees(relay, parentPath, parentSlug, parentIcon, par
41
41
  if (worktreeRegistry[parentSlug][j] === wtSlug) { alreadyRegistered = true; break; }
42
42
  }
43
43
  if (alreadyRegistered) continue;
44
- var wtMeta = { parentSlug: parentSlug, branch: wt.branch || wt.dirName, accessible: wt.accessible };
44
+ var wtMeta = { parentSlug: parentSlug, branch: wt.branch || wt.dirName, external: wt.external };
45
45
  relay.addProject(wt.path, wtSlug, wt.branch || wt.dirName, parentIcon, parentOwnerId, wtMeta);
46
46
  worktreeRegistry[parentSlug].push(wtSlug);
47
- console.log("[daemon] Registered worktree:", wtSlug, "->", wt.path, wt.accessible ? "(accessible)" : "(inaccessible)");
47
+ console.log("[daemon] Registered worktree:", wtSlug, "->", wt.path, wt.external ? "(external)" : "(inside project folder)");
48
48
  }
49
49
  daemonSync.register(getWorktreeSyncKey(parentSlug), function () {
50
50
  rescanWorktrees(relay, parentPath, parentSlug, parentIcon, parentOwnerId);
@@ -79,7 +79,7 @@ function rescanWorktrees(relay, parentPath, parentSlug, parentIcon, parentOwnerI
79
79
  if (existingSlugs[ei] === wtSlug) { found = true; break; }
80
80
  }
81
81
  if (!found) {
82
- var wtMeta = { parentSlug: parentSlug, branch: wt.branch || wt.dirName, accessible: wt.accessible };
82
+ var wtMeta = { parentSlug: parentSlug, branch: wt.branch || wt.dirName, external: wt.external };
83
83
  relay.addProject(wt.path, wtSlug, wt.branch || wt.dirName, parentIcon, parentOwnerId, wtMeta);
84
84
  if (!worktreeRegistry[parentSlug]) worktreeRegistry[parentSlug] = [];
85
85
  worktreeRegistry[parentSlug].push(wtSlug);
package/lib/daemon.js CHANGED
@@ -978,7 +978,7 @@ var relay = createServer({
978
978
  if (!result.ok) return result;
979
979
  // Register the new worktree as ephemeral project
980
980
  var wtSlug = parentSlug + "--" + dirName;
981
- var wtMeta = { parentSlug: parentSlug, branch: branchName, accessible: true };
981
+ var wtMeta = { parentSlug: parentSlug, branch: branchName, external: false };
982
982
  relay.addProject(result.path, wtSlug, branchName, parent.icon, parent.ownerId, wtMeta);
983
983
  registerWorktreeSlug(parentSlug, wtSlug);
984
984
  console.log("[daemon] Created worktree:", wtSlug, "->", result.path);
@@ -1041,64 +1041,6 @@ function shutdownProjects() {
1041
1041
 
1042
1042
  // Worktree tracking extracted to daemon-projects.js
1043
1043
 
1044
- // --- Register projects ---
1045
- var projects = config.projects || [];
1046
- for (var i = 0; i < projects.length; i++) {
1047
- var p = projects[i];
1048
- if (fs.existsSync(p.path)) {
1049
- console.log("[daemon] Adding project:", p.slug, "→", p.path);
1050
- relay.addProject(p.path, p.slug, p.title, p.icon, p.ownerId);
1051
- // Discover and register worktrees for this project
1052
- scanAndRegisterWorktrees(relay, p.path, p.slug, p.icon, p.ownerId);
1053
- } else {
1054
- console.log("[daemon] Skipping missing project:", p.path);
1055
- }
1056
- }
1057
-
1058
- // Migrate legacy mates storage before registration
1059
- mates.migrateLegacyMates();
1060
-
1061
- // Register existing mates as projects
1062
- if (usersModule.isMultiUser()) {
1063
- // Multi-user mode: iterate over all users and load each user's mates
1064
- var allUsers = usersModule.getAllUsers();
1065
- for (var ui = 0; ui < allUsers.length; ui++) {
1066
- var mateCtx = mates.buildMateCtx(allUsers[ui].id);
1067
- var userMates = mates.getAllMates(mateCtx);
1068
- for (var mi = 0; mi < userMates.length; mi++) {
1069
- var m = userMates[mi];
1070
- var mateDir = mates.getMateDir(mateCtx, m.id);
1071
- var mateSlug = "mate-" + m.id;
1072
- var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
1073
- if (fs.existsSync(mateDir)) {
1074
- var mateDef = m.builtinKey ? require("./builtin-mates").getBuiltinByKey(m.builtinKey) : null;
1075
- var isHost = !!(mateDef && mateDef.hostAgent);
1076
- console.log("[daemon] Adding mate project:", mateSlug + (isHost ? " (host agent)" : ""));
1077
- relay.addProject(mateDir, mateSlug, mateName, null, m.createdBy, null, { isMate: true, mateDisplayName: mateName, isHostAgent: isHost });
1078
- }
1079
- }
1080
- }
1081
- } else {
1082
- // Single-user mode: use null ctx
1083
- var mateCtx = mates.buildMateCtx(null);
1084
- var allMates = mates.getAllMates(mateCtx);
1085
- for (var mi = 0; mi < allMates.length; mi++) {
1086
- var m = allMates[mi];
1087
- var mateDir = mates.getMateDir(mateCtx, m.id);
1088
- var mateSlug = "mate-" + m.id;
1089
- var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
1090
- if (fs.existsSync(mateDir)) {
1091
- var mateDef2 = m.builtinKey ? require("./builtin-mates").getBuiltinByKey(m.builtinKey) : null;
1092
- var isHost2 = !!(mateDef2 && mateDef2.hostAgent);
1093
- console.log("[daemon] Adding mate project:", mateSlug + (isHost2 ? " (host agent)" : ""));
1094
- relay.addProject(mateDir, mateSlug, mateName, null, m.createdBy, null, { isMate: true, mateDisplayName: mateName, isHostAgent: isHost2 });
1095
- }
1096
- }
1097
- }
1098
-
1099
- // Sync ~/.clayrc on startup
1100
- try { syncClayrc(config.projects); } catch (e) {}
1101
-
1102
1044
  // --- IPC server ---
1103
1045
  // Clean up stale socket/config left by a previously killed daemon
1104
1046
  var existingConfig = loadConfig();
@@ -1334,6 +1276,64 @@ var ipc = createIPCServer(socketPath(), function (msg) {
1334
1276
  }
1335
1277
  });
1336
1278
 
1279
+ // --- Register projects ---
1280
+ var projects = config.projects || [];
1281
+ for (var i = 0; i < projects.length; i++) {
1282
+ var p = projects[i];
1283
+ if (fs.existsSync(p.path)) {
1284
+ console.log("[daemon] Adding project:", p.slug, "→", p.path);
1285
+ relay.addProject(p.path, p.slug, p.title, p.icon, p.ownerId);
1286
+ // Discover and register worktrees for this project
1287
+ scanAndRegisterWorktrees(relay, p.path, p.slug, p.icon, p.ownerId);
1288
+ } else {
1289
+ console.log("[daemon] Skipping missing project:", p.path);
1290
+ }
1291
+ }
1292
+
1293
+ // Migrate legacy mates storage before registration
1294
+ mates.migrateLegacyMates();
1295
+
1296
+ // Register existing mates as projects
1297
+ if (usersModule.isMultiUser()) {
1298
+ // Multi-user mode: iterate over all users and load each user's mates
1299
+ var allUsers = usersModule.getAllUsers();
1300
+ for (var ui = 0; ui < allUsers.length; ui++) {
1301
+ var mateCtx = mates.buildMateCtx(allUsers[ui].id);
1302
+ var userMates = mates.getAllMates(mateCtx);
1303
+ for (var mi = 0; mi < userMates.length; mi++) {
1304
+ var m = userMates[mi];
1305
+ var mateDir = mates.getMateDir(mateCtx, m.id);
1306
+ var mateSlug = "mate-" + m.id;
1307
+ var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
1308
+ if (fs.existsSync(mateDir)) {
1309
+ var mateDef = m.builtinKey ? require("./builtin-mates").getBuiltinByKey(m.builtinKey) : null;
1310
+ var isHost = !!(mateDef && mateDef.hostAgent);
1311
+ console.log("[daemon] Adding mate project:", mateSlug + (isHost ? " (host agent)" : ""));
1312
+ relay.addProject(mateDir, mateSlug, mateName, null, m.createdBy, null, { isMate: true, mateDisplayName: mateName, isHostAgent: isHost });
1313
+ }
1314
+ }
1315
+ }
1316
+ } else {
1317
+ // Single-user mode: use null ctx
1318
+ var mateCtx = mates.buildMateCtx(null);
1319
+ var allMates = mates.getAllMates(mateCtx);
1320
+ for (var mi = 0; mi < allMates.length; mi++) {
1321
+ var m = allMates[mi];
1322
+ var mateDir = mates.getMateDir(mateCtx, m.id);
1323
+ var mateSlug = "mate-" + m.id;
1324
+ var mateName = (m.profile && m.profile.displayName) || m.name || "New Mate";
1325
+ if (fs.existsSync(mateDir)) {
1326
+ var mateDef2 = m.builtinKey ? require("./builtin-mates").getBuiltinByKey(m.builtinKey) : null;
1327
+ var isHost2 = !!(mateDef2 && mateDef2.hostAgent);
1328
+ console.log("[daemon] Adding mate project:", mateSlug + (isHost2 ? " (host agent)" : ""));
1329
+ relay.addProject(mateDir, mateSlug, mateName, null, m.createdBy, null, { isMate: true, mateDisplayName: mateName, isHostAgent: isHost2 });
1330
+ }
1331
+ }
1332
+ }
1333
+
1334
+ // Sync ~/.clayrc on startup
1335
+ try { syncClayrc(config.projects); } catch (e) {}
1336
+
1337
1337
  // --- Cleanup stale SDK warmup CLI session files ---
1338
1338
  //
1339
1339
  // On adapter init the SDK is given a dummy "hi" prompt to discover models,
@@ -22,6 +22,28 @@ function dispatchMessageSafely(handleMessage, sendTo, slug, ws, msg) {
22
22
  }
23
23
  }
24
24
 
25
+ function buildInitialModelInfo(sm, initialVendor, initialModel, initialModels, sessionId) {
26
+ var model = initialModel || "";
27
+ var models = initialModels || [];
28
+ if (model && models.length > 0) {
29
+ var found = false;
30
+ for (var i = 0; i < models.length; i++) {
31
+ var entry = models[i];
32
+ if (typeof entry === "string" ? entry === model : (entry.value === model || entry.id === model || entry.resolvedModel === model)) found = true;
33
+ }
34
+ if (!found) model = typeof models[0] === "string" ? models[0] : (models[0].value || models[0].id || "");
35
+ }
36
+ return {
37
+ type: "model_info",
38
+ model: model,
39
+ models: models,
40
+ vendor: initialVendor,
41
+ sessionId: sessionId || null,
42
+ availableVendors: sm.availableVendors || [],
43
+ installedVendors: sm.installedVendors || [],
44
+ };
45
+ }
46
+
25
47
  /**
26
48
  * Attach connection/disconnection handlers to a project context.
27
49
  *
@@ -116,7 +138,7 @@ function attachConnection(ctx) {
116
138
  if (!_warmedUp) {
117
139
  _warmedUp = true;
118
140
  if (typeof warmup === "function") {
119
- try { warmup(); }
141
+ try { warmup(wsUser && wsUser.linuxUser ? wsUser.linuxUser : null); }
120
142
  catch (e) { console.error("[project-connection] warmup failed for " + slug + ":", e && e.message ? e.message : e); }
121
143
  }
122
144
  }
@@ -166,11 +188,12 @@ function attachConnection(ctx) {
166
188
  if (sm.slashCommands) {
167
189
  sendTo(ws, { type: "slash_commands", commands: sm.slashCommands });
168
190
  }
169
- var initialModel = (restoredActive && restoredActive.model) || sm.currentModel || "";
170
- if (initialModel) {
171
- sendTo(ws, { type: "model_info", model: initialModel, models: initialModels, vendor: initialVendor, availableVendors: sm.availableVendors || [], installedVendors: sm.installedVendors || [] });
172
- }
173
- sendTo(ws, { type: "config_state", model: initialModel, mode: sm.currentPermissionMode || "default", effort: initialEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
191
+ var initialModel = (restoredActive && restoredActive.model) || ((sm.defaultModelByVendor || {})[initialVendor]) || "";
192
+ // Vendor installation state is needed even before a new project has a
193
+ // model. Always send it so reconnects cannot fall back to "Not installed."
194
+ var initialModelInfo = buildInitialModelInfo(sm, initialVendor, initialModel, initialModels, restoredActive ? restoredActive.localId : null);
195
+ sendTo(ws, initialModelInfo);
196
+ sendTo(ws, { type: "config_state", model: initialModelInfo.model, vendor: initialVendor, sessionId: restoredActive ? restoredActive.localId : null, mode: sm.currentPermissionMode || "default", effort: initialEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
174
197
  sendTo(ws, { type: "last_vendor", vendor: sm.lastVendor || "" });
175
198
  sendTo(ws, Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
176
199
  sendTo(ws, { type: "term_list", terminals: tm.list() });
@@ -271,6 +294,7 @@ function attachConnection(ctx) {
271
294
  if (active.isProcessing) {
272
295
  sendTo(ws, { type: "status", status: "processing" });
273
296
  }
297
+ sendTo(ws, { type: "active_background_tasks", tasks: active.activeBackgroundTasks || [] });
274
298
  var pendingIds = Object.keys(active.pendingPermissions);
275
299
  for (var pi = 0; pi < pendingIds.length; pi++) {
276
300
  var p = active.pendingPermissions[pendingIds[pi]];
@@ -333,4 +357,8 @@ function attachConnection(ctx) {
333
357
  };
334
358
  }
335
359
 
336
- module.exports = { attachConnection: attachConnection, dispatchMessageSafely: dispatchMessageSafely };
360
+ module.exports = {
361
+ attachConnection: attachConnection,
362
+ buildInitialModelInfo: buildInitialModelInfo,
363
+ dispatchMessageSafely: dispatchMessageSafely,
364
+ };
@@ -604,6 +604,7 @@ function attachDebate(ctx) {
604
604
  var _modMate = matesModule.getMate(mateCtx, debate.moderatorId);
605
605
  ctx.sdk.createMentionSession({
606
606
  vendor: _modMate ? _modMate.vendor : null,
607
+ linuxUser: mateCtx.linuxUser || undefined,
607
608
  claudeMd: claudeMd,
608
609
  initialContext: digests,
609
610
  initialMessage: quickBriefPrompt,
@@ -799,6 +800,7 @@ function attachDebate(ctx) {
799
800
  var _modMate2 = matesModule.getMate(mateCtx, debate.moderatorId);
800
801
  ctx.sdk.createMentionSession({
801
802
  vendor: _modMate2 ? _modMate2.vendor : null,
803
+ linuxUser: mateCtx.linuxUser || undefined,
802
804
  claudeMd: claudeMd,
803
805
  initialContext: moderatorContext,
804
806
  initialMessage: "Begin the debate on: " + debate.topic,
@@ -994,6 +996,7 @@ function attachDebate(ctx) {
994
996
  var _panMate = matesModule.getMate(debate.mateCtx, mateId);
995
997
  ctx.sdk.createMentionSession({
996
998
  vendor: _panMate ? _panMate.vendor : null,
999
+ linuxUser: debate.mateCtx && debate.mateCtx.linuxUser ? debate.mateCtx.linuxUser : undefined,
997
1000
  claudeMd: claudeMd,
998
1001
  initialContext: panelistContext + historyContext,
999
1002
  initialMessage: "The moderator addresses you:\n\n" + moderatorText,
@@ -1530,6 +1533,7 @@ function attachDebate(ctx) {
1530
1533
  var _modMate3 = matesModule.getMate(mateCtx, debate.moderatorId);
1531
1534
  ctx.sdk.createMentionSession({
1532
1535
  vendor: _modMate3 ? _modMate3.vendor : null,
1536
+ linuxUser: mateCtx.linuxUser || undefined,
1533
1537
  claudeMd: claudeMd,
1534
1538
  initialContext: moderatorContext,
1535
1539
  initialMessage: resumePrompt,
@@ -179,6 +179,7 @@ function attachMateInteraction(ctx) {
179
179
  var _digestQueue = [];
180
180
  var _digestBusy = false;
181
181
  var _digestWorkerTurns = 0;
182
+ var _digestWorkerLinuxUser = null;
182
183
  var DIGEST_WORKER_MAX_TURNS = 20;
183
184
 
184
185
  function enqueueDigest(job) {
@@ -191,6 +192,13 @@ function attachMateInteraction(ctx) {
191
192
  _digestBusy = true;
192
193
  var job = _digestQueue.shift();
193
194
 
195
+ if (_digestWorker && _digestWorkerLinuxUser !== (job.linuxUser || null)) {
196
+ try { _digestWorker.close(); } catch (e) {}
197
+ _digestWorker = null;
198
+ _digestWorkerTurns = 0;
199
+ _digestWorkerLinuxUser = null;
200
+ }
201
+
194
202
  var mateDir = matesModule.getMateDir(job.mateCtx, job.mateId);
195
203
  var knowledgeDir = path.join(mateDir, "knowledge");
196
204
 
@@ -315,6 +323,7 @@ function attachMateInteraction(ctx) {
315
323
  try { _digestWorker.close(); } catch (e) {}
316
324
  _digestWorker = null;
317
325
  _digestWorkerTurns = 0;
326
+ _digestWorkerLinuxUser = null;
318
327
  }
319
328
 
320
329
  var responseText = "";
@@ -328,12 +337,14 @@ function attachMateInteraction(ctx) {
328
337
  console.error("[digest-worker] Error:", err);
329
338
  _digestWorker = null;
330
339
  _digestWorkerTurns = 0;
340
+ _digestWorkerLinuxUser = null;
331
341
  if (job.onError) job.onError(err);
332
342
  processDigestQueue();
333
343
  },
334
344
  });
335
345
  } else {
336
346
  sdk.createMentionSession({
347
+ linuxUser: job.linuxUser || undefined,
337
348
  claudeMd: "",
338
349
  model: "haiku",
339
350
  initialContext: "[Digest Worker] You generate memory digests. Respond with ONLY JSON.",
@@ -344,10 +355,15 @@ function attachMateInteraction(ctx) {
344
355
  onError: function (err) {
345
356
  console.error("[digest-worker] Create error:", err);
346
357
  _digestWorker = null;
358
+ _digestWorkerLinuxUser = null;
347
359
  if (job.onError) job.onError(err);
348
360
  processDigestQueue();
349
361
  },
350
- }).then(function (ws) { _digestWorker = ws; _digestWorkerTurns = 1; }).catch(function (err) {
362
+ }).then(function (ws) {
363
+ _digestWorker = ws;
364
+ _digestWorkerTurns = 1;
365
+ _digestWorkerLinuxUser = job.linuxUser || null;
366
+ }).catch(function (err) {
351
367
  if (job.onError) job.onError(err || new Error("digest worker creation failed"));
352
368
  processDigestQueue();
353
369
  });
@@ -378,6 +394,7 @@ function attachMateInteraction(ctx) {
378
394
 
379
395
  enqueueDigest({
380
396
  mateCtx: mateCtx,
397
+ linuxUser: mateCtx.linuxUser || undefined,
381
398
  mateId: mateId,
382
399
  type: "mention",
383
400
  conversationContent: conversationContent,
@@ -473,6 +490,7 @@ function attachMateInteraction(ctx) {
473
490
 
474
491
  enqueueDigest({
475
492
  mateCtx: mateCtx,
493
+ linuxUser: mateCtx.linuxUser || undefined,
476
494
  mateId: mateId,
477
495
  type: "dm",
478
496
  priorSummary: priorSummary || "",
@@ -716,6 +734,7 @@ function attachMateInteraction(ctx) {
716
734
  // Create new persistent mention session
717
735
  sdk.createMentionSession({
718
736
  vendor: mate.vendor || null,
737
+ linuxUser: getLinuxUserForSession(session) || undefined,
719
738
  claudeMd: claudeMd,
720
739
  initialContext: mentionContext,
721
740
  initialMessage: mentionFullInput,
@@ -887,6 +906,7 @@ function attachMateInteraction(ctx) {
887
906
  ].join("\n");
888
907
 
889
908
  sdk.createMentionSession({
909
+ linuxUser: mateCtx.linuxUser || undefined,
890
910
  claudeMd: "",
891
911
  model: "haiku",
892
912
  initialContext: synthesisContext,
@@ -339,6 +339,7 @@ function attachMemory(ctx) {
339
339
  var gateText = "";
340
340
  var _gateSession = null;
341
341
  sdk.createMentionSession({
342
+ linuxUser: mateCtx.linuxUser || undefined,
342
343
  claudeMd: "",
343
344
  model: "haiku",
344
345
  initialContext: gateContext,
@@ -448,6 +449,7 @@ function attachMemory(ctx) {
448
449
  var updateText = "";
449
450
  var _updateSession = null;
450
451
  sdk.createMentionSession({
452
+ linuxUser: mateCtx.linuxUser || undefined,
451
453
  claudeMd: "",
452
454
  model: "haiku",
453
455
  initialContext: updateContext,
@@ -553,6 +555,7 @@ function attachMemory(ctx) {
553
555
  var initText = "";
554
556
  var _initSession = null;
555
557
  sdk.createMentionSession({
558
+ linuxUser: mateCtx.linuxUser || undefined,
556
559
  claudeMd: "",
557
560
  model: "haiku",
558
561
  initialContext: initContext,
@@ -0,0 +1,220 @@
1
+ var yoke = require("./yoke");
2
+
3
+ function modelEntryValue(entry) {
4
+ if (!entry) return "";
5
+ if (typeof entry === "string") return entry;
6
+ return entry.value || entry.id || "";
7
+ }
8
+
9
+ function modelEntryMatches(entry, value) {
10
+ if (!entry || !value) return false;
11
+ if (typeof entry === "string") return entry === value;
12
+ return entry.value === value || entry.id === value || entry.resolvedModel === value;
13
+ }
14
+
15
+ function attachModels(ctx) {
16
+ var cwd = ctx.cwd;
17
+ var slug = ctx.slug;
18
+ var sm = ctx.sm;
19
+ var sdk = ctx.sdk;
20
+ var adapters = ctx.adapters;
21
+ var sendTo = ctx.sendTo;
22
+ var getSessionForWs = ctx.getSessionForWs;
23
+ var getLinuxUserForWs = ctx.getLinuxUserForWs;
24
+ var serverPort = ctx.serverPort;
25
+ var serverTls = ctx.serverTls;
26
+ var serverAuthToken = ctx.serverAuthToken;
27
+
28
+ async function loadVendorModels(ws, msg) {
29
+ var vendor = msg.vendor || "";
30
+ var requestId = msg.requestId || null;
31
+ var loadError = null;
32
+ var vendorAdapter = null;
33
+ var session = getSessionForWs(ws);
34
+
35
+ if (!vendor) {
36
+ sendTo(ws, {
37
+ type: "model_info",
38
+ vendor: vendor,
39
+ requestId: requestId,
40
+ modelStatus: "error",
41
+ error: "No vendor was selected.",
42
+ model: "",
43
+ models: [],
44
+ sessionId: session ? session.localId : null,
45
+ });
46
+ return;
47
+ }
48
+
49
+ try {
50
+ var modelLinuxUser = getLinuxUserForWs(ws);
51
+ vendorAdapter = adapters[vendor] || null;
52
+ if (!vendorAdapter) {
53
+ vendorAdapter = await yoke.lazyCreateAdapter(adapters, vendor, {
54
+ cwd: cwd,
55
+ linuxUser: modelLinuxUser || undefined,
56
+ clayPort: serverPort,
57
+ clayTls: serverTls,
58
+ clayAuthToken: serverAuthToken,
59
+ slug: slug,
60
+ });
61
+ }
62
+
63
+ var cachedModels = (sm.modelsByVendor && sm.modelsByVendor[vendor]) || [];
64
+ var cachedCapabilities = sm.capabilitiesByVendor && sm.capabilitiesByVendor[vendor];
65
+ var needsReadyMetadata = !cachedCapabilities || cachedModels.length === 0;
66
+ if (vendorAdapter && needsReadyMetadata && typeof vendorAdapter.init === "function") {
67
+ try {
68
+ var readyResult = await vendorAdapter.init({
69
+ cwd: cwd,
70
+ linuxUser: modelLinuxUser || undefined,
71
+ clayPort: serverPort,
72
+ clayTls: serverTls,
73
+ clayAuthToken: serverAuthToken,
74
+ slug: slug,
75
+ });
76
+ sm.capabilitiesByVendor = sm.capabilitiesByVendor || {};
77
+ sm.capabilitiesByVendor[vendor] = readyResult.capabilities || {};
78
+ sm.modelsByVendor = sm.modelsByVendor || {};
79
+ if (Array.isArray(readyResult.models) && readyResult.models.length > 0) {
80
+ sm.modelsByVendor[vendor] = readyResult.models;
81
+ }
82
+ } catch (e) {
83
+ loadError = e;
84
+ console.error("[project-models] " + vendor + " init failed:", e.message || e);
85
+ }
86
+ }
87
+
88
+ if (vendorAdapter) {
89
+ sm.availableVendors = Object.keys(adapters);
90
+ sm.modelsByVendor = sm.modelsByVendor || {};
91
+ var currentModels = sm.modelsByVendor[vendor] || [];
92
+ if (currentModels.length === 0 && typeof vendorAdapter.supportedModels === "function") {
93
+ try {
94
+ var supported = await vendorAdapter.supportedModels();
95
+ if (Array.isArray(supported) && supported.length > 0) {
96
+ sm.modelsByVendor[vendor] = supported;
97
+ loadError = null;
98
+ }
99
+ } catch (e) {
100
+ if (!loadError) loadError = e;
101
+ console.error("[project-models] " + vendor + " model listing failed:", e.message || e);
102
+ }
103
+ }
104
+ } else {
105
+ loadError = new Error("The vendor adapter is unavailable.");
106
+ }
107
+ } catch (e) {
108
+ loadError = e;
109
+ console.error("[project-models] model loading failed for " + vendor + ":", e.message || e);
110
+ }
111
+
112
+ var vendorModels = (sm.modelsByVendor && sm.modelsByVendor[vendor]) || [];
113
+ var modelToSend = modelEntryValue(vendorModels[0]);
114
+ var preferredModel = (session && session.vendor === vendor && session.model) || (sm.defaultModelByVendor && sm.defaultModelByVendor[vendor]) || "";
115
+ if (preferredModel) {
116
+ for (var i = 0; i < vendorModels.length; i++) {
117
+ if (modelEntryMatches(vendorModels[i], preferredModel)) {
118
+ modelToSend = modelEntryValue(vendorModels[i]);
119
+ break;
120
+ }
121
+ }
122
+ }
123
+ var vendorInfo = yoke.getVendorInfo(vendor);
124
+ var displayName = vendorInfo ? vendorInfo.displayName : vendor;
125
+ var status = vendorModels.length > 0 ? "ready" : (loadError ? "error" : "empty");
126
+ var errorText = "";
127
+ if (status === "error") {
128
+ errorText = "Could not load " + displayName + " models: " + (loadError.message || loadError);
129
+ } else if (status === "empty") {
130
+ errorText = displayName + " returned no models. Check CLI authentication and retry.";
131
+ }
132
+ sendTo(ws, {
133
+ type: "model_info",
134
+ model: modelToSend,
135
+ models: vendorModels,
136
+ vendor: vendor,
137
+ sessionId: session ? session.localId : null,
138
+ capabilities: (sm.capabilitiesByVendor && sm.capabilitiesByVendor[vendor]) || {},
139
+ availableVendors: sm.availableVendors || [],
140
+ installedVendors: sm.installedVendors || [],
141
+ requestId: requestId,
142
+ modelStatus: status,
143
+ error: errorText,
144
+ });
145
+ }
146
+
147
+ async function selectModel(ws, msg) {
148
+ var session = getSessionForWs(ws);
149
+ if (!session) {
150
+ sendTo(ws, { type: "model_selection_result", requestId: msg.requestId || null, ok: false, error: "No active session." });
151
+ return;
152
+ }
153
+ var sessionVendor = session.vendor || sm.defaultVendor || "claude";
154
+ if (msg.vendor && msg.vendor !== sessionVendor) {
155
+ sendTo(ws, {
156
+ type: "model_selection_result",
157
+ requestId: msg.requestId || null,
158
+ ok: false,
159
+ error: "The session vendor changed before the model selection completed.",
160
+ });
161
+ return;
162
+ }
163
+ var knownModels = (sm.modelsByVendor && sm.modelsByVendor[sessionVendor]) || [];
164
+ if (knownModels.length > 0) {
165
+ var known = false;
166
+ for (var i = 0; i < knownModels.length; i++) {
167
+ if (modelEntryMatches(knownModels[i], msg.model)) {
168
+ known = true;
169
+ break;
170
+ }
171
+ }
172
+ if (!known) {
173
+ sendTo(ws, {
174
+ type: "model_selection_result",
175
+ requestId: msg.requestId || null,
176
+ ok: false,
177
+ error: "That model is not available for the selected vendor.",
178
+ });
179
+ return;
180
+ }
181
+ }
182
+ var result = await sdk.setModel(session, msg.model);
183
+ sendTo(ws, {
184
+ type: "model_selection_result",
185
+ requestId: msg.requestId || null,
186
+ ok: !!(result && result.ok),
187
+ model: result && result.model ? result.model : msg.model,
188
+ vendor: sessionVendor,
189
+ error: result && result.error ? result.error : "",
190
+ });
191
+ }
192
+
193
+ function handleMessage(ws, msg) {
194
+ if (msg.type === "get_vendor_models") {
195
+ loadVendorModels(ws, msg).catch(function(e) {
196
+ console.error("[project-models] Unexpected model loading failure:", e.message || e);
197
+ });
198
+ return true;
199
+ }
200
+ if (msg.type === "set_model" && msg.model) {
201
+ selectModel(ws, msg).catch(function(e) {
202
+ sendTo(ws, { type: "model_selection_result", requestId: msg.requestId || null, ok: false, error: e.message || String(e) });
203
+ });
204
+ return true;
205
+ }
206
+ return false;
207
+ }
208
+
209
+ return {
210
+ handleMessage: handleMessage,
211
+ loadVendorModels: loadVendorModels,
212
+ selectModel: selectModel,
213
+ };
214
+ }
215
+
216
+ module.exports = {
217
+ attachModels: attachModels,
218
+ modelEntryMatches: modelEntryMatches,
219
+ modelEntryValue: modelEntryValue,
220
+ };