clay-server 2.32.0-beta.7 → 2.32.0-beta.9

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.
@@ -0,0 +1,18 @@
1
+ var CODEX_DEFAULTS = {
2
+ approval: "on-failure",
3
+ sandbox: "danger-full-access",
4
+ webSearch: "live",
5
+ };
6
+
7
+ function getCodexConfig(sm) {
8
+ return {
9
+ approval: (sm && sm.codexApproval) || CODEX_DEFAULTS.approval,
10
+ sandbox: (sm && sm.codexSandbox) || CODEX_DEFAULTS.sandbox,
11
+ webSearch: (sm && sm.codexWebSearch) || CODEX_DEFAULTS.webSearch,
12
+ };
13
+ }
14
+
15
+ module.exports = {
16
+ CODEX_DEFAULTS: CODEX_DEFAULTS,
17
+ getCodexConfig: getCodexConfig,
18
+ };
@@ -3,6 +3,7 @@ var path = require("path");
3
3
  var usersModule = require("./users");
4
4
  var userPresence = require("./user-presence");
5
5
  var emailAccounts = require("./email-accounts");
6
+ var { getCodexConfig } = require("./codex-defaults");
6
7
 
7
8
  /**
8
9
  * Attach connection/disconnection handlers to a project context.
@@ -84,10 +85,9 @@ function attachConnection(ctx) {
84
85
  var project = getProject();
85
86
  var ownerLocked = !!(osUsers && osUsers.length > 0 && /^\/home\/[^/]+\//.test(cwd));
86
87
  sendTo(ws, { type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, dangerouslySkipPermissions: dangerouslySkipPermissions, osUsers: osUsers, lanHost: lanHost, projectCount: _filteredProjects.length, projects: _filteredProjects, projectOwnerId: projectOwnerId, ownerLocked: ownerLocked });
87
- var latestVersion = getLatestVersion();
88
- if (latestVersion && ws._clayUser && ws._clayUser.role === "admin") {
89
- sendTo(ws, { type: "update_available", version: latestVersion });
90
- }
88
+ // Update notifications are pushed on a scheduled interval (see
89
+ // scheduleUpdateBroadcast). We no longer push on connect to avoid
90
+ // re-triggering the banner on every page refresh.
91
91
  if (sm.slashCommands) {
92
92
  sendTo(ws, { type: "slash_commands", commands: sm.slashCommands });
93
93
  }
@@ -96,6 +96,7 @@ function attachConnection(ctx) {
96
96
  sendTo(ws, { type: "model_info", model: sm.currentModel, models: sm.availableModels || [], vendor: sm.defaultVendor || "claude", availableVendors: sm.availableVendors || [], installedVendors: sm.installedVendors || [] });
97
97
  }
98
98
  sendTo(ws, { type: "config_state", model: sm.currentModel || "", mode: sm.currentPermissionMode || "default", effort: sm.currentEffort || "medium", betas: sm.currentBetas || [], thinking: sm.currentThinking || "adaptive", thinkingBudget: sm.currentThinkingBudget || 10000 });
99
+ sendTo(ws, Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
99
100
  sendTo(ws, { type: "term_list", terminals: tm.list() });
100
101
  // Context sources sent after session is resolved (per-session storage)
101
102
  // Send email accounts list for context sources picker
@@ -4,6 +4,7 @@ var crypto = require("crypto");
4
4
 
5
5
  function attachMateInteraction(ctx) {
6
6
  var cwd = ctx.cwd;
7
+ var slug = ctx.slug || "";
7
8
  var sm = ctx.sm;
8
9
  var sdk = ctx.sdk;
9
10
  var send = ctx.send;
@@ -21,6 +22,7 @@ function attachMateInteraction(ctx) {
21
22
  var loadMateDigests = ctx.loadMateDigests;
22
23
  var updateMemorySummary = ctx.updateMemorySummary;
23
24
  var initMemorySummary = ctx.initMemorySummary;
25
+ var getNotificationsModule = ctx.getNotificationsModule || function () { return null; };
24
26
  // checkForDmDebateBrief is accessed via ctx at call time because
25
27
  // it comes from the debate module initialized after this one.
26
28
 
@@ -104,6 +106,30 @@ function attachMateInteraction(ctx) {
104
106
  return lines.join("\n") + "\n\n";
105
107
  }
106
108
 
109
+ function getHostMateId() {
110
+ if (typeof slug === "string" && slug.indexOf("mate-") === 0) {
111
+ return slug.substring(5) || null;
112
+ }
113
+ return null;
114
+ }
115
+
116
+ function notifyMentionResponse(session, mentionMateId, mentionMateName, fullText) {
117
+ var notificationsModule = getNotificationsModule();
118
+ var hostMateId = getHostMateId();
119
+ var preview = (fullText || "").replace(/\s+/g, " ").trim();
120
+ if (preview.length > 140) preview = preview.substring(0, 140) + "...";
121
+ if (!notificationsModule || !hostMateId) return;
122
+ notificationsModule.notify("mention_response", {
123
+ title: (mentionMateName || "Mate") + " responded",
124
+ preview: preview,
125
+ slug: slug,
126
+ sessionId: session.localId,
127
+ mateId: hostMateId,
128
+ avatarMateId: mentionMateId || null,
129
+ ownerId: session.ownerId || null,
130
+ });
131
+ }
132
+
107
133
  // --- Shared digest worker: one reusable Haiku session for gate+digest ---
108
134
  // Combines gate check and digest generation into a single prompt,
109
135
  // processes jobs sequentially from a queue, reuses the session across calls.
@@ -557,6 +583,10 @@ function attachMateInteraction(ctx) {
557
583
  sendToSession(session.localId, { type: "mention_done", mateId: msg.mateId });
558
584
  send({ type: "mention_processing", mateId: msg.mateId, active: false });
559
585
 
586
+ if (isMate) {
587
+ notifyMentionResponse(session, msg.mateId, mateName, fullText);
588
+ }
589
+
560
590
  // Check if the mate wrote a debate brief during this turn
561
591
  ctx.checkForDmDebateBrief(session, msg.mateId, mateCtx);
562
592
 
@@ -60,6 +60,15 @@ var formatters = {
60
60
  body: data.preview || "Sent you a message",
61
61
  };
62
62
  },
63
+
64
+ mention_response: function (data) {
65
+ return {
66
+ type: "mention_response",
67
+ title: data.title || "Mention response ready",
68
+ body: data.preview || "",
69
+ meta: { avatarMateId: data.avatarMateId || null },
70
+ };
71
+ },
63
72
  };
64
73
 
65
74
  // ========================================================
@@ -1,6 +1,7 @@
1
1
  var fs = require("fs");
2
2
  var path = require("path");
3
3
  var { execFileSync } = require("child_process");
4
+ var { CODEX_DEFAULTS, getCodexConfig } = require("./codex-defaults");
4
5
 
5
6
  /**
6
7
  * Attach session management, config, project management, and mid-section
@@ -520,18 +521,18 @@ function attachSessions(ctx) {
520
521
 
521
522
  // Codex-specific settings (stored on sessionManager, passed to adapter via adapterOptions)
522
523
  if (msg.type === "set_codex_approval") {
523
- sm.codexApproval = msg.approval || "on-failure";
524
- send({ type: "codex_config", approval: sm.codexApproval, sandbox: sm.codexSandbox || "workspace-write", webSearch: sm.codexWebSearch || "disabled" });
524
+ sm.codexApproval = msg.approval || CODEX_DEFAULTS.approval;
525
+ send(Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
525
526
  return true;
526
527
  }
527
528
  if (msg.type === "set_codex_sandbox") {
528
- sm.codexSandbox = msg.sandbox || "workspace-write";
529
- send({ type: "codex_config", approval: sm.codexApproval || "on-failure", sandbox: sm.codexSandbox, webSearch: sm.codexWebSearch || "disabled" });
529
+ sm.codexSandbox = msg.sandbox || CODEX_DEFAULTS.sandbox;
530
+ send(Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
530
531
  return true;
531
532
  }
532
533
  if (msg.type === "set_codex_websearch") {
533
- sm.codexWebSearch = msg.webSearch || "disabled";
534
- send({ type: "codex_config", approval: sm.codexApproval || "on-failure", sandbox: sm.codexSandbox || "workspace-write", webSearch: sm.codexWebSearch });
534
+ sm.codexWebSearch = msg.webSearch || CODEX_DEFAULTS.webSearch;
535
+ send(Object.assign({ type: "codex_config" }, getCodexConfig(sm)));
535
536
  return true;
536
537
  }
537
538
 
package/lib/project.js CHANGED
@@ -615,15 +615,33 @@ function createProjectContext(opts) {
615
615
  var tm = createTerminalManager({ cwd: cwd, send: send, sendTo: sendTo });
616
616
  var nm = createNotesManager({ cwd: cwd, send: send, sendTo: sendTo });
617
617
 
618
- // Check for updates in background (admin only)
619
- fetchVersion(updateChannel).then(function (v) {
620
- if (v && isNewer(v, currentVersion)) {
621
- latestVersion = v;
622
- sendToAdmins({ type: "update_available", version: v });
623
- }
624
- }).catch(function (e) {
625
- console.error("[project] Background version check failed:", e.message || e);
626
- });
618
+ // Check for updates in background (admin only). The result is stored in
619
+ // latestVersion; broadcast is handled by the hourly scheduler below, so
620
+ // page refreshes don't re-trigger the banner.
621
+ function runVersionCheck(broadcast) {
622
+ fetchVersion(updateChannel).then(function (v) {
623
+ if (v && isNewer(v, currentVersion)) {
624
+ latestVersion = v;
625
+ if (broadcast) sendToAdmins({ type: "update_available", version: v });
626
+ }
627
+ }).catch(function (e) {
628
+ console.error("[project] Background version check failed:", e.message || e);
629
+ });
630
+ }
631
+ runVersionCheck(false);
632
+
633
+ // Push update_available on every hour boundary. Clients can dismiss the
634
+ // banner; the next hourly push acts as a fresh ping. This avoids needing
635
+ // any dismissed-state persistence.
636
+ function scheduleNextHourlyBroadcast() {
637
+ var now = Date.now();
638
+ var msUntilNextHour = 60 * 60 * 1000 - (now % (60 * 60 * 1000));
639
+ setTimeout(function tick() {
640
+ runVersionCheck(true);
641
+ setTimeout(tick, 60 * 60 * 1000);
642
+ }, msUntilNextHour);
643
+ }
644
+ scheduleNextHourlyBroadcast();
627
645
 
628
646
  // --- WS connection handler (delegated to project-connection.js) ---
629
647
  function handleConnection(ws, wsUser) {
@@ -850,6 +868,7 @@ function createProjectContext(opts) {
850
868
  // so we use a lazy getter that resolves at call time.
851
869
  var _mateInteraction = attachMateInteraction({
852
870
  cwd: cwd,
871
+ slug: slug,
853
872
  sm: sm,
854
873
  sdk: sdk,
855
874
  send: send,
@@ -867,6 +886,7 @@ function createProjectContext(opts) {
867
886
  loadMateDigests: loadMateDigests,
868
887
  updateMemorySummary: updateMemorySummary,
869
888
  initMemorySummary: initMemorySummary,
889
+ getNotificationsModule: function () { return _notifications; },
870
890
  get checkForDmDebateBrief() { return checkForDmDebateBrief; },
871
891
  });
872
892
  var handleMention = _mateInteraction.handleMention;
package/lib/public/app.js CHANGED
@@ -309,6 +309,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
309
309
  currentBetas: [],
310
310
  currentThinking: "adaptive",
311
311
  currentThinkingBudget: 10000,
312
+ codexApproval: null,
313
+ codexSandbox: null,
314
+ codexWebSearch: null,
312
315
  ctxPopoverVisible: false,
313
316
  headerContextEl: null,
314
317
  // loop
@@ -782,6 +785,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
782
785
  var _perms = store.get('permissions');
783
786
  if (_perms) {
784
787
  if (!_perms.terminal) {
788
+ var termBtn = document.getElementById("terminal-toggle-btn");
789
+ if (termBtn) termBtn.style.display = "none";
785
790
  var termSideBtn = document.getElementById("terminal-sidebar-btn");
786
791
  if (termSideBtn) termSideBtn.style.display = "none";
787
792
  }
@@ -746,12 +746,14 @@
746
746
  display: flex;
747
747
  align-items: center;
748
748
  justify-content: space-between;
749
+ gap: 4px;
749
750
  }
750
751
 
751
752
  #input-bottom-right {
752
753
  display: flex;
753
754
  align-items: center;
754
755
  gap: 4px;
756
+ flex-shrink: 0;
755
757
  }
756
758
 
757
759
  /* --- Vendor toggle (split toggle) --- */
