clay-server 2.30.0 → 2.31.0

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 (36) hide show
  1. package/lib/email-accounts.js +299 -0
  2. package/lib/email-mcp-server.js +646 -0
  3. package/lib/project-connection.js +26 -2
  4. package/lib/project-email.js +418 -0
  5. package/lib/project-sessions.js +16 -0
  6. package/lib/project-user-message.js +26 -5
  7. package/lib/project.js +72 -25
  8. package/lib/public/app.js +18 -5
  9. package/lib/public/css/filebrowser.css +80 -2
  10. package/lib/public/css/input.css +196 -0
  11. package/lib/public/css/notifications-center.css +3 -0
  12. package/lib/public/css/sidebar.css +77 -2
  13. package/lib/public/css/sticky-notes.css +0 -48
  14. package/lib/public/css/user-settings.css +85 -0
  15. package/lib/public/icons/email/gmail.svg +7 -0
  16. package/lib/public/icons/email/outlook.svg +35 -0
  17. package/lib/public/icons/email/yahoo.svg +1 -0
  18. package/lib/public/index.html +36 -3
  19. package/lib/public/modules/app-dm.js +4 -9
  20. package/lib/public/modules/app-messages.js +37 -2
  21. package/lib/public/modules/app-panels.js +2 -1
  22. package/lib/public/modules/context-sources.js +527 -1
  23. package/lib/public/modules/filebrowser.js +72 -0
  24. package/lib/public/modules/mate-sidebar.js +7 -0
  25. package/lib/public/modules/sidebar-mobile.js +1 -1
  26. package/lib/public/modules/sidebar.js +144 -2
  27. package/lib/public/modules/sticky-notes.js +1 -91
  28. package/lib/public/modules/terminal.js +0 -12
  29. package/lib/public/modules/theme.js +4 -0
  30. package/lib/public/modules/tools.js +23 -0
  31. package/lib/public/modules/user-settings.js +74 -0
  32. package/lib/sdk-bridge.js +16 -0
  33. package/lib/sdk-message-processor.js +33 -0
  34. package/lib/server-email.js +148 -0
  35. package/lib/server.js +5 -0
  36. package/package.json +3 -2
@@ -113,6 +113,15 @@ export function initSidebar(_ctx) {
113
113
  });
114
114
  }
115
115
 
116
+ // --- Tools section collapse/expand ---
117
+ var toolsToggle = ctx.$("sidebar-tools-toggle");
118
+ var toolsSection = ctx.$("sidebar-tools");
119
+ if (toolsToggle && toolsSection) {
120
+ toolsToggle.addEventListener("click", function () {
121
+ toolsSection.classList.toggle("collapsed");
122
+ });
123
+ }
124
+
116
125
  // --- Panel switch (sessions / files / projects) ---
117
126
  var fileBrowserBtn = ctx.$("file-browser-btn");
118
127
  var projectsPanel = ctx.$("sidebar-panel-projects");
