clay-server 2.13.1-beta.1 → 2.14.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.
package/lib/daemon.js CHANGED
@@ -1421,6 +1421,19 @@ if (process.platform === "win32") {
1421
1421
  }
1422
1422
 
1423
1423
  process.on("uncaughtException", function (err) {
1424
+ var errMsg = err ? (err.message || String(err)) : "";
1425
+ var isAbort = errMsg.indexOf("Operation aborted") !== -1
1426
+ || errMsg.indexOf("AbortError") !== -1
1427
+ || (err && err.name === "AbortError");
1428
+
1429
+ if (isAbort) {
1430
+ // A single session's SDK write was aborted (e.g. stream closed before
1431
+ // write completed). This is recoverable, so do NOT tear down the whole
1432
+ // daemon and kill every other session.
1433
+ console.error("[daemon] Suppressed AbortError (single-session failure):", errMsg);
1434
+ return;
1435
+ }
1436
+
1424
1437
  console.error("[daemon] Uncaught exception:", err);
1425
1438
  writeCrashInfo({
1426
1439
  reason: err ? (err.stack || err.message || String(err)) : "unknown",
@@ -1429,3 +1442,17 @@ process.on("uncaughtException", function (err) {
1429
1442
  });
1430
1443
  gracefulShutdown();
1431
1444
  });
1445
+
1446
+ process.on("unhandledRejection", function (reason) {
1447
+ var errMsg = reason ? (reason.message || String(reason)) : "";
1448
+ var isAbort = errMsg.indexOf("Operation aborted") !== -1
1449
+ || errMsg.indexOf("AbortError") !== -1
1450
+ || (reason && reason.name === "AbortError");
1451
+
1452
+ if (isAbort) {
1453
+ console.error("[daemon] Suppressed unhandled rejection (AbortError):", errMsg);
1454
+ return;
1455
+ }
1456
+
1457
+ console.error("[daemon] Unhandled rejection:", reason);
1458
+ });
package/lib/project.js CHANGED
@@ -1065,6 +1065,8 @@ function createProjectContext(opts) {
1065
1065
  latestVersion = v;
1066
1066
  sendToAdmins({ type: "update_available", version: v });
1067
1067
  }
1068
+ }).catch(function (e) {
1069
+ console.error("[project] Background version check failed:", e.message || e);
1068
1070
  });
1069
1071
 
1070
1072
  // --- WS connection handler ---
@@ -2025,6 +2027,8 @@ function createProjectContext(opts) {
2025
2027
  console.error("[project] Error starting plan execution:", e);
2026
2028
  sendTo(ws, { type: "error", text: "Failed to start plan execution: " + (e.message || e) });
2027
2029
  }
2030
+ }).catch(function (e) {
2031
+ console.error("[project] Plan execution stream wait failed:", e.message || e);
2028
2032
  });
2029
2033
  return;
2030
2034
  }
@@ -3502,23 +3506,31 @@ function createProjectContext(opts) {
3502
3506
  }
3503
3507
  var child = spawn("npx", ["skills", "add", url, "--skill", skill, "--yes", scopeFlag], skillSpawnOpts);
3504
3508
  child.on("close", function (code) {
3505
- var success = code === 0;
3506
- send({
3507
- type: "skill_installed",
3508
- skill: skill,
3509
- scope: scope,
3510
- success: success,
3511
- error: success ? null : "Process exited with code " + code,
3512
- });
3509
+ try {
3510
+ var success = code === 0;
3511
+ send({
3512
+ type: "skill_installed",
3513
+ skill: skill,
3514
+ scope: scope,
3515
+ success: success,
3516
+ error: success ? null : "Process exited with code " + code,
3517
+ });
3518
+ } catch (e) {
3519
+ console.error("[project] skill_installed send failed:", e.message || e);
3520
+ }
3513
3521
  });
3514
3522
  child.on("error", function (err) {
3515
- send({
3516
- type: "skill_installed",
3517
- skill: skill,
3518
- scope: scope,
3519
- success: false,
3520
- error: err.message,
3521
- });
3523
+ try {
3524
+ send({
3525
+ type: "skill_installed",
3526
+ skill: skill,
3527
+ scope: scope,
3528
+ success: false,
3529
+ error: err.message,
3530
+ });
3531
+ } catch (e) {
3532
+ console.error("[project] skill_installed error send failed:", e.message || e);
3533
+ }
3522
3534
  });
3523
3535
  res.writeHead(200, { "Content-Type": "application/json" });
3524
3536
  res.end('{"ok":true}');