@@ -812,22 +814,26 @@
812
814
  .vendor-toggle-label {
813
815
  pointer-events: none;
814
816
  }
815
- @media (max-width: 600px) {
817
+ @media (max-width: 900px) {
816
818
  .vendor-toggle-label { display: none; }
819
+ .vendor-toggle-btn { padding: 0 8px; }
817
820
  }
818
821
 
819
822
  /* --- Attach buttons & menu --- */
820
823
  #attach-wrap {
821
824
  position: relative;
822
- flex-shrink: 0;
825
+ min-width: 0;
823
826
  display: flex;
824
827
  align-items: center;
825
828
  gap: 2px;
829
+ flex-wrap: nowrap;
830
+ overflow: hidden;
826
831
  }
827
832
 
828
833
  #attach-file-btn,
829
834
  #attach-image-btn,
830
- #schedule-btn {
835
+ #schedule-btn,
836
+ #input-more-btn {
831
837
  width: 36px;
832
838
  height: 36px;
833
839
  border-radius: 50%;
@@ -844,11 +850,13 @@
844
850
 
845
851
  #attach-file-btn .lucide,
846
852
  #attach-image-btn .lucide,
847
- #schedule-btn .lucide { width: 20px; height: 20px; flex-shrink: 0; }
853
+ #schedule-btn .lucide,
854
+ #input-more-btn .lucide { width: 20px; height: 20px; flex-shrink: 0; }
848
855
 