@@ -143,16 +152,37 @@ export function initSidebar(_ctx) {
143
152
 
144
153
  function showFilesPanel() {
145
154
  hideAllPanels();
146
- if (filesPanel) filesPanel.classList.remove("hidden");
155
+ if (filesPanel) {
156
+ filesPanel.classList.remove("hidden");
157
+ filesPanel.classList.remove("fb-exit");
158
+ filesPanel.classList.add("fb-enter");
159
+ }
147
160
  if (filesHeaderContent) filesHeaderContent.classList.remove("hidden");
148
161
  if (ctx.onFilesTabOpen) ctx.onFilesTabOpen();
149
162
  }
150
163
 
164
+ function hideFilesPanel(cb) {
165
+ if (!filesPanel || filesPanel.classList.contains("hidden")) { if (cb) cb(); return; }
166
+ filesPanel.classList.remove("fb-enter");
167
+ filesPanel.classList.add("fb-exit");
168
+ function onDone() {
169
+ filesPanel.removeEventListener("animationend", onDone);
170
+ filesPanel.classList.remove("fb-exit");
171
+ filesPanel.classList.add("hidden");
172
+ if (cb) cb();
173
+ }
174
+ filesPanel.addEventListener("animationend", onDone);
175
+ }
176
+
151
177
  if (fileBrowserBtn) {
152
178
  fileBrowserBtn.addEventListener("click", showFilesPanel);
153
179
  }
154
180
  if (filePanelClose) {
155
- filePanelClose.addEventListener("click", showSessionsPanel);
181
+ filePanelClose.addEventListener("click", function() {
182
+ hideFilesPanel(function() {
183
+ showSessionsPanel();
184
+ });
185
+ });
156
186
  }
157
187
 
158
188
  // --- User island width sync ---
@@ -192,6 +222,9 @@ export function initSidebar(_ctx) {
192
222
  if (newWidth < 192) newWidth = 192;
193
223
  if (newWidth > 320) newWidth = 320;
194
224
  sidebarColumn.style.width = newWidth + "px";
225
+ // Sync mate sidebar to same width
226
+ var mateSC = document.getElementById("mate-sidebar-column");
227
+ if (mateSC) mateSC.style.width = newWidth + "px";
195
228
  syncResizeHandle();
196
229
  syncUserIslandWidth();
197
230
  }
@@ -234,6 +267,14 @@ export function initSidebar(_ctx) {
234
267
  sidebarColumn.style.width = px + "px";
235
268
  sidebarColumn.offsetWidth; // force reflow
236
269
  sidebarColumn.style.transition = "";
270
+ // Sync mate sidebar
271
+ var mateSC2 = document.getElementById("mate-sidebar-column");
272
+ if (mateSC2) {
273
+ mateSC2.style.transition = "none";
274
+ mateSC2.style.width = px + "px";
275
+ mateSC2.offsetWidth;
276
+ mateSC2.style.transition = "";
277
+ }
237
278
  }
238
279
  }
239
280
  } catch (e) {}
@@ -242,6 +283,107 @@ export function initSidebar(_ctx) {
242
283
  syncUserIslandWidth();
243
284
  }
244
285
 