@@ -3722,15 +3734,21 @@ function createProjectContext(opts) {
3722
3734
  var data = "";
3723
3735
  resp.on("data", function (chunk) { data += chunk; });
3724
3736
  resp.on("end", function () {
3725
- var remoteVer = parseVersionFromSkillMd(data);
3726
- var status = "ok";
3727
- if (!installed) {
3728
- status = "missing";
3729
- } else if (remoteVer && compareVersions(installedVer, remoteVer) < 0) {
3730
- status = "outdated";
3737
+ try {
3738
+ var remoteVer = parseVersionFromSkillMd(data);
3739
+ var status = "ok";
3740
+ if (!installed) {
3741
+ status = "missing";
3742
+ } else if (remoteVer && compareVersions(installedVer, remoteVer) < 0) {
3743
+ status = "outdated";
3744
+ }
3745
+ results.push({ name: skill.name, installed: installed, installedVersion: installedVer, remoteVersion: remoteVer, status: status });
3746
+ finishOne();
3747
+ } catch (e) {
3748
+ console.error("[project] Skill version check failed for " + skill.name + ":", e.message || e);
3749
+ results.push({ name: skill.name, installed: installed, installedVersion: installedVer, remoteVersion: "", status: installed ? "ok" : "error" });
3750
+ finishOne();
3731
3751
  }
3732
- results.push({ name: skill.name, installed: installed, installedVersion: installedVer, remoteVersion: remoteVer, status: status });
3733
- finishOne();
3734
3752
  });
3735
3753
  }).on("error", function () {
3736
3754
  results.push({ name: skill.name, installed: installed, installedVersion: installedVer, remoteVersion: "", status: installed ? "ok" : "missing" });
package/lib/public/app.js CHANGED
@@ -809,6 +809,14 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
809
809
  if (msg.type === "init" && msg.sessions) {
810
810
  renderMateSessionList(msg.sessions);
811
811
  requestKnowledgeList();
812
+ // Override title bar with mate name (not session/project title)
813
+ if (dmTargetUser && dmTargetUser.isMate) {
814
+ var mateDN = dmTargetUser.displayName || "New Mate";
815
+ if (headerTitleEl) headerTitleEl.textContent = mateDN;
816
+ var tbPN = document.getElementById("title-bar-project-name");
817
+ if (tbPN) tbPN.textContent = mateDN;
818
+ updatePageTitle();
819
+ }
812
820
  }
813
821
  if (msg.type === "session_list") {
814
822
  renderMateSessionList(msg.sessions || []);
@@ -863,6 +871,11 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
863
871
  savedMainWs = null;
864
872
  }
865
873
  mateWs = null;
874
+ // If main ws is also closed (server shutdown), show disconnect screen
875
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
876
+ setStatus("disconnected");
877
+ scheduleReconnect();
878
+ }
866
879
  };
867
880
  }
868
881
 
@@ -1779,9 +1792,18 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
1779
1792
  if (!activityEl) {
1780
1793
  activityEl = document.createElement("div");
1781
1794
  activityEl.className = "activity-inline";
1782
- activityEl.innerHTML =
1783
- '<span class="activity-icon">' + iconHtml("sparkles") + '</span>' +
1784
- '<span class="activity-text"></span>';
1795
+ var isMateDmActive = dmMode && dmTargetUser && dmTargetUser.isMate;
1796
+ if (isMateDmActive) {
1797
+ activityEl.classList.add("mate-activity");
1798
+ var mateAvUrl = document.body.dataset.mateAvatarUrl || "";
1799
+ activityEl.innerHTML =
1800
+ '<img class="mate-activity-avatar" src="' + mateAvUrl + '" alt="">' +
1801
+ '<span class="activity-text"></span>';
1802
+ } else {
1803
+ activityEl.innerHTML =
1804
+ '<span class="activity-icon">' + iconHtml("sparkles") + '</span>' +
1805
+ '<span class="activity-text"></span>';
1806
+ }
1785
1807
  addToMessages(activityEl);
1786
1808
  refreshIcons();
1787
1809
  }
@@ -1795,6 +1817,32 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
1795
1817
  }
1796
1818
  }
1797
1819
 
1820
+ // --- Mate pre-thinking (instant dots before server responds) ---
1821
+ var matePreThinkingEl = null;
1822
+ function showMatePreThinking() {
1823
+ removeMatePreThinking();
1824
+ var mateName = dmTargetUser ? (dmTargetUser.displayName || "Mate") : "Mate";
1825
+ var mateAvatar = document.body.dataset.mateAvatarUrl || "";
1826
+ matePreThinkingEl = document.createElement("div");
1827
+ matePreThinkingEl.className = "thinking-item mate-thinking mate-pre-thinking";
1828
+ matePreThinkingEl.innerHTML =
1829
+ '<div class="mate-thinking-row" style="display:flex">' +
1830
+ '<img class="mate-thinking-avatar" src="' + escapeHtml(mateAvatar) + '" alt="">' +
1831
+ '<div class="mate-thinking-body">' +
1832
+ '<span class="mate-thinking-name">' + escapeHtml(mateName) + '</span>' +
1833
+ '<span class="mate-thinking-dots"><span></span><span></span><span></span></span>' +
1834
+ '</div>' +
1835
+ '</div>';
1836
+ addToMessages(matePreThinkingEl);
1837
+ scrollToBottom();
1838
+ }
1839
+ function removeMatePreThinking() {
1840
+ if (matePreThinkingEl) {
1841
+ matePreThinkingEl.remove();
1842
+ matePreThinkingEl = null;
1843
+ }
1844
+ }
1845
+
1798
1846
  // --- Config chip (model + mode + effort) ---