849
856
  #attach-file-btn:hover,
850
857
  #attach-image-btn:hover,
851
- #schedule-btn:hover { background: rgba(var(--overlay-rgb), 0.06); color: var(--text); }
858
+ #schedule-btn:hover,
859
+ #input-more-btn:hover { background: rgba(var(--overlay-rgb), 0.06); color: var(--text); }
852
860
 
853
861
  #ask-mate-btn {
854
862
  width: 36px;
@@ -1137,6 +1145,146 @@
1137
1145
  body.keyboard-open #input-area {
1138
1146
  padding-bottom: 8px;
1139
1147
  }
1148
+
1149
+ /* Mobile visibility utilities for input toolbar */
1150
+ .desktop-only { display: none !important; }
1151
+ .mobile-only { display: inline-flex !important; }
1152
+ }
1153
+
1154
+ /* Desktop: hide mobile-only, show desktop-only */
1155
+ @media (min-width: 769px) {
1156
+ .mobile-only { display: none !important; }
1157
+ }
1158
+
1159
+ /* ==========================================================================
1160
+ Input More Bottom Sheet (mobile)
1161
+ ========================================================================== */
1162
+
1163
+ #input-more-sheet {
1164
+ position: fixed;
1165
+ inset: 0;
1166
+ z-index: 10000;
1167
+ pointer-events: none;
1168
+ }
1169
+
1170
+ #input-more-sheet.hidden {
1171
+ display: none;
1172
+ }
1173
+
1174
+ .input-more-backdrop {
1175
+ position: absolute;
1176
+ inset: 0;
1177
+ background: rgba(0, 0, 0, 0.4);
1178
+ opacity: 0;
1179
+ transition: opacity 0.2s ease;
1180
+ pointer-events: auto;
1181
+ }
1182
+
1183
+ #input-more-sheet.open .input-more-backdrop {
1184
+ opacity: 1;
1185
+ }
1186
+
1187
+ .input-more-content {
1188
+ position: absolute;
1189
+ left: 0;
1190
+ right: 0;
1191
+ bottom: 0;
1192
+ background: var(--bg);
1193
+ border-top-left-radius: 16px;
1194
+ border-top-right-radius: 16px;
1195
+ padding: 8px 0 calc(var(--safe-bottom) + 16px);
1196
+ transform: translateY(100%);
1197
+ transition: transform 0.25s ease;
1198
+ pointer-events: auto;
1199
+ box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.15);
1200
+ }
1201
+
1202
+ #input-more-sheet.open .input-more-content {
1203
+ transform: translateY(0);
1204
+ }
1205
+
1206
+ .input-more-handle {
1207
+ width: 40px;
1208
+ height: 4px;
1209
+ border-radius: 2px;
1210
+ background: var(--border-subtle);
1211
+ margin: 8px auto 12px;
1212
+ }
1213
+
1214
+ .input-more-actions {
1215
+ display: grid;
1216
+ grid-template-columns: 1fr 1fr;
1217
+ gap: 8px;
1218
+ padding: 4px 16px 12px;
1219
+ }
1220
+
1221
+ .input-more-action {
1222
+ display: flex;
1223
+ flex-direction: column;
1224
+ align-items: center;
1225
+ justify-content: center;
1226
+ gap: 6px;
1227
+ padding: 16px 8px;
1228
+ background: var(--hover);
1229
+ border: 1px solid var(--border-subtle);
1230
+ border-radius: 10px;
1231
+ font-size: 13px;
1232
+ color: var(--text);
1233
+ cursor: pointer;
1234
+ }
1235
+
1236
+ .input-more-action:active {
1237
+ opacity: 0.7;
1238
+ }
1239
+
1240
+ .input-more-action .lucide {
1241
+ width: 22px;
1242
+ height: 22px;
1243
+ color: var(--text-secondary);
1244
+ }
1245
+
1246
+ .input-more-divider {
1247
+ height: 1px;
1248
+ background: var(--border-subtle);
1249
+ margin: 0 16px;
1250
+ }
1251
+
1252
+ .input-more-section-label {
1253
+ display: flex;
1254
+ align-items: center;
1255
+ gap: 8px;
1256
+ padding: 14px 20px 6px;
1257
+ font-size: 13px;
1258
+ font-weight: 500;
1259
+ color: var(--text-secondary);
1260
+ text-transform: uppercase;
1261
+ letter-spacing: 0.04em;
1262
+ }
1263
+
1264
+ .input-more-section-label .lucide {
1265
+ width: 16px;
1266
+ height: 16px;
1267
+ }
1268
+
1269
+ .input-more-context-body {
1270
+ max-height: 50vh;
1271
+ overflow-y: auto;
1272
+ padding: 0 8px 8px;
1273
+ }
1274
+
1275
+ .input-more-context-body .context-picker-item {
1276
+ padding: 12px 16px;
1277
+ font-size: 15px;
1278
+ border-radius: 8px;
1279
+ }
1280
+
1281
+ .input-more-context-body .context-picker-item .lucide {
1282
+ width: 18px;
1283
+ height: 18px;
1284
+ }
1285
+
1286
+ .input-more-context-body .context-picker-section-label {
1287
+ padding: 10px 16px 4px;
1140
1288
  }