286
+ // --- Mate sidebar resize handle ---
287
+ var mateResizeHandle = document.getElementById("mate-sidebar-resize-handle");
288
+ var mateSidebarCol = document.getElementById("mate-sidebar-column");
289
+
290
+ function syncMateResizeHandle() {
291
+ if (!mateResizeHandle || !mateSidebarCol) return;
292
+ var rect = mateSidebarCol.getBoundingClientRect();
293
+ var parentRect = mateSidebarCol.parentElement.getBoundingClientRect();
294
+ mateResizeHandle.style.left = (rect.right - parentRect.left) + "px";
295
+ }
296
+
297
+ if (mateResizeHandle && mateSidebarCol) {
298
+ var mateDragging = false;
299
+
300
+ function onMateResizeMove(e) {
301
+ if (!mateDragging) return;
302
+ e.preventDefault();
303
+ var clientX = e.touches ? e.touches[0].clientX : e.clientX;
304
+ var iconStrip = document.getElementById("icon-strip");
305
+ var stripWidth = iconStrip ? iconStrip.offsetWidth : 72;
306
+ var newWidth = clientX - stripWidth;
307
+ if (newWidth < 192) newWidth = 192;
308
+ if (newWidth > 320) newWidth = 320;
309
+ mateSidebarCol.style.width = newWidth + "px";
310
+ syncMateResizeHandle();
311
+ syncUserIslandWidth();
312
+ }
313
+
314
+ function onMateResizeEnd() {
315
+ if (!mateDragging) return;
316
+ mateDragging = false;
317
+ mateResizeHandle.classList.remove("dragging");
318
+ document.body.style.cursor = "";
319
+ document.body.style.userSelect = "";
320
+ document.removeEventListener("mousemove", onMateResizeMove);
321
+ document.removeEventListener("mouseup", onMateResizeEnd);
322
+ document.removeEventListener("touchmove", onMateResizeMove);
323
+ document.removeEventListener("touchend", onMateResizeEnd);
324
+ var finalWidth = mateSidebarCol.style.width;
325
+ try { localStorage.setItem("sidebar-width", finalWidth); } catch (e) {}
326
+ // Pre-apply to project sidebar so it's ready when dm-mode is removed
327
+ if (sidebarColumn) sidebarColumn.style.width = finalWidth;
328
+ }
329
+
330
+ function onMateResizeStart(e) {
331
+ e.preventDefault();
332
+ mateDragging = true;
333
+ mateResizeHandle.classList.add("dragging");
334
+ document.body.style.cursor = "col-resize";
335
+ document.body.style.userSelect = "none";
336
+ document.addEventListener("mousemove", onMateResizeMove);
337
+ document.addEventListener("mouseup", onMateResizeEnd);
338
+ document.addEventListener("touchmove", onMateResizeMove, { passive: false });
339
+ document.addEventListener("touchend", onMateResizeEnd);
340
+ }
341
+
342
+ mateResizeHandle.addEventListener("mousedown", onMateResizeStart);
343
+ mateResizeHandle.addEventListener("touchstart", onMateResizeStart, { passive: false });
344
+ }
345
+
346
+ // Show/hide mate resize handle when DM mode changes
347
+ var _mateResizeObserver = new MutationObserver(function () {
348
+ if (!mateResizeHandle || !mateSidebarCol) return;
349
+ var isVisible = !mateSidebarCol.classList.contains("hidden");
350
+ if (isVisible) {
351
+ mateResizeHandle.classList.remove("hidden");
352
+ syncMateResizeHandle();
353
+ } else {
354
+ mateResizeHandle.classList.add("hidden");
355
+ // Mate sidebar just hid = returning to project. Sync project handle.
356
+ requestAnimationFrame(function () {
357
+ syncResizeHandle();
358
+ syncUserIslandWidth();
359
+ });
360
+ }
361
+ });
362
+ if (mateSidebarCol) {
363
+ _mateResizeObserver.observe(mateSidebarCol, { attributes: true, attributeFilter: ["class"] });
364
+ }
365
+
366
+ // Expose for external callers (e.g. after DM exit)
367
+ ctx.syncResizeHandles = function () {
368
+ // Restore project sidebar width from localStorage (may have changed during DM)
369
+ try {
370
+ var sw = localStorage.getItem("sidebar-width");
371
+ if (sw && sidebarColumn) {
372
+ var px = parseInt(sw, 10);
373
+ if (px >= 192 && px <= 320) {
374
+ sidebarColumn.style.width = px + "px";
375
+ sidebarColumn.offsetWidth; // force reflow
376
+ }
377
+ }
378
+ } catch (e) {}
379
+ // Defer handle sync to next frame so layout settles after display changes
380
+ requestAnimationFrame(function () {
381
+ syncResizeHandle();
382
+ syncMateResizeHandle();
383
+ syncUserIslandWidth();
384
+ });
385
+ };
386
+
245
387
  // Initial sync even if no resize handle
246
388
  syncUserIslandWidth();
247
389
 