1799
1847
  var configChipWrap = $("config-chip-wrap");
1800
1848
  var configChip = $("config-chip");
@@ -2503,6 +2551,9 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
2503
2551
  var win = contextData.contextWindow;
2504
2552
  return win > 0 ? Math.round((used / win) * 100) : 0;
2505
2553
  },
2554
+ isMateDm: function () { return dmMode && dmTargetUser && dmTargetUser.isMate; },
2555
+ getMateName: function () { return dmTargetUser ? (dmTargetUser.displayName || "Mate") : "Mate"; },
2556
+ getMateAvatarUrl: function () { return document.body.dataset.mateAvatarUrl || ""; },
2506
2557
  });
2507
2558
 
2508
2559
  // isPlanFile, toolSummary, toolActivityText, shortPath -> modules/tools.js
@@ -3393,9 +3444,17 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
3393
3444
  projectName = msg.project || msg.cwd;
3394
3445
  if (msg.slug) currentSlug = msg.slug;
3395
3446
  try { localStorage.setItem("clay-project-name-" + (currentSlug || "default"), projectName); } catch (e) {}
3396
- headerTitleEl.textContent = projectName;
3397
- var tbProjectName = $("title-bar-project-name");
3398
- if (tbProjectName) tbProjectName.textContent = msg.title || projectName;
3447
+ // In mate DM, keep title as mate name (not project/session title)
3448
+ if (dmMode && dmTargetUser && dmTargetUser.isMate) {
3449
+ var _mateDN = dmTargetUser.displayName || "New Mate";
3450
+ headerTitleEl.textContent = _mateDN;
3451
+ var tbProjectName = $("title-bar-project-name");
3452
+ if (tbProjectName) tbProjectName.textContent = _mateDN;
3453
+ } else {
3454
+ headerTitleEl.textContent = projectName;
3455
+ var tbProjectName = $("title-bar-project-name");
3456
+ if (tbProjectName) tbProjectName.textContent = msg.title || projectName;
3457
+ }
3399
3458
  updatePageTitle();
3400
3459
  if (msg.version) {
3401
3460
  setPaletteVersion(msg.version);
@@ -3682,6 +3741,7 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
3682
3741
  break;
3683
3742
 
3684
3743
  case "thinking_start":
3744
+ removeMatePreThinking();
3685
3745
  startThinking();
3686
3746
  break;
3687
3747
 
@@ -3696,6 +3756,7 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
3696
3756
 
3697
3757
  case "delta":
3698
3758
  if (typeof msg.text !== "string") break;
3759
+ removeMatePreThinking();
3699
3760
  stopThinking();
3700
3761
  resetThinkingGroup();
3701
3762
  setActivity(null);
@@ -3703,6 +3764,7 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
3703
3764
  break;
3704
3765
 
3705
3766
  case "tool_start":
3767
+ removeMatePreThinking();
3706
3768
  stopThinking();
3707
3769
  markAllToolsDone();
3708
3770
  if (msg.name === "EnterPlanMode") {
@@ -4519,6 +4581,10 @@ import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } fro
4519
4581
  isDmMode: function () { return dmMode && !(dmTargetUser && dmTargetUser.isMate); },
4520
4582
  getDmKey: function () { return dmKey; },
4521
4583
  handleDmSend: function () { handleDmSend(); },
4584
+ isMateDm: function () { return dmMode && dmTargetUser && dmTargetUser.isMate; },
4585
+ getMateName: function () { return dmTargetUser ? (dmTargetUser.displayName || "Mate") : "Mate"; },
4586
+ getMateAvatarUrl: function () { return document.body.dataset.mateAvatarUrl || ""; },
4587
+ showMatePreThinking: function () { showMatePreThinking(); },
4522
4588
  });
4523
4589
 
4524
4590
  // --- STT module (voice input via Web Speech API) ---
@@ -1323,6 +1323,230 @@ body.mate-dm-active .turn-meta {
1323
1323
  display: none;
1324
1324
  }
1325
1325
 