1141
1289
 
1142
1290
  /* ==========================================================================
@@ -81,6 +81,13 @@
81
81
  }
82
82
  .notif-banner-icon .lucide { width: 16px; height: 16px; }
83
83
  .notif-banner-emoji { font-size: 18px; line-height: 1; }
84
+ .notif-banner-avatar {
85
+ width: 100%;
86
+ height: 100%;
87
+ object-fit: cover;
88
+ border-radius: 8px;
89
+ display: block;
90
+ }
84
91
 
85
92
  .notif-banner-body {
86
93
  flex: 1;
@@ -18,3 +18,50 @@
18
18
  }
19
19
 
20
20
  .tooltip.visible { opacity: 1; }
21
+
22
+ .tooltip.multi-line {
23
+ white-space: pre-line;
24
+ text-align: left;
25
+ max-width: 320px;
26
+ line-height: 1.5;
27
+ }
28
+
29
+ /* Context sources tooltip */
30
+ .ctx-tip-header {
31
+ font-size: 11px;
32
+ font-weight: 600;
33
+ text-transform: uppercase;
34
+ letter-spacing: 0.04em;
35
+ color: var(--text-muted);
36
+ margin-bottom: 6px;
37
+ }
38
+
39
+ .ctx-tip-row {
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 8px;
43
+ padding: 4px 0;
44
+ color: var(--text);
45
+ font-size: 12px;
46
+ }
47
+
48
+ .ctx-tip-row .lucide {
49
+ width: 14px;
50
+ height: 14px;
51
+ flex-shrink: 0;
52
+ color: var(--text-secondary);
53
+ }
54
+
55
+ .ctx-tip-favicon {
56
+ width: 14px;
57
+ height: 14px;
58
+ flex-shrink: 0;
59
+ border-radius: 2px;
60
+ }
61
+
62
+ .ctx-tip-row span {
63
+ overflow: hidden;
64
+ text-overflow: ellipsis;
65
+ white-space: nowrap;
66
+ max-width: 260px;
67
+ }
@@ -352,6 +352,7 @@
352
352
  <div class="status">
