clay-server 2.47.0-beta.2 → 2.47.0-beta.4

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/project.js CHANGED
@@ -28,9 +28,9 @@ var { attachSessions } = require("./project-sessions");
28
28
  var { attachUserMessage } = require("./project-user-message");
29
29
  var { attachConnection } = require("./project-connection");
30
30
  var { attachMcp } = require("./project-mcp");
31
- var { attachMateDatastore } = require("./project-mate-datastore");
32
31
  var { createLocalMcp } = require("./mcp-local");
33
32
  var { attachEmail: attachEmailModule } = require("./project-email");
33
+ var { attachSessionSpawn } = require("./project-session-spawn");
34
34
  // project-notifications is attached globally in server.js, passed via opts.notificationsModule
35
35
 
36
36
  // --- Context Sources persistence ---
@@ -476,23 +476,34 @@ function createProjectContext(opts) {
476
476
  },
477
477
  });
478
478
 
479
- // --- Mate datastore (Mate projects only) ---
480
- var _mateDatastore = attachMateDatastore({
479
+ // The SDK bridge is created after local MCP servers. Session spawning uses
480
+ // a getter so tool handlers see the initialized bridge when they run.
481
+ var sdk = null;
482
+ var _sessionSpawn = attachSessionSpawn({
481
483
  cwd: cwd,
482
- slug: slug,
483
- isMate: isMate,
484
+ sm: sm,
485
+ getSdk: function () { return sdk; },
484
486
  send: send,
485
- sendTo: sendTo,
486
- clients: clients,
487
- getSessionForWs: getSessionForWs,
487
+ isMate: isMate,
488
488
  usersModule: usersModule,
489
- getProjectOwnerId: function () { return projectOwnerId; },
489
+ adapters: adapters,
490
+ getLinuxUserForSession: getLinuxUserForSession,
490
491
  });
491
492
 
492
493
  // --- MCP tool servers (created via YOKE adapter) ---
493
494
  var mcpServers = (function () {
494
495
  var servers = {};
495
496
 
497
+ // Agent-driven sibling session fan-out (main projects only).
498
+ if (!isMate) {
499
+ try {
500
+ var sessionSpawnMcpConfig = _sessionSpawn.createMcpServer(adapter);
501
+ if (sessionSpawnMcpConfig) servers[sessionSpawnMcpConfig.name || "clay-sessions"] = sessionSpawnMcpConfig;
502
+ } catch (e) {
503
+ console.error("[project] Failed to create session spawn MCP server:", e.message);
504
+ }
505
+ }
506
+
496
507
  // Debate MCP server (available to both mates and main project)
497
508
  try {
498
509
  var debateMcp = require("./debate-mcp-server");
@@ -643,15 +654,6 @@ function createProjectContext(opts) {
643
654
  console.error("[project] Failed to create email MCP server:", e.message);
644
655
  }
645
656
 
646
- if (isMate) {
647
- try {
648
- var datastoreMcp = _mateDatastore.createMcpServer();
649
- if (datastoreMcp) servers[datastoreMcp.name || "clay-datastore"] = datastoreMcp;
650
- } catch (e) {
651
- console.error("[project] Failed to create datastore MCP server:", e.message);
652
- }
653
- }
654
-
655
657
  return Object.keys(servers).length > 0 ? servers : undefined;
656
658
  })();
657
659
 
@@ -663,7 +665,12 @@ function createProjectContext(opts) {
663
665
  //
664
666
  // clay-browser -> only when the Chrome extension is connected
665
667
  // clay-email -> only when the user has an account or server SMTP
666
- function getLocalMcpServers() {
668
+ //
669
+ // forSession (optional): the session whose query these servers are mounted
670
+ // into. clay-sessions must know its caller (depth guard + child ownership),
671
+ // so it is re-instantiated bound to that session; the static instance in
672
+ // mcpServers only serves descriptor listing and fails closed on calls.
673
+ function getLocalMcpServers(forSession) {
667
674
  if (!mcpServers) return undefined;
668
675
  var extWs = browserState._extensionWs;
669
676
  var extConnected = !!(extWs && extWs.readyState === 1);
@@ -675,6 +682,15 @@ function createProjectContext(opts) {
675
682
  var name = keys[i];
676
683
  if (name === "clay-browser" && !extConnected) continue;
677
684
  if (name === "clay-email" && !emailAvailable) continue;
685
+ if (name === "clay-sessions" && forSession) {
686
+ try {
687
+ var boundSpawn = _sessionSpawn.createMcpServer(adapter, forSession);
688
+ if (boundSpawn) { filtered[name] = boundSpawn; hasAny = true; }
689
+ } catch (e) {
690
+ console.error("[project] Failed to bind session spawn MCP server:", e.message);
691
+ }
692
+ continue;
693
+ }
678
694
  filtered[name] = mcpServers[name];
679
695
  hasAny = true;
680
696
  }
@@ -682,7 +698,7 @@ function createProjectContext(opts) {
682
698
  }
683
699
 
684
700
  // --- SDK bridge ---
685
- var sdk = createSDKBridge({
701
+ sdk = createSDKBridge({
686
702
  cwd: cwd,
687
703
  slug: slug,
688
704
  sessionManager: sm,
@@ -914,18 +930,25 @@ function createProjectContext(opts) {
914
930
  clayAuthToken: serverAuthToken,
915
931
  slug: slug,
916
932
  });
917
- } else if ((!sm.modelsByVendor || !sm.modelsByVendor[msg.vendor]) && typeof vendorAdapter.init === "function") {
933
+ }
934
+ var needsReadyMetadata = !sm.capabilitiesByVendor || !sm.capabilitiesByVendor[msg.vendor]
935
+ || !sm.modelsByVendor || !sm.modelsByVendor[msg.vendor];
936
+ if (vendorAdapter && needsReadyMetadata && typeof vendorAdapter.init === "function") {
918
937
  // Init warms the adapter, but a slow/failed init must not block
919
938
  // model listing (e.g. Codex models are a fixed list). Keep going
920
939
  // to supportedModels() even if init throws.
921
940
  try {
922
- await vendorAdapter.init({
941
+ var readyResult = await vendorAdapter.init({
923
942
  cwd: cwd,
924
943
  clayPort: serverPort,
925
944
  clayTls: serverTls,
926
945
  clayAuthToken: serverAuthToken,
927
946
  slug: slug,
928
947
  });
948
+ sm.capabilitiesByVendor = sm.capabilitiesByVendor || {};
949
+ sm.capabilitiesByVendor[msg.vendor] = readyResult.capabilities || {};
950
+ sm.modelsByVendor = sm.modelsByVendor || {};
951
+ if (Array.isArray(readyResult.models)) sm.modelsByVendor[msg.vendor] = readyResult.models;
929
952
  } catch (e) {
930
953
  console.error("[project] " + msg.vendor + " init failed (continuing to model list):", e.message || e);
931
954
  }
@@ -957,7 +980,8 @@ function createProjectContext(opts) {
957
980
  }
958
981
  }
959
982
  }
960
- sendTo(ws, { type: "model_info", model: modelToSend, models: vendorModels, vendor: msg.vendor, availableVendors: sm.availableVendors || [], installedVendors: sm.installedVendors || [] });
983
+ var vendorCapabilities = (sm.capabilitiesByVendor && sm.capabilitiesByVendor[msg.vendor]) || {};
984
+ sendTo(ws, { type: "model_info", model: modelToSend, models: vendorModels, vendor: msg.vendor, capabilities: vendorCapabilities, availableVendors: sm.availableVendors || [], installedVendors: sm.installedVendors || [] });
961
985
  })();
962
986
  return;
963
987
  }
@@ -1020,9 +1044,6 @@ function createProjectContext(opts) {
1020
1044
  // --- MCP bridge (remote MCP servers via extension) ---
1021
1045
  if (_mcp.handleMcpMessage(ws, msg)) return;
1022
1046
 
1023
- // --- Mate datastore ---
1024
- if (_mateDatastore.handleMateDatastoreMessage(ws, msg)) return;
1025
-
1026
1047
  // --- Knowledge file management (delegated to project-knowledge.js) ---
1027
1048
  if (_knowledge.handleKnowledgeMessage(ws, msg)) return;
1028
1049
 
@@ -1483,9 +1504,6 @@ function createProjectContext(opts) {
1483
1504
  function destroy() {
1484
1505
  _loop.stopTimer();
1485
1506
  _email.destroy();
1486
- if (_mateDatastore && typeof _mateDatastore.closeAllDatastores === "function") {
1487
- try { _mateDatastore.closeAllDatastores(); } catch (e) {}
1488
- }
1489
1507
  stopFileWatch();
1490
1508
  stopAllDirWatches();
1491
1509
  // Abort all active sessions and clean up mention sessions
package/lib/public/app.js CHANGED
@@ -18,7 +18,6 @@ import {
18
18
  openMobileSheet, setMobileSheetMateData, refreshMobileChatSheet
19
19
  } from './modules/sidebar-mobile.js';
20
20
  import { initMateSidebar, showMateSidebar, hideMateSidebar, renderMateSessionList, updateMateSidebarProfile, handleMateSearchResults } from './modules/mate-sidebar.js';
21
- import { initMateDatastoreUI } from './modules/mate-datastore-ui.js';
22
21
  import { initMateKnowledge, requestKnowledgeList, renderKnowledgeList, handleKnowledgeContent, hideKnowledge } from './modules/mate-knowledge.js';
23
22
  import { initMateMemory, renderMemoryList, hideMemory } from './modules/mate-memory.js';
24
23
  import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, addRewindButton, onRewindComplete, onRewindError } from './modules/rewind.js';
@@ -312,6 +311,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
312
311
  currentModels: [],
313
312
  // Project's last-used vendor; seeds the sidebar's "New session" button.
314
313
  lastVendor: "",
314
+ // Static adapter metadata sent before any vendor is initialized.
315
+ vendorInfo: {},
315
316
  // How Claude sessions open: "gui" (default) or "tui". The server sends
316
317
  // claude_open_mode_changed on connect; this seeds it beforehand so the
317
318
  // new-session menu doesn't flash the TUI-only entry.
@@ -414,7 +415,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
414
415
  initSidebar(sidebarCtx);
415
416
  var wsGetter = function () { return _getWsRef(); };
416
417
  initMateSidebar(wsGetter);
417
- initMateDatastoreUI(wsGetter);
418
418
  initMateKnowledge(wsGetter);
419
419
  initMateMemory(wsGetter, { onShow: function () { hideKnowledge(); hideNotes(); } });
420
420
  initMateWizard(
@@ -2011,202 +2011,6 @@ body.mate-dm-active #layout.sidebar-collapsed .mate-collapsed-info {
2011
2011
  color: var(--accent);
2012
2012
  }
2013
2013
 
2014
- /* Mate datastore inspector */
2015
- #mate-datastore-panel {
2016
- position: absolute;
2017
- inset: 0;
2018
- z-index: 40;
2019
- background: var(--bg);
2020
- display: flex;
2021
- flex-direction: column;
2022
- overflow: hidden;
2023
- }
2024
-
2025
- #mate-datastore-panel.hidden {
2026
- display: none !important;
2027
- }
2028
-
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 {
2032
- display: none !important;
2033
- }
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
-
2067
- .mate-db-status {
2068
- padding: 10px 14px 0;
2069
- font-size: 12px;
2070
- color: var(--text-secondary, #8e8e8e);
2071
- }
2072
-
2073
- .mate-db-status[data-kind="error"] {
2074
- color: var(--error, #ff6b6b);
2075
- }
2076
-
2077
- .mate-db-status[data-kind="warn"] {
2078
- color: var(--warning, #f5a524);
2079
- }
2080
-
2081
- .mate-db-status[data-kind="ok"] {
2082
- color: var(--success, #36b37e);
2083
- }
2084
-
2085
- .mate-db-layout {
2086
- display: grid;
2087
- grid-template-columns: 220px minmax(0, 1fr);
2088
- gap: 10px;
2089
- padding: 12px 10px 10px;
2090
- min-height: 0;
2091
- flex: 1;
2092
- }
2093
-
2094
- .mate-db-table-column,
2095
- .mate-db-detail {
2096
- min-height: 0;
2097
- display: flex;
2098
- flex-direction: column;
2099
- }
2100
-
2101
- .mate-db-table-list {
2102
- overflow-y: auto;
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;
2107
- flex: 1;
2108
- }
2109
-
2110
- .mate-db-table-item {
2111
- width: 100%;
2112
- display: flex;
2113
- justify-content: space-between;
2114
- gap: 6px;
2115
- padding: 8px 10px;
2116
- border: none;
2117
- background: transparent;
2118
- color: var(--text-secondary, #aaa);
2119
- border-radius: 8px;
2120
- text-align: left;
2121
- cursor: pointer;
2122
- }
2123
-
2124
- .mate-db-table-item:hover,
2125
- .mate-db-table-item.active {
2126
- background: var(--sidebar-active, rgba(255,255,255,0.08));
2127
- color: var(--text, #fff);
2128
- }
2129
-
2130
- .mate-db-table-item-name {
2131
- overflow: hidden;
2132
- text-overflow: ellipsis;
2133
- white-space: nowrap;
2134
- min-width: 0;
2135
- }
2136
-
2137
- .mate-db-table-item-type {
2138
- font-size: 10px;
2139
- text-transform: uppercase;
2140
- letter-spacing: 0.05em;
2141
- opacity: 0.7;
2142
- flex-shrink: 0;
2143
- }
2144
-
2145
- .mate-db-empty {
2146
- padding: 16px 10px;
2147
- color: var(--text-secondary, #8e8e8e);
2148
- font-size: 12px;
2149
- }
2150
-
2151
- .mate-db-section-title {
2152
- font-size: 11px;
2153
- font-weight: 700;
2154
- text-transform: uppercase;
2155
- letter-spacing: 0.08em;
2156
- color: var(--text-muted, #9aa0a6);
2157
- margin: 0 0 6px;
2158
- }
2159
-
2160
- .mate-db-detail {
2161
- gap: 8px;
2162
- min-width: 0;
2163
- }
2164
-
2165
- .mate-db-table-schema,
2166
- .mate-db-result {
2167
- margin: 0;
2168
- padding: 10px;
2169
- border-radius: 10px;
2170
- border: 1px solid var(--border-subtle, rgba(255,255,255,0.08));
2171
- background: var(--bg-alt, rgba(255,255,255,0.03));
2172
- color: var(--text-secondary, #b8b8b8);
2173
- font-size: 11px;
2174
- line-height: 1.5;
2175
- overflow: auto;
2176
- white-space: pre-wrap;
2177
- word-break: break-word;
2178
- min-height: 0;
2179
- }
2180
-
2181
- .mate-db-table-schema {
2182
- max-height: 180px;
2183
- }
2184
-
2185
- @media (max-width: 900px) {
2186
- .mate-db-layout {
2187
- grid-template-columns: 1fr;
2188
- }
2189
- }
2190
-
2191
- body.mate-dm-active .mate-datastore-top-bar {
2192
- background: var(--mate-color);
2193
- color: #fff;
2194
- }
2195
-
2196
- body.mate-dm-active .mate-datastore-top-title,
2197
- body.mate-dm-active .mate-datastore-top-title .lucide {
2198
- color: #fff;
2199
- }
2200
-
2201
- body.mate-dm-active .mate-datastore-top-bar .scheduler-close-btn {
2202
- color: rgba(255, 255, 255, 0.85);
2203
- }
2204
-
2205
- body.mate-dm-active .mate-datastore-top-bar .scheduler-close-btn:hover {
2206
- color: #fff;
2207
- background: rgba(255, 255, 255, 0.15);
2208
- }
2209
-
2210
2014
  .mate-session-item.search-match {
2211
2015
  background: var(--accent-12, rgba(108, 92, 231, 0.12));
2212
2016
  }
@@ -14,7 +14,6 @@ import { renderSessionList, updateSessionPresence, handleSearchResults, updateSe
14
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
- import { handleMateDatastoreTablesResult, handleMateDatastoreDescribeResult, handleMateDatastoreQueryResult, handleMateDatastoreError, handleMateDatastoreChange } from './mate-datastore-ui.js';
18
17
  import { handleHomeClayHistory, handleHomeClayDelta, handleHomeClayDone, handleHomeClayError } from './home-chat.js';
19
18
  import { renderKnowledgeList, handleKnowledgeContent } from './mate-knowledge.js';
20
19
  import { renderMemoryList } from './mate-memory.js';
@@ -289,7 +288,7 @@ export function processMessage(msg) {
289
288
  // host lives on document.body (it's position: fixed), so it
290
289
  // survives project navigation unless we detach explicitly here.
291
290
  detachTuiView();
292
- store.set({ projectName: msg.project || msg.cwd });
291
+ store.set({ projectName: msg.project || msg.cwd, vendorInfo: msg.vendors || {} });
293
292
  if (msg.cwd) store.set({ cwd: msg.cwd });
294
293
  if (msg.slug) store.set({ currentSlug: msg.slug });
295
294
  try { var _is = store.snap(); localStorage.setItem("clay-project-name-" + (_is.currentSlug || "default"), _is.projectName); } catch (e) {}
@@ -327,26 +326,6 @@ export function processMessage(msg) {
327
326
  updateProjectList(msg);
328
327
  break;
329
328
 
330
- case "mate_db_tables_result":
331
- handleMateDatastoreTablesResult(msg);
332
- break;
333
-
334
- case "mate_db_describe_result":
335
- handleMateDatastoreDescribeResult(msg);
336
- break;
337
-
338
- case "mate_db_query_result":
339
- handleMateDatastoreQueryResult(msg);
340
- break;
341
-
342
- case "mate_db_error":
343
- handleMateDatastoreError(msg);
344
- break;
345
-
346
- case "mate_db_change":
347
- handleMateDatastoreChange(msg);
348
- break;
349
-
350
329
  case "update_available":
351
330
  // In multi-user mode, only show update UI to admins
352
331
  if (store.get('isMultiUserMode')) {
@@ -464,6 +443,7 @@ export function processMessage(msg) {
464
443
  if (msg.vendor && !store.get('vendorSelectionLocked')) _miUpdate.currentVendor = msg.vendor;
465
444
  if (msg.availableVendors) _miUpdate.availableVendors = msg.availableVendors;
466
445
  if (msg.installedVendors) _miUpdate.installedVendors = msg.installedVendors;
446
+ if (msg.capabilities) _miUpdate.vendorCapabilities = msg.capabilities;
467
447
  store.set(_miUpdate);
468
448
  updateSettingsModels(_modelVal, msg.models || []);
469
449
  break;
@@ -1359,7 +1339,7 @@ export function processMessage(msg) {
1359
1339
  var _lm = store.get('pendingLoginModal');
1360
1340
  store.set({ pendingLoginModal: null });
1361
1341
  openTuiModal(msg.id, _lm.slug, {
1362
- sessionTitle: (_lm.vendor === "codex" ? "Codex" : "Claude") + " login",
1342
+ sessionTitle: (VENDOR_NAMES[_lm.vendor] || "Claude Code") + " login",
1363
1343
  projectName: _lm.slug,
1364
1344
  compact: true,
1365
1345
  });
@@ -19,6 +19,14 @@ var bannerContainer = null;
19
19
  var bellBtn = null;
20
20
  var badgeEl = null;
21
21
 
22
+ function getVendorLoginCommand(vendor) {
23
+ var vendors = store.get('vendorInfo') || {};
24
+ var info = vendors[vendor];
25
+ if (info && info.loginCommand) return info.loginCommand;
26
+ var fallbacks = { codex: "codex login --device-auth", claude: "claude login" };
27
+ return fallbacks[vendor] || fallbacks.claude;
28
+ }
29
+
22
30
  // --- Pending TUI attention tracking ---
23
31
  // Mirrors the icon-shake / favicon-blink behavior the SDK side already gets
24
32
  // from `pendingPermissions` on project status broadcasts. The notification
@@ -252,7 +260,7 @@ function showBanner(notif, autoDismissMs) {
252
260
  removeBanner(banner);
253
261
  dismissNotif(notif.id);
254
262
  var authMeta = notif.meta || {};
255
- startLoginInModal(authMeta.loginCommand || ((authMeta.vendor || "claude") === "codex" ? "codex login --device-auth" : "claude login"), authMeta.vendor || "claude");
263
+ startLoginInModal(authMeta.loginCommand || getVendorLoginCommand(authMeta.vendor || "claude"), authMeta.vendor || "claude");
256
264
  showLoginReminderBanner();
257
265
  });
258
266
  }
@@ -332,7 +340,7 @@ function startLoginInModal(loginCommand, vendor) {
332
340
  if (authReminderVisible) return;
333
341
  var ws = getWs();
334
342
  if (!ws || ws.readyState !== 1) return;
335
- var cmd = loginCommand || (vendor === "codex" ? "codex login --device-auth" : "claude login");
343
+ var cmd = loginCommand || getVendorLoginCommand(vendor);
336
344
  var slug = currentProjectSlug();
337
345
  if (!slug) { startLoginCommand(cmd); return; }
338
346
  store.set({ pendingLoginModal: { slug: slug, vendor: vendor || "claude" } });
@@ -366,7 +374,7 @@ export function autoStartLoginIfNeeded(msg) {
366
374
  if (authReminderVisible) return false;
367
375
  var vendor = msg.vendor || "claude";
368
376
  var cmd = msg.loginCommand
369
- || (vendor === "codex" ? "codex login --device-auth" : "claude login");
377
+ || getVendorLoginCommand(vendor);
370
378
  startLoginInModal(cmd, vendor);
371
379
  showLoginReminderBanner();
372
380
  return true;
@@ -426,7 +434,7 @@ function showLoginReminderBanner() {
426
434
  export function showAuthRequiredBanner(msg) {
427
435
  if (!bannerContainer) return;
428
436
  var vendor = (msg && (msg.vendor || (msg.meta && msg.meta.vendor))) || "claude";
429
- var loginCommand = (msg && (msg.loginCommand || (msg.meta && msg.meta.loginCommand))) || (vendor === "codex" ? "codex login --device-auth" : "claude login");
437
+ var loginCommand = (msg && (msg.loginCommand || (msg.meta && msg.meta.loginCommand))) || getVendorLoginCommand(vendor);
430
438
  activeAuthRequiredMsg = Object.assign({}, msg || {}, {
431
439
  id: (msg && msg.id) || ("_auth_" + Date.now()),
432
440
  type: "auth_required",
@@ -93,7 +93,7 @@ var EFFORT_LEVELS_BY_VENDOR = {
93
93
  var THINKING_OPTIONS = ["disabled", "adaptive", "budget"];
94
94
  var CODEX_APPROVAL_OPTIONS = [
95
95
  { value: "never", label: "Auto" },
96
- { value: "on-failure", label: "On Fail" },
96
+ { value: "untrusted", label: "Untrusted" },
97
97
  { value: "on-request", label: "Ask" },
98
98
  ];
99
99
  var CODEX_SANDBOX_OPTIONS = [
@@ -641,11 +641,14 @@ export function updateConfigChip() {
641
641
  rebuildModeList();
642
642
  rebuildEffortBar();
643
643
 
644
- // Vendor-specific sections
644
+ // MODE remains Claude-specific until adapter modes become capabilities.
645
645
  var isClaude = vendor === "claude";
646
- // MODE, THINKING, BETA are Claude-only
647
646
  if (configModeList && configModeList.parentElement) configModeList.parentElement.style.display = isClaude ? "" : "none";
648
647
  rebuildThinkingSection();
648
+ // capabilities.thinking means "emits a thinking stream" (true for every
649
+ // vendor), NOT "accepts thinking config". The adaptive/extended toggle and
650
+ // budget only feed Claude queries (sm.currentThinking), so the section
651
+ // stays Claude-only until a dedicated thinkingConfig capability exists.
649
652
  if (configThinkingSection) configThinkingSection.style.display = isClaude ? "" : "none";
650
653
  // BETA section deprecated (1M context is now standard)
651
654
  if (configBetaSection) configBetaSection.style.display = "none";
@@ -23,20 +23,37 @@ var fastModeIndicatorEl = null;
23
23
  // --- Internal helpers ---
24
24
 
25
25
  function getVendorUsageMeta(vendor) {
26
- if (vendor === "codex") {
27
- return {
26
+ var vendors = store.get('vendorInfo') || {};
27
+ var info = vendors[vendor];
28
+ if (info && info.usageDashboard) return info.usageDashboard;
29
+ var fallbacks = {
30
+ codex: {
28
31
  icon: "/codex-avatar.png",
29
32
  alt: "Codex",
30
33
  href: "https://chatgpt.com/admin/usage",
31
34
  title: "Check usage on ChatGPT",
32
- };
33
- }
34
- return {
35
- icon: "/claude-code-avatar.png",
36
- alt: "Claude Code",
37
- href: "https://claude.ai/settings/usage",
38
- title: "Check usage on claude.ai",
35
+ },
36
+ claude: {
37
+ icon: "/claude-code-avatar.png",
38
+ alt: "Claude Code",
39
+ href: "https://claude.ai/settings/usage",
40
+ title: "Check usage on claude.ai",
41
+ },
39
42
  };
43
+ return fallbacks[vendor] || fallbacks.claude;
44
+ }
45
+
46
+ function vendorTracksRateLimits(vendor) {
47
+ var vendors = store.get('vendorInfo') || {};
48
+ var info = vendors[vendor];
49
+ if (info) return info.rateLimitTracking !== false;
50
+ var legacyTrackedVendors = ["claude", "codex"];
51
+ return legacyTrackedVendors.indexOf(vendor) !== -1;
52
+ }
53
+
54
+ function vendorSupportsScheduledMessages(vendor) {
55
+ var scheduledMessageVendors = ["claude"];
56
+ return scheduledMessageVendors.indexOf(vendor) !== -1;
40
57
  }
41
58
 
42
59
  function rateLimitTypeLabel(type) {
@@ -178,8 +195,13 @@ function tickRateLimitUsage() {
178
195
 
179
196
  export function initRateLimit() {
180
197
  store.subscribe(function(state, prev) {
181
- if (state.currentVendor !== prev.currentVendor && state.currentVendor && state.currentVendor !== "claude") {
182
- clearScheduleDelay();
198
+ if (state.currentVendor !== prev.currentVendor && state.currentVendor) {
199
+ if (!vendorSupportsScheduledMessages(state.currentVendor)) clearScheduleDelay();
200
+ }
201
+ if (state.currentVendor !== prev.currentVendor || state.vendorInfo !== prev.vendorInfo) {
202
+ if (rateLimitUsageEl) {
203
+ rateLimitUsageEl.style.display = vendorTracksRateLimits(state.currentVendor || "claude") ? "" : "none";
204
+ }
183
205
  }
184
206
  });
185
207
  }
@@ -203,7 +225,7 @@ export function handleRateLimitEvent(msg) {
203
225
  if (rateLimitResetTimer) clearTimeout(rateLimitResetTimer);
204
226
  // Auto-switch input to schedule mode: any message typed will be queued for after reset
205
227
  var delayUntilReset = msg.resetsAt - Date.now();
206
- if (delayUntilReset > 0 && (store.get('currentVendor') || "claude") === "claude") {
228
+ if (delayUntilReset > 0 && vendorSupportsScheduledMessages(store.get('currentVendor') || "claude")) {
207
229
  setScheduleDelayMs(delayUntilReset + 60000); // +1min buffer after reset
208
230
  }
209
231
  rateLimitResetTimer = setTimeout(function () {
@@ -222,6 +244,11 @@ export function handleRateLimitEvent(msg) {
222
244
  }
223
245
 
224
246
  export function updateRateLimitUsage(msg) {
247
+ var activeVendor = store.get('currentVendor') || "claude";
248
+ if (!vendorTracksRateLimits(activeVendor)) {
249
+ if (rateLimitUsageEl) rateLimitUsageEl.style.display = "none";
250
+ return;
251
+ }
225
252
  if (msg.rateLimitType && msg.resetsAt) {
226
253
  rateLimitResetState[msg.rateLimitType] = { resetsAt: msg.resetsAt, status: msg.status };
227
254
  }
@@ -238,6 +265,7 @@ export function updateRateLimitUsage(msg) {
238
265
  var ref = document.getElementById("skip-perms-pill");
239
266
  topBarActions.insertBefore(rateLimitUsageEl, ref);
240
267
  }
268
+ rateLimitUsageEl.style.display = "";
241
269
 
242
270
  // Build label from available reset times
243
271
  var parts = [];
@@ -251,7 +279,7 @@ export function updateRateLimitUsage(msg) {
251
279
  }
252
280
 
253
281
  var label = parts.length > 0 ? parts.join(" · ") : "Check usage";
254
- var vendor = store.get('currentVendor') || "claude";
282
+ var vendor = activeVendor;
255
283
  var meta = getVendorUsageMeta(vendor);
256
284
  rateLimitUsageEl.href = meta.href;
257
285
  rateLimitUsageEl.title = meta.title;
@@ -14,6 +14,8 @@ import { sendMessage, hasSendableContent } from './input.js';
14
14
  import { getChatLayout } from './theme.js';
15
15
  import { getScheduledMsgEl } from './app-rate-limit.js';
16
16
 
17
+ // Keep these client constants aligned with lib/yoke/vendor-registry.js. The
18
+ // browser cannot import the server's CommonJS registry directly.
17
19
  export var VENDOR_AVATARS = {
18
20
  claude: "/claude-code-avatar.png",
19
21
  codex: "/codex-avatar.png",
@@ -5,6 +5,7 @@ import { checkForMention, showMentionMenu, hideMentionMenu, isMentionMenuVisible
5
5
  import { store } from './store.js';
6
6
  import { mateAvatarUrl } from './avatar.js';
7
7
  import { tuiIsActive, tuiSubmitText } from './session-tui-view.js';
8
+ import { VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
8
9
 
9
10
  var ctx;
10
11
 
@@ -300,11 +301,9 @@ export function sendMessage() {
300
301
  _vtw2.classList.remove("locked");
301
302
  }
302
303
  if (_avi && _avIcon) {
303
- var _vendorAvatars = { claude: "/claude-code-avatar.png", codex: "/codex-avatar.png", kiro: "/kiro-avatar.svg" };
304
- var _vendorNames = { claude: "Claude", codex: "Codex", kiro: "Kiro CLI" };
305
- _avIcon.src = _vendorAvatars[_committedVendor] || _vendorAvatars.claude;
306
- _avIcon.alt = _vendorNames[_committedVendor] || _vendorNames.claude;
307
- _avi.title = (_vendorNames[_committedVendor] || _vendorNames.claude) + " session";
304
+ _avIcon.src = VENDOR_AVATARS[_committedVendor] || VENDOR_AVATARS.claude;
305
+ _avIcon.alt = VENDOR_NAMES[_committedVendor] || VENDOR_NAMES.claude;
306
+ _avi.title = (VENDOR_NAMES[_committedVendor] || VENDOR_NAMES.claude) + " session";
308
307
  _avi.classList.remove("hidden");
309
308
  }
310
309
  } else if (_vtw2) {