@@ -52,27 +52,6 @@ function reclampAllNotes() {
52
52
  export function initStickyNotes(_ctx) {
53
53
  ctx = _ctx;
54
54
 
55
- var toggleBtn = document.getElementById("sticky-notes-toggle-btn");
56
- if (toggleBtn) {
57
- toggleBtn.addEventListener("click", function () {
58
- dismissOnboarding();
59
- if (!notesVisible && notes.size > 0) {
60
- // Hidden with existing notes → just show them
61
- showNotes();
62
- } else {
63
- // Visible or no notes → create a new one
64
- showNotes();
65
- createNote();
66
- }
67
- });
68
-
69
- // Long-press or right-click to toggle hide
70
- toggleBtn.addEventListener("contextmenu", function (e) {
71
- e.preventDefault();
72
- if (notesVisible) hideNotes();
73
- });
74
- }
75
-
76
55
  // Close format toolbar on outside click
77
56
  document.addEventListener("mousedown", function (e) {
78
57
  if (formatToolbarEl && !e.target.closest(".sn-format-toolbar") && !e.target.closest(".sticky-note-text") && !e.target.closest(".sticky-note-rendered")) {
@@ -91,67 +70,6 @@ export function initStickyNotes(_ctx) {
91
70
  }, 100);
92
71
  });
93
72
 
94
- // First-time onboarding beacon
95
- maybeShowOnboarding();
96
- }
97
-
98
- // --- Onboarding beacon (one-time discovery hint) ---
99
-
100
- var onboardingEl = null;
101
- var ONBOARDING_KEY = "clay-sticky-notes-discovered";
102
-
103
- function maybeShowOnboarding() {
104
- try {
105
- if (localStorage.getItem(ONBOARDING_KEY)) return;
106
- } catch (e) { return; }
107
-
108
- var toggleBtn = document.getElementById("sticky-notes-toggle-btn");
109
- if (!toggleBtn) return;
110
-
111
- // Show beacon after a short delay so UI settles
112
- setTimeout(function () {
113
- // Don't show if user already has notes (they know the feature)
114
- if (notes.size > 0) {
115
- dismissOnboarding();
116
- return;
117
- }
118
-
119
- toggleBtn.classList.add("sn-onboarding-pulse");
120
-
121
- var tooltip = document.createElement("div");
122
- tooltip.className = "sn-onboarding-tooltip";
123
- tooltip.innerHTML = '<span>Click here to create a sticky note</span>';
124
- document.body.appendChild(tooltip);
125
- onboardingEl = tooltip;
126
-
127
- // Position tooltip below the button
128
- var rect = toggleBtn.getBoundingClientRect();
129
- tooltip.style.left = (rect.left + rect.width / 2) + "px";
130
- tooltip.style.top = (rect.bottom + 8) + "px";
131
-
132
- // Auto-dismiss after 8 seconds
133
- setTimeout(function () {
134
- dismissOnboarding();
135
- }, 8000);
136
-
137
- // Dismiss on click anywhere
138
- document.addEventListener("click", function onClickDismiss() {
139
- dismissOnboarding();
140
- document.removeEventListener("click", onClickDismiss);
141
- }, { once: true });
142
- }, 2000);
143
- }
144
-
145
- function dismissOnboarding() {
146
- var toggleBtn = document.getElementById("sticky-notes-toggle-btn");
147
- if (toggleBtn) toggleBtn.classList.remove("sn-onboarding-pulse");
148
- if (onboardingEl) {
149
- onboardingEl.classList.add("sn-onboarding-fade-out");
150
- var el = onboardingEl;
151
- setTimeout(function () { el.remove(); }, 300);
152
- onboardingEl = null;
153
- }
154
- try { localStorage.setItem(ONBOARDING_KEY, "1"); } catch (e) {}
155
73
  }
156
74
 
157
75
  // --- Visibility ---
@@ -159,21 +77,17 @@ function dismissOnboarding() {
159
77
  export function showNotes() {
160
78
  notesVisible = true;
161
79
  var container = document.getElementById("sticky-notes-container");
162
- var toggleBtn = document.getElementById("sticky-notes-toggle-btn");
163
80
  if (container) container.classList.remove("hidden");
164
- if (toggleBtn) toggleBtn.classList.add("active");
165
81
  }
166
82
 
167
83
  export function hideNotes() {
168
84
  notesVisible = false;
169
85
  var container = document.getElementById("sticky-notes-container");
170
- var toggleBtn = document.getElementById("sticky-notes-toggle-btn");
171
86
  if (container) container.classList.add("hidden");
172
- if (toggleBtn) toggleBtn.classList.remove("active");
173
87
  closeColorPicker();
174
88
  }
175
89
 
176
- function createNote() {
90
+ export function createNote() {
177
91
  var container = document.getElementById("sticky-notes-container");
178
92
  if (!container) return;
179
93
  // Scatter position so notes don't stack exactly
@@ -978,8 +892,6 @@ export function handleNotesList(msg) {
978
892
  if (list.length > 0 && !notesVisible) {
979
893
  notesVisible = true;
980
894
  container.classList.remove("hidden");
981
- var toggleBtn = document.getElementById("sticky-notes-toggle-btn");
982
- if (toggleBtn) toggleBtn.classList.add("active");
983
895
  }
984
896
  }
985
897
 
@@ -1003,8 +915,6 @@ export function handleNoteCreated(msg) {
1003
915
  if (!notesVisible) {
1004
916
  notesVisible = true;
1005
917
  container.classList.remove("hidden");
1006
- var toggleBtn = document.getElementById("sticky-notes-toggle-btn");
1007
- if (toggleBtn) toggleBtn.classList.add("active");
1008
918
  }
1009
919
  }
1010
920
 
@@ -152,18 +152,6 @@ export function initTerminal(_ctx) {
152
152
  fitTerminal();
153
153
  });
154
154
 
155
- // Header toggle button
156
- var toggleBtn = document.getElementById("terminal-toggle-btn");
157
- if (toggleBtn) {
158
- toggleBtn.addEventListener("click", function () {
159
- if (isOpen && !ctx.terminalContainerEl.classList.contains("hidden")) {
160
- closeTerminal();
161
- } else {
162
- openTerminal();
163
- }
164
- });
165
- }
166
-
167
155
  // Sidebar terminal button
168
156
  var sidebarTermBtn = document.getElementById("terminal-sidebar-btn");
169
157
  if (sidebarTermBtn) {
@@ -77,6 +77,8 @@ var defaultExactVars = {
77
77
  "--sidebar-bg": "#242631",
78
78
  "--sidebar-hover": "#2f2f3f",
79
79
  "--sidebar-active": "#3d3e51",
80
+ "--filebrowser-bg": "#2e3042",
81
+ "--filebrowser-border": "#383a4d",
80
82
  "--accent-8": "rgba(255, 184, 108, 0.08)",
81
83
  "--accent-12": "rgba(255, 184, 108, 0.12)",
82
84
  "--accent-15": "rgba(255, 184, 108, 0.15)",
@@ -164,6 +166,8 @@ function computeVars(theme) {
164
166
  "--sidebar-bg": isLight ? darken(b.base00, 0.02) : darken(b.base00, 0.10),
165
167
  "--sidebar-hover": isLight ? darken(b.base00, 0.06) : mixColors(b.base00, b.base01, 0.5),
166
168
  "--sidebar-active": isLight ? darken(b.base01, 0.05) : mixColors(b.base01, b.base02, 0.5),
169
+ "--filebrowser-bg": isLight ? lighten(b.base00, 0.03) : lighten(b.base00, 0.04),
170
+ "--filebrowser-border": isLight ? darken(b.base00, 0.08) : mixColors(b.base02, b.base00, 0.5),
167
171
  "--accent-8": hexToRgba(b.base09, 0.08),
168
172
  "--accent-12": hexToRgba(b.base09, 0.12),
169
173
  "--accent-15": hexToRgba(b.base09, 0.15),
@@ -2068,6 +2068,29 @@ export function initSubagentStop(parentToolId, taskId) {
2068
2068
  header.appendChild(btn);
2069
2069
  }
2070
2070
 
2071
+ export function updateSubagentTaskStatus(parentToolId, patch) {
2072
+ var tool = tools[parentToolId];
2073
+ if (!tool || !tool.el) return;
2074
+ if (patch.description) {
2075
+ var summaryEl = tool.el.querySelector(".subagent-summary");
2076
+ if (!summaryEl) {
2077
+ var progressEl = tool.el.querySelector(".subagent-progress");
2078
+ if (progressEl) {
2079
+ summaryEl = document.createElement("div");
2080
+ summaryEl.className = "subagent-summary";
2081
+ progressEl.parentNode.insertBefore(summaryEl, progressEl.nextSibling);
2082
+ }
2083
+ }
2084
+ if (summaryEl) summaryEl.textContent = patch.description;
2085
+ }
2086
+ if (patch.status === "failed" || patch.status === "killed") {
2087
+ var subtitleText = tool.el.querySelector(".tool-subtitle-text");
2088
+ if (subtitleText) subtitleText.textContent = patch.status === "failed" ? "Agent failed" : "Agent killed";
2089
+ var stopBtn = tool.el.querySelector(".subagent-stop-btn");
2090
+ if (stopBtn) stopBtn.remove();
2091
+ }
2092
+ }
2093
+
2071
2094
  export function markSubagentDone(parentToolId, status, summary, usage) {
2072
2095
  var tool = tools[parentToolId];
2073
2096
  if (!tool || !tool.el) return;
@@ -4,6 +4,7 @@
4
4
  import { refreshIcons } from './icons.js';
5
5
  import { showToast } from './utils.js';
6
6
  import { toggleDarkMode, getCurrentTheme, getChatLayout, setChatLayout } from './theme.js';
7
+ import { showEmailSetupModal, getEmailAccountListCache } from './context-sources.js';
7
8
 
8
9
  var ctx = null;
9
10
  var settingsEl = null;
@@ -142,6 +143,14 @@ export function initUserSettings(appCtx) {
142
143
  });
143
144
  });
144
145
  }
146
+
147
+ // Email: Add Account button
148
+ var emailAddBtn = document.getElementById('us-email-add');
149
+ if (emailAddBtn) {
150
+ emailAddBtn.addEventListener('click', function () {
151
+ showEmailSetupModal();
152
+ });
153
+ }
145
154
  }
146
155
 
147
156
  function openUserSettings() {
@@ -170,6 +179,7 @@ function switchSection(sectionName) {
170
179
  }
171
180
  var navDropdown = document.getElementById('user-settings-nav-dropdown');
172
181
  if (navDropdown) navDropdown.value = sectionName;
182
+ if (sectionName === 'us-email') renderEmailSettings();
173
183
  }
174
184
 
175
185
  function stopProp(e) {
@@ -265,3 +275,67 @@ function savePin(pin) {
265
275
  }
266
276
  });
267
277
  }