353
353
  <button id="debate-pdf-btn" class="hidden" title="Export debate as PDF"><i data-lucide="download"></i></button>
354
354
  <button id="find-in-session-btn" title="Search in session (Ctrl+F)"><i data-lucide="search"></i></button>
355
+ <button id="terminal-toggle-btn" title="Terminal"><i data-lucide="square-terminal"></i><span id="terminal-count" class="hidden"></span></button>
355
356
  </div>
356
357
  </div>
357
358
  <div id="main-panels">
@@ -459,13 +460,14 @@
459
460
  </div>
460
461
  <div id="input-bottom">
461
462
  <div id="attach-wrap">
462
- <button id="attach-file-btn" type="button" aria-label="Attach file" title="Attach file"><i data-lucide="paperclip"></i></button>
463
- <button id="attach-image-btn" type="button" aria-label="Attach image" title="Attach image"><i data-lucide="image"></i></button>
463
+ <button id="input-more-btn" type="button" aria-label="More options" title="More options" class="mobile-only"><i data-lucide="plus"></i></button>
464
+ <button id="attach-file-btn" type="button" aria-label="Attach file" title="Attach file" class="desktop-only"><i data-lucide="paperclip"></i></button>
465
+ <button id="attach-image-btn" type="button" aria-label="Attach image" title="Attach image" class="desktop-only"><i data-lucide="image"></i></button>
464
466
  <button id="stt-btn" type="button" aria-label="Voice input" title="Voice input"><i data-lucide="mic"></i></button>