1326
+ /* ==========================================================================
1327
+ Mate DM: Humanized thinking / tools / permissions
1328
+ ========================================================================== */
1329
+
1330
+ /* --- Mate Thinking: avatar + name + bouncing dots --- */
1331
+ .mate-thinking-row {
1332
+ display: none;
1333
+ align-items: center;
1334
+ gap: 10px;
1335
+ padding: 6px 0;
1336
+ }
1337
+ body.mate-dm-active .mate-thinking:not(.done) .mate-thinking-row {
1338
+ display: flex;
1339
+ }
1340
+ .mate-thinking-avatar {
1341
+ width: 28px;
1342
+ height: 28px;
1343
+ border-radius: 6px;
1344
+ flex-shrink: 0;
1345
+ }
1346
+ .mate-thinking-body {
1347
+ display: flex;
1348
+ align-items: center;
1349
+ gap: 8px;
1350
+ }
1351
+ .mate-thinking-name {
1352
+ font-weight: 700;
1353
+ font-size: 14px;
1354
+ color: var(--text);
1355
+ }
1356
+ .mate-thinking-dots {
1357
+ display: inline-flex;
1358
+ align-items: center;
1359
+ gap: 3px;
1360
+ }
1361
+ .mate-thinking-dots span {
1362
+ width: 6px;
1363
+ height: 6px;
1364
+ border-radius: 50%;
1365
+ background: var(--text-muted);
1366
+ animation: mate-dot-bounce 1.4s ease-in-out infinite;
1367
+ }
1368
+ .mate-thinking-dots span:nth-child(2) {
1369
+ animation-delay: 0.2s;
1370
+ }
1371
+ .mate-thinking-dots span:nth-child(3) {
1372
+ animation-delay: 0.4s;
1373
+ }
1374
+ @keyframes mate-dot-bounce {
1375
+ 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
1376
+ 30% { transform: translateY(-4px); opacity: 1; }
1377
+ }
1378
+
1379
+ /* When done, hide mate row (JS does this too), show compact expandable header */
1380
+ body.mate-dm-active .mate-thinking.done .mate-thinking-row {
1381
+ display: none;
1382
+ }
1383
+ body.mate-dm-active .mate-thinking.done .thinking-header {
1384
+ display: inline-flex !important;
1385
+ font-size: 12px;
1386
+ padding: 4px 10px;
1387
+ background: rgba(var(--overlay-rgb), 0.04);
1388
+ border-radius: 6px;
1389
+ opacity: 0.7;
1390
+ transition: opacity 0.15s;
1391
+ }
1392
+ body.mate-dm-active .mate-thinking.done .thinking-header:hover {
1393
+ opacity: 1;
1394
+ background: rgba(var(--overlay-rgb), 0.08);
1395
+ }
1396
+ body.mate-dm-active .mate-thinking .thinking-content {
1397
+ max-height: 0;
1398
+ overflow: hidden;
1399
+ transition: max-height 0.25s ease;
1400
+ }
1401
+ body.mate-dm-active .mate-thinking.expanded .thinking-content {
1402
+ max-height: 2000px;
1403
+ }
1404
+
1405
+ /* --- Mate Tool Group: compact, always collapsed with summary header --- */
1406
+ body.mate-dm-active .mate-tool-group {
1407
+ margin: 2px 0;
1408
+ }
1409
+ body.mate-dm-active .mate-tool-group .tool-group-header {
1410
+ font-size: 12px;
1411
+ padding: 4px 10px;
1412
+ background: rgba(var(--overlay-rgb), 0.04);
1413
+ border-radius: 6px;
1414
+ opacity: 0.7;
1415
+ transition: opacity 0.15s;
1416
+ }
1417
+ body.mate-dm-active .mate-tool-group .tool-group-header:hover {
1418
+ opacity: 1;
1419
+ background: rgba(var(--overlay-rgb), 0.08);
1420
+ }
1421
+ body.mate-dm-active .mate-tool-group .tool-group-label {
1422
+ font-size: 12px;
1423
+ }
1424
+ body.mate-dm-active .mate-tool-group .tool-item {
1425
+ border: none;
1426
+ background: none;
1427
+ padding: 2px 0;
1428
+ }
1429
+ body.mate-dm-active .mate-tool-group .tool-name {
1430
+ font-size: 13px;
1431
+ color: var(--text-muted);
1432
+ }
1433
+
1434
+ /* --- Mate Permission: conversational chat bubble --- */
1435
+ body.mate-dm-active .mate-permission {
1436
+ border: none;
1437
+ border-radius: 0;
1438
+ background: none;
1439
+ box-shadow: none;
1440
+ padding: 4px 16px;
1441
+ }
1442
+ body.mate-dm-active .mate-permission:hover {
1443
+ background: var(--bg-alt);
1444
+ }
1445
+ .mate-permission-ask {
1446
+ font-size: 15px;
1447
+ line-height: 1.46;
1448
+ color: var(--text);
1449
+ padding-left: 36px; /* align with name (avatar 28px + gap 8px) */
1450
+ margin-bottom: 8px;
1451
+ }
1452
+ .mate-permission-details {
1453
+ padding-left: 36px;
1454
+ margin-bottom: 8px;
1455
+ font-size: 12px;
1456
+ color: var(--text-muted);
1457
+ }
1458
+ .mate-permission-details summary {
1459
+ cursor: pointer;
1460
+ user-select: none;
1461
+ }
1462
+ .mate-permission-details pre {
1463
+ font-size: 11px;
1464
+ margin: 4px 0 0;
1465
+ padding: 8px;
1466
+ background: rgba(var(--overlay-rgb), 0.04);
1467
+ border-radius: 6px;
1468
+ overflow-x: auto;
1469
+ max-height: 200px;
1470
+ }
1471
+ .mate-permission-actions {
1472
+ display: flex;
1473
+ gap: 6px;
1474
+ padding-left: 36px;
1475
+ padding-bottom: 4px;
1476
+ }
1477
+ .mate-permission-reply {
1478
+ padding: 5px 14px;
1479
+ border-radius: 16px;
1480
+ font-size: 13px;
1481
+ font-weight: 500;
1482
+ font-family: inherit;
1483
+ cursor: pointer;
1484
+ border: 1px solid var(--border);
1485
+ background: var(--bg-alt);
1486
+ color: var(--text);
1487
+ transition: background 0.15s, border-color 0.15s;
1488
+ }
1489
+ .mate-permission-reply:hover {
1490
+ border-color: var(--text-dimmer);
1491
+ background: rgba(var(--overlay-rgb), 0.06);
1492
+ }
1493
+ .mate-permission-allow {
1494
+ background: var(--accent, #6c5ce7);
1495
+ color: #fff;
1496
+ border-color: var(--accent, #6c5ce7);
1497
+ }
1498
+ .mate-permission-allow:hover {
1499
+ opacity: 0.9;
1500
+ background: var(--accent, #6c5ce7);
1501
+ border-color: var(--accent, #6c5ce7);
1502
+ }
1503
+ .mate-permission-deny {
1504
+ color: var(--text-muted);
1505
+ }
1506
+ /* Resolved state */
1507
+ body.mate-dm-active .mate-permission.resolved .mate-permission-actions {
1508
+ pointer-events: none;
1509
+ }
1510
+ body.mate-dm-active .mate-permission.resolved .mate-permission-reply {
1511
+ display: none;
1512
+ }
1513
+ body.mate-dm-active .mate-permission.resolved .permission-decision-label {
1514
+ font-size: 12px;
1515
+ color: var(--text-dimmer);
1516
+ padding-left: 36px;
1517
+ }
1518
+
1519
+ /* --- Mate Activity: avatar + text (hidden, dots row is enough) --- */
1520
+ body.mate-dm-active .activity-inline {
1521
+ display: none;
1522
+ }
1523
+
1524
+ /* --- Mate AskUserQuestion: avatar + content layout (matches msg-assistant) --- */
1525
+ body.mate-dm-active .mate-ask-user {
1526
+ display: flex;
1527
+ flex-direction: row;
1528
+ align-items: flex-start;
1529
+ gap: 8px;
1530
+ padding: 4px 16px;
1531
+ margin: 0;
1532
+ max-width: 100%;
1533
+ }
1534
+ body.mate-dm-active .mate-ask-user:hover {
1535
+ background: var(--bg-alt);
1536
+ }
1537
+ body.mate-dm-active .mate-ask-user > .dm-bubble-avatar {
1538
+ display: block;
1539
+ width: 36px;
1540
+ height: 36px;
1541
+ border-radius: 6px;
1542
+ flex-shrink: 0;
1543
+ margin-top: 2px;
1544
+ }
1545
+ body.mate-dm-active .mate-ask-user > .dm-bubble-content {
1546
+ flex: 1;
1547
+ min-width: 0;
1548
+ }
1549
+
1326
1550
  /* --- Interstitial elements: indent to align with message content (16px pad + 36px avatar + 8px gap = 60px) --- */
1327
1551
  body.mate-dm-active .thinking-item,
1328
1552
  body.mate-dm-active .tool-item,
@@ -105,6 +105,11 @@ export function sendMessage() {
105
105
  }
106
106
  ctx.ws.send(JSON.stringify(payload));
107
107
 
108
+ // Mate DM: show pre-thinking dots immediately (before server responds)
109
+ if (ctx.isMateDm && ctx.isMateDm()) {
110
+ ctx.showMatePreThinking();
111
+ }
112
+
108
113
  ctx.inputEl.value = "";
109
114
  sendInputSync();
110
115
  clearPendingImages();
@@ -112,9 +112,16 @@ function updateToolGroupHeader(group) {
112
112
  refreshIcons();
113
113
  }
114
114
 
115
- // Show group header only when 2+ visible tools
115
+ // Show group header only when 2+ visible tools (or always in mate DM)
116
116
  var header = group.el.querySelector(".tool-group-header");
117
- if (group.toolCount >= 2) {
117
+ var isMate = group.el.classList.contains("mate-tool-group");
118
+ if (isMate) {
119
+ // Mate DM: always show header and collapse
120
+ header.style.display = "";
121
+ if (!group.userToggled) {
122
+ group.el.classList.add("collapsed");
123
+ }
124
+ } else if (group.toolCount >= 2) {
118
125
  header.style.display = "";
119
126
  // When 2+ tools, ensure collapsed by default (unless user already toggled)
120
127
  if (!group.userToggled && !group.el.classList.contains("expanded-by-user")) {
@@ -182,6 +189,29 @@ export function renderAskUserQuestion(toolId, input) {
182
189
  container.className = "ask-user-container";
183
190
  container.dataset.toolId = toolId;
184
191
 
192
+ // Mate DM: wrap in avatar + content layout (same as msg-assistant)
193
+ var mateContentWrap = null;
194
+ if (ctx.isMateDm && ctx.isMateDm()) {
195
+ container.classList.add("mate-ask-user");
196
+ var mateName = ctx.getMateName();
197
+ var mateAvatar = ctx.getMateAvatarUrl();
198
+
199
+ var avi = document.createElement("img");
200
+ avi.className = "dm-bubble-avatar";
201
+ avi.src = mateAvatar;
202
+ container.appendChild(avi);
203
+
204
+ mateContentWrap = document.createElement("div");
205
+ mateContentWrap.className = "dm-bubble-content";
206
+
207
+ var headerEl = document.createElement("div");
208
+ headerEl.className = "dm-bubble-header";
209
+ headerEl.innerHTML =
210
+ '<span class="dm-bubble-name">' + escapeHtml(mateName) + '</span>' +
211
+ '<span class="dm-bubble-time">' + String(new Date().getHours()).padStart(2, "0") + ":" + String(new Date().getMinutes()).padStart(2, "0") + '</span>';
212
+ mateContentWrap.appendChild(headerEl);
213
+ }
214
+
185
215
  var answers = {};
186
216
  var multiSelections = {};
187
217
 
@@ -300,6 +330,20 @@ export function renderAskUserQuestion(toolId, input) {
300
330
  });
301
331
  container.appendChild(skipBtn);
302
332
 
333
+ // Mate DM: move all content into the bubble content wrapper
334
+ if (mateContentWrap) {
335
+ var children = [];
336
+ for (var ci = 0; ci < container.childNodes.length; ci++) {
337
+ if (container.childNodes[ci] !== container.querySelector(".dm-bubble-avatar")) {
338
+ children.push(container.childNodes[ci]);
339
+ }
340
+ }
341
+ for (var cj = 0; cj < children.length; cj++) {
342
+ mateContentWrap.appendChild(children[cj]);
343
+ }
344
+ container.appendChild(mateContentWrap);
345
+ }
346
+
303
347
  ctx.addToMessages(container);
304
348
  disableMainInput();
305
349
  ctx.setActivity(null);
@@ -378,6 +422,12 @@ export function renderPermissionRequest(requestId, toolName, toolInput, decision
378
422
  return;
379
423
  }
380
424
 
425
+ // Mate DM: render as conversational chat bubble instead of formal dialog
426
+ if (ctx.isMateDm && ctx.isMateDm()) {
427
+ renderMatePermission(requestId, toolName, toolInput);
428
+ return;
429
+ }
430
+
381
431
  var container = document.createElement("div");
382
432
  container.className = "permission-container";
383
433
  container.dataset.requestId = requestId;
@@ -613,6 +663,108 @@ function sendPlanResponse(container, requestId, decision, feedback) {
613
663
  delete pendingPermissions[requestId];
614
664
  }
615
665
 
666
+ function matePermissionVerb(toolName) {
667
+ switch (toolName) {
668
+ case "Write": return "write to";
669
+ case "Edit": return "edit";
670
+ case "Read": return "read";
671
+ case "Bash": return "run a command on";
672
+ case "Grep": return "search";
673
+ case "Glob": return "search for files in";
674
+ case "WebFetch": return "fetch";
675
+ case "WebSearch": return "search the web for";
676
+ default: return "use " + toolName + " on";
677
+ }
678
+ }
679
+
680
+ function matePermissionTarget(toolName, toolInput) {
681
+ if (!toolInput || typeof toolInput !== "object") return "";
682
+ switch (toolName) {
683
+ case "Write": case "Edit": case "Read": return shortPath(toolInput.file_path);
684
+ case "Bash": return toolInput.description || (toolInput.command || "").substring(0, 60);
685
+ case "Grep": return toolInput.pattern || "";
686
+ case "Glob": return toolInput.pattern || "";
687
+ case "WebFetch": return toolInput.url || "";
688
+ case "WebSearch": return toolInput.query || "";
689
+ default: return "";
690
+ }
691
+ }
692
+
693
+ function renderMatePermission(requestId, toolName, toolInput) {
694
+ var mateName = ctx.getMateName();
695
+ var mateAvatar = ctx.getMateAvatarUrl();
696
+ var verb = matePermissionVerb(toolName);
697
+ var target = matePermissionTarget(toolName, toolInput);
698
+
699
+ var container = document.createElement("div");
700
+ container.className = "permission-container mate-permission";
701
+ container.dataset.requestId = requestId;
702
+
703
+ // Chat bubble: avatar + name + time
704
+ var headerRow = document.createElement("div");
705
+ headerRow.className = "dm-bubble-header";
706
+ headerRow.style.cssText = "display:flex;align-items:center;gap:8px;margin-bottom:6px";
707
+ headerRow.innerHTML =
708
+ '<img class="dm-bubble-avatar" src="' + escapeHtml(mateAvatar) + '" alt="" style="display:block;width:28px;height:28px;border-radius:6px">' +
709
+ '<span class="dm-bubble-name">' + escapeHtml(mateName) + '</span>' +
710
+ '<span class="dm-bubble-time">' + String(new Date().getHours()).padStart(2, "0") + ":" + String(new Date().getMinutes()).padStart(2, "0") + '</span>';
711
+ container.appendChild(headerRow);
712
+
713
+ // Conversational ask text
714
+ var askText = document.createElement("div");
715
+ askText.className = "mate-permission-ask";
716
+ var askMsg = "Can I " + verb + (target ? " " : "") + (target ? target : "") + "?";
717
+ askText.textContent = askMsg;
718
+ container.appendChild(askText);
719
+
720
+ // Collapsible details (subtle)
721
+ var details = document.createElement("details");
722
+ details.className = "mate-permission-details";
723
+ var detailsSummary = document.createElement("summary");
724
+ detailsSummary.textContent = "Details";
725
+ var detailsPre = document.createElement("pre");
726
+ detailsPre.textContent = JSON.stringify(toolInput, null, 2);
727
+ details.appendChild(detailsSummary);
728
+ details.appendChild(detailsPre);
729
+ container.appendChild(details);
730
+
731
+ // Quick reply buttons (chat-style)
732
+ var actions = document.createElement("div");
733
+ actions.className = "permission-actions mate-permission-actions";
734
+
735
+ var allowBtn = document.createElement("button");
736
+ allowBtn.className = "mate-permission-reply mate-permission-allow";
737
+ allowBtn.textContent = "Sure";
738
+ allowBtn.addEventListener("click", function () {
739
+ sendPermissionResponse(container, requestId, "allow");
740
+ });
741
+
742
+ var alwaysBtn = document.createElement("button");
743
+ alwaysBtn.className = "mate-permission-reply mate-permission-always";
744
+ alwaysBtn.textContent = "Always allow this";
745
+ alwaysBtn.addEventListener("click", function () {
746
+ sendPermissionResponse(container, requestId, "allow_always");
747
+ });
748
+
749
+ var denyBtn = document.createElement("button");
750
+ denyBtn.className = "mate-permission-reply mate-permission-deny";
751
+ denyBtn.textContent = "No";
752
+ denyBtn.addEventListener("click", function () {
753
+ sendPermissionResponse(container, requestId, "deny");
754
+ });
755
+
756
+ actions.appendChild(allowBtn);
757
+ actions.appendChild(alwaysBtn);
758
+ actions.appendChild(denyBtn);
759
+ container.appendChild(actions);
760
+
761
+ ctx.addToMessages(container);
762
+ pendingPermissions[requestId] = container;
763
+ refreshIcons();
764
+ ctx.setActivity(null);
765
+ ctx.scrollToBottom();
766
+ }
767
+
616
768
  function sendPermissionResponse(container, requestId, decision) {
617
769
  if (container.classList.contains("resolved")) return;
618
770
  container.classList.add("resolved");
@@ -1173,6 +1325,13 @@ export function startThinking() {
1173
1325
  var el = thinkingGroup.el;
1174
1326
  el.classList.remove("done");
1175
1327
  el.querySelector(".thinking-content").textContent = "";
1328
+ // Mate mode: restore dots row, hide thinking header
1329
+ if (el.classList.contains("mate-thinking")) {
1330
+ var dotsRow = el.querySelector(".mate-thinking-row");
1331
+ if (dotsRow) dotsRow.style.display = "flex";
1332
+ var header = el.querySelector(".thinking-header");
1333
+ if (header) header.style.display = "none";
1334
+ }
1176
1335
  currentThinking = { el: el, fullText: "", startTime: Date.now() };
1177
1336
  refreshIcons();
1178
1337
  ctx.scrollToBottom();
@@ -1182,14 +1341,36 @@ export function startThinking() {
1182
1341
 
1183
1342
  var el = document.createElement("div");
1184
1343
  el.className = "thinking-item";
1185
- el.innerHTML =
1186
- '<div class="thinking-header">' +
1187
- '<span class="thinking-chevron">' + iconHtml("chevron-right") + '</span>' +
1188
- '<span class="thinking-label">Thinking</span>' +
1189
- '<span class="thinking-duration"></span>' +
1190
- '<span class="thinking-spinner">' + iconHtml("loader", "icon-spin") + '</span>' +
1191
- '</div>' +
1192
- '<div class="thinking-content"></div>';
1344
+
1345
+ if (ctx.isMateDm()) {
1346
+ var mateName = ctx.getMateName();
1347
+ var mateAvatar = ctx.getMateAvatarUrl();
1348
+ el.classList.add("mate-thinking");
1349
+ el.innerHTML =
1350
+ '<div class="mate-thinking-row">' +
1351
+ '<img class="mate-thinking-avatar" src="' + escapeHtml(mateAvatar) + '" alt="">' +
1352
+ '<div class="mate-thinking-body">' +
1353
+ '<span class="mate-thinking-name">' + escapeHtml(mateName) + '</span>' +
1354
+ '<span class="mate-thinking-dots"><span></span><span></span><span></span></span>' +
1355
+ '</div>' +
1356
+ '</div>' +
1357
+ '<div class="thinking-header" style="display:none">' +
1358
+ '<span class="thinking-chevron">' + iconHtml("chevron-right") + '</span>' +
1359
+ '<span class="thinking-label">Thinking</span>' +
1360
+ '<span class="thinking-duration"></span>' +
1361
+ '<span class="thinking-spinner">' + iconHtml("loader", "icon-spin") + '</span>' +
1362
+ '</div>' +
1363
+ '<div class="thinking-content"></div>';
1364
+ } else {
1365
+ el.innerHTML =
1366
+ '<div class="thinking-header">' +
1367
+ '<span class="thinking-chevron">' + iconHtml("chevron-right") + '</span>' +
1368
+ '<span class="thinking-label">Thinking</span>' +
1369
+ '<span class="thinking-duration"></span>' +
1370
+ '<span class="thinking-spinner">' + iconHtml("loader", "icon-spin") + '</span>' +
1371
+ '</div>' +
1372
+ '<div class="thinking-content"></div>';
1373
+ }
1193
1374
 
1194
1375
  el.querySelector(".thinking-header").addEventListener("click", function () {
1195
1376
  el.classList.toggle("expanded");
@@ -1221,6 +1402,16 @@ export function stopThinking(duration) {
1221
1402
  } else {
1222
1403
  currentThinking.el.querySelector(".thinking-duration").textContent = " " + secs.toFixed(1) + "s";
1223
1404
  }
1405
+ // In mate mode: hide dots, show compact expandable thinking header
1406
+ if (currentThinking.el.classList.contains("mate-thinking")) {
1407
+ var dotsRow = currentThinking.el.querySelector(".mate-thinking-row");
1408
+ if (dotsRow) dotsRow.style.display = "none";
1409
+ var header = currentThinking.el.querySelector(".thinking-header");
1410
+ if (header) {
1411
+ header.style.display = "";
1412
+ header.style.cursor = "pointer";
1413
+ }
1414
+ }
1224
1415
  currentThinking = null;
1225
1416
  }
1226
1417
 
@@ -1238,6 +1429,7 @@ export function createToolItem(id, name) {
1238
1429
  toolGroupCounter++;
1239
1430
  var groupEl = document.createElement("div");
1240
1431
  groupEl.className = "tool-group";
1432
+ if (ctx.isMateDm()) groupEl.classList.add("mate-tool-group");
1241
1433
  groupEl.dataset.groupId = "g" + toolGroupCounter;
1242
1434
  groupEl.innerHTML =
1243
1435
  '<div class="tool-group-header" style="display:none">' +
@@ -1250,6 +1442,7 @@ export function createToolItem(id, name) {
1250
1442
 
1251
1443
  groupEl.querySelector(".tool-group-header").addEventListener("click", function () {
1252
1444
  groupEl.classList.toggle("collapsed");
1445
+ if (currentToolGroup) currentToolGroup.userToggled = true;
1253
1446
  });
1254
1447
 
1255
1448
  ctx.addToMessages(groupEl);
package/lib/sdk-bridge.js CHANGED
@@ -857,6 +857,8 @@ function createSDKBridge(opts) {
857
857
  signal: session.abortController ? { addEventListener: function() {} } : undefined,
858
858
  }).then(function(result) {
859
859
  worker.send({ type: "permission_response", requestId: msg.requestId, result: result });
860
+ }).catch(function(e) {
861
+ console.error("[sdk-bridge] permission_response send failed:", e.message || e);
860
862
  });
861
863
  break;
862
864
 
@@ -867,6 +869,8 @@ function createSDKBridge(opts) {
867
869
  signal: session.abortController ? { addEventListener: function() {} } : undefined,
868
870
  }).then(function(result) {
869
871
  worker.send({ type: "ask_user_response", toolUseId: msg.toolUseId, result: result });
872
+ }).catch(function(e) {
873
+ console.error("[sdk-bridge] ask_user_response send failed:", e.message || e);
870
874
  });
871
875
  break;
872
876
 
@@ -882,6 +886,8 @@ function createSDKBridge(opts) {
882
886
  signal: session.abortController ? { addEventListener: function() {} } : undefined,
883
887
  }).then(function(result) {
884
888
  worker.send({ type: "elicitation_response", requestId: msg.requestId, result: result });
889
+ }).catch(function(e) {
890
+ console.error("[sdk-bridge] elicitation_response send failed:", e.message || e);
885
891
  });
886
892
  break;
887
893
 
package/lib/sessions.js CHANGED
@@ -628,6 +628,8 @@ function createSessionManager(opts) {
628
628
  }
629
629
  chain.then(function() {
630
630
  console.log("[session] Migrated " + toMigrate.length + " session title(s) to SDK format");
631
+ }).catch(function(e) {
632
+ console.error("[session] Migration chain failed:", e.message || e);
631
633
  });
632
634
  }).catch(function() {});
633
635
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clay-server",
3
- "version": "2.13.1-beta.1",
3
+ "version": "2.14.0-beta.10",
4
4
  "description": "Web UI for Claude Code. Any device. Push notifications.",
5
5
  "bin": {
6
6
  "clay-server": "./bin/cli.js",