278
+
279
+ // --- Email settings ---
280
+
281
+ function renderEmailSettings() {
282
+ var listEl = document.getElementById('us-email-list');
283
+ if (!listEl) return;
284
+ listEl.innerHTML = '';
285
+
286
+ var accounts = getEmailAccountListCache();
287
+ if (!accounts || accounts.length === 0) {
288
+ var empty = document.createElement('div');
289
+ empty.className = 'settings-field';
290
+ empty.innerHTML = '<div class="us-email-empty">No email accounts connected yet.</div>';
291
+ listEl.appendChild(empty);
292
+ return;
293
+ }
294
+
295
+ for (var i = 0; i < accounts.length; i++) {
296
+ var acc = accounts[i];
297
+ var row = document.createElement('div');
298
+ row.className = 'us-email-row';
299
+ row.innerHTML =
300
+ '<div class="us-email-icon">' + providerIcon(acc.provider) + '</div>' +
301
+ '<div class="us-email-info">' +
302
+ '<div class="us-email-addr">' + escHtml(acc.email) + '</div>' +
303
+ '<div class="us-email-provider">' + escHtml(acc.label || acc.provider || 'Custom') + '</div>' +
304
+ '</div>' +
305
+ '<button class="us-email-remove-btn" data-account-id="' + escHtml(acc.id) + '">Remove</button>';
306
+
307
+ var removeBtn = row.querySelector('button');
308
+ removeBtn.addEventListener('click', function () {
309
+ var accountId = this.getAttribute('data-account-id');
310
+ if (ctx && ctx.ws && ctx.connected) {
311
+ ctx.ws.send(JSON.stringify({ type: 'email_account_remove', accountId: accountId }));
312
+ }
313
+ });
314
+
315
+ listEl.appendChild(row);
316
+ }
317
+ }
318
+
319
+ export function refreshEmailSettings() {
320
+ if (isUserSettingsOpen()) renderEmailSettings();
321
+ }
322
+
323
+ var PROVIDER_ICON_PATHS = {
324
+ gmail: '/icons/email/gmail.svg',
325
+ outlook: '/icons/email/outlook.svg',
326
+ yahoo: '/icons/email/yahoo.svg',
327
+ };
328
+
329
+ function providerIcon(provider) {
330
+ var src = PROVIDER_ICON_PATHS[provider];
331
+ if (src) {
332
+ return '<img src="' + src + '" class="us-email-provider-icon" alt="' + provider + '">';
333
+ }
334
+ return '<i data-lucide="mail" class="us-email-provider-icon-fallback"></i>';
335
+ }
336
+
337
+ function escHtml(str) {
338
+ var d = document.createElement('div');
339
+ d.textContent = str;
340
+ return d.innerHTML;
341
+ }
package/lib/sdk-bridge.js CHANGED
@@ -1068,6 +1068,22 @@ function createSDKBridge(opts) {
1068
1068
  return { behavior: "allow", updatedInput: input };
1069
1069
  }