465
467
  <button id="schedule-btn" type="button" aria-label="Schedule message" title="Schedule message"><i data-lucide="clock"></i></button>
466
468
  <button id="ask-mate-btn" type="button" aria-label="Ask Mate" title="Ask a Mate for advice on this session"><i data-lucide="at-sign"></i></button>
467
- <div id="context-sources-btn-wrap">
468
- <button id="context-sources-add" type="button" title="Add context sources"><i data-lucide="plus"></i><span class="ctx-label">Context</span></button>
469
+ <div id="context-sources-btn-wrap" class="desktop-only">
470
+ <button id="context-sources-add" type="button" title="Add context sources"><i data-lucide="library"></i><span class="ctx-label">Context</span></button>
469
471
  <div id="context-sources-picker" class="hidden">
470
472
  <div class="context-picker-section" id="context-picker-email"></div>
471
473
  <div class="context-picker-section" id="context-picker-terminals"></div>
@@ -542,6 +544,34 @@
542
544
  </div>
543
545
  </div>
544
546
  </div>
547
+ <!-- Mobile input-more bottom sheet: attach / image + context sources -->
548
+ <div id="input-more-sheet" class="hidden">
549
+ <div class="input-more-backdrop"></div>
550
+ <div class="input-more-content">
551
+ <div class="input-more-handle"></div>
552
+ <div class="input-more-actions">
553
+ <button class="input-more-action" id="input-more-attach">
554
+ <i data-lucide="paperclip"></i>
555
+ <span>Attach file</span>
556
+ </button>
557
+ <button class="input-more-action" id="input-more-image">
558
+ <i data-lucide="image"></i>
559
+ <span>Image</span>
560
+ </button>
561
+ </div>
562
+ <div class="input-more-divider"></div>
563
+ <div class="input-more-section-label">
564
+ <i data-lucide="library"></i>
565
+ <span>Context sources</span>
566
+ </div>
567
+ <div class="input-more-context-body" id="input-more-context-body">
568
+ <div class="context-picker-section" id="context-picker-email-mobile"></div>
569
+ <div class="context-picker-section" id="context-picker-terminals-mobile"></div>
570
+ <div class="context-picker-section" id="context-picker-tabs-mobile"></div>
571
+ </div>
572
+ </div>
573
+ </div>
574
+
545
575
  <!-- Mobile fullscreen sheet overlay (Projects / Sessions) -->
