clay-server 2.34.0-beta.1 → 2.34.0-beta.10

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 (40) hide show
  1. package/lib/ask-user-mcp-server.js +120 -0
  2. package/lib/daemon.js +97 -38
  3. package/lib/mate-datastore.js +27 -5
  4. package/lib/mates.js +2 -2
  5. package/lib/os-users.js +13 -0
  6. package/lib/project-connection.js +16 -9
  7. package/lib/project-mate-datastore.js +16 -2
  8. package/lib/project-sessions.js +110 -7
  9. package/lib/project-user-message.js +4 -3
  10. package/lib/project.js +97 -10
  11. package/lib/public/css/mates.css +94 -52
  12. package/lib/public/css/mobile-nav.css +0 -14
  13. package/lib/public/css/notifications-center.css +23 -19
  14. package/lib/public/css/sidebar.css +326 -101
  15. package/lib/public/index.html +24 -57
  16. package/lib/public/modules/app-dm.js +0 -2
  17. package/lib/public/modules/app-messages.js +3 -5
  18. package/lib/public/modules/app-rendering.js +0 -2
  19. package/lib/public/modules/diff.js +21 -7
  20. package/lib/public/modules/mate-datastore-ui.js +108 -98
  21. package/lib/public/modules/mate-sidebar.js +0 -9
  22. package/lib/public/modules/mate-wizard.js +15 -15
  23. package/lib/public/modules/sidebar-mobile.js +10 -20
  24. package/lib/public/modules/sidebar-sessions.js +490 -113
  25. package/lib/public/modules/sidebar.js +8 -6
  26. package/lib/public/modules/tools.js +58 -13
  27. package/lib/public/sw.js +1 -1
  28. package/lib/sdk-bridge.js +36 -28
  29. package/lib/sdk-message-processor.js +14 -3
  30. package/lib/server.js +28 -72
  31. package/lib/sessions.js +157 -20
  32. package/lib/ws-schema.js +2 -0
  33. package/lib/yoke/adapters/claude-worker.js +114 -2
  34. package/lib/yoke/adapters/claude.js +56 -5
  35. package/lib/yoke/adapters/codex.js +349 -58
  36. package/lib/yoke/index.js +73 -35
  37. package/lib/yoke/instructions.js +0 -1
  38. package/lib/yoke/mcp-bridge-server.js +14 -6
  39. package/package.json +1 -2
  40. package/lib/yoke/adapters/gemini.js +0 -709
@@ -3,6 +3,33 @@ var path = require("path");
3
3
  var { execFileSync } = require("child_process");
4
4
  var { CODEX_DEFAULTS, getCodexConfig } = require("./codex-defaults");
5
5
 