1070
1070
 
1071
+ // Auto-approve read-only email MCP tools.
1072
+ // These only read data from accounts the user explicitly checked.
1073
+ // Write operations (send, reply, mark_read) still require permission.
1074
+ var safeEmailTools = {
1075
+ clay_read_email: true,
1076
+ clay_read_email_body: true,
1077
+ clay_search_email: true,
1078
+ clay_list_labels: true,
1079
+ };
1080
+ if (toolName.indexOf("mcp__clay-email__") === 0) {
1081
+ var emailToolName = toolName.substring(toolName.lastIndexOf("__") + 2);
1082
+ if (safeEmailTools[emailToolName]) {
1083
+ return { behavior: "allow", updatedInput: input };
1084
+ }
1085
+ }
1086
+
1071
1087
  // Auto-approve remote MCP tools that the user explicitly enabled in project settings.
1072
1088
  // These are user-owned local MCP servers, so no additional permission prompt needed.
1073
1089
  if (toolName.indexOf("mcp__") === 0 && getRemoteMcpServers) {
@@ -458,6 +458,25 @@ function attachMessageProcessor(ctx) {
458
458
  });
459
459
  }
460
460
 
461
+ } else if (parsed.type === "system" && parsed.subtype === "task_updated") {
462
+ // Live task state patches (status, description, error, backgrounded)
463
+ var taskId = parsed.task_id;
464
+ var patch = parsed.patch || {};
465
+ var parentId = null;
466
+ if (session.taskIdMap) {
467
+ for (var k in session.taskIdMap) {
468
+ if (session.taskIdMap[k] === taskId) { parentId = k; break; }
469
+ }
470
+ }
471
+ if (parentId) {
472
+ sendAndRecord(session, {
473
+ type: "task_updated",
474
+ parentToolId: parentId,
475
+ taskId: taskId,
476
+ patch: patch,
477
+ });
478
+ }
479
+
461
480
  } else if (parsed.type === "tool_progress") {
462
481
  // Sub-agent tool_progress: forward as activity update
463
482
  var parentId = parsed.parent_tool_use_id;
@@ -552,6 +571,20 @@ function attachMessageProcessor(ctx) {
552
571
  suggestion: parsed.suggestion || "",
553
572
  });
554
573
 
574
+ } else if (parsed.type === "system" && parsed.subtype === "notification") {
575
+ var notifText = parsed.text || "";
576
+ var notifPriority = parsed.priority || "low";
577
+ if (notifText) {
578
+ sendAndRecord(session, {
579
+ type: "sdk_notification",
580
+ key: parsed.key || "",
581
+ text: notifText,
582
+ priority: notifPriority,
583
+ color: parsed.color || null,
584
+ timeoutMs: parsed.timeout_ms || null,
585
+ });
586
+ }
587
+
555
588
  } else if (parsed.type === "system" && parsed.subtype === "api_retry") {
556
589
  // Transient retry notification — show in UI but don't persist in history
557
590
  var retryText = parsed.message || parsed.error || "Retrying API request...";