546
576
  <div id="mobile-sheet" class="hidden">
547
577
  <div class="mobile-sheet-backdrop"></div>
@@ -210,6 +210,8 @@ export function enterDmMode(key, targetUser, messages) {
210
210
  // Close file viewer and terminal panel BEFORE switching WS (needs old WS still open)
211
211
  try { closeFileViewer(); } catch(e) {}
212
212
  closeTerminal();
213
+ var termBtn = document.getElementById("terminal-toggle-btn");
214
+ if (termBtn) termBtn.style.display = "none";
213
215
  // Apply mate color to chat title bar and panels
214
216
  var mateColor = (targetUser.profile && targetUser.profile.avatarColor) || targetUser.avatarColor || "#7c3aed";
215
217
  document.body.style.setProperty("--mate-color", mateColor);
@@ -323,6 +325,9 @@ export function exitDmMode(skipProjectSwitch) {
323
325
  hideKnowledge();
324
326
  hideMemory();
325
327
  if (isSchedulerOpen()) closeScheduler();
328
+ // Restore terminal button
329
+ var termBtn = document.getElementById("terminal-toggle-btn");
330
+ if (termBtn) termBtn.style.display = "";
326
331
  // Reset DM header
327
332
  var dmHeaderBar = document.getElementById("dm-header-bar");
328
333
  if (dmHeaderBar) {
@@ -11,7 +11,7 @@ import { refreshIcons, iconHtml } from './icons.js';
11
11
  import { renderMarkdown } from './markdown.js';
12
12
  import { updatePageTitle } from './sidebar.js';
13
13
  import { renderSessionList, updateSessionPresence, populateCliSessionList, handleSearchResults, updateSessionBadge } from './sidebar-sessions.js';
14
- import { updateDmBadge, renderSidebarPresence, setMentionActive } from './sidebar-mates.js';
14
+ import { updateDmBadge, renderSidebarPresence, setMentionActive, renderUserStrip } from './sidebar-mates.js';
15
15
  import { refreshMobileChatSheet } from './sidebar-mobile.js';
16
16
  import { renderMateSessionList, handleMateSearchResults, updateMateSidebarProfile } from './mate-sidebar.js';
17
17
  import { renderKnowledgeList, handleKnowledgeContent } from './mate-knowledge.js';
@@ -369,9 +369,9 @@ export function processMessage(msg) {
369
369
 
370
370
  case "codex_config":
371
371
  store.set({
372
- codexApproval: msg.approval || "on-failure",
373
- codexSandbox: msg.sandbox || "workspace-write",
374
- codexWebSearch: msg.webSearch || "disabled",
372
+ codexApproval: msg.approval,
373
+ codexSandbox: msg.sandbox,
374
+ codexWebSearch: msg.webSearch,
375
375
  });
376
376
  break;
377
377
 
@@ -1171,6 +1171,7 @@ export function processMessage(msg) {
1171
1171
 
1172
1172
  case "projects_updated":
1173
1173
  updateProjectList(msg);
1174
+ renderUserStrip();
1174
1175
  break;
1175
1176
 
1176
1177
  case "project_owner_changed":