6
+ // Format a user's answer to an ask_user_questions card as a plain user
7
+ // message so the MCP path can feed it back to the agent on the next turn.
8
+ // The agent sees: its own question text, then the selected answer(s).
9
+ function formatAskUserAnswerAsMessage(input, answers) {
10
+ var questions = (input && Array.isArray(input.questions)) ? input.questions : [];
11
+ if (questions.length === 0) {
12
+ // Shouldn't happen, but be defensive.
13
+ try { return "(answered with: " + JSON.stringify(answers || {}) + ")"; }
14
+ catch (e) { return "(answered)"; }
15
+ }
16
+ var lines = [];
17
+ for (var i = 0; i < questions.length; i++) {
18
+ var q = questions[i];
19
+ var qText = (q && q.question) ? q.question : ("Question " + (i + 1));
20
+ var ans = (answers && answers[i] != null) ? String(answers[i]) : "";
21
+ if (!ans) continue;
22
+ if (questions.length === 1) {
23
+ // Single question: keep it tight.
24
+ lines.push(ans);
25
+ } else {
26
+ lines.push("- " + qText + " → " + ans);
27
+ }
28
+ }
29
+ if (lines.length === 0) return "(no answer provided)";
30
+ return lines.join("\n");
31
+ }
32
+
6
33
  /**
7
34
  * Attach session management, config, project management, and mid-section
8
35
  * message handlers to a project context.
@@ -12,7 +39,7 @@ var { CODEX_DEFAULTS, getCodexConfig } = require("./codex-defaults");
12
39
  * sm, sdk, tm, clients,
13
40
  * send, sendTo, sendToAdmins, sendToSession, sendToSessionOthers,
14
41
  * opts, usersModule, userPresence, matesModule, pushModule,
15
- * getSessionForWs, getLinuxUserForSession, getOsUserInfoForWs,
42
+ * getSessionForWs, getLinuxUserForSession, ensureProjectAccessForSession, getOsUserInfoForWs,
16
43
  * hydrateImageRefs, onProcessingChanged, broadcastPresence,
17
44
  * adapter, getProjectList, getProjectCount, getScheduleCount,
18
45
  * moveScheduleToProject, moveAllSchedulesToProject, getHubSchedules,
@@ -43,6 +70,7 @@ function attachSessions(ctx) {
43
70
  var pushModule = ctx.pushModule;
44
71
  var getSessionForWs = ctx.getSessionForWs;
45
72
  var getLinuxUserForSession = ctx.getLinuxUserForSession;
73
+ var ensureProjectAccessForSession = ctx.ensureProjectAccessForSession;
46
74
  var getOsUserInfoForWs = ctx.getOsUserInfoForWs;
47
75
  var hydrateImageRefs = ctx.hydrateImageRefs;
48
76
  var onProcessingChanged = ctx.onProcessingChanged;
@@ -136,6 +164,39 @@ function attachSessions(ctx) {
136
164
  return true;
137
165
  }
138
166
 
167
+ if (msg.type === "reorder_session_bookmarks") {
168
+ if (typeof msg.sourceId === "number" && typeof msg.targetId === "number" && msg.sourceId !== msg.targetId) {
169
+ var source = sm.sessions.get(msg.sourceId);
170
+ var target = sm.sessions.get(msg.targetId);
171
+ if (!source || !target) return true;
172
+ if (usersModule.isMultiUser() && ws._clayUser) {
173
+ if (!usersModule.canAccessSession(ws._clayUser.id, source, { visibility: "public" })) return true;
174
+ if (!usersModule.canAccessSession(ws._clayUser.id, target, { visibility: "public" })) return true;
175
+ }
176
+ sm.reorderBookmarkedSessions(msg.sourceId, msg.targetId, msg.insertBefore !== false);
177
+ }
178
+ return true;
179
+ }
180
+
181
+ if (msg.type === "bulk_delete_sessions") {
182
+ if (!Array.isArray(msg.sessionIds) || msg.sessionIds.length === 0) return true;
183
+ var deletableIds = [];
184
+ for (var di = 0; di < msg.sessionIds.length; di++) {
185
+ var bulkId = msg.sessionIds[di];
186
+ if (typeof bulkId !== "number") continue;
187
+ var bulkTarget = sm.sessions.get(bulkId);
188
+ if (!bulkTarget) continue;
189
+ if (usersModule.isMultiUser() && ws._clayUser) {
190
+ if (!usersModule.canAccessSession(ws._clayUser.id, bulkTarget, { visibility: "public" })) continue;
191
+ }
192
+ deletableIds.push(bulkId);
193
+ }
194
+ if (deletableIds.length > 0) {
195
+ sm.deleteSessionsBulk(deletableIds, ws);
196
+ }
197
+ return true;
198
+ }
199
+
139
200
  if (msg.type === "transfer_project_owner") {
140
201
  // Home directory projects: ownership is permanently locked
141
202
  if (osUsers && osUsers.length > 0 && /^\/home\/[^/]+\//.test(cwd)) {
@@ -243,6 +304,17 @@ function attachSessions(ctx) {
243
304
 
244
305
  if (msg.type === "switch_session") {
245
306
  if (msg.id && sm.sessions.has(msg.id)) {
307
+ // If the target session's vendor doesn't own the currently cached
308
+ // model, clear sm.currentModel so the UI and next query don't leak
309
+ // the previous session's vendor-specific model into this one.
310
+ var switchTargetSess = sm.sessions.get(msg.id);
311
+ if (switchTargetSess && sm.currentModel) {
312
+ var targetVendor = switchTargetSess.vendor || sm.defaultVendor || null;
313
+ var tvModels = (targetVendor && sm.modelsByVendor && sm.modelsByVendor[targetVendor]) || [];
314
+ if (tvModels.length > 0 && tvModels.indexOf(sm.currentModel) === -1) {
315
+ sm.currentModel = "";
316
+ }
317
+ }
246
318
  // Check access in multi-user mode
247
319
  if (usersModule.isMultiUser() && ws._clayUser) {
248
320
  var switchTarget = sm.sessions.get(msg.id);
@@ -747,10 +819,41 @@ function attachSessions(ctx) {
747
819
  if (!pending) return true;
748
820
  delete session.pendingAskUser[toolId];
749
821
  sm.sendAndRecord(session, { type: "ask_user_answered", toolId: toolId, answers: answers });
750
- pending.resolve({
751
- behavior: "allow",
752
- updatedInput: Object.assign({}, pending.input, { answers: answers }),
753
- });
822
+
823
+ if (pending.mode === "mcp") {
824
+ // Stateless MCP path: the tool already returned. Inject the user's
825
+ // answer as a new user message so the conversation continues
826
+ // naturally on the next turn. This matches how the mate would see
827
+ // any other user input.
828
+ var answerText = formatAskUserAnswerAsMessage(pending.input, answers);
829
+ var userMsg = { type: "user_message", text: answerText };
830
+ session.history.push(userMsg);
831
+ sm.appendToSessionFile(session, userMsg);
832
+ sendToSession(session.localId, userMsg);
833
+
834
+ if (!session.isProcessing) {
835
+ session.isProcessing = true;
836
+ onProcessingChanged();
837
+ session.sentToolResults = {};
838
+ sendToSession(session.localId, { type: "status", status: "processing" });
839
+ if (!session.queryInstance && !session.worker) {
840
+ sdk.startQuery(session, answerText, undefined, ensureProjectAccessForSession(session));
841
+ } else {
842
+ sdk.pushMessage(session, answerText);
843
+ }
844
+ } else {
845
+ // Turn is still running; queue for the next turn.
846
+ sdk.pushMessage(session, answerText);
847
+ }
848
+ } else {
849
+ // Claude native AskUserQuestion path (canUseTool). The SDK is
850
+ // synchronously blocked on the permission callback, so we must
851
+ // resolve it with the standard permission shape.
852
+ pending.resolve({
853
+ behavior: "allow",
854
+ updatedInput: Object.assign({}, pending.input, { answers: answers }),
855
+ });
856
+ }
754
857
  return true;
755
858
  }
756
859
 
@@ -863,7 +966,7 @@ function attachSessions(ctx) {
863
966
  newSession.sentToolResults = {};
864
967
  sendToSession(newSession.localId, { type: "status", status: "processing" });
865
968
  newSession.acceptEditsAfterStart = true;
866
- sdk.startQuery(newSession, planPrompt, undefined, getLinuxUserForSession(newSession));
969
+ sdk.startQuery(newSession, planPrompt, undefined, ensureProjectAccessForSession(newSession));
867
970
  } catch (e) {
868
971
  console.error("[project] Error starting plan execution:", e);
869
972
  sendTo(ws, { type: "error", text: "Failed to start plan execution: " + (e.message || e) });
@@ -894,7 +997,7 @@ function attachSessions(ctx) {
894
997
  session.sentToolResults = {};
895
998
  sendToSession(session.localId, { type: "status", status: "processing" });
896
999
  if (!session.queryInstance && !session.worker) {
897
- sdk.startQuery(session, feedback, undefined, getLinuxUserForSession(session));
1000
+ sdk.startQuery(session, feedback, undefined, ensureProjectAccessForSession(session));
898
1001
  } else {
899
1002
  sdk.pushMessage(session, feedback);
900
1003
  }
@@ -13,7 +13,7 @@ var fs = require("fs");
13
13
  * send, sendTo, sendToSession, sendToSessionOthers,
14
14
  * clients, opts,
15
15
  * usersModule, matesModule,
16
- * getSessionForWs, getLinuxUserForSession, getOsUserInfoForWs,
16
+ * getSessionForWs, getLinuxUserForSession, ensureProjectAccessForSession, getOsUserInfoForWs,
17
17
  * hydrateImageRefs, saveImageFile, imagesDir,
18
18
  * onProcessingChanged, onSessionDone,
19
19
  * _loop - { handleLoopMessage: fn(ws, msg) }
@@ -49,6 +49,7 @@ function attachUserMessage(ctx) {
49
49
 
50
50
  var getSessionForWs = ctx.getSessionForWs;
51
51
  var getLinuxUserForSession = ctx.getLinuxUserForSession;
52
+ var ensureProjectAccessForSession = ctx.ensureProjectAccessForSession;
52
53
  var getOsUserInfoForWs = ctx.getOsUserInfoForWs;
53
54
 
54
55
  var hydrateImageRefs = ctx.hydrateImageRefs;
@@ -281,7 +282,7 @@ function attachUserMessage(ctx) {
281
282
  nowSession.isProcessing = true;
282
283
  onProcessingChanged();
283
284
  sendToSession(nowSession.localId, { type: "status", status: "processing" });
284
- sdk.startQuery(nowSession, schedText, null, getLinuxUserForSession(nowSession));
285
+ sdk.startQuery(nowSession, schedText, null, ensureProjectAccessForSession(nowSession));
285
286
  sm.broadcastSessionList();
286
287
  return true;
287
288
  }
@@ -484,7 +485,7 @@ function attachUserMessage(ctx) {
484
485
  // No active query (or worker idle between queries): start a new query
485
486
  session._queryStartTs = Date.now();
486
487
  console.log("[PERF] project.js: startQuery called, localId=" + session.localId + " t=0ms");
487
- sdk.startQuery(session, finalText, msg.images, getLinuxUserForSession(session));
488
+ sdk.startQuery(session, finalText, msg.images, ensureProjectAccessForSession(session));
488
489
  } else {
489
490
  sdk.pushMessage(session, finalText, msg.images);
490
491
  }
package/lib/project.js CHANGED
@@ -9,7 +9,7 @@ var { createNotesManager } = require("./notes");
9
9
  var { fetchLatestVersion, fetchVersion, isNewer } = require("./updater");
10
10
  var { execFileSync, spawn } = require("child_process");
11
11
  var usersModule = require("./users");
12
- var { resolveOsUserInfo, fsAsUser } = require("./os-users");
12
+ var { resolveOsUserInfo, fsAsUser, grantProjectAccess } = require("./os-users");
13
13
  var crisisSafety = require("./crisis-safety");
14
14
  var matesModule = require("./mates");
15
15
  var sessionSearch = require("./session-search");
@@ -160,9 +160,10 @@ function createProjectContext(opts) {
160
160
  var serverTls = opts.tls || false;
161
161
  var serverAuthToken = opts.authToken || null;
162
162
  var latestVersion = null;
163
+ var sessionTitleMigrationScheduled = false;
163
164
 
164
165
  // --- YOKE adapters (multi-vendor, lazy init) ---
165
- var _yokeState = yoke.createAdapters({ cwd: cwd });
166
+ var _yokeState = yoke.createAdapters({ cwd: cwd, slug: slug });
166
167
  var adapters = _yokeState.adapters;
167
168
  var defaultVendor = adapters.claude ? "claude" : Object.keys(adapters)[0] || "claude";
168
169
  var adapter = adapters[defaultVendor] || null;
@@ -187,6 +188,14 @@ function createProjectContext(opts) {
187
188
  return user.linuxUser;
188
189
  }
189
190
 
191
+ function ensureProjectAccessForSession(session) {
192
+ var linuxUser = getLinuxUserForSession(session);
193
+ if (linuxUser) {
194
+ grantProjectAccess(cwd, linuxUser);
195
+ }
196
+ return linuxUser;
197
+ }
198
+
190
199
  function getLinuxUserForWs(ws) {
191
200
  if (!osUsers) return null;
192
201
  if (!ws._clayUser || !ws._clayUser.linuxUser) return null;
@@ -496,6 +505,63 @@ function createProjectContext(opts) {
496
505
  console.error("[project] Failed to create debate MCP server:", e.message);
497
506
  }
498
507
 
508
+ // Ask-user MCP server (mates only)
509
+ if (isMate) {
510
+ try {
511
+ var askUserMcp = require("./ask-user-mcp-server");
512
+ var askUserToolDefs = askUserMcp.getToolDefs(function onAsk(input) {
513
+ // Stateless: the tool's job is to *post* the question card.
514
+ // We do NOT hold a promise open waiting for the user. When the
515
+ // user answers, the answer is injected as a fresh user message
516
+ // on the next turn (see project-sessions.js ask_user_response).
517
+ // This avoids HTTP long-poll timeouts on the MCP bridge and
518
+ // matches the natural multi-turn agent loop.
519
+ var session = sm.getActiveSession();
520
+ if (!session) {
521
+ // No active session means we have no way to show a card or
522
+ // route the answer. Fail closed rather than pretend success.
523
+ return Promise.resolve({
524
+ content: [{ type: "text", text: "Error: no active session in " + slug + "; cannot display question card." }],
525
+ isError: true,
526
+ });
527
+ }
528
+ if (session.loop && session.loop.active && session.loop.role !== "crafting") {
529
+ return Promise.resolve({
530
+ content: [{ type: "text", text: "Error: Autonomous mode. Make your own decision." }],
531
+ isError: true,
532
+ });
533
+ }
534
+
535
+ var toolId = "ask_" + Date.now() + "_" + crypto.randomUUID().slice(0, 8);
536
+ // Track for UI card lifecycle + answer routing. No resolve function.
537
+ session.pendingAskUser[toolId] = {
538
+ input: input,
539
+ mode: "mcp",
540
+ sessionId: session.localId,
541
+ postedAt: Date.now(),
542
+ };
543
+
544
+ sm.sendAndRecord(session, {
545
+ type: "tool_executing",
546
+ id: toolId,
547
+ name: "AskUserQuestion",
548
+ input: input,
549
+ });
550
+
551
+ return Promise.resolve({
552
+ content: [{
553
+ type: "text",
554
+ text: "The question card has been posted to the user. End this turn now without further commentary; the user's answer will arrive as the next user message.",
555
+ }],
556
+ });
557
+ });
558
+ var askUserMcpConfig = adapter.createToolServer({ name: "clay-ask-user", version: "1.0.0", tools: askUserToolDefs });
559
+ if (askUserMcpConfig) servers[askUserMcpConfig.name || "clay-ask-user"] = askUserMcpConfig;
560
+ } catch (e) {
561
+ console.error("[project] Failed to create ask-user MCP server:", e.message);
562
+ }
563
+ }
564
+
499
565
  // Browser MCP server (main project only, not mates)
500
566
  if (!isMate) {
501
567
  try {
@@ -742,7 +808,7 @@ function createProjectContext(opts) {
742
808
  session.isProcessing = true;
743
809
  onProcessingChanged();
744
810
  sendToSession(session.localId, { type: "status", status: "processing" });
745
- sdk.startQuery(session, text, null, getLinuxUserForSession(session));
811
+ sdk.startQuery(session, text, null, ensureProjectAccessForSession(session));
746
812
  sm.broadcastSessionList();
747
813
  }, schedDelay),
748
814
  };
@@ -1076,6 +1142,7 @@ function createProjectContext(opts) {
1076
1142
  pushModule: pushModule,
1077
1143
  getSessionForWs: getSessionForWs,
1078
1144
  getLinuxUserForSession: getLinuxUserForSession,
1145
+ ensureProjectAccessForSession: ensureProjectAccessForSession,
1079
1146
  getOsUserInfoForWs: getOsUserInfoForWs,
1080
1147
  hydrateImageRefs: hydrateImageRefs,
1081
1148
  onProcessingChanged: onProcessingChanged,
@@ -1124,6 +1191,7 @@ function createProjectContext(opts) {
1124
1191
  matesModule: matesModule,
1125
1192
  getSessionForWs: getSessionForWs,
1126
1193
  getLinuxUserForSession: getLinuxUserForSession,
1194
+ ensureProjectAccessForSession: ensureProjectAccessForSession,
1127
1195
  getOsUserInfoForWs: getOsUserInfoForWs,
1128
1196
  hydrateImageRefs: hydrateImageRefs,
1129
1197
  saveImageFile: saveImageFile,
@@ -1318,6 +1386,22 @@ function createProjectContext(opts) {
1318
1386
  getLatestVersion: function () { return latestVersion; },
1319
1387
  getTitle: function () { return title; },
1320
1388
  getProject: function () { return project; },
1389
+ // Exposed so the first websocket connection can lazily warm up the
1390
+ // adapters for this project (see project-connection handleConnection).
1391
+ warmup: function () {
1392
+ sdk.warmup();
1393
+ sdk.startIdleReaper();
1394
+ if (!osUsers && !sessionTitleMigrationScheduled) {
1395
+ sessionTitleMigrationScheduled = true;
1396
+ setTimeout(function () {
1397
+ try {
1398
+ sm.migrateSessionTitles(adapter, cwd);
1399
+ } catch (e) {
1400
+ console.error("[project] Session title migration failed for " + slug + ":", e && e.message ? e.message : e);
1401
+ }
1402
+ }, 5000);
1403
+ }
1404
+ },
1321
1405
  });
1322
1406
 
1323
1407
  // --- Destroy ---
@@ -1376,6 +1460,15 @@ function createProjectContext(opts) {
1376
1460
  var tmpDir = path.join(os.tmpdir(), "clay-" + cwdHash);
1377
1461
  fs.rmSync(tmpDir, { recursive: true, force: true });
1378
1462
  } catch (e) {}
1463
+
1464
+ var codexShutdown = Promise.resolve(true);
1465
+ if (adapters && adapters.codex && typeof adapters.codex.shutdown === "function") {
1466
+ codexShutdown = adapters.codex.shutdown().catch(function(err) {
1467
+ console.error("[project] Codex shutdown failed for " + slug + ":", err && err.message ? err.message : err);
1468
+ return false;
1469
+ });
1470
+ }
1471
+ return codexShutdown;
1379
1472
  }
1380
1473
 
1381
1474
  // --- Status info ---
@@ -1523,15 +1616,9 @@ function createProjectContext(opts) {
1523
1616
  broadcastClientCount();
1524
1617
  broadcastPresence();
1525
1618
  },
1526
- warmup: function () {
1527
- sdk.warmup();
1528
- sdk.startIdleReaper();
1529
- // Migrate existing relay session titles to SDK format (one-time, async)
1530
- sm.migrateSessionTitles(adapter, cwd);
1531
- },
1532
1619
  destroy: function () {
1533
1620
  sdk.stopIdleReaper();
1534
- destroy();
1621
+ return destroy();
1535
1622
  },
1536
1623
  };
1537
1624
  }
@@ -410,18 +410,18 @@
410
410
  line-height: 1.4;
411
411
  }
412
412
 
413
- /* Step 4: Autonomy options */
414
- .mate-autonomy-options {
413
+ /* Step 4: Vendor options */
414
+ .mate-vendor-option-list {
415
415
  display: flex;
416
416
  flex-direction: column;
417
- gap: 8px;
417
+ gap: 10px;
418
418
  }
419
- .mate-autonomy-btn {
419
+ .mate-vendor-option-btn {
420
420
  display: flex;
421
- flex-direction: column;
421
+ flex-direction: row;
422
422
  align-items: flex-start;
423
- gap: 2px;
424
- padding: 12px 16px;
423
+ gap: 14px;
424
+ padding: 14px 16px;
425
425
  background: var(--input-bg, #1a1a2e);
426
426
  border: 2px solid var(--border, #333);
427
427
  border-radius: 10px;
@@ -430,21 +430,36 @@
430
430
  text-align: left;
431
431
  color: var(--text, #fff);
432
432
  font-family: inherit;
433
+ width: 100%;
433
434
  }
434
- .mate-autonomy-btn:hover {
435
+ .mate-vendor-option-btn:hover {
435
436
  border-color: var(--text-dimmer, #6272a4);
436
437
  }
437
- .mate-autonomy-btn.active {
438
+ .mate-vendor-option-btn.active {
438
439
  border-color: var(--accent, #6c5ce7);
439
440
  background: var(--accent-bg, rgba(108,92,231,0.12));
440
441
  }
441
- .mate-autonomy-title {
442
+ .mate-vendor-option-icon {
443
+ width: 36px;
444
+ height: 36px;
445
+ border-radius: 8px;
446
+ flex-shrink: 0;
447
+ object-fit: cover;
448
+ }
449
+ .mate-vendor-option-text {
450
+ display: flex;
451
+ flex-direction: column;
452
+ gap: 4px;
453
+ min-width: 0;
454
+ }
455
+ .mate-vendor-option-title {
442
456
  font-weight: 600;
443
457
  font-size: 14px;
444
458
  }
445
- .mate-autonomy-desc {
459
+ .mate-vendor-option-desc {
446
460
  font-size: 12px;
447
461
  color: var(--text-muted, #9ea8c7);
462
+ line-height: 1.4;
448
463
  }
449
464
 
450
465
  /* Footer */
@@ -1997,19 +2012,60 @@ body.mate-dm-active #layout.sidebar-collapsed .mate-collapsed-info {
1997
2012
  }
1998
2013
 
1999
2014
  /* Mate datastore inspector */
2000
- #mate-sidebar-datastore {
2015
+ #mate-datastore-panel {
2016
+ position: absolute;
2017
+ inset: 0;
2018
+ z-index: 40;
2019
+ background: var(--bg);
2001
2020
  display: flex;
2002
2021
  flex-direction: column;
2003
- flex: 1;
2004
- min-height: 0;
2022
+ overflow: hidden;
2023
+ }
2024
+
2025
+ #mate-datastore-panel.hidden {
2026
+ display: none !important;
2005
2027
  }
2006
2028
 
2007
- #mate-sidebar-datastore.hidden {
2029
+ #main-column.mate-datastore-open > .title-bar-content,
2030
+ #main-column.mate-datastore-open > .dm-header-bar,
2031
+ #main-column.mate-datastore-open > #main-panels {
2008
2032
  display: none !important;
2009
2033
  }
2010
2034
 
2035
+ .mate-datastore-top-bar {
2036
+ display: flex;
2037
+ align-items: center;
2038
+ justify-content: space-between;
2039
+ height: 48px;
2040
+ padding: 0 16px;
2041
+ border-bottom: 1px solid var(--border-subtle);
2042
+ flex-shrink: 0;
2043
+ }
2044
+
2045
+ .mate-datastore-top-title {
2046
+ display: flex;
2047
+ align-items: center;
2048
+ gap: 6px;
2049
+ font-weight: 700;
2050
+ font-size: 15px;
2051
+ color: var(--text);
2052
+ }
2053
+
2054
+ .mate-datastore-top-title .lucide {
2055
+ width: 14px;
2056
+ height: 14px;
2057
+ color: var(--accent);
2058
+ opacity: 0.85;
2059
+ }
2060
+
2061
+ .mate-datastore-top-actions {
2062
+ display: flex;
2063
+ align-items: center;
2064
+ gap: 8px;
2065
+ }
2066
+
2011
2067
  .mate-db-status {
2012
- padding: 8px 12px 0;
2068
+ padding: 10px 14px 0;
2013
2069
  font-size: 12px;
2014
2070
  color: var(--text-secondary, #8e8e8e);
2015
2071
  }
@@ -2028,9 +2084,9 @@ body.mate-dm-active #layout.sidebar-collapsed .mate-collapsed-info {
2028
2084
 
2029
2085
  .mate-db-layout {
2030
2086
  display: grid;
2031
- grid-template-columns: 130px minmax(0, 1fr);
2087
+ grid-template-columns: 220px minmax(0, 1fr);
2032
2088
  gap: 10px;
2033
- padding: 8px 8px 12px;
2089
+ padding: 12px 10px 10px;
2034
2090
  min-height: 0;
2035
2091
  flex: 1;
2036
2092
  }
@@ -2044,10 +2100,10 @@ body.mate-dm-active #layout.sidebar-collapsed .mate-collapsed-info {
2044
2100
 
2045
2101
  .mate-db-table-list {
2046
2102
  overflow-y: auto;
2047
- border: 1px solid var(--border-subtle, rgba(255,255,255,0.08));
2048
- border-radius: 10px;
2049
- background: var(--bg-alt, rgba(255,255,255,0.03));
2050
- padding: 4px;
2103
+ border: 1px solid var(--border-subtle, rgba(255,255,255,0.06));
2104
+ border-radius: 12px;
2105
+ background: var(--sidebar-bg, rgba(255,255,255,0.03));
2106
+ padding: 6px;
2051
2107
  flex: 1;
2052
2108
  }
2053
2109
 
@@ -2123,46 +2179,32 @@ body.mate-dm-active #layout.sidebar-collapsed .mate-collapsed-info {
2123
2179
  }
2124
2180
 
2125
2181
  .mate-db-table-schema {
2126
- max-height: 120px;
2182
+ max-height: 180px;
2127
2183
  }
2128
2184
 
2129
- .mate-db-query,
2130
- .mate-db-params {
2131
- width: 100%;
2132
- min-height: 72px;
2133
- padding: 10px;
2134
- border-radius: 10px;
2135
- border: 1px solid var(--border-subtle, rgba(255,255,255,0.1));
2136
- background: var(--bg-secondary, rgba(255,255,255,0.04));
2137
- color: var(--text, #fff);
2138
- font-family: var(--mono-font, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
2139
- font-size: 11px;
2140
- resize: vertical;
2185
+ @media (max-width: 900px) {
2186
+ .mate-db-layout {
2187
+ grid-template-columns: 1fr;
2188
+ }
2141
2189
  }
2142
2190
 
2143
- .mate-db-params {
2144
- min-height: 54px;
2191
+ body.mate-dm-active .mate-datastore-top-bar {
2192
+ background: var(--mate-color);
2193
+ color: #fff;
2145
2194
  }
2146
2195
 
2147
- .mate-db-actions {
2148
- display: flex;
2149
- gap: 8px;
2196
+ body.mate-dm-active .mate-datastore-top-title,
2197
+ body.mate-dm-active .mate-datastore-top-title .lucide {
2198
+ color: #fff;
2150
2199
  }
2151
2200
 
2152
- .mate-db-actions button {
2153
- flex: 1;
2154
- height: 30px;
2155
- border: none;
2156
- border-radius: 8px;
2157
- background: var(--sidebar-active, rgba(255,255,255,0.08));
2158
- color: var(--text, #fff);
2159
- font-size: 12px;
2160
- font-weight: 600;
2161
- cursor: pointer;
2201
+ body.mate-dm-active .mate-datastore-top-bar .scheduler-close-btn {
2202
+ color: rgba(255, 255, 255, 0.85);
2162
2203
  }
2163
2204
 
2164
- .mate-db-actions button:hover {
2165
- background: var(--sidebar-hover, rgba(255,255,255,0.12));
2205
+ body.mate-dm-active .mate-datastore-top-bar .scheduler-close-btn:hover {
2206
+ color: #fff;
2207
+ background: rgba(255, 255, 255, 0.15);
2166
2208
  }
2167
2209
 
2168
2210
  .mate-session-item.search-match {
@@ -526,20 +526,6 @@
526
526
  white-space: nowrap;
527
527
  }
528
528
 
529
- .mobile-session-bookmark {
530
- display: inline-flex;
531
- align-items: center;
532
- color: var(--accent, #ff7b54);
533
- flex-shrink: 0;
534
- }
535
-
536
- .mobile-session-bookmark .lucide,
537
- .mobile-session-bookmark svg {
538
- width: 13px;
539
- height: 13px;
540
- display: block;
541
- }
542
-
543
529
  .mobile-session-processing {
544
530
  width: 7px;
545
531
  height: